Nate
Nate

Reputation: 41

Force download on a docx file results in gibberish

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';

Upvotes: 1

Views: 312

Answers (2)

Nate
Nate

Reputation: 41

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);

Upvotes: 0

GrowingBrick
GrowingBrick

Reputation: 751

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

Upvotes: 1

Related Questions