6.8 KiB
6.8 KiB
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) |
// ❌ 禁止
$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 绑定参数
// ❌ 禁止
$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()
// ❌ 禁止
exec("convert {$userFile} output.png");
// ✅ 正确(使用内置库)
$image = new Imagick($sanitizedPath);
TypeScript Prototype Pollution
- 使用
Map/Set替代对象字面量存储用户数据 - 合并对象时拦截
__proto__、constructor、prototype键
// ❌ 禁止
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] })
🔑 密码存储
// ❌ 禁止(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 查询资源,必须附加所有权校验
// ❌ 禁止 — 用户可访问任意 ID 的数据
$order = Order::find($request->input('id'));
// ✅ 正确 — 限定当前用户范围
$order = $this->user->orders()->findOrFail($request->input('id'));
Mass Assignment 防护
- Hyperf FormRequest 必须声明
rules()白名单 - Model 必须使用
$fillable而非$guarded = []
// ❌ 禁止
$user->fill($request->all());
// ✅ 正确(只允许明确字段)
$user->fill($request->only(['name', 'email', 'avatar']));
🍪 Session 和 Cookie 安全
// 登录成功后必须轮转 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 防护
// ❌ 禁止 — 直接使用 v-html 且无净化
// <div v-html="userContent"></div>
// ✅ 正确 — 使用 DOMPurify 净化
import DOMPurify from 'dompurify'
const clean = DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ['b', 'i', 'p', 'a', 'ul', 'li'],
ALLOWED_ATTR: ['href'],
})
// <div v-html="clean"></div>
// ❌ 禁止 — 动态代码执行
eval(userCode)
new Function(userCode)()
setTimeout(userString, 100)
// ❌ 禁止 — 直接 DOM 写入
element.innerHTML = userInput
document.write(userInput)
外链安全
<!-- ✅ 所有 target="_blank" 必须加防护 -->
<a href="..." target="_blank" rel="noopener noreferrer">外链</a>
CSRF 防护
- 所有状态变更请求(POST/PUT/DELETE/PATCH)必须携带 CSRF Token
- 禁止用 GET 请求执行状态变更操作
📁 文件上传安全
// ✅ 文件上传安全检查清单
// 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('文件过大');
📊 安全日志规范
// ❌ 禁止记录敏感信息
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 请求必须验证目标域名白名单
// 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/