文章总结: 本文解析Web登录SQL注入风险与危害,提出过滤危险字符与验证输入类型的双重防护。重点展示Java过滤器代码实现,适配已上线系统快速部署,并建议新系统采用请求级验证防范URL编码绕过,构建全流程后端逻辑安全防线。 综合评分: 84 文章分类: WEB安全,漏洞分析,安全开发,解决方案
SQL注入绕开登录?过滤器+数据验证双保险!——Web应用安全实战(PART 3 下)
原创
耶度
野猪与安全
2025年12月25日 10:01 广东
在上一篇「Web 应用安全实战(PART 3 上)」中,我们拆解了登录环节的“已解密的登录请求”漏洞,重点讲解了 HTTPS 加密和临时过渡方案,帮大家筑牢了登录数据传输的安全防线。除了数据传输,登录环节的后端验证逻辑同样暗藏风险,其中最致命的就是——SQL 注入。
今天第三篇(下),我们就聚焦登录环节的后端逻辑安全,深入拆解 SQL 注入这一黑客常用的攻击手段,从漏洞描述、核心风险,到解决方案和技术实现,给出全流程的防护指南。
已解密的登录请求——登录信息“裸奔”传输
中风险漏洞
- 漏洞描述:用户输入未过滤,构造 SQL 语句直接入侵
SQL 注入是 Web 应用中最常见且危害极大的漏洞之一,核心问题在于:后端程序没有对用户输入的登录信息进行合法性判断和过滤,直接将用户输入的内容拼接成SQL语句执行。
黑客利用这一漏洞,在登录界面的用户名或密码输入框中,输入特殊构造的SQL语句片段(比如“’ or 1=1 –”),就能篡改原本的登录验证SQL逻辑,绕过账号密码验证,直接以合法用户(甚至管理员)身份登录系统。
比如原本的登录验证SQL是“select * from user where username=’xxx’ and password=’xxx’”,黑客输入特殊语句后,可能被篡改为“select * from user where username=” or 1=1 –‘ and password=’xxx’”,由于“1=1”恒成立,直接查询出所有用户信息,实现登录。
安全级别:
高风险!无需知道正确账号密码,就能直接入侵系统,是黑客最常用的攻击手段之一。
2. 核心风险:数据库被操控,系统完全失控
SQL 注入的危害远不止绕开登录验证,黑客后续还能:
- 查看数据库中的所有数据,包括用户信息、交易记录、核心业务数据等;
- 修改数据库中的数据,比如篡改用户密码、交易金额等;
- 删除数据库表和数据,导致系统功能瘫痪;
- 通过数据库权限提升,获取服务器控制权,植入恶意代码。
3. 解决方案:多重防护,从输入到开发全流程管控
核心思路是“堵住用户输入的漏洞+规范开发流程”,具体方案如下:
- 过滤危险字符:对用户输入的内容进行严格过滤,剔除 SQL 语句中的特殊字符(如单引号、分号、or、and、–、()等);
- 验证输入类型:检查用户输入的字段类型是否符合预期(比如用户名应为字符串、用户 ID 应为整数),避免输入非法类型数据;
- 屏蔽详细错误信息:避免向用户返回包含 SQL 语句、数据库结构的错误提示,防止黑客利用这些信息构造攻击语句;
- 使用专业漏洞扫描工具:定期对 Web 应用进行扫描,及时发现潜在的 SQL 注入漏洞;
- 规范开发流程:在开发全阶段实施代码安全检查,部署前进行安全测试,部署后定期用扫描工具和站点监视工具检测。
4. 技术实现:两种核心方案,适配不同项目阶段
(1)采用过滤器技术(适配已上线系统)
核心逻辑:通过自定义过滤器,对所有请求的参数进行扫描,若检测到危险字符,直接弹出提示并阻断请求。该方案侵入性小、代码量少、部署方便,适合已完成且正在运行的系统。
具体实现包括三个部分:过滤器类、辅助工具类和 web.xml 配置:
- 过滤器类 ParameterFilter.java:
/** * 参数过滤类,过滤普通表单 */public class ParameterFilter implements Filter { private Pattern scriptPattern; private Pattern sqlPattern; private Pattern letterPattern; private String notProtect = ""; private String totalLetter = ""; public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain filtreChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; String contentType = request.getContentType() == null ? "" : request .getContentType(); if (!contentType.startsWith("multipart/form-data") && !FilterUtils.isContainUrl(notProtect, FilterUtils .getFullUrlFromRequest(req))) { Enumeration params = req.getParameterNames(); boolean isSecurity = true; while (null != params && params.hasMoreElements()) { String para_name = (String) params.nextElement(); String[] para_value = null; para_value = (String[]) req.getParameterValues(para_name); for (int i = 0; i < para_value.length; i++) { String _para_value = para_value[i].toLowerCase(); if (scriptPattern.matcher(_para_value).matches() || sqlPattern.matcher(_para_value).matches()) { isSecurity = false; break; } } if (!isSecurity) break; } if (!isSecurity) { response.setContentType("text/html; charset=GBK"); response.getWriter().write("<script language='javascript'>alert('输入有误,请不要包含"+totalLetter+"中的任何敏感字符!');history.go(-1);</script>"); return; } } filtreChain.doFilter(request, response); } public void init(FilterConfig filterConfig) throws ServletException { letterPattern = Pattern.compile("[a-z]*"); String scriptPara = filterConfig.getInitParameter("scriptRegx").toLowerCase(); String sqlPara = filterConfig.getInitParameter("sqlRegx").toLowerCase(); notProtect = filterConfig.getInitParameter("notProtect"); scriptPattern = Pattern.compile(resultStr(scriptPara)); sqlPattern = Pattern.compile(resultStr(sqlPara)); totalLetter = scriptPara+"|"+sqlPara; int index = totalLetter.indexOf("'"); if(index > 0){ totalLetter = totalLetter.substring(0, index)+"\\'"+totalLetter.substring(index+1); } } public String resultStr(String oldStr) { String newStr = ""; if (oldStr != null && !oldStr.equals("")) { String[] temp = oldStr.split("\\\\|"); if (temp.length > 0) { for (int i = 0; i < temp.length; i++) { String tempStr = ""; boolean isLetter = false; for (int j = 0; j < temp[i].length(); j++) { String str = temp[i].substring(j, j + 1); if (letterPattern.matcher(str).matches()) { tempStr = tempStr+ "((\\\\%"+ parseAscii(str.toUpperCase()) + ")|" + "(\\\\%"+ parseAscii(str)+ ")|(" + str + "))"; isLetter = true; } else { if(str.equals("(" )|| str.equals(")")) str = "\\\\"+str; tempStr = tempStr + "((\\\\%"+ parseAscii(str) + ")|(" + str+ "))"; isLetter = false; } } if(isLetter) if (newStr.equals("")) { newStr = newStr + ".*(\\\\s+)(" + tempStr + ")(\\\\s+).*"; } else { newStr = newStr + "|.*(\\\\s+)(" + tempStr + ")(\\\\s+).*"; } else if (newStr.equals("")) { newStr = newStr + ".*(" + tempStr + ").*"; } else { newStr = newStr + "|.*(" + tempStr + ").*"; } } } } return newStr; } private String toHexUtil(int n){ String rt=""; switch(n){ case 10:rt+="A";break; case 11:rt+="B";break; case 12:rt+="C";break; case 13:rt+="D";break; case 14:rt+="E";break; case 15:rt+="F";break; default: rt+=n; } return rt; } public String toHex(int n){ StringBuffer sb=new StringBuffer(); if(n/16==0){ return toHexUtil(n); }else{ String t=toHex(n/16); int nn=n%16; sb.append(t).append(toHexUtil(nn)); } return sb.toString(); } public String parseAscii(String str){ StringBuffer sb=new StringBuffer(); byte[] bs=str.getBytes(); for(int i=0;i<bs.length;i++) sb.append(toHex(bs[i])); return sb.toString(); }}
- 辅助工具类 FilterUtils.java:
public class FilterUtils {public static String getFullUrlFromRequest(HttpServletRequest httRequest) {String url = httRequest.getRequestURI();String queryString = httRequest.getQueryString();if (null != queryString && !queryString.trim().equals("")) { url = url + "?" + queryString; }return url; }public static boolean isContainUrl(String urlList, String url) {boolean isExclude = false;if (null != urlList && !urlList.trim().equals("")) {String[] resources = urlList.split(",");if (null != resources) {for (int i = 0; i < resources.length; i++) {if (null != resources[i] && !resources[i].equals(""))if (url.indexOf(resources[i].trim()) >= 0) { isExclude = true;break; } } } }return isExclude; }}web.xml配置片段:
<filter> <filter-name>SecurityFilter</filter-name> <filter-class> cn.com.victorysoft.devcenter.component.usrmgr.security.filter.ParameterFilter </filter-class> <init-param> <param-name>scriptRegx</param-name> <param-value> <![CDATA[<|>|=|"|and]]> </param-value> </init-param> <init-param> <param-name>sqlRegx</param-name> <param-value><![CDATA['|;|or|--|(|)]]></param-value> </init-param> <init-param> <param-name>notProtect</param-name> <param-value>login.html,.upload</param-value> </init-param> </filter> <filter-mapping> <filter-name>SecurityFilter</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping>
(2)请求级数据验证(适配开发中系统)
核心逻辑:对每个登录请求(及其他用户输入请求),单独编写数据验证函数,验证输入数据的类型、长度,仅允许特定的特殊字符输入。
重要提示:
- 黑客可能将敏感字符转化为 URL 编码(如<对应%3C)绕过过滤,因此验证时需同时对 URL 编码的敏感字符进行过滤;
- 已上线系统优先采用过滤器方案(侵入性小、部署方便);开发中系统优先采用请求级验证方案(适配特殊业务需求,过滤更精准)。
连
载
预
告
安全攻击及防范手册
连载预告:登录环节收尾!下一篇聚焦前端交互安全
今天我们完成了“已解密的登录请求”和“SQL注入”两个漏洞的拆解,从数据传输到后端逻辑,为大家筑牢了登录环节的安全防线。这两个漏洞的高发,要么是忽视了传输加密,要么是开发时未规范处理用户输入,希望大家能对照项目及时自查整改。
下一篇(PART 4),我们将完成登录环节的安全漏洞拆解,聚焦前端交互层面的两个高频漏洞:跨站点脚本攻击(XSS)(注入恶意脚本窃取信息)和跨站点请求伪造(CSRF)(伪造用户请求执行恶意操作),并带来对应的防护指南。
你在项目中是否遇到过SQL注入相关的安全问题?或者对过滤器部署有疑问?欢迎在评论区留言讨论,我们将在后续文章中针对性解答!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:野猪与安全 耶度《SQL注入绕开登录?过滤器+数据验证双保险!——Web应用安全实战(PART 3 下)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论