开始学习PHP代码审计,看网上大表哥推荐最好先审计一下DVWA,搞清楚这些漏洞的原理,然后再进行审计的深入学习和实战。
关于爆破我就只是简单说一下就可以了,感觉没啥好说的,而且他这个是没有验证码的,如果查询错误只会休眠一段时间
else {
// Login failed
sleep( 2 );
echo "
Username and/or password incorrect.
";
}
//Medium和High都只是休眠一段时间
在Impossible当中,登陆账号错误会进行计数,如果错误次数超过3次,就会进行账号登陆限制,经过一段时间过后才会解封。
大概代码是这样
//定义允许错误的次数
$total_failed_login = 3;
//如果登陆失败就会更新错误次数
SQL语句:UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;
//如果错误次数超过3次,就冻结账号
if( ( $data->rowCount() == 1 ) && ( $row[ 'failed_login' ] >= $total_failed_login ) ) {
// User locked out. Note, using this method would allow for user enumeration!
//echo "
This account has been locked due to too many incorrect logins.
";
// 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;
}
这个漏洞的原理就是满足条件之后就执行shell_exec()这个函数,可以执行系统命令
Windows下
&& 前一个命令执行成功才会执行后一个命令(如果前一个命令正确,后一个错误,则只执行前一个)
& 两条命令没有直接关系,互不影响
|| 只要其中一条命令正确即可执行,如果都正确,只执行前一条命令(在Windows命令行下是这样)
| 管道符,要两条命令同时都正确时才能执行,且只显示后一条命令的执行结果
Linux:
&& 第一条命令正确,才开始执行,执行并显示所有正确的命令
& 从后面的命令开始往前执行并显示正确命令的执行结果
|| 如果两条命令都正确,只执行第一条,如果其中一条正确,执行正确命令并显示结果
| 当第二条命令正确时,执行第二条命令并显示结果
(这里只是举得拼接两条命令,多条命令效果类似,这里的第二条命令相当于是多条命令中的最后一条命令)
回到dvwa中:
根据代码
$cmd = shell_exec( 'ping -c 4 ' . $target );
echo "{$cmd}
";
正常应该是ping一个ip地址
low:
没有对用户的输入进行过滤,所以可以直接进行拼接命令
medium:
也只是顾虑了 && 和 ;
可以用其他符号继续执行命令
high:
过滤了很多,但是过滤有点奇怪,他这里哟一个过滤是 |+空格
这就导致了可以用 127.0.0.1 || whoami 或者 127.0.0.1 |whoami绕过
在str_replace执行过程中,会先替换掉 |+空格,然后变成 127.0.0.1 |whoami,这种后面就没法过滤了,命令同样可以执行
impossible我就不说了,直接进行判断用户输入的内容从而达到过滤的效果
小技巧:
过滤敏感命令的情况下:
在Windows下可以使用双引号 "" 绕过黑名单 例如:whoami 被过滤 whoa""mi 进行绕过
Linux :双引号,单引号 都可绕过
low:
除了判断输入的两次密码是否相同,在用户身份上面并没有做判断,所以存在csrf漏洞
medium:
eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] )
用正则匹配了referer
网上有很多绕过referer的文章
http://blog.51cto.com/0x007/1610946
我没有太深究这个东西
high:
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
验证了token
impossible:
除了判断token,还判断了旧密码是否正确
关于csrf我没有做深入的研究,因为我觉得人们的安全意识在不断加强,关于那些绕过,利用的难度太大
提供的文件有file1.php file2.php file3.php include.php
low:
直接进行文件包含就可以,可以使用 ../遍历包含
medium:
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\"" ), "", $file );
过滤了字符,将http/https/ ../ / ..\ 替换为空,可以双写进行绕过
high:
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
如果$page 没有匹配到file开头的文件或者include.php就退出
这种刚好契合了 file:/// 协议,使用file:///协议绕过,只支持包含本地文件。
low:
没有任何过滤,上传任意文件
medium:
$uploaded_name = $_FILES[ 'uploaded' ][ 'name' ];
$uploaded_type = $_FILES[ 'uploaded' ][ 'type' ];
$uploaded_size = $_FILES[ 'uploaded' ][ 'size' ];
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
( $uploaded_size < 100000 ) )
判断了Content-Type,只要Content-Type为image/jpeg或image/png就可以绕过
通过抓包修改数据包就可以绕过
high:
//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 ) )
这里判断了文件头是否为图片类型,判断函数 getimagesize():
array getimagesize ( string $filename [, array &$imageinfo ] )
getimagesize() 函数将测定任何 GIF,JPG,PNG,SWF,SWC,PSD,TIFF,BMP,IFF,JP2,JPX,JB2,JPC,XBM 或 WBMP 图像文件的大小并返回图像的尺寸以及文件类型和一个可以用于普通 HTML 文件中 IMG 标记中的 height/width 文本字符串。
如果不能访问 filename 指定的图像或者其不是有效的图像,getimagesize() 将返回 FALSE 并产生一条 E_WARNING 级的错误。
要绕过对文件头的检测,就要制作图片马了
copy xx.jpg/b+hack.php hack.jpg
绕过了对文件头的检测,因为在后端检测了文件后缀,所以我理解的就是上传图片马,然后使用文件包含漏洞getshell,如果不对,请各位纠正。
low:
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
直接拼接用户传入的参数
medium:
$id = $_POST[ 'id' ];
$id = mysql_real_escape_string( $id );
//mysql_real_escape_string 转义\x00 \n \r \ ' " \x1a
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
虽然对特殊字符进行了转义,但是拼接的时候并没有使用 单引号或双引号,所以过滤不起作用。
high:
$id = $_SESSION[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
就是在SQL语句后面添加了一个 limit,这种直接可以用注释符将后面的语句注释掉。
impossible:
使用is_numeric()函数判断了id参数是否为数字,就和 int($id) 效果差不多,没办法拼接语句。
就和前面的SQL Injection一样的,只不过是换成了盲注,判断的方式换了而已,这里就不说了。
low:
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Feedback for end user
echo 'Hello ' . $_GET[ 'name' ] . '
';
}
没有做任何过滤
medium:
$name = str_replace( '