python.flask.security.injection.nan-injection.nan-injection

profile photo of semgrepsemgrep
Author
unknown
Download Count*

Found user input going directly into typecast for bool(), float(), or complex(). This allows an attacker to inject Python's not-a-number (NaN) into the typecast. This results in undefind behavior, particularly when doing comparisons. Either cast to a different type, or add a guard checking for all capitalizations of the string 'nan'.

Run Locally

Run in CI

Defintion

rules:
  - id: nan-injection
    message: Found user input going directly into typecast for bool(), float(), or
      complex(). This allows an attacker to inject Python's not-a-number (NaN)
      into the typecast. This results in undefind behavior, particularly when
      doing comparisons. Either cast to a different type, or add a guard
      checking for all capitalizations of the string 'nan'.
    languages:
      - python
    severity: ERROR
    mode: taint
    pattern-sources:
      - pattern-either:
          - pattern: flask.request.$SOMETHING.get(...)
          - pattern: flask.request.$SOMETHING[...]
          - patterns:
              - pattern-inside: |
                  @$APP.route(...)
                  def $FUNC(..., $ROUTEVAR, ...):
                    ...
              - pattern: $ROUTEVAR
    pattern-sinks:
      - pattern-either:
          - pattern: float(...)
          - pattern: bool(...)
          - pattern: complex(...)
    pattern-sanitizers:
      - not_conflicting: true
        pattern: $ANYTHING(...)
    metadata:
      references:
        - https://discuss.python.org/t/nan-breaks-min-max-and-sorting-functions-a-solution/2868
        - https://blog.bitdiscovery.com/2021/12/python-nan-injection/
      category: security
      cwe:
        - "CWE-704: Incorrect Type Conversion or Cast"
      technology:
        - flask
      subcategory:
        - vuln
      impact: MEDIUM
      likelihood: MEDIUM
      confidence: MEDIUM
      license: Commons Clause License Condition v1.0[LGPL-2.1-only]
      vulnerability_class:
        - Improper Validation

Examples

nan-injection.py

import os
import flask
import hashlib
import requests

app = flask.Flask(__name__)

@app.route("/buy/<tid>")
def buy_thing(tid):
    price = get_price()

    # ruleid: nan-injection
    x = float(tid)

    if x < price:
        return deny()
    return buy()

@app.route("unit_1")
def unit_1():
    tid = flask.request.args.get("tid")

    # ruleid: nan-injection
    bool(tid)

    # ruleid: nan-injection
    complex(tid)

@app.route("unit_1_5")
def unit_1_5():
    tid = flask.request.args["tid"]

    # ruleid: nan-injection
    float(tid)

    # ruleid: nan-injection
    bool(tid)

    # ruleid: nan-injection
    complex(tid)

@app.route("unit_2")
def unit_2():
    tid = flask.request.args.get("tid")

    # ok: nan-injection
    bool(int(tid))

    # ok: nan-injection
    float(int(tid))

@app.route("unit_3")
def unit_3():
    tid = flask.request.args.get("tid")

    # ok: nan-injection
    obj = fetch_obj(tid)

    # ok: nan-injection
    num = float(obj.num)

@app.route("/drip")
def drip():
    # ruleid: nan-injection
    duration = float(flask.request.args.get("duration", 2))
    numbytes = min(int(flask.request.args.get("numbytes", 10)), (10 * 1024 * 1024))  # set 10MB limit
    code = int(flask.request.args.get("code", 200))

    if numbytes <= 0:
        response = Response("number of bytes must be positive", status=400)
        return response

    # ruleid: nan-injection
    delay = float(flask.request.args.get("delay", 0))
    if delay > 0:
        time.sleep(delay)

    pause = duration / numbytes

    def generate_bytes():
        for i in xrange(numbytes):
            yield b"*"
            time.sleep(pause)

    response = Response(
        generate_bytes(),
        headers={
            "Content-Type": "application/octet-stream",
            "Content-Length": str(numbytes),
        },
    )

    response.status_code = code

    return response