# 026-secure-coding.mdc (Deep Reference) > 该文件为原始详细规范归档,供 Tier 3 按需读取。 --- # 🔐 Secure Coding Standards (Project CodeGuard) > 安全不是事后审查,而是编码时的默认选择。 ## ❌ 禁用加密算法(绝对禁止) 以下算法已被证实不安全,**禁止在任何新代码中使用**: | 类型 | 禁用 | 替代 | |------|------|------| | 哈希 | MD2、MD4、**MD5**、SHA-0、**SHA-1** | SHA-256、SHA-3 | | 对称加密 | RC2、RC4、Blowfish、DES、3DES、**AES-ECB** | **AES-GCM**、ChaCha20-Poly1305 | | 密钥交换 | 静态 RSA、匿名 DH | ECDHE (X25519) | ```php // ❌ 禁止 $hash = md5($password); $hash = sha1($data); openssl_encrypt($data, 'AES-128-ECB', $key); // ✅ 正确 $hash = password_hash($password, PASSWORD_ARGON2ID); $hash = hash('sha256', $data); openssl_encrypt($data, 'AES-256-GCM', $key, 0, $iv, $tag); ``` ## 💉 注入防护 ### SQL 注入 - **100% 使用参数化查询**,禁止字符串拼接 SQL - 使用 Hyperf ORM 或 PDO 绑定参数 ```php // ❌ 禁止 $result = Db::select("SELECT * FROM users WHERE name = '{$name}'"); // ✅ 正确 $result = Db::select('SELECT * FROM users WHERE name = ?', [$name]); // 或 User::where('name', $name)->first(); ``` ### OS 命令注入 - 优先使用内置函数替代 shell 调用 - 禁止将用户输入直接传入 `exec()`、`system()`、`shell_exec()` ```php // ❌ 禁止 exec("convert {$userFile} output.png"); // ✅ 正确(使用内置库) $image = new Imagick($sanitizedPath); ``` ### TypeScript Prototype Pollution - 使用 `Map` / `Set` 替代对象字面量存储用户数据 - 合并对象时拦截 `__proto__`、`constructor`、`prototype` 键 ```typescript // ❌ 禁止 function merge(target, source) { Object.assign(target, source) // 可能导致 prototype pollution } // ✅ 正确 const safe = Object.create(null) const blocked = ['__proto__', 'constructor', 'prototype'] Object.keys(source).filter(k => !blocked.includes(k)).forEach(k => { safe[k] = source[k] }) ``` ## 🔑 密码存储 ```php // ❌ 禁止(bcrypt 有 72 字节截断限制) $hash = password_hash($password, PASSWORD_BCRYPT); // ✅ 推荐(Argon2id:更安全,无长度限制) $hash = password_hash($password, PASSWORD_ARGON2ID, [ 'memory_cost' => 65536, // 64 MiB 'time_cost' => 2, 'threads' => 1, ]); // 验证(常量时间比较,防时序攻击) $valid = password_verify($input, $hash); ``` ## 🔒 访问控制(IDOR / Mass Assignment 防护) ### IDOR 防护 - 永远不要只凭用户传入的 ID 查询资源,必须附加所有权校验 ```php // ❌ 禁止 — 用户可访问任意 ID 的数据 $order = Order::find($request->input('id')); // ✅ 正确 — 限定当前用户范围 $order = $this->user->orders()->findOrFail($request->input('id')); ``` ### Mass Assignment 防护 - Hyperf FormRequest 必须声明 `rules()` 白名单 - Model 必须使用 `$fillable` 而非 `$guarded = []` ```php // ❌ 禁止 $user->fill($request->all()); // ✅ 正确(只允许明确字段) $user->fill($request->only(['name', 'email', 'avatar'])); ``` ## 🍪 Session 和 Cookie 安全 ```php // 登录成功后必须轮转 Session ID(防 Session Fixation) session_regenerate_id(true); // Cookie 必须设置安全属性 setcookie('session', $id, [ 'secure' => true, // 仅 HTTPS 'httponly' => true, // 禁止 JS 访问 'samesite' => 'Strict', // 防 CSRF 'path' => '/', ]); ``` - Session 超时:高风险操作 5 分钟,常规 30 分钟,绝对上限 8 小时 - **禁止在 `localStorage` / `sessionStorage` 存储 Session Token**(XSS 可窃取) ## 🌐 客户端安全(Vue 3 / TypeScript) ### XSS 防护 ```typescript // ❌ 禁止 — 直接使用 v-html 且无净化 //
// ✅ 正确 — 使用 DOMPurify 净化 import DOMPurify from 'dompurify' const clean = DOMPurify.sanitize(userContent, { ALLOWED_TAGS: ['b', 'i', 'p', 'a', 'ul', 'li'], ALLOWED_ATTR: ['href'], }) // // ❌ 禁止 — 动态代码执行 eval(userCode) new Function(userCode)() setTimeout(userString, 100) // ❌ 禁止 — 直接 DOM 写入 element.innerHTML = userInput document.write(userInput) ``` ### 外链安全 ```html 外链 ``` ### CSRF 防护 - 所有状态变更请求(POST/PUT/DELETE/PATCH)必须携带 CSRF Token - 禁止用 GET 请求执行状态变更操作 ## 📁 文件上传安全 ```php // ✅ 文件上传安全检查清单 // 1. 白名单扩展名(不是黑名单) $allowed = ['jpg', 'png', 'gif', 'pdf']; $ext = strtolower(pathinfo($file->getClientFilename(), PATHINFO_EXTENSION)); if (!in_array($ext, $allowed)) throw new ValidationException('不允许的文件类型'); // 2. 验证 magic bytes(不信任 Content-Type) $finfo = new finfo(FILEINFO_MIME_TYPE); $mimeType = $finfo->file($tmpPath); // 3. 生成随机文件名(不使用用户提供的文件名) $safeName = Str::uuid() . '.' . $ext; // 4. 存储在 webroot 外 $storagePath = BASE_PATH . '/storage/uploads/' . $safeName; // 5. 设置文件大小限制 if ($file->getSize() > 10 * 1024 * 1024) throw new ValidationException('文件过大'); ``` ## 📊 安全日志规范 ```php // ❌ 禁止记录敏感信息 Log::info('用户登录', ['password' => $password, 'token' => $token]); // ✅ 正确 — 只记录安全信息,用 hash 标识 session Log::info('用户登录', [ 'user_id' => $userId, 'ip' => $request->getServerParams()['REMOTE_ADDR'], 'user_agent' => substr($request->getHeaderLine('user-agent'), 0, 200), 'session_id' => substr(hash('sha256', $sessionId), 0, 16), // 前16位,不泄露原值 'timestamp' => date('c'), ]); ``` ## ⚙️ API 安全 - **禁止在 URL 参数中传递敏感数据**(会出现在日志里) - GraphQL:生产环境禁用 introspection - SSRF:所有外部 HTTP 请求必须验证目标域名白名单 ```php // SSRF 防护示例 $allowedDomains = ['api.trusted.com', 'cdn.trusted.com']; $parsedUrl = parse_url($userProvidedUrl); if (!in_array($parsedUrl['host'], $allowedDomains)) { throw new SecurityException('不允许的目标地址'); } // 还需阻断私有 IP 范围:10.x, 172.16.x, 192.168.x, 127.x ``` ## ✅ 编码时自查清单 每次提交前确认: - [ ] 无 MD5/SHA1/DES/AES-ECB 使用 - [ ] SQL 查询 100% 参数化 - [ ] 资源查询已限定所有权范围(无 IDOR 风险) - [ ] 无 `$request->all()` 直接绑定模型 - [ ] Cookie 包含 Secure + HttpOnly + SameSite 属性 - [ ] `v-html` 使用了 DOMPurify 净化 - [ ] 文件上传使用 UUID 文件名 + magic bytes 验证 - [ ] 日志未包含明文密码/Token/Session ID > 📚 深度参考:`.cursor/skills/security-audit/references/codeguard/`