网络安全XSS跨站脚本攻击漏洞和不安全HTTP方法的修补

一、项目背景

一个Springboot+MyBatis+Redis+MySQL的辣鸡小项目。奈何再小的项目也需要保证安全,今天提交给测试部门做渗透测试,打回来两个网络安全漏洞,网上有很多“模糊”的修改办法,我们来看看具体怎么修补吧。

二、漏洞描述

1.不安全的HTTP方法

不安全的HTTP方法一般包括:TRACE、PUT、DELETE、COPY 等。其中最常见的为TRACE方法可以回显服务器收到的请求,主要用于测试或诊断,恶意攻击者可以利用该方法进行跨站跟踪攻击(即XST攻击),从而进行网站钓鱼、盗取管理员cookie等。其他说明方式如表所示:

名称 介绍
PUT 向指定的目录上传文件
DELETE 删除指定的资源
COPY 将特定的资源复制到特定位置
SEARCH 在一个目录路径中搜索资源
MOVE 将特定资源移动到特定位置
PROPFIND 获取与指定资源有关的信息
OPTION 预检请求,可用于检测服务器允许的http方法,和跨域有关

我被检测到的是存在OPTION请求,应该是仅允许GET,POST,HEAD就没事。

2.XSS跨站脚本攻击

当应用程序的新网页中包含不受信任的、未经恰当验证或转义的数据时,或者使用可以创建 HTML或JavaScript 的浏览器 API 更新现有的网页时,就会出现 XSS 缺陷。XSS 让攻击者能够在受害者的浏览器中执行脚本,并劫持用户会话、破坏网站或将用户重定向到恶意站点。
已知的跨站脚本攻击漏洞有三种:
1、存储型跨站脚本攻击:用户输入的文本信息保存到数据库中,并能够在页面展示的功能点,例如用户留言、发送站内消息、个人信息修改等功能点;
2、反射型跨站脚本攻击:URL参数需要在页面显示的功能点都可能存在反射型跨站脚本攻击,例如站内搜索、查询功能点;
3、基于DOM跨站脚本攻击:涉及DOM对象的页面程序,包括但不限于:document.URL、document.URLUnencoded、document.location、document.referrer、window.location。
检测到我的项目应该是不能过滤敏感字符,例如参数中包含script、img、src等等

三、解决方案

1.不安全的HTTP方法

在Springboot项目的启动类中,增加一个方法即可,如下:
addMethod是屏蔽的方法。

//拦截不安全的http请求
	@Bean
	public ConfigurableServletWebServerFactory configurableServletWebServerFactory() {
		TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
		factory.addContextCustomizers(context -> {
			SecurityConstraint securityConstraint = new SecurityConstraint();
			securityConstraint.setUserConstraint("CONFIDENTIAL");
			SecurityCollection collection = new SecurityCollection();
			collection.addPattern("/*");
			collection.addMethod("PUT");
			collection.addMethod("DELETE");
			collection.addMethod("TRACE");
			collection.addMethod("OPTIONS");
			collection.addMethod("PATCH");
			collection.addMethod("COPY");
			collection.addMethod("SEARCH");
			collection.addMethod("PROPFIND");
			securityConstraint.addCollection(collection);
			context.addConstraint(securityConstraint);
		});
		return factory;
	}

加完后如何测试?
下载一个postman插件并安装(安装包网上很多,解压后,双击.exe文件就能安装),在method选择你要测试的方法即可。
网络安全XSS跨站脚本攻击漏洞和不安全HTTP方法的修补_第1张图片

2.XSS跨站脚本攻击

先列网上的解决方案,我只是将敏感字符都过滤掉,并非最终办法。
1.对用户输入的数据进行严格过滤,包括但不限于以下字符及字符串
Javascript script src img { } ( ) < > = , . ; : " ‘ # ! / * \ `
2. 根据页面的输出背景环境,对输出进行编码;
3. 建议使用一个统一的规则库,对用户的所有输入进行安全性验证,验证不通过应直接拒绝用户的请求;
4. 对于富文本框,使用白名单控制输入,而不是黑名单;
5. 在Cookie 上设置HTTPOnly 标志,从而禁止客户端脚本访问Cookie。

建立一个过滤器,把敏感字符都过滤掉,这里先列一下可能存在的敏感字符:

String badStr = "|script|src|img|onerror|{|}|'|*|$|\"|<|>|javascript|(|)|=|,|:|!|`|.";

在启动类的上加上注解,文件位置:

@ServletComponentScan("com.xx.filter.safeFilter")

最后建立一个SafeFilter.java


package com.ouma.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Enumeration;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;


@Slf4j
@Component
@WebFilter(value = "/filter")
public class safeFilter implements Filter {

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub
    }
    @Override
    public void doFilter(ServletRequest args0, ServletResponse args1,
                         FilterChain chain) throws ServletException, IOException {

        HttpServletRequest req=(HttpServletRequest)args0;

        HttpServletResponse res=(HttpServletResponse)args1;

        //获得所有请求参数名
        Enumeration params = req.getParameterNames();

        //获取url的
        String sql = "";

        String url=req.getRequestURI().replaceAll("\\\\","/").toLowerCase();
        System.out.println(url);
        String method="";
        String action="";
        while (params.hasMoreElements()) {
            //得到参数名
            String name = params.nextElement().toString();
            System.out.println("name===========================" + name + "--");
            //得到参数对应值
            String[] value = req.getParameterValues(name);

            for (int i = 0; i < value.length; i++) {
                sql = sql + value[i];
            }
            if(name.equalsIgnoreCase("method")){
                method=value[0];
            }
            if(name.equalsIgnoreCase("action")){
                action=value[0];
            }
        }

        if( ("".equals(method)&&(!"".equals(action)||url.indexOf(".do")>=0))){
            //csrf跨站攻击
            String referer = req.getHeader("referer");
            System.err.println(referer);
            //if (referer == null || !referer.contains(req.getServerName())) {
            if (referer != null && !referer.contains(req.getServerName())) {
                System.out.println("【管理端refer校验未通过】referer = "+referer+" ,未包含"+req.getServerName());
                //如果 链接地址来自其他网站,则返回错误页面
                req.setAttribute("retInfo","疑似跨站伪造请求!");
                req.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(req, res);
            }
        }

        //有sql关键字,跳转到error.html
        if (sqlValidate(sql)) {
            System.out.println("sql error");
            req.setAttribute("retInfo","您发送请求中的参数中含有非法字符!");
            req.getRequestDispatcher("/WEB-INF/jsp/error.jsp").forward(req, res);
            //throw new IOException("您发送请求中的参数中含有非法字符!"+sql);
        } else {
            res.setHeader("Set-Cookie", "Secrue; HTTPOnly; ");
            chain.doFilter(args0,args1);
        }
    }
    //效验
    protected static boolean sqlValidate(String str) {
        str = str.toLowerCase();//统一转为小写
//	        String badStr = "'|select|update|and|or|delete|insert|truncate|char|into"
//	        		+ "|substr|declare|exec|master|drop|execute|"
//	        		+ "union|;|--|+|,|like|//|/|%|#|*|$|\"|http|cr|lf|<|>|(|)";//过滤掉的sql关键字,可以手动添加
//	        String badStr = "'|;|--|+|//|/|%|#|*|$|\"|cr|lf|<|>|(|)";//过滤掉的sql关键字,可以手动添加

        //String badStr = "script|src|img|onerror|{|}|'|%|*|$|\"|<|>";//过滤掉的sql关键字,可以手动添加
        String badStr = "";//过滤掉的sql关键字,可以手动添加

        badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|*|chr|mid|master|%25|truncate|" +
                "char|declare|sitename|net user|xp_cmdshell|;|or|-|+|,|like'|and|exec|execute|insert|create|drop|" +
                "table|from|grant|use|group_concat|column_name|" +
                "information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|*|" +
                "chr|mid|master|truncate|char|declare|or|;|-|--|+|,|like|//|/|#";
        badStr += "|script|src|img|onerror|{|}|'|*|$|\"|<|>|javascript|(|)|=|,|:|!|`|.";
        String[] badStrs = badStr.split("\\|");
        for (int i = 0; i < badStrs.length; i++) {
            if (str.indexOf(badStrs[i]) >= 0) {
                System.out.println("【管理端特殊字符校验未通过】当前解析串 :"+str+" ,其中未通过校验字符:"+badStrs[i]);
                return true;
            }
        }
        return false;
    }

}

就大功告成啦。
不过不能得意太早,这个过滤器可能把业务中的某些参数给过滤掉,加完这个过滤器一定要仔细测试系统功能是否被拦截到了。

20201224更新,高兴早了,测试部门又通过双重URL编码给我XSS攻击了。

例如输入:%25%33%63,意为:%0c,再次编码意为:<。这样又把尖括号注入进来了。所有的编码在最后贴一下。
在上面的过滤器类的方法中增加如下代码:

 str = convertPercent(str);
 str = URLDecoder.decode(str);

调用以下方法convertPercent:这个方法是过滤只输入%的情况

 //判断是否为16进制数
    public static boolean isHex(char c){
        if(((c >= '0') && (c <= '9')) ||
                ((c >= 'a') && (c <= 'f')) ||
                ((c >= 'A') && (c <= 'F')))
            return true;
        else
            return false;
    }
    public static String convertPercent(String str){
        StringBuilder sb = new StringBuilder(str);

        for(int i = 0; i < sb.length(); i++){
            char c = sb.charAt(i);
            //判断是否为转码符号%
            if(c == '%'){
                if(((i + 1) < sb.length() -1) && ((i + 2) < sb.length() - 1)){
                    char first = sb.charAt(i + 1);
                    char second = sb.charAt(i + 2);
                    //如只是普通的%则转为%25
                    if(!(isHex(first) && isHex(second)))
                        sb.insert(i+1, "25");
                }
                else{//如只是普通的%则转为%25
                    sb.insert(i+1, "25");
                }

            }
        }

        return sb.toString();
    }

关于所有的URL编码:https://blog.csdn.net/weixin_34409357/article/details/91565794
链接: 关于所有的URL编码

关于如何在前端防止XSS攻击:https://segmentfault.com/a/1190000016551188
链接: 关于如何在前端防止XSS攻击

你可能感兴趣的:(后端,网络安全,安全漏洞,java,安全漏洞,网络安全)