clay icon indicating copy to clipboard operation
clay copied to clipboard

Added CSRF protection

Open nydelzaf opened this issue 2 years ago • 1 comments

Notes

The stated goal for Clay is to be “a framework for building RESTful backend services using best practices.” One of the best practices omitted, however, is the Cross-Site Request Forgery (CSRF) protection. In the fix suggested, I suggested introducing the CSRF protection so that the protection is available downstream.

I created a demo that shows an app importing clay being vulnerable to CSRF. If the downstream app explicitly enables CSRF protection, the CSRF can be prevented. But this should really be done at the library level. Here are two reasons why:

  1. A consumer of the library may assume that the library has added the protection by default and do not do anything explicitly. Looking out for these even after using a framework/library becomes a hassle. It opens up a supply chain issue.
  2. A consumer may reference the library code to learn best practices. Since CSRF protection is not mentioned anywhere, it will teach an unsafe way to build an application.

Demo Video

https://drive.google.com/file/d/1kbjGNcgJVAUHzQ9zq_S9S_p7e8wRqXP1/view?usp=sharing

Attack Nature

Modern browsers issue pre-flight requests to check if CORS (Cross-Origin-Resource-Sharing) policy is implemented by the server or not. This takes place for JavaScript related request mechanisms like fetch and XMLHttpRequest (xhr). In those cases, the browser blocks the request if origin of the request doesn’t match the endpoint.

However, for HTML based request mechanism (POST using Form Element) or (GET request through Image, embed) the pre-flight mechanism doesn’t take place. In this attack, we have taken advantage of that.

Source Code for Demo

I created a vulnerable clay app that mimics a banking system. There are two accounts in the bank. The GET request shows the account detail and balance. The POST request performs a transaction.

Test Clay App (Vulnerable)

from flask import request
from clay import app

database = {
    "user": {
        "ataf": {
            "balance": 2000
        },
        "rabid": {
            "balance": 10000
        }
    }
}

token_user = {
    "or_23h123h1983h39h123": "ataf",
    "or_23ndadsoasdiuasie9": "rabid",
}

@app.route("/", methods=["GET"])
def get_homepage():
    #   get the logged in user by their token from cookies
    token = request.cookies.get("usertoken")
    user_name = token_user.get(token)
    balance   = database.get("user").get(user_name).get("balance")

    res_body = f"""
        <h1>
            Account Details of {user_name}
        </h1>
        <h2>
            Balance: {balance}
        </h2>
    """
    return res_body

@app.route("/", methods=["POST"])
def post_homepage():
    try:
        #   get the logged in user by their token from cookies
        token = request.cookies.get("usertoken")
        tnx_from = token_user.get(token)

        #   transaction details
        tnx_to   = request.form.get("to")
        tnx_amt  = int(request.form.get("amount"))

        database["user"][tnx_from]["balance"] -= tnx_amt
        database["user"][tnx_to]["balance"] += tnx_amt
        return "Transaction Successful!"

    except Exception as e:
        return str(e)

app.run()

For the sake of simplicity, we haven’t implemented Login & Registration functionality. As a result, we have to copy the token or_23h123h1983h39h123 and paste it as a Cookie for our web app through the browser. The cookie key is usertoken. It’s shown in the video.

Clay App (Safe)

We enabled CSRF protection after importing from clay. Again, this is not the best practice. The protection should be at the library level. We are doing it here to show that protection works without having to recompile the library. This keeps the demo short.

from flask_wtf.csrf import CSRFProtect
CSRFProtect(app=app)

In this Clay App (Safe) we have imported CSRFProtect from flask_wtf library. And configure the app instance with it. As a result, when we restart our Clay app, and try to perform the CSRF attack, the CSRFProtect middleware detects it and returns a HTTP 500: Internal Server Error which saves our user from being a victim of CSRF.

Malicious HTML

This malicious/attacker website has a form that’s mostly hidden except a button that says “You Won Lottery”. Upon clicking that, an HTML form POST request gets issued (with the cookies attached) to our web app’s route that’s used for the transaction. Since our web app uses the cookie to identify currently logged-in user, the transaction takes place & an amount of 100 gets deducted.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Not A Malicious Website</title>
</head>

<body>
    <h2>
        We are definitely *NOT* a malicious website
    </h2>
    <form action="http://127.0.0.1:5000/" method="post">
        <input hidden name="to"     value="rabid">        
        <input hidden name="amount" value="100">
        <input type="submit" value="You Won Lottery" placeholder="Hehe">
    </form>
</body>
</html>

Sponsorship and Support

This work is done by the security researchers from OpenRefactory and is supported by the Open Source Security Foundation (OpenSSF): Project Alpha-Omega. Alpha-Omega is a project partnering with open source software project maintainers to systematically find new, as-yet-undiscovered vulnerabilities in open source code - and get them fixed – to improve global software supply chain security.

The bug is found by running the Intelligent Code Repair (iCR) tool by OpenRefactory and then manually triaging the results.

nydelzaf avatar Aug 22 '23 05:08 nydelzaf

CLA assistant check
All committers have signed the CLA.

CLAassistant avatar Aug 22 '23 05:08 CLAassistant