一、 历史
1995年,网景公司为其Netscape Navigator(网景导航者)浏览器引入JavaScript,这改变了之前Web内容是静态HTML的状况。JS使得网页的交互越来越频繁,促使了web的蓬勃发展,但是也带来了新的问题:JS可以执行服务器发送的代码片段,存在恶意代码注入的巨大安全风险,网页里的恶意代码可以向另外一个服务发送数据。使得攻击者可以利用这个方法窃取敏感信息。
2000年2月,CERT(卡内基梅隆大学计算机紧急回应小组协调中心) 公布了一份XSS最早的报告,记载了网页因为疏忽包含了恶意的html标签和脚本的案例,以及这些恶意代码会对用户造成什么影响。
因为RD(Research & Development,一般指后端研发)在开发时不可能对所有的用户输入都进行检测和处理,所以XSS是广泛长期存在的。根据OWASP Top 10的统计,XSS每次都能抢到前排小板凳,2010年位列第三、2013年位列第三、2017年位列第七。
二、 简介
XSS(Cross site scripting,跨站脚本攻击),是一种网站应用程序的安全漏洞攻击,是代码注入的一种。XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括Java,VBScript,ActiveX,Flash或者甚至是普通的HTML。XSS的重点于脚本的执行,其原理是:攻击者在web页面中插入一些恶意的script代码,当用户浏览该页面时,嵌入到页面的scripte代码执行,达到攻击用户的目的。XSS攻击主要分为几类:反射型、存储型和DOM-based型,其中反射型和DOM-based型归类为非持久性攻击,存储型为持久性攻击。
三、反射型 XSS
- 原理:攻击者通过特定的方式(比如给你一个嘿嘿图片),诱惑用户访问一个包含恶意代码的url,用户点击该url后,恶意代码在用户的浏览器上执行。被称为反射型XSS的原因是:注入代码是通过目标服务器的错误信息、搜索结果等方式反射回来的,非持久性的原因是:该攻击只会发生一次。相比于存储型XSS,反射型XSS的恶意代码存放在URL中。
- 攻击步骤
a)攻击者构造成包含恶意代码的URL;
b)用户打开带有恶意代码的URL,网站服务端将恶意代码从URL中取出,拼接在HTML上返回给用户;
c)用户浏览器接收到响应解析执行,混在其中的恶意代码也被执行;
d)恶意代码窃取用户敏感数据发送给攻击者,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。 - 样例:攻击者通过电子邮件等方式将包含恶意代码的URL发给用户,当用户点击该链接的时候,恶意代码被传输到目标服务器上,目标服务器将恶意代码"反射"到用户的浏览器上,恶意代码被执行。
- low级别
a)前端代码
<div class="vulnerable_code_area">
<form name="XSS" action="#" method="GET">
<p>
What's your name?
<input type="text" name="name">
<input type="submit" value="Submit">
</p>
</form>
</div>
b)服务端代码
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
c)源码分析
1)前端填写完“name”字段、点击“submit”提交后,向服务端发送请求;
2)服务器端未对用户输入"name"做任何检查和过滤,并直接引用了name,存在明显的XSS漏洞。
d)漏洞利用
构造URL:
http://43.247.91.228:81/vulnerabilities/xss_r/?name=<script>alert(/xss/)</script>,(http://43.247.91.228:81/ 是dvwa的一个在线靶场)
当用户访问该URL后,JavaScript代码被执行。
5. medium
a)前端代码
同Low级别
b)服务端代码
<?php
// 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>";
}
?>
c)代码分析
服务端代码使用str_replace将“name”参数中的 “script “标签替换为””,可通过双写或大小写绕过。
d)漏洞利用
构造URL:
http://43.247.91.228:81/vulnerabilities/xss_r/?name=<ScripT>alert(/xss/)</ScripT>或者http://43.247.91.228:81/vulnerabilities/xss_r/?name=<scr<script>ipt>alert(/xss/)</scr<script>ipt>
当用户访问该URL后,JavaScript代码被执行,结果和low级别相同,不再贴出。
- high
a)前端代码
同Low级别
b)服务端代码
<?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>";
}
?>
c)代码分析
High级别的代码使用preg_replace对用户输入进行检查和处理,避免了双写和大小写绕过,虽然无法使用"script"标签注入XSS代码,但是可以通过img、body等其他标签的事件或者iframe等标签的src注入恶意的js代码。
d)漏洞利用
构造URL:
http://43.247.91.228:81/vulnerabilities/xss_r/?name=<img src=1 οnerrοr=alert(/xss/)>
当用户访问该URL后,JavaScript代码被执行。
- impossible
a)前端代码
同low级别
b)服务端代码
<?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();
?>
c)代码分析
服务器端代码使用htmlspecialchars函数把预定义的字符&、”、 ’、<、>转换为HTML实体,防止浏览器将其作为HTML元素。
四、存储型 XSS
-
简介:存储型XSS的原理是:主要是将恶意代码上传或存储到服务器中,下次只要受害者浏览包含次恶意代码的页面就会执行恶意代码。与反射型XSS不同的是,存储型XSS恶意代码存储在数据库中。
-
攻击步骤:
a)攻击者把恶意代码提交到目标网站的数据库中;
b)用户打开目标网站,网站服务端把恶意代码从数据库中取出,拼接在HTML上返回给用户;
c)用户浏览器接收到响应解析执行,混在其中的恶意代码也被执行;
d)恶意代码窃取用户敏感数据发送给攻击者,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。 -
攻击样例:
以留言板功能来说,每当用户访问给应用时,均会将其他用户的留言显示在浏览器上。假定攻击者在留言板留下了恶意代码并成功存储进数据库,那么其他用户访问留言板时,就会执行恶意代码。 -
low
a)前端代码
<h1>Vulnerability: Stored Cross Site Scripting (XSS)</h1>
<div class="vulnerable_code_area">
<form method="post" name="guestform" onsubmit="return validate_form(this)">
<table width="550" border="0" cellpadding="2" cellspacing="1">
<tr>
<td width="100">Name *</td>
<td><input name="txtName" type="text" size="30" maxlength="10"></td>
</tr>
<tr>
<td width="100">Message *</td>
<td><textarea name="mtxMessage" cols="50" rows="3" maxlength="50"></textarea></td>
</tr>
<tr>
<td width="100"> </td>
<td><input name="btnSign" type="submit" value="Sign Guestbook" onClick="return checkForm();"></td>
</tr>
</table>
</form>
</div>
b)服务端代码
<?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();
}
?>
c)代码分析
前端代码:当用户点击"submit"按钮之后,前端会通过"post"方法将用户输入"txtName"和"mtxMessage"发送到服务器,并向服务器发
送查询所有留言请求并回显;
后端代码:
1)trim ( string $str [, string $character_mask = " \t\n\r\0\x0B" ] ):删除字符串str首尾的特殊字符,默认特殊字符是
" \t\n\r\0\x0B"。
2)stripslashes ( string $str ) :转义字符串str中的"",比如"“变为”","\“变为”"。
3)mysqli_real_escape_string(connection,escapestring):转义在 SQL 语句中使用的字符串中的特殊字符,包括NUL、
\n、\r、\、’、" 和 Ctrl-Z,connection是要使用的 MySQL 连接,escapestring要转义的字符串。
可以看到,low级别的代码并没有对要存入数据库的数据"name"和"message"是否存在恶意脚本做过滤和检查,而是直接存储在数据
库中,因此这里存在明显的存储型XSS漏洞。
d)漏洞利用
1)恶意用户在"name"出输入"123",在"message"出输入如下代码,该恶意代码被存入数据库。
<script>alert(/xss/)</script>
2)正常用户在"name"出输入"456",在"message"出输入"hello world",浏览器从数据库搜索出所有留言,执行了恶意代码。
- medium
a)服务端代码
<?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 = str_replace( '<script>', '', $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();
}
?>
b)代码分析
1)strip_tags ( string $str [, string $allowable_tags ] ):去除字符串str中的html、xml以及php标签,allowable_tags用以指定不
被去除的字符列表。
2)htmlspecialchars :将预定义的字符转换为 HTML 实体,也就是作为文本显示,预定义的字符包括"&、"、’、<、>"
可以看到,medium级别的代码对message参数使用strip_tags、htmlspecialchars函数进行了处理,因此无法再通过message参数注
入XSS代码,但是对于name参数,只是简单的将"script"标签替换为‘’,可通过双写或者大小写进行绕过,仍然存在存储型的XSS。
c)漏洞利用
1)由于前端对"name"字段有长度限制,可通过burpsuite拦截-修改-重放请求,恶意用户通过双写绕过服务器端对"name"字段的检查,恶意代码被存入数据库。
1)正常用户留言提交后,回显其他用户的留言,恶意代码被执行。
- high
a)服务端代码
<?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();
}
?>
b)代码分析
high级别的代码使用preg_replace对过滤了name参数中的"script"标签,但是却忽略了img、iframe等其它危险的标签,因此name
参数依旧存在存储型XSS。
c)漏洞利用
抓包改name参数:
- impossible
a)服务器端代码
<?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();
?>
b)代码分析
impossible级别的代码使用htmlspecialchars函数对所有的用户输入均进行了处理,将预定义的字符转换为HTML实体,然后在存入
数据库,防止回显时浏览器将其作为HTML元素。
四、DOM-based型
- 原理:客户端的js可以对页面dom节点进行动态的操作,比如插入,修改页面的内容,例如客户端从url中提取数据并在本地执行,如果攻击者在客户端输入的数据包含恶意脚本,而服务器未对用户输入进行检查处理,那么用户可能受到DOM-based XSS的攻击。