Uploaded image for project: 'WildFly'
  1. WildFly
  2. WFLY-14805

ServletRequest#getLocalPort(), getLocalAddr() and getLocalName() can return wrong information when proxy-address-forwarding="true" is enabled

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: Major Major
    • None
    • None
    • None
    • None
    • Hide

      1. Configure proxy-address-forwarding="true" on http-listener in the undertow subsystem:

      <http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true" record-request-start-time="true" proxy-address-forwarding="true"/>
      

      2. Deploy an example web application that contains the following test.jsp

      <%
      out.println("request.getServerName() = " + request.getServerName());
      out.println("request.getServerPort() = " + request.getServerPort());
      out.println("request.getLocalName() = " + request.getLocalName());
      out.println("request.getLocalAddr() = " + request.getLocalAddr());
      out.println("request.getLocalPort() =  " + request.getLocalPort());
      out.println("request.getRemoteAddr() = " + request.getRemoteAddr());
      out.println("request.getRemotePort() = " + request.getRemotePort());
      %>
      

      3. Start JBoss EAP with the default binding interface (127.0.0.1:8080)

      $ ./bin/standalone.sh
      

      4. Send the curl requests (one without "X-Forwarded-*" headers and another one with "X-Forwarded-*" headers). For example:

      $ curl -v http://127.0.0.1:8080/example/test.jsp
      
      ...
      
      request.getServerName() = 127.0.0.1
      request.getServerPort() = 8080
      request.getLocalName() = localhost
      request.getLocalAddr() = 127.0.0.1
      request.getLocalPort() = 8080
      request.getRemoteAddr() = 127.0.0.1
      request.getRemotePort() = 57412
      
      $ curl -v http://127.0.0.1:8080/example/test.jsp -H "X-Forwarded-For: 192.0.2.43" -H "X-Forwarded-Proto: http" -H "X-Forwarded-Host: 192.0.2.10" -H "X-Forwarded-Port: 7777" 
      
      ...
      
      request.getServerName() = 192.0.2.10
      request.getServerPort() = 7777
      request.getLocalName() = 192.0.2.10
      request.getLocalAddr() = 192.0.2.10
      request.getLocalPort() = 7777
      request.getRemoteAddr() = 192.0.2.43
      request.getRemotePort() = 0
      

      Actual result:
      X-Forwarded-Host and X-Forwarded-Port also affect the result of getLocalName(), getLocalAddr() and getLocalPort().

      Expected result:
      X-Forwarded-Host and X-Forwarded-Port does not affect the result of getLocalName(), getLocalAddr() and getLocalPort(). These methods return the same information with the above two requests.

      Also, you can do a similar test with enabling ForwardedHandler in the undertow subsystem:

                  <server name="default-server">
                      <http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true" record-request-start-time="true"/>
                      ...
                      <host name="default-host" alias="localhost">
                          <location name="/" handler="welcome-content"/>
                          <http-invoker security-realm="ApplicationRealm"/>
                          <filter-ref name="forwarded-filter"/> <!-- add this -->
                      </host>
                  </server>
                  ...
                  <filters>
                      <expression-filter name="forwarded-filter" expression="forwarded"/> <!-- add this -->
                  </filters>
              </subsystem>
      

      Then, send a curl request with "Forwarded" headers and check the results. For example:

      $ curl http://127.0.0.1:8080/example/test.jsp -H "Forwarded: for=192.0.2.43;by=203.0.113.60;proto=http;host=192.0.2.10:7777"
      

      Actual result:
      Forwarded headers "host" value affects the result of getLocalName(), getLocalAddr() and getLocalPort().

      Expected result:
      Forwarded headers "host" value does not affect the result of getLocalName(), getLocalAddr() and getLocalPort().

      Show
      1. Configure proxy-address-forwarding="true" on http-listener in the undertow subsystem: <http-listener name= " default " socket-binding= "http" redirect-socket= "https" enable-http2= " true " record-request-start-time= " true " proxy-address-forwarding= " true " /> 2. Deploy an example web application that contains the following test.jsp <% out.println( "request.getServerName() = " + request.getServerName()); out.println( "request.getServerPort() = " + request.getServerPort()); out.println( "request.getLocalName() = " + request.getLocalName()); out.println( "request.getLocalAddr() = " + request.getLocalAddr()); out.println( "request.getLocalPort() = " + request.getLocalPort()); out.println( "request.getRemoteAddr() = " + request.getRemoteAddr()); out.println( "request.getRemotePort() = " + request.getRemotePort()); %> 3. Start JBoss EAP with the default binding interface (127.0.0.1:8080) $ ./bin/standalone.sh 4. Send the curl requests (one without "X-Forwarded-*" headers and another one with "X-Forwarded-*" headers). For example: $ curl -v http: //127.0.0.1:8080/example/test.jsp ... request.getServerName() = 127.0.0.1 request.getServerPort() = 8080 request.getLocalName() = localhost request.getLocalAddr() = 127.0.0.1 request.getLocalPort() = 8080 request.getRemoteAddr() = 127.0.0.1 request.getRemotePort() = 57412 $ curl -v http: //127.0.0.1:8080/example/test.jsp -H "X-Forwarded-For: 192.0.2.43" -H "X-Forwarded-Proto: http" -H "X-Forwarded-Host: 192.0.2.10" -H "X-Forwarded-Port: 7777" ... request.getServerName() = 192.0.2.10 request.getServerPort() = 7777 request.getLocalName() = 192.0.2.10 request.getLocalAddr() = 192.0.2.10 request.getLocalPort() = 7777 request.getRemoteAddr() = 192.0.2.43 request.getRemotePort() = 0 Actual result: X-Forwarded-Host and X-Forwarded-Port also affect the result of getLocalName(), getLocalAddr() and getLocalPort(). Expected result: X-Forwarded-Host and X-Forwarded-Port does not affect the result of getLocalName(), getLocalAddr() and getLocalPort(). These methods return the same information with the above two requests. — Also, you can do a similar test with enabling ForwardedHandler in the undertow subsystem: <server name= " default -server" > <http-listener name= " default " socket-binding= "http" redirect-socket= "https" enable-http2= " true " record-request-start-time= " true " /> ... <host name= " default -host" alias= "localhost" > <location name= "/" handler= "welcome-content" /> <http-invoker security-realm= "ApplicationRealm" /> <filter-ref name= "forwarded-filter" /> <!-- add this --> </host> </server> ... <filters> <expression-filter name= "forwarded-filter" expression= "forwarded" /> <!-- add this --> </filters> </subsystem> Then, send a curl request with "Forwarded" headers and check the results. For example: $ curl http: //127.0.0.1:8080/example/test.jsp -H "Forwarded: for =192.0.2.43;by=203.0.113.60;proto=http;host=192.0.2.10:7777" Actual result: Forwarded headers "host" value affects the result of getLocalName(), getLocalAddr() and getLocalPort(). Expected result: Forwarded headers "host" value does not affect the result of getLocalName(), getLocalAddr() and getLocalPort().
    • Hide

      The following Servlet Filter can be used as a workaround. This provides HttpServletRequestWrapper that uses Undertow API to override the current implementation:

      @WebFilter("/*")
      public class ExampleServletFilter implements Filter {
      
          ...
      
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
              throws IOException, ServletException {
      
              if (!(request instanceof HttpServletRequest)) {
                  chain.doFilter(request, response);
                  return;
              }
      
              HttpServletRequest httpRequest = (HttpServletRequest) request;
              HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) {
      
                  @Override
                  public String getLocalName() {
                      HttpServerExchange exchange = ((HttpServletRequestImpl) request).getExchange();
                      SocketAddress address = exchange.getConnection().getLocalAddress();
                      if (address instanceof InetSocketAddress) {
                          return ((InetSocketAddress) address).getAddress().getHostName();
                          // return ((InetSocketAddress) address).getHostString(); // this does not attempt a reverse lookup
                      }
                      return null;
                  }
      
                  @Override
                  public String getLocalAddr() {
                      HttpServerExchange exchange = ((HttpServletRequestImpl) request).getExchange();
                      SocketAddress address = exchange.getConnection().getLocalAddress();
                      if (address instanceof InetSocketAddress) {
                          return ((InetSocketAddress) address).getAddress().getHostAddress();
                      }
                      return null;
                  }
      
                  @Override
                  public int getLocalPort() {
                      HttpServerExchange exchange = ((HttpServletRequestImpl) request).getExchange();
                      SocketAddress address = exchange.getConnection().getLocalAddress();
                      if (address instanceof InetSocketAddress) {
                          return ((InetSocketAddress) address).getPort();
                      }
                      return -1;
                  }
              };
      
              chain.doFilter(wrappedRequest, response);
          }
      
      ...
      
      Show
      The following Servlet Filter can be used as a workaround. This provides HttpServletRequestWrapper that uses Undertow API to override the current implementation: @WebFilter( "/*" ) public class ExampleServletFilter implements Filter { ... public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!(request instanceof HttpServletRequest)) { chain.doFilter(request, response); return ; } HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(httpRequest) { @Override public String getLocalName() { HttpServerExchange exchange = ((HttpServletRequestImpl) request).getExchange(); SocketAddress address = exchange.getConnection().getLocalAddress(); if (address instanceof InetSocketAddress) { return ((InetSocketAddress) address).getAddress().getHostName(); // return ((InetSocketAddress) address).getHostString(); // this does not attempt a reverse lookup } return null ; } @Override public String getLocalAddr() { HttpServerExchange exchange = ((HttpServletRequestImpl) request).getExchange(); SocketAddress address = exchange.getConnection().getLocalAddress(); if (address instanceof InetSocketAddress) { return ((InetSocketAddress) address).getAddress().getHostAddress(); } return null ; } @Override public int getLocalPort() { HttpServerExchange exchange = ((HttpServletRequestImpl) request).getExchange(); SocketAddress address = exchange.getConnection().getLocalAddress(); if (address instanceof InetSocketAddress) { return ((InetSocketAddress) address).getPort(); } return -1; } }; chain.doFilter(wrappedRequest, response); } ...
    • Undefined

      ServletRequest#getLocalPort(), getLocalAddr(), and getLocalName() does not return the correct information of the interface on which the request was received when proxy-address-forwarding="true" is enabled on http-listener. I think this behavior has started to happen since EAP 7.2.0 that incorporates UNDERTOW-1280 and UNDERTOW-1282 (via EAP7-834).

      Servlet API javax.servlet.ServletRequest says:

      • getLocalPort() returns the Internet Protocol (IP) port number of the interface on which the request was received.
      • getLocalAddr() returns the Internet Protocol (IP) address of the interface on which the request was received.
      • getLocalName() returns the host name of the Internet Protocol (IP) interface on which the request was received.

      So, it should return the actual IP address/port and host name of the binding interface of the listener.

      However, when ProxyPeerAddressHandler is enabled (e.g. proxy-address-forwarding="true" is specified), the X-Forwarded-Host/X-Forwarded-Port headers affect the results of the above methods. Similarly, when ForwardedHandler is enabled, the Forwarded header affects the results of the above methods. It's okay that these headers affects the results of getServerName() and getServerPort() with these handlers, but I think it should not affect the results of getLocalPort(), getLocalAddr() and getLocalName().

              flaviarnn Flavia Rainone
              flaviarnn Flavia Rainone
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

                Created:
                Updated:
                Resolved: