Rouji / single_php_filehost

Simple Filehosting Page in a Single PHP File (obvious 0x0 clone)
ISC License
235 stars 35 forks source link

preventing spam #28

Closed i0day closed 4 months ago

i0day commented 4 months ago

I tested out your php filehost on my domain s0s.xyz for about a month. I recently discovered that my domain got blacklisted by https://urlhaus.abuse.ch/ and other malware blocking source. my domain name is blocked by Quad9 dns service. Some one use my site to host a malicious executable file. By the time I found out and removed the files it was already hosted for more than 3 days. I completely disable the file sharing services. Is there any way I could limit what kind of files to be uploaded. For example, images and text files and disable all executable files like .exe application/x-dosexec, application/x-executable, application/x-sharedlib, application/x-hdf5, application/java-archive, application/vnd.android.package-archive, application/x-rar, application/vnd.microsoft.portable-executable.

Thank you so much. James

i0day commented 4 months ago

anyway to check the uploader's Ip and original file name? Thanks I answer my own question. In the script, the location of the log file is determined by the LOG_PATH constant defined in the CONFIG class. Let's look at the value of LOG_PATH:


const LOG_PATH = null; // path to log uploads + resulting links to

In this script, LOG_PATH is set to null by default, which means that no log file will be created. If you want to log the uploader's IP address and original file name, you need to set a valid file path for LOG_PATH.

For example, if you want to log the information to a file named uploads.log in the same directory as the script, you would set LOG_PATH as follows:


const LOG_PATH = 'uploads.log'; // path to log uploads + resulting links to

If you set a valid file path for LOG_PATH, the log file will be created in that location when files are uploaded, and it will contain entries with the uploader's IP address and original file name.

i0day commented 4 months ago

This is the modified code to only allow Jpeg, png, gif, txt file extension to upload.

i0day commented 4 months ago
<?php
class CONFIG
{
    const MAX_FILESIZE = 512; //max. filesize in MiB
    const MAX_FILEAGE = 180; //max. age of files in days
    const MIN_FILEAGE = 31; //min. age of files in days
    const DECAY_EXP = 2; //high values penalise larger files more

    const UPLOAD_TIMEOUT = 5*60; //max. time an upload can take before it times out
    const MIN_ID_LENGTH = 3; //min. length of the random file ID
    const MAX_ID_LENGTH = 24; //max. length of the random file ID, set to MIN_ID_LENGTH to disable
    const STORE_PATH = 'files/'; //directory to store uploaded files in
    const LOG_PATH = null; //path to log uploads + resulting links to
    const DOWNLOAD_PATH = '%s'; //the path part of the download url. %s = placeholder for filename
    const MAX_EXT_LEN = 7; //max. length for file extensions
    const EXTERNAL_HOOK = null; //external program to call for each upload
    const AUTO_FILE_EXT = false; //automatically try to detect file extension for files that have none

    const ADMIN_EMAIL = 'admin@example.com';  //address for inquiries

    public static function SITE_URL() : string
    {
        $proto = ($_SERVER['HTTPS'] ?? 'off') == 'on' ? 'https' : 'http';
        return "$proto://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}";
    }
};

// generate a random string of characters with given length
function rnd_str(int $len) : string
{
    $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
    $max_idx = strlen($chars) - 1;
    $out = '';
    while ($len--)
    {
        $out .= $chars[mt_rand(0,$max_idx)];
    }
    return $out;
}

// check php.ini settings and print warnings if anything's not configured properly
function check_config() : void
{
    $warn_config_value = function($ini_name, $var_name, $var_val)
    {
        $ini_val = intval(ini_get($ini_name));
        if ($ini_val < $var_val)
            print("<pre>Warning: php.ini: $ini_name ($ini_val) set lower than $var_name ($var_val)\n</pre>");
    };

    $warn_config_value('upload_max_filesize', 'MAX_FILESIZE', CONFIG::MAX_FILESIZE);
    $warn_config_value('post_max_size', 'MAX_FILESIZE', CONFIG::MAX_FILESIZE);
    $warn_config_value('max_input_time', 'UPLOAD_TIMEOUT', CONFIG::UPLOAD_TIMEOUT);
    $warn_config_value('max_execution_time', 'UPLOAD_TIMEOUT', CONFIG::UPLOAD_TIMEOUT);
}

//extract extension from a path (does not include the dot)
function ext_by_path(string $path) : string
{
    $ext = pathinfo($path, PATHINFO_EXTENSION);
    //special handling of .tar.* archives
    $ext2 = pathinfo(substr($path,0,-(strlen($ext)+1)), PATHINFO_EXTENSION);
    if ($ext2 === 'tar')
    {
        $ext = $ext2.'.'.$ext;
    }
    return $ext;
}

function ext_by_finfo(string $path) : string
{
    $finfo = finfo_open(FILEINFO_EXTENSION);
    $finfo_ext = finfo_file($finfo, $path);
    finfo_close($finfo);
    if ($finfo_ext != '???')
    {
        return explode('/', $finfo_ext, 2)[0];
    }
    else
    {
        $finfo = finfo_open();
        $finfo_info = finfo_file($finfo, $path);
        finfo_close($finfo);
        if (strstr($finfo_info, 'text') !== false)
        {
            return 'txt';
        }
    }
    return '';
}

// store an uploaded file, given its name and temporary path (e.g. values straight out of $_FILES)
// files are stored wit a randomised name, but with their original extension
//
// $name: original filename
// $tmpfile: temporary path of uploaded file
// $formatted: set to true to display formatted message instead of bare link
function store_file(string $name, string $tmpfile, bool $formatted = false) : void
{
    //create folder, if it doesn't exist
    if (!file_exists(CONFIG::STORE_PATH))
    {
        mkdir(CONFIG::STORE_PATH, 0750, true); //TODO: error handling
    }

    // check file extension
    $allowed_extensions = array('jpg', 'jpeg', 'png', 'gif', 'txt');
    $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    if (!in_array($ext, $allowed_extensions)) {
        header('HTTP/1.0 403 Forbidden');
        print("Error 403: File extension not allowed\n");
        return;
    }

    //check file size
    $size = filesize($tmpfile);
    if ($size > CONFIG::MAX_FILESIZE * 1024 * 1024)
    {
        header('HTTP/1.0 413 Payload Too Large');
        print("Error 413: Max File Size ({CONFIG::MAX_FILESIZE} MiB) Exceeded\n");
        return;
    }
    if ($size == 0)
    {
        header('HTTP/1.0 400 Bad Request');
        print('Error 400: Uploaded file is empty\n');
        return;
    }

    $ext = ext_by_path($name);
    if (empty($ext) && CONFIG::AUTO_FILE_EXT)
    {
        $ext = ext_by_finfo($tmpfile);
    }
    $ext = substr($ext, 0, CONFIG::MAX_EXT_LEN);
    $tries_per_len=3; //try random names a few times before upping the length

    $id_length=CONFIG::MIN_ID_LENGTH;
    if(isset($_POST['id_length']) && ctype_digit($_POST['id_length'])) {
        $id_length = max(CONFIG::MIN_ID_LENGTH, min(CONFIG::MAX_ID_LENGTH, $_POST['id_length']));
    }

    for ($len = $id_length; ; ++$len)
    {
        for ($n=0; $n<=$tries_per_len; ++$n)
        {
            $id = rnd_str($len);
            $basename = $id . (empty($ext) ? '' : '.' . $ext);
            $target_file = CONFIG::STORE_PATH . $basename;

            if (!file_exists($target_file))
                break 2;
        }
    }

    $res = move_uploaded_file($tmpfile, $target_file);
    if (!$res)
    {
        //TODO: proper error handling?
        header('HTTP/1.0 520 Unknown Error');
        return;
    }

    if (CONFIG::EXTERNAL_HOOK !== null)
    {
        putenv('REMOTE_ADDR='.$_SERVER['REMOTE_ADDR']);
        putenv('ORIGINAL_NAME='.$name);
        putenv('STORED_FILE='.$target_file);
        $ret = -1;
        $out = null;
        $last_line = exec(CONFIG::EXTERNAL_HOOK, $out, $ret);
        if ($last_line !== false && $ret !== 0)
        {
            unlink($target_file);
            header('HTTP/1.0 400 Bad Request');
            print("Error: $last_line\n");
            return;
        }
    }

    //print the download link of the file
    $url = sprintf(CONFIG::SITE_URL().CONFIG::DOWNLOAD_PATH, $basename);

    if ($formatted)
    {
        print("<pre>Access your file here: <a href=\"$url\">$url</a></pre>");
    }
    else
    {
        print("$url\n");
    }

    // log uploader's IP, original filename, etc.
    if (CONFIG::LOG_PATH)
    {
        file_put_contents(
            CONFIG::LOG_PATH,
            implode("\t", array(
                date('c'),
                $_SERVER['REMOTE_ADDR'],
                filesize($tmpfile),
                escapeshellarg($name),
                $basename
            )) . "\n",
            FILE_APPEND
        );
    }
}

// purge all files older than their retention period allows.
function purge_files() : void
{
    $num_del = 0;    //number of deleted files
    $total_size = 0; //total size of deleted files

    //for each stored file
    foreach (scandir(CONFIG::STORE_PATH) as $file)
    {
        //skip virtual . and .. files
        if ($file === '.' ||
            $file === '..')
        {
            continue;
        }

        $file = CONFIG::STORE_PATH . $file;

        $file_size = filesize($file) / (1024*1024); //size in MiB
        $file_age = (time()-filemtime($file)) / (60*60*24); //age in days

        //keep all files below the min age
        if ($file_age < CONFIG::MIN_FILEAGE)
        {
            continue;
        }

        //calculate the maximum age in days for this file
        $file_max_age = CONFIG::MIN_FILEAGE +
                        (CONFIG::MAX_FILEAGE - CONFIG::MIN_FILEAGE) *
                        pow(1 - ($file_size / CONFIG::MAX_FILESIZE), CONFIG::DECAY_EXP);

        //delete if older
        if ($file_age > $file_max_age)
        {
            unlink($file);

            print("deleted $file, $file_size MiB, $file_age days old\n");
            $num_del += 1;
            $total_size += $file_size;
        }
    }
    print("Deleted $num_del files totalling $total_size MiB\n");
}

function send_text_file(string $filename, string $content) : void
{
    header('Content-type: application/octet-stream');
    header("Content-Disposition: attachment; filename=\"$filename\"");
    header('Content-Length: '.strlen($content));
    print($content);
}

// send a ShareX custom uploader config as .json
function send_sharex_config() : void
{
    $name = $_SERVER['SERVER_NAME'];
    $site_url = str_replace("?sharex", "", CONFIG::SITE_URL());
    send_text_file($name.'.sxcu', <<<EOT
{
  "Name": "$name",
  "DestinationType": "ImageUploader, FileUploader",
  "RequestType": "POST",
  "RequestURL": "$site_url",
  "FileFormName": "file",
  "ResponseType": "Text"
}
EOT);
}

// send a Hupl uploader config as .hupl (which is just JSON)
function send_hupl_config() : void
{
    $name = $_SERVER['SERVER_NAME'];
    $site_url = str_replace("?hupl", "", CONFIG::SITE_URL());
    send_text_file($name.'.hupl', <<<EOT
{
  "name": "$name",
  "type": "http",
  "targetUrl": "$site_url",
  "fileParam": "file"
}
EOT);
}

// print a plaintext info page, explaining what this script does and how to
// use it, how to upload, etc.
function print_index() : void
{
    $site_url = CONFIG::SITE_URL();
    $sharex_url = $site_url.'?sharex';
    $hupl_url = $site_url.'?hupl';
    $decay = CONFIG::DECAY_EXP;
    $min_age = CONFIG::MIN_FILEAGE;
    $max_size = CONFIG::MAX_FILESIZE;
    $max_age = CONFIG::MAX_FILEAGE;
    $mail = CONFIG::ADMIN_EMAIL;
    $max_id_length = CONFIG::MAX_ID_LENGTH;

    $length_info = "\nTo use a longer file ID (up to $max_id_length characters), add -F id_length=&lt;number&gt;\n";
    if (CONFIG::MIN_ID_LENGTH == CONFIG::MAX_ID_LENGTH)
    {
        $length_info  = "";
    }

echo <<<EOT
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Filehost</title>
    <meta name="description" content="Minimalistic service for sharing temporary files." />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<pre>
 === How To Upload ===
You can upload files to this site via a simple HTTP POST, e.g. using curl:
curl -F "file=@/path/to/your/file.jpg" $site_url

Or if you want to pipe to curl *and* have a file extension, add a "filename":
echo "hello" | curl -F "file=@-;filename=.txt" $site_url
$length_info
On Windows, you can use <a href="https://getsharex.com/">ShareX</a> and import <a href="$sharex_url">this</a> custom uploader.
On Android, you can use an app called <a href="https://github.com/Rouji/Hupl">Hupl</a> with <a href="$hupl_url">this</a> uploader.

Or simply choose a file and click "Upload" below:
(Hint: If you're lucky, your browser may support drag-and-drop onto the file 
selection input.)
</pre>
<form id="frm" method="post" enctype="multipart/form-data">
<input type="file" name="file" id="file" />
<input type="hidden" name="formatted" value="true" />
<input type="submit" value="Upload"/>
</form>
<pre>

 === File Sizes etc. ===
The maximum allowed file size is $max_size MiB.

Files are kept for a minimum of $min_age, and a maximum of $max_age Days.

How long a file is kept depends on its size. Larger files are deleted earlier 
than small ones. This relation is non-linear and skewed in favour of small 
files.

The exact formula for determining the maximum age for a file is:

MIN_AGE + (MAX_AGE - MIN_AGE) * (1-(FILE_SIZE/MAX_SIZE))^$decay

 === Source ===
The PHP script used to provide this service is open source and available on 
<a href="https://github.com/Rouji/single_php_filehost">GitHub</a>

 === Contact ===
If you want to report abuse of this service, or have any other inquiries, 
please write an email to $mail
</pre>
</body>
</html>
EOT;
}

// decide what to do, based on POST parameters etc.
if (isset($_FILES['file']['name']) &&
    isset($_FILES['file']['tmp_name']) &&
    is_uploaded_file($_FILES['file']['tmp_name']))
{
    //file was uploaded, store it
    $formatted = isset($_REQUEST['formatted']);
    store_file($_FILES['file']['name'],
              $_FILES['file']['tmp_name'],
              $formatted);
}
else if (isset($_GET['sharex']))
{
    send_sharex_config();
}
else if (isset($_GET['hupl']))
{
    send_hupl_config();
}
else if ($argv[1] ?? null === 'purge')
{
    purge_files();
}
else
{
    check_config();
    print_index();
}
?>
i0day commented 4 months ago

you can implement IP rate limiting in your script to prevent an IP address from uploading more than a certain number of files within a specified time frame. Here's a general approach to achieve this:

Maintain a record of uploaded files along with their upload timestamps and the IP address of the uploader.
When a new file is uploaded, check the record to see how many files have been uploaded by the same IP address within the specified time frame.
If the number of uploads exceeds the limit, deny the upload and possibly take further action, such as logging the attempt or blocking the IP address.

Here's a modified version of your script with IP rate limiting implemented:


// Define a constant for the rate limiting parameters
const RATE_LIMIT_TIME_FRAME = 300; // Time frame in seconds (5 minutes)
const RATE_LIMIT_MAX_UPLOADS = 5; // Maximum number of uploads allowed within the time frame

// Function to check if an IP address has exceeded the upload rate limit
function ip_exceeds_rate_limit(string $ip): bool {
    // Load existing upload records from the log file
    $uploads = file_exists(CONFIG::LOG_PATH) ? file(CONFIG::LOG_PATH, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];

    // Filter uploads by the given IP address and within the time frame
    $recent_uploads = array_filter($uploads, function($line) use ($ip) {
        list($timestamp, $uploader_ip, $file_name) = explode("\t", $line);
        return $uploader_ip === $ip && time() - strtotime($timestamp) <= RATE_LIMIT_TIME_FRAME;
    });

    // Check if the number of recent uploads exceeds the limit
    return count($recent_uploads) >= RATE_LIMIT_MAX_UPLOADS;
}

// Modify the store_file function to include rate limiting
function store_file(string $name, string $tmpfile, bool $formatted = false) : void {
    $uploader_ip = $_SERVER['REMOTE_ADDR'];

    // Check if the uploader IP exceeds the rate limit
    if (ip_exceeds_rate_limit($uploader_ip)) {
        header('HTTP/1.0 429 Too Many Requests');
        print("Error 429: Too Many Requests\n");
        return;
    }

    // Your existing code for storing the file goes here...
    // Don't forget to log the upload if it's successful
}

In this modified script:

We define two constants RATE_LIMIT_TIME_FRAME and RATE_LIMIT_MAX_UPLOADS to specify the time frame and maximum number of uploads allowed.
The ip_exceeds_rate_limit function checks if the given IP address has exceeded the rate limit by filtering the upload records.
In the store_file function, we first obtain the uploader's IP address ($uploader_ip) and then check if it exceeds the rate limit using ip_exceeds_rate_limit. If the limit is exceeded, we return an error response. Otherwise, we proceed with storing the file as usual.

With this implementation, an IP address will be prevented from uploading more than 5 files within a 5-minute time frame. Adjust the RATE_LIMIT_TIME_FRAME and RATE_LIMIT_MAX_UPLOADS constants as needed for your specific requirements.

Rouji commented 4 months ago

I tested out your php filehost on my domain s0s.xyz for about a month. I recently discovered that my domain got blacklisted by https://urlhaus.abuse.ch/ and other malware blocking source. my domain name is blocked by Quad9 dns service. Some one use my site to host a malicious executable file. By the time I found out and removed the files it was already hosted for more than 3 days. I completely disable the file sharing services. Is there any way I could limit what kind of files to be uploaded. For example, images and text files and disable all executable files like .exe application/x-dosexec, application/x-executable, application/x-sharedlib, application/x-hdf5, application/java-archive, application/vnd.android.package-archive, application/x-rar, application/vnd.microsoft.portable-executable.

Thank you so much. James

@i0day Hi! Hosting a completely public file host does bring some risks with it, no matter what software you use. Automatic and manual moderation is very much required, unfortunately.

I appreciate the enthusiasm, but I don't think those are good ways to go about those things.

To filter out file extensions (and do malware scans or whatever you want to do) on upload, use the $EXTERNAL_HOOK.
e.g. with a bash script that returns non-zero for file extensions that you don't want.

For rate limiting, use your web server's facilities. Nginx does a very good job of it, and even apache2 has mod_ratelimit.

Turning on the logging is indeed a good idea.

Also, depending on what file types you end up allowing, you probably should configure somewhat strict CSPs. You can have a look at the headers on x0.at for inspiration.

I found blocking ToR exit nodes, VPS/cloud compute providers and certain countries at the firewall to also massively inhibit spam and illegal content.

If you need more details for any of this, feel free to ask.

i0day commented 4 months ago

Hi, Thank you so much for your reply. could you elaborate on these "To filter out file extensions (and do malware scans or whatever you want to do) on upload, use the $EXTERNAL_HOOK. e.g. with a bash script that returns non-zero for file extensions that you don't want." How to do automatically malware scans what do you recommend to check the uploaded files for malware under ubuntu 22.04 , could you explain on blocking ToR exit nodes and vps providers at the firewall. How do i check the header of x0.at to configure strict CSPs. I am using Nginx how to set rate limited on nginx without modify the php code.

Thank you so much, James

Rouji commented 4 months ago

If you put a path to an executable in $EXTERNAL_HOOK, it gets called for every new upload. It gets passed a few environment variables for you to work with, see https://github.com/Rouji/single_php_filehost/blob/027b150798aa5d24d987de2591df035ab841151f/index.php#L156-L171

So you can do something like:

BANNED_EXT = ['exe', 'bat', 'apk', 'cmd', 'scr', 'plist', 'dll', 'jar', 'rar', 'ps1']

remote_addr = os.environ['REMOTE_ADDR']
stored_file = os.environ['STORED_FILE']
_, ext = os.path.splitext(original_name)
if ext.lower().strip('.') in BANNED_EXT:
    print('File type not allowed')
    exit(1)
exit(0)

Sorry for the python, I cba working out how to do that in bash right now.

For the other things, I'm not gonna spoonfeed, but have a look at:

Please be aware, that this is the tip of the iceberg. You should really know what you're doing, if you want to host a wide-open anonymous file hosting site.

i0day commented 4 months ago

Thank you so much for your quick reply. For malware scan Can i just add const EXTERNAL_HOOK= 'clamscan --remove=yes -i -r /var/www/'; to class CONFIG. Will It call the clamscan automatically everytime the file uploaded?

Thanks again for your time. James

Rouji commented 4 months ago

If that works at all, it would be a horrendously slow process and overall bad idea.

i0day commented 4 months ago

what is the proper way to call for malware scan everytime a file uploaded. Thanks

Rouji commented 4 months ago

Use clamdscan. And your own brain.

i0day commented 4 months ago

I figured out the way to automatically scan uploaded file. ---after these line---

// Move the file to the target location
    $res = move_uploaded_file($tmpfile, $target_file);
    if (!$res)
    {
        //TODO: proper error handling?
        header('HTTP/1.0 520 Unknown Error');
        return;
    }

    // Perform ClamAV scan
    $clamscan_output = shell_exec('clamdscan --remove=yes -i ' . escapeshellarg($target_file));
    // You might want to handle the $clamscan_output here to check for any virus detections

---Before these line---
    // Print the download link of the file
    $url = sprintf(CONFIG::SITE_URL().CONFIG::DOWNLOAD_PATH, $basename);

Thank you so much for all the information and clarifications.

Rouji commented 4 months ago

There is no need to do that in the .php, but if that's the way you want to do things, :shrug:

i0day commented 4 months ago

I add a feature to fetch a remote url,If you have a remote URL to upload, you can use the following curl command: curl -F "url=http://example.com/image.jpg" $site_url

function store_file(string $name, string $tmpfile, bool $formatted = false) : void
{
    //create folder, if it doesn't exist
    if (!file_exists(CONFIG::STORE_PATH))
    {
        mkdir(CONFIG::STORE_PATH, 0750, true); //TODO: error handling
    }

    // Check if the file extension is allowed
    $allowed_extensions = array('jpg', 'jpeg', 'png', 'gif', 'txt');
 $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
    if (!in_array($ext, $allowed_extensions)) {
        header('HTTP/1.0 403 Forbidden');
        print("Error 403: File extension not allowed\n");
        return;
    }

    // Check file size
    $size = filesize($tmpfile);
    if ($size > CONFIG::MAX_FILESIZE * 1024 * 1024)
    {
        header('HTTP/1.0 413 Payload Too Large');
        print("Error 413: Max File Size (".CONFIG::MAX_FILESIZE." MiB) Exceeded\n");
        return;
    }
    if ($size == 0)
    {
        header('HTTP/1.0 400 Bad Request');
        print('Error 400: Uploaded file is empty\n');
        return;
    }

     $ext = ext_by_path($name);
    if (empty($ext) && CONFIG::AUTO_FILE_EXT)
    {
        $ext = ext_by_finfo($tmpfile);
    }
    $ext = substr($ext, 0, CONFIG::MAX_EXT_LEN);
    $tries_per_len=3; //try random names a few times before upping the length

    $id_length=CONFIG::MIN_ID_LENGTH;
    if(isset($_POST['id_length']) && ctype_digit($_POST['id_length'])) {
        $id_length = max(CONFIG::MIN_ID_LENGTH, min(CONFIG::MAX_ID_LENGTH, $_POST['id_length']));
    }

    for ($len = $id_length; ; ++$len)
    {
        for ($n=0; $n<=$tries_per_len; ++$n)
        {
            $id = rnd_str($len);
            $basename = $id . (empty($ext) ? '' : '.' . $ext);
            $target_file = CONFIG::STORE_PATH . $basename;

            if (!file_exists($target_file))
                break 2;
        }
    }

    // Generate a random filename
    $basename = rnd_str(CONFIG::MIN_ID_LENGTH) . '.' . strtolower(pathinfo($name, PATHINFO_EXTENSION));
    $target_file = CONFIG::STORE_PATH . $basename;

    // Move the file to the target location
$res = copy($tmpfile, $target_file);
if (!$res)
{
    //TODO: proper error handling?
    header('HTTP/1.0 520 Unknown Error');
    return;
}
    // Print the download link of the file
    $url = sprintf(CONFIG::SITE_URL().CONFIG::DOWNLOAD_PATH, $basename);

    if ($formatted)
    {
        print("<pre>Access your file here: <a href=\"$url\">$url</a></pre>");
    }
    else
    {
        print("$url\n");
    }

    // Log uploader's IP, original filename, etc.
    if (CONFIG::LOG_PATH)
    {
        file_put_contents(
            CONFIG::LOG_PATH,
            implode("\t", array(
                date('c'),
                $_SERVER['REMOTE_ADDR'],
                filesize($tmpfile),
                escapeshellarg($name),
                $basename
            )) . "\n",
            FILE_APPEND
        );
    }
}

modify the last few lines

// decide what to do, based on POST parameters etc.
if (isset($_FILES['file']['name']) &&
    isset($_FILES['file']['tmp_name']) &&
    is_uploaded_file($_FILES['file']['tmp_name']))
{
    // File uploaded via form
    store_file($_FILES['file']['name'], $_FILES['file']['tmp_name'], isset($_REQUEST['formatted']));
}
elseif (isset($_POST['url']) && !empty($_POST['url']))
{
    // File uploaded via URL
    $url = $_POST['url'];

    // Download the file from the URL to a temporary location
    $tmpfile = tempnam(sys_get_temp_dir(), 'remote_file');
    file_put_contents($tmpfile, file_get_contents($url));

    // Call the store_file function with the downloaded file
    store_file(basename($url), $tmpfile, isset($_REQUEST['formatted']));
}
elseif (isset($_GET['sharex']))
{
    send_sharex_config();
}
elseif (isset($_GET['hupl']))
{
    send_hupl_config();
}
elseif ($argv[1] ?? null === 'purge')
{
    purge_files();
}
else
{
    check_config();
    print_index();
}
?>
Rouji commented 4 months ago

That is unrelated to this issue. Please create a PR instead. Also that implementation very much lends itself to abuse.