DVWA–XSS详解
XSS概念:通常指黑客通过HTML注入纂改了网页,插入恶意脚本,从而在用户浏览网页时,控制用户浏览器的一种攻击。
XSS三种:
反射型xss:只是简单地把用户输入的数据反射给浏览器,简单来说,黑客往往需要用户诱使用户点击一个恶意链接,才能攻击成功。
存储型XSS:将用户输入的数据存储在服务器端。用户访问了带有xss得页面代码后,产生安全问题。
DOM XSS:通过修改页面的DOM节点形成的XSS。
XSS(DOM)
DOM型xss不会和后台服务器产生交互,而是通过浏览器的dom树解析。代码流向:前端–>浏览器
low
<?php
# No protections, anything goes
?>
没有任何代码过滤。
payload:
?default=<script>alert('oh')</script>
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;
}
}
?>
stripos(): 返回字符串在另一个字符串中第一次出现的位置。
1.此处过滤了<script>
标签,所以我们可以使用:<img src=1 onerror=alert('oh')>
来替代。
发现语句被插入到了values值中,并没有插入到option标签中,失败了。
2.闭合前面的option标签
default=></option><img src=1 onerror=alert('oh')>
发现只有>被插入到了option标签中。因为</option>
闭合了option标签,使用img标签并没有插入。
3.继续构造语句去闭合select标签,制造出img独立语句,闭合</option></select>
标签构造XSS事件
payload:
?default=></script></select><img src=1 onerror='alert('oh')'>
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;
}
}
?>
default=只允许French,English,German,Spanish这几个字符串,否则会直接跳转到?default=English。
payload:
?default=English#<script>alert('oh')</script>
原理:由于form表单提交的数据需要经过js过滤,所以注释部分的JavaScript代码不会被传到服务器端(也就符合了白名单的要求)从而只在客户端显示。
impossible
<?php
# Don't need to do anything, protction handled on the client side
?>
没有任何东西,保护的代码在客户端里。
[](https://imgchr.com/i/rpeGlQ)
[](https://imgchr.com/i/rpeL7t)
查看源代码:
decodeURL(lang),对lang进行URL解码。而decodeURL(lang)被(lang)替代了。发现我们输入的参数并没有进行URL解码,而我们输入的参数都是经过URL编码的,被直接赋予了option标签,所以不存在XSS漏洞。
#### XSS(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参数判断了一下是否为空,没有进行任何过滤和检测,可直接进行输入。
``<script>alert('hhh')</script>``
[](https://imgchr.com/i/rpnxyQ)
一步到位。
##### 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( ‘
// Feedback for end user
echo "<pre>Hello ${name}</pre>"; }
?>
**str_replace():** 用其他字符代替字符串中的一些字符。
medium级别的代码只是在low上增加了对``<script>``的过滤,我们可以直接大小写绕过(双写绕过)。
``<Script>alert('hhh')</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>"; }
?>
**preg_replace():**执行一个正则表达式的搜索和替换。
high级别代码使用了正则表达式直接把<script过滤了,*表示一个或多个任意字符,i代表不区分大小写。所以大小写绕过和双写绕过不能用了,所以我们可以采用其他标签进行注入。
这里让弹个cookie吧:
``<img src=1 onerror=alert('document.cookie')>``
当图片显示错误时,执行alert(),就将我们要的cookie弹出来了。
[](https://imgchr.com/i/rplpcT)
##### 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(string):** 把预定义的字符 "<", ">", "&", ""(双引号)", "'(单引号)" 转换为HTML实体,防止浏览器将其作为HTML元素。
impossible级别的代码先判断name是否为空,不为空的话然后验证其token,来防范CSRF攻击。然后再用htmlspecialchars函数将name中的预定义字符转换成html实体,这样就防止了我们填入标签。
#### XSS(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(); }
?>
**trim(string,charlist):** 移除string字符两侧的预定义字符,预定义字符包括\t 、 \n 、\x0B 、\r以及空格,可选参数charlist支持添加额外需要删除的字符
**stripslashes(string):** 去除掉string字符的反斜杠\
**mysqli_real_escape_string(string,connection):**函数会对字符串string中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。
**$GLOBALS:**引用全局作用域中可用的全部变量。$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。
可以看出,low级别的代码对我们输入的message和name进行了SQL防御,并没有进行XSS过滤,而且数据存储在数据库中,存在比较明显的存储型XSS漏洞
直接输入即可:
[](https://imgchr.com/i/rp3Vl6)
##### 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(); }
?>
**addslashes(string):** 函数返回在预定义字符之前添加反斜杠的字符串,预定义字符:"'(单引号)",""(双引号)","\\", "null"。
**strip_tags(string):** 函数剥去string字符串中的 HTML、XML 以及 PHP 的标签
**htmlspecialchars(string):** 把预定义的字符 "<" 、 ">" 、& 、‘’、“” 转换为 HTML 实体,防止浏览器将其作为HTML元素
可以看出对message参数进行了严格的过滤,但对那么参数的过滤比较简单,只是进行了script替换,和长度限制。
可以进行大小写绕过,长度限制可以F12直接修改一下 。
[](https://imgchr.com/i/rp88UJ)
在name栏进行注入
[](https://imgchr.com/i/rp8wDO)
##### 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(); }
?>
比medium多了对name进行正则表达式的过滤。
同样F12改一下name的限制长度,在用其他标签进行注入。
``<img src=1 onerror=alert('oh')>``
``<body onload=alert('yyy')>``
``<a herf="" onclick=alert('hhh')>click</a>``
##### 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();
?> ```
对name也进行了更加严格的过滤,无法进行name参数注入,还增加了Anti-CSRF token防止CSRF攻击,完全杜绝了XSS漏洞和CSRF漏洞。