sql注入、js注入、csrf漏洞修复
以前没接触过网页安全方面的内容,正好这次碰巧客户有要求,学习了下,现将方案记录于此。
Sql注入
解决方案:
服务器访问层的控制:
1、在过滤器,针对后缀为jsp、html、htm的访问进行过滤拦截,判断请求参数中是否包含敏感字符
2、在拦截其中,针对后缀为(.do )的Controller访问进行拦截,判断请求参数中是否包含敏感字符
数据访问层的控制:
1、由于我们采用MyBaties,所以在写Sql时,尽量按#号传参,能有效防止SQL注入
js注入
解决方案:
前端页面控制
1、在输入框中添加js验证,具体可参考我随便里easyUI的一些正则表达式的写法。
服务器访问控制
1、在过滤器,针对后缀为jsp、html、htm的访问进行过滤拦截,判断请求参数中是否包含敏感字符
2、在拦截其中,针对后缀为(.do )的Controller访问进行拦截,判断请求参数中是否包含敏感字符
csrf漏洞
解决方案:
服务器端控制
1、对所有服务器请求进行拦截(通过过滤器、拦截器),判断请求中是否有crsftoken标记
a、如果没有则系统自动往seesion中添加csrftoken(就是个自动生成的guid)变量,允许继续访问
b、如果请求中有csrftoken标记,则判断是否和服务器session中的csrftoken值相同
b-1、不同则将请求重置到错误页面(如果是ajax请求,则访问错误信息)
b-2、相同则说明csrf验证通过,允许继续访问
前台页面控制
1、通过上面描述可知,只要是向服务器请求过一次页面,那么session中必定是包含csrftoken这个值,那么在服务端生成页面时要做的就是读取session中的csrftoken这个值,针对于所有服务器请求都添加上这个csrftoken参数。这些请求类型包括:表单提交、href超链接、ajax请求等等。
注意:切忌在向服务器请求后,在浏览器的地址栏中能够看见你刚才提交的csrftoken值。
代码实现
我们用的是SpringMVC,实现把三种漏洞的处理杂糅在一起了,下面直接上代码。但为了保密,所以代码可能掐头掐尾,希望能不影响大家阅读
1、配置文件web.xml中配置 过滤器,用于对于jsp、htm、html等请求进行拦截
<filter> <filter-name> validateFilter</filter-name> <filter-class> com.XXX.XX.XX.filter.ValidateFilter </filter-class> </filter> <filter-mapping> <filter-name>validateFilter</filter-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.htm</url-pattern> <url-pattern>*.html</url-pattern> </filter-mapping>
</filter>
2、spring-mvc.xml中配置 拦截器,用于对于.do的mvc请求进行拦截
<mvc:interceptors> <bean class="com.XXX.XX.XX.interceptor.ValidateInterceptor" /> </mvc:interceptors>
3、拦截器、过滤器的实现代码
@Controller public class ValidateFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //验证请求参数是否包含敏感字符 boolean isValidate = ValidateUtil.sensitiveKeywordJudge(servletRequest, servletResponse); if (isValidate) //验证是否存在CSRF漏洞 isValidate = ValidateUtil.csrfTokenJudge(servletRequest, servletResponse); if(isValidate) filterChain.doFilter(servletRequest, servletResponse); } @Override public void init(FilterConfig filterConfig) throws ServletException { } } @Controller @Scope("prototype") public class ValidateInterceptor implements HandlerInterceptor { /** * 对传入请求进行拦截 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //验证请求参数是否包含敏感字符 boolean allowPass=ValidateUtil.sensitiveKeywordJudge(request, response); if(allowPass) //验证是否存在CSRF漏洞 allowPass=ValidateUtil.csrfTokenJudge(request,response); return allowPass; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //资源释放 } } public class ValidateUtil { /** * SQL注入敏感字符、关键字验证 * 请求参数中不包含敏感字符,通过 * * @param request 请求 * @param response 响应 */ public final static boolean sensitiveKeywordJudge(ServletRequest request, ServletResponse response) throws ServletException, IOException { boolean notSensitiveKeyWord = true; //判定请求参数中是否包含敏感字符 Pattern pattern = Pattern.compile(Contstants.SQL_SENSITIVE_KEY_WORDS); for (Object value : request.getParameterMap().values()) { String reqData = ((String[]) value)[0]; Matcher match = pattern.matcher(reqData); if (match.find()) { notSensitiveKeyWord = false; break; } } //如果验证不通过,跳转到异常界面异常 if (!notSensitiveKeyWord) { WriteResponseUtil.JumpToErrorPage((HttpServletRequest) request, (HttpServletResponse) response, Contstants.ContainSensitiveWord); } return notSensitiveKeyWord; } /** * CSRF TOKEN校验 * @param request 请求 * @param response 响应 */ public final static boolean csrfTokenJudge(ServletRequest request, ServletResponse response) throws ServletException { String requestUri = ((HttpServletRequest) request).getRequestURI(); HttpServletRequest req = (HttpServletRequest) request; HttpSession s = req.getSession(); // 从 session 中得到 csrftoken 属性 String sToken = (String) s.getAttribute("csrftoken"); if (sToken == null) { // 产生新的 token 放入 session 中 sToken = UUID.randomUUID().toString(); s.setAttribute("csrftoken", sToken); return true; } //跳转登陆页,错误页面时不进行校验 if ( requestUri.equals("/") || requestUri.contains("loginController/mainPage.do") || requestUri.contains("loginController/login.do")||requestUri.contains("/login.jsp")||requestUri.contains("/error.jsp") ||requestUri.contains("/nopermission.jsp")) { return true; } // 从 HTTP 头中取得 csrftoken String xhrToken = req.getHeader("csrftoken"); // 从请求参数中取得 csrftoken String pToken = req.getParameter("csrftoken"); if (sToken != null && xhrToken != null && sToken.equals(xhrToken)) { return true; } else if (sToken != null && pToken != null && sToken.equals(pToken)) { return true; } WriteResponseUtil.JumpToErrorPage((HttpServletRequest) request, (HttpServletResponse) response, Contstants.ErrCSRFToken); return false; } }
4、敏感字符,采用正则表达式:
public static final String SQL_SENSITIVE_KEY_WORDS="['\"<>=%;|&]|-{2}|\\\\{2}";
5、前端页面引入session变量
<script type="text/javascript"> var tokenSession="<%=request.getSession().getAttribute("csrftoken")%>" var token=tokenSession!=null?tokenSession.toString():""; </script>
6、针对form元素进行后缀添加csrftoken
function updateForms( ) { var forms = document.getElementsByTagName('form'); for(i=0; i<forms.length; i++) { var tokenInput=$("form[name='"+forms[i].name+"']").children("[name='csrftoken']"); if(tokenInput.length==0) { tokenInput =document.createElement("input"); tokenInput.name = "csrftoken"; tokenInput.value = token; tokenInput.type="hidden"; forms[i].appendChild(tokenInput); }else{ tokenInput.val(token); } } // 得到页面中所有的 form 元素 // var forms = document.getElementsByTagName('form'); // for(i=0; i<forms.length; i++) { // var form=forms[i]; // var input=form.getElementsByName("csrftoken"); // if(input==null){ // // 动态生成 input 元素,加入到 form 之后 // input =document.createElement("input"); // input.name = "csrftoken"; // input.value = token; // input.type="hidden"; // forms[i].appendChild(input); // } // else // { // input.setValue(token); // } // } } //为Html<a>标签的 href添加Token后缀 //function updateTags() { // var all = document.getElementsByTagName('a'); // var len = all.length; // // // 遍历所有 a 元素 // for(var i=0; i<len; i++) { // var e = all[i]; // updateTag(e, 'href', token); // } //} // //function updateTag(element, attr, token) { // var location = element.getAttribute(attr); // if(location != null && location != "" ) { // var fragmentIndex = location.indexOf('#'); // var fragment = null; // if(fragmentIndex != -1){ // // //url 中含有只相当页的锚标记 // fragment = location.substring(fragmentIndex); // location = location.substring(0,fragmentIndex); // } // var index = location.indexOf('?'); // if(index != -1) { // //url 中已含有其他参数 // location = location + '&csrftoken=' + token; // } else { // //url 中没有其他参数 // location = location + '?csrftoken=' + token; // } // if(fragment != null){ // location += fragment; // } // element.setAttribute(attr, location); // } //}
配合的每个界面的初始化方法中要调用一下上面的js
$(function () { updateForms(); });
注:其中注释的代码你也可以看看,根据自己需要调整。
7、在前端ajax中请求参数后缀添加csrftoken,由于一些写法和差异,这边直接黏贴一个js以供参考了,具体根据自己的项目可以区别处理(只需要关注提交请求的相关代码段即可)
$(function () { updateForms(); loadData(null); }) function loadData(url) { $('#TDataDictTable').datagrid({ url: url, fit: true, pagination: true, fitColumns: true, rownumbers: true, sortName: 'itemName', sortOrder: 'desc', idField: 'id', loadMsg: "数据加载中,请稍候……", columns: [ [ {field : 'id',title : '编号',checkbox:true }, { field: 'systemId', title: '编号', hidden: true, sortable: true, align: 'left', width: 100 }, { field: 'itemName', title: '字典项名', sortable: true, align: 'left', width: 100 }, { field: 'colNameCn', title: '列中文名', sortable: true, align: 'left', width: 100 }, { field: 'colName', title: '列英文名', sortable: true, align: 'left', width: 100 }, { field: 'itemVal', title: '列值项', sortable: true, align: 'left', width: 100 }, { field: 'itemDesc', title: '描述', sortable: true, align: 'left', width: 200 }, { field: 'ext1', title: '扩展字段1', hidden: true, sortable: true, align: 'left', width: 200 }, { field: 'ext2', title: '扩展字段2', hidden: true, sortable: true, align: 'left', width: 200 }, { field: 'createDatetime', title: '记录生成日期', sortable: true, align: 'left', width: 100 }, { field: 'updateDatetime', title: '记录更新日期', sortable: true, align: 'left', width: 100 }, { field: 'operid', title: '操作员', sortable: true, align: 'left', width: 100 } ] ], onLoadError: function (r) { if (r.statusText == 'Forbidden') { if (r.responseText != null && r.responseText != "") { Showbo.Msg.alert(r.responseText); } else { Showbo.Msg.alert('抱歉,您的身份验证已过期!'); window.top.location.href = projectPath + "loginController/login.do"; } } } }); } function showDialog(rowDate, title) { var permissUrl = "?permissionCode=YWDATADICTADDCODE" if (rowDate != null) { permissUrl = "?permissionCode=YWDATADICTUPDATECODE" } var d = $('<div/>').dialog({ href: projectPath + "view/vayw/systemmanager/datadict/addDataDict.jsp?csrftoken=" + token, modal: true, closable: false, title: title, width: 370, height: 450, buttons: [ { text: '确定', handler: function () { if ($('#DataDictAddForm').form('validate')) { $.ajax({ type: 'post', dataType: 'json', url: projectPath + 'TYwDataDictController/save.do' + permissUrl, data: $('#DataDictAddForm').serialize(), success: function (r) { if (r.success) { Showbo.Msg.alert(r.msg); d.dialog('close'); if (rowDate == null) searchDataDict(); else holdSearchDataDict(); } else { Showbo.Msg.alert(r.msg); } }, error: function (r) { if (r.statusText == 'Forbidden') { if (r.responseText != null && r.responseText != "") { Showbo.Msg.alert(r.responseText); } else { Showbo.Msg.alert('抱歉,您的身份验证已过期!'); window.top.location.href = projectPath + "loginController/login.do"; } } } }); } } }, { text: '取消', handler: function () { d.dialog('close'); } } ], onLoad: function () { if (rowDate != null) { $('#pid').val(rowDate.id); $('#psystemId').val(rowDate.systemId); $('#pitemName').val(rowDate.itemName); $('#pcolNameCn').val(rowDate.colNameCn); $('#pcolName').val(rowDate.colName); $('#pitemVal').val(rowDate.itemVal); $('#pitemDesc').val(rowDate.itemDesc); $('#pext1').val(rowDate.ext1); $('#pext2').val(rowDate.ext2); } }, onLoadError: function (r) { if (r.statusText == 'Forbidden') { if (r.responseText != null && r.responseText != "") { d.dialog('close'); Showbo.Msg.alert(r.responseText); } else { Showbo.Msg.alert('抱歉,您的身份验证已过期!'); window.top.location.href = projectPath + "loginController/login.do"; } } }, onClose: function () { $(this).dialog('destroy'); } }); } //添加 function addDataDict() { showDialog(null, "添加数据字典"); } //修改 function updateDataDict() { var rows = $('#TDataDictTable').datagrid('getChecked'); if (rows.length == 0) { Showbo.Msg.alert('请先选中要修改的记录!'); return; } else if (rows.length > 1) { Showbo.Msg.alert('一次只能对一条数据字典信息进行修改!'); return; } $.ajax({ type: 'post', dataType: 'json', url: projectPath + 'TYwDataDictController/findById.do?permissionCode=YWDATADICTUPDATECODE', data: {'id': rows[0].id, 'csrftoken': token}, success: function (r) { if (r != null) { showDialog(rows[0], "修改数据字典"); } else { Showbo.Msg.alert('数据字典信息不存在,无法完成修改操作!'); } }, error: function (r) { if (r.statusText == 'Forbidden') { if (r.responseText != null && r.responseText != "") { Showbo.Msg.alert(r.responseText); } else { Showbo.Msg.alert('抱歉,您的身份验证已过期!'); window.top.location.href = projectPath + "loginController/login.do"; } } } }); } //删除 function deleteDataDict() { var delrow = $('#TDataDictTable').datagrid('getChecked'); if (delrow.length == 0) { Showbo.Msg.alert('请先选中要删除的记录!'); return; } var ids = ''; for (var i = 0; i < delrow.length; i++) { if (i != delrow.length) ids += delrow[i].id + ","; else ids += delrow[i].id; } Showbo.Msg.confirm('请确认是否要删除您选择的记录?', function (b) { if (b == 'yes') { $.ajax({ type: 'post', dataType: 'json', url: projectPath + 'TYwDataDictController/delete.do?permissionCode=YWDATADICTDELCODE', data: {'ids': ids, 'csrftoken': token}, success: function (r) { if (r.success) { Showbo.Msg.alert(r.msg); $('#TDataDictTable').datagrid('clearSelections'); holdSearchDataDict(); } else Showbo.Msg.alert(r.msg); }, error: function (r) { if (r.statusText == 'Forbidden') { if (r.responseText != null && r.responseText != "") { Showbo.Msg.alert(r.responseText); } else { Showbo.Msg.alert('抱歉,您的身份验证已过期!'); window.top.location.href = projectPath + "loginController/login.do"; } } } }); } }); } //保持当前页刷新 function holdSearchDataDict() { var dataDictTable = $('#TDataDictTable'); if ($('#TDataDictForm').form('validate')) { dataDictTable.datagrid({ url: projectPath + 'TYwDataDictController/findAll.do?permissionCode=YWDATADICTSEARCHCODE', queryParams: $('#TDataDictForm').serializeObject(), onLoadError: function (r) { if (r.statusText == 'Forbidden') { if (r.responseText != null && r.responseText != "") { Showbo.Msg.alert(r.responseText); } else { Showbo.Msg.alert('抱歉,您的身份验证已过期!'); window.top.location.href = projectPath + "loginController/login.do"; } } }}); dataDictTable.datagrid('clearChecked'); } else { Showbo.Msg.alert('查询条件不能包含敏感字符!'); } } //查询按钮 function searchDataDict() { var dataDictTable = $('#TDataDictTable'); if ($('#TDataDictForm').form('validate')) { dataDictTable.datagrid({pageNumber: 1, url: projectPath + 'TYwDataDictController/findAll.do?permissionCode=YWDATADICTSEARCHCODE', queryParams: $('#TDataDictForm').serializeObject(), onLoadError: function (r) { if (r.statusText == 'Forbidden') { if (r.responseText != null && r.responseText != "") { Showbo.Msg.alert(r.responseText); } else { Showbo.Msg.alert('抱歉,您的身份验证已过期!'); window.top.location.href = projectPath + "loginController/login.do"; } } }}); dataDictTable.datagrid('clearChecked'); } else { Showbo.Msg.alert('查询条件不能包含敏感字符!'); } }