
1) 【一句话结论】:防止SQL注入的核心是使用参数化查询(预编译语句),防止XSS的核心是对用户输入进行输出编码(转义/编码),结合输入验证(如白名单)可增强防御,两者结合可有效阻断注入和跨站脚本攻击。
2) 【原理/概念讲解】:SQL注入是指攻击者通过在输入字段中插入恶意SQL代码,操纵数据库执行非预期查询。例如,用户输入“' or 1=1 --”会绕过验证逻辑,导致查询结果为所有记录。参数化查询通过将SQL语句与参数分离(如Java的PreparedStatement用“?”占位符),数据库执行时仅替换参数,不解析SQL文本,从而阻止恶意代码执行。XSS是指攻击者注入恶意脚本(如<script>标签),在用户浏览器中执行。输出编码(如HTML编码)将用户输入中的特殊字符(如<、>、&)转换为实体(如<、>、&),使脚本无法被浏览器解析为代码。类比:SQL注入像把“炸弹”塞进数据库查询的管道,参数化查询是给管道装了“过滤器和固定容器”,不管输入是什么,都按预设格式处理;XSS像把“病毒”放进网页的展示框,输出编码是给展示框加了“防篡改标签”,把病毒字符变成文本,无法执行。
3) 【对比与适用场景】:
| 攻击类型 | 防御方法 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|---|
| SQL注入 | 参数化查询(预编译语句) | 将SQL语句的参数与语句本身分离,参数通过占位符传递 | 防止恶意SQL拼接,数据库执行时替换参数 | Java(PreparedStatement)、Python(psycopg2等预编译) | 需正确处理参数类型,避免类型混淆(如字符串参数传入数字导致错误或绕过) |
| XSS | 输出编码(字符编码/转义) | 对用户输入的HTML/JavaScript字符进行编码或转义 | 防止脚本执行,将脚本字符转换为文本 | Java(HtmlUtils.htmlEscape)、Python(html.escape) | 需根据上下文选择编码方式(如HTML场景用HTML编码,JavaScript场景用JavaScript编码),避免过度编码导致功能失效 |
| 辅助手段(输入验证) | 白名单验证 | 对动态SQL的表名、列名等输入进行白名单检查 | 确保输入属于允许的集合,防止绕过参数化 | 动态表名/列名查询 | 需维护白名单,避免遗漏或误判 |
4) 【示例】:
SQL注入防御(Java,含动态表名白名单验证):
假设用户输入表名,需验证是否在白名单:
// 白名单集合
List<String> allowedTables = Arrays.asList("users", "orders", "products");
String userTable = request.getParameter("table"); // 用户输入的表名
if (!allowedTables.contains(userTable)) {
throw new IllegalArgumentException("Invalid table name: " + userTable);
}
// 参数化查询
String sql = "SELECT * FROM ? WHERE id = ?";
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setString(1, userTable); // 替换第一个参数为表名
pstmt.setInt(2, userId);
ResultSet rs = pstmt.executeQuery();
}
(注意:动态表名拼接时,必须先验证,再通过参数化传递,避免SQL注入)
XSS防御(Java,存储型两次编码):
用户输入评论,存储前和显示前均编码:
String rawComment = "用户输入的评论,包含<script>alert('XSS')</script>"; // 原始输入
// 存储到数据库前编码(防止存储型XSS)
String storedComment = HtmlUtils.htmlEscape(rawComment);
// 显示时再次编码(确保浏览器解析为文本)
String displayedComment = HtmlUtils.htmlEscape(storedComment);
// 显示页面使用displayedComment
5) 【面试口播版答案】:
面试官您好,针对SQL注入和XSS的防御,核心是参数化查询和输出编码,结合输入验证。比如SQL注入,用Java的PreparedStatement,把SQL语句和参数分开,用户输入即使有恶意代码,数据库也只替换参数,不会执行恶意SQL;XSS的话,用户输入的评论内容,先进行HTML编码存入数据库,显示时再编码,这样两次编码,防止脚本执行。具体来说,比如查询用户时,用预编译语句,传入用户名参数,不会因为用户输入“' or 1=1 --”而出错;XSS的话,用户输入的评论经过两次编码后,显示为文本,不会执行恶意脚本。这样能有效防止两种攻击。
6) 【追问清单】:
问题1:为什么说参数化查询比手动字符串拼接更安全?
回答要点:参数化查询在数据库层面处理参数,避免SQL解析拼接,而手动拼接可能导致SQL语法错误或恶意代码执行,且数据库不会执行拼接后的恶意SQL。
问题2:如何处理动态SQL(如带动态表名或列名的查询)?
回答要点:对于动态表名,需通过白名单验证(如检查是否在允许的表名列表中),再通过参数化传递;对于动态列名,避免直接拼接,改用预编译的参数化方式,或使用数据库的元数据查询(如信息模式表)。
问题3:参数化查询中参数类型不匹配(如字符串参数传入数字)会有什么风险?
回答要点:可能导致数据库错误(如类型转换失败),或攻击者利用类型混淆绕过验证(例如,将字符串“1”传入数字参数,可能绕过某些条件判断)。
问题4:存储型XSS为什么需要两次编码?
回答要点:存储型XSS是用户输入存入数据库后,后续页面显示时未编码,导致脚本执行。两次编码(插入前和显示前)可确保无论何时显示,输入内容都被正确转义,防止脚本执行。
7) 【常见坑/雷区】:
SELECT * FROM " + userTable + " WHERE id = ?)。String sql = "SELECT * FROM users WHERE username = '" + userInput + "'";)。