Skip Navigation
Get a Demo
 
 
 
 
 
 
 
 
 
Resources Blog Testing and validation

Find security bugs in web application routes with route-detect

route-detect is a new command-line security tool for finding authentication and authorization bugs in web application routes.

Matt Schwager

I’m excited to release route-detect, a free command line tool developed for software engineers, security researchers, application security engineers, and other security practitioners who work with web application code. It uses static analysis to quickly detect issues in code before they hit production. It also includes a browser-based visualization component so users can inspect routes and their authentication (authn) and authorization (authz) security properties:

 

Routes from koel streaming server

Routes from koel streaming server

 

authn and authz bugs are common web application security issues, the severity and prevalence of which are underscored by the following industry standard resources:

 

This post will describe the problem route-detect aims to solve, explain its origin, discuss implementation details of the tool, and announce some plans for the future.

Authentication and authorization

First, let’s start with a brief explanation of authentication and authorization, and how they can cause security bugs in web application routes. The following terms will appear throughout the rest of this post:

  • Route: connects a URL path to the application code that handles it
  • Authentication: validates who you are
  • Authorization: validates what you can access
  • Roles: used by the authorization process to determine what you can access
  • Insecure: in this context, improperly specified authn or authz

It’s worth a brief exploration of why these types of bugs are so common. Modern web applications are often composed of hundreds or thousands of routes, multiple authn methods, and dozens of authz roles. Users may authenticate via a password in the browser, an API token in code, or with a SAML identity provider. Further, that user may be an administrator, a guest, an analyst, or any number of user roles an application provides. This complexity naturally gives rise to bugs. Programmer error, forgetfulness, or unfamiliarity with a codebase may mean functionality is left unauthenticated—or uses an incorrect user role. This problem is further exacerbated by the commonly “opt-in” nature of authn and authz.

Whenever possible, software should adopt secure-by-default behavior. This means software defaults to secure behavior, and insecure behavior requires additional work. Web application frameworks often require adding authn and authz after the fact rather than defaulting to secure behavior. Consider the following basic Flask application as an example:

 

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
    return "<p>Hello, World!</p>"

Adding authentication would look something like the following:

@app.route("/")
@login_required  # <-- Require authentication
def hello_world():
    return "<p>Hello, World!</p>"

This is certainly doable and straightforward, but the opt-in nature of authn means it’s easy to forget, especially when you have hundreds or thousands of routes. Combine this with the complexity of modern web applications and it’s easy to see why authn and authz issues are so prolific.

So, what to do? A good first step would be identifying the issue, and surfacing the information necessary to do something about it. route-detect seeks to find web application routes and their authn and authz properties, and provide this information to users in an accessible and actionable manner.

Ideation

route-detect was born out of my Georgia Tech Master’s in Cybersecurity practicum project. These projects are part of a capstone course for the program, typically taken in your final semester. They seek to fuse academia and industry. Red Canary was nice enough to let me use some of our proprietary, internal code as a testbed for route-detect. This helped me test out my new tool on a real-world codebase, and it helped Red Canary ensure our web application code was secure.

In the end, route-detect found 18 authenticated, but unintentionally unauthorized web application routes in one of our codebases. This means these routes were correctly scoped to a specific organization, but may have allowed access to a lower privileged user than intended. These findings represent 12 percent of the codebase’s 149 routes. authz was subsequently added to these routes to ensure proper access control. All routes in this codebase should be both authenticated and authorized, so we automated detection of unauthorized routes and added a check to our CI pipelines to ensure nothing slips through the cracks in the future.

Implementation

route-detect is built on three primary technologies:

  1. Semgrep, which provides the static analysis capabilities for detecting routes and their authn and authz properties
  2. D3.js, which provides the visualization component seen above for quickly inspecting a codebase
  3. Python code for gluing everything together and providing an installation mechanism via pip

Semgrep rules use basic boolean logic to search code for specific patterns. In route-detect‘s case, we are searching for web application routes and their authn and authz security properties. But first, what exactly is a web application route?

A web application route connects a URL path to the application code responsible for handling requests to that path. For example, connecting an HTTP GET request to the /api/users endpoint to the code that returns all users. Consider the following example code using Python’s Flask web application framework:

from internal_module import get_users
from flask import Flask

app = Flask(__name__)

@app.route("/api/users", methods=["GET"])
def get_all_users():
    return get_users()

In this example, the Flask framework connects HTTP GET requests to the /api/users endpoint to the get_all_users function. route-detec builds on detecting web application routes such as this Python Flask example to support six programming languages, 17 web application frameworks, and an ever-growing number of authn and authz libraries for these frameworks. See the project’s README for the full list.

Now, back to Semgrep rules and boolean logic. route-detect seeks to find three types of routes:

  1. Unauthenticated and unauthorized
    a. (code is a route) AND (route is NOT authn) AND (route is NOT authz)
  2. Authenticated and authorized
    a. (code is a route) AND (route is authn) AND (route is NOT authz)
  3. Unauthenticated and authorized
    a. (code is a route) AND (route is NOT authn) AND (route is authz)

Set logic is another useful way to think about this:

Set logic chart for authenticated web application routes

These cases are often interesting from a security perspective because routes may be unintentionally unauthenticated or unauthorized. The careful reader may note that authorization implies authentication. Authentication determines who you are, and authorization determines what you can access. Determining what you can access presumes we know who you are. This means when we see a route that is authorized, we can assume it is also authenticated. That’s why there’s not a fourth case listed above considering authenticated and authorized.

Next, we have to encode these three cases as Semgrep rules. A Semgrep rule to find two of these cases may look something like the following:

rules:
- id: laravel-route-authenticated
  patterns:
    - pattern: Route::post(...)->middleware("=~/^auth.*/");
  message: Found authenticated Laravel route
  languages: [php]
  severity: INFO
- id: laravel-route-unauthenticated
  patterns:
    - pattern: Route::post(...)->...;
    - pattern-not: Route::post(...)->middleware("=~/^auth.*/");
  message: Found authenticated Laravel route
  languages: [php]
  severity: INFO

Describing Semgrep’s rule and pattern syntax is outside the scope of this post, but the basic boolean logic building blocks should be apparent. There are two PHP Laravel rules: one searching for authenticated routes and one searching for unauthenticated routes. From here we can build up to the three previously mentioned cases and expand from there when frameworks have more advanced security mechanisms. For more information on Semgrep rules for a specific framework, please see route-detect‘s rules.

Now that we have an understanding of how route-detect finds routes and their authn and authz security properties, we can move on to its D3.js visualization component. Before passing any data to D3.js, we first need a way to programmatically access our Semgrep route results. Semgrep supports JSON output with the --json flag. route-detect can use this flag to programmatically access the Semgrep results, re-shape the data, and feed that to D3.js for viewing in the browser:

$ semgrep --json --config $(routes which django) --output routes.json path/to/django/code
$ routes viz --browser routes.json

route-detect provides the routes CLI command. The first command runs Semgrep with route-detect‘s Django route detection rules. The second command ingests the Semgrep JSON output and produces a local HTML file for viewing in the browser. The --browser flag will automatically open the file in the user’s designated browser. It’s important to note that this is all processed and displayed locally, and findings are never sent anywhere outside the user’s machine. route-detect uses a tidy tree visualization to display routes based on where they are located in the filesystem. See the image at the beginning of this post for an example of the visualization.

This is an abbreviated explanation of route-detect‘s implementation. For more details please see my full practicum paper.

More to come

route-detect can be expanded either horizontally or vertically—or in another direction altogether. Horizontal expansion includes things like adding support for new languages, frameworks, and libraries. In other words, broadening support for additional technologies. Security practitioners often move between many technologies in a short period of time as they conduct pentests, security assessments, and code reviews against different targets. Because of this, there’s lots of value in providing a one-stop shop tool that can analyze the full breadth of technologies in a modern environment.

Additionally, route-detect can be expanded vertically. This means finding new ways routes and their authn and authz properties can be specified in a framework that’s already supported. In the domain of static analysis, there will always be false positives and false negatives. We can also use measurements like precision, recall, and F-score to improve existing rules. In short, expanding vertically means we can dive deeper into existing implementations.

Heuristics can also be taken in a different direction altogether. route-detect could implement anomaly detection to find insecure routes. For example, what if all routes in a given file are authenticated, except for one? What if all routes in a given directory are authenticated except for one? What if all routes in a given file have the same authz role, except for one? Perhaps these findings are benign, but they may also indicate a mistake that has security implications.

I’m proposing these improvements as vague ideas that came to me as I worked on this tool for a few months. If you have your own ideas for improvements you’d like to see and/or implement, feel free to open an issue. But, for the near term, I’ll be hitting the road and showing off route-detect. If you happen to find yourself at any of the following conferences feel free to drop by and say hi:

 

Emu-lation: Validating detections for SocGholish with Atomic Red Team

 

Emu-lation: Validating detection for Gootloader with Atomic Red Team

 

Safely validate executable file attributes with Atomic Test Harnesses

 

The Validated Canary: Unearthing changes in our detection engine with Coalmine

Subscribe to our blog

 
 
Back to Top