There is a Java, Spring, Tomcat webapp. One part is responsible for sending activation emails to the newly created users with generated activationUrls (consisting of unique tokens). The email is created from a Freemarker template, where the activationUrl gets substituted before email is sent to the user. Once the user opens activation URL in a browser it should load (redirect to) the user activation page where they need to provide a password, etc.
Issue is, that the browser hangs with "Processing..." and nothing is happening in the frontend. In the backend we can see this stacktrace:
13-Dec-2023 15:48:17.595 SEVERE [http-nio-8080-exec-9] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [default] in context with path [/ui] threw exception
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
at org.springframework.security.web.firewall.StrictHttpFirewall.rejectedBlacklistedUrls(StrictHttpFirewall.java:288)
at org.springframework.security.web.firewall.StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:267)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:193)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:176)
at org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:145)
at org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:92)
at org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:394)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153)
at org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.doFilterInternal(OpenEntityManagerInViewFilter.java:178)
The activationUrl which is genereted and received via email looks like:
http://localhost:8080/ui/activate-account?tokenId=3&token=1462
(it's already simplified, but the key bit is, it has two url parameters separated by ('&') ampersand char, which is getting "translated" into '&'
which contains that potentially malicious String ";".
We use tuckey.org UrlRewrite 4.0.3
We do have specified a rule for that:
<rule>
<name>RULE: redirect security urls to index</name>
<from>/(forgot-password|forgot-username|activate-account|reset-password|signout|locked-account|sso-error|ird-access-token)([\?\&\=a-z0-9-]*)</from>
<to last="true">/security/index.zul?function=$1</to>
</rule>
Please note, the way how the ampersand (url parameter separator) is represented in the rule.
My suspicious is that processing the rule is causing that ?param1=xxxx¶m2=yyyy
is converted into ?param1=xxxx&param2=yyyy
which is causing that exception to be thrown.
From the urlrewrite manual, I am not able to figure out any sort of attributes which I could apply on the to
element. Something like escapeBackReference
(whether the back-references in the rewritten URL should be escaped or not) or encodeUrl
attribute which would specify whether the rewritten URL should be encoded or not.
Any idea how to resolve it?
FYI: When reload page is triggered in a browser, then the page is reloaded and URL is processed and a correct activation page is displayed. This can be reliably reproduce by pasting the URL into a private/incognito window of any browser (Edge, Chrome, FF). Repeated attempts from a normal browser session or even from the same incognito mode won't lead to this issue and will work as expected. However, each new user will have a fresh "new" browser before not connected to our servers, so each of them is going to experience it.
Debugging through Java code of external libraries, I noticed that in this method:
org.springframework.security.web.firewall.StrictHttpFirewall.rejectedBlacklistedUrls(HttpServletRequest request)
where encodedUrlContains(...)
is called, they are using two queries on the HttpServletRequest:
valueContains(request.getContextPath(), value)
valueContains(request.getRequestURI(), value)
where value is e.g. ";"
, but
request.getContextPath()
is /app-ui
request.getRequestURI()
is /app-ui/zkau/web/db026fbc/js/zk.wpd;jsessionid=C748DE1595B19CCFE6370FB096DF7942
and
request.getRequestURL()
is http://localhost:8080/app-ui/zkau/web/db026fbc/js/zk.wpd;jsessionid=C748DE1595B19CCFE6370FB096DF7942
where the ";"
semicolon is present.
Actually, whilst I thought a browser supposed to process a single URL, StrictHttpFirewall.getFirewalledRequest(HttpServletRequest request)
processes multiple times different requests with various values obtained from request.getRequestURI()
, eg:
But the situation of attaching
";"
withjsessionid=...
parameter is happening only in a browser private mode (or when the browser doesn’t have stored any session info in the cookies).
So, the issue doesn’t lie in the activation-url, but somewhere else where we handle the session info.
Looking into a browser developers tools confirms that a session info is attached to the URL (if it is a 1st time visit)
Please note the Status, Indicator and Name (hoover a mouse over it to see a full URL with the ";"
in there separating a URL of some kind of an App object from jsessionid attribute).
If a reload is hit (or pretty much the activate account URL is opened in a browser which “remembers” it was connecting to the App-UI app before), then no jsessionid
attribute is added after the URLs, see below:
All statuses are 200, no ";"
and jsessionid
in the URLs.
So, it turned out to be the cause with the jsessionid
. It seems that it will be sent on the url for the first request as it does not know if the browser supports cookies yet. To prevent this we had to add the following to the bottom of the webapp/WEB-INF/web.xml
in the app-ui
<session-config>
<tracking-mode>COOKIE</tracking-mode>
<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
</session-config>
This definitely had resolved the issue for new users who hadn't visited our app before.