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 import javax.servlet.http.HttpSession;
52
53 import org.apache.commons.lang3.StringUtils;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
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
83 public class CsrfTokenSecurityFilter extends AbstractMgnlFilter {
84
85 private static final Logger log = LoggerFactory.getLogger(CsrfTokenSecurityFilter.class);
86
87 static final String CSRF_ATTRIBUTE_NAME = "csrf";
88 private static final String EVENT_TYPE = "Possible CSRF Attack";
89
90 private Random random = new SecureRandom();
91
92 private final Provider<Context> contextProvider;
93
94 @Inject
95 public CsrfTokenSecurityFilter(final Provider<Context> contextProvider) {
96 this.contextProvider = contextProvider;
97 }
98
99 @Override
100 public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
101 HttpSession session = request.getSession(false);
102 if (request.getMethod().equals("POST")
103 && session != null && !session.isNew()) {
104 String token = request.getParameter(CSRF_ATTRIBUTE_NAME);
105 if (StringUtils.isBlank(token)) {
106 csrfTokenMissing(request, response, request.getServletPath());
107 return;
108 }
109 if (!token.equals(session.getAttribute(CSRF_ATTRIBUTE_NAME))) {
110 csrfTokenMismatch(request, response, request.getServletPath());
111 return;
112 }
113 } else if (session != null && session.getAttribute(CSRF_ATTRIBUTE_NAME) == null) {
114 session.setAttribute(CSRF_ATTRIBUTE_NAME, generateSafeToken());
115 }
116 chain.doFilter(request, response);
117 }
118
119 private void csrfTokenMissing(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
120 final String auditDetails = String.format("CSRF token not set while user '%s' attempted to access url '%s'.", contextProvider.get().getUser().getName(), url);
121 handleError(request, response, auditDetails);
122 }
123
124 private void csrfTokenMismatch(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
125 final String auditDetails = String.format("CSRF token mismatched while user '%s' attempted to access url '%s'.", contextProvider.get().getUser().getName(), url);
126 handleError(request, response, auditDetails);
127 }
128
129
130
131
132
133 protected void handleError(HttpServletRequest request, HttpServletResponse response, String message) throws IOException {
134 auditLogging(request, response, message);
135 response.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF token mismatch possibly caused by expired session. Please re-open the page and submit the form again.");
136 }
137
138 private void auditLogging(HttpServletRequest request, HttpServletResponse response, String auditDetails) throws IOException {
139 log.warn("{}. {}", new Object[]{EVENT_TYPE, auditDetails});
140 AuditLoggingUtil.logSecurity(request.getRemoteAddr(), EVENT_TYPE, auditDetails);
141 }
142
143 private String generateSafeToken() {
144 byte bytes[] = new byte[20];
145 random.nextBytes(bytes);
146 Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
147 String token = encoder.encodeToString(bytes);
148 return token;
149 }
150
151 }