Skip to content

Commit 579fcdc

Browse files
fix(iast): patch json.encoder to encode LazyTaintDict as dict [backport 2.1] (#7350)
Backport fee5ed0 from #7348 to 2.1. IAST: Fix an issue where JSON encoder would fail at encoding a tainted dict or list. ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) - [x] If this PR touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. - [x] This PR doesn't touch any of that. Co-authored-by: Federico Mon <[email protected]>
1 parent 2508d28 commit 579fcdc

File tree

3 files changed

+43
-0
lines changed

3 files changed

+43
-0
lines changed

ddtrace/appsec/_iast/_patches/json_tainting.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def patch():
3232
if not set_and_check_module_is_patched("json", default_attr=_DEFAULT_ATTR):
3333
return
3434
try_wrap_function_wrapper("json", "loads", wrapped_loads)
35+
try_wrap_function_wrapper("json.encoder", "JSONEncoder.default", patched_json_encoder_default)
3536

3637

3738
def wrapped_loads(wrapped, instance, args, kwargs):
@@ -60,3 +61,10 @@ def wrapped_loads(wrapped, instance, args, kwargs):
6061
log.debug("Unexpected exception while reporting vulnerability", exc_info=True)
6162
raise
6263
return obj
64+
65+
66+
def patched_json_encoder_default(original_func, instance, args, kwargs):
67+
if isinstance(args[0], (LazyTaintList, LazyTaintDict)):
68+
return args[0]._obj
69+
70+
return original_func(*args, **kwargs)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
fixes:
3+
- |
4+
IAST: This fix resolves an issue where JSON encoder would throw an exception while encoding a tainted dict or list.

tests/appsec/iast/test_taint_utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@
44

55
try:
66
from ddtrace.appsec._iast import oce
7+
from ddtrace.appsec._iast._patch_modules import patch_iast
78
from ddtrace.appsec._iast._taint_tracking import OriginType
89
from ddtrace.appsec._iast._taint_tracking import create_context
910
from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted
1011
from ddtrace.appsec._iast._taint_tracking import taint_pyobject
1112
from ddtrace.appsec._iast._taint_utils import LazyTaintDict
13+
from ddtrace.appsec._iast._taint_utils import LazyTaintList
1214
from ddtrace.appsec._iast._taint_utils import check_tainted_args
1315
except (ImportError, AttributeError):
1416
pytest.skip("IAST not supported for this Python version", allow_module_level=True)
1517

1618

1719
def setup():
20+
patch_iast()
1821
create_context()
1922
oce._enabled = True
2023

@@ -234,3 +237,31 @@ def test_checked_tainted_args():
234237
assert check_tainted_args(
235238
args=(tainted_arg, untainted_arg), kwargs=None, tracer=None, integration_name="psycopg", method=cursor.execute
236239
)
240+
241+
242+
def test_json_encode_dict():
243+
import json
244+
245+
tainted_dict = LazyTaintDict(
246+
{
247+
"tr_key_001": ["tr_val_001", "tr_val_002", "tr_val_003", {"tr_key_005": "tr_val_004"}],
248+
"tr_key_002": {"tr_key_003": {"tr_key_004": "tr_val_005"}},
249+
},
250+
origins=(OriginType.PARAMETER, OriginType.PARAMETER),
251+
)
252+
253+
assert json.dumps(tainted_dict) == (
254+
'{"tr_key_001": ["tr_val_001", "tr_val_002", "tr_val_003", '
255+
'{"tr_key_005": "tr_val_004"}], "tr_key_002": {"tr_key_003": {"tr_key_004": "tr_val_005"}}}'
256+
)
257+
258+
259+
def test_json_encode_list():
260+
import json
261+
262+
tainted_list = LazyTaintList(
263+
["tr_val_001", "tr_val_002", "tr_val_003", {"tr_key_005": "tr_val_004"}],
264+
origins=(OriginType.PARAMETER, OriginType.PARAMETER),
265+
)
266+
267+
assert json.dumps(tainted_list) == '["tr_val_001", "tr_val_002", "tr_val_003", {"tr_key_005": "tr_val_004"}]'

0 commit comments

Comments
 (0)