Ο στόχος σας είναι να επιτεθείτε στον server project-2.csec.chatzi.org
.
Γνωρίζετε ότι στο url http://project-2.csec.chatzi.org:8000
τρέχει o pico webserver, ο κώδικας του οποίου
υπάρχει στο πακάτω repository.
Εχετε επίσης ήδη υποκλέψει:
- το username του site:
admin
- το password:
8c6e2f34df08e2f879e61eeb9e8ba96f8d9e96d8033870f80127567d270d7d96
(ο συγκεκριμένος webserver το δέχεται μόνο σε encrypted μορφή)
Tasks:
- Βρείτε το MD5 digest του plaintext password
- Βρείτε το plaintext password
- Βρείτε το περιεχόμενο του αρχείου
/etc/secret
στον server - Βρείτε το αποτέλεσμα της εντολής
lspci
στον server
-
Οι ίδιες ομάδες με την εργασία 1
-
Εγγραφή στο github: https://classroom.github.com/a/HxmDkdtS
-
Η ταχύτητα επίλυσης δεν έχει βαθμολογική σημασία, αλλά θα υπάρχει "leaderboard" με τους 3 πρώτους που λύνουν κάθε task καθαρά για λόγους "flexing". Αν είστε στους πρώτους στείλτε claim στο
ys13@chatzi.org
(αλλιώς δεν χρειάζεται). -
Τα βήματα μπορούν να λυθούν με οποιαδήποτε σειρά, δεν χρειάζεται η λύση του ενός για το επόμενο (αλλά προτείνεται η σειρά που δίνεται).
-
Hints:
- Task 1: πρέπει να χρησιμοποιήσετε μια απλή ευπάθεια στον C κώδικα
- Task 2: πρέπει να σπάσετε το encryption χρησιμοποιώντας μια ευπάθεια της υλοποίησης. Δεν πρέπει να κάνετε invert το digest από το task 1 (δεν θα το βρείτε σε MD5 databases, εκτός και αν κάποια άλλη ομάδα το βρει και το προσθέσει).
- Tasks 3/4: buffer overflow attack. Το attack στο task 4 είναι λίγο πιο δύσκολο (αν θέλετε μπορείτε να κάνετε τα δύο tasks μαζί, αλλά στο 3 υπάρχει και λίγο πιο εύκολη λύση).
-
Βαθμολογία μαθήματος
- Εργασία 1: 4 μονάδες
- Εργασία 2:
- Task 1: 1 μονάδα
- Task 2: 1 μονάδα
- Task 3: 2 μονάδες
- Task 4: 1 μονάδα
- Docker: 1 μονάδα
-
Στο τέλος του
README.md
: αναφέρετε τις απαντήσεις, και περιγράψτε τα βήματα που ακολουθήσατε. Μην ξεχάσετε να κάνετε commit μαζί με οποιοδήποτε κώδικα χρησιμοποιήσατε. Για ό,τι δεν ολοκληρώσετε περιγράψτε (και υλοποιήστε στο πρόγραμμα) την πρόοδό σας και πώς θα μπορούσατε να συνεχίσετε. -
Για όλα τα βήματα απαιτείται να γράψετε ένα πρόγραμμα που να αυτοματοποιεί την εύρεση της λύσης. Μπορείτε να χρησιμοποιήσετε ό,τι γλώσσα προγραμματισμού θέλετε, αλλά θα πρέπει να μπορώ να το τρέξω σε Ubuntu 22.04 χρησιμοποιώντας software που είναι διαθέσιμο στο Ubuntu. Θα πρέπει επίσης να φτιάξετε ένα script
run.sh
που εκτελεί το πρόγραμμα με ό,τι παραμέτρους χρειάζονται. -
Η πλήρης λύση της εργασίας απαιτεί να φτιάξετε ένα Docker container που να αυτοματοποιεί πλήρως την επίθεση. Ένα script ουσιαστικά, που απλά να εκτελείται σε container ώστε να μπορεί να τρέξει οπουδήποτε. Πάραδειγμα
Dockerfile
υπάρχει στο repository, και θα πρέπει να τρέχει με:docker build --tag attack . && docker run attack
Λύσεις χωρίς docker γίνονται δεκτές, απλά χάνετε 1 μονάδα.
-
Deadline: 20/7 (μέχρι το τέλος της ημέρας)
- Μπορείτε να παραδώσετε την εργασία και το Σεπτέμβρη, με μόνη διαφορά ότι το docker τότε θα πιάνει 3 μονάδες γιατί έχετε παραπάνω χρόνο (και πάλι όμως μπορείτε να πάρετε 10).
-
Οχι spoilers
-
Οχι DoS ή brute force. Μπορείτε να χρησιμοποιείτε scripts που να κάνουν μια επίθεση με έναν λογικό αριθμό από requests (να μπορεί να τελειώσει σε μία ώρα max). Aλλά όποιος βαράει στα τυφλά μηδενίζεται (θέλουμε οι servers να είναι accessible από όλους). Αν δεν είστε σίγουροι αν κάτι επιτρέπεται, απλά ρωτήστε.
-
Είναι σαφώς προτιμότερο να υλοποιήσετε πρώτα όλα τα attacks locally πριν τα τρέξετε στον server.
-
Ο pico server έχει γίνει compile στο
linux03.di.uoa.gr
, οπότε μπορείτε εκεί να φτιάξετε ένα executable ακριβώς σαν αυτό που εκτελείται στον server. -
Αν θέλετε hints ρωτήστε privately (χωρίς βαθμολογική συνέπεια, σε λογικά πλαίσια).
Προς συμπλήρωση:
Απαντήσεις:
TASK 1.
Έγινε
Στο συγκεκριμένο task μας ζητείται να βρούμε το MD5 digest του password του site που είναι live.
Ξεκινήσαμε, λοιπόν, κάνοντας compile το pico project τοπικά και κατά το compilation παρατηρήσαμε ένα warning του gcc, το οποίο μας προειδοποιούσε για μία printf η οποία είχε ως όρισμα μία μεταβλητή. Ύστερα από ανάλυση του κώδικα και κάποια πληροφόρηση που λάβαμε online (όπως εδώ https://owasp.org/www-community/attacks/Format_string_attack), φτάσαμε στο συμπέρασμα ότι αν η μεταβλητή αυτή λάβει τη τιμή '%x', τότε διαβάζει δεδομένα από τη στοίβα και μετακινεί τον stack pointer.
Μέσα από τον κώδικα, καταλαβάμε ότι η μεταβλητή αυτή περιλαμβάνει το decoded base64 header, που στείλαμε μαζί με το request για authorization. Έτσι, προσπαθήσαμε να μαντέψουμε με κάποιες δοκιμές το πλήθος των '%x' που χρειάζονται για να φτάσουμε στο επιθυμητό αποτέλεσμα, δηλαδή να αποκαλυφθεί το πραγματικό decoded base64 του admin και έτσι να υποκλέψουμε τον MD5 digest password του. Για την εκτύπωση του password αυτού προσθέταμε το '%s' στο τέλος των προσπαθειών μας, ώστε η εκτύπωση να γίνει σε string format του εκάστοτε περιεχομένου. Έτσι, δοκιμάσαμε διαδοχικά '%s', '%x%s', '%x%x%s' κλπ.
Ύστερα, από κάποιες δοκιμές, φτάσαμε στο μαγικό %x%x%x%x%x%x%s
το οποίο, εκτός από διάφορες άλλες τιμές,
στο τέλος του εκτυπώνει το decoded base64 <user>:<MD5 digest>
.
Έτσι, βρήκαμε το md5 digest password, το οποίο είναι το ef281a07091268a0d779cf489d00380c
.
Έχει υλοποποιηθεί και το αντίστοιχο αυτοματοποιημένο script, το οποίο εκτυπώνει το password αυτό, αφού το υποκλέψει με τον τρόπο που περιγράφεται παραπάνω. Το script λέγεται task_1.py και έχει γίνει η απαραίτητη διαδικασία για να τρέχει μέσω της εντολής του docker, που αναφέρεται παραπάνω:
docker build --tag attack . && docker run attack
TASK 2.
Δεν έγινε
TASK 3. Έγινε
Λαμβάνοντας υπόψην τον κώδικα του pico project είδαμε ότι κατά το post request καλείται η post_param, η οποία στο εσωτερικό της δημιουργεί ένα array μεγέθους payload_size, το οποίο λαμβάνεται από το request του χρήστη, δηλαδή το Content-Length header. Το array αυτό λαμβάνει σαν περιεχόμενο το περιεχόμενο του payload που στέλνει ο χρήστης. Άρα, συμπεραίνουμε ότι το μέγεθος του array σε σχέση με το περιεχόμενό του δεν συμβαδίζουν κατά ανάγκη, αν εμείς στείλουμε ως Content-Length κάτι μικρότερο από το πραγματικό μέγεθος του payload.
Ύστερα, το περιεχόμενο αντιγράφεται με την strcpy στον buffer μας. Η strcpy() είναι vulnerable συνάρτηση
σε buffer overflows. Άρα, σκοπός μας είναι να στείλουμε ένα request με Content-Length: 0 (δηλαδή payload_size = 0)
και κατάλληλο payload, ώστε να πετύχουμε buffer overflow και να λάβουμε το περιεχόμενο του /etc/secret
.
Ο σκοπός μας, δηλαδή, είναι να αλλάξουμε τη return_address της post_param και να δείχνει στη send_file(). Ταυτόχρονα, θα πρέπει να αλλάξουμε το όρισμα που βρίσκεται πάνω από τη return_address, ώστε να δείχνει σε ένα δικό μας string, το '/etc/secret'.
Πρέπει τώρα να δούμε τη δομή που θα πρέπει να έχει το payload. Για να το πετύχουμε αυτό χρειάζεται να αναλύσουμε τη στοίβα που βρίσκεται από τον buffer μας μέχρι τη return_address. Έτσι, χρησιμοποιώντας τον gdb θέσαμε ένα breakpoint στο σημείο ακριβώς πριν εκτελεστεί η strcpy() και στείλαμε ένα τυχαίο post request. Παρατηρήσαμε, λοιπόν, τα εξής: - Διεύθυνση του $ebp - Διεύθυνση του $esp (δηλαδή του buffer μας) - Διεύθυνση του return_address (δηλαδή $ebp + 4)
Με βάση αυτά και κάνοντας την αφαίρεση δεκαεξαδικών $ebp - $esp, βρήκαμε τη διαφορά που έχουν μεταξύ τους και συνεπακόλουθα τη διαφορά που έχει ο $esp με τη return_address. Η διαφορά αυτή είναι 18 words + 1 word για να φτάσουμε από τον $esp στη return_address. Επίσης, παρατηρήσαμε ότι το canary, το οποίο δε πρέπει να αλλάξει μετά το attack μας βρίσκεται 3 words μακριά από τη return_address και η διεύθυνση του buffer βρίσκεται 1 word μακριά από το canary.
Άρα, η δομή της στοίβας από τον $esp προς τη return_address είναι: - 13 τυχαία words - Η διεύθυνση του buffer - 1 τυχαίο word - Το canary - 2 τυχαία words - Το περιέχομενο του $ebp - Το return_address της post_param
Σε αυτό το βήμα θα χρησιμοποιήσουμε το printf() vulnerability της check_auth() για να υποκλέψουμε από το live site το περιεχόμενο του $ebp και το canary. Τρέχοντας τον gdb και θέτοντας breakpoint ακριβώς πριν την εκτέλεση της printf(auth_username), καταφέραμε να βρούμε τη διεύθυνση του $esp, $ebp και έτσι και τη μεταξύ τους διαφορά. Έτσι, μπορέσαμε να βρούμε πόσα words πρέπει να εκτυπώσουμε μέσω του get request για να πάρουμε το canary που υπάρχει στον τρέχοντα live server και το περιεχόμενο του $ebp, αφού πλέον θα ξέραμε σε ποιο σημείο βρίσκονται.
Για να το κάνουμε αυτό στείλαμε στο authorization header 31 * %08x
, ύστερα από δοκιμές (λόγω προβλημάτων
που δημιουργούσαν οι έξτρα printf()). Η διαφορά του 08 είναι απλά για καλύτερο format εκτύπωσης, που συμβουλευτήκαμε
online. Αναλυτικά:
- Η 31η τιμή που επιστρέφεται είναι η return_address της check_auth (θα τη χρειαστούμε μετά για υπολογισμό
της διεύθυνσης της send_file())
- Η 30η τιμή που επιστρέφεται είναι η τιμή του $ebp
- Η 27η τιμή που επιστρέφεται είναι το canary
Τρέχοντας για άλλη μια φορά τον gdb με σκοπό να βρούμε τι offset πρέπει να προσθέσουμε στον $ebp και στο return_address της check_auth(), ώστε να βρούμε τη διεύθυνση του buffer και τη διεύθυνση της send_file() αντίστοιχα.
Εκτελώντας, x/a post_data
βρίσκουμε τη διεύθυνση του buffer στον τοπικό server.
Εκτελώντας, info address send_file
βρίσκουμε τη διεύθυνση της send_file() στον τοπικό server.
Η διαφορά, λοιπόν, της διεύθυνσης του buffer με το $ebp είναι -120
και η διαφορά της send_file_address με τη
return_address_of_check_auth είναι 1581
. Συνεπώς, αν προσθέσουμε 1581 στη διεύθυνση επιστροφής της
check_auth (δεκαδικό), θα πάρουμε τη διεύθυνση της send_file() και αν αφαιρέσουμε 120 από τον $ebp,
θα πάρουμε τη διεύθυνση του buffer (στον live server, αφού γνωρίζουμε ότι ο κώδικας είναι ο ίδιος με τον
τοπικό μας).
Για να δημιουργήσουμε το payload του attack, θα πρέπει αρχικά να αποθηκεύσουμε το string '/etc/secret' στη στοίβα. Αυτό μπορούμε να το κάνουμε αν βάλουμε τα αντίστοιχα bytes στην αρχή του payload. Αυτά είναι 11 bytes + 1 byte το 00, για να συμβολίσουμε το τέλος του string. Άρα, έχουμε 3 words. Χρειάζεται ακόμα να συμπληρώσουμε 10 τυχαία words, να βάλουμε τη διεύθυνση του buffer, 1 ακόμα τυχαίο word, το canary, 2 τυχαία words, το περιεχόμενο του $ebp και τη διεύθυνση της send_file(). Στη συνέχεια, πρέπει να περάσουμε το string μας ως όρισμα στη send_file. Για να το κάνουμε αυτό, αρκεί να γράψουμε τη διεύθυνση του buffer πάνω τη return_address της send_file που μόλις γράψαμε, αφού το string το αποθηκεύσαμε να ξεκινάει στη διεύθυνση του buffer.
Τα τυχαία words τα θέσαμε είτε σε AAAAAAAA
είτε στη διεύθυνση του buffer. Επίσης, για να εκτελεστεί η strcpy()
όπως επιθυμούμε αντικαταστήσουμε το 00 με 26 (δηλαδή το '&'), αφού υπάρχει το μαγικό for..loop ακριβώς κάτω από
την strcpy() που θα μας σώσει και θα τα γυρίσει πάλι σε 00. Για να το στείλουμε αυτό σε post request
έπρεπε να μετατρέψουμε όλα αυτά τα words σε little-endian και ύστερα σε binary.
Έτσι, στείλαμε το request, με το binary αρχείο και Content-Length: 0. Η απάντηση που πήραμε ήταν ...
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣦⣴⣶⣶⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣤⣤⣤⣤⣤⣤⣤⣤⣀⣀⣀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣶⠿⠟⠛⠛⠋⠉⠉⠉⠉⠉⠉⠛⠛⠛⠿⢷⣦⣤⣀⡹⠿⠿⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⣴⣶⣶⣾⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⣿⣿⣶⣶⣶⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣴⣿⠟⠉⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠟⠀⠀⠀⠉⠙⢿⣦⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣠⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢦⣽⣿⡄⠀⠀⠀⠀⠀
⠀⠀⠀⣰⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣷⠀⠀⠀⠀⠀
⠀⠀⢰⣿⡏⣤⠀⠀⠀⠀⠀⢀⡼⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣻⡀⠀⠀⢤⢠⣼⣿⡆⠀⠀⠀⠀
⠀⠀⠀⢿⣿⠁⠀⠀⠀⠀⣴⡾⠁⠀⠀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣀⠀⠀⠀⠀⠀⠈⢻⣇⠀⠀⠈⣇⣿⣿⠀⠀⠀⠀⠀
⠀⠀⠀⢸⣿⠀⡀⣀⠀⢠⣿⠃⠀⠀⢀⣾⣿⣿⡿⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⡷⠀⠀⠀⠀⠀⢸⣿⠀⢠⣠⣿⣿⠇⠀⠀⠀⠀⠀
⠀⠀⠀⠈⢿⣷⣇⣽⠀⢈⡏⠀⠀⠀⠸⣿⣿⣿⣦⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣧⣤⠥⠀⠀⠀⠀⣿⣿⣧⣾⣿⠟⠁⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠈⠛⠿⣿⣧⣾⣿⡄⠀⠀⠀⠙⠿⠿⠿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠛⠛⠋⠀⠀⠀⠀⠀⢸⣿⡿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⣿⡇⣴⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⢶⣼⣿⣀⣠⣤⣤⣤⣀⠀⠀⠀⠀⠀
⠀⠀⣠⣶⣾⠿⠛⠛⠻⢷⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⡿⠋⠉⠉⠉⠛⢿⣦⡀⠀⠀
⢀⣾⡿⠋⠀⠀⠀⠀⠀⠀⠙⣿⡆⢀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣤⣿⡟⠀⠀⠀⠀⠀⠀⠀⠹⣿⡆⠀
⣼⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⣸⣷⣿⣷⣧⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣄⠀⢠⡾⣠⣇⣠⣿⣿⣿⡇⠀⢀⠀⠀⠀⢀⠀⠀⢹⣷⠀
⣿⣷⡀⠀⣷⠀⠀⠀⣼⣦⣴⣿⠏⠙⠻⠿⣷⡿⠷⣶⣶⡾⠿⠿⠷⢶⣶⣦⣤⣾⣿⣷⣿⣿⠿⠿⠛⠛⠙⠻⣿⣤⣾⣇⠀⢀⣸⣇⣀⣼⣿⠃
⠘⢿⣿⣾⣿⣷⣴⣾⡿⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⠻⠿⠿⠿⠟⠛⠛⠁⠀
You guessed it... puppies!
Όλα τα παραπάνω φαίνονται και στο αυτοματοποιημένο script που έχει γραφτεί (task_3.py), το οποίο μπορεί να εκτελεστεί και αυτόματα αφού έχει ρυθμιστεί κατάλληλα και το dockerfile για να το τρέχει. Η εντολή για να τρέξει κι αυτό και όλα τα υπόλοιπα scripts για τα tasks είναι αυτή που αναφέρεται παραπάνω:
docker build --tag attack . && docker run attack
TASK 4.
Δεν έγινε
TASK 5. (Υλοποίηση σε docker)
Έγινε