English · Ελληνικά
krayin-docker-setup του οποίου η πρώτη commit ακόμα περιέχει ένα password στο .env — επαναχρησιμοποιημένο για τον admin CRM. Το Krayin 2.2.0 είναι ευάλωτο στο CVE-2026-38526, ένα authenticated upload TinyMCE χωρίς έλεγχο τύπου, οπότε ένα webshell PHP προσγειώνεται σε έναν φάκελο web-served και δίνει www-data. Ένα δεύτερο .env στον δίσκο αποδίδει ακόμα ένα επαναχρησιμοποιημένο password — SSH σαν jones. Το βήμα root είναι το ενδιαφέρον: ένα script "template sync" που τρέχει σαν root τραβάει αρχεία από template repos Gitea και γράφει κάθε blob στο os.path.join(staging, filepath), όπου το filepath είναι το όνομα tree entry του git. Το Git κανονικά αρνείται paths ../ και με αρχικό slash — αλλά μπορείς να πλαστογραφήσεις με το χέρι ένα tree object του οποίου το όνομα entry είναι ένα απόλυτο path, και το os.path.join πετάει το prefix staging τη στιγμή που βλέπει ένα αρχικό /. Αυτό είναι ένα αυθαίρετο root file-write, που γίνεται ένα cronjob και ένα shell root. Όλα οδηγημένα από macOS.
Δύο θύρες, και το web root ανακατευθύνει σε ένα hostname — μια ένδειξη να ξεκινήσεις κάνοντας fuzz vhosts, όχι paths.
Το κύριο site είναι στατικό — το μόνο αξιοσημείωτο είναι ένα email, j.matthew@nexus.htb, σε ένα τμήμα careers. Το brute-forcing φακέλων δεν βρίσκει τίποτα, οπότε κάνε pivot σε ανακάλυψη virtual-host με ffuf, φιλτράροντας το default μέγεθος απάντησης:
Πρόσθεσε nexus.htb, billing.nexus.htb και git.nexus.htb στο /etc/hosts (ή οδήγησε τα πάντα με curl -H "Host: …", που είναι αυτό που έκανα για να κρατήσω τα πράγματα scriptable).
Το git.nexus.htb είναι ένα instance Gitea με ένα δημόσιο repo, το admin/krayin-docker-setup. Το API του Gitea κάνει το περπάτημα στο commit history τετριμμένο χωρίς browser:
Το .env στο HEAD έχει ένα άδειο DB_PASSWORD — αλλά secrets που έχουν γίνει commit μία φορά δεν εξαφανίζονται όταν επεξεργάζεσαι το αρχείο, απλά μετακινούνται κάτω στο history. Το να τραβήξεις το .env στην αρχική commit λέει μια διαφορετική ιστορία:
Το billing.nexus.htb είναι ένα login admin Krayin CRM. Το email από τη σελίδα careers συν το διαρρεύσαν password μας βάζουν μέσα — το login του Laravel χρειάζεται απλά ένα έγκυρο _token τραβηγμένο πρώτα από τη φόρμα login:
Η έκδοση (2.2.0, φαίνεται κάτω από το μενού profile) είναι ευάλωτη στο CVE-2026-38526 — το endpoint /admin/tinymce/upload δεν κάνει καμία server-side validation τύπου ή επέκτασης, και γράφει μέσα στο web-served /storage/tinymce/. Οπότε ένας authenticated χρήστης μπορεί να ανεβάσει ένα αρχείο .php (κάνοντας spoof ένα MIME εικόνας για να φαίνεται φυσιολογικό) και μετά να το εκτελέσει με ένα απλό GET. Αντί για το reverse shell του δημόσιου PoC, ανέβασα ένα μικρό scriptable webshell — κανένα listener να προσέχω:
Το /etc/passwd δείχνει έναν πραγματικό χρήστη, τον jones, του οποίου το home δεν μπορούμε να διαβάσουμε σαν www-data. Αλλά η deployed εφαρμογή κουβαλά το δικό της ζωντανό .env, και κρατά ένα διαφορετικό password από αυτό στο git:
Η επαναχρησιμοποίηση password κάνει τα υπόλοιπα — εκείνη η τιμή είναι το password SSH του jones. Πρώτο πράγμα μόλις μπεις, φύτεψε ένα κλειδί SSH ώστε το foothold να είναι ένα σταθερό ssh -i αντί για ένα εύθραυστο session:
Χωρίς sudo για τον jones. Το στοιχείο είναι στο /etc/systemd/system: ένα timer πυροδοτεί ένα oneshot root κάθε λεπτό.
Το script (world-readable) κάνει query το Gitea για κάθε repo σημασμένο σαν template, μετά για το καθένα τρέχει git ls-tree -r HEAD πάνω στο bare repo και αντιγράφει κάθε blob σε έναν φάκελο staging. Ορίστε όλη η ευπάθεια, σε τρεις γραμμές:
Το filepath ελέγχεται από τον επιτιθέμενο — είναι όποιο όνομα κουβαλά ένα tree entry — και ρέει κατευθείαν μέσα στο os.path.join. Η ιδιαιτερότητα που το κάνει μοιραίο:
Οπότε αν μπορέσω να αποκτήσω ένα tree entry του οποίου το όνομα είναι /etc/cron.d/pwncron, η διαδικασία root γράφει το αρχείο μου κατευθείαν στο /etc/cron.d. Το εμπόδιο είναι ότι το plumbing του Git (και οι έλεγχοι push του Gitea) απορρίπτουν κανονικά paths ../ και με αρχικό slash. Ο τρόπος να περάσεις είναι να πλαστογραφήσεις τα αντικείμενα git με το χέρι — χτίσε ένα ωμό tree object του οποίου το όνομα entry είναι το απόλυτο path, χρησιμοποιώντας hash-object --literally, που παρακάμπτει τους ελέγχους λογικής:
Τώρα δημιούργησε ένα repo σαν jones, σήμανέ το template (αυτή είναι η σημαία που φιλτράρει το script sync), και κάνε push το πλαστογραφημένο history. Το /tmp/.copybash.sh που αναφέρει το cron απλά αντιγράφει το bash και ορίζει το bit SUID:
Μέσα σε ένα λεπτό το timer root κάνει sync το "template", το os.path.join κάνει resolve το όνομα entry σε /etc/cron.d/pwncron, και ο root το γράφει. Το cron τρέχει το αντίγραφο SUID, και το bash -p κρατά το EUID root:
os.path.join — είναι το να εμπιστεύεσαι ένα filename από μη έμπιστη πηγή σαν στόχο εγγραφής. Ένα όνομα tree entry git είναι δεδομένα επιτιθέμενου· το να το ταΐσεις σε ένα path join που ένα αρχικό slash μπορεί να καταλάβει μετατρέπει το "κάνε sync κάποια αρχεία template" σε αυθαίρετο root file-write. Δύο ανεξάρτητοι φρουροί θα το είχαν σταματήσει ο καθένας: ένωση μόνο με το basename (ή απόρριψη absolute/.. components μετά την ένωση), και το να τρέχεις το sync σαν unprivileged service account αντί για root.| Σταδιο | Ριζα του προβληματος |
|---|---|
| → git .env | Secret κάνει commit στο version control· το scrubbing του HEAD το αφήνει στο history (CWE-538 / CWE-312) |
| → admin Krayin | Επαναχρησιμοποίηση password ανάμεσα στο config DB και τον λογαριασμό admin CRM (CWE-521 / CWE-798) |
| → www-data | CVE-2026-38526 — unrestricted file upload, χωρίς έλεγχο τύπου/επέκτασης, μέσα σε web-served φάκελο (CWE-434) |
| → jones | Ένα ακόμα επαναχρησιμοποιημένο password που κάθεται στο .env της deployed εφαρμογής (CWE-522) |
| → root | Το sync root γράφει σε ένα μη έμπιστο path git tree-entry· ένα απόλυτο path ξεφεύγει από τον φάκελο staging (CWE-22 / CWE-250) |
?c=) κάλυψε το βήμα www-data, και ένα κλειδί SSH κάλυψε τον jones — και τα δύο σταθερά και non-interactive, οπότε τίποτα να προσέχεις σε ένα VPN./etc/hosts. Κάθε web request χρησιμοποίησε curl -H "Host: …", οπότε δεν χρειάστηκε sudo για να δοκιμάσω τα τρία vhosts.git hash-object -t tree --literally και commit-tree πλαστογραφούν το object graph πανομοιότυπα παντού· το κόλπο είναι να καταλάβεις ότι το όνομα ενός tree entry είναι απλά ένα string σε ένα blob που μπορείς να γράψεις εσύ..env στην κορυφή είναι η κλασική παραπλάνηση.