trailofbits.python.pickles-in-numpy.pickles-in-numpy

Author
unknown
Download Count*
License
Functions reliant on pickle can result in arbitrary code execution. Consider using fickling or switching to a safer serialization method
Run Locally
Run in CI
Defintion
rules:
- id: pickles-in-numpy
message: Functions reliant on pickle can result in arbitrary code
execution. Consider using fickling or switching to a safer serialization
method
languages:
- python
severity: ERROR
metadata:
category: security
cwe: "CWE-502: Deserialization of Untrusted Data"
subcategory:
- vuln
confidence: MEDIUM
likelihood: MEDIUM
impact: HIGH
technology:
- numpy
description: Potential arbitrary code execution from `NumPy` functions reliant
on pickling
references:
- https://blog.trailofbits.com/2021/03/15/never-a-dill-moment-exploiting-machine-learning-pickle-files/
license: CC-BY-NC-SA-4.0
patterns:
- pattern: numpy.load(..., allow_pickle=$VALUE, ...)
- pattern-not: numpy.load("...", ...)
- pattern-not: numpy.load(..., file="...", ...)
- metavariable-pattern:
metavariable: $VALUE
patterns:
- pattern-not: |
False
- pattern-not: |
[]
- pattern-not: |
None
- pattern-not: '""'
Examples
pickles-in-numpy.py
import numpy
import os
import pickle
class Test(object):
def __init__(self):
self.a = 1
def __reduce__(self):
return (os.system, ("python run.py",))
def test(param_path):
payload = Test()
with open(param_path, "wb") as f:
pickle.dump(payload, f)
# ruleid: pickles-in-numpy
y = numpy.load(param_path, allow_pickle = True)
# ruleid: pickles-in-numpy
x = numpy.load(param_path, allow_pickle = 1)
# ruleid: pickles-in-numpy
z = numpy.load(param_path, allow_pickle=3, encoding='latin1')
# ruleid: pickles-in-numpy
z = numpy.load(allow_pickle='True', file=param_path, encoding='latin1')
# ruleid: pickles-in-numpy
z = numpy.load(allow_pickle='False', file=param_path, encoding='latin1')
# ok: pickles-in-numpy
q = numpy.load(param_path, allow_pickle = False)
# ok: pickles-in-numpy
q = numpy.load(param_path, allow_pickle=None)
# ok: pickles-in-numpy
q = numpy.load(param_path, allow_pickle=[])
# ok: pickles-in-numpy
q = numpy.load(param_path, allow_pickle="")
# ok: pickles-in-numpy
q = numpy.load(param_path, encoding="latin1")
# ok: pickles-in-numpy
q = numpy.load(param_path)
def test2():
payload = Test()
with open("this_is_a_file.pickle", "wb") as f:
pickle.dump(payload, f)
# ok: pickles-in-numpy
y = numpy.load("this_is_a_file.pickle", allow_pickle = True)
# ok: pickles-in-numpy
x = numpy.load("this_is_a_file.pickle", allow_pickle = 1)
# ok: pickles-in-numpy
z = numpy.load("this_is_a_file.pickle", allow_pickle=3, encoding='latin1')
# ok: pickles-in-numpy
z = numpy.load(allow_pickle='True', file="completely_safe_for_sure", encoding='latin1')
# ok: pickles-in-numpy
z = numpy.load(allow_pickle='False', file="completely_safe_for_sure", encoding='latin1')
Short Link: https://sg.run/ryKe