dvwa存储型XSS
存储型XSS:会把用户输入的数据“存储”在服务器端,一般出现在需要用户可以输入数据的地方,比如网站的留言板、评论等地方,当网站这些地方过滤不严格的时候,就会被黑客注入恶意攻击代码,存储在服务器端,每当用户加载该页面时,都会受到攻击,所以这种攻击行为也称为“持久型XSS(Persistent XSS)”。
漏洞测试
low级别
在Name和Message输入框中分别输入一个String(admin)发现输入的数据会显示在当前页面中,同时可以知道数据提交是以POST请求的方式
查看源码:
<?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(); } ?>
代码审计发现,并不存在Protect,trim()函数、stripslashes()函数、mysqli_real_escape_string()
函数等不会对JS代码进行过滤
trim(string,charlist) 函数移除字符串两侧的空白字符或其他预定义字符。 string 必需。规定要检查的字符串。 charlist 可选。规定从字符串中删除哪些字符。如果被省略,则移除以下所有字符: " " - NULL " " - 制表符 " " - 换行 "x0B" - 垂直制表符 " " - 回车 " " - 空格 ######################################################################################## stripslashes(string) 函数删除由 addslashes() 函数添加的反斜杠。 string 必需。规定要检查的字符串。 提示:该函数可用于清理从数据库中或者从 HTML 表单中取回的数据。 ######################################################################################## addslashes(string) 函数返回在预定义字符之前添加反斜杠的字符串。 预定义字符是: 单引号(') 双引号(") 反斜杠() NULL string 必需。规定要转义的字符串。 提示:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。 ######################################################################################## mysql_real_escape_string(string,connection) 函数转义 SQL 语句中使用的字符串中的特殊字符。 下列字符受影响: x00 ' " x1a 如果成功,则该函数返回被转义的字符串。如果失败,则返回 false。 string 必需。规定要转义的字符串。 connection 可选。规定 MySQL 连接。如果未规定,则使用上一个连接。 提示:可使用本函数来预防数据库攻击。
构造payload,执行最简单的XSS攻击
POST data
txtName=admin&mtxMessage=<script>alert("_XSS_")</script>&btnSign=Sign+Guestbook
恶意代码被存入服务端,每当用户打开该网页都会受到攻击
服务端数据库文件已存入攻击者的恶意代码
进入该网页,触发恶意攻击
medium 级别
<?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(); } ?>
经过代码审计,可以发现medium级别进行了Protect,对POST请求到的的内容进行过滤:
str_replace()函数 对 Name data进行<script>标签匹配置空
strip_tags()函数 对 message data进行标签匹配删除
str_replace(find,replace,string,count) 函数以其他字符替换字符串中的一些字符(区分大小写)。 该函数必须遵循下列规则: 如果搜索的字符串是数组,那么它将返回数组。 如果搜索的字符串是数组,那么它将对数组中的每个元素进行查找和替换。 如果同时需要对数组进行查找和替换,并且需要执行替换的元素少于查找到的元素的数量,那么多余元素将用空字符串进行替换 如果查找的是数组,而替换的是字符串,那么替代字符串将对所有查找到的值起作用。 注释:该函数区分大小写。请使用 str_ireplace() 函数执行不区分大小写的搜索。 参数 描述 find 必需。规定要查找的值。 replace 必需。规定替换 find 中的值的值。 string 必需。规定被搜索的字符串。 count 可选。对替换数进行计数的变量。 ########################################################################################### strip_tags(string,allow) 函数剥去字符串中的 HTML、XML 以及 PHP 的标签。 注释:该函数始终会剥离 HTML 注释。这点无法通过 allow 参数改变。 参数 描述 string 必需。规定要检查的字符串。 allow 可选。规定允许的标签。这些标签不会被删除。
分析可知要想利用XSS攻击,只有对Name data进行注入,利用<script>标签大小写进行绕过,但你会发现Name data length=10 受到限制
篡改数据,绕过length限制
构造payload
POST Name data
txtName=%3CScript%3Ealert%281111%29%3C%2FscriPt%3E&mtxMessage=admin&btnSign=Sign+Guestbook
Name data的绕过
服务端数据库文件已存入攻击者的恶意代码
在这里也可以使用<img>标签进行绕过:<img src=# onerror=alert("_XSS_")>
high 级别
<?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(); } ?>
代码审计之后,发现high级别的Name data存在另外一种的Protect,利用preg_replace()函数进行匹配“< s r i p t”等字符,将其置为空,此时Name data里面的<script>标签是不能使用的,不管是大小进行区分写都不可以进行绕过
所以,就需要利用其它标签进行XSS攻击,此处可以利用 medium 级别 中提到的<img>标签进行绕过preg_replace()函数的Protect
构造payload
POST Name data
txtName=%3Cimg+src%3D%23+onerror%3Dalert%28%22_XSS_%22%29%3E&mtxMessage=Hello+admin&btnSign=Sign+Guestbook
<img>标签成功绕过preg_replace()
服务端数据库文件已存入攻击者的恶意代码
impossible 级别
<?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(); ?>
impossible 级别的Protect不能被绕过,由于Name data和Message data都受到htmlspecialchars()函数的保护作用
htmlspecialchars() 函数把预定义的字符转换为 HTML 实体。 预定义的字符是: & (和号)成为 & " (双引号)成为 " ' (单引号)成为 ' < (小于)成为 < > (大于)成为 > 它的语法如下: htmlspecialchars(string,flags,character-set,double_encode) 其中第二个参数flags需要重要注意,很多开发者就是因为没有注意到这个参数导致使用htmlspecialchars()函数过滤XSS时被绕过。因为flags参数对于引号的编码如下: 可用的引号类型: ENT_COMPAT - 默认。仅编码双引号。 ENT_QUOTES - 编码双引号和单引号。 ENT_NOQUOTES - 不编码任何引号。 默认是只编码双引号的
因为输入的所有标签都被转义,所以此处不存在XSS攻击,但是要注意flags属性,使用不当过滤XSS时就会被绕过