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 package info.magnolia.cms.security;
35
36 import info.magnolia.audit.AuditLoggingUtil;
37 import info.magnolia.cms.filters.AbstractMgnlFilter;
38 import info.magnolia.context.Context;
39
40 import java.io.IOException;
41 import java.security.SecureRandom;
42 import java.util.Base64;
43 import java.util.Random;
44
45 import javax.inject.Inject;
46 import javax.inject.Provider;
47 import javax.servlet.FilterChain;
48 import javax.servlet.ServletException;
49 import javax.servlet.http.HttpServletRequest;
50 import javax.servlet.http.HttpServletResponse;
51
52 import org.apache.commons.lang3.StringUtils;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public class CsrfTokenSecurityFilter extends AbstractMgnlFilter {
83
84 private static final Logger log = LoggerFactory.getLogger(CsrfTokenSecurityFilter.class);
85
86 static final String CSRF_ATTRIBUTE_NAME = "csrf";
87 private static final String EVENT_TYPE = "Possible CSRF Attack";
88
89 private Random random = new SecureRandom();
90
91 private final Provider<Context> contextProvider;
92
93 @Inject
94 public CsrfTokenSecurityFilter(final Provider<Context> contextProvider) {
95 this.contextProvider = contextProvider;
96 }
97
98 @Override
99 public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
100 if (request.getMethod().equals("POST")) {
101 final String token = request.getParameter(CSRF_ATTRIBUTE_NAME);
102 if (StringUtils.isBlank(token)) {
103 csrfTokenMissing(request, response, request.getServletPath());
104 return;
105 }
106 if (!token.equals(request.getSession().getAttribute(CSRF_ATTRIBUTE_NAME))) {
107 csrfTokenMismatch(request, response, request.getServletPath());
108 return;
109 }
110 } else {
111 if (request.getSession().getAttribute(CSRF_ATTRIBUTE_NAME) == null) {
112 request.getSession().setAttribute(CSRF_ATTRIBUTE_NAME, generateSafeToken());
113 }
114 }
115 chain.doFilter(request, response);
116 }
117
118 private void csrfTokenMissing(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
119 final String auditDetails = String.format("CSRF token not set while user '%s' attempted to access url '%s'.", contextProvider.get().getUser().getName(), url);
120 handleError(request, response, auditDetails);
121 }
122
123 private void csrfTokenMismatch(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
124 final String auditDetails = String.format("CSRF token mismatched while user '%s' attempted to access url '%s'.", contextProvider.get().getUser().getName(), url);
125 handleError(request, response, auditDetails);
126 }
127
128
129
130
131
132 protected void handleError(HttpServletRequest request, HttpServletResponse response, String message) throws IOException {
133 auditLogging(request, response, message);
134 response.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF token mismatch possibly caused by expired session. Please re-open the page and submit the form again.");
135 }
136
137 private void auditLogging(HttpServletRequest request, HttpServletResponse response, String auditDetails) throws IOException {
138 log.warn("{}. {}", new Object[]{EVENT_TYPE, auditDetails});
139 AuditLoggingUtil.logSecurity(request.getRemoteAddr(), EVENT_TYPE, auditDetails);
140 }
141
142 private String generateSafeToken() {
143 byte bytes[] = new byte[20];
144 random.nextBytes(bytes);
145 Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
146 String token = encoder.encodeToString(bytes);
147 return token;
148 }
149
150 }