FEAT_VAPT_FILE_RESTRICTION&FORM_SANITIZATION
This commit is contained in:
parent
332c412d0f
commit
7be77dacd5
@ -16,6 +16,8 @@ use App\Filters\AuthClientApi;
|
|||||||
use App\Filters\CommissionApiFilter;
|
use App\Filters\CommissionApiFilter;
|
||||||
use App\Filters\VerifyAppSignature;
|
use App\Filters\VerifyAppSignature;
|
||||||
use App\Filters\Cors;
|
use App\Filters\Cors;
|
||||||
|
use App\Filters\SecurityInputFilter;
|
||||||
|
use App\Filters\GlobalPostFileUploadGuard;
|
||||||
|
|
||||||
use App\Filters\AuthJWT;
|
use App\Filters\AuthJWT;
|
||||||
|
|
||||||
@ -43,6 +45,8 @@ class Filters extends BaseConfig
|
|||||||
'CommissionApiFilter'=> CommissionApiFilter::class,
|
'CommissionApiFilter'=> CommissionApiFilter::class,
|
||||||
'appSignature' => VerifyAppSignature::class,
|
'appSignature' => VerifyAppSignature::class,
|
||||||
'Cors' => Cors::class,
|
'Cors' => Cors::class,
|
||||||
|
'SecurityInputFilter' => SecurityInputFilter::class,
|
||||||
|
'GlobalPostFileUploadGuard' => GlobalPostFileUploadGuard::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,6 +60,8 @@ class Filters extends BaseConfig
|
|||||||
'before' => [
|
'before' => [
|
||||||
'HttpRequestLog' => ['except' => 'cli/*'],
|
'HttpRequestLog' => ['except' => 'cli/*'],
|
||||||
'Cors',
|
'Cors',
|
||||||
|
'SecurityInputFilter',
|
||||||
|
'GlobalPostFileUploadGuard'
|
||||||
// 'csrf',
|
// 'csrf',
|
||||||
// 'invalidchars',
|
// 'invalidchars',
|
||||||
],
|
],
|
||||||
|
|||||||
@ -519,6 +519,7 @@ class ClientController extends AdminController
|
|||||||
public function updateEmpAndPolicyStatus()
|
public function updateEmpAndPolicyStatus()
|
||||||
{
|
{
|
||||||
$return = $this->clientPolicyModel->updateStatus();
|
$return = $this->clientPolicyModel->updateStatus();
|
||||||
|
print_rr($return);
|
||||||
$this->myLogger->logme('error', 'Client Policy Status Update Count: {data}', ['data' => $return['client']]);
|
$this->myLogger->logme('error', 'Client Policy Status Update Count: {data}', ['data' => $return['client']]);
|
||||||
$this->myLogger->logme('error', 'Employee Policy Status Update Count: {data}', ['data' => $return['emp']]);
|
$this->myLogger->logme('error', 'Employee Policy Status Update Count: {data}', ['data' => $return['emp']]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1492,6 +1492,7 @@ class EmployeeController extends AdminController
|
|||||||
|
|
||||||
//new step check in S3 if yes then fetch from S3 bucket
|
//new step check in S3 if yes then fetch from S3 bucket
|
||||||
$s3_key = 'ecard_'.$get_emp_code_and_client_policy_id['name'].'('.$get_emp_code_and_client_policy_id['emp_code'].')'.'_'.$get_emp_code_and_client_policy_id['tpa_id'].'.pdf';
|
$s3_key = 'ecard_'.$get_emp_code_and_client_policy_id['name'].'('.$get_emp_code_and_client_policy_id['emp_code'].')'.'_'.$get_emp_code_and_client_policy_id['tpa_id'].'.pdf';
|
||||||
|
$s3_key = $this->sanitizeFilePart($s3_key);
|
||||||
// echo $s3_key;die();
|
// echo $s3_key;die();
|
||||||
$s3 = \Config\Services::getS3Service();
|
$s3 = \Config\Services::getS3Service();
|
||||||
if($s3->exists($s3_key) && $mode != 2) //2 => for bulk generate so skip s3 check and generate PDF
|
if($s3->exists($s3_key) && $mode != 2) //2 => for bulk generate so skip s3 check and generate PDF
|
||||||
@ -3346,7 +3347,8 @@ class EmployeeController extends AdminController
|
|||||||
|
|
||||||
public function initiateWellnessOnboard($client_policy_id)
|
public function initiateWellnessOnboard($client_policy_id)
|
||||||
{
|
{
|
||||||
$r = Jobs::addJob(['job_name' => 'initiateWellnessOnboardJob','payload' => ['client_policy_id' => $client_policy_id]]);
|
$r = Jobs::addJob(['job_name' => 'initiateWellnessOnboardJob','payload' => ['client_policy_id' => $client_policy_id]]);
|
||||||
|
//$this->initiateWellnessOnboardJob(['client_policy_id' => $client_policy_id]);
|
||||||
return $this->respond(['status' => true, 'code' => 200, 'message' => 'Process started'], 200);
|
return $this->respond(['status' => true, 'code' => 200, 'message' => 'Process started'], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3377,7 +3379,7 @@ class EmployeeController extends AdminController
|
|||||||
// echo '==============================';die();
|
// echo '==============================';die();
|
||||||
// $data = '[{"id":12847,"employee_id":"TEST_EMP_001","client_policy_id":null,"tpa_id":null,"uhid":null,"batch_code":null,"status":"active","pre_existing_alignments":null,"age_band":null,"basic_cover_si":"0","date_coverage":"2025-01-01","policy_end_date":"2025-12-31","days":"0","premium":"0","rata_premimum":"0","gst":"0","si_enhancement_date":null,"date_of_exit":null,"reason_for_exit":null,"claim_status":"0","created_by":null,"created_at":null,"updated_by":null,"updated_at":null,"is_active":"1","rand_string":null,"ecard_sent_status":"0","payable_employee":"0","file_id":null,"wellness_onboard":"0","name":"test name","relationship":"SELF","emp_code":"TEST_EMP_001","email_corporate":"test@gmail.com","mobile":"9797976565","dob":"1975-08-09"},{"id":12846,"employee_id":"TEST_EMP_001","client_policy_id":null,"tpa_id":null,"uhid":null,"batch_code":null,"status":"active","pre_existing_alignments":null,"age_band":null,"basic_cover_si":"0","date_coverage":"2025-01-01","policy_end_date":"2025-12-31","days":"0","premium":"0","rata_premimum":"0","gst":"0","si_enhancement_date":null,"date_of_exit":null,"reason_for_exit":null,"claim_status":"0","created_by":null,"created_at":null,"updated_by":null,"updated_at":null,"is_active":"1","rand_string":null,"ecard_sent_status":"0","payable_employee":"0","file_id":null,"wellness_onboard":"0","name":"dependent 1","relationship":"SON","emp_code":"TEST_EMP_001","email_corporate":"dependent1@gmail.com","mobile":"9898989898","dob":"2001-08-09"}]';
|
// $data = '[{"id":12847,"employee_id":"TEST_EMP_001","client_policy_id":null,"tpa_id":null,"uhid":null,"batch_code":null,"status":"active","pre_existing_alignments":null,"age_band":null,"basic_cover_si":"0","date_coverage":"2025-01-01","policy_end_date":"2025-12-31","days":"0","premium":"0","rata_premimum":"0","gst":"0","si_enhancement_date":null,"date_of_exit":null,"reason_for_exit":null,"claim_status":"0","created_by":null,"created_at":null,"updated_by":null,"updated_at":null,"is_active":"1","rand_string":null,"ecard_sent_status":"0","payable_employee":"0","file_id":null,"wellness_onboard":"0","name":"test name","relationship":"SELF","emp_code":"TEST_EMP_001","email_corporate":"test@gmail.com","mobile":"9797976565","dob":"1975-08-09"},{"id":12846,"employee_id":"TEST_EMP_001","client_policy_id":null,"tpa_id":null,"uhid":null,"batch_code":null,"status":"active","pre_existing_alignments":null,"age_band":null,"basic_cover_si":"0","date_coverage":"2025-01-01","policy_end_date":"2025-12-31","days":"0","premium":"0","rata_premimum":"0","gst":"0","si_enhancement_date":null,"date_of_exit":null,"reason_for_exit":null,"claim_status":"0","created_by":null,"created_at":null,"updated_by":null,"updated_at":null,"is_active":"1","rand_string":null,"ecard_sent_status":"0","payable_employee":"0","file_id":null,"wellness_onboard":"0","name":"dependent 1","relationship":"SON","emp_code":"TEST_EMP_001","email_corporate":"dependent1@gmail.com","mobile":"9898989898","dob":"2001-08-09"}]';
|
||||||
// $data = (array)json_decode($data,true);
|
// $data = (array)json_decode($data,true);
|
||||||
// print_r($data);
|
// print_r(count($data));
|
||||||
// echo '==============================';die();
|
// echo '==============================';die();
|
||||||
if(is_array($data) && count($data))
|
if(is_array($data) && count($data))
|
||||||
{
|
{
|
||||||
@ -3406,7 +3408,7 @@ class EmployeeController extends AdminController
|
|||||||
$familiesPayload[$empCode] = $this->buildFamilyPayload($empCode, $members);
|
$familiesPayload[$empCode] = $this->buildFamilyPayload($empCode, $members);
|
||||||
}
|
}
|
||||||
|
|
||||||
// print_r($familiesPayload);die();
|
// print_rr($familiesPayload);die();
|
||||||
$apiResponse = $this->sendFamiliesToWellnessApi($familiesPayload);
|
$apiResponse = $this->sendFamiliesToWellnessApi($familiesPayload);
|
||||||
// print_r($apiResponse);
|
// print_r($apiResponse);
|
||||||
$updatedData = $this->updateWellnessOnboardResponseToDB($apiResponse);
|
$updatedData = $this->updateWellnessOnboardResponseToDB($apiResponse);
|
||||||
@ -3439,7 +3441,7 @@ class EmployeeController extends AdminController
|
|||||||
{
|
{
|
||||||
// Use the first member as primary reference for policy level data
|
// Use the first member as primary reference for policy level data
|
||||||
$primary = $members[0];
|
$primary = $members[0];
|
||||||
|
// print_rr($primary);die();
|
||||||
// Map DB fields to your required "policyDetails" structure
|
// Map DB fields to your required "policyDetails" structure
|
||||||
$policyStartDate = $primary['cp_policy_start_date'] ?? null;
|
$policyStartDate = $primary['cp_policy_start_date'] ?? null;
|
||||||
// $policyStartDate = '2025-01-01';
|
// $policyStartDate = '2025-01-01';
|
||||||
@ -3495,6 +3497,7 @@ class EmployeeController extends AdminController
|
|||||||
public function sendFamiliesToWellnessApi(array $familiesPayload): array
|
public function sendFamiliesToWellnessApi(array $familiesPayload): array
|
||||||
{
|
{
|
||||||
// CI4 HTTP client
|
// CI4 HTTP client
|
||||||
|
// print_rr($familiesPayload);die();
|
||||||
$client = \Config\Services::curlrequest();//die();
|
$client = \Config\Services::curlrequest();//die();
|
||||||
$endpointUrl = getenv('WELLNESS_ONBOARD_ENDPOINT_URL');
|
$endpointUrl = getenv('WELLNESS_ONBOARD_ENDPOINT_URL');
|
||||||
// Custom headers
|
// Custom headers
|
||||||
@ -3504,7 +3507,7 @@ class EmployeeController extends AdminController
|
|||||||
];
|
];
|
||||||
|
|
||||||
foreach ($familiesPayload as $empCode => &$family) {
|
foreach ($familiesPayload as $empCode => &$family) {
|
||||||
|
// print_rr($family);die();
|
||||||
try {
|
try {
|
||||||
$response = $client->post($endpointUrl, [
|
$response = $client->post($endpointUrl, [
|
||||||
'headers' => $headers,
|
'headers' => $headers,
|
||||||
@ -3522,6 +3525,8 @@ class EmployeeController extends AdminController
|
|||||||
'rawBody' => $body,
|
'rawBody' => $body,
|
||||||
'data' => $decoded,
|
'data' => $decoded,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
// In case of exception, store error info
|
// In case of exception, store error info
|
||||||
$family['apiResponse'] = [
|
$family['apiResponse'] = [
|
||||||
@ -3531,6 +3536,8 @@ class EmployeeController extends AdminController
|
|||||||
'error' => $e->getMessage(),
|
'error' => $e->getMessage(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// print_rr($body );die();
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($family); // break reference
|
unset($family); // break reference
|
||||||
|
|||||||
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) {}
|
||||||
|
}
|
||||||
129
app/Filters/SecurityInputFilter.php
Normal file
129
app/Filters/SecurityInputFilter.php
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filters;
|
||||||
|
|
||||||
|
use CodeIgniter\Filters\FilterInterface;
|
||||||
|
use CodeIgniter\HTTP\RequestInterface;
|
||||||
|
use CodeIgniter\HTTP\ResponseInterface;
|
||||||
|
use Config\Services;
|
||||||
|
|
||||||
|
class SecurityInputFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* High-confidence XSS patterns only
|
||||||
|
* (low false-positive set)
|
||||||
|
**/
|
||||||
|
|
||||||
|
protected array $xssPatterns = [
|
||||||
|
// Script execution
|
||||||
|
'/<\s*script\b/i',
|
||||||
|
'/<\/\s*script\s*>/i',
|
||||||
|
|
||||||
|
// JavaScript execution vectors
|
||||||
|
'/javascript\s*:/i',
|
||||||
|
'/vbscript\s*:/i',
|
||||||
|
'/data\s*:\s*text\/html/i',
|
||||||
|
|
||||||
|
// Inline event handlers (strong signal)
|
||||||
|
'/on\w+\s*=\s*["\']?/i',
|
||||||
|
|
||||||
|
// Dangerous HTML tags
|
||||||
|
'/<\s*iframe\b/i',
|
||||||
|
'/<\s*object\b/i',
|
||||||
|
'/<\s*embed\b/i',
|
||||||
|
'/<\s*applet\b/i',
|
||||||
|
|
||||||
|
// Image-based execution
|
||||||
|
'/<\s*img\b[^>]*on\w+/i',
|
||||||
|
|
||||||
|
// SVG-based execution (modern bypass)
|
||||||
|
'/<\s*svg\b/i',
|
||||||
|
'/<\s*math\b/i',
|
||||||
|
|
||||||
|
// Meta refresh redirect
|
||||||
|
'/<\s*meta\b[^>]*http-equiv\s*=\s*["\']?refresh/i',
|
||||||
|
|
||||||
|
// HTML injection via src/href
|
||||||
|
'/<\s*\w+\b[^>]*(src|href)\s*=\s*["\']?\s*(javascript|data)\s*:/i'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
public function before(RequestInterface $request, $arguments = null)
|
||||||
|
{
|
||||||
|
$logger = Services::mylogger();
|
||||||
|
$response = Services::response();
|
||||||
|
|
||||||
|
// Collect all user-controlled input
|
||||||
|
$inputs = array_merge(
|
||||||
|
$request->getGet(),
|
||||||
|
$request->getPost()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (empty($inputs)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($inputs as $field => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$value = json_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Canonicalization (VERY IMPORTANT)
|
||||||
|
$canonical = $this->canonicalize($value);
|
||||||
|
|
||||||
|
// Step 2: Trim (hygiene)
|
||||||
|
$canonical = trim($canonical);
|
||||||
|
|
||||||
|
// Step 3: Detection (signal-only)
|
||||||
|
if ($this->detectXss($canonical)) {
|
||||||
|
|
||||||
|
// 🔐 Log intent, not data
|
||||||
|
$logger->logme('critical','SECURITY_BLOCKED_REQUEST - '. json_encode([
|
||||||
|
'ip' => $request->getIPAddress(),
|
||||||
|
'method' => $request->getMethod(),
|
||||||
|
'uri' => current_url(),
|
||||||
|
'field' => $field,
|
||||||
|
'attack' => 'XSS_PATTERN',
|
||||||
|
'length' => strlen($canonical),
|
||||||
|
'hash' => hash('sha256', $canonical),
|
||||||
|
]));
|
||||||
|
|
||||||
|
// ⛔ Block request
|
||||||
|
return $response
|
||||||
|
->setStatusCode(403)
|
||||||
|
->setJSON([
|
||||||
|
'status' => 403,
|
||||||
|
'error' => 'Forbidden',
|
||||||
|
'message' => 'Malicious input detected'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
||||||
|
{
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canonicalization prevents encoded bypass
|
||||||
|
*/
|
||||||
|
private function canonicalize(string $value): string
|
||||||
|
{
|
||||||
|
$value = urldecode($value);
|
||||||
|
$value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||||||
|
|
||||||
|
// Remove invisible control characters
|
||||||
|
return preg_replace('/[\x00-\x1F\x7F]/u', '', $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function detectXss(string $value): bool
|
||||||
|
{
|
||||||
|
foreach ($this->xssPatterns as $pattern) {
|
||||||
|
if (preg_match($pattern, $value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,35 +9,70 @@
|
|||||||
<label for="zip_folder">Zip Folder (inside zip to deploy)</label>
|
<label for="zip_folder">Zip Folder (inside zip to deploy)</label>
|
||||||
<select name="zip_folder" id="zip_folder">
|
<select name="zip_folder" id="zip_folder">
|
||||||
<option value="web/">web/</option>
|
<option value="web/">web/</option>
|
||||||
<option value="dist/">dist/</option>
|
<!-- <option value="dist/">dist/</option> -->
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="s3_bucket">S3 Bucket</label>
|
<label for="s3_bucket">S3 Bucket</label>
|
||||||
<select name="s3_bucket" id="s3_bucket">
|
<select name="s3_bucket" id="s3_bucket">
|
||||||
<option value="benefits-app-bucket">benefits-app-bucket</option>
|
<option value="uat-benefits-app-bucket">UAT Benefits</option>
|
||||||
<option value="other-bucket">other-bucket</option>
|
<option value="uat-hr-app-bucket">UAT HR</option>
|
||||||
|
<option value="benefits-app-bucket">Live Benefits</option>
|
||||||
|
<option value="live-hr-app-bucket">Live HR</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="s3_prefix">S3 Prefix</label>
|
<label for="s3_prefix">S3 Prefix</label>
|
||||||
<input type="text" name="s3_prefix" id="s3_prefix" value="hr/">
|
<input type="text" name="s3_prefix" id="s3_prefix" value="/*">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="cf_distribution_id">CloudFront Distribution ID (optional)</label>
|
<label for="cf_distribution_id">CloudFront Distribution ID (optional)</label>
|
||||||
<input type="text" name="cf_distribution_id" id="cf_distribution_id" value="E1MKRK4U5MZ3BD">
|
<!-- <input type="text" name="cf_distribution_id" id="cf_distribution_id" value="E1MKRK4U5MZ3BD"> -->
|
||||||
|
<select name="cf_distribution_id" id="cf_distribution_id">
|
||||||
|
<option value="EUBZ8CDSV9KZZ">UAT Benefits</option>
|
||||||
|
<option value="E9TNPRI9ITM1M">UAT HR</option>
|
||||||
|
<option value="E1MKRK4U5MZ3BD">Live Benefits</option>
|
||||||
|
<option value="E3TE01DPKHTD8B">Live HR</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="cf_paths">
|
<label for="cf_paths">
|
||||||
CloudFront Invalidation Paths (comma or newline separated, e.g. <code>/hr/*,/hr/special/*</code>)
|
CloudFront Invalidation Paths (comma or newline separated, e.g. <code>/hr/*,/hr/special/*</code>)
|
||||||
</label>
|
</label>
|
||||||
<input type="text" name="cf_paths" id="cf_paths" value="/hr/*">
|
<input type="text" name="cf_paths" id="cf_paths" value="/*">
|
||||||
<!-- If you prefer multi-line, use <textarea> instead of <input> -->
|
<!-- If you prefer multi-line, use <textarea> instead of <input> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit">Deploy</button>
|
<button type="submit">Deploy</button>
|
||||||
</form>
|
</form>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const bucketToDistributionMap = {
|
||||||
|
'uat-benefits-app-bucket': 'EUBZ8CDSV9KZZ',
|
||||||
|
'uat-hr-app-bucket': 'E9TNPRI9ITM1M',
|
||||||
|
'benefits-app-bucket': 'E1MKRK4U5MZ3BD',
|
||||||
|
'live-hr-app-bucket': 'E3TE01DPKHTD8B'
|
||||||
|
};
|
||||||
|
|
||||||
|
const s3BucketEl = document.getElementById('s3_bucket');
|
||||||
|
const cfDistributionEl = document.getElementById('cf_distribution_id');
|
||||||
|
|
||||||
|
s3BucketEl.addEventListener('change', function () {
|
||||||
|
const selectedBucket = this.value;
|
||||||
|
|
||||||
|
if (bucketToDistributionMap[selectedBucket]) {
|
||||||
|
cfDistributionEl.value = bucketToDistributionMap[selectedBucket];
|
||||||
|
} else {
|
||||||
|
// Optional: reset if no mapping found
|
||||||
|
cfDistributionEl.value = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-select on page load (useful for edit forms)
|
||||||
|
s3BucketEl.dispatchEvent(new Event('change'));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@ -5,6 +5,27 @@ Options -Indexes
|
|||||||
# Rewrite engine
|
# 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.
|
# Turning on the rewrite engine is necessary for the following rules and features.
|
||||||
# FollowSymLinks must be enabled for this to work.
|
# FollowSymLinks must be enabled for this to work.
|
||||||
<IfModule mod_rewrite.c>
|
<IfModule mod_rewrite.c>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user