2023FCTF

本文最后更新于:10 个月前

2023FCTF

热身赛

Web

ViewSource

一道简单的前端题,根据提示ViewSource,ctrl+u查看源代码,这里网页代码被加密过了,直接往下看发现可疑段落

分析逻辑,用户输入your_flag,如果your_flagmy_flag相同,则弹窗my_flag

提供两种做法

既然my_flag变量在js代码中定义,那么我们就能用控制台把它输出

ctrl+s保存网页源代码用编辑器打开

修改代码逻辑,如果用户输入的your_flag与实际flag不相等则弹窗my_flag,保存,打开html文件往输入框里随便输个什么提交


javaDeserialize-1

点开题目,


javaDeserialize-2


filechecker_mini

打开题目,让我们上传一个文件:

桌面上随便丢了个php文件进去提交看看会有啥情况:

判断文件类型,(MIME绕过预定)

附件下载下来先看源码:

index.html:

可以看出该网页使用模块渲染将result值渲染进index对应位置,那么就看下后端代码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
from flask import Flask, request, render_template, render_template_string
from waitress import serve
import os
import subprocess

app_dir = os.path.split(os.path.realpath(__file__))[0]
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = f'{app_dir}/upload/'

@app.route('/', methods=['GET','POST'])
def index():
try:
if request.method == 'GET':
return render_template('index.html',result="ヽ(=^・ω・^=)丿 ヽ(=^・ω・^=)丿 ヽ(=^・ω・^=)丿")

elif request.method == 'POST':
f = request.files['file-upload']
filepath = os.path.join(app.config['UPLOAD_FOLDER'], f.filename)

if os.path.exists(filepath) and ".." in filepath:
return render_template('index.html', result="Don't (^=◕ᴥ◕=^) (^=◕ᴥ◕=^) (^=◕ᴥ◕=^)")
else:
f.save(filepath)
file_check_res = subprocess.check_output(
["/bin/file", "-b", filepath],
shell=False,
encoding='utf-8',
timeout=1
)
os.remove(filepath)
if "empty" in file_check_res or "cannot open" in file_check_res:
file_check_res="wafxixi ฅ•ω•ฅ ฅ•ω•ฅ ฅ•ω•ฅ"
return render_template_string(file_check_res)

except:
return render_template('index.html', result='Error ฅ(๑*д*๑)ฅ ฅ(๑*д*๑)ฅ ฅ(๑*д*๑)ฅ')

if __name__ == '__main__':
serve(app, host="0.0.0.0", port=3000, threads=1000, cleanup_interval=30)

上面大家都用render_template()就你爱用render_template_string()是吧(指指点点,一眼模板注入,那么我们就希望file_check_res里有我们能够执行的rce代码。file_check_res哪来的?倒退往上看。

1
2
3
4
5
6
7
8
f.save(filepath)
file_check_res = subprocess.check_output(
["/bin/file", "-b", filepath],
shell=False,
encoding='utf-8',
imeout=1
)
os.remove(filepath)

先保存filepath这样一个文件,subprocess.check_output(command)返回Linux命令行输出,然后再把filepath文件删除,那么这里的file_check_res就是file -b {filepath}的结果。往上看filepath其实就是将上传文件目录的绝对路径和该文件的文件名拼接起来来标定用户上传的这个文件在容器中的绝对位置。逻辑搞明白了,现在的重点就在于如何对一个文件使用file -b 命令后返回值中能回显我们所期望的值。动手操作下flie命令,发现其不会输出文件的内容只会输出其类型,

代码中的-b参数作用:

1
-b  #列出辨识结果时,不显示文件名称。

所以在文件名上动手脚的想法也破灭了(悲。

卡住了,向大佬博客寻求帮助,去guthub查找file命令源码。第一个仓库点开。

点开tests里面是各种针对该file命令的测试结果

这8个分别分别是在文本中写入bash脚本的4种情况和对应的用file命令执行的输出结果,可以看出如果文本内容为#!/usr/bin开头的那么输出结果中会显示文本中的其他内容。

本地做测试:创建一个文本文件修改内容如下

测试结果如下:

显而易见,输出可控,可以进行模板渲染。新建一个文本文件内容如下,上传文件

存在ssti漏洞,开始利用,调用os模块

调用popen()方法。

上传文件,获取flag。

Misc

png

附件下载下来是一张png图片,

010打开划拉到最底部发现冗余数据,部分flagFCTF{To

010跑PNGTemplate.bt脚本报错,左下提示CRC不匹配,说明修改了高宽却没有修改CRC导致读取报错,图片宽高很可能被修改过。(或者放入kali中无法打开)

进行一个高度的改,将高度640改为700

文件头数据块IHDR包含的第一部分数据就是图片宽高,分别对应第二行中的第一组四个字节和第二组四个字节,掏出计算器算算高度拉长点。

得到另一部分flag:_the_flawless_

然后就是做这道题时比较懵逼的一个地方了,招最后一部分flag。先开起zsteg看看能拿到什么吧。查到了之前找到的FCTF{To,但是愣是没发现最后一部分flag,如果有LSB隐写那zsteg也应该能淦出来才对呀?

由于过于依赖工具,死磕这条路坚信不存在lsb隐写。愣是没用Stegsolve手搓,到处找,查IDAT块、翻EXIF信息…最后还得感谢ixout手搓LSB出来。

呜呜呜呜呜呜呜呜呜呜。

最后三段flag拼接起来得到flag。

呜呜呜呜呜呜呜呜呜呜。

cet6

一道基础的USB取证题,但是不太常规。。。

zip套娃

第一层:

binwalk无法分离,不是伪加密。没给其他条件明文攻击也不太行,那就爆破试试吧(。

ARCHPR开起来,攻击方式字典,选的是kali字典。跑了一段时间后成功拿到第一层密码。

第二层:

依然先丢到kali里binwalk试下,分离成功,伪加密

分离出来的东西多了个0.zip打开其实就是第二层的包不过问题不大,直接看第三层的压缩包

第三层:

打开压缩包看到了一个支点.txt文件,同时第二层解压出来后也有一个支点.txt,支点.txt文件大小大于12字节,大胆猜测是明文攻击。

WinRAR将泄密出来的文件压缩为zip,开始明文攻击,然后就。。。

相信不止我一个人遇到这种情况。。。这里我们忽略了一些细节,加密文件是通过什么方式压缩的呢?不同压缩软件采用的压缩算法也会不同,自然会出现不匹配的情况,这里多尝试几次,鼠标右键发送到压缩文件可行,开始明文攻击

等待了一段时间之后拿到秘钥(btw这一坨是啥。。。)

第四层:

字典、明文、伪加密,各种姿势都试过了,打开压缩包看一眼,分散成这么多小文件,大胆猜测CRC碰撞

EXIF查看一下压缩文件数据,CRC32、字节数等等

大致整理下6个文本文件的CRC32、字节数:

文件名 CRC32 字节数
1.txt 0x92716b7c 5
2.txt 0x1ab6bb72 4
3.txt 0xfcf21afd 3
4.txt 0x89a155cb 5
5.txt 0x2d09a3d6 6
6.txt 0xe3f20a9d 2

1-3字节的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'''
1byte
'''

import binascii
import string

def crack_crc():
print('-------------Start Crack CRC-------------')
crc_list = [0xda6fd2a0, 0xf6a70, 0x70659eff, 0x862575d]#文件的CRC32值列表,注意顺序
comment = ''
chars = string.printable
for crc_value in crc_list:
for char1 in chars:
char_crc = binascii.crc32(char1.encode())#获取遍历字符的CRC32值
calc_crc = char_crc & 0xffffffff#将获取到的字符的CRC32值与0xffffffff进行与运算
if calc_crc == crc_value:#将每个字符的CRC32值与每个文件的CRC32值进行匹配
print('[+] {}: {}'.format(hex(crc_value),char1))
comment += char1
print('-----------CRC Crack Completed-----------')
print('Result: {}'.format(comment))

if __name__ == '__main__':
crack_crc()
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
'''
2bytes
'''

import binascii
import string

def crack_crc():
print('-------------Start Crack CRC-------------')
crc_list = [0xe3f20a9d]#文件的CRC32值列表,注意顺序
comment = ''
chars = string.printable
for crc_value in crc_list:
for char1 in chars:
for char2 in chars:
res_char = char1 + char2#获取遍历的任意2Byte字符
char_crc = binascii.crc32(res_char.encode())#获取遍历字符的CRC32值
calc_crc = char_crc & 0xffffffff#将获取到的字符的CRC32值与0xffffffff进行与运算
if calc_crc == crc_value:#将获取字符的CRC32值与每个文件的CRC32值进行匹配
print('[+] {}: {}'.format(hex(crc_value),res_char))
comment += res_char
print('-----------CRC Crack Completed-----------')
print('Result: {}'.format(comment))

if __name__ == '__main__':
crack_crc()

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
'''
3bytes
'''
import binascii
import string

def crack_crc():
print('-------------Start Crack CRC-------------')
crc_list = [0x92716b7c, 0x1ab6bb72, 0xfcf21afd, 0x89a155cb, 0x2d09a3d6, 0xe3f20a9d]#文件的CRC32值列表,注意顺序
comment = ''
chars = string.printable
for crc_value in crc_list:
for char1 in chars:
for char2 in chars:
for char3 in chars:
res_char = char1 + char2 + char3#获取遍历的任意3Byte字符
char_crc = binascii.crc32(res_char.encode())#获取遍历字符的CRC32值
calc_crc = char_crc & 0xffffffff#将遍历的字符的CRC32值与0xffffffff进行与运算
if calc_crc == crc_value:#将获取字符的CRC32值与每个文件的CRC32值进行匹配
print('[+] {}: {}'.format(hex(crc_value),res_char))
comment += res_char
print('-----------CRC Crack Completed-----------')
print('Result: {}'.format(comment))

if __name__ == '__main__':
crack_crc()

4-6字节的使用theonlypwner工具,修改供选字符

1
2
permitted_characters = set(
map(ord, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_{}!')) # \w

FTCF{

Y0u_

_z1p_

master

拼接起来就是FTCF{Y0u_4re_z1p_master!}

使用theonlypwner时出了点问题,最开始忘记了flag中必含的”{“”}”字符,导致怎么都跑不出来,还是看了眼hint想起来应该修改脚本中的供选字符。如果感叹号是在4-6字节的那些文件的话肯定也跑不出来了,考虑问题还是不够周到。

(大佬的博客:https://www.cnblogs.com/ECJTUACM-873284962/p/9884416.html

(CTFwiki:https://ctf-wiki.org/misc/archive/zip/#_7)


正式赛

Web

Misc

Puzzle

附件如图所示:

一整张图构成极其简单,想要藏什么数据的话大概率只能是IDAT块隐写了,pngcheck查看下

9个IDAT块,块长度没有隐藏什么特殊信息,一张正常的png图片的IDAT块的前面的块应该都是填充满且相同大小的,这个check结果显然就不正常,感觉每一个块都单独成图。试着删除块。

tweakpng:

准备删IDAT块,九个块就拷贝九份先。删块的时候不要把IEND块删了,这是png格式的结尾标识。

每个IDAT块单独成一张图片,已经很明显了,是一张二维码。

自己对于二维码结构的认知只有三个定位块(悲),所以瞎拼,然后都没法扫(,根据学长放的hint

归纳就是三个定位块的周围一个像素块的一圈必须是空白的,且相互之间有黑白像素块交叉分布的定时标志,根据特征拿到ps里拼了下

扫一下,拿到flag:

FCTF{n1ce_puzzl3}

自信音游人

附件是给的曲目选自阿卡伊的だいあるのーと。