Closed i0day closed 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.
This is the modified code to only allow Jpeg, png, gif, txt file extension to upload.
<?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=<number>\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();
}
?>
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.
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.
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
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.
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
If that works at all, it would be a horrendously slow process and overall bad idea.
what is the proper way to call for malware scan everytime a file uploaded. Thanks
Use clamdscan. And your own brain.
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.
There is no need to do that in the .php, but if that's the way you want to do things, :shrug:
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();
}
?>
That is unrelated to this issue. Please create a PR instead. Also that implementation very much lends itself to abuse.
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