XSS(Stored)
"Cross-Site Scripting (XSS)" attacks are a type of injection problem, in which malicious scripts are injected into the otherwise benign and trusted web sites. XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script, to a different end user. Flaws that allow these attacks to succeed are quite widespread and occur anywhere a web application using input from a user in the output, without validating or encoding it.
跨站点脚本(XSS)攻击是一种注入攻击,恶意脚本会被注入到可信的网站中。当攻击者使用 web 应用程序将恶意代码(通常以浏览器端脚本的形式)发送给其他最终用户时,就会发生 XSS 攻击。允许这些攻击成功的漏洞很多,并且在 web 应用程序的任何地方都有可能发生,这些漏洞会在使用用户的输入,没有对其进行验证或编码。
An attacker can use XSS to send a malicious script to an unsuspecting user. The end user's browser has no way to know that the script should not be trusted, and will execute the JavaScript. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other sensitive information retained by your browser and used with that site. These scripts can even rewrite the content of the HTML page.
攻击者可以使用 XSS 向不知情的用户发送恶意脚本,用户的浏览器并不知道脚本不应该被信任,并将执行 JavaScript。因为它认为脚本来自可信来源,所以恶意脚本可以访问浏览器并作用于该站点的任何 cookie、会话令牌或其他敏感信息,甚至可以重写 HTML 页面的内容。
The XSS is stored in the database. The XSS is permanent, until the database is reset or the payload is manually deleted.
存储型 XSS 存储在数据库中,直到数据库被重置或者负载被手动删除。
Redirect everyone to a web page of your choosing.
将所有人重定向到您选择的网页。
Low Level
Low level will not check the requested input, before including it to be used in the output text.
在将请求的输入包含在输出文本中之前,Low level 将不检查输入的任何的请求。
源码审计
源码如下,trim(string,charlist) 函数用于移除字符串两侧的空白字符或其他预定义字符,charlist 参数可以规定从字符串中删除哪些字符。stripslashes() 函数用于删除反斜杠,可用于清理从数据库中或者从 HTML 表单中取回的数据。mysqli_real_escape_string() 函数用于对字符串中的特殊字符进行转义,使得这个字符串是一个合法的 SQL 语句。GLOBALS 是引用全局作用域中可用的全部变量,是一个包含了全部变量的全局组合数组。
源码的用意是输入一个名字和一段文本,然后网页把输入的信息加入到数据库中,同时服务器也会将服务器的内容回显到网页上。
<?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();
}
?>
例如我们随便输入一个名字和一个文本,点击提交,网页会把这些内容加入数据库并回显。
打开数据库查看,这些文本的确被加入了数据库。
源码对我们输入的 message 和 name 并没有进行 XSS 过滤,直接将数据存储在数据库中,存在存储型 XSS 漏洞。
攻击方式
直接在 Message 注入 payload,服务器成功弹出页面。这种攻击是永久性的,每次切换到这个页面都会弹出,除非开发者删除数据库内容。
<script>alert('xss')</script>
查看网页源码,我们注入的 JS 脚本已经被插入了。
Medium Level
The developer had added some protection, however hasn't done every field the same way.
开发人员已经添加了一些保护,但是并不是每个区域都有这样的防护。
源码审计
源码如下,addslashes() 函数在每个双引号前添加反斜杠,返回在预定义字符之前添加反斜杠的字符串。strip_tags() 函数用于剥去字符串中的 HTML、XML 以及 PHP 的标签,htmlspecialchars() 函数用于把预定义的字符 "<" 和 ">" 转换为 HTML 实体。
可以看到 Message 参数对所有的 XSS 都进行了过滤,但是 name 参数只是过滤了 < script > 标签而已,我们可以对 name 参数进行注入。
<?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();
}
?>
攻击方式
由于 name 参数有输入限制,因此我们需要抓包改参数,注入的 JS 脚本可以用大小写绕过。
<SCRIPT>alert('xss')</SCRIPT>
注入 payload 后,服务器成功弹出页面。
High Level
The developer believe they have disabled all script usage by removing the pattern "<script".
开发人员相信他们通过模式匹配 “<script”,禁用了所有脚本的使用。
源码审计
源码如下,preg_replace() 函数执行一个正则表达式的搜索和替换,“*” 代表一个或多个任意字符,“i” 代表不区分大小写。也就是说 name 参数 “< script >” 标签在这里被完全过滤了,但是我们可以通过其他的标签例如 img、body 等标签的事件或者iframe 等标签的 src 注入 JS 攻击脚本。
<?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();
}
?>
攻击方式
HTML 的 < img > 标签定义 HTML 页面中的图像,该标签支持 onerror 事件,在装载文档或图像的过程中如果发生了错误就会触发。使用这些内容构造出 payload 如下,因为我们没有图片可供载入,因此会出错从而触发 onerror 事件。
<img src = 1 onerror = alert('xss')>
仍然是抓包之后改参数,放包后成功弹出页面。
Impossible Level
Using inbuilt PHP functions (such as "htmlspecialchars()"), its possible to escape any values which would alter the behaviour of the input.
使用内置的PHP函数(例如 “htmlspecialchars()”),可以转义任何会改变输入行为的值。
<?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();
?>
源码在 high 级别的基础上对 name 参数也进行了更严格的过滤,导致 name 参数也无法进行 JS 脚本注入。同时加入 Anti-CSRF token 防护 CSRF 攻击,进一步提高安全性。
总结与防御
跨站脚本 (Cross-Site Scripting) 是一种针对 Web 程序的代码注入型漏洞攻击,它允许攻击者将恶意脚本注入网页,使得其他用户浏览网页时收到影响。所谓存储型 XSS 又称为持久型 XSS,攻击脚本会被永久保存在目标服务器的数据库或文件中,具有更高的隐蔽性。
存储型 XSS 的攻击常见于论坛、博客或留言等需要提交文本的页面,攻击者将攻击脚本和文本一起注入。当文本被服务器存储下来时,恶意脚本也会被永久存放与服务器的数据库或文件中。当其他用户访问这个含有恶意脚本的页面时,恶意脚本会在用户的浏览器中执行。XSS 漏洞的修复方式有以下 2 种:
- 过滤输入的字符,例如 “ ' ”,“ " ”,“<”,“>” 等非法字符;
- 对输出到页面的数据进行编码。