中级漏洞处理

问题概述
项目后台使用的技术是Spring+Shiro+MVC,AppScan一共扫描出7种低危漏洞,以下是对这些漏洞的处理说明。

1、“Content-Security-Policy”头缺失
在网上查了关于这个响应头的说明,CSP相当于前台的白名单,用来限制网站内部一些资源获取的来源,如限制CSS、JS、图片或者第三方链接等。

CSP的设置可以在一定程度上限制XSS攻击,有2种方式可以设置。第一种通过设置HTTP响应头,另一种通过HTML的<meta>标签。

具体的设置和说明请参考: http://www.ruanyifeng.com/blog/2016/09/csp.html

我是在自定义的拦截器中加了CSP设置,后面2个关于响应头的设置也是在这里加的。

public class YourInterceptor implements HandlerInterceptor {
     
     @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //your code
        //……
        //设置CSP
        response.addHeader("Content-Security-Policy","default-src 'self' 'unsafe-inline' 'unsafe-eval';");
        response.addHeader("X-Content-Type-Options","nosniff");
        response.addHeader("X-XSS-Protection","1");
        //your code
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //your code
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //your code
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2、“X-Content-Type-Options”头缺失或不安全
响应头缺失,按照扫描要求设置,具体设置请参照第一点。如果不知道具体的属性值,可以参考官方的API或者去网上找一找别人的说明。
这是别人写的,大家有需要的话可以参考一下:
https://www.cnblogs.com/vekair/p/11233649.html

3、“X-XSS-Protection”头缺失或不安全
响应头缺失,按照扫描要求设置,具体设置请参照前2点。

4、查询中接受的主体参数
这个就是禁用GET请求,但是由于系统内部需要用到,所以只需要对携带数据传递的请求设置为POST方式。


5、启用了不安全的“OPTIONS”HTTP 方法
这个在网上有很多方法可以参考,如果服务器有Nginx,可以直接在Nginx中配置,如果没有可以配置在Tomcat上。我这里是配置在Tomcat上的。
Tomcat的web.xml文件进行修改(修改前请先记得备份哦)

 <!-- 限制HTTP请求方法配置,开始 -->
 <!-- 先注释掉原来的引入文件 -->
<!-- <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
  version="3.1"> -->
  
   <!-- 添加新的引入文件 -->
  <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
           xmlns="http://java.sun.com/xml/ns/j2ee" 
           xmlns:web="http://java.sun.com/xml/ns/j2ee" 
           xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
                               http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
           version="2.5">

<!-- 过滤掉DELETE、HEAD、PUT等请求方式 -->
  <security-constraint>
      <web-resource-collection>
              <url-pattern>/*</url-pattern>
              <http-method>PUT</http-method>
              <http-method>DELETE</http-method>
              <http-method>HEAD</http-method>
              <http-method>OPTIONS</http-method>
              <http-method>TRACE</http-method>
              <http-method>PATCH</http-method>
    </web-resource-collection>  
    <auth-constraint></auth-constraint>  
</security-constraint>  
 
  <login-config>  
          <auth-method>BASIC</auth-method>  
  </login-config>
 <!--限制HTTP请求方法配置,结束 -->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
6、发现数据库错误模式
这个就是说不允许用户在前台看到有关数据库的报错,就是不能在前台看到下图这样类型的报错。

所以这里就对数据库后台的查询进行拦截,做一个统一的异常处理,使前台看不到这种报错,类似下图这种,具体的样式可以自己设计。

后端需要自定义一个异常处理类,继承HandlerExceptionResolver,重写resolveException()方法,在方法里面对异常信息进行处理即可。我这里直接简单写了几句话,返回给前台页面,加上了时间是便于排查问题。要记得把这个类注入进去。

@Component
public class WebExceptionResolver implements HandlerExceptionResolver {
    private static transient Logger logger = LoggerFactory.getLogger(WebExceptionResolver.class);

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        logger.error("WebExceptionResolver:{}", ex);

        // if json
        boolean isJson = false;
        HandlerMethod method = (HandlerMethod) handler;
        ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class);
        if (responseBody != null) {
            isJson = true;
        }

        // error result
        Map<String, String> map = new HashMap<>();
        map.put("errorCode", "500");
        map.put("errorMsg", "出错了,请联系管理员");
        map.put("errorDate", DateUtil.getToday19());
        String errorResult = JSONObject.toJSONString(map);
        // response
        ModelAndView mv = new ModelAndView();
        if (isJson) {
            try {
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().print(errorResult);
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }
            return mv;
        } else {
            mv.addObject("exceptionMsg",map.get("errorMsg"));
            mv.addObject("date", DateUtil.getToday19());
            mv.setViewName("sys/errorPage");
            return mv;
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
7、具有不安全、不正确或缺少 SameSite 属性的 Cookie
这个就是要求对Cookie设置SameSite属性,防止跨域和XSS攻击的。因为我这里使用了Shiro框架作为Cookie管理器,要从Shiro的角度处理。
从Shiro的配置文件可以看到,默认使用的是DefaultSessionManager。

<!--Session集群配置 -->
    <bean id="sessionManager"
        class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="${globalSessionTimeout}" />
        <property name="sessionDAO" ref="shiroSessionDAO" />
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler" />
        <property name="sessionValidationSchedulerEnabled" value="true" />
        <property name="sessionIdCookie" ref="wapsession" />
    </bean>
1
2
3
4
5
6
7
8
9
因为Shiro版本问题(1.2.3),去查官网文档发现这个版本的cookie是没有SameSite属性的。1.7.1及以上版本才添加了这个属性,所以现在要么就把Shiro升级版本,要么就重写这个类。由于考虑到Shiro与Spring的兼容问题,所以我这里直接重写DefaultWebSessionManager。

看源码我们可以看到,在DefaultWebSessionManager这个类有2个属性,其中有一个就是Cookie,它的构造方法可以去改变cookie的值,从而给cookie加上SameSite属性。

但是仔细观察构造方法,可以看到这个Cookie是一个SimpleCookie,也就是说我们想要给cookie加上一个新的属性,还需要自定义SimpleCookie。

除此之外,还有一点很重要,除了构造方法以外,还要关注cookie是什么时候生成的。这个onStart()方法很重要,是存储session的核心方法。

观察完毕之后,可以动手写我们自己的WebSessionManager和SimpleCookie了。
(1)自定义WebSessionManager:MyDefaultWebSessionManager

public class MyDefaultWebSessionManager extends DefaultWebSessionManager {
    private static final Logger log = LoggerFactory.getLogger(MyDefaultWebSessionManager.class);
    @Override
    protected void onStart(Session session, SessionContext context) {
        super.onStart(session, context);
        if (!WebUtils.isHttp(context)) {
            log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response pair. No session ID cookie will be set.");
        } else {
            HttpServletRequest request = WebUtils.getHttpRequest(context);
            HttpServletResponse response = WebUtils.getHttpResponse(context);
            if (this.isSessionIdCookieEnabled()) {
                Serializable sessionId = session.getId();
                //重写父类方法,使用我们自己定义的cookie
                this.storeSessionId(sessionId, request, response);
            } else {
                log.debug("Session ID cookie is disabled.  No cookie has been set for new session with id {}", session.getId());
            }

            request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);
        }
    }

    private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {
        if (currentId == null) {
            String msg = "sessionId cannot be null when persisting for subsequent requests.";
            throw new IllegalArgumentException(msg);
        } else {
            Cookie cookie = this.getSessionIdCookie();
            //Cookie cookie = new MySimpleCookie(template);
            String idString = currentId.toString();
            cookie.setValue(idString);
            //这一行决定自定义的cookie中的属性
            cookie.saveTo(request, response);
            log.trace("Set session ID cookie for session with id {}", idString);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
(2)自定义SimpleCookie:MySimpleCookie

public class MySimpleCookie extends SimpleCookie {
    private static final transient Logger log = LoggerFactory.getLogger(MySimpleCookie.class);
    //自定义的属性sameSite
    private String sameSite;

    //重写该方法,添加SameSite属性
    @Override
    public void saveTo(HttpServletRequest request, HttpServletResponse response) {
        String name = this.getName();
        String value = this.getValue();
        String comment = this.getComment();
        String domain = this.getDomain();
        String path = this.calculatePath(request);
        int maxAge = this.getMaxAge();
        int version = this.getVersion();
        boolean secure = this.isSecure();
        boolean httpOnly = this.isHttpOnly();
        String s = this.addCookieHeader(name, value, comment, domain, path, maxAge,
                version, secure, httpOnly);
        //在原来的基础上添加SameSite属性
        String headerValue = appendtSameSite(s, sameSite);
        response.addHeader("Set-Cookie", headerValue);
    }

    private String addCookieHeader(String name,
                                   String value, String comment, String domain,
                                   String path, int maxAge, int version, boolean secure,
                                   boolean httpOnly) {
        String headerValue = this.buildHeaderValue(name, value, comment, domain, path, maxAge, version, secure, httpOnly);

        if (log.isDebugEnabled()) {
            log.debug("Added HttpServletResponse Cookie [{}]", headerValue);
        }
        return headerValue;
    }

    private String calculatePath(HttpServletRequest request) {
        String path = StringUtils.clean(this.getPath());
        if (!StringUtils.hasText(path)) {
            path = StringUtils.clean(request.getContextPath());
        }

        if (path == null) {
            path = "/";
        }

        log.trace("calculated path: {}", path);
        return path;
    }

    //这里只拼接一个字符串,没用StringBuilder,不考虑效率问题
    private String appendtSameSite(String s, String sameSite) {
        if (org.apache.commons.lang3.StringUtils.isNotBlank(sameSite)) {
            s += ("; ");
            s += ("SameSite=") + sameSite;
        }
        return s;
    }

    public String getSameSite() {
        return sameSite;
    }

    public void setSameSite(String sameSite) {
        this.sameSite = sameSite;
    }

    public MySimpleCookie(String name, String sameSite) {
        super(name);
        this.sameSite = sameSite;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
(3)调整自己的配置文件,使WebSessionManager指向自定义的类。

    <!--Session集群配置 -->
    <bean id="sessionManager"
        class="com.mytest.common.sys.service.realm.MyDefaultWebSessionManager">
        <property name="globalSessionTimeout" value="${globalSessionTimeout}" />
        <property name="sessionDAO" ref="shiroSessionDAO" />
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler" />
        <property name="sessionValidationSchedulerEnabled" value="true" />
        <property name="sessionIdCookie" ref="wapsession" />
    </bean>

    <!-- 指定本系统SESSIONID, 默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID-->
    <bean id="wapsession" class="com.mytest.common.sys.service.realm.MySimpleCookie">
        <property name="secure" value="${cookieIsSecure}"/>
        <constructor-arg name="name" value="${cookieName}"/>
        <constructor-arg name="sameSite" value="${sameSite}"/>
    </bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(4)最后呈现的效果


最后
遇到这种低危漏洞,一般来说就是去网上找一找处理方法,当找不到有效方法的时候,根据扫描结果和修复建议,可以尝试通过看源码解决。
————————————————
版权声明:本文为CSDN博主「yingz_QAQ」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Aliux_JLQ/article/details/123061774

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值