《前端CSRF & XSS攻击》

88 阅读2分钟

1. XSS(跨站脚本攻击)

概念:XSS(跨站脚本攻击),是指攻击者利用站点的漏洞,在表单提交时,当其他正常用户浏览页面,而页面中刚好出现攻击者的恶意在表单内容中加入一些恶意脚本脚本时,脚本被执行,从而使得页面遭到破坏,或者用户信息被窃取

1.1 反射型 XSS

// 危险的代码
const content = new URLSearchParams(location.search).get('content');
document.getElementById('message').innerHTML = content;

// 防御措施
function escapeHtml(str) {
    return str
        .replace(/&/g, '&')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}

// 安全的代码
document.getElementById('message').innerHTML = escapeHtml(content);

1.2 存储型 XSS

// 前端防御
const comment = {
    content: '<script>alert("xss")</script>',
    author: 'hacker'
};

// 渲染评论内容
function renderComment(comment) {
    // 使用 textContent 而不是 innerHTML
    commentEl.textContent = comment.content;
    
    // 或者使用 DOMPurify 库
    import DOMPurify from 'dompurify';
    commentEl.innerHTML = DOMPurify.sanitize(comment.content);
}

1.3 DOM 型 XSS

// 危险的 URL 解析
const hash = location.hash.substring(1);
document.querySelector(hash).style.display = 'block';

// 安全的实现
const safeHash = encodeURIComponent(hash);
const allowedSelectors = ['#profile', '#settings', '#dashboard'];
if (allowedSelectors.includes(safeHash)) {
    document.querySelector(safeHash).style.display = 'block';
}

2. CSRF(跨站请求伪造)

概念:它是指攻击者利用了用户的身份信息,执行了用户非本意的操作

2.1 防御措施

// 1. CSRF Token
class CSRFProtection {
    constructor() {
        this.token = this.generateToken();
    }

    generateToken() {
        return Math.random().toString(36).substring(2);
    }

    // 添加 token 到请求头
    addTokenToHeaders(headers = {}) {
        return {
            ...headers,
            'X-CSRF-Token': this.token
        };
    }

    // 验证 token
    validateToken(token) {
        return token === this.token;
    }
}

// 2. 使用示例
const csrf = new CSRFProtection();

// 发送请求
async function sendRequest(url, data) {
    try {
        const response = await fetch(url, {
            method: 'POST',
            headers: csrf.addTokenToHeaders({
                'Content-Type': 'application/json'
            }),
            body: JSON.stringify(data),
            credentials: 'include'  // 包含 cookies
        });
        return await response.json();
    } catch (error) {
        console.error('Request failed:', error);
    }
}

3. 点击劫持防护

// 1. 前端 JavaScript 防护
function antiClickjacking() {
    if (window.self !== window.top) {
        window.top.location = window.self.location;
    }
}

// 2. CSP 头部设置
// Content-Security-Policy: frame-ancestors 'none'

4. 安全的 Cookie 设置

// 设置安全的 Cookie
document.cookie = "session=123; Secure; HttpOnly; SameSite=Strict";

// Cookie 管理类
class SecureCookieManager {
    setCookie(name, value, options = {}) {
        const defaultOptions = {
            path: '/',
            secure: true,
            httpOnly: true,
            sameSite: 'Strict',
            maxAge: 7200 // 2小时
        };

        const cookieOptions = { ...defaultOptions, ...options };
        let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;

        Object.entries(cookieOptions).forEach(([key, value]) => {
            cookie += `; ${key}=${value}`;
        });

        document.cookie = cookie;
    }
}

5. 内容安全策略(CSP)

// 在 HTML 中设置 CSP
// <meta http-equiv="Content-Security-Policy" content="default-src 'self'">

// CSP 违规报告
document.addEventListener('securitypolicyviolation', (e) => {
    const violation = {
        blockedURI: e.blockedURI,
        violatedDirective: e.violatedDirective,
        originalPolicy: e.originalPolicy
    };
    
    // 发送违规报告
    fetch('/api/csp-report', {
        method: 'POST',
        body: JSON.stringify(violation)
    });
});

6. 安全的文件上传

class SecureFileUpload {
    constructor() {
        this.allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
        this.maxSize = 5 * 1024 * 1024; // 5MB
    }

    validateFile(file) {
        if (!this.allowedTypes.includes(file.type)) {
            throw new Error('Invalid file type');
        }

        if (file.size > this.maxSize) {
            throw new Error('File too large');
        }

        return true;
    }

    async uploadFile(file) {
        try {
            this.validateFile(file);

            const formData = new FormData();
            formData.append('file', file);

            const response = await fetch('/api/upload', {
                method: 'POST',
                body: formData,
                headers: {
                    'X-Requested-With': 'XMLHttpRequest'
                }
            });

            return await response.json();
        } catch (error) {
            console.error('Upload failed:', error);
            throw error;
        }
    }
}

7. 安全的 API 请求封装

class SecureAPIClient {
    constructor(baseURL) {
        this.baseURL = baseURL;
        this.csrf = new CSRFProtection();
    }

    async request(endpoint, options = {}) {
        const defaultOptions = {
            credentials: 'include',
            headers: {
                'Content-Type': 'application/json',
                'X-Requested-With': 'XMLHttpRequest'
            }
        };

        const secureOptions = {
            ...defaultOptions,
            ...options,
            headers: {
                ...defaultOptions.headers,
                ...options.headers,
                ...this.csrf.addTokenToHeaders()
            }
        };

        try {
            const response = await fetch(
                `${this.baseURL}${endpoint}`, 
                secureOptions
            );

            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            return await response.json();
        } catch (error) {
            console.error('API request failed:', error);
            throw error;
        }
    }
}

8. 安全最佳实践

8.1 输入验证

class InputValidator {
    static sanitizeInput(input) {
        return DOMPurify.sanitize(input);
    }

    static validateEmail(email) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return emailRegex.test(email);
    }

    static validatePassword(password) {
        // 至少8位,包含大小写字母和数字
        const passwordRegex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}$/;
        return passwordRegex.test(password);
    }
}

8.2 错误处理

class SecurityError extends Error {
    constructor(message, code) {
        super(message);
        this.name = 'SecurityError';
        this.code = code;
    }
}

function handleError(error) {
    if (error instanceof SecurityError) {
        // 处理安全相关错误
        console.error(`Security Error ${error.code}: ${error.message}`);
    } else {
        // 处理其他错误
        console.error('An error occurred:', error);
    }
}