
正文
Natas20 Writeup(Session登录,注入参数)
提示:扫一扫查出行【扫一扫了解最新限行尾号】
复制提示
Natas20:

读取源码,发现把sessionID存到了文件中,按键值对存在,以空格分隔,如果$_SESSION["admin"]==1,则成功登陆,得到flag。并且通过查询所提交的参数,也会被存到文件中,因此,可以采取注入键值对admin 1的方式来实现修改。
使用burp抓包,将name参数修改为:name=xxx%0Aadmin 1,得到flag。
源码解析:
<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": "natas20", "pass": "<censored>" };</script></head>
<body>
<h1>natas20</h1>
<div id="content">
<?//debug($msg)表示打开了调试信息,可以通过在URL的末尾添加 /index.php?debug来查看调试消息 $msg
function debug($msg) { /* {{{ */
//php中预定义的 $_GET 变量用于收集来自 method="get" 的表单中的值。
//array_key_exists(key,array)函数检查键名是否存在于数组中,如果键名存在则返回 TRUE,如果键名不存在则返回 FALSE。
if(array_key_exists("debug", $_GET)) {
print "DEBUG: $msg<br>";
}
}
/* }}} */
function print_credentials() { /* {{{ */
//主要功能就是判断session[admin]=1后显示密码
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");
//strspn(string,charlist):返回在字符串string中包含charlist中字符的数目
//判断sid是否是由数字/字母/-组成,如果不是,则无效
if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
debug("Invalid SID");
return "";
}
//session_save_path() - 返回当前会话的保存路径。
$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();
//使用换行符分割data数据
foreach(explode("\n", $data) as $line) {
debug("Read [$line]");
//使用空格符分割line数据,返回的数组包含2个元素,最后那个元素包含line的剩余部分
$parts = explode(" ", $line, 2);
if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
}
//session_encode — 将当前会话数据编码为一个字符串
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!!
//判断sid是否是由数字/字母/-组成,如果不是,则无效
if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
debug("Invalid SID");
return;
}
$filename = session_save_path() . "/" . "mysess_" . $sid;
$data = "";
debug("Saving in ". $filename);
//ksort — 对数组按照键名排序
ksort($_SESSION);
//foreach 遍历给定的数组。每次循环中,当前单元的键名被赋给$key、当前单元的值被赋给$value,并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。
foreach($_SESSION as $key => $value) {
debug("$key => $value");
//给data赋值
$data .= "$key $value\n";
}
//将data存在文件中
file_put_contents($filename, $data);
//改变文件模式:Read and write for owner, nothing for everybody else
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"];
}?><form action="index.php" method="POST">
Your name: <input name="name" value="<?=$name?>"><br>
<input type="submit" value="Change name" />
</form>
<div id="viewsource"><a href="index-source.html">View sourcecode</a></div>
</div>
</body>
</html>
natas20-sourcecode.html
我们来看看每个函数的作用:
debug($msg)函数表示打开了调试信息,可以通过在URL的末尾添加 /index.php?debug来查看调试消息 $msg。
访问之后将看到一些提示信息:
DEBUG: MYWRITE 9l3s4uq5gqk1u5c3mk8gti5sr6 name|s::"admin";
DEBUG: Saving in /var/lib/php5/sessions//mysess_9l3s4uq5gqk1u5c3mk8gti5sr6
DEBUG: name => adminDEBUG: MYREAD 9l3s4uq5gqk1u5c3mk8gti5sr6
DEBUG: Reading from /var/lib/php5/sessions//mysess_9l3s4uq5gqk1u5c3mk8gti5sr6
DEBUG: Read [name admin]
DEBUG: Read []

mywrite和myread是两个关键函数,它们的作用是管理会话状态。
function myread($sid) {
debug("MYREAD $sid");
//strspn(string,charlist):返回在字符串string中包含charlist中字符的数目
//判断sid是否是由数字/字母/-组成,如果不是,则无效
if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
debug("Invalid SID");
return "";
}
//session_save_path() - 返回当前会话的保存路径。
$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();
//使用换行符分割data数据
foreach(explode("\n", $data) as $line) {
debug("Read [$line]");
//使用空格符分割line数据,返回的数组包含2个元素,最后那个元素包含line的剩余部分
$parts = explode(" ", $line, 2);
if($parts[0] != "") $_SESSION[$parts[0]] = $parts[1];
}
//session_encode — 将当前会话数据编码为一个字符串
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!!
//判断sid是否是由数字/字母/-组成,如果不是,则无效
if(strspn($sid, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM-") != strlen($sid)) {
debug("Invalid SID");
return;
}
$filename = session_save_path() . "/" . "mysess_" . $sid;
$data = "";
debug("Saving in ". $filename);
//ksort — 对数组按照键名排序
ksort($_SESSION);
//foreach 遍历给定的数组。每次循环中,当前单元的键名被赋给$key、当前单元的值被赋给$value,并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。
foreach($_SESSION as $key => $value) {
debug("$key => $value");
//给data赋值
$data .= "$key $value\n";
}
//将data存在文件中
file_put_contents($filename, $data);
//改变文件模式:Read and write for owner, nothing for everybody else
chmod($filename, 0600);
}
简单来说,myread首先对sid(第一次由服务器自动生成并保存在cookie中)进行校验,若非字母/数字则不返回会话状态。
若sid合法,则进入相关目录寻找/读取文件,若是老的会话/文件已经删除会新建文件保存会话,文件读取完后将session的最后一对键值覆盖到第一的位置。
mywrite则会在会话结束的时候重新读取session,并对session进行一次ksort,将排序后的键值对重新写入文件。
print_credentials()函数的主要功能,是判断$_SESSION["admin"] == 1后显示密码。
由于源码里面没有向$SESSION里面添加admin的键值对,默认情况下,$_SESSION中唯一的key是name,其值通过/index.php中的表单提交进行设置。
我们可以通过对name键值对进行注入:将data里面的值变为:name xxx\nadmin 1\n。所以应该输入xxx\nadmin 1,将其进行URL编码后进行提交。
换行符对应的URL编码为%0A,所以最终应该输入xxx%0Aadmin 1提交。
当然不能在网页中直接输入xxx%0Aadmin 1提交,因为会被编码成xxx%250Aadmin+1,失去了我们的本意。
正确的做法是,使用burp抓包,修改name参数值为xxx%0Aadmin 1,第一次会显示regular,因为没有文件/状态可以读取,session里还是没有Admin的,会话关闭后xxx\nadmin 1就会被写入到状态中,下次登录后session就会加入admin 1了。

flag:IFekPyrQXftziDEsUr3x21sYuahypdgJ
参考:
https://www.cnblogs.com/ichunqiu/p/9554885.html
https://www.cnblogs.com/liqiuhao/p/6882068.html
https://www.freebuf.com/column/182518.html






