# Fixing the case of the Implicit Flow modification

- Link: https://www.agilicus.com/fixing-the-case-of-the-implicit-flow-modification/
- Published: 2020-05-29T00:05:11+00:00
- Author: Don Bowman

Last year I met this web application. Let’s call it Hank. Hank was pretty, but not
that smart. In particular, Hank was very prone to trusting what user’s told it. 
Earlier we [learned](https://www.agilicus.com/fixing-the-unsanitised-input-web-app/)
about how Hank would just trust what a user entered, and then later use that against
others. But that is not what we are talking about today. Today we are talking about
how Hank would let a user modify some data and become an administrator.

You see, the browser is a funny world. It tries very hard to protect its user from
the threats that lurk on the web. But, it’s also not very trustworthy itself. Any
user can modify data in it. You can’t have secrets, you can’t trust fields. You 
can’t have some variable ‘admin=true’ and expect the user to just behave.

In Hank’s case it used a login method called OpenID Connect. Yes the very same one
I’m a big fan of. But, Hank skipped some classes and used the Implicit Flow instead
of the PKCE flow. Bad Hank. This would be fine if Hank just used the authentication
and passed the token as-is to the back-end, but, Hank used some fields in the ID
Token to indicate role (e.g. admin vs end-user). Worse, Hank’s back-end used the
same fields but did not validate the ID Token. So a user could login, modify the
resulting info, and pass it back to the back-end and become an admin. Naughty Hank!

Now the correct solution here is sometimes called the 3-legged flow. In this model,
the front end (the browser), sets up and finishes the login flow, but, the last 
step is it shares a code w/ the back-end which then goes and fetches from the authentication
and retrieves the token. In this model we have a protected code base and can have
secrets, we would often cipher the result and make a session cookie, making it impossible
to modify in the front-end.
So, how would you solve this? Send Hank in for some 
code surgery? That would be the best. But what if you don’t have the time or money?
Can you let Hank loose on the Internet?

Sure you can! The fix was complex, but, in the Web Application Firewall we intercepted
the response to the back-end that had the fields in it that were prone to modification.
Using the Access Token that Hank supplied, we then went and fetched the real values(
in this example the user role), overwriting what the user supplied. We then ciphered
with something the user doesn’t have access to (since its in the back-end), and 
returned it as a cookie. Later, on other requests, we would translate it back for
the back-end.

Fully transparent, fully secure. Now that you’ve read the story of Hank, I hope 
that you will read your OpenID Code Flows and look closely at PKCE or 3-legged. 
Don’t allow critical information to be modified in the browser and just trusted 
in the backend. Trust, but verify. Defense in Depth. Assume the browser is broken.
Its just easier that way.

As for the workaround in the Web Application Firewall? First we make the observation
that we have already confirmed the validity of the Bearer token. Then, we put in
a rule like this:

    ```wp-block-preformatted
    local auth_header = ngx.req.get_headers()["authorization"]
    local str = require "resty.string"
    local aes = require "resty.aes"
    local ck = require "resty.cookie"
    local cookie, err = ck:new()
    local aes_256_cbc_sha512x5 = aes:new(session_secret, nil, aes.cipher(256,"cbc"), aes.hash.sha512, 5)
    local encrypted = aes_256_cbc_sha512x5:encrypt(auth_header)
    -- str.to_hex(encrypted),
    local ok, err = cookie:set({
      key = "agilicus_token",
      value = ngx.encode_base64(encrypted, true),
      path = "/",
      httponly = true,
      secure = true,
      samesite = "Strict"
    })
    ```

Later we use that encrypted cookie overriding what the user can provide. We also
do an east-west call (replicating what the browser did and trusted), but in the 
back-end where they cannot modify:

    ```wp-block-preformatted
    local http = require "resty.http"
    local httpc = http.new()
    local auth_header = ngx.req.get_headers()["authorization"]
    -- Initialise response to 500, override if we logout
    ngx.status = 500
    if auth_header then
      ngx.say("Error: cannot log out, Host or Authentication header is not set")
    else
      local domain = x.."."..y.."."..z
      local uri = "https://auth."..domain.."/token/revoke"
      local token = string.match(auth_header, "[^ ]+ (.*)")
      local res, err = httpc:request_uri(uri, {
      method = "POST",
      body = "token="..token.."&token_type_hint=access_token",
      headers = {
        ["Content-Type"] = "application/x-www-form-urlencoded",
      },
      keepalive_timeout = 1,
      keepalive_pool = 1
    })
    if not res then
      ngx.say("Failed to revoke access token on "..uri.." : ", err)
    else
      ngx.status = res.status
      ngx.say(res.body)
    end
    end
    ```
