aiptx 2.0.7__py3-none-any.whl
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.
- aipt_v2/__init__.py +110 -0
- aipt_v2/__main__.py +24 -0
- aipt_v2/agents/AIPTxAgent/__init__.py +10 -0
- aipt_v2/agents/AIPTxAgent/aiptx_agent.py +211 -0
- aipt_v2/agents/__init__.py +46 -0
- aipt_v2/agents/base.py +520 -0
- aipt_v2/agents/exploit_agent.py +688 -0
- aipt_v2/agents/ptt.py +406 -0
- aipt_v2/agents/state.py +168 -0
- aipt_v2/app.py +957 -0
- aipt_v2/browser/__init__.py +31 -0
- aipt_v2/browser/automation.py +458 -0
- aipt_v2/browser/crawler.py +453 -0
- aipt_v2/cli.py +2933 -0
- aipt_v2/compliance/__init__.py +71 -0
- aipt_v2/compliance/compliance_report.py +449 -0
- aipt_v2/compliance/framework_mapper.py +424 -0
- aipt_v2/compliance/nist_mapping.py +345 -0
- aipt_v2/compliance/owasp_mapping.py +330 -0
- aipt_v2/compliance/pci_mapping.py +297 -0
- aipt_v2/config.py +341 -0
- aipt_v2/core/__init__.py +43 -0
- aipt_v2/core/agent.py +630 -0
- aipt_v2/core/llm.py +395 -0
- aipt_v2/core/memory.py +305 -0
- aipt_v2/core/ptt.py +329 -0
- aipt_v2/database/__init__.py +14 -0
- aipt_v2/database/models.py +232 -0
- aipt_v2/database/repository.py +384 -0
- aipt_v2/docker/__init__.py +23 -0
- aipt_v2/docker/builder.py +260 -0
- aipt_v2/docker/manager.py +222 -0
- aipt_v2/docker/sandbox.py +371 -0
- aipt_v2/evasion/__init__.py +58 -0
- aipt_v2/evasion/request_obfuscator.py +272 -0
- aipt_v2/evasion/tls_fingerprint.py +285 -0
- aipt_v2/evasion/ua_rotator.py +301 -0
- aipt_v2/evasion/waf_bypass.py +439 -0
- aipt_v2/execution/__init__.py +23 -0
- aipt_v2/execution/executor.py +302 -0
- aipt_v2/execution/parser.py +544 -0
- aipt_v2/execution/terminal.py +337 -0
- aipt_v2/health.py +437 -0
- aipt_v2/intelligence/__init__.py +194 -0
- aipt_v2/intelligence/adaptation.py +474 -0
- aipt_v2/intelligence/auth.py +520 -0
- aipt_v2/intelligence/chaining.py +775 -0
- aipt_v2/intelligence/correlation.py +536 -0
- aipt_v2/intelligence/cve_aipt.py +334 -0
- aipt_v2/intelligence/cve_info.py +1111 -0
- aipt_v2/intelligence/knowledge_graph.py +590 -0
- aipt_v2/intelligence/learning.py +626 -0
- aipt_v2/intelligence/llm_analyzer.py +502 -0
- aipt_v2/intelligence/llm_tool_selector.py +518 -0
- aipt_v2/intelligence/payload_generator.py +562 -0
- aipt_v2/intelligence/rag.py +239 -0
- aipt_v2/intelligence/scope.py +442 -0
- aipt_v2/intelligence/searchers/__init__.py +5 -0
- aipt_v2/intelligence/searchers/exploitdb_searcher.py +523 -0
- aipt_v2/intelligence/searchers/github_searcher.py +467 -0
- aipt_v2/intelligence/searchers/google_searcher.py +281 -0
- aipt_v2/intelligence/tools.json +443 -0
- aipt_v2/intelligence/triage.py +670 -0
- aipt_v2/interactive_shell.py +559 -0
- aipt_v2/interface/__init__.py +5 -0
- aipt_v2/interface/cli.py +230 -0
- aipt_v2/interface/main.py +501 -0
- aipt_v2/interface/tui.py +1276 -0
- aipt_v2/interface/utils.py +583 -0
- aipt_v2/llm/__init__.py +39 -0
- aipt_v2/llm/config.py +26 -0
- aipt_v2/llm/llm.py +514 -0
- aipt_v2/llm/memory.py +214 -0
- aipt_v2/llm/request_queue.py +89 -0
- aipt_v2/llm/utils.py +89 -0
- aipt_v2/local_tool_installer.py +1467 -0
- aipt_v2/models/__init__.py +15 -0
- aipt_v2/models/findings.py +295 -0
- aipt_v2/models/phase_result.py +224 -0
- aipt_v2/models/scan_config.py +207 -0
- aipt_v2/monitoring/grafana/dashboards/aipt-dashboard.json +355 -0
- aipt_v2/monitoring/grafana/dashboards/default.yml +17 -0
- aipt_v2/monitoring/grafana/datasources/prometheus.yml +17 -0
- aipt_v2/monitoring/prometheus.yml +60 -0
- aipt_v2/orchestration/__init__.py +52 -0
- aipt_v2/orchestration/pipeline.py +398 -0
- aipt_v2/orchestration/progress.py +300 -0
- aipt_v2/orchestration/scheduler.py +296 -0
- aipt_v2/orchestrator.py +2427 -0
- aipt_v2/payloads/__init__.py +27 -0
- aipt_v2/payloads/cmdi.py +150 -0
- aipt_v2/payloads/sqli.py +263 -0
- aipt_v2/payloads/ssrf.py +204 -0
- aipt_v2/payloads/templates.py +222 -0
- aipt_v2/payloads/traversal.py +166 -0
- aipt_v2/payloads/xss.py +204 -0
- aipt_v2/prompts/__init__.py +60 -0
- aipt_v2/proxy/__init__.py +29 -0
- aipt_v2/proxy/history.py +352 -0
- aipt_v2/proxy/interceptor.py +452 -0
- aipt_v2/recon/__init__.py +44 -0
- aipt_v2/recon/dns.py +241 -0
- aipt_v2/recon/osint.py +367 -0
- aipt_v2/recon/subdomain.py +372 -0
- aipt_v2/recon/tech_detect.py +311 -0
- aipt_v2/reports/__init__.py +17 -0
- aipt_v2/reports/generator.py +313 -0
- aipt_v2/reports/html_report.py +378 -0
- aipt_v2/runtime/__init__.py +53 -0
- aipt_v2/runtime/base.py +30 -0
- aipt_v2/runtime/docker.py +401 -0
- aipt_v2/runtime/local.py +346 -0
- aipt_v2/runtime/tool_server.py +205 -0
- aipt_v2/runtime/vps.py +830 -0
- aipt_v2/scanners/__init__.py +28 -0
- aipt_v2/scanners/base.py +273 -0
- aipt_v2/scanners/nikto.py +244 -0
- aipt_v2/scanners/nmap.py +402 -0
- aipt_v2/scanners/nuclei.py +273 -0
- aipt_v2/scanners/web.py +454 -0
- aipt_v2/scripts/security_audit.py +366 -0
- aipt_v2/setup_wizard.py +941 -0
- aipt_v2/skills/__init__.py +80 -0
- aipt_v2/skills/agents/__init__.py +14 -0
- aipt_v2/skills/agents/api_tester.py +706 -0
- aipt_v2/skills/agents/base.py +477 -0
- aipt_v2/skills/agents/code_review.py +459 -0
- aipt_v2/skills/agents/security_agent.py +336 -0
- aipt_v2/skills/agents/web_pentest.py +818 -0
- aipt_v2/skills/prompts/__init__.py +647 -0
- aipt_v2/system_detector.py +539 -0
- aipt_v2/telemetry/__init__.py +7 -0
- aipt_v2/telemetry/tracer.py +347 -0
- aipt_v2/terminal/__init__.py +28 -0
- aipt_v2/terminal/executor.py +400 -0
- aipt_v2/terminal/sandbox.py +350 -0
- aipt_v2/tools/__init__.py +44 -0
- aipt_v2/tools/active_directory/__init__.py +78 -0
- aipt_v2/tools/active_directory/ad_config.py +238 -0
- aipt_v2/tools/active_directory/bloodhound_wrapper.py +447 -0
- aipt_v2/tools/active_directory/kerberos_attacks.py +430 -0
- aipt_v2/tools/active_directory/ldap_enum.py +533 -0
- aipt_v2/tools/active_directory/smb_attacks.py +505 -0
- aipt_v2/tools/agents_graph/__init__.py +19 -0
- aipt_v2/tools/agents_graph/agents_graph_actions.py +69 -0
- aipt_v2/tools/api_security/__init__.py +76 -0
- aipt_v2/tools/api_security/api_discovery.py +608 -0
- aipt_v2/tools/api_security/graphql_scanner.py +622 -0
- aipt_v2/tools/api_security/jwt_analyzer.py +577 -0
- aipt_v2/tools/api_security/openapi_fuzzer.py +761 -0
- aipt_v2/tools/browser/__init__.py +5 -0
- aipt_v2/tools/browser/browser_actions.py +238 -0
- aipt_v2/tools/browser/browser_instance.py +535 -0
- aipt_v2/tools/browser/tab_manager.py +344 -0
- aipt_v2/tools/cloud/__init__.py +70 -0
- aipt_v2/tools/cloud/cloud_config.py +273 -0
- aipt_v2/tools/cloud/cloud_scanner.py +639 -0
- aipt_v2/tools/cloud/prowler_tool.py +571 -0
- aipt_v2/tools/cloud/scoutsuite_tool.py +359 -0
- aipt_v2/tools/executor.py +307 -0
- aipt_v2/tools/parser.py +408 -0
- aipt_v2/tools/proxy/__init__.py +5 -0
- aipt_v2/tools/proxy/proxy_actions.py +103 -0
- aipt_v2/tools/proxy/proxy_manager.py +789 -0
- aipt_v2/tools/registry.py +196 -0
- aipt_v2/tools/scanners/__init__.py +343 -0
- aipt_v2/tools/scanners/acunetix_tool.py +712 -0
- aipt_v2/tools/scanners/burp_tool.py +631 -0
- aipt_v2/tools/scanners/config.py +156 -0
- aipt_v2/tools/scanners/nessus_tool.py +588 -0
- aipt_v2/tools/scanners/zap_tool.py +612 -0
- aipt_v2/tools/terminal/__init__.py +5 -0
- aipt_v2/tools/terminal/terminal_actions.py +37 -0
- aipt_v2/tools/terminal/terminal_manager.py +153 -0
- aipt_v2/tools/terminal/terminal_session.py +449 -0
- aipt_v2/tools/tool_processing.py +108 -0
- aipt_v2/utils/__init__.py +17 -0
- aipt_v2/utils/logging.py +202 -0
- aipt_v2/utils/model_manager.py +187 -0
- aipt_v2/utils/searchers/__init__.py +269 -0
- aipt_v2/verify_install.py +793 -0
- aiptx-2.0.7.dist-info/METADATA +345 -0
- aiptx-2.0.7.dist-info/RECORD +187 -0
- aiptx-2.0.7.dist-info/WHEEL +5 -0
- aiptx-2.0.7.dist-info/entry_points.txt +7 -0
- aiptx-2.0.7.dist-info/licenses/LICENSE +21 -0
- aiptx-2.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Scanners Module
|
|
3
|
+
|
|
4
|
+
Integrations with popular security scanning tools:
|
|
5
|
+
- Nuclei - Template-based vulnerability scanner
|
|
6
|
+
- Nmap - Network scanner
|
|
7
|
+
- Nikto - Web server scanner
|
|
8
|
+
- SQLMap - SQL injection scanner
|
|
9
|
+
- Dirb/Gobuster - Directory enumeration
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .base import BaseScanner, ScanResult
|
|
13
|
+
from .nuclei import NucleiScanner, NucleiConfig
|
|
14
|
+
from .nmap import NmapScanner, NmapConfig
|
|
15
|
+
from .nikto import NiktoScanner
|
|
16
|
+
from .web import WebScanner, WebScanConfig
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"BaseScanner",
|
|
20
|
+
"ScanResult",
|
|
21
|
+
"NucleiScanner",
|
|
22
|
+
"NucleiConfig",
|
|
23
|
+
"NmapScanner",
|
|
24
|
+
"NmapConfig",
|
|
25
|
+
"NiktoScanner",
|
|
26
|
+
"WebScanner",
|
|
27
|
+
"WebScanConfig",
|
|
28
|
+
]
|
aipt_v2/scanners/base.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Base Scanner
|
|
3
|
+
|
|
4
|
+
Abstract base class for all scanner integrations.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import shutil
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Any, AsyncIterator, Optional
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ScanSeverity(Enum):
|
|
21
|
+
"""Vulnerability severity levels"""
|
|
22
|
+
INFO = "info"
|
|
23
|
+
LOW = "low"
|
|
24
|
+
MEDIUM = "medium"
|
|
25
|
+
HIGH = "high"
|
|
26
|
+
CRITICAL = "critical"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ScanFinding:
|
|
31
|
+
"""Individual scan finding"""
|
|
32
|
+
title: str
|
|
33
|
+
severity: ScanSeverity
|
|
34
|
+
description: str = ""
|
|
35
|
+
url: str = ""
|
|
36
|
+
host: str = ""
|
|
37
|
+
port: int = 0
|
|
38
|
+
|
|
39
|
+
# Details
|
|
40
|
+
evidence: str = ""
|
|
41
|
+
request: str = ""
|
|
42
|
+
response: str = ""
|
|
43
|
+
|
|
44
|
+
# Classification
|
|
45
|
+
cve: Optional[str] = None
|
|
46
|
+
cwe: Optional[str] = None
|
|
47
|
+
cvss: Optional[float] = None
|
|
48
|
+
|
|
49
|
+
# Scanner metadata
|
|
50
|
+
scanner: str = ""
|
|
51
|
+
template: str = ""
|
|
52
|
+
tags: list[str] = field(default_factory=list)
|
|
53
|
+
|
|
54
|
+
# Timestamps
|
|
55
|
+
found_at: datetime = field(default_factory=datetime.utcnow)
|
|
56
|
+
|
|
57
|
+
def to_dict(self) -> dict:
|
|
58
|
+
return {
|
|
59
|
+
"title": self.title,
|
|
60
|
+
"severity": self.severity.value,
|
|
61
|
+
"description": self.description,
|
|
62
|
+
"url": self.url,
|
|
63
|
+
"host": self.host,
|
|
64
|
+
"port": self.port,
|
|
65
|
+
"evidence": self.evidence[:500] if self.evidence else "",
|
|
66
|
+
"cve": self.cve,
|
|
67
|
+
"cwe": self.cwe,
|
|
68
|
+
"cvss": self.cvss,
|
|
69
|
+
"scanner": self.scanner,
|
|
70
|
+
"template": self.template,
|
|
71
|
+
"tags": self.tags,
|
|
72
|
+
"found_at": self.found_at.isoformat(),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class ScanResult:
|
|
78
|
+
"""Complete scan result"""
|
|
79
|
+
scanner: str
|
|
80
|
+
target: str
|
|
81
|
+
status: str = "pending" # pending, running, completed, failed
|
|
82
|
+
findings: list[ScanFinding] = field(default_factory=list)
|
|
83
|
+
|
|
84
|
+
# Timing
|
|
85
|
+
start_time: Optional[datetime] = None
|
|
86
|
+
end_time: Optional[datetime] = None
|
|
87
|
+
duration_seconds: float = 0.0
|
|
88
|
+
|
|
89
|
+
# Statistics
|
|
90
|
+
requests_made: int = 0
|
|
91
|
+
errors: list[str] = field(default_factory=list)
|
|
92
|
+
|
|
93
|
+
# Raw output
|
|
94
|
+
raw_output: str = ""
|
|
95
|
+
|
|
96
|
+
def add_finding(self, finding: ScanFinding) -> None:
|
|
97
|
+
"""Add a finding"""
|
|
98
|
+
finding.scanner = self.scanner
|
|
99
|
+
self.findings.append(finding)
|
|
100
|
+
|
|
101
|
+
def get_findings_by_severity(self, severity: ScanSeverity) -> list[ScanFinding]:
|
|
102
|
+
"""Get findings of a specific severity"""
|
|
103
|
+
return [f for f in self.findings if f.severity == severity]
|
|
104
|
+
|
|
105
|
+
def get_critical_and_high(self) -> list[ScanFinding]:
|
|
106
|
+
"""Get critical and high severity findings"""
|
|
107
|
+
return [
|
|
108
|
+
f for f in self.findings
|
|
109
|
+
if f.severity in [ScanSeverity.CRITICAL, ScanSeverity.HIGH]
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
def severity_counts(self) -> dict[str, int]:
|
|
113
|
+
"""Get count by severity"""
|
|
114
|
+
counts = {s.value: 0 for s in ScanSeverity}
|
|
115
|
+
for finding in self.findings:
|
|
116
|
+
counts[finding.severity.value] += 1
|
|
117
|
+
return counts
|
|
118
|
+
|
|
119
|
+
def to_dict(self) -> dict:
|
|
120
|
+
return {
|
|
121
|
+
"scanner": self.scanner,
|
|
122
|
+
"target": self.target,
|
|
123
|
+
"status": self.status,
|
|
124
|
+
"findings_count": len(self.findings),
|
|
125
|
+
"severity_counts": self.severity_counts(),
|
|
126
|
+
"duration_seconds": self.duration_seconds,
|
|
127
|
+
"requests_made": self.requests_made,
|
|
128
|
+
"errors": self.errors,
|
|
129
|
+
"start_time": self.start_time.isoformat() if self.start_time else None,
|
|
130
|
+
"end_time": self.end_time.isoformat() if self.end_time else None,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class BaseScanner(ABC):
|
|
135
|
+
"""
|
|
136
|
+
Abstract base class for scanner integrations.
|
|
137
|
+
|
|
138
|
+
Subclasses must implement:
|
|
139
|
+
- scan(): Perform the scan
|
|
140
|
+
- is_available(): Check if scanner is installed
|
|
141
|
+
- parse_output(): Parse scanner output
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
class MyScanner(BaseScanner):
|
|
145
|
+
async def scan(self, target):
|
|
146
|
+
result = ScanResult(scanner="my_scanner", target=target)
|
|
147
|
+
# Run scan...
|
|
148
|
+
return result
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
def __init__(self):
|
|
152
|
+
self._running = False
|
|
153
|
+
self._process: Optional[asyncio.subprocess.Process] = None
|
|
154
|
+
|
|
155
|
+
@abstractmethod
|
|
156
|
+
async def scan(self, target: str, **kwargs) -> ScanResult:
|
|
157
|
+
"""Perform scan on target"""
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
@abstractmethod
|
|
161
|
+
def is_available(self) -> bool:
|
|
162
|
+
"""Check if scanner is available/installed"""
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
@abstractmethod
|
|
166
|
+
def parse_output(self, output: str) -> list[ScanFinding]:
|
|
167
|
+
"""Parse scanner output into findings"""
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
async def stream_scan(self, target: str, **kwargs) -> AsyncIterator[str]:
|
|
171
|
+
"""Stream scan output (if supported)"""
|
|
172
|
+
yield "Streaming not implemented for this scanner"
|
|
173
|
+
|
|
174
|
+
async def stop(self) -> bool:
|
|
175
|
+
"""Stop running scan"""
|
|
176
|
+
if self._process:
|
|
177
|
+
try:
|
|
178
|
+
self._process.terminate()
|
|
179
|
+
await asyncio.wait_for(self._process.wait(), timeout=5.0)
|
|
180
|
+
return True
|
|
181
|
+
except asyncio.TimeoutError:
|
|
182
|
+
self._process.kill()
|
|
183
|
+
return True
|
|
184
|
+
except Exception:
|
|
185
|
+
return False
|
|
186
|
+
return True
|
|
187
|
+
|
|
188
|
+
def _check_tool(self, tool_name: str) -> bool:
|
|
189
|
+
"""Check if a tool is available in PATH"""
|
|
190
|
+
return shutil.which(tool_name) is not None
|
|
191
|
+
|
|
192
|
+
async def _run_command(
|
|
193
|
+
self,
|
|
194
|
+
command: list[str],
|
|
195
|
+
timeout: float = 300.0,
|
|
196
|
+
) -> tuple[int, str, str]:
|
|
197
|
+
"""
|
|
198
|
+
Run a command and return exit code, stdout, stderr.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
command: Command and arguments
|
|
202
|
+
timeout: Timeout in seconds
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Tuple of (exit_code, stdout, stderr)
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
self._running = True
|
|
209
|
+
self._process = await asyncio.create_subprocess_exec(
|
|
210
|
+
*command,
|
|
211
|
+
stdout=asyncio.subprocess.PIPE,
|
|
212
|
+
stderr=asyncio.subprocess.PIPE,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
stdout, stderr = await asyncio.wait_for(
|
|
217
|
+
self._process.communicate(),
|
|
218
|
+
timeout=timeout,
|
|
219
|
+
)
|
|
220
|
+
return (
|
|
221
|
+
self._process.returncode or 0,
|
|
222
|
+
stdout.decode("utf-8", errors="replace"),
|
|
223
|
+
stderr.decode("utf-8", errors="replace"),
|
|
224
|
+
)
|
|
225
|
+
except asyncio.TimeoutError:
|
|
226
|
+
self._process.kill()
|
|
227
|
+
await self._process.wait()
|
|
228
|
+
return -1, "", "Command timed out"
|
|
229
|
+
|
|
230
|
+
except FileNotFoundError:
|
|
231
|
+
return -1, "", f"Command not found: {command[0]}"
|
|
232
|
+
except Exception as e:
|
|
233
|
+
return -1, "", str(e)
|
|
234
|
+
finally:
|
|
235
|
+
self._running = False
|
|
236
|
+
self._process = None
|
|
237
|
+
|
|
238
|
+
async def _stream_command(
|
|
239
|
+
self,
|
|
240
|
+
command: list[str],
|
|
241
|
+
timeout: float = 300.0,
|
|
242
|
+
) -> AsyncIterator[str]:
|
|
243
|
+
"""Stream command output line by line"""
|
|
244
|
+
try:
|
|
245
|
+
self._running = True
|
|
246
|
+
self._process = await asyncio.create_subprocess_exec(
|
|
247
|
+
*command,
|
|
248
|
+
stdout=asyncio.subprocess.PIPE,
|
|
249
|
+
stderr=asyncio.subprocess.STDOUT,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
start_time = asyncio.get_event_loop().time()
|
|
253
|
+
|
|
254
|
+
async for line in self._process.stdout:
|
|
255
|
+
# Check timeout
|
|
256
|
+
if asyncio.get_event_loop().time() - start_time > timeout:
|
|
257
|
+
self._process.kill()
|
|
258
|
+
yield "[TIMEOUT]"
|
|
259
|
+
break
|
|
260
|
+
|
|
261
|
+
yield line.decode("utf-8", errors="replace").rstrip()
|
|
262
|
+
|
|
263
|
+
await self._process.wait()
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
yield f"[ERROR] {str(e)}"
|
|
267
|
+
finally:
|
|
268
|
+
self._running = False
|
|
269
|
+
self._process = None
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def is_running(self) -> bool:
|
|
273
|
+
return self._running
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIPT Nikto Scanner Integration
|
|
3
|
+
|
|
4
|
+
Web server vulnerability scanning using Nikto.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from .base import BaseScanner, ScanFinding, ScanResult, ScanSeverity
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NiktoScanner(BaseScanner):
|
|
20
|
+
"""
|
|
21
|
+
Nikto web server scanner integration.
|
|
22
|
+
|
|
23
|
+
Nikto scans for:
|
|
24
|
+
- Dangerous files/programs
|
|
25
|
+
- Outdated server versions
|
|
26
|
+
- Version-specific vulnerabilities
|
|
27
|
+
- Server configuration issues
|
|
28
|
+
- Default files
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
scanner = NiktoScanner()
|
|
32
|
+
result = await scanner.scan("https://target.com")
|
|
33
|
+
|
|
34
|
+
for finding in result.findings:
|
|
35
|
+
print(f"{finding.severity}: {finding.title}")
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
timeout: int = 10,
|
|
41
|
+
tuning: str = "", # Scan tuning: 1-9,a,b,c,x
|
|
42
|
+
plugins: list[str] = None,
|
|
43
|
+
no_ssl: bool = False,
|
|
44
|
+
):
|
|
45
|
+
super().__init__()
|
|
46
|
+
self.timeout = timeout
|
|
47
|
+
self.tuning = tuning
|
|
48
|
+
self.plugins = plugins or []
|
|
49
|
+
self.no_ssl = no_ssl
|
|
50
|
+
|
|
51
|
+
def is_available(self) -> bool:
|
|
52
|
+
"""Check if Nikto is installed"""
|
|
53
|
+
return self._check_tool("nikto")
|
|
54
|
+
|
|
55
|
+
async def scan(self, target: str, **kwargs) -> ScanResult:
|
|
56
|
+
"""
|
|
57
|
+
Run Nikto scan on target.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
target: URL to scan
|
|
61
|
+
**kwargs: Additional options
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
ScanResult with findings
|
|
65
|
+
"""
|
|
66
|
+
result = ScanResult(scanner="nikto", target=target)
|
|
67
|
+
result.start_time = datetime.utcnow()
|
|
68
|
+
result.status = "running"
|
|
69
|
+
|
|
70
|
+
if not self.is_available():
|
|
71
|
+
result.status = "failed"
|
|
72
|
+
result.errors.append("Nikto is not installed")
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
# Build command
|
|
76
|
+
command = self._build_command(target, **kwargs)
|
|
77
|
+
logger.info(f"Running Nikto: {' '.join(command)}")
|
|
78
|
+
|
|
79
|
+
# Execute
|
|
80
|
+
exit_code, stdout, stderr = await self._run_command(
|
|
81
|
+
command,
|
|
82
|
+
timeout=kwargs.get("timeout", 1200.0), # 20 min default
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
result.end_time = datetime.utcnow()
|
|
86
|
+
result.duration_seconds = (result.end_time - result.start_time).total_seconds()
|
|
87
|
+
result.raw_output = stdout
|
|
88
|
+
|
|
89
|
+
if exit_code != 0 and "0 error(s)" not in stdout:
|
|
90
|
+
if "OSVDB" in stdout or "vulnerability" in stdout.lower():
|
|
91
|
+
# Nikto found something, not a failure
|
|
92
|
+
result.status = "completed"
|
|
93
|
+
else:
|
|
94
|
+
result.status = "failed"
|
|
95
|
+
result.errors.append(stderr)
|
|
96
|
+
else:
|
|
97
|
+
result.status = "completed"
|
|
98
|
+
|
|
99
|
+
# Parse output
|
|
100
|
+
result.findings = self.parse_output(stdout)
|
|
101
|
+
|
|
102
|
+
logger.info(
|
|
103
|
+
f"Nikto scan complete: {len(result.findings)} findings in {result.duration_seconds:.1f}s"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
def parse_output(self, output: str) -> list[ScanFinding]:
|
|
109
|
+
"""Parse Nikto output"""
|
|
110
|
+
findings = []
|
|
111
|
+
|
|
112
|
+
# Pattern for Nikto findings
|
|
113
|
+
# Format: + OSVDB-XXXX: /path: Description
|
|
114
|
+
# Or: + /path: Description
|
|
115
|
+
finding_pattern = r"\+ (?:OSVDB-(\d+): )?([^:]+): (.+)"
|
|
116
|
+
|
|
117
|
+
for line in output.split("\n"):
|
|
118
|
+
line = line.strip()
|
|
119
|
+
if not line.startswith("+"):
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
match = re.match(finding_pattern, line)
|
|
123
|
+
if match:
|
|
124
|
+
osvdb, path, description = match.groups()
|
|
125
|
+
|
|
126
|
+
# Determine severity
|
|
127
|
+
severity = self._determine_severity(description, path)
|
|
128
|
+
|
|
129
|
+
finding = ScanFinding(
|
|
130
|
+
title=self._clean_title(description),
|
|
131
|
+
severity=severity,
|
|
132
|
+
description=description,
|
|
133
|
+
url=path,
|
|
134
|
+
scanner="nikto",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if osvdb:
|
|
138
|
+
finding.template = f"OSVDB-{osvdb}"
|
|
139
|
+
finding.evidence = f"OSVDB-{osvdb}"
|
|
140
|
+
|
|
141
|
+
findings.append(finding)
|
|
142
|
+
|
|
143
|
+
return findings
|
|
144
|
+
|
|
145
|
+
def _determine_severity(self, description: str, path: str) -> ScanSeverity:
|
|
146
|
+
"""Determine severity from finding description"""
|
|
147
|
+
desc_lower = description.lower()
|
|
148
|
+
path_lower = path.lower()
|
|
149
|
+
|
|
150
|
+
# Critical
|
|
151
|
+
if any(kw in desc_lower for kw in ["rce", "remote code execution", "backdoor", "shell"]):
|
|
152
|
+
return ScanSeverity.CRITICAL
|
|
153
|
+
|
|
154
|
+
# High
|
|
155
|
+
if any(kw in desc_lower for kw in [
|
|
156
|
+
"sql injection", "sqli",
|
|
157
|
+
"command injection",
|
|
158
|
+
"file inclusion", "lfi", "rfi",
|
|
159
|
+
"authentication bypass",
|
|
160
|
+
"default password",
|
|
161
|
+
"admin access",
|
|
162
|
+
]):
|
|
163
|
+
return ScanSeverity.HIGH
|
|
164
|
+
|
|
165
|
+
# Medium
|
|
166
|
+
if any(kw in desc_lower for kw in [
|
|
167
|
+
"xss", "cross-site",
|
|
168
|
+
"information disclosure",
|
|
169
|
+
"directory listing",
|
|
170
|
+
"source code",
|
|
171
|
+
"backup file",
|
|
172
|
+
"config file",
|
|
173
|
+
"outdated",
|
|
174
|
+
]):
|
|
175
|
+
return ScanSeverity.MEDIUM
|
|
176
|
+
|
|
177
|
+
# Path-based severity
|
|
178
|
+
if any(p in path_lower for p in [
|
|
179
|
+
"/admin", "/manager", "/phpmyadmin",
|
|
180
|
+
".bak", ".old", ".sql", ".zip",
|
|
181
|
+
"phpinfo", "test.php",
|
|
182
|
+
]):
|
|
183
|
+
return ScanSeverity.MEDIUM
|
|
184
|
+
|
|
185
|
+
# Low
|
|
186
|
+
if any(kw in desc_lower for kw in [
|
|
187
|
+
"cookie", "header",
|
|
188
|
+
"version", "banner",
|
|
189
|
+
"allowed method",
|
|
190
|
+
]):
|
|
191
|
+
return ScanSeverity.LOW
|
|
192
|
+
|
|
193
|
+
return ScanSeverity.INFO
|
|
194
|
+
|
|
195
|
+
def _clean_title(self, description: str) -> str:
|
|
196
|
+
"""Create clean title from description"""
|
|
197
|
+
# Truncate long descriptions
|
|
198
|
+
if len(description) > 100:
|
|
199
|
+
return description[:97] + "..."
|
|
200
|
+
return description
|
|
201
|
+
|
|
202
|
+
def _build_command(self, target: str, **kwargs) -> list[str]:
|
|
203
|
+
"""Build Nikto command"""
|
|
204
|
+
command = ["nikto", "-h", target]
|
|
205
|
+
|
|
206
|
+
# Timeout
|
|
207
|
+
command.extend(["-timeout", str(self.timeout)])
|
|
208
|
+
|
|
209
|
+
# Output format
|
|
210
|
+
command.extend(["-Format", "txt"])
|
|
211
|
+
|
|
212
|
+
# Tuning
|
|
213
|
+
if self.tuning:
|
|
214
|
+
command.extend(["-Tuning", self.tuning])
|
|
215
|
+
|
|
216
|
+
# Plugins
|
|
217
|
+
if self.plugins:
|
|
218
|
+
command.extend(["-Plugins", ",".join(self.plugins)])
|
|
219
|
+
|
|
220
|
+
# SSL
|
|
221
|
+
if self.no_ssl:
|
|
222
|
+
command.append("-nossl")
|
|
223
|
+
|
|
224
|
+
# Disable interactive mode
|
|
225
|
+
command.append("-ask")
|
|
226
|
+
command.append("no")
|
|
227
|
+
|
|
228
|
+
return command
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# Convenience functions
|
|
232
|
+
async def quick_nikto_scan(target: str) -> ScanResult:
|
|
233
|
+
"""Quick Nikto scan"""
|
|
234
|
+
scanner = NiktoScanner(timeout=5)
|
|
235
|
+
return await scanner.scan(target, timeout=300.0)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
async def full_nikto_scan(target: str) -> ScanResult:
|
|
239
|
+
"""Comprehensive Nikto scan"""
|
|
240
|
+
scanner = NiktoScanner(
|
|
241
|
+
timeout=15,
|
|
242
|
+
tuning="123456789abc", # All checks
|
|
243
|
+
)
|
|
244
|
+
return await scanner.scan(target, timeout=3600.0)
|