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

Author
unknown
Download Count*
License
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]
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
Short Link: https://sg.run/e598