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
authn
and authz
bugs are common web application security issues, the severity and prevalence of which are underscored by the following industry standard resources:
- 2021 OWASP Top 10 #1 – Broken Access Control
- 2021 OWASP Top 10 #7 – Identification and Authentication Failures
- 2023 OWASP API Top 10 #1 – Broken Object Level Authorization
- 2023 OWASP API Top 10 #2 – Broken Authentication
- 2023 OWASP API Top 10 #5 – Broken Function Level Authorization
- 2023 CWE Top 25 #11 – CWE-862: Missing Authorization
- 2023 CWE Top 25 #13 – CWE-287: Improper Authentication
- 2023 CWE Top 25 #20 – CWE-306: Missing Authentication for Critical Function
- 2023 CWE Top 25 #24 – CWE-863: Incorrect Authorization
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
orauthz
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:
- Semgrep, which provides the static analysis capabilities for detecting routes and their
authn
andauthz
properties D3.js
, which provides the visualization component seen above for quickly inspecting a codebase- 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:
- Unauthenticated and unauthorized
a. (code is a route) AND (route is NOTauthn
) AND (route is NOTauthz
) - Authenticated and authorized
a. (code is a route) AND (route isauthn
) AND (route is NOTauthz
) - Unauthenticated and authorized
a. (code is a route) AND (route is NOTauthn
) AND (route isauthz
)
Set logic is another useful way to think about this:
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: