1

I'm working on an app that generates documents for users using PhpWord.

I created a "firewall.php" file to make sure that users can only see their own files.

The files are being generated fine and I have no problem viewing them from the "uploads" folder.. But when downloading them through filewall.php, they're getting messed like this:

뽐䬃Д ¤憂匎ꕩ䷱ àȀ _牥汳⼮牥汳궒셊̱ႆ 䖘笷�氯⋴⚲㻀遌眗㞙遌땽筃兴ꆬ舽컌㼟�갷ܿ꨷䩹恙햠⡘癃벴辋㭐夰㠜㦐脣旘㑗 ᩑ쩎 顕膄沠ញ柛鏇山ꑐ☻丞ꖔ꧓ᇭ⭶ꑗ畽ꯓ伆㐓ꛚ㨃槫隠�놵❁螂�䱥㯉偎儭Ꚏ쒀揻哚璉ꠊ᧴禡헟蕸뜛ⰽ냝笊狎謎䋁醛埂ᣧ貮⽩擷姘￲䒧천틍▕ꚉ澟睎亻쿶鞍鹼쳦͐䬃Д ¤憂叼뉧ꂿ ;Ā d潣偲潰猯慰瀮硭沝辽櫃〔藷㺅톞죍၊遥䩋졒뚳邮涁疯遮跽⧘鶻鴟䞵䮘ꨙ勶蒍砼횢ʴຍ碿式侢쩬킙褐᪱䈖굾偝ꈈ褽ူ㝢撎ᜩ댝↘簬㖖ꚧᐌលى緯ⶼ鋽઀ⱏ畽隰゠ͷ蠛僼ሯ㏿៪죾 ῷ㔖鹖켱仞ᨮ❵矫㸩㤥ニ 䈴롪┷퀌遊뒫ﶶ︆偋̄᐀Ȁࠀꑡ艓 砋̜偿氝䗎�厈田恅싵髸ᰣ灜뎁㍊摧씿褻耭쑼㘲佼쵙ꁪ⚻辎陃ṹ饒�䏟㰞続蒇U_睩阦�ꈋ⁨瀬㷐

This is my code for downloading the Docx file (firewall.php):

<?php
require_once( $_SERVER['DOCUMENT_ROOT'] .'/app.php');

$user_id = intval( $_SESSION['user_id']);

if( $user_id) {
    $file = htmlspecialchars( $_GET['file']);
    $file_arr = explode('/', $file);
    $file_ext = trim( mb_substr( $file, -4), '.');
    $disposition = 'inline';

    if( strpos( $file_ext, 'docx') !== false) {
        $file_ext = 'vnd.openxmlformats-officedocument.wordprocessingml.document';
        $disposition = 'attachment';
    }

    // Allow only if user_id is the same as in the file's path
    if( intval( $file_arr[1]) == $user_id) {
        $file_path = __DIR__ ."/app/uploads/$file";

        header('Content-Description: File Transfer');
        header('Content-Disposition: '. $disposition .'; filename="'. $file_arr[2] .'"');
        header('Content-Type: application/'. $file_ext);
        header('Content-Transfer-Encoding: binary');
        header('Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0');
        header('Expires: 0');
        header('Pragma: public');
        header('Content-Length: ' . filesize( $file_path));

        readfile( $file_path);

    } else {
        echo '403: Forbidden';
    }
}

die();

Param values

$file = 'users/1/304811_Doc_02-2021_330.docx';

Content-Type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';

$file_path = '/var/www/dashboard/app/users/1/304811_Doc_02-2021_330.docx';

$dispostion = 'attachment';

Nate
  • 41
  • 1
  • 1
  • 11
  • Can you tell us more about: _"when downloading them through filewall.php"_? Is that the code in your question, or something else? – KIKO Software Dec 02 '21 at 12:13
  • When downloading a docx file, it goes through this code but for some reason it turns docx files into gibberish – Nate Dec 02 '21 at 12:22
  • OK, so I guess the code in your question is the actual "firewall.php" you're talking about. I guess it is also incomplete, because the ` – KIKO Software Dec 02 '21 at 12:31
  • Yes, this is the firewall.php file but there's nothing much in it yet.. The gibberish shows in Google Docs (I'm using linux). I added the full code – Nate Dec 02 '21 at 12:42
  • Sorry, I've never used Google Docs, no idea what it does. – KIKO Software Dec 02 '21 at 13:24
  • This problem is not related to Google Docs and the file is being generate properly.. The problem is with how I serve the file in PHP – Nate Dec 02 '21 at 13:33
  • Your code is vulnerable to path traversal attack leading to an arbitrary file read for not properly escaping slashes in the GET variable file. You should update the question with what you actually pass to the script in the GET variable, does the code actually enters the check for .docx extension? The wrong (inline) content disposition header could explain this problem – GrowingBrick Dec 02 '21 at 13:37
  • The code does enter the check of docx extension and changes disposition to "attachment". I updated my question and added the value of the $_GET variable. – Nate Dec 02 '21 at 13:39
  • You could try `header('Content-Type: application/octet-stream');`, because, in the end, all you want to do is [download a binary file](https://stackoverflow.com/questions/13513420/headers-used-to-download-file-php). – KIKO Software Dec 02 '21 at 13:43

2 Answers2

1

I recreated your folder structure and it worked fine, downloading a valid docx of mine, but you wrote:

$file_path = '/var/www/dashboard/app/users/1/304811_Doc_02-2021_330.docx';

And the path is wrong, because after /app/ there should be /uploads/ according to your code. So I tried downloading a non-existing file and resulted in downloading two PHP warnings and the wrong encoding could cause displaying that gibberish characters.

However I remind you that your code is vulnerable to path traversal attack leading to an arbitrary file read for not properly escaping slashes in the GET variable file. PoC:

firewall.php?file=users/my_id/../../../../index.php
GrowingBrick
  • 731
  • 4
  • 12
0

Solved by adding ob_end_clean(); before headers

        $file_path = __DIR__ ."/app/uploads/$file";
        ob_end_clean();

        header('Content-Description: File Transfer');
        header('Content-Type: application/'. $file_ext);
        header('Content-Disposition: '. $disposition .'; filename="'. $file_arr[2] .'"');
        header('Content-Transfer-Encoding: binary');
        header('Expires: 0');
        header('Cache-Control: no-cache, must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Content-Length: '. filesize( $file_path));

        readfile( $file_path);
Nate
  • 41
  • 1
  • 1
  • 11