FIX_FILE_RESTRICTION
This commit is contained in:
parent
4cd9f8ade4
commit
95f15d8ca8
22
.htaccess
22
.htaccess
@ -5,6 +5,28 @@ Options -Indexes
|
||||
# Rewrite engine
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
|
||||
## ADDED for - block any script execution inside folder of public
|
||||
<If "%{REQUEST_URI} =~ m#/(logo|add_image_upload|e_card_imgs|assets|claim_sample_forms|sample_import_excel|writable)/#">
|
||||
Deny from all
|
||||
# Disable PHP engine
|
||||
<IfModule mod_php.c>
|
||||
php_flag engine off
|
||||
</IfModule>
|
||||
|
||||
# Disable CGI and other executable handlers
|
||||
Options -ExecCGI
|
||||
AddHandler cgi-script .php .pl .py .jsp .asp .sh .cgi
|
||||
|
||||
# Block access to any script-like files entirely
|
||||
<FilesMatch "\.(php|php5|php7|phtml|pl|py|cgi|asp|aspx|sh|rb)$">
|
||||
ForceType text/plain
|
||||
#Order allow,deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
</If>
|
||||
|
||||
|
||||
# Turning on the rewrite engine is necessary for the following rules and features.
|
||||
# FollowSymLinks must be enabled for this to work.
|
||||
<IfModule mod_rewrite.c>
|
||||
|
||||
@ -16,6 +16,7 @@ use App\Filters\VerifyAppSignature;
|
||||
|
||||
use App\Filters\AuthJWT;
|
||||
use App\Filters\Cors;
|
||||
use App\Filters\GlobalPostFileUploadGuard;
|
||||
|
||||
class Filters extends BaseConfig
|
||||
{
|
||||
@ -39,6 +40,7 @@ class Filters extends BaseConfig
|
||||
'CloseDbConnection' => CloseDbConnection::class,
|
||||
'Cors' => Cors::class,
|
||||
'appSignature' => VerifyAppSignature::class,
|
||||
'GlobalPostFileUploadGuard' => GlobalPostFileUploadGuard::class,
|
||||
|
||||
];
|
||||
|
||||
@ -53,7 +55,7 @@ class Filters extends BaseConfig
|
||||
'before' => [
|
||||
'HttpRequestLog' => ['except' => 'cli/*'],
|
||||
'Cors',
|
||||
// 'csrf',
|
||||
'GlobalPostFileUploadGuard',
|
||||
// 'invalidchars',
|
||||
],
|
||||
'after' => [
|
||||
|
||||
147
app/Filters/GlobalPostFileUploadGuard.php
Normal file
147
app/Filters/GlobalPostFileUploadGuard.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use CodeIgniter\HTTP\RequestInterface;
|
||||
use CodeIgniter\HTTP\ResponseInterface;
|
||||
use CodeIgniter\Filters\FilterInterface;
|
||||
use Config\Services;
|
||||
use finfo;
|
||||
|
||||
class GlobalPostFileUploadGuard implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* Max file size (in bytes) → 25MB
|
||||
*/
|
||||
protected int $maxFileSize = 25 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* Allowed MIME types mapped to extensions
|
||||
*/
|
||||
protected array $allowedMimeMap = [
|
||||
'image/jpeg' => ['jpg', 'jpeg'],
|
||||
'image/png' => ['png'],
|
||||
'image/gif' => ['gif'],
|
||||
'image/webp' => ['webp'],
|
||||
'image/svg+xml' => ['svg'],
|
||||
'application/pdf' => ['pdf'],
|
||||
'application/msword' => ['doc'],
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'],
|
||||
'application/vnd.oasis.opendocument.text' => ['odt'],
|
||||
'text/rtf' => ['rtf'],
|
||||
'application/rtf' => ['rtf'],
|
||||
'application/vnd.ms-excel' => ['xls'],
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'],
|
||||
'application/vnd.oasis.opendocument.spreadsheet' => ['ods'],
|
||||
'text/csv' => ['csv'],
|
||||
'application/csv' => ['csv'],
|
||||
'text/plain' => ['txt', 'csv'],
|
||||
];
|
||||
|
||||
protected array $blockedExtensions = [
|
||||
'php', 'phtml', 'pht', 'phar', 'php3', 'php4', 'php5', 'php7', 'php8', 'phps',
|
||||
'cgi', 'fcgi', 'pl', 'py', 'rb', 'lua', 'tcl', 'go', 'rs', 'jar', 'class',
|
||||
'exe', 'dll', 'com', 'bat', 'cmd', 'msi', 'vbs', 'ps1', 'scr',
|
||||
'sh', 'bash', 'zsh', 'apk', 'app', 'deb', 'rpm', 'bin', 'run',
|
||||
'js', 'mjs', 'jsp', 'asp', 'aspx', 'cer', 'swf',
|
||||
'env', 'ini', 'user.ini', 'htaccess', 'htpasswd', 'conf', 'config', 'log', 'sql',
|
||||
'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'iso',
|
||||
'lnk', 'url', 'reg', 'sys', 'drv', 'vxd', 'tmp', 'bak', 'old', 'backup', 'key', 'pem'
|
||||
];
|
||||
|
||||
public function before(RequestInterface $request, $arguments = null)
|
||||
{
|
||||
if ($request->getMethod() !== 'post') {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = $request->getFiles();
|
||||
if (empty($files)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($files as $inputName => $fileData) {
|
||||
$this->validateFileInput($fileData, $inputName);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateFileInput($fileData, string $inputName): void
|
||||
{
|
||||
if (is_array($fileData)) {
|
||||
foreach ($fileData as $file) {
|
||||
$this->validateSingleFile($file, $inputName);
|
||||
}
|
||||
} else {
|
||||
$this->validateSingleFile($fileData, $inputName);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSingleFile($file, string $inputName): void
|
||||
{
|
||||
$request = Services::request();
|
||||
$clientIp = $request->getIPAddress();
|
||||
$uri = $request->getUri()->getPath();
|
||||
|
||||
if (!$file->isValid()) {
|
||||
if ($file->getError() === UPLOAD_ERR_INI_SIZE || $file->getError() === UPLOAD_ERR_FORM_SIZE) {
|
||||
$this->block("File exceeds server-side size limit", $clientIp, $uri, $inputName, $file->getClientName(), 'unknown', 'unknown', 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$originalName = $file->getClientName();
|
||||
$extension = strtolower($file->getExtension());
|
||||
$mime = $file->getMimeType();
|
||||
$size = $file->getSize();
|
||||
|
||||
// --- 1. Fixed Null Byte & Path Traversal Check ---
|
||||
if (preg_match('/\0|[\/\\\]/', $originalName)) {
|
||||
$this->block("Malicious filename characters", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||
}
|
||||
|
||||
// --- 2. Double Extension Attack Check ---
|
||||
if (preg_match('/\.(php|phtml|phar|exe|sh|bat|cmd|js|jsp|asp|aspx|py|pl)\./i', $originalName)) {
|
||||
$this->block("Double extension attack", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||
}
|
||||
|
||||
// --- 3. Forbidden Extension ---
|
||||
if (in_array($extension, $this->blockedExtensions, true)) {
|
||||
$this->block("Forbidden extension", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||
}
|
||||
|
||||
// --- 4. File Size Limit ---
|
||||
if ($size > $this->maxFileSize) {
|
||||
$this->block("File too large", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||
}
|
||||
|
||||
// --- 5. MIME Allow-list Check ---
|
||||
if (!array_key_exists($mime, $this->allowedMimeMap)) {
|
||||
$this->block("MIME type not allowed ($mime)", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||
}
|
||||
|
||||
// --- 6. MIME-Extension Consistency ---
|
||||
if (!in_array($extension, $this->allowedMimeMap[$mime], true)) {
|
||||
$this->block("MIME-extension mismatch", $clientIp, $uri, $inputName, $originalName, $mime, $extension, $size);
|
||||
}
|
||||
}
|
||||
|
||||
private function block(string $reason, string $ip, string $uri, string $field, string $filename, string $mime, string $ext, int $size): void
|
||||
{
|
||||
log_message('critical',
|
||||
'[UPLOAD_BLOCKED] {reason} | IP: {ip} | URI: {uri} | Field: {field} | File: {file} | MIME: {mime} | EXT: {ext} | SIZE: {size}',
|
||||
['reason'=>$reason, 'ip'=>$ip, 'uri'=>$uri, 'field'=>$field, 'file'=>$filename, 'mime'=>$mime, 'ext'=>$ext, 'size'=>$size]
|
||||
);
|
||||
|
||||
$response = Services::response();
|
||||
$response->setStatusCode(403)
|
||||
->setJSON([
|
||||
'status' => 'error',
|
||||
'message' => 'File upload rejected: Security policy violation.',
|
||||
'debug' => (ENVIRONMENT === 'development') ? $reason : null
|
||||
])
|
||||
->send();
|
||||
exit;
|
||||
}
|
||||
|
||||
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) {}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user