phpmadness
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| phpmadness [2025/10/30 16:27] – mbunic | phpmadness [2025/12/01 11:40] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ==== Zadatak s Hacknite platforme - PHPmadness ==== | ==== Zadatak s Hacknite platforme - PHPmadness ==== | ||
| - | Uz zadatak | + | < |
| + | Ovaj zadatak | ||
| - | {{ :slika1.png? | + | http://chal.platforma.hacknite.hr: |
| + | </ | ||
| - | Pregledom *Dockerfilea* | + | Dan je i izvorni kod zadatka. |
| - | Odlaskom na stranicu zadatka potrebno je registrirati novi korisnički račun. Nakon logina prikazuje se stranica na kojoj se može pohraniti jedna slika. \\ | + | {{ phpmadness: |
| - | {{ : | + | Pregledom **Dockerfilea** |
| - | Nakon što se uploada neka mala nasumična slika (npr. *whatever.png*), | + | < |
| + | fl4g_< | ||
| + | </ | ||
| - | {{ : | + | Odlaskom |
| - | Pregledom koda u datoteci **user.php** može se pronaći kod koji validira da je učitana datoteka zapravo valjana slika. \\ | + | {{ phpmadness: |
| - | {{ :slika4.png? | + | Nakon što se uploada neka mala nasumična slika (npr. **whatever.png**), dobije se poveznica na lokaciju |
| - | Kod se sastoji od tri provjere: | + | {{ phpmadness:slika3.png? |
| - | **1. Provjera | + | Pregledom koda u datoteci |
| - | <file> | + | |
| + | {{ phpmadness: | ||
| + | |||
| + | Kod se sastoji od tri provjere, prva provjera je samo provjera | ||
| + | |||
| + | <code> | ||
| if (preg_match('/ | if (preg_match('/ | ||
| - | </file> | + | </code> |
| - | Provjera uzima ekstenziju nakon zadnje točke (npr. `.jpg`) i provjerava da ne počinje znakovima `ph`. | + | Ovu provjeru lako je zaobići, |
| - | Ovu provjeru lako je zaobići | + | |
| - | U tom slučaju | + | |
| - | **2. Provjera | + | < |
| - | <file> | + | file.php.jpg |
| + | </ | ||
| + | |||
| + | U kojem bi slučaju ova provjera uzela samo **.jpg**, nakon čega sprema datoteku bez provjerene ekstenzije, što bi značilo da bi samo druga ekstenzija, **.php**, ostala. | ||
| + | |||
| + | Druga provjera, provjerava da MIME tip poslan | ||
| + | |||
| + | <code> | ||
| if (strpos($_FILES[" | if (strpos($_FILES[" | ||
| - | </file> | + | </code> |
| - | Ova provjera koristi podatak koji klijent šalje i može se jednostavno namjestiti ručno pri slanju datoteke. | + | |
| - | **3. Provjera “file signature” (serverska | + | Ovu provjeru je lagano zaobići namještanjem ove vrijednosti na korisničkoj strani pri slanju datoteke. |
| - | <file> | + | |
| + | Treća | ||
| + | |||
| + | <code> | ||
| $info = @getimagesize($_FILES[" | $info = @getimagesize($_FILES[" | ||
| $mime = $info[' | $mime = $info[' | ||
| + | |||
| ... | ... | ||
| + | |||
| strpos($mime, | strpos($mime, | ||
| - | </file> | + | </code> |
| - | Ova provjera čita stvarne “magic bytes” datoteke i određuje je li slika. | + | |
| - | Zaobići se može tako da se na početak PHP datoteke dodaju “magic byteovi” slike (.jpg, .png, …). PHP parser ih preskače i izvršava ostatak koda. | + | |
| - | --- | + | Provjerava datoteku na serverskoj strani, čitajući njezin "file signature" |
| - | No, čak i ako se uspješno upload-a | + | I ovu provjeru moguće je jednostavno zaobići, tako da se napravi |
| - | {{ : | + | No postoji problem, čak i da se uploada datoteka s PHP kodom koja zaobilazi sve ove provjere, ona se i dalje ne bi izvršila, zato što će se učitati pod putanjom **/uploads/**, a u **/ |
| - | Međutim, | + | {{ phpmadness: |
| - | To znači da, ako se može uploadati **.htaccess** datoteka koja bi zaobišla validaciju, ona bi mogla **nadjačati** (override) roditeljsku `.htaccess` konfiguraciju i time dopustiti izvršavanje PHP datoteka unutar korisničkog direktorija. | + | |
| - | Problem: korisnik može imati **samo jednu** datoteku po sesiji — upload nove briše staru, što je prikazano | + | No uploadana datoteka će se nalaziti u poddirektoriju od **/uploads**, nalazit će se u: |
| - | {{ : | + | < |
| + | /uploads/< | ||
| + | </ | ||
| - | To znači: | + | Što znači |
| - | - ako se upload-a `.php` datoteka, | + | |
| - | - ako se upload-a `.htaccess`, neće se moći uploadati dodatna `.php` datoteka jer se prva briše. | + | |
| - | --- | + | No problem je da svaki korisnik smije imati samo jednu učitanu datoteku po korisničkoj sesiji, i učitavanjem nove datoteke, stara datoteka se briše i samo nova čuva, što je prikazano u kodu na slici 6. \\ |
| - | Postoji ipak način da **dvije datoteke** završe u istom direktoriju. | + | {{ phpmadness: |
| - | Pogledajmo kako se određuje ime korisničkog direktorija: | + | |
| - | <file> | + | To znači da ako korisnik učita **.php** |
| + | |||
| + | No postoji način za učitati dvije datoteke u isti direktorij, kako bi se mogla učitati prvo **.htaccess** datoteka koja bi dozvolila izvršavanja PHP koda i onda druga datoteka koja bi sadržavala PHP kod, koji bi se izvršio i koristio za čitanje flaga. | ||
| + | |||
| + | Odgovor leži u logici koja određuje u koji direktorij će se pohraniti korisnikova učitana datoteka, što je određeno u ovoj liniji koda: | ||
| + | |||
| + | <code> | ||
| $userdir = substr(md5($_SESSION[' | $userdir = substr(md5($_SESSION[' | ||
| - | </file> | + | </code> |
| - | Dakle, | + | Ova linija koda određuje da će se korisnikov |
| - | Ako nađemo **dva korisnička imena** X i Y s istim prvim 6 znakova MD5 hash-a: | + | |
| - | <file> | + | To znači da ako možemo pronaći dva korisnička imena, koja oba imaju istih prvih 6 znakova njihovih MD5 sažetka, odnosno |
| + | |||
| + | <code> | ||
| MD5(X)[0:6] = MD5(Y)[0:6] | MD5(X)[0:6] = MD5(Y)[0:6] | ||
| - | </file> | + | </code> |
| + | |||
| + | gdje su X i Y dva različita usernamea, svaki korisnik može uploadati jednu datoteku u isti direktorij, tako da korištenjem jednog korisnika se može uploadati **.htaccess** datoteka, a korištenjem drugog korisnika datoteka sa PHP kodom, čije je izvršavanje omogućeno **.htaccess** datotekom. | ||
| - | onda će oba korisnika spremati datoteke u isti direktorij. | + | Pronalazak para usernameova čijih prvih 6 znakova MD5 hash algoritma se podudaraju je jednostavan problem i čistim bruteforceom se može pronaći vrlo brzo, ispod minute (implementacijom u C-u). |
| - | Jedan može uploadati `.htaccess`, a drugi `.php` datoteku. | + | |
| - | Pronalazak takvog para je trivijalan brute-force problem — može se riješiti u manje od minute. | + | Sljedeće je jedan primjer C koda koji pronalazi ovo rješenje. |
| - | Slijedi | + | |
| < | < | ||
| - | // md5_collision_finder6 | + | // md5_collision_finder6 |
| #include < | #include < | ||
| #include < | #include < | ||
| Line 108: | Line 129: | ||
| printf(" | printf(" | ||
| printf(" | printf(" | ||
| - | for (int i = 0; i < MD5_DIGEST_LENGTH; | + | for (int i = 0; i < MD5_DIGEST_LENGTH; |
| + | | ||
| + | } | ||
| printf(" | printf(" | ||
| - | for (int i = 0; i < 3; i++) printf(" | + | for (int i = 0; i < 3; i++) { |
| - | printf(" | + | |
| + | } | ||
| + | printf(" | ||
| + | // Extract first 3 bytes as target | ||
| unsigned char target[3]; | unsigned char target[3]; | ||
| memcpy(target, | memcpy(target, | ||
| - | const int min_len = 8; | + | const int min_len = 8; // MINIMUM candidate length |
| - | const int max_len = 15; | + | const int max_len = 15; // MAXIMUM candidate length |
| const size_t charset_size = sizeof(charset) - 1; | const size_t charset_size = sizeof(charset) - 1; | ||
| unsigned long long attempts = 0; | unsigned long long attempts = 0; | ||
| - | char candidate[max_len + 1]; | + | |
| + | | ||
| srand(time(NULL)); | srand(time(NULL)); | ||
| - | |||
| while (1) { | while (1) { | ||
| attempts++; | attempts++; | ||
| + | // Generate random candidate length (8-15) | ||
| int len = rand() % (max_len - min_len + 1) + min_len; | int len = rand() % (max_len - min_len + 1) + min_len; | ||
| - | for (int i = 0; i < len; i++) candidate[i] = charset[rand() % charset_size]; | + | |
| + | | ||
| + | | ||
| + | } | ||
| candidate[len] = ' | candidate[len] = ' | ||
| - | if (len == input_len && memcmp(candidate, | + | |
| + | | ||
| + | | ||
| + | } | ||
| + | // Compute MD5 | ||
| unsigned char candidate_digest[MD5_DIGEST_LENGTH]; | unsigned char candidate_digest[MD5_DIGEST_LENGTH]; | ||
| MD5((unsigned char*)candidate, | MD5((unsigned char*)candidate, | ||
| + | // Progress report | ||
| + | if (attempts % report_interval == 0) { | ||
| + | printf(" | ||
| + | } | ||
| + | // Check first 3 bytes (6 hex characters) | ||
| if (memcmp(candidate_digest, | if (memcmp(candidate_digest, | ||
| - | printf(" | + | printf(" |
| - | printf(" | + | printf(" |
| + | printf(" | ||
| + | for (int i = 0; i < MD5_DIGEST_LENGTH; | ||
| + | printf(" | ||
| + | } | ||
| + | printf(" | ||
| + | for (int i = 0; i < 3; i++) { | ||
| + | printf(" | ||
| + | } | ||
| + | printf(" | ||
| break; | break; | ||
| } | } | ||
| Line 140: | Line 188: | ||
| </ | </ | ||
| - | Pokretanje: | + | Program se treba kompajlirati i nakon toga se može pokrenuti, prosljeđujući mu username za koji je potrebno naći MD5 hash collision u prvih 6 znakova kao argument. |
| + | |||
| + | Primjer ovakvog para korisničkih imena dobivenog ovim programom je: | ||
| < | < | ||
| $ ./ | $ ./ | ||
| Line 146: | Line 197: | ||
| Full MD5: affc2dc1a3f9fb05392d3cb0a784ff61 | Full MD5: affc2dc1a3f9fb05392d3cb0a784ff61 | ||
| First 6 chars: affc2d | First 6 chars: affc2d | ||
| + | Searching for collision (min 8 chars)... | ||
| + | Attempts: 10M, Current: ZF7ciZizcHo8 | ||
| + | |||
| Collision found after 19093362 attempts! | Collision found after 19093362 attempts! | ||
| - | Colliding string: BQuxIUGH | + | Colliding string: BQuxIUGH |
| Full MD5: affc2da2a9c70df41678d24f997a95f0 | Full MD5: affc2da2a9c70df41678d24f997a95f0 | ||
| First 6 chars: affc2d | First 6 chars: affc2d | ||
| </ | </ | ||
| - | Dakle, dva korisnička imena koja dijele direktorij | + | Dva korisnička imena, čije bi učitane datoteke bile u istom direktoriju |
| + | |||
| + | < | ||
| + | korisnik1 i BQuxIUGH | ||
| + | </ | ||
| + | |||
| + | Preostaje problem kako učitati | ||
| - | --- | + | .htaccess datoteka koja bi koristila ovaj pristup bi se mogla zvati **.htaccess.jpg**. |
| - | Sada treba napraviti .htaccess datoteku koja će proći validaciju slike, ali će se i dalje pravilno parsirati. | + | Postoje dva rješenja. |
| - | Pri parsiranju PHP datoteka, nepoznati bajtovi se preskaču, no .htaccess parser zahtijeva ispravan početak datoteke — to je najteži dio zadatka. | + | |
| - | Postoje dva rješenja: | + | ==== .wbmp ==== |
| - | ### 1. `.wbmp` pristup | + | Prvo se zasniva na činjenici da se pri parsiranju |
| - | Parser | + | |
| - | Ako se dodaju | + | |
| - | Takva datoteka | + | |
| < | < | ||
| - | <4 null byteova><new line> | + | <4 null bajtova><new line (\n ili 0a u hex)> |
| < | < | ||
| Require all granted | Require all granted | ||
| Line 174: | Line 230: | ||
| </ | </ | ||
| - | ### 2. `.xbm` pristup | + | ==== .xbm ==== |
| - | Komentari | + | |
| - | Ako se doda zaglavlje valjane | + | Drugo rješenje se zasniva na činjenici da u .htaccess |
| + | |||
| + | < | ||
| + | #define a_width 1 | ||
| + | #define a_height 1 | ||
| + | </ | ||
| + | |||
| + | što prolazi provjeru je li učitana datoteka slika, interpretirajući | ||
| < | < | ||
| Line 187: | Line 250: | ||
| </ | </ | ||
| - | PHP provjera prepoznaje datoteku kao sliku, a Apache parser ignorira prve dvije linije kao komentare. | + | Sada su riješena sva ograničenja zadatka i jasan je način kako se može stvoriti 2 korisnika s odgovarajućim korisničkim imenima kako bi se mogle učitati dvije datoteke, **.htaccess** i datoteka s PHP kodom u isti korisnički direktorij, uz zaobilazak svih provjera i ograničenja zadatka za učitavanje datoteke. |
| - | --- | + | Još jedno ograničenje je preostalo za PHP kod koji se treba učitati i izvršavati za dohvat flaga, to je skup zabranjenih funkcija, definiran u **/php-config/ |
| - | Time je moguće zaobići sve provjere i ograničenja. | + | Zabranjene funkcije su: |
| - | Dakle, strategija je: | + | |
| - | 1. Dva korisnika s istim prvih 6 znakova MD5 hash-a. | + | |
| - | 2. Jedan upload-a `.htaccess.jpg` (s gornjim sadržajem). | + | |
| - | 3. Drugi upload-a PHP datoteku, npr. `readFlag.php3.jpg`. | + | |
| - | --- | + | <code> |
| - | + | ||
| - | Preostaje PHP kod koji pronalazi i ispisuje flag. | + | |
| - | Ograničenja su definirana u `/ | + | |
| - | + | ||
| - | <file> | + | |
| disable_functions = exec, | disable_functions = exec, | ||
| - | </file> | + | </code> |
| - | Ipak, moguće je čitati | + | No i dalje i s ovim ograničenjem ima dosta načina za pronaći flag datoteku i pročitati |
| - | Primjer datoteke **readFlag.php3.jpg** | + | Primjer datoteke |
| < | < | ||
| - | <4 null byteova>< | + | <4 null byteova>< |
| <?php | <?php | ||
| $files = scandir("/ | $files = scandir("/ | ||
| Line 226: | Line 280: | ||
| </ | </ | ||
| - | --- | + | Preostali koraci: |
| - | + | * s jednim | |
| - | Zatim: | + | {{ phpmadness: |
| - | 1. Prvim korisnikom | + | |
| - | {{ : | + | |
| - | 2. Drugim | + | * s drugim |
| - | {{ : | + | {{ phpmadness: |
| - | 3. Posjeti link do učitane PHP datoteke. \\ | + | * otići na lokaciju |
| - | {{ : | + | {{ phpmadness: |
| - | PHP kod će se izvršiti i ispisati | + | kako bi se PHP kod izvršio i ispisao |
| - | {{ : | + | {{ phpmadness: |
phpmadness.1761841630.txt.gz · Last modified: 2025/12/01 11:40 (external edit)