导语

XSS作为OWASP top10之一,是不容小视的安全威胁。

简介

人们经常将跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为XSS。
跨站脚本攻击(XSS),是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。
XSS的分类
绿盟这张图我觉得挺好

但是由于广泛被接受的分类是反射型、存储型、DOM-based,并且DVWA也是这三类,因此我们还按照反射型、存储型和DOM-based型来简单分析

DVWA之反射型XSS

low

开局一个输入框,直接回显html,无任何过滤

因此直接用<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>';
}

?>

确实直接就echo了输入的name,且没有过滤。

medium

开局一个输入框,发现会过滤<script>,但是不会过滤</script>
于是直接双写,输入<scr<script>ipt>,发现成了。

查看源代码

<?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>删掉了罢了
于是还可以用大小写绕过,比如<scRipt>

high

emmm直接看源码了

<?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>";
}

?>

可以看到用了正则表达式过滤了。
但是可以通过img、body等标签的事件或者iframe等标签的src注入恶意的js代码
比如酱紫<img src="" οnerrοr="alert('XSS')">

impossible

众所周知,DVWAimpossible就差不多是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()把<>都给过滤了,还用了session,且session并没有在response里。
好!impossible!

DVWA之存储射型XSS

low

开局一个注册,可以提交的有name和message
直接回显输入的所有内容,包括name和message。
但是name有长度限制,直接改前端
在name输入<script>alert("name")</script>,然后在message输入<script>alert("message")</script>



(而且还会把库里所有的东西都echo出来)
看源代码

<?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过滤空格、换行、缩进等,并没有针对DOM的标签。

medium

猜测和反射型一样,于是用大小写绕过

发现message还加了一个引号过滤。
查看源码

<?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用了addslashes,此外还用了一个htmlspecialchars,因此message基本不能注入了。但是name的注入依旧只是简单的过滤罢了没有影响。

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();
}

?>

可以发现message像medium一样,过滤的很完善,而name加了正则,同样和反射型一样用含有src的标签用onerror即可

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也用了htmlspecialchars,合理。但是要注意的是,如果htmlspecialchars函数使用不当,攻击者就可以通过编码的方式绕过函数进行XSS注入,这个具体看其他博客吧,也是我仍然需要学习的知识。

DVWA之DOM型XSS

low

在选了English之后,会发现url里有一个?default=English,于是我们把English改为<script>alert('123')</script>,在访问时可以看到确实被执行了。

查看源码

<?php

# No protections, anything goes

?>

就说了句无防备……

medium

尝试和low一样的操作,发现不行,于是尝试双写和大小写绕过,发现也并不行。
于是尝试用onerror绕过,输入<img src="" onerror=alert("123")>发现也不行。
晕了,看下源码

<?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,查找字符串在另一字符串中第一次出现的位置,如果有就为true,即就不等于false,就会执行
header ("location: ?default=English");
也就是说第一步的想法是正确的,要用onerror绕过。

可以看到,我们的img已经插入了html,但是只是一个value罢了。
于是我们尝试闭合option标签,输入</option><img src="" onerror=alert("123")>
发现还是不行,原来此时img还在<select>标签,于是再把select标签页闭合了
</option></select><img src="" onerror=alert("123")>

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参数必须是这几个之一。
这里不太会,去抄答案了。
https://www.jianshu.com/p/001daa7cf1f5
?default=English #<script>alert(/xss/)</script>
由于 form表单提交的数据 想经过JS 过滤 所以注释部分的javascript 代码 不会被传到服务器端(也就符合了白名单的要求)

impossible

<?php

# Don't need to do anything, protction handled on the client side

?>

可以看到前端变成了

<form name="XSS" method="GET">
<select name="default">
<script>
if (document.location.href.indexOf("default=") >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
document.write("<option value='" + lang + "'>" + (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>");
</script>
</select>
<input type="submit" value="Select" />
</form>

这里用另一个js脚本,将我们赋给default的值赋给option,而浏览器会将这些值进行url编码,因此就可以防止注入。

后记

其实在刚开始用dvwa了解xss漏洞的时候,我一直不太理解这漏洞到底能造成什么危害,因为说到底各式各样的解都只是POC罢了,也没有利用,直到后来看到了一些真正恶意的js才终于明白js也能是很恶意的东西,比如这篇文章。
https://cloud.tencent.com/developer/article/1038173?from=information.detail.js%E6%81%B6%E6%84%8F%E4%BB%A3%E7%A0%81%E6%9C%89%E5%93%AA%E4%BA%9B
给我的收获是,学习网络安全不能只看poc,一定要看看exp,甚至亲自去操作一些exp实践,才能真正的理解、掌握相应的技巧

参考:
https://www.jianshu.com/p/001daa7cf1f5
http://blog.nsfocus.net/xss-advance/
https://baike.baidu.com/item/XSS%E6%94%BB%E5%87%BB/954065?fr=aladdin
https://blog.csdn.net/lay_loge/article/details/90440207