สวัสดีครับวันนี้ผมอยากจะเอาทริคเล็กๆน้อยๆมาฝากกัน เป็นเงื่อนไขที่ว่า ถ้าเราสามารถควบคุมส่วน host ของ mysql ของระบบที่ทดสอบไปเชื่อมต่อที่ไหนก็ได้จะมีผลเป็นอย่างไร
MySQL Arbitrary File Read
พอดีระหว่างการทำ Pentest ผมไปอ่านเจอใน Slide งานๆนึงอธิบายเกี่ยวกับการโจมตีที่น่าสนใจเอาไว้ว่าถ้าเราสามารถควบคุมให้ Mysql client มาเชื่อมต่อที่ Mysql server ของเราได้เราจะสามารถอ่านไฟล์บน client นั้นๆ เมื่อลองค้นไปเรื่อยๆพบว่าวิธีการโจมตีถูกระบุไว้บน Mysql Document ในส่วนการเตือนเรื่องความปลอดภัยของคำสั่ง LOAD DATA LOCAL ตามนี้
6.1.6 Security Considerations for LOAD DATA LOCAL
The LOAD DATA statement loads a data file into a table. The statement can load a file located on the server host, or, if the LOCAL keyword is specified, on the client host.
The LOCAL version of LOAD DATA has two potential security issues:
- Because LOAD DATA LOCAL is an SQL statement, parsing occurs on the server side, and transfer of the file from the client host to the server host is initiated by the MySQL server, which tells the client the file named in the statement. In theory, a patched server could tell the client program to transfer a file of the server’s choosing rather than the file named in the statement. Such a server could access any file on the client host to which the client user has read access. (A patched server could in fact reply with a file-transfer request to any statement, not just LOAD DATA LOCAL, so a more fundamental issue is that clients should not connect to untrusted servers. )
- In a Web environment where the clients are connecting from a Web server, a user could use LOAD DATA LOCAL to read any files that the Web server process has read access to (assuming that a user could run any statement against the SQL server). In this environment, the client with respect to the MySQL server actually is the Web server, not a remote program being run by users who connect to the Web server.
To avoid connecting to untrusted servers, clients can establish a secure connection and verify the server identity by connecting using the — ssl-mode=VERIFY_IDENTITY option and the appropriate CA certificate.
To avoid LOAD DATA issues, clients should avoid using LOCAL unless proper client-side precautions have been taken.
For control over local data loading, MySQL permits the capability to be enabled or disabled. In addition, as of MySQL 8.0.21, MySQL enables clients to restrict local data loading operations to files located in a designated directory.
เมื่อสรุปคร่าว ๆ จะได้ว่าในทางทฤษฎีแล้ว เราสามารถ Patch MySQL Server ให้สามารถร้องขอไฟล์จาก Client ได้แม้ว่า Client ไม่ได้ส่งคำสั่งอย่าง LOAD DATA LOCAL มา what??? เพราะฉะนั้นจึงเตือนว่า เราไม่ควรที่จะลอง Connect กับ Mysql Server ที่ไม่รู้ที่มาที่ไปไฟล์อาจจะโดนขโมยเพราะกลายเป็นว่าดันไปต่อ Honeypot คนอื่นเค้าแทน
เมื่อค้นต่อไปเรื่อยๆผมพบว่ามีคนเขียน script ที่จำลองตัวเองว่าเป็น MySQL Server และไม่ว่า Client จะส่ง Query อะไรมาก็จะส่ง Request เพื่ออ่านไฟล์เท่านั้น https://github.com/Gifts/Rogue-MySql-Server
พอได้ script การโจมตีแล้ว ผมจึงลองสร้างทดสอบโจมตีดังนี้
Case #1: PhpMyAdmin + AllowArbitraryServer
ปกติ PhpMyAdmin จะถูก set ไม่ให้ต่อไป server อื่นๆ แต่ถ้ามันมีบาง server ที่มีการ set ให้ต่อไปได้หลายๆที่ล่ะ
มีคนทำ Labs ไว้ให้เล่นแล้วที่ https://www.vsplate.com/?github=vulnspy/phpmyadmin-4.8.4-allowarbitraryserver เราสามารถกดแล้วเข้าไปลองเล่นได้เลย
ขั้นตอนแรกคือการสร้าง instance สำหรับ labs
ทำการรัน Patch mysql server เพื่ออ่านไฟล์ที่เตรียมไว้
จากนั้นเราสามารถใช้ ngrok เพื่อให้ phpMyAdmin มาต่อที่ server ของเราได้
ทำการใส่ server เพื่อ connect มาที่ patched mysql server ที่เตรียมไว้
เมื่อ PhpMyAdmin มาเชื่อมต่อจะพบว่าไม่ว่า Client จะทำการส่ง request อะไรเข้ามา Server เราจะบอกว่าไปอ่านไฟล์ XXX มาให้หน่อย และ Client ก็จะไปอ่านและส่งกลับมาให้เรา What??
ซึ่งเมื่อลอง Follow TCP Stream ใน Wireshark จะเห็นภาพได้มาขึ้นโดย
— สีแดง คือ PhpMyAdmin
— สีฟ้า คือ Patched MySQL Server
Case #2: Adminer < 4.6.3
ตัว Script Adminer ผู้พัฒนาอาจจะมีความตั้งใจที่ออกแบบมาให้มีความ lightweight และ deploy ง่าย จึงมีความ secure พอประมาณแต่ไม่ได้มากเท่า phpMyAdmin เพราะงั้น By Default จึงมีอาจจะช่องโหว่เป็นเรื่องปกติ บางครั้งเมื่อมีการทดสอบระบบเราอาจใช้ Directory Bruteforce แล้วไปเจอไฟล์ adminer.php แต่ทำอะไรไม่ได้มากเพราะเจอแค่หน้า Login แต่ถ้าเรามาลองทำการทดสอบโจมตี Adminer ด้วยวิธีเดียวกัน
- เริ่มแรกเราทำการใส่ Server ให้เป็น Patched Mysql Server ที่เราจะทำการส่ง Packet เพื่ออ่านไฟล์ และใส่ username กับ password เป็นค่าอะไรก็ได้
2. เมื่อกด Login จะพบว่าตัวระบบมองว่ามีการทำ authenticate สำเร็จ และพบจาก error message ว่ามีการส่ง Query “SELECT USER()”
3. หลังจากเข้าไปดูใน Wireshark จะพบว่ามีการส่ง Packet เพื่ออ่านไฟล์ /etc/passwd ของระบบ
Case #3: PHP < 7.3.4 Library
ผมลองทดสอบกับ php version 7.1.33 พบว่าถ้าใช้ Extension MySQLi โจมตีได้เหมือนกัน
แต่พอเปลี่ยนเป็นเขียนโดยใช้ Extension PDO พบว่ากลับมีการป้องกันไว้
อื่นๆ
เนื่องจากผมยังลองไม่ครบ แต่ระหว่างเขียนผมมีไอเดียว่าถ้า Mysql Client รันอยู่บน Windows เราอาจจะใช้เทคนิคนี้เพื่อต่อยอดไป Access file ผ่าน SMB ใน Internal Network หรือใช้เพื่อทำ SMB Relay ได้ต่อไป ถ้าใครลองแล้วได้ไม่ได้ยังไง คอมเม้นบอกผมได้นะครับ
แนวทางการป้องกัน
จาก Document แนะนำไว้ว่า
- ตั้งแต่ Mysql version 8.0.21 สามารถ config เพื่อป้องกันให้ client สามารถอ่านไฟล์ได้ภายใต้ Directory ที่กำหนดไว้เท่านั้นได้
- สามารถ Build Library เพื่อให้ปิดการอ่านไฟล์ไปเลยได้เช่นกัน โดยใช้ flag -DENABLED_LOCAL_INFILE=False
- สำหรับ PHP MySQLi ต่ำกว่า 7.2.16 หรือ 7.3.3 สามารถ config php.ini เพื่อไม่อนุญาติให้อ่านไฟล์ได้ที่ mysqli.allow_local_infile = Off
Ref.
- https://dev.mysql.com/doc/refman/8.0/en/load-data-local-security.html
- https://www.slideshare.net/qqlan/database-honeypot-by-design-25195927
- http://russiansecurity.expert/2016/04/20/mysql-connect-file-read/
- https://w00tsec.blogspot.com/2018/04/abusing-mysql-local-infile-to-read.html