หน้าเว็บ

วันพฤหัสบดีที่ 12 กุมภาพันธ์ พ.ศ. 2558

In-Depth Analysis: phpMyAdmin /scripts/setup.php Remote Code Execution


       
          จบไปประมาณเกือบ 1 สัปดาห์แล้วสำหรับงาน MHCon2015 หรือ Meet the Hackers Conference 2015 เป็นงานที่มีวิทยากรที่มีความรู้ความสามารถด้าน Cybersecurity มาพูดบรรยายให้ฟังกัน ซึ่งท้ายๆงานที่วิทยากรบรรยายหัวข้อ Exploit for Fun and Profit Vulnerabilities Types and Exploit Scenarios Demo (vulnerability exploitation) โดยผมจะนำช่องโหว่ของ phpMyAdmin ที่ติดมากับ Appserv เวอร์ชั่น 2.5.10 ได้รหัส PMASA-2010-3 จนถึงวันนี้ก็ประมาณ 4-5 ปีแล้วมาอธิบายครับ
          ช่องโหว่นี้เป็นช่องโหว่ประเภท PHP Object Injection ซึ่งตามที่เข้าใจนั้น PHP Object Injection เป็นช่องโหว่ที่ เมื่อโจมตีส่งข้อมูลผ่านทาง Input ต่างๆ ไม่ว่าจะเป็น GET, POST, COOKIE, HTTP Header หรืออื่นๆ เข้ามายังฟังก์ชั่น unserialize โดยฟังก์ชั่น unserialize จะสามารถกำหนดหรือสร้าง Object ที่เป็น Instance ของ Class ที่มีอยู่ได้ ส่วนการทำงานของ Class ก็จะมีการไปเรียก Magic Method อื่นให้ทำงานอัตโนมัติ ตัวอย่าง __wakeup, __toString เป็นต้น ส่วนรายละเอียดแต่ละ Magic Method นั้นลองหาอ่านดูครับ
         กลับมาที่ช่องโหว่ของ phpMyAdmin เริ่มจากไฟล์ที่มีช่องโหว่นั้นอยู่ที่ /scripts/setup.php โดยผู้โจมตีจะส่ง POST data มาที่ไฟล์นี้
if (isset($_POST['configuration']) && $action != 'clear' ) {
    // Grab previous configuration, if it should not be cleared
    $configuration = unserialize($_POST['configuration']);
} else {
    // Start with empty configuration
    $configuration = array();
}

          มีการรับค่า $_POST['configuration'] มาใส่ฟังก์ชั่น unserialize เพื่อกำหนดค่าในตัวแปร $source ของคลาส PMA_Config ในไฟล์ /libraries/Config.class.php
class PMA_Config
{
    /**
     * @var string  default config source
     */
    var $default_source = './libraries/config.default.php';

    /**
     * @var array   configuration settings
     */
    var $settings = array();

    /**
     * @var string  config source
     */
    var $source = '';

          สังเกตุตัวแปร $source ว่างเปล่า การโจมตีจะทำการกำหนดค่าในตัวแปรนี้ คลาส PMA_Config ทำงาน และจะมีการไปเรียก __wakeup() มาใช้งานทันที
    /**
     * re-init object after loading from session file
     * checks config file for changes and relaods if neccessary
     */
    function __wakeup()
    {
        if (! $this->checkConfigSource()
          || $this->source_mtime !== filemtime($this->getSource())
          || $this->default_source_mtime !== filemtime($this->default_source)
          || $this->error_config_file
          || $this->error_config_default_file) {
            $this->settings = array();
            $this->load();
            $this->checkSystem();
        }

          ฟังก์ชั่น load() จะมี $source=null โดย default อยู่แล้ว แต่ $source ถูกกำหนดให้เป็นพาธของไฟล์ที่ต้องการอ่านค่า เพื่อใส่ลงฟังก์ชั่น eval
    /**
     * loads configuration from $source, usally the config file
     * should be called on object creation and from __wakeup if config file
     * has changed
     *
     * @param   string $source  config file
     */
    function load($source = null)
    {
        $this->loadDefaults();

        if (null !== $source) {
            $this->setSource($source);
        }

        if (! $this->checkConfigSource()) {
            return false;
        }

        $cfg = array();

        /**
         * Parses the configuration file
         */
        $old_error_reporting = error_reporting(0);
        if (function_exists('file_get_contents')) {
            $eval_result =
                eval('?>' . trim(file_get_contents($this->getSource())));
        } else {
            $eval_result =
                eval('?>' . trim(implode("\n", file($this->getSource()))));
        }
        error_reporting($old_error_reporting);

        if ($eval_result === false) {
            $this->error_config_file = true;
        } else  {
            $this->error_config_file = false;
            $this->source_mtime = filemtime($this->getSource());
        }

          ถ้ามีฟังก์ชั่น flie_get_contents จะส่งไป eval ที่บรรทัด 391 แต่ถ้าไม่มีจะส่งไปใช้ฟังก์ชั่น file ที่บรรทัด 394 โดยมีการใช้ฟังก์ชั่น eval กับ $this->getSource() ตามไปดูฟังก์ชั่น getSource จะมีการชี้ไปที่ $source เพื่อส่งค่าในตัวแปร $source กลับไปนั่นเอง
    /**
     * returns source for current config
     * @return  string  config source
     */
    function getSource()
    {
        return $this->source;
    }

          สรุปการโจมตีด้วย PHP Object Injection ใน phpMyAdmin นี้จะเป็นการกำหนดค่าใน $source ที่เป็น Object และไม่มีค่าอะไรอยู่ เมื่อไล่ตามโค้ดลงมา ค่าใน $source ก็จะไปอยู่ในฟังก์ชั่น eval ถ้าค่าในตัวแปร $source เป็นโค้ด PHP ก็สามารถรันโค้ด PHP นั้นๆ ได้
          สำหรับการแก้ไขก็เป็นการอัพเดทเป็นเวอร์ชั่นที่ใหม่กว่าอาจจะเป็นเวอร์ชั่น 4 หรือปัจจุบันครับ สำหรับแพทช์ [setup] avoid usage of (un)serialize, what might be unsafe in some cases
          นอกจากช่องโหว่นี้ยังมีช่องโหว่ PHP Object Injection จาก CMS อื่นๆซึ่งน่าสนใจเช่นกัน สุดท้ายนี้ ...


Ref: PHP Object Injection
Ref: phpMyadmin /scripts/setup.php Execute Arbitrary PHP Code Via unserialize Vul Object Injection
Ref: Shocking News in PHP Exploitation
Ref: 2600Thailand
Special Thank: sleepya, LongCat, Xelenonz

ไม่มีความคิดเห็น:

แสดงความคิดเห็น