こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

DEF CON CTF Qualifier 2018 - Easy Pisy

問題文

There, I've said it…
http://5a7f02d0.quals2018.oooverflow.io
Files: samples.tgz

f:id:graneed:20180513163453p:plain

writeup

sign.phpとexecute.phpの2種類のphpがある。
phpソースコードはlinkより参照可能。

  • sign.php
    アップロードしたPDF内に書かれた文字列が"ECHO "で始まる場合に、
    PDFと秘密鍵をopenssl_sign関数に渡して署名を生成する。
    "EXECUTE "で始まる場合は、Go awayと怒られる。

  • execute.php
    アップロードしたPDF、画面入力した署名文字列および公開鍵をopenssl_verify関数に渡して照合する。
    照合OKの場合、アップロードしたPDF内に書かれた文字列が"ECHO "で始まる場合に、文字列を出力。
    "EXECUTE "で始まる場合に、文字列のコマンドを実行。

sign.phpソースコードは以下の通り。

<?php

include 'common.php';

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  print highlight_string(file_get_contents("sign.php"), TRUE);
  exit(0);
}

$keys = get_keys();
$privkey = $keys[0];
$pubkey = $keys[1];

if ($privkey === FALSE || $pubkey === FALSE) {
  die("Could not load keys. Contact admin.<br/>");
}

$file_info = $_FILES['userfile'];
check_uploaded_file($file_info);

$text = pdf_to_text($file_info['tmp_name']);
print "Extracted text: \"$text\"<br/>";

$execute_query = "EXECUTE ";
$echo_query = "ECHO ";
if (substr($text, 0, strlen($execute_query)) === $execute_query) {
  print "I don't sign EXECUTE commands. Go away.<br/>";
} else if (substr($text, 0, strlen($echo_query)) === $echo_query) {
  print "I'm OK with ECHO commands. Here is the signature: <br/>";
  $data = file_get_contents($file_info['tmp_name']);
  openssl_sign($data, $signature, $privkey);
  print bin2hex($signature);
} else {
  print "I can't recognize the command type. Go away.<br/>";
}

execute.phpソースコードは以下の通り。

<?php

include 'common.php';

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
  print highlight_string(file_get_contents("execute.php"), TRUE);
  exit(0);
}

$keys = get_keys();
$privkey = $keys[0];
$pubkey = $keys[1];

$file_info = $_FILES['userfile'];
check_uploaded_file($file_info);

$data = file_get_contents($file_info['tmp_name']);
$signature = hex2bin($_POST['signature']);
if (openssl_verify($data, $signature, $pubkey)) {
  print 'Signature is OK.<br/>';
} else {
  die('Bad signature.');
}

$text = pdf_to_text($file_info['tmp_name']);
print "Text: \"$text\"<br/>";

$execute_query = "EXECUTE ";
$echo_query = "ECHO ";
if (substr($text, 0, strlen($execute_query)) === $execute_query) {
  $payload = substr($text, strlen($execute_query));
  print "About to execute: \"$payload\".<br/>";
  $out = shell_exec($payload);
  print "Output: $out";
} else if (substr($text, 0, strlen($echo_query)) === $echo_query) {
  $payload = substr($text, strlen($echo_query));
  print "About to echo: \"$payload\".<br/>";
  echo $payload;
} else {
  print "I can't recognize the command type. Go away.<br/>";
}

?>

添付されていたsampleのPDFにlsコマンドを実行するPDFと署名があるので、アップロードをかけてみる。

Signature is OK.
Executing 'convert -depth 8 /tmp/phprLCQq1.pdf /tmp/phprLCQq1.ppm'
Executing 'ocrad /tmp/phprLCQq1.ppm'
Text: "EXECUTE ls"
About to execute: "ls".
Output: common.php execute.php flag index.nginx-debian.html index.php private_key.pem public_key.pem sign.php

flagファイルがある。これをcatできれば勝ち。 ただ、秘密鍵も公開鍵も取得できない。

openssl_sign関数のI/Fを確認。 PHP: openssl_sign - Manual
第4引数を指定しないと、OPENSSL_ALGO_SHA1となるようだ。
SHA1衝突が使えそうだ。

github.com このスクリプトで、任意の2つの画像からSHA1が同じファイルを生成できる。
以下の文字列を書いた画像を作成する。

  • ECHO cat flag
    f:id:graneed:20180513164314j:plain
  • EXECUTE cat flag
    f:id:graneed:20180513164336j:plain

スクリプト実行すると、echo-collision.pdfとexecute-collision.pdfができた。

ctfuser@kali:sha1-collider-master$ python collider.py echo.jpg execute.jpg 
Image size: (380, 100)
Successfully Generated Collision PDF !!!

SHA1ハッシュ値が一致した。

ctfuser@kali:sha1-collider-master$ sha1sum echo-collision.pdf 
35b03ecc67af9e8032f9c85e4ed10e0421b2a512  echo-collision.pdf
         
ctfuser@kali:sha1-collider-master$ sha1sum execute-collision.pdf 
35b03ecc67af9e8032f9c85e4ed10e0421b2a512  execute-collision.pdf

echo-collision.pdfをsign.phpにアップロードする。

Executing 'convert -depth 8 /tmp/php3Qt02x.pdf /tmp/php3Qt02x.ppm'
Executing 'ocrad /tmp/php3Qt02x.ppm'
Extracted text: "ECHO cat flag"
I'm OK with ECHO commands. Here is the signature: 
2a33067ffc44d04293adde3d16e7f1b7c813c808576e224406b5e648232f90d79c33fea1a9888140c5c8c7f9ccf1b5d87fcf09df8ff48b20bc7c6cb91667885075bbaac3d20594ef3d357e1ee9e1ede9f00809d91bbf6961e80b2ef890f581c14e815a1b8cd0b56c50afb87f4422b1a029bbbbc9636c055bcf6ae018c1bddb053bf89e779290973451cb7333e019a1ee344d1ebf2d5c6a30ea19ea7db0982b71f980eecc36dbb813eed1874272ac4eebb754b78dd364b10d4b3ea46836d86d870092023f6b430e6a3531317f5c09d5f591daaf6197942cc2d8c0a428081b455902d0028b7a203cd627b9e7209ed9e3a81c1bc1a2cbaf3ae1140a527c56429936

execute-collision.pdfをexecute.phpにアップロードする。署名文字列は上記を使用。

Signature is OK.
Executing 'convert -depth 8 /tmp/phpbZ3uXB.pdf /tmp/phpbZ3uXB.ppm'
Executing 'ocrad /tmp/phpbZ3uXB.ppm'
Text: "EXECUTE cat flag"
About to execute: "cat flag".
Output: OOO{phP_4lw4y5_d3l1v3r5_3h7_b35T_fl4g5}