Jetty.project: Circumvent authentication with multiple slashes in URL

Created on 30 Mar 2016  Â·  13Comments  Â·  Source: eclipse/jetty.project

I have a servlet mapped on /api/* and a <security-constraint> requiring HTTP basic auth on /api/devicemanagement/gateways/*.

Jetty _doesn't_ enforce authentication when accessing /api//devicemanagement/gateways/add (note the extra slash).

I talked to @joakime on IRC about this last week – over the weekend I have minimized the example and reproduced the bug: https://github.com/hho/servletsecuritytest

You can run the example in Jetty as well as deploy the app to Tomcat, which doesn't seem to have the same bug.

Question

Most helpful comment

I pushed a commit that improves the activation of the rewrite rule for compaction.
This can now be done with

--module=rewrite,rewrite-compactpath

Also added a rewrite request customizer to increase options for deployment.

All 13 comments

@hho,

Currently I'm thinking this is a bug with the spring web framework which is mapping the pathInfo //devicemanagement/gateways/add to a resource at /devicemanagement/gateways/add

Those paths are different as // is significant in URIs. This is made worse by the servlet specs stupid security model that allows everything but denies specific URLs (should be the other way around).

So in this case, Jetty sees a request for /api//devicemanagement/gateways/add and correctly determines that it does not match the security constraint. It then passes the request to the servlet at /api/*, which looks at the path info of //devicemanagement/gateways/add and leniently maps it to /devicemanagement/gateways/add

I will double research that our // handling is correct... but for now there are a few ways around this for you...

You can apply the RewriteHandler module to your server and install the CompactPath rule, which replaces all // with / prior to the request being run past the security constraints (I expect tomcat does this by default?)

But I think a better way is to install a security constraint at /api/* that denies all requests and then add extra more permissive constraints that allow access (with user constraint) on precise URIs. For your example the following works:

        <security-constraint>
                <web-resource-collection>
                        <web-resource-name>Forbidden</web-resource-name>
                        <url-pattern>/api/*</url-pattern>
                </web-resource-collection>
                <auth-constraint/>
        </security-constraint>
        <security-constraint>
                <web-resource-collection>
                        <web-resource-name>Special API methods</web-resource-name>
                        <url-pattern>/api/devicemanagement/gateways/*</url-pattern>
                </web-resource-collection>
                <auth-constraint>
                        <role-name>user</role-name>
                </auth-constraint>
        </security-constraint>

This approach is a far superior security model as it is not vulnerable to resource aliasing - which is the problem that the spring framework is giving you.

Checking RFC3986 section 3.3 shows that URI path is defined by:

      path          = path-abempty    ; begins with "/" or is empty
                    / path-absolute   ; begins with "/" but not "//"
                    / path-noscheme   ; begins with a non-colon segment
                    / path-rootless   ; begins with a segment
                    / path-empty      ; zero characters

      path-abempty  = *( "/" segment )
      path-absolute = "/" [ segment-nz *( "/" segment ) ]
      path-noscheme = segment-nz-nc *( "/" segment )
      path-rootless = segment-nz *( "/" segment )
      path-empty    = 0<pchar>
      segment       = *pchar
      segment-nz    = 1*pchar
      segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
                    ; non-zero-length segment without any colon ":"

      pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"

So a path is a sequence of segments, each separated by / and segments may be empty. Thus /a/b is 2 segments 'a' & 'b', while /a//b is 3 segments 'a', '' & 'b'. The fact that spring web framework chooses to ignore empty segments is outside of jetty's control (other than if we use rewrite to remove them).

Leaving this open now for discussion to see if there is something better we can do, but I don't think this is a bug

I'm working on an improvement to the rewrite modules to make it easier to enable the CompressPath rule. Also adding an option to install the rule as a HttpConfiguration.Customizer instead of a handler.

Thanks for the explanation! So it's technically not a bug – but it's very unexpected behavior. Also, the reference implementations (Glassfish, Tomcat) don't discern multiple slashes either.

Maybe the new CompressPath rule could become the default?

@hho I don't think we can change default behaviour as significant as this in a dot release, maybe not even in a point release.

I do agree that it is unexpected behaviour, but I maintain that is primarily due to the servlet security model. Any time a servlet is written that will allow any aliasing (eg case insensitive or any other forgiving of content like //) then the security constraints can often be bypassed.

So I do think that it is a bug on spring web framework to unilaterally decide to ignore //. If that decision is to be made safely, it can only be done by the container before security constraints are applied.

While in this case Spring boot was used, the same occurs for the embedded jetty example, which involves only these three files, and no external framework, and no configuration for compacting URLs:

So unless I am mistaken, there is not just spring boot's leniency at fault.

Sorry, forget that, I guess in the minimal example the secured handler just aliases any request to the hello handler, in a way

@tkruse, If you use the standard security constraint pattern of permit-deny, then any content provider that can server the same content for different URIs is going to be vulnerable to aliases bypassing security constraints.

In jetty we try to avoid all aliasing in our serving of resources (static content & JSPs), but you can't avoid that with simple HelloWorld style servlets.

If you are using servlet authentication, it is best to switch it to deny-permit semantics by having a forbid constraint at /* and then selectively relaxing uris like root "" and specific URIs

@tkruse Note that jetty has had some problems like this in the past... ie if something like /foobar.jsp%00 shows the source code of the JSP, that would be a Jetty bug and we'd be all over it to fix it ASAP.

I had a reply already from Jesse, pointing to
https://github.com/eclipse/jetty.project/issues/467
thanks

On Sun, Apr 3, 2016 at 12:57 AM, Greg Wilkins [email protected]
wrote:

@tkruse https://github.com/tkruse Note that jetty has had some problems
like this in the past... ie if something like /foobar.jsp%00 shows the
source code of the JSP, that would be a Jetty bug and we'd be all over it
to fix it ASAP.

—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/eclipse/jetty.project/issues/467#issuecomment-204817859

I pushed a commit that improves the activation of the rewrite rule for compaction.
This can now be done with

--module=rewrite,rewrite-compactpath

Also added a rewrite request customizer to increase options for deployment.

That is awesome! Thank you very much!

Now, I totally agree this can't be done in a dot release – but given that other servlet containers do so by default, I'll keep hoping for this as the default in the next point release… 😉

Was this page helpful?
0 / 5 - 0 ratings