各位观众老爷们,大家好!今天咱们来聊聊JS安全审计这事儿,但咱不搞那些虚头巴脑的理论,直接上干货,教你用自定义规则来武装你的代码。
开场白:代码界的“朝阳群众”
话说江湖险恶,代码世界也一样。各种XSS、SQL注入、CSRF,防不胜防。咱们程序员每天辛辛苦苦搬砖,结果一不小心就被黑客给端了老窝,你说憋屈不憋屈?
所以啊,咱们得想办法,在代码上线之前,就把这些潜在的风险给揪出来。这就是静态代码分析的意义所在,它就像代码界的“朝阳群众”,时刻监视着你的代码,一旦发现可疑之处,立刻报警!
主角登场:ESLint & SonarJS
今天的主角是两位:ESLint 和 SonarJS。
- ESLint: JS 界的“老大哥”,语法检查、代码风格统一不在话下,更重要的是,它支持自定义规则,允许我们根据自己的安全需求,定制专属的“安全卫士”。
- SonarJS: SonarQube 的 JS 插件,功能更强大,除了静态代码分析,还能进行代码质量评估、漏洞检测等。
自定义规则:打造你的专属“安全卫士”
自定义规则是核心,它允许我们针对特定的安全漏洞,编写检测逻辑,让工具自动扫描代码,发现潜在的风险。
案例一:防止 XSS 攻击
XSS (Cross-Site Scripting) 攻击是最常见的 Web 安全漏洞之一。攻击者通过注入恶意脚本到网页中,窃取用户数据或执行恶意操作。
咱们可以写一个 ESLint 规则,检测代码中是否存在将用户输入直接插入到 DOM 中的情况。
// custom-eslint-rules/no-innerhtml.js
module.exports = {
meta: {
type: 'problem',
docs: {
description: '禁止使用 innerHTML 插入用户输入,防止 XSS 攻击',
category: 'Possible Errors',
recommended: 'error',
},
fixable: null, // or "code" if you want to provide a fix
schema: [], // no options
},
create: function (context) {
return {
MemberExpression: function (node) {
if (node.property.name === 'innerHTML') {
const objectName = node.object.name;
// 这里可以添加更复杂的逻辑来判断 `objectName` 是否是用户输入
// 比如,判断它是否来自于 URL 参数、表单数据等
context.report({
node: node,
message: '避免使用 innerHTML 插入用户输入,可能导致 XSS 攻击',
});
}
},
};
},
};
代码解读:
meta
: 定义规则的元数据,包括类型、描述、分类、推荐级别等。create
: 核心函数,接收context
对象,用于注册访问者,并在代码中发现问题时进行报告。MemberExpression
: 访问成员表达式,比如element.innerHTML
。node.property.name === 'innerHTML'
: 判断是否是访问innerHTML
属性。context.report
: 报告问题,node
指定问题节点,message
指定错误信息。
使用方法:
- 创建
custom-eslint-rules
文件夹,将no-innerhtml.js
放入其中。 - 在
.eslintrc.js
中配置:
module.exports = {
"plugins": ["custom-eslint-rules"],
"rules": {
"custom-eslint-rules/no-innerhtml": "error"
},
"rulesDirectory": ["custom-eslint-rules/"]
};
案例二:防止 SQL 注入
SQL 注入是另一种常见的 Web 安全漏洞。攻击者通过构造恶意的 SQL 查询,窃取或篡改数据库中的数据。
咱们可以写一个 SonarJS 规则,检测代码中是否存在拼接 SQL 语句的情况。
// SonarJS 规则示例 (需要 SonarQube 环境)
import type { RuleContext } from "sonarqube";
import { BaseNodeListener } from "./../utils/base-node-listener";
export const rule = {
meta: {
type: "vulnerability",
ruleId: "no-string-based-sql-injection",
description: "防止SQL注入风险",
securityStandards: {
"OWASP": ["A03:2021 - Injection"],
"CWE": ["CWE-89"]
}
},
create: (context: RuleContext) => {
return {
...BaseNodeListener,
TaggedTemplateExpression(node: any) {
if (node.tag.name === 'sql') {
context.report({
node,
message: '避免使用字符串拼接方式构建SQL语句,推荐使用参数化查询。',
});
}
},
BinaryExpression(node: any) {
if (node.operator === '+' && (isStringConcatenation(node.left) || isStringConcatenation(node.right))) {
context.report({
node,
message: '避免使用字符串拼接方式构建SQL语句,推荐使用参数化查询。',
});
}
},
};
function isStringConcatenation(node: any): boolean {
if(!node) return false;
if (node.type === "Literal" && typeof node.value === "string") {
return true;
}
if (node.type === "BinaryExpression" && node.operator === "+") {
return isStringConcatenation(node.left) || isStringConcatenation(node.right);
}
return false;
}
}
};
代码解读:
meta
: 定义规则的元数据,包括类型、描述、安全标准等。TaggedTemplateExpression
: 检测标记模板字符串,比如sql
SELECT * FROM users WHERE id = ${id}“。BinaryExpression
: 检测二元表达式,比如"SELECT * FROM users WHERE id = " + id
。isStringConcatenation
: 递归判断表达式是否是字符串拼接。context.report
: 报告问题,node
指定问题节点,message
指定错误信息。
使用方法:
- 将规则文件放入 SonarQube 插件目录。
- 在 SonarQube 中激活规则。
案例三:防止 CSRF 攻击
CSRF (Cross-Site Request Forgery) 攻击是指攻击者冒充用户发送恶意请求,执行未授权的操作。
咱们可以写一个 ESLint 规则,检测代码中是否存在未添加 CSRF token 的表单提交。
// custom-eslint-rules/require-csrf-token.js
module.exports = {
meta: {
type: 'problem',
docs: {
description: '要求所有表单提交都包含 CSRF token',
category: 'Security',
recommended: 'error',
},
fixable: null,
schema: [],
},
create: function (context) {
return {
CallExpression: function (node) {
if (node.callee.name === 'fetch' || node.callee.property?.name === 'post') {
// 检查 fetch 或 axios.post 请求
const options = node.arguments[1];
if (options && options.type === 'ObjectExpression') {
let hasCsrfToken = false;
for (const property of options.properties) {
if (property.key.name === 'headers' && property.value.type === 'ObjectExpression') {
for (const headerProperty of property.value.properties) {
if (headerProperty.key.value === 'X-CSRF-Token' || headerProperty.key.name === 'X-CSRF-Token') {
hasCsrfToken = true;
break;
}
}
}
}
if (!hasCsrfToken) {
context.report({
node: node,
message: '缺少 CSRF token,可能存在 CSRF 攻击风险',
});
}
}
}
if (node.callee.name === 'XMLHttpRequest') {
// 检查 XMLHttpRequest 请求
context.report({
node: node,
message: '请确保 XMLHttpRequest 请求包含了 CSRF token',
});
}
},
};
},
};
代码解读:
CallExpression
: 访问函数调用表达式,比如fetch('/api/data', { ... })
。- 检查
fetch
或axios.post
请求的配置对象中是否包含X-CSRF-Token
头部。 context.report
: 报告问题,node
指定问题节点,message
指定错误信息。
使用方法:
- 创建
custom-eslint-rules
文件夹,将require-csrf-token.js
放入其中。 - 在
.eslintrc.js
中配置:
module.exports = {
"plugins": ["custom-eslint-rules"],
"rules": {
"custom-eslint-rules/require-csrf-token": "error"
},
"rulesDirectory": ["custom-eslint-rules/"]
};
案例四:禁止使用高危函数
有些 JS 函数本身就存在安全风险,比如 eval
、Function
等。咱们可以写一个 ESLint 规则,禁止使用这些高危函数。
// custom-eslint-rules/no-dangerous-functions.js
module.exports = {
meta: {
type: 'problem',
docs: {
description: '禁止使用 eval 和 Function 等高危函数',
category: 'Security',
recommended: 'error',
},
fixable: null,
schema: [],
},
create: function (context) {
return {
Identifier: function (node) {
if (node.name === 'eval' || node.name === 'Function') {
context.report({
node: node,
message: '禁止使用 eval 和 Function 等高危函数,可能存在安全风险',
});
}
},
};
},
};
代码解读:
Identifier
: 访问标识符,比如变量名、函数名等。- 判断标识符是否是
eval
或Function
。 context.report
: 报告问题,node
指定问题节点,message
指定错误信息。
使用方法:
- 创建
custom-eslint-rules
文件夹,将no-dangerous-functions.js
放入其中。 - 在
.eslintrc.js
中配置:
module.exports = {
"plugins": ["custom-eslint-rules"],
"rules": {
"custom-eslint-rules/no-dangerous-functions": "error"
},
"rulesDirectory": ["custom-eslint-rules/"]
};
更多可能性:规则的无限扩展
这只是几个简单的例子,你可以根据自己的安全需求,编写更复杂的规则。
安全漏洞 | 规则示例 |
---|---|
目录遍历 | 检测代码中是否存在拼接文件路径的情况,防止攻击者访问任意文件。 |
SSRF | 检测代码中是否存在使用用户输入作为 URL 的情况,防止攻击者利用服务器发送恶意请求。 |
反序列化 | 检测代码中是否存在使用 JSON.parse 解析用户输入的情况,防止攻击者构造恶意对象,执行任意代码。 |
总结:安全,永无止境
静态代码分析只是安全审计的一部分,它不能解决所有问题。但是,它可以帮助我们发现潜在的风险,提高代码的安全性。
记住,安全是一个持续的过程,我们需要不断学习新的安全知识,更新我们的规则,才能更好地保护我们的代码。
友情提示:
- 编写规则时,要充分考虑各种情况,避免误报。
- 定期更新规则,以应对新的安全漏洞。
- 将静态代码分析集成到 CI/CD 流程中,自动化安全审计。
好了,今天的讲座就到这里。希望大家能够学以致用,打造自己的专属“安全卫士”,让我们的代码更加安全可靠! 感谢各位的收看!