Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
*.manifest
*.spec

# Testing
.pytest_cache/
.coverage
.coverage.*
htmlcov/
coverage.xml
*.cover
*.py,cover
.hypothesis/
.tox/
nosetests.xml
test-results/
junit.xml

# Virtual environments
venv/
ENV/
env/
.venv/
.env

# IDEs
.vscode/
.idea/
*.swp
*.swo
*~
.project
.pydevproject
.settings/
*.sublime-project
*.sublime-workspace

# OS files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Claude
.claude/*

# Logs
*.log
logs/

# Temporary files
tmp/
temp/
*.tmp
*.temp

# Security
*.key
*.pem
*.cert
*.crt

# Cache
.cache/
*.cache

# Build artifacts
*.pyc
*.pyo
*.pyd
.pytype/

# Documentation builds
docs/_build/
docs/_static/
docs/_templates/
516 changes: 516 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

82 changes: 82 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
[tool.poetry]
name = "shiroscan"
version = "0.1.0"
description = "Apache Shiro deserialization vulnerability scanner and exploitation tool"
authors = ["Your Name <you@example.com>"]
readme = "README.md"
packages = [{include = "moule"}]

[tool.poetry.dependencies]
python = "^3.8"
requests = "^2.28.0"
threadpool = "^1.3.2"
pycryptodome = "^3.17.0"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.12.0"

[tool.poetry.scripts]
test = "pytest:main"
tests = "pytest:main"

[tool.pytest.ini_options]
minversion = "6.0"
addopts = [
"-ra",
"--strict-markers",
"--strict-config",
"--cov=moule",
"--cov-branch",
"--cov-report=term-missing:skip-covered",
"--cov-report=html",
"--cov-report=xml",
"--cov-fail-under=15",
]
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"
markers = [
"unit: marks tests as unit tests (fast)",
"integration: marks tests as integration tests (slower)",
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
]

[tool.coverage.run]
source = ["moule"]
branch = true
omit = [
"*/tests/*",
"*/__init__.py",
"*/setup.py",
"moule/plugins/*",
"*/ysoserial.jar",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if __name__ == .__main__.:",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if False:",
"pass",
]
precision = 2
show_missing = true
skip_covered = false

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
136 changes: 136 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import os
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, MagicMock
import pytest


@pytest.fixture
def temp_dir():
"""Create a temporary directory for testing."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path, ignore_errors=True)


@pytest.fixture
def temp_file(temp_dir):
"""Create a temporary file for testing."""
def _create_temp_file(name="test_file.txt", content=""):
file_path = temp_dir / name
file_path.write_text(content)
return file_path
return _create_temp_file


@pytest.fixture
def mock_requests(mocker):
"""Mock the requests library."""
mock = mocker.patch('requests.get')
mock.return_value.status_code = 200
mock.return_value.text = "Mock response"
mock.return_value.content = b"Mock response"
mock.return_value.headers = {'Content-Type': 'text/html'}
return mock


@pytest.fixture
def mock_config():
"""Provide a mock configuration for testing."""
return {
'target_url': 'http://example.com',
'timeout': 10,
'threads': 5,
'key_file': 'key.log',
'dns_log': 'test.dnslog.cn',
'cookie': 'rememberMe=test',
'mode': 'cbc',
}


@pytest.fixture
def mock_shiro_response():
"""Mock a typical Shiro response with rememberMe cookie."""
response = Mock()
response.status_code = 200
response.headers = {
'Set-Cookie': 'rememberMe=deleteMe; Path=/; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:00 GMT'
}
response.text = "<html><body>Test response</body></html>"
return response


@pytest.fixture
def mock_key_list():
"""Provide a mock list of encryption keys for testing."""
return [
"kPH+bIxk5D2deZiIxcaaaA==",
"4AvVhmFLUs0KTA3Kprsdag==",
"Z3VucwAAAAAAAAAAAAAAAA==",
"fCq+/xW488hMTCD+cmJ3aQ==",
"1QWLxg+NYmxraMoxAXu/Iw=="
]


@pytest.fixture
def mock_plugin():
"""Create a mock plugin for testing."""
plugin = MagicMock()
plugin.poc.return_value = "Mock payload"
plugin.__name__ = "MockPlugin"
return plugin


@pytest.fixture
def sample_base64_payload():
"""Provide a sample base64 encoded payload."""
return "rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA=="


@pytest.fixture
def mock_subprocess_run(mocker):
"""Mock subprocess.run for testing command execution."""
mock = mocker.patch('subprocess.run')
mock.return_value.returncode = 0
mock.return_value.stdout = "Mock command output"
mock.return_value.stderr = ""
return mock


@pytest.fixture
def mock_threading(mocker):
"""Mock threading for testing concurrent operations."""
mock_pool = mocker.patch('threadpool.ThreadPool')
mock_pool.return_value.putRequest.return_value = None
mock_pool.return_value.wait.return_value = None
return mock_pool


@pytest.fixture(autouse=True)
def reset_environment():
"""Reset environment variables before each test."""
original_env = os.environ.copy()
yield
os.environ.clear()
os.environ.update(original_env)


@pytest.fixture
def capture_stdout(monkeypatch):
"""Capture stdout for testing print statements."""
from io import StringIO
captured_output = StringIO()
monkeypatch.setattr('sys.stdout', captured_output)
return captured_output


@pytest.fixture
def mock_dns_log(mocker):
"""Mock DNS log functionality."""
mock = mocker.patch('requests.get')
mock.return_value.json.return_value = {
'status': 'success',
'data': ['test.example.com']
}
return mock
Empty file added tests/integration/__init__.py
Empty file.
Loading