sourcecode核心代码:
1 <? 2 3 function debug($msg) { 4 if(array_key_exists("debug", $_GET)) { 5 print "DEBUG: $msg<br>"; 6 } 7 } 8 9 function print_credentials() { 10 if($_SESSION and array_key_exists("admin", $_SESSION) and $_SESSION["admin"] == 1) { 11 print "You are an admin. The credentials for the next level are:<br>"; 12 print "<pre>Username: natas21\n"; 13 print "Password: <censored></pre>"; 14 } else { 15 print "You are logged in as a regular user. Login as an admin to retrieve credentials for natas21."; 16 } 17 } 18 19 20 /* we don‘t need this */ 21 function myopen($path, $name) { 22 //debug("MYOPEN $path $name"); 23 return true; 24 } 25 26 /* we don‘t need this */ 27 function myclose() { 28 //debug("MYCLOSE"); 29 return true; 30 } 31 32 function myread($sid) { 33 debug("MYREAD $sid"); 34 if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) { 35 debug("Invalid SID"); 36 return ""; 37 } 38 $filename = session_save_path() . "/" . "mysess_" . $sid; 39 if(!file_exists($filename)) { 40 debug("Session file doesn‘t exist"); 41 return ""; 42 } 43 debug("Reading from ". $filename); 44 $data = file_get_contents($filename); 45 $_SESSION = array(); 46 foreach(explode("\n", $data) as $line) { 47 debug("Read [$line]"); 48 $parts = explode(" ", $line, 2); 49 if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1]; 50 } 51 return session_encode(); 52 } 53 54 function mywrite($sid, $data) { 55 // $data contains the serialized version of $_SESSION 56 // but our encoding is better 57 debug("MYWRITE $sid $data"); 58 // make sure the sid is alnum only!! 59 if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) { 60 debug("Invalid SID"); 61 return; 62 } 63 $filename = session_save_path() . "/" . "mysess_" . $sid; 64 $data = ""; 65 debug("Saving in ". $filename); 66 ksort($_SESSION); 67 foreach($_SESSION as $key => $value) { 68 debug("$key => $value"); 69 $data .= "$key $value\n"; 70 } 71 file_put_contents($filename, $data); 72 chmod($filename, 0600); 73 } 74 75 /* we don‘t need this */ 76 function mydestroy($sid) { 77 //debug("MYDESTROY $sid"); 78 return true; 79 } 80 /* we don‘t need this */ 81 function mygarbage($t) { 82 //debug("MYGARBAGE $t"); 83 return true; 84 } 85 86 session_set_save_handler( 87 "myopen", 88 "myclose", 89 "myread", 90 "mywrite", 91 "mydestroy", 92 "mygarbage"); 93 session_start(); 94 95 if(array_key_exists("name", $_REQUEST)) { 96 $_SESSION["name"] = $_REQUEST["name"]; 97 debug("Name set to " . $_REQUEST["name"]); 98 } 99 100 print_credentials(); 101 102 $name = ""; 103 if(array_key_exists("name", $_SESSION)) { 104 $name = $_SESSION["name"]; 105 } 106 107 ?> 108 109 <form action="index.php" method="POST"> 110 Your name: <input name="name" value="<?=$name?>"><br> 111 <input type="submit" value="Change name" /> 112 </form>
关键部分已经用红色标出,mywrite和myread是两个管理会话状态的关键函数,以下是一些参考资料:
http://php.net/manual/zh/function.session-set-save-handler.php
http://php.net/manual/zh/function.strspn.php
http://www.w3school.com.cn/php/func_string_explode.asp
http://php.net/manual/zh/function.session-encode.php
简单来说,myread首先对sid(第一次由服务器自动生成并保存在cookie中)进行校验,若非字母/数字则不返回会话状态。
若sid合法,则进入相关目录寻找/读取文件,若是老的会话/文件已经删除会新建文件保存会话,文件读取完后将session的最后一对键值覆盖到第一的位置。
mywrite则会在会话结束的时候重新读取session,并对session进行一次ksort,将排序后的键值对重新写入文件。
一开始我的思路是通过使sid带有目录路径(改cookie),使得debug输出的时候将/etc/....../natas20的文件内容读出来,但是在myread和mywrite里面都进行了检验,所以很难通过sid进行注入。
另一个思路就是,源码里面其实没有向session里面添加admin的键值对,这也提示我们可以通过“唯一”的一个name键值对进行注入:将data里面的值变为:name xxxx\nadmin 1\n
如何添加这个\n呢?既然通过get方式提交,也就要用URL编码编码后进行提交。(这里有个插曲,一开始我对mywrite的写入和myread的读取没有理解好,以为注入name xxx admin 1就可以了,实际上如果让mywrite这么写入,myread读取会话状态时$parts = explode(" ", $line, 2),只会将这仅有的一行的第一个键值对name xxx添加到session里面)。\n对应%0A (\r对应%0D,但在linux下用换行号就够了)。所以应该输入xxx\nadmin 1,第一次会显示regular,因为没有文件/状态可以读取,session里还是没有Admin的,会话关闭后xxx\nadmin1就会被写入到状态中,下次登录后session就会被加入admin 1了。
但是,这里的输入并不能直接在form中输入并提交:
因为\n即回车并不会被编码为%0A这个字节存入到file中,可以直接url中输入并提交或者改postbody。
http://natas20.natas.labs.overthewire.org/index.php?name=xxx%0Aadmin%201&debug=1(%20为空格 直接空格也行)
总结:如果用户的输入要保存为会话状态,服务端读取和写入会话时要进行防注入处理,,否则存在提权或劫持等注入问题。
flag:IFekPyrQXftziDEsUr3x21sYuahypdgJ