-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_poc_vulnerability.py
More file actions
524 lines (367 loc) · 18.3 KB
/
Copy pathtest_poc_vulnerability.py
File metadata and controls
524 lines (367 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
"""
Test suite for CodeSafe security vulnerabilities.
Tests the fix for the sandbox escape vulnerability and other potential attack vectors.
"""
import unittest
import os
from pathlib import Path
from codesafe import safe_eval, UnsafeExpressionError
# Top-level helper function for multiprocessing pickling compatibility on Windows
def custom_func(x):
return x * 2
class TestVulnerabilityFixes(unittest.TestCase):
"""Test cases for security vulnerabilities and their fixes."""
def setUp(self):
"""Set up test fixtures."""
self.marker_file = Path(__file__).parent / "codesafe_rce_marker.txt"
if self.marker_file.exists():
self.marker_file.unlink()
def tearDown(self):
"""Clean up test files."""
if self.marker_file.exists():
self.marker_file.unlink()
# ==================== MAIN VULNERABILITY TESTS ====================
def test_poc_direct_exec_blocked(self):
"""Test that direct exec() calls are blocked (negative control)."""
inner_code = (
"from pathlib import Path\n"
f"Path({str(self.marker_file)!r}).write_text("
"'codesafe-rce', encoding='utf-8')"
)
payload = f"exec({inner_code!r})"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
self.assertFalse(self.marker_file.exists())
print("[PASS] Direct exec is correctly blocked")
def test_poc_subscript_bypass_fixed(self):
"""Test the main PoC: __builtins__['exec']() bypass is now fixed."""
inner_code = (
"from pathlib import Path\n"
f"Path({str(self.marker_file)!r}).write_text("
"'codesafe-rce', encoding='utf-8')"
)
payload = f"__builtins__['exec']({inner_code!r})"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
self.assertFalse(self.marker_file.exists())
print("[PASS] Subscript bypass (__builtins__['exec']) is now blocked")
def test_poc_getattr_exec_blocked(self):
"""Test getattr(__builtins__, 'exec') bypass is blocked."""
inner_code = (
"from pathlib import Path\n"
f"Path({str(self.marker_file)!r}).write_text('getattr-rce')"
)
payload = f"getattr(__builtins__, 'exec')({inner_code!r})"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
self.assertFalse(self.marker_file.exists())
print("[PASS] getattr exec bypass is blocked")
def test_poc_eval_via_builtins_blocked(self):
"""Test __builtins__['eval']() is blocked."""
payload = "__builtins__['eval']('1 + 1')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] __builtins__['eval'] is blocked")
def test_poc_compile_via_builtins_blocked(self):
"""Test __builtins__['compile']() is blocked."""
payload = "__builtins__['compile']('1+1', '<string>', 'eval')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] __builtins__['compile'] is blocked")
def test_poc_import_via_builtins_blocked(self):
"""Test __builtins__['__import__']() is blocked."""
payload = "__builtins__['__import__']('os')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] __builtins__['__import__'] is blocked")
# ==================== INDIRECT CALL BYPASS TESTS ====================
def test_lambda_call_bypass_blocked(self):
"""Test lambda-based call bypass is blocked."""
payload = "(lambda: __import__('os'))()"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Lambda call bypass is blocked")
def test_nested_subscript_bypass_blocked(self):
"""Test nested subscript access is blocked."""
payload = "__builtins__['dict']['keys']()"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Nested subscript bypass is blocked")
def test_attribute_then_call_blocked(self):
"""Test attribute access followed by call is blocked."""
payload = "__builtins__.get('exec')('1+1')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Attribute-then-call bypass is blocked")
def test_list_comprehension_call_blocked(self):
"""Test list comprehension with function call is blocked."""
payload = "[__builtins__['exec']('1') for x in [1]]"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] List comprehension call bypass is blocked")
def test_generator_expression_call_blocked(self):
"""Test generator expression with function call is blocked."""
payload = "list(__builtins__['exec']('1') for x in [1])"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Generator expression call bypass is blocked")
# ==================== DUNDER METHOD ATTACKS ====================
def test_class_dunder_access_blocked(self):
"""Test __class__ attribute access is blocked."""
payload = "''.__class__"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, allow_attributes=True)
print("[PASS] __class__ access is blocked")
def test_bases_dunder_access_blocked(self):
"""Test __bases__ attribute access is blocked."""
payload = "().__class__.__bases__"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, allow_attributes=True)
print("[PASS] __bases__ access is blocked")
def test_subclasses_dunder_access_blocked(self):
"""Test __subclasses__ attribute access is blocked."""
payload = "''.__class__.__mro__[1].__subclasses__()"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, allow_attributes=True)
print("[PASS] __subclasses__ access is blocked")
def test_globals_dunder_access_blocked(self):
"""Test __globals__ attribute access is blocked."""
payload = "(lambda: None).__globals__"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, allow_attributes=True)
print("[PASS] __globals__ access is blocked")
def test_closure_dunder_access_blocked(self):
"""Test __closure__ attribute access is blocked."""
payload = "(lambda: None).__closure__"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, allow_attributes=True)
print("[PASS] __closure__ access is blocked")
def test_code_dunder_access_blocked(self):
"""Test __code__ attribute access is blocked."""
payload = "(lambda: None).__code__"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, allow_attributes=True)
print("[PASS] __code__ access is blocked")
# ==================== IMPORT AND MODULE ACCESS TESTS ====================
def test_direct_import_blocked(self):
"""Test direct import statements are blocked."""
payload = "import os"
try:
safe_eval(payload)
self.fail("Should have raised SyntaxError")
except SyntaxError:
pass
print("[PASS] Direct import is blocked (syntax error)")
def test_from_import_blocked(self):
"""Test from-import statements are blocked."""
payload = "from os import system"
try:
safe_eval(payload)
self.fail("Should have raised SyntaxError")
except SyntaxError:
pass
print("[PASS] From-import is blocked (syntax error)")
def test_os_system_via_builtins_blocked(self):
"""Test accessing os.system via builtins is blocked."""
payload = "__builtins__['__import__']('os').system('echo pwned')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] os.system via __import__ is blocked")
def test_subprocess_call_blocked(self):
"""Test subprocess module access is blocked."""
payload = "__builtins__['__import__']('subprocess').call(['ls'])"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] subprocess.call is blocked")
# ==================== FILE ACCESS TESTS ====================
def test_open_function_blocked_by_default(self):
"""Test open() is blocked when file_access=False."""
payload = "open('/etc/passwd').read()"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, file_access=False)
print("[PASS] open() is blocked by default")
def test_pathlib_write_blocked(self):
"""Test pathlib.Path write operations are blocked."""
marker = Path(__file__).parent / "pathlib_test.txt"
if marker.exists():
marker.unlink()
payload = f"from pathlib import Path; Path({str(marker)!r}).write_text('test')"
try:
safe_eval(payload)
self.fail("Should have raised SyntaxError")
except SyntaxError:
pass
print("[PASS] pathlib write is blocked (syntax error)")
def test_io_module_blocked(self):
"""Test io module access is blocked."""
payload = "__builtins__['__import__']('io').open('/tmp/test')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] io.open is blocked")
# ==================== NETWORK ACCESS TESTS ====================
def test_socket_module_blocked(self):
"""Test socket module access is blocked."""
payload = "__builtins__['__import__']('socket').socket()"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] socket module is blocked")
def test_requests_module_blocked(self):
"""Test requests module access is blocked."""
payload = "__builtins__['__import__']('requests').get('http://example.com')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] requests module is blocked")
def test_urllib_module_blocked(self):
"""Test urllib module access is blocked."""
payload = "__builtins__['__import__']('urllib.request').urlopen('http://example.com')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] urllib module is blocked")
# ==================== CODE EXECUTION PRIMITIVES ====================
def test_exec_function_blocked(self):
"""Test exec() function call is blocked."""
payload = "exec('print(1)')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] exec() is blocked")
def test_eval_function_blocked(self):
"""Test eval() function call is blocked."""
payload = "eval('1+1')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] eval() is blocked")
def test_compile_function_blocked(self):
"""Test compile() function call is blocked."""
payload = "compile('1+1', '<string>', 'eval')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] compile() is blocked")
def test_input_function_behavior(self):
"""Test input() behavior (should be blocked as it's not in safe builtins)."""
payload = "input()"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] input() is blocked")
# ==================== METACLASS AND TYPE ATTACKS ====================
def test_type_subclasses_blocked(self):
"""Test type().subclasses() attack is blocked."""
payload = "type('')[0].__subclasses__()"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, allow_attributes=True)
print("[PASS] type subclasses attack is blocked")
def test_object_subclasses_blocked(self):
"""Test object.__subclasses__() attack is blocked."""
payload = "object().__subclasses__()"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, allow_attributes=True)
print("[PASS] object.__subclasses__ is blocked")
def test_mro_access_blocked(self):
"""Test __mro__ access is blocked."""
payload = "''.__class__.__mro__"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload, allow_attributes=True)
print("[PASS] __mro__ access is blocked")
# ==================== BUILTIN MODIFICATION TESTS ====================
def test_builtins_dict_modification_blocked(self):
"""Test modifying __builtins__ dict is blocked."""
payload = "__builtins__['malicious'] = lambda: 'pwned'"
try:
safe_eval(payload)
self.fail("Should have raised SyntaxError")
except SyntaxError:
pass
print("[PASS] __builtins__ modification is blocked (syntax error)")
def test_builtins_update_blocked(self):
"""Test __builtins__.update() is blocked."""
payload = "__builtins__.update({'evil': eval})"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] __builtins__.update() is blocked")
# ==================== SAFE OPERATIONS STILL WORK ====================
def test_safe_arithmetic_works(self):
"""Test that safe arithmetic operations still work."""
result = safe_eval("2 + 2 * 3")
self.assertEqual(result, 8)
print("[PASS] Safe arithmetic works")
def test_safe_string_operations_work(self):
"""Test that safe string operations still work."""
result = safe_eval("'hello'.upper()", allow_attributes=True)
self.assertEqual(result, "HELLO")
print("[PASS] Safe string operations work")
def test_safe_list_operations_work(self):
"""Test that safe list operations still work."""
result = safe_eval("[1, 2, 3][1]")
self.assertEqual(result, 2)
print("[PASS] Safe list operations work")
def test_safe_dict_operations_work(self):
"""Test that safe dict operations still work."""
result = safe_eval("{'a': 1}['a']")
self.assertEqual(result, 1)
print("[PASS] Safe dict operations work")
def test_safe_builtin_functions_work(self):
"""Test that safe builtin functions still work."""
result = safe_eval("max([1, 5, 3])")
self.assertEqual(result, 5)
result = safe_eval("abs(-5)")
self.assertEqual(result, 5)
result = safe_eval("len([1, 2, 3])")
self.assertEqual(result, 3)
print("[PASS] Safe builtin functions work")
def test_allowed_function_calls_work(self):
"""Test that explicitly allowed function calls work."""
result = safe_eval(
"custom_func(5)",
allowed_vars={"custom_func": custom_func},
allowed_function_calls=["custom_func"]
)
self.assertEqual(result, 10)
print("[PASS] Allowed function calls work")
class TestEdgeCases(unittest.TestCase):
"""Test edge cases and complex attack vectors."""
def test_multiple_indirect_lookups(self):
"""Test multiple levels of indirect lookup."""
payload = "__builtins__['dict']()"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Multiple indirect lookups blocked")
def test_tuple_unpacking_call_blocked(self):
"""Test tuple unpacking with call is blocked."""
payload = "(*[__builtins__['exec']('1')],)"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Tuple unpacking call blocked")
def test_dict_comprehension_call_blocked(self):
"""Test dict comprehension with call is blocked."""
payload = "{x: __builtins__['exec']('1') for x in [1]}"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Dict comprehension call blocked")
def test_set_comprehension_call_blocked(self):
"""Test set comprehension with call is blocked."""
payload = "{__builtins__['exec']('1') for x in [1]}"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Set comprehension call blocked")
def test_ternary_with_call_blocked(self):
"""Test ternary operator with call is blocked."""
payload = "__builtins__['exec']('1') if True else None"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Ternary with call blocked")
def test_and_or_shortcircuit_call_blocked(self):
"""Test short-circuit evaluation with call is blocked."""
payload = "True and __builtins__['exec']('1')"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Short-circuit call blocked")
def test_walrus_operator_call_blocked(self):
"""Test walrus operator with call is blocked."""
try:
payload = "(x := __builtins__['exec']('1'))"
with self.assertRaises(UnsafeExpressionError):
safe_eval(payload)
print("[PASS] Walrus operator call blocked")
except SyntaxError:
print("[SKIP] Walrus operator not available in this Python version")
if __name__ == "__main__":
unittest.main(verbosity=2)