zoukankan      html  css  js  c++  java
  • 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
    ";
        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("
    ", $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
    ";
        }
        //将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:5:"admin";
    DEBUG: Saving in /var/lib/php5/sessions//mysess_9l3s4uq5gqk1u5c3mk8gti5sr6
    DEBUG: name => admin
    
    DEBUG: 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("
    ", $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
    ";
        }
        //将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 admin 1 。所以应该输入xxx admin 1,将其进行URL编码后进行提交。

    换行符对应的URL编码为%0A,所以最终应该输入xxx%0Aadmin 1提交。

    当然不能在网页中直接输入xxx%0Aadmin 1提交,因为会被编码成xxx%250Aadmin+1,失去了我们的本意。

    正确的做法是,使用burp抓包,修改name参数值为xxx%0Aadmin 1,第一次会显示regular,因为没有文件/状态可以读取,session里还是没有Admin的,会话关闭后xxx admin 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

  • 相关阅读:
    转:git上传本地项目到github
    转:git常用操作
    idea常用快捷键
    转:spring中InitailizingBean接口的简单理解
    resulting in duplicate entry '1' for key 'primary'
    Uncaught SyntaxError: Unexpected identifier
    This application has no explicit mapping for /error, so you are seeing this as a fallback.
    启动web项目报错:The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone.
    解决 "Could not autowire. No beans of 'SationMapper' type found" 的问题
    javascript 判断系统设备
  • 原文地址:https://www.cnblogs.com/zhengna/p/12330335.html
Copyright © 2011-2022 走看看