Bento check: Detecting authentication credentials leaked over HTTP

get this check now in Bento: pip3 install bento-cli && bento init

Our mission at r2c is to profoundly improve software security and reliability to safeguard human progress, and we wanted to contribute to the static analysis community by writing some security-focused checks for Python.

Understandably, Python devs have focused on style for a long time. Good style makes code more readable, and code is read more often than it is written. This has led to linters for style. However, fixing style nits takes away from valuable development time, especially when multiple developers debate style during code reviews. r2c did away with such bikeshedding and bought ourselves some time back in our lives by using deterministic formatters with pre-commit. (We use Black for Python and Prettier for JavaScript and friends.)

Meanwhile, companies like Google and Facebook have systematically eliminated entire vulnerability classes in their code with the help of static analysis. We want to bring some of that power to developers at all kinds of companies.

Common Python bugs are covered fairly well by Flake8 and friends. Considering that web vulnerabilities are incredibly pervasive, we narrowed our focus to web frameworks. And, since we had many to choose from, we lasered in on a framework we use at r2c: Flask. However! The check in this article isn't about Flask--we have other articles about our Flask checks. Sometimes, instead of serving requests we want to make them. And we want to make them securely!

This check detects uses of the requests module that send authentication credentials over an insecure channel (HTTP). The check looks for any requests call that has the auth parameter and examines the URL. If the URL uses HTTP, the check will alert you to the finding.

The check will detect these cases:

import requests
r = requests.get("http://example.com", auth=('user', 'pass'))

from requests import get, post
post("http://example.com", auth=('user', 'pass'))

The check considers these cases acceptable:

# No auth parameter
import requests
r = requests.get("http://example.com")

# Uses https
import requests
r = requests.post("https://example.com", auth=('user', 'pass'))

# No import
r = requests.get("http://example.com", auth=('user', 'pass'))

As always, we used our static analysis platform to test this check in the wild. We found a handful of repositories that ostensibly send credentials over HTTP. One example we found is shown below.

def MethodNameRedacted(parameter1, parameter2, parameter3, parameter4, CONSUMER_KEY, CONSUMER_SECRET):
  import requests
  from requests.auth import OAuth1
  from urllib import quote_plus

  url = u'http://api.companyapis.com/api/finder'
  query_params = {
    'q' : quote_plus(parameter1+ ' ' + parameter2 + ' ' + parameter3 + ' ' + str(parameter4)),
    'flags' : 'J',
  }


  queryoauth = OAuth1(CONSUMER_KEY, CONSUMER_SECRET)
  response = requests.get(url, params=query_params, auth=queryoauth)

You can check your own codebase for intsances of this check right now with Bento:

$ pip3 install bento-cli && bento init

References