Search code examples
servletsjbossjboss7.xundertowjboss-eap-7

Undertow (JBoss 7) mangles / re-encodes URL-encoded parameter on redirect


Posted in JBoss forums as well: https://developer.jboss.org/thread/280195

update 2019-06-26 apparently this is now confirmed as a bug in Undertow, with a pull request submitted here.

This is a SSCCE.

I have a very simple Servlet that does nothing except print the value of a parameter:

public class TestServlet extends HttpServlet{
    public void service(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {
        final String URL = req.getParameter("url");
        System.out.printf("url parameter read as: [%s]\n", URL);
    }
}

My application's web.xml is configured so as to automatically redirect http access to https:

<web-app>
 ...
<security-constraint>
    <web-resource-collection>
        <web-resource-name>SECURE</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>
</web-app>

… and I also have (in my standalone-full.xml configuration file) the redirect-socket attribute set in the definition of the http-listener:

<http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true"/>

If I deploy to JBoss EAP 7.1 and enter the following URL in my browser (where the url parameter carries the URL-encoded value of "http://www.google.com"):

http://localhost:8082/get-parameter-test/min?url=http%3A%2F%2Fwww.google.com

… this is what I see in the developer console:

enter image description here

As a result, after the automatic redirect, my code fails to obtain the correct value of the url parameter and I see in the log files:

url parameter read as: [http%3A%2F%2Fwww.google.com]

However, if I deploy to JBoss EAP 6.2 and do the same, the URL is not mangled in the redirect and everything works fine:

enter image description here

update

this answer suggests that the decode-url parameter in the configuration of the http-listener and https-listener in the undertow system in the JBoss configuration file (standalong-full.xml) may have something to do with this. This is wrong. I tried all four combinations:

  • http-listener: decode-url="false" and https-listener: decode-url="false"
  • http-listener: decode-url="false" and https-listener: decode-url="true"
  • http-listener: decode-url="true" and https-listener: decode-url="false"
  • http-listener: decode-url="true" and https-listener: decode-url="true"

In all cases, the 302 response that's effecting the redirect from http to https has the following header:

Location: https://localhost:8445/get-parameter-test?url=http%253A%252F%252Fwww.google.com

That is in all cases, the URL is mangled (call it re-encoded it if you like, it's mangled AFAIAC). There's no reason for this behavior at all and it is not what EAP 6.2 does. The value of the decode-url parameter only affects the behavior of the HttpServletRequest#getRequest method inside your servlet, it has no effect whatsoever in the redirected URL.


Solution

  • update 2019-06-26 Apparently this is now confirmed as a bug in Undertow, with a pull request submitted here

    Here's what ultimately worked for me. First of all, I removed from my web.xml the entire <security-constraint> element as it is not needed for the solution I implemented. I also removed the redirect-socket="https" from the <http-listener> configuration. That too, is also not needed. So here's what my <http-listener> and <https-listener> look like:

    <http-listener name="default" socket-binding="http" enable-http2="true"/>
    <https-listener name="https" socket-binding="https" security-realm="ApplicationRealm" enable-http2="true"/>
    

    I think the above is exactly what you get in JBoss EAP 7.1 out of the box, so no need to change that.

    I then created a filter and added it in the <filters> element of the undertow subsystem:

    <rewrite name="http-to-https" redirect="true" target="https://%h:8445%U%q"/>
    
    • %h is the remote host name
    • %U is the requested URL path
    • %q is the query string (automatically prepended with ? if it exists)

    I found the above codes here - I am sure there's a more normative reference somewhere else but they seem to work.

    Finally, I added a reference to the filter, along with a predicate, in the <server>/<host> element (also in the undertow subsystem):

    <server name="default-server">
        <http-listener name="default" socket-binding="http" enable-http2="true"/>
        <https-listener name="https" socket-binding="https" security-realm="ApplicationRealm" enable-http2="true"/>
        <host name="default-host" alias="localhost">
            <location name="/" handler="welcome-content"/>
            <filter-ref name="server-header"/>
            <filter-ref name="x-powered-by-header"/>
            <filter-ref name="http-to-https" predicate="equals(%p, 8082)"/>
            <http-invoker security-realm="ApplicationRealm"/>
        </host>
    </server>
    

    With the above configuration the request gets redirected without re-encoding the URL:

    $ curl -I -L -k http://localhost:8082/get-parameter-test?url=http%3A%2F%2Fwww.google.com
    HTTP/1.1 302 Found
    Connection: keep-alive
    Server: JBoss-EAP/7
    Location: https://127.0.0.1:8445/get-parameter-test?url=http%3A%2F%2Fwww.google.com
    Content-Length: 0
    Date: Tue, 11 Jun 2019 17:43:23 GMT
    
    HTTP/1.1 200 OK
    Connection: keep-alive
    X-Powered-By: Undertow/1
    Server: JBoss-EAP/7
    Content-Length: 0
    Date: Tue, 11 Jun 2019 17:43:23 GMT
    

    … and the parameter is correctly read from Java:

    url parameter read as: [http://www.google.com]
    

    There is no need to set decode-url="true" in the http/https listeners as that's the default value.

    NB: The above causes JBoss EAP 7.1 to send a 302 redirect. I have no idea how to configure a 303 or 307 redirect.

    Final remark

    The obvious alternative to the above is to do the redirect programmatically from your application's code using HttpServletRequest#sendRedirect. In that case too, you do not need redirect-socket="https" in your http-listener.

    Apparently, the redirect-socket attribute is only necessary in conjunction with the <security-constraint> element in your application's web.xml. That's because otherwise (i.e. if you have the <security-constraint> in your web.xml but no redirect-socket in your http-listener), you get hit with:

    ERROR [io.undertow.request] (default task-14) UT005001: An exception occurred processing the request: java.lang.IllegalStateException: UT010053: No confidential port is available to redirect the current request.).

    However if you have both the <security-constraint> and the redirect-socket, the query String is needlessly re-URL-encoded (and thus mangled) in the redirected URL as explained in the question. So it's not clear to me what's the use of <security-constraint> in JBoss EAP 7.1.