XSS基础学习
By:Mirror王宇阳
什么是XSS
XSS攻击是指在网页中嵌入一段恶意的客户端Js脚本代码片段,JS脚本恶意代码可以获取用户的Cookie、URL跳转、内容篡改、会话劫持……等。
xss攻击手段本身对服务端没有直接的危害,xss主要是借助网站传播;一般通过留言板、邮件、等其他途径向受害者发送一段恶意的URL,受害者通过访问该恶意URL可能会导致恶意的xss脚步会在受害者的客户端浏览器中执行,实现自己的目的
XSS的攻击类别分为:反射型、存储型、DOM型等三大类攻击类别。
反射型XSS
反射型XSS会把用户输入的数据直接返回给页面,是一种非持久型攻击;这类型的xss是最为常见的,主要的利用方法就是恶意脚本添加到参数(URL)发送给用户诱骗用户点击后反射数据给页面。
-
页面源码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>XSS | 反射型</title> </head> <body> <form action="xss.php" method="GET"> <h2>xss反射型注入攻击测试</h2> <span>测试语句:</span> <input type="text" name="name"> <input type="submit" value="提交"> </form> </body> </html>
-
后台源码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Text page | Return</title> </head> <body> <h2>反射型测试页面</h2> <?php echo $_GET["name"]; ?> </body> </html>
-
源码分析
我们输入的内容会被执行并嵌入在HTML页面中;$_GET['name']会触发js恶意代码并嵌入HTML页面中。
-
测试
- 正常页面
-
XSS测试
-
结果发现:我们在输入text框中写入了一个Js代码,代码直接被执行并嵌入在HTML页面中;众所周知,Js代码和HTML代码直接暴露在客户端,一旦写入的Js代码可以被执行并嵌入在HTML页面中即视为存在XSS攻击。
-
恶意利用
我们通过向受害者发送如下的恶意URL就可以实现在用户客户端执行该恶意js脚本;
http://127.0.0.1/xss/xss.php?name=<script>alert('XSS测试结果');</script>
攻击者还会将URL进行各式各样的加密转换的处理,最大程度的减少URL恶意脚本的暴露;
存储型XSS
存储型XSS是一种持久的xss攻击类别,攻击者将恶意脚本植入到服务端数据库或长期的嵌入在HTML页面中;当用户符合触发条件后就会触发Js的xss恶意脚本。
存储型的xss通常会存储在客户端或数据库中,当用户访问页面即触发xss。
存储型的xss不需要构造URL诱骗用户去点击,大大的减少暴露和增加隐秘性。
-
创建数据库和表
create table text( uid int(10) not null auto_increment primary key, title varchar(20) null, content text(100) null )engine=innodb default charset=utf8;
-
HTML页面源码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>xss | 存储型</title> </head> <body> <form action="xss_storage.php" method="POST"> <span> <h2>xss|存储型测试</h2> <h3>留言板测试</h3> </span> 主题:<input type="text" name="title"></br> 留言:<textarea name="content"> </textarea><!-- 文本域留言 --> <input type="submit" name="提交"> </form> </body> </html>
-
PHP后台源码
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>xss | 存储型</title> </head> <body> <!-- create databases xss_text 创建数据库--> <?php $conn = mysqli_connect("127.0.0.1","root","root","xss_text") or die("数据库连接错误".mysqli_error()); // set names utf-8 -- 写入数据库采用的编码(utf-8) mysqli_query($conn, 'set names utf-8'); if (isset($_POST["title"])) { $title = $_POST["title"]; $content = $_POST["content"]; //向数据库添加 title content 两字段的内容 $sql = "INSERT INTO `xss_text`.`text` (`uid`, `title`, `content`) VALUES (NULL, '$title', '$content')"; echo $sql; $result_1 = mysqli_query($conn,$sql); // 页面回显/查询数据 $result_2 = mysqli_query($conn , "select * from text"); echo "<table boreder='1'><tr><td>标题</td><td>内容</td></tr>"; while($row = mysqli_fetch_array($result_2, MYSQLI_BOTH)) { echo "<tr><td>" . $row['title']. "</td><td>" .$row['content']. "</td>"; } echo "</table>"; echo $row['title']; } ?> </body> </html>
-
分析
当用户在content写入一个可执行恶意js脚本的标签即可过程xss例如:
<input type="button" value="xss" onclick="alert(xss)"/>
,提交查询后内容就会写入在数据库中,在数据库的查询结果回显至页面后就可以触发了,这里举两个例子,一个是手动触发,一个是自动加载触发。 -
测试
这是写入一个input标签,鼠标点击事件可执行一个js脚本即一个弹窗。
这里是写入了一个img标签但是标签src无索引会错误error,由此触发onerror属性执行js;也可以使用其他类似于onload属性……
测试过程中发现单引号无法存入数据库,原因本小白也是半懂不懂;在sql执行的写入的时候单引号会被转义,对此可以尝试双单引号来实现最后也会以单引号的语句保存在表中。
DOM型xss
基于DOM的XSS攻击手段,效果上和反射型XSS类似;通过修改页面的DOM节点形成XSS。
DOM规定:
-
一个文档就是一个文档节点
-
每个HTML标签就是一个元素节点
-
包含在HTML元素中的文本是文本节点
-
每一个HTML属性是一个属性节点
-
节点与节点之间都有等级关系
-
测试源码
<!DOCTYPE html> <head> <meta charset='utf-8'> <title>xss | DOM</title> </head> <body> <h1> XSS - DOM攻击测试页面 </h1> <script type="text/javascript"> function xss() { var str = document.getElementById("xss").value; document.getElementById("demo").innerHTML = "<a href = '"+str+"'>超链接</a>"; } </script> <div id = "demo"> 测试区域 </div> <div> xss测试: <input type="text" id="xss" /> <input type="button" id="a" value="提交" onclick="xss()" /> </div> </body>
-
分析
当我们想id=xss提交数据后,提交的内容则会作为a标签的href属性被写入在HTML页面中;而攻击者则采用闭合拼接的方式来构成恶意的xss
-
测试
-
构造恶意的闭合xss
<a href = "" onclick=alert("Hello,World!") >超链接</a> xss 提交: " onclick=alert("Hello,World!")
这样的xss构造语句会向测试区域发送恶意的构造标签实现攻击目的
-
XSS常见测试语句
<script> alert(1) </script>
<img src=x onerror = alert(1) />
<svg onload=alert(1)>
<a href=javascript:alert(1)>
XSS绕过思路
- Js编码
JS提供了四种字符编码的策略
-
三个八进制数字,空位补0,
***
-
两个十六进制数,空位补0,
x**
-
四个十六进制数,空位补0,
u****
-
控制字符,例如 , h ,
-
HTML实体编码
命名实体:命名以 “&” 开头,分号结尾;参考:实体编码字符
字符编码:十进制、十六进制ASCII编码或Unicode字符编码
- URL编码
在线工具:http://tool.chinaz.com/tools/urlencode.aspx
线下工具:URL编码解码工具(Burp-Decoder)
XSS检测
使用手动检测可以最大精确化,但是对于大型的web应用是困难的是;最首要的重要就是哪里有输入、输入的结果输出的地方。
手工检测XSS要使用特殊意义的字符,这样可以快速的测试是否存在XSS;
-
可得知输出位置
输入一些敏感字符,例如: > < " ' ( )等,遵照自提交后查看HTML源代码,查看自己输入的字符是否被转义;
-
无法得知输出位置
大多的web应用时非开源的,测试XSS的时候无法得知具体的输出位置显示;例如:我们的评论提交后会交由后台进行内容敏感检查,我们就无法得知具体的输出位置了;
<input type="text" id="name" value="Coke" />
上述是一个表单,我们可以使用
/> xxx
测试该标签的具体输出位置
xss利用方式
Cookie窃取
Cookie时能够让网站服务器吧少量的文本数据存储到客户端的硬盘或内存中,用于维持HTTP无状态协议导致的可持续网站会话;
-
如何产生:
当我们访问某网站,网站服务端由于HTTP时无状态协议,而客户端和服务器无法直接判断是否来自同一个客户源,为此当用户访问第一次网站后并登录等操作,服务端会返回Cookie给客户端的硬盘或内存中存留
-
如何使用:
当用户第二次访问服务端的时候,服务端就会检查客户端中是否有Cookie文件,如果有Cookie则会利用该文件登录并访问网站
-
Cookie格式:
每一个浏览器保存的Cookie的文件位置均不同,这里以Firefox浏览器为例,我们访问DVWA平台并登录,保存用户和密码形成Cookie
Cookie主要由变量名key和值value组成:
Set-Cookie: <name>=<value>[;<Max-Age>=<age>][;expires=<date>][;domain=<domain_name>][;path=<some_path>][;secure][;HttpOnly]
Set-Cookie
HTTP服务器的响应头,Web服务器通过此头讲Cookie发给客户端name=value
Cookie必有部分,用户通过name取得Cookie的对应的Value值expires=<date>
规定了Cookie的有效终止日期;缺省该字段则cookie不会存储在硬盘中domain=<domain_name>
规定了哪些Internet域中的Web服务器可以读取客户端的Cookie文件,如果缺省则Web服务器的域名为Value;path=<some_path>
定义Web服务器上哪些路径下的页面可以获取服务器发送的Cookie文件;Value为/
表示Web服务器中所有页面都可以获取Cookie文件;如果缺省,Path的Value则是Web服务器向客户端发送Cookie的URL;Secure
Cookie中标明变量,只有当web服务器和客户端之间采用HTTPS加密认证协议才可以进行连接通信提交Cookie文件HttpOnly
禁止JavaScript读取Cookie中的内容经过加密处理,只有Web服务器的Cookie处理程序可以解析Cookie真正的意义
-
利用xss获取cookie
-
创建一个cookie
setcookie()
函数向客户端发送一个 HTTP cookie。参考文章setcookie(name,value,expire,path,domain,secure) # 参数定义 name 必需。规定 cookie 的名称。 value 必需。规定 cookie 的值。 expire 可选。规定 cookie 的有效期。 若是删除一个cookie则可以设置时间为过去时 path 可选。规定 cookie 的服务器路径。 domain 可选。规定 cookie 的域名。 secure 可选。规定是否通过安全的 HTTPS 连接来传输 cookie。 注释:可以通过 $HTTP_COOKIE_VARS["user"] 或 $_COOKIE["user"] 来访问名为 "user" 的 cookie 的值。 注释:在发送 cookie 时,cookie 的值会自动进行 URL 编码。接收时会进行 URL 解码。如果你不需要这样,可以使用 setrawcookie() 代替。
-
页面添加设置COOKIE
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>XSS | 反射型</title> </head> <body> <form action="xss.php" method="GET"> <h2>xss反射型注入攻击测试</h2> <span>测试语句:</span> <input type="text" name="name"> <input type="submit" value="提交"> </form> <?php $COOKIE="XSS获取COOKIE测试"; setcookie("xss_cookie",$COOKIE); echo "COOKIE设置成功"; ?> </body> </html>
-
xss获取Cookie
<script> alert(document.cookie);//获取Cookie </script>
-
恶意构造URL
http://127.0.0.1/xss/xss.php?name=<script>alert(document.cookie);</script>
若是用户访问了这个URL则会直接暴露自己的COOKIE,一旦暴露,攻击者可以伪造COOKIE访问。
-
COOKIE安全
正是因为Cookie存在客户端且可以容易被伪造,所以最初的预防办法就是高度加密;用户获取Cookie信息后伪造Cookie登录即可操作该账号。对于高度加密的COOKIE攻击者至于要原样伪造一个即可,因为解密的大多是在服务端解密Cookie的;Cookie被窃取后就会导致Cookie伪造会话/Cookie欺骗严重的安全漏洞。
-
常规防御XSS
字符过滤
-
输入过滤
永远不要相信用户的输入;一般情况在客户端要设置字符验证过滤敏感的字符、限制长度、要求格式……等。当然客户端的内容用户都是可控的,单单依靠客户端是不可靠的,通过Burp等工具,可以轻易的修改数据包,绕过 客户端的过滤检查。
-
输出转码
千万不要把用户的输入内容完整的回显至HTML页面中!一般使用HTMLEncode进行编码处理。
htmlspecialchars()函数可以将部分特殊字符转出HTML实体编码。
<?php echo htmlentities($_GET["name"]); // echo $_GET["name"]; ?>
输出的转码可以预防xss脚本直接回显执行
-
黑名单
使用黑名单和白名单对输入的内容进行正则匹配,不符合的则不执行并取消。开发人员将敏感的关键词 、特殊字符进行黑名单设置,将一些符合条件的字符、关键词纳入白名单。
-
前端过滤函数
function xss_clean($data){ // Fix &entity\n; $data=str_replace(array('&','<','>'),array('&','<','>'),$data); $data=preg_replace('/(&#*\w+)[\x00-\x20]+;/u','$1;',$data); $data=preg_replace('/(&#x*[0-9A-F]+);*/iu','$1;',$data); $data=html_entity_decode($data,ENT_COMPAT,'UTF-8'); // Remove any attribute starting with "on" or xmlns $data=preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu','$1>',$data); // Remove javascript: and vbscript: protocols $data=preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu','$1=$2nojavascript...',$data); $data=preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu','$1=$2novbscript...',$data); $data=preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u','$1=$2nomozbinding...',$data); // Only works in IE: <span style=" expression(alert('Ping!'));"></span> $data=preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i','$1>',$data); $data=preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i','$1>',$data); $data=preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu','$1>',$data); // Remove namespaced elements (we do not need them) $data=preg_replace('#</*\w+:\w[^>]*+>#i','',$data); do{// Remove really unwanted tags $old_data=$data; $data=preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:frame|layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i','',$data); }while($old_data!==$data); // we are done... return $data; }
-
后端过滤
<?php //php防注入和XSS攻击通用过滤. //by qq:831937 $_GET && SafeFilter($_GET); $_POST && SafeFilter($_POST); $_COOKIE && SafeFilter($_COOKIE); function SafeFilter (&$arr) { $ra=Array('/([x00-x08,x0b-x0c,x0e-x19])/','/script/','/javascript/','/vbscript/','/expression/','/applet/','/meta/','/xml/','/blink/','/link/','/style/','/embed/','/object/','/frame/','/layer/','/title/','/bgsound/','/base/','/onload/','/onunload/','/onchange/','/onsubmit/','/onreset/','/onselect/','/onblur/','/onfocus/','/onabort/','/onkeydown/','/onkeypress/','/onkeyup/','/onclick/','/ondblclick/','/onmousedown/','/onmousemove/','/onmouseout/','/onmouseover/','/onmouseup/','/onunload/'); if (is_array($arr)) { foreach ($arr as $key => $value) { if (!is_array($value)) { if (!get_magic_quotes_gpc())//不对magic_quotes_gpc转义过的字符使用addslashes(),避免双重转义。 { $value = addslashes($value); //给单引号(')、双引号(")、反斜线()与NUL(NULL字符)加上反斜线转义 } $value = preg_replace($ra,'',$value); //删除非打印字符,粗暴式过滤xss可疑字符串 $arr[$key] = htmlentities(strip_tags($value)); //去除 HTML 和 PHP 标记并转换为HTML实体 } else { SafeFilter($arr[$key]); } } } } ?>
HttpOnly Cookie
防止xss窃取Cookie可以使用HttpOnlyCookie;
当一个Cookie在Set-cookie消息头中被标明为HttpOnly时,客户端的js是不可以直接访问该cookie的。
-
PHP设置HttpOnly
-
修改php.ini文件,设置
session.cookie_httponly =1
-
setcookie()函数和setrawcookie()函数的第七个参数用来做HttpOnly启动选项
setcookie('','','','','','',TRUE); setrawcookie('','','','','','',TRUE);
-
php代码开启HttpOnly
<?php ini_set("session.cookie_httponly",1); session_set_cookie_params(0,null,null,null,TRUE) ?>
-
客户端预防
用户在访问网站的时候为了防止恶意脚本在自己的客户端上呗执行,也可以在浏览器上安装一个插件,利用插件的功能来禁止页面的脚本执行。