Skip to content

Commit 52a019e

Browse files
fix(iast): patch json.encoder to encode LazyTaintDict as dict [backport 1.x] [backport 1.18] (#7360)
Backport 0f49c37 from #7357 to 1.18. Backport fee5ed0 from #7348 to 1.x. 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 42c288b commit 52a019e

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
@@ -26,6 +26,7 @@ def patch():
2626
if not set_and_check_module_is_patched("json", default_attr=_DEFAULT_ATTR):
2727
return
2828
try_wrap_function_wrapper("json", "loads", wrapped_loads)
29+
try_wrap_function_wrapper("json.encoder", "JSONEncoder.default", patched_json_encoder_default)
2930

3031

3132
def wrapped_loads(wrapped, instance, args, kwargs):
@@ -54,3 +55,10 @@ def wrapped_loads(wrapped, instance, args, kwargs):
5455
log.debug("Unexpected exception while reporting vulnerability", exc_info=True)
5556
raise
5657
return obj
58+
59+
60+
def patched_json_encoder_default(original_func, instance, args, kwargs):
61+
if isinstance(args[0], (LazyTaintList, LazyTaintDict)):
62+
return args[0]._obj
63+
64+
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
@@ -5,18 +5,21 @@
55

66
try:
77
from ddtrace.appsec.iast import oce
8+
from ddtrace.appsec.iast._patch_modules import patch_iast
89
from ddtrace.appsec.iast._taint_tracking import OriginType
910
from ddtrace.appsec.iast._taint_tracking import create_context
1011
from ddtrace.appsec.iast._taint_tracking import is_pyobject_tainted
1112
from ddtrace.appsec.iast._taint_tracking import setup as taint_tracking_setup
1213
from ddtrace.appsec.iast._taint_tracking import taint_pyobject
1314
from ddtrace.appsec.iast._taint_utils import LazyTaintDict
15+
from ddtrace.appsec.iast._taint_utils import LazyTaintList
1416
from ddtrace.appsec.iast._taint_utils import check_tainted_args
1517
except (ImportError, AttributeError):
1618
pytest.skip("IAST not supported for this Python version", allow_module_level=True)
1719

1820

1921
def setup():
22+
patch_iast()
2023
create_context()
2124
taint_tracking_setup(bytes.join, bytearray.join)
2225
oce._enabled = True
@@ -237,3 +240,31 @@ def test_checked_tainted_args():
237240
assert check_tainted_args(
238241
args=(tainted_arg, untainted_arg), kwargs=None, tracer=None, integration_name="psycopg", method=cursor.execute
239242
)
243+
244+
245+
def test_json_encode_dict():
246+
import json
247+
248+
tainted_dict = LazyTaintDict(
249+
{
250+
"tr_key_001": ["tr_val_001", "tr_val_002", "tr_val_003", {"tr_key_005": "tr_val_004"}],
251+
"tr_key_002": {"tr_key_003": {"tr_key_004": "tr_val_005"}},
252+
},
253+
origins=(OriginType.PARAMETER, OriginType.PARAMETER),
254+
)
255+
256+
assert json.dumps(tainted_dict) == (
257+
'{"tr_key_001": ["tr_val_001", "tr_val_002", "tr_val_003", '
258+
'{"tr_key_005": "tr_val_004"}], "tr_key_002": {"tr_key_003": {"tr_key_004": "tr_val_005"}}}'
259+
)
260+
261+
262+
def test_json_encode_list():
263+
import json
264+
265+
tainted_list = LazyTaintList(
266+
["tr_val_001", "tr_val_002", "tr_val_003", {"tr_key_005": "tr_val_004"}],
267+
origins=(OriginType.PARAMETER, OriginType.PARAMETER),
268+
)
269+
270+
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)