问题概述
项目后台使用的技术是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