sysvex 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sysvex-0.1.0/LICENSE +21 -0
- sysvex-0.1.0/MANIFEST.in +11 -0
- sysvex-0.1.0/PKG-INFO +27 -0
- sysvex-0.1.0/pyproject.toml +40 -0
- sysvex-0.1.0/setup.cfg +4 -0
- sysvex-0.1.0/src/sysvex/__init__.py +0 -0
- sysvex-0.1.0/src/sysvex/cli.py +21 -0
- sysvex-0.1.0/src/sysvex/engine/__init__.py +0 -0
- sysvex-0.1.0/src/sysvex/engine/loader.py +10 -0
- sysvex-0.1.0/src/sysvex/engine/models.py +13 -0
- sysvex-0.1.0/src/sysvex/engine/runner.py +11 -0
- sysvex-0.1.0/src/sysvex/modules/__init__.py +0 -0
- sysvex-0.1.0/src/sysvex/modules/base.py +5 -0
- sysvex-0.1.0/src/sysvex/modules/filesystem.py +155 -0
- sysvex-0.1.0/src/sysvex/modules/network.py +115 -0
- sysvex-0.1.0/src/sysvex/modules/processes.py +165 -0
- sysvex-0.1.0/src/sysvex/reporting/console.py +11 -0
- sysvex-0.1.0/src/sysvex/reporting/json_report.py +5 -0
- sysvex-0.1.0/src/sysvex/utils/__init__.py +0 -0
- sysvex-0.1.0/src/sysvex/utils/platform.py +104 -0
- sysvex-0.1.0/src/sysvex/utils/system.py +5 -0
- sysvex-0.1.0/src/sysvex.egg-info/PKG-INFO +27 -0
- sysvex-0.1.0/src/sysvex.egg-info/SOURCES.txt +25 -0
- sysvex-0.1.0/src/sysvex.egg-info/dependency_links.txt +1 -0
- sysvex-0.1.0/src/sysvex.egg-info/entry_points.txt +2 -0
- sysvex-0.1.0/src/sysvex.egg-info/requires.txt +1 -0
- sysvex-0.1.0/src/sysvex.egg-info/top_level.txt +1 -0
sysvex-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 PuRiToX
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sysvex-0.1.0/MANIFEST.in
ADDED
sysvex-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sysvex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modular system security auditing toolkit
|
|
5
|
+
Author: PuRIToX
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/PuRiToX/sysvex
|
|
8
|
+
Project-URL: Repository, https://github.com/PuRiToX/sysvex
|
|
9
|
+
Project-URL: Documentation, https://github.com/PuRiToX/sysvex#readme
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/PuRiToX/sysvex/issues
|
|
11
|
+
Keywords: security,auditing,system,forensics,cross-platform
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Topic :: System :: Systems Administration
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: psutil
|
|
27
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sysvex"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Modular system security auditing toolkit"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "PuRIToX" }
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"psutil"
|
|
13
|
+
]
|
|
14
|
+
keywords = ["security", "auditing", "system", "forensics", "cross-platform"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: System Administrators",
|
|
18
|
+
"Intended Audience :: Information Technology",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Topic :: Security",
|
|
25
|
+
"Topic :: System :: Systems Administration",
|
|
26
|
+
"Topic :: System :: Monitoring"
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/PuRiToX/sysvex"
|
|
31
|
+
Repository = "https://github.com/PuRiToX/sysvex"
|
|
32
|
+
Documentation = "https://github.com/PuRiToX/sysvex#readme"
|
|
33
|
+
"Bug Tracker" = "https://github.com/PuRiToX/sysvex/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
sysvex = "sysvex.cli:main"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["setuptools", "wheel"]
|
|
40
|
+
build-backend = "setuptools.build_meta"
|
sysvex-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from sysvex.engine.loader import load_modules
|
|
3
|
+
from sysvex.engine.runner import run_modules
|
|
4
|
+
from sysvex.reporting.console import print_report
|
|
5
|
+
|
|
6
|
+
DEFAULT_MODULES = ["filesystem", "network", "processes"]
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(description="Sysvex Security Auditor")
|
|
10
|
+
parser.add_argument("--modules", help="Comma-separated modules")
|
|
11
|
+
|
|
12
|
+
args = parser.parse_args()
|
|
13
|
+
|
|
14
|
+
module_names = (
|
|
15
|
+
args.modules.split(",") if args.modules else DEFAULT_MODULES
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
modules = load_modules(module_names)
|
|
19
|
+
findings = run_modules(modules)
|
|
20
|
+
|
|
21
|
+
print_report(findings)
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Finding:
|
|
2
|
+
def __init__(self, finding_id, title, severity, description,
|
|
3
|
+
evidence=None, recommendation=None, source_module=None):
|
|
4
|
+
self.id = finding_id
|
|
5
|
+
self.title = title
|
|
6
|
+
self.severity = severity
|
|
7
|
+
self.description = description
|
|
8
|
+
self.evidence = evidence
|
|
9
|
+
self.recommendation = recommendation
|
|
10
|
+
self.source_module = source_module
|
|
11
|
+
|
|
12
|
+
def to_dict(self):
|
|
13
|
+
return self.__dict__
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
def run_modules(modules, context=None):
|
|
2
|
+
findings = []
|
|
3
|
+
|
|
4
|
+
for module in modules:
|
|
5
|
+
try:
|
|
6
|
+
results = module.run(context)
|
|
7
|
+
findings.extend(results)
|
|
8
|
+
except (NotImplementedError, AttributeError, ValueError, RuntimeError) as e:
|
|
9
|
+
print(f"[ERROR] Module {module.name} failed: {e}")
|
|
10
|
+
|
|
11
|
+
return findings
|
|
File without changes
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import stat
|
|
4
|
+
from sysvex.engine.models import Finding
|
|
5
|
+
from .base import BaseModule
|
|
6
|
+
from sysvex.utils.platform import get_platform_config, get_default_scan_path, is_windows
|
|
7
|
+
|
|
8
|
+
HIDDEN_FILE_DAYS = 7
|
|
9
|
+
|
|
10
|
+
class Module(BaseModule):
|
|
11
|
+
name = "filesystem"
|
|
12
|
+
|
|
13
|
+
def run(self, context=None):
|
|
14
|
+
config = get_platform_config()
|
|
15
|
+
scan_path = context.get('scan_path', get_default_scan_path()) if context else get_default_scan_path()
|
|
16
|
+
findings = []
|
|
17
|
+
|
|
18
|
+
# Scan for sensitive file permissions
|
|
19
|
+
for sensitive_path in config['sensitive_paths']:
|
|
20
|
+
if os.path.exists(sensitive_path):
|
|
21
|
+
try:
|
|
22
|
+
file_stat = os.stat(sensitive_path)
|
|
23
|
+
mode = file_stat.st_mode
|
|
24
|
+
|
|
25
|
+
# Check for world-readable sensitive files (Unix only)
|
|
26
|
+
if not is_windows():
|
|
27
|
+
if mode & stat.S_IROTH:
|
|
28
|
+
findings.append(Finding(
|
|
29
|
+
finding_id="FS-004",
|
|
30
|
+
title="World-readable sensitive file",
|
|
31
|
+
severity="HIGH",
|
|
32
|
+
description=f"Sensitive file {sensitive_path} is readable by others",
|
|
33
|
+
evidence=sensitive_path,
|
|
34
|
+
recommendation="Restrict permissions: chmod o-r {sensitive_path}",
|
|
35
|
+
source_module=self.name
|
|
36
|
+
))
|
|
37
|
+
|
|
38
|
+
# Check for world-writable sensitive files (Unix only)
|
|
39
|
+
if mode & stat.S_IWOTH:
|
|
40
|
+
findings.append(Finding(
|
|
41
|
+
finding_id="FS-005",
|
|
42
|
+
title="World-writable sensitive file",
|
|
43
|
+
severity="CRITICAL",
|
|
44
|
+
description=f"Sensitive file {sensitive_path} is writable by others",
|
|
45
|
+
evidence=sensitive_path,
|
|
46
|
+
recommendation="Restrict permissions: chmod o-w {sensitive_path}",
|
|
47
|
+
source_module=self.name
|
|
48
|
+
))
|
|
49
|
+
else:
|
|
50
|
+
# Windows: Check if sensitive file has weak permissions
|
|
51
|
+
# This is a simplified check - in reality, Windows ACLs are more complex
|
|
52
|
+
try:
|
|
53
|
+
# Try to read the file - if we can, it might be too permissive
|
|
54
|
+
with open(sensitive_path, 'r', encoding='utf-8') as f:
|
|
55
|
+
f.read(1) # Try to read first byte
|
|
56
|
+
findings.append(Finding(
|
|
57
|
+
finding_id="FS-004",
|
|
58
|
+
title="Sensitive file with potentially weak permissions",
|
|
59
|
+
severity="MEDIUM",
|
|
60
|
+
description=f"Sensitive file {sensitive_path} may have overly permissive access",
|
|
61
|
+
evidence=sensitive_path,
|
|
62
|
+
recommendation="Review file permissions and ACLs",
|
|
63
|
+
source_module=self.name
|
|
64
|
+
))
|
|
65
|
+
except (PermissionError, OSError):
|
|
66
|
+
# Good - file is properly protected
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
except (OSError, PermissionError, FileNotFoundError):
|
|
70
|
+
continue
|
|
71
|
+
|
|
72
|
+
for root, _, files in os.walk(scan_path):
|
|
73
|
+
for f in files:
|
|
74
|
+
path = os.path.join(root, f)
|
|
75
|
+
try:
|
|
76
|
+
file_stat = os.stat(path)
|
|
77
|
+
mode = file_stat.st_mode
|
|
78
|
+
|
|
79
|
+
# World-writable files (Unix only)
|
|
80
|
+
if not is_windows():
|
|
81
|
+
if mode & stat.S_IWOTH:
|
|
82
|
+
findings.append(Finding(
|
|
83
|
+
finding_id="FS-001",
|
|
84
|
+
title="World-writable file",
|
|
85
|
+
severity="HIGH",
|
|
86
|
+
description="File is writable by others",
|
|
87
|
+
evidence=path,
|
|
88
|
+
recommendation="Restrict permissions: chmod o-w {path}",
|
|
89
|
+
source_module=self.name
|
|
90
|
+
))
|
|
91
|
+
|
|
92
|
+
# SUID/SGID files (Unix only)
|
|
93
|
+
if mode & stat.S_ISUID:
|
|
94
|
+
findings.append(Finding(
|
|
95
|
+
finding_id="FS-006",
|
|
96
|
+
title="SUID executable",
|
|
97
|
+
severity="HIGH",
|
|
98
|
+
description="File has SUID bit set - runs with owner privileges",
|
|
99
|
+
evidence=path,
|
|
100
|
+
recommendation="Verify SUID bit is necessary and file is secure",
|
|
101
|
+
source_module=self.name
|
|
102
|
+
))
|
|
103
|
+
|
|
104
|
+
if mode & stat.S_ISGID:
|
|
105
|
+
findings.append(Finding(
|
|
106
|
+
finding_id="FS-007",
|
|
107
|
+
title="SGID executable",
|
|
108
|
+
severity="MEDIUM",
|
|
109
|
+
description="File has SGID bit set - runs with group privileges",
|
|
110
|
+
evidence=path,
|
|
111
|
+
recommendation="Verify SGID bit is necessary and file is secure",
|
|
112
|
+
source_module=self.name
|
|
113
|
+
))
|
|
114
|
+
else:
|
|
115
|
+
# Windows: Check for files in suspicious locations
|
|
116
|
+
if any(path.lower().startswith(temp_dir.lower()) for temp_dir in config['temp_dirs']):
|
|
117
|
+
findings.append(Finding(
|
|
118
|
+
finding_id="FS-001",
|
|
119
|
+
title="File in temporary directory",
|
|
120
|
+
severity="MEDIUM",
|
|
121
|
+
description="File located in temporary directory",
|
|
122
|
+
evidence=path,
|
|
123
|
+
recommendation="Review file - temporary directories are common attack vectors",
|
|
124
|
+
source_module=self.name
|
|
125
|
+
))
|
|
126
|
+
|
|
127
|
+
# Hidden files (cross-platform)
|
|
128
|
+
if f.startswith(".") or (is_windows() and f.startswith("$")):
|
|
129
|
+
findings.append(Finding(
|
|
130
|
+
finding_id="FS-002",
|
|
131
|
+
title="Hidden file",
|
|
132
|
+
severity="MEDIUM",
|
|
133
|
+
description="Hidden file detected",
|
|
134
|
+
evidence=path,
|
|
135
|
+
recommendation="Review file contents",
|
|
136
|
+
source_module=self.name
|
|
137
|
+
))
|
|
138
|
+
|
|
139
|
+
# Recently modified files (cross-platform)
|
|
140
|
+
mtime = os.path.getmtime(path)
|
|
141
|
+
if (time.time() - mtime) < (HIDDEN_FILE_DAYS * 86400):
|
|
142
|
+
findings.append(Finding(
|
|
143
|
+
finding_id="FS-003",
|
|
144
|
+
title="Recently modified file",
|
|
145
|
+
severity="LOW",
|
|
146
|
+
description=f"File modified in last {HIDDEN_FILE_DAYS} days",
|
|
147
|
+
evidence=path,
|
|
148
|
+
recommendation="Check if change is expected",
|
|
149
|
+
source_module=self.name
|
|
150
|
+
))
|
|
151
|
+
|
|
152
|
+
except (OSError, PermissionError, FileNotFoundError):
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
return findings
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
import ipaddress
|
|
3
|
+
from sysvex.engine.models import Finding
|
|
4
|
+
from .base import BaseModule
|
|
5
|
+
|
|
6
|
+
# Common public service ports
|
|
7
|
+
PUBLIC_SERVICES = {
|
|
8
|
+
20: "FTP Data", 21: "FTP Control", 22: "SSH", 23: "Telnet", 25: "SMTP",
|
|
9
|
+
53: "DNS", 80: "HTTP", 110: "POP3", 143: "IMAP", 443: "HTTPS",
|
|
10
|
+
993: "IMAPS", 995: "POP3S", 3389: "RDP", 5432: "PostgreSQL", 3306: "MySQL"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
# Suspicious remote ports (commonly used in attacks)
|
|
14
|
+
SUSPICIOUS_PORTS = {
|
|
15
|
+
4444: "Metasploit", 5555: "Android Debug", 6667: "IRC", 8080: "Proxy",
|
|
16
|
+
8443: "Alternative HTTPS", 9000: "Alternative HTTP", 12345: "NetBus",
|
|
17
|
+
31337: "Back Orifice", 5900: "VNC"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class Module(BaseModule):
|
|
21
|
+
name = "network"
|
|
22
|
+
|
|
23
|
+
def run(self, context=None):
|
|
24
|
+
findings = []
|
|
25
|
+
|
|
26
|
+
# Get listening ports
|
|
27
|
+
listening_ports = {}
|
|
28
|
+
for conn in psutil.net_connections(kind='inet'):
|
|
29
|
+
if conn.status == "LISTEN" and conn.laddr:
|
|
30
|
+
listening_ports[conn.laddr.port] = conn
|
|
31
|
+
|
|
32
|
+
# Get all current network connections
|
|
33
|
+
for conn in psutil.net_connections(kind='inet'):
|
|
34
|
+
laddr = f"{conn.laddr.ip}:{conn.laddr.port}" if conn.laddr else "N/A"
|
|
35
|
+
raddr = f"{conn.raddr.ip}:{conn.raddr.port}" if conn.raddr else "N/A"
|
|
36
|
+
status = conn.status
|
|
37
|
+
|
|
38
|
+
# High severity: listening on all interfaces for public services
|
|
39
|
+
if conn.status == "LISTEN" and conn.laddr and conn.laddr.ip == "0.0.0.0":
|
|
40
|
+
service_name = PUBLIC_SERVICES.get(conn.laddr.port, "Unknown service")
|
|
41
|
+
findings.append(Finding(
|
|
42
|
+
finding_id="NET-001",
|
|
43
|
+
title="Public service listening on all interfaces",
|
|
44
|
+
severity="HIGH",
|
|
45
|
+
description=f"{service_name} listening on 0.0.0.0:{conn.laddr.port}",
|
|
46
|
+
evidence=f"Local: {laddr}, Service: {service_name}",
|
|
47
|
+
recommendation="Bind service to specific IP or firewall if not intended for public",
|
|
48
|
+
source_module=self.name
|
|
49
|
+
))
|
|
50
|
+
|
|
51
|
+
# Medium severity: unknown public services
|
|
52
|
+
if conn.status == "LISTEN" and conn.laddr and conn.laddr.port in PUBLIC_SERVICES:
|
|
53
|
+
service_name = PUBLIC_SERVICES[conn.laddr.port]
|
|
54
|
+
findings.append(Finding(
|
|
55
|
+
finding_id="NET-003",
|
|
56
|
+
title="Known public service detected",
|
|
57
|
+
severity="MEDIUM",
|
|
58
|
+
description=f"{service_name} service is running",
|
|
59
|
+
evidence=f"Local: {laddr}, Service: {service_name}",
|
|
60
|
+
recommendation="Ensure service is properly configured and secured",
|
|
61
|
+
source_module=self.name
|
|
62
|
+
))
|
|
63
|
+
|
|
64
|
+
# High severity: connections to suspicious ports
|
|
65
|
+
if conn.status == "ESTABLISHED" and conn.raddr and conn.raddr.port in SUSPICIOUS_PORTS:
|
|
66
|
+
suspicious_service = SUSPICIOUS_PORTS[conn.raddr.port]
|
|
67
|
+
findings.append(Finding(
|
|
68
|
+
finding_id="NET-004",
|
|
69
|
+
title="Connection to suspicious port",
|
|
70
|
+
severity="HIGH",
|
|
71
|
+
description=f"Connection to {suspicious_service} service on port {conn.raddr.port}",
|
|
72
|
+
evidence=f"Local: {laddr}, Remote: {raddr}, Service: {suspicious_service}",
|
|
73
|
+
recommendation="Investigate potential malicious activity",
|
|
74
|
+
source_module=self.name
|
|
75
|
+
))
|
|
76
|
+
|
|
77
|
+
# Medium severity: established connections to unknown remote addresses
|
|
78
|
+
if conn.status == "ESTABLISHED" and conn.raddr:
|
|
79
|
+
# Check if remote IP is public (not private)
|
|
80
|
+
if not self._is_private_ip(conn.raddr.ip):
|
|
81
|
+
findings.append(Finding(
|
|
82
|
+
finding_id="NET-002",
|
|
83
|
+
title="Established external connection",
|
|
84
|
+
severity="MEDIUM",
|
|
85
|
+
description="Connection established to remote host",
|
|
86
|
+
evidence=f"Local: {laddr}, Remote: {raddr}, Status: {status}",
|
|
87
|
+
recommendation="Verify connection is expected and authorized",
|
|
88
|
+
source_module=self.name
|
|
89
|
+
))
|
|
90
|
+
|
|
91
|
+
# Low severity: unusual outbound connection patterns
|
|
92
|
+
if conn.status == "ESTABLISHED" and conn.raddr and conn.raddr.port > 1024:
|
|
93
|
+
if conn.raddr.port not in PUBLIC_SERVICES and conn.raddr.port not in SUSPICIOUS_PORTS:
|
|
94
|
+
findings.append(Finding(
|
|
95
|
+
finding_id="NET-005",
|
|
96
|
+
title="Unusual outbound connection",
|
|
97
|
+
severity="LOW",
|
|
98
|
+
description=f"Connection to non-standard port {conn.raddr.port}",
|
|
99
|
+
evidence=f"Local: {laddr}, Remote: {raddr}",
|
|
100
|
+
recommendation="Monitor for potential data exfiltration",
|
|
101
|
+
source_module=self.name
|
|
102
|
+
))
|
|
103
|
+
|
|
104
|
+
return findings
|
|
105
|
+
|
|
106
|
+
def _is_private_ip(self, ip):
|
|
107
|
+
"""Check if IP address is private"""
|
|
108
|
+
try:
|
|
109
|
+
ip_obj = ipaddress.ip_address(ip)
|
|
110
|
+
return ip_obj.is_private
|
|
111
|
+
except ValueError:
|
|
112
|
+
# Fallback to basic check for common private ranges
|
|
113
|
+
return (ip.startswith('10.') or ip.startswith('192.168.') or
|
|
114
|
+
ip.startswith('172.') or ip.startswith('127.') or
|
|
115
|
+
ip.startswith('169.254.'))
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
from sysvex.engine.models import Finding
|
|
3
|
+
from .base import BaseModule
|
|
4
|
+
from sysvex.utils.platform import get_platform_config, is_windows
|
|
5
|
+
|
|
6
|
+
class Module(BaseModule):
|
|
7
|
+
name = "processes"
|
|
8
|
+
|
|
9
|
+
def run(self, context=None):
|
|
10
|
+
config = get_platform_config()
|
|
11
|
+
findings = []
|
|
12
|
+
|
|
13
|
+
for proc in psutil.process_iter(['pid', 'name', 'exe', 'cmdline', 'username', 'uids', 'gids']):
|
|
14
|
+
try:
|
|
15
|
+
proc_info = proc.info
|
|
16
|
+
|
|
17
|
+
# Skip if we can't get basic info
|
|
18
|
+
if not proc_info['name'] or not proc_info['exe']:
|
|
19
|
+
continue
|
|
20
|
+
|
|
21
|
+
# Check for unsigned/unexpected binaries
|
|
22
|
+
if self._is_suspicious_binary(proc_info['exe'], config):
|
|
23
|
+
findings.append(Finding(
|
|
24
|
+
finding_id="PROC-001",
|
|
25
|
+
title="Suspicious or unsigned binary",
|
|
26
|
+
severity="HIGH",
|
|
27
|
+
description=f"Process running from suspicious location: {proc_info['exe']}",
|
|
28
|
+
evidence=f"PID: {proc_info['pid']}, Name: {proc_info['name']}, Path: {proc_info['exe']}",
|
|
29
|
+
recommendation="Investigate binary authenticity and origin",
|
|
30
|
+
source_module=self.name
|
|
31
|
+
))
|
|
32
|
+
|
|
33
|
+
# Check for suspicious command-line patterns
|
|
34
|
+
if proc_info['cmdline']:
|
|
35
|
+
cmdline = ' '.join(proc_info['cmdline']).lower()
|
|
36
|
+
for pattern in config['suspicious_patterns']:
|
|
37
|
+
if pattern in cmdline:
|
|
38
|
+
findings.append(Finding(
|
|
39
|
+
finding_id="PROC-002",
|
|
40
|
+
title="Suspicious command-line pattern",
|
|
41
|
+
severity="HIGH",
|
|
42
|
+
description=f"Process with suspicious command line: {pattern}",
|
|
43
|
+
evidence=f"PID: {proc_info['pid']}, Command: {' '.join(proc_info['cmdline'])}",
|
|
44
|
+
recommendation="Investigate potential malicious activity",
|
|
45
|
+
source_module=self.name
|
|
46
|
+
))
|
|
47
|
+
break # Only report once per process
|
|
48
|
+
|
|
49
|
+
# Check for privilege anomalies
|
|
50
|
+
if proc_info['uids'] and proc_info['gids']:
|
|
51
|
+
real_uid, effective_uid, saved_uid = proc_info['uids']
|
|
52
|
+
real_gid, effective_gid, saved_gid = proc_info['gids']
|
|
53
|
+
|
|
54
|
+
# Process running with elevated privileges (Unix: root, Windows: admin)
|
|
55
|
+
if is_windows():
|
|
56
|
+
# Windows: Check for SYSTEM or Administrator privileges
|
|
57
|
+
if effective_uid == 0 and not self._is_system_process(proc_info['name'], config):
|
|
58
|
+
findings.append(Finding(
|
|
59
|
+
finding_id="PROC-003",
|
|
60
|
+
title="Non-system process with elevated privileges",
|
|
61
|
+
severity="MEDIUM",
|
|
62
|
+
description=f"Process {proc_info['name']} running with elevated privileges",
|
|
63
|
+
evidence=f"PID: {proc_info['pid']}, Name: {proc_info['name']}, User: {proc_info['username']}",
|
|
64
|
+
recommendation="Verify if elevated privileges are necessary",
|
|
65
|
+
source_module=self.name
|
|
66
|
+
))
|
|
67
|
+
else:
|
|
68
|
+
# Unix: Check for root privileges
|
|
69
|
+
if effective_uid == 0 and not self._is_system_process(proc_info['name'], config):
|
|
70
|
+
findings.append(Finding(
|
|
71
|
+
finding_id="PROC-003",
|
|
72
|
+
title="Non-system process running as root",
|
|
73
|
+
severity="MEDIUM",
|
|
74
|
+
description=f"Process {proc_info['name']} running with root privileges",
|
|
75
|
+
evidence=f"PID: {proc_info['pid']}, Name: {proc_info['name']}, User: {proc_info['username']}",
|
|
76
|
+
recommendation="Verify if root privileges are necessary",
|
|
77
|
+
source_module=self.name
|
|
78
|
+
))
|
|
79
|
+
|
|
80
|
+
# SetUID/SetGID anomalies
|
|
81
|
+
if real_uid != effective_uid:
|
|
82
|
+
findings.append(Finding(
|
|
83
|
+
finding_id="PROC-004",
|
|
84
|
+
title="Process with elevated user privileges",
|
|
85
|
+
severity="HIGH",
|
|
86
|
+
description=f"Process running with different effective UID than real UID",
|
|
87
|
+
evidence=f"PID: {proc_info['pid']}, Real UID: {real_uid}, Effective UID: {effective_uid}",
|
|
88
|
+
recommendation="Investigate privilege escalation",
|
|
89
|
+
source_module=self.name
|
|
90
|
+
))
|
|
91
|
+
|
|
92
|
+
if real_gid != effective_gid:
|
|
93
|
+
findings.append(Finding(
|
|
94
|
+
finding_id="PROC-005",
|
|
95
|
+
title="Process with elevated group privileges",
|
|
96
|
+
severity="MEDIUM",
|
|
97
|
+
description=f"Process running with different effective GID than real GID",
|
|
98
|
+
evidence=f"PID: {proc_info['pid']}, Real GID: {real_gid}, Effective GID: {effective_gid}",
|
|
99
|
+
recommendation="Investigate group privilege escalation",
|
|
100
|
+
source_module=self.name
|
|
101
|
+
))
|
|
102
|
+
|
|
103
|
+
# Check for processes with no executable path (potential malware)
|
|
104
|
+
if not proc_info['exe'] and proc_info['name']:
|
|
105
|
+
findings.append(Finding(
|
|
106
|
+
finding_id="PROC-006",
|
|
107
|
+
title="Process without executable path",
|
|
108
|
+
severity="HIGH",
|
|
109
|
+
description=f"Process {proc_info['name']} has no associated executable",
|
|
110
|
+
evidence=f"PID: {proc_info['pid']}, Name: {proc_info['name']}",
|
|
111
|
+
recommendation="Investigate potential memory-only malware",
|
|
112
|
+
source_module=self.name
|
|
113
|
+
))
|
|
114
|
+
|
|
115
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
return findings
|
|
119
|
+
|
|
120
|
+
def _is_suspicious_binary(self, exe_path, config):
|
|
121
|
+
"""Check if binary is in suspicious location or has suspicious characteristics"""
|
|
122
|
+
if not exe_path:
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
# Common legitimate binary locations
|
|
126
|
+
legitimate_paths = config['legitimate_paths']
|
|
127
|
+
|
|
128
|
+
# Check if binary is in legitimate location
|
|
129
|
+
is_legitimate = any(exe_path.lower().startswith(path.lower()) for path in legitimate_paths)
|
|
130
|
+
|
|
131
|
+
# Check for temporary directories
|
|
132
|
+
suspicious_paths = config['temp_dirs']
|
|
133
|
+
is_suspicious = any(exe_path.lower().startswith(temp_dir.lower()) for temp_dir in suspicious_paths)
|
|
134
|
+
|
|
135
|
+
# Check for hidden directories
|
|
136
|
+
if "/." in exe_path and not is_legitimate:
|
|
137
|
+
is_suspicious = True
|
|
138
|
+
|
|
139
|
+
# Windows-specific hidden directory check
|
|
140
|
+
if is_windows() and ("\\." in exe_path or exe_path.startswith("\\\\?\\")):
|
|
141
|
+
is_suspicious = True
|
|
142
|
+
|
|
143
|
+
return is_suspicious or not is_legitimate
|
|
144
|
+
|
|
145
|
+
def _is_system_process(self, process_name, config):
|
|
146
|
+
"""Check if process is a known system process"""
|
|
147
|
+
if not process_name:
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
# Check against whitelist
|
|
151
|
+
for legitimate in config['legitimate_processes']:
|
|
152
|
+
if legitimate.lower() in process_name.lower():
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
# Platform-specific checks
|
|
156
|
+
if is_windows():
|
|
157
|
+
# Windows system processes
|
|
158
|
+
if process_name.lower().endswith('.exe') and any(sys_proc in process_name.lower() for sys_proc in ['system', 'smss', 'csrss', 'wininit', 'services']):
|
|
159
|
+
return True
|
|
160
|
+
else:
|
|
161
|
+
# Unix kernel processes
|
|
162
|
+
if process_name.startswith('[') and process_name.endswith(']'):
|
|
163
|
+
return True
|
|
164
|
+
|
|
165
|
+
return False
|
|
File without changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import platform as _platform
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
def get_platform():
|
|
5
|
+
"""Get the current platform name"""
|
|
6
|
+
return _platform.system().lower()
|
|
7
|
+
|
|
8
|
+
def is_windows():
|
|
9
|
+
"""Check if running on Windows"""
|
|
10
|
+
return get_platform() == 'windows'
|
|
11
|
+
|
|
12
|
+
def is_linux():
|
|
13
|
+
"""Check if running on Linux"""
|
|
14
|
+
return get_platform() == 'linux'
|
|
15
|
+
|
|
16
|
+
def is_macos():
|
|
17
|
+
"""Check if running on macOS"""
|
|
18
|
+
return get_platform() == 'darwin'
|
|
19
|
+
|
|
20
|
+
def get_platform_config():
|
|
21
|
+
"""Get platform-specific configuration"""
|
|
22
|
+
if is_windows():
|
|
23
|
+
return {
|
|
24
|
+
'sensitive_paths': [
|
|
25
|
+
os.path.expandvars(r'%SystemRoot%\System32\config\SAM'),
|
|
26
|
+
os.path.expandvars(r'%SystemRoot%\System32\drivers\etc\hosts'),
|
|
27
|
+
os.path.expandvars(r'%SystemRoot%\System32\drivers\etc\networks'),
|
|
28
|
+
os.path.expandvars(r'%SystemRoot%\System32\config\SECURITY'),
|
|
29
|
+
os.path.expandvars(r'%ProgramData%\Microsoft\Network\Connections\Pbk\rasphone.pbk'),
|
|
30
|
+
],
|
|
31
|
+
'temp_dirs': [
|
|
32
|
+
os.path.expandvars(r'%TEMP%'),
|
|
33
|
+
os.path.expandvars(r'%TMP%'),
|
|
34
|
+
os.path.expandvars(r'%SystemRoot%\Temp'),
|
|
35
|
+
],
|
|
36
|
+
'legitimate_paths': [
|
|
37
|
+
os.path.expandvars(r'%SystemRoot%\System32'),
|
|
38
|
+
os.path.expandvars(r'%SystemRoot%\SysWOW64'),
|
|
39
|
+
os.path.expandvars(r'%ProgramFiles%'),
|
|
40
|
+
os.path.expandvars(r'%ProgramFiles(x86)%'),
|
|
41
|
+
os.path.expandvars(r'%ProgramData%'),
|
|
42
|
+
],
|
|
43
|
+
'suspicious_patterns': [
|
|
44
|
+
'powershell -c', 'cmd /c', 'powershell.exe -enc',
|
|
45
|
+
'rundll32.exe', 'regsvr32.exe', 'certutil.exe',
|
|
46
|
+
'bitsadmin.exe', 'wmic.exe', 'netsh.exe',
|
|
47
|
+
'schtasks.exe', 'sc.exe', 'wevtutil.exe'
|
|
48
|
+
],
|
|
49
|
+
'legitimate_processes': {
|
|
50
|
+
'svchost.exe', 'lsass.exe', 'winlogon.exe', 'explorer.exe',
|
|
51
|
+
'chrome.exe', 'firefox.exe', 'code.exe', 'python.exe',
|
|
52
|
+
'java.exe', 'node.exe', 'powershell.exe', 'cmd.exe',
|
|
53
|
+
'system', 'smss.exe', 'csrss.exe', 'wininit.exe'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else: # Linux/Unix/macOS
|
|
57
|
+
return {
|
|
58
|
+
'sensitive_paths': [
|
|
59
|
+
'/etc/passwd', '/etc/shadow', '/etc/sudoers',
|
|
60
|
+
'/etc/ssh/sshd_config', '/etc/hosts', '/etc/crontab',
|
|
61
|
+
'/etc/gshadow', '/etc/group', '/etc/protocols'
|
|
62
|
+
],
|
|
63
|
+
'temp_dirs': [
|
|
64
|
+
'/tmp', '/var/tmp', '/dev/shm', '/run/user'
|
|
65
|
+
],
|
|
66
|
+
'legitimate_paths': [
|
|
67
|
+
'/usr/bin', '/usr/sbin', '/bin', '/sbin',
|
|
68
|
+
'/usr/local/bin', '/usr/local/sbin',
|
|
69
|
+
'/opt', '/snap', '/flatpak',
|
|
70
|
+
'/lib', '/lib64', '/usr/lib', '/usr/lib64'
|
|
71
|
+
],
|
|
72
|
+
'suspicious_patterns': [
|
|
73
|
+
'nc -l', 'netcat', 'ncat', 'socat',
|
|
74
|
+
'bash -i', 'sh -i', '/bin/sh', '/bin/bash',
|
|
75
|
+
'python -c', 'perl -e', 'ruby -e',
|
|
76
|
+
'wget', 'curl', 'fetch',
|
|
77
|
+
'chmod +x', 'chmod 777',
|
|
78
|
+
'nohup', 'screen', 'tmux',
|
|
79
|
+
'iptables', 'ufw', 'firewall',
|
|
80
|
+
'crontab', 'at', 'batch',
|
|
81
|
+
'ssh-keygen', 'authorized_keys',
|
|
82
|
+
'passwd', 'shadow', '/etc/passwd'
|
|
83
|
+
],
|
|
84
|
+
'legitimate_processes': {
|
|
85
|
+
'init', 'kthreadd', 'ksoftirqd', 'migration', 'rcu_', 'watchdog',
|
|
86
|
+
'systemd', 'kmod', 'udevd', 'NetworkManager', 'gdm', 'Xorg',
|
|
87
|
+
'gnome-shell', 'firefox', 'chrome', 'chromium', 'code', 'vim',
|
|
88
|
+
'bash', 'zsh', 'fish', 'python', 'python3', 'node', 'java'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
def get_default_scan_path():
|
|
93
|
+
"""Get default scan path for the current platform"""
|
|
94
|
+
if is_windows():
|
|
95
|
+
return os.path.expandvars(r'%TEMP%')
|
|
96
|
+
else:
|
|
97
|
+
return '/tmp'
|
|
98
|
+
|
|
99
|
+
def normalize_path(path):
|
|
100
|
+
"""Normalize path for the current platform"""
|
|
101
|
+
if is_windows():
|
|
102
|
+
return os.path.normpath(path).replace('/', '\\')
|
|
103
|
+
else:
|
|
104
|
+
return os.path.normpath(path)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sysvex
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modular system security auditing toolkit
|
|
5
|
+
Author: PuRIToX
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/PuRiToX/sysvex
|
|
8
|
+
Project-URL: Repository, https://github.com/PuRiToX/sysvex
|
|
9
|
+
Project-URL: Documentation, https://github.com/PuRiToX/sysvex#readme
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/PuRiToX/sysvex/issues
|
|
11
|
+
Keywords: security,auditing,system,forensics,cross-platform
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Intended Audience :: Information Technology
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Topic :: System :: Systems Administration
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: psutil
|
|
27
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/sysvex/__init__.py
|
|
5
|
+
src/sysvex/cli.py
|
|
6
|
+
src/sysvex.egg-info/PKG-INFO
|
|
7
|
+
src/sysvex.egg-info/SOURCES.txt
|
|
8
|
+
src/sysvex.egg-info/dependency_links.txt
|
|
9
|
+
src/sysvex.egg-info/entry_points.txt
|
|
10
|
+
src/sysvex.egg-info/requires.txt
|
|
11
|
+
src/sysvex.egg-info/top_level.txt
|
|
12
|
+
src/sysvex/engine/__init__.py
|
|
13
|
+
src/sysvex/engine/loader.py
|
|
14
|
+
src/sysvex/engine/models.py
|
|
15
|
+
src/sysvex/engine/runner.py
|
|
16
|
+
src/sysvex/modules/__init__.py
|
|
17
|
+
src/sysvex/modules/base.py
|
|
18
|
+
src/sysvex/modules/filesystem.py
|
|
19
|
+
src/sysvex/modules/network.py
|
|
20
|
+
src/sysvex/modules/processes.py
|
|
21
|
+
src/sysvex/reporting/console.py
|
|
22
|
+
src/sysvex/reporting/json_report.py
|
|
23
|
+
src/sysvex/utils/__init__.py
|
|
24
|
+
src/sysvex/utils/platform.py
|
|
25
|
+
src/sysvex/utils/system.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
psutil
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sysvex
|