zombie-vdp 1.0.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.
- zombie_vdp-1.0.0/MANIFEST.in +1 -0
- zombie_vdp-1.0.0/PKG-INFO +87 -0
- zombie_vdp-1.0.0/README.md +51 -0
- zombie_vdp-1.0.0/setup.cfg +4 -0
- zombie_vdp-1.0.0/setup.py +46 -0
- zombie_vdp-1.0.0/zombie_vdp/__init__.py +10 -0
- zombie_vdp-1.0.0/zombie_vdp/config.yaml +100 -0
- zombie_vdp-1.0.0/zombie_vdp/zombie.py +649 -0
- zombie_vdp-1.0.0/zombie_vdp.egg-info/PKG-INFO +87 -0
- zombie_vdp-1.0.0/zombie_vdp.egg-info/SOURCES.txt +12 -0
- zombie_vdp-1.0.0/zombie_vdp.egg-info/dependency_links.txt +1 -0
- zombie_vdp-1.0.0/zombie_vdp.egg-info/entry_points.txt +2 -0
- zombie_vdp-1.0.0/zombie_vdp.egg-info/requires.txt +11 -0
- zombie_vdp-1.0.0/zombie_vdp.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include zombie_vdp/config.yaml
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zombie-vdp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 🧟♂️ Zombie VDP – Ultimate Identity Edition
|
|
5
|
+
Home-page: https://github.com/pormes-90/ZOMBIE.git
|
|
6
|
+
Author: Dikha Pormes
|
|
7
|
+
Author-email: pormesdikha90@gmail.com
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Information Technology
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Security
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: aiohttp>=3.8
|
|
17
|
+
Requires-Dist: pyyaml>=6.0
|
|
18
|
+
Requires-Dist: dnspython>=2.3
|
|
19
|
+
Requires-Dist: psutil>=5.9
|
|
20
|
+
Requires-Dist: colorama>=0.4
|
|
21
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
22
|
+
Requires-Dist: cryptography>=41.0
|
|
23
|
+
Provides-Extra: full
|
|
24
|
+
Requires-Dist: scikit-learn>=1.3; extra == "full"
|
|
25
|
+
Requires-Dist: matplotlib>=3.7; extra == "full"
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: provides-extra
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
36
|
+
|
|
37
|
+
# 🧟♂️ Zombie VDP – Ultimate Identity Edition
|
|
38
|
+
|
|
39
|
+
**NASA VDP Compliant · 100% Passive · Triple Filter · Evidence Locker · Time Slice · Auto-PoC · Memory**
|
|
40
|
+
|
|
41
|
+
Zombie VDP adalah kerangka kerja bug bounty pribadi yang menggabungkan seluruh pipeline pengujian keamanan dalam satu nafas. Semua fitur dirancang dari nol, bukan meniru alat lain, melainkan menciptakan identitas sendiri: **Ninja**, **Samurai**, dan **Ghost**.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 🔥 Fitur Legendaris
|
|
46
|
+
|
|
47
|
+
### ⚔️ Triple Filter (Ninja · Samurai · Ghost)
|
|
48
|
+
- **Ninja Filter** – Hanya menerima temuan dengan bukti nyata (evidence).
|
|
49
|
+
- **Samurai Filter** – Hanya HIGH/CRITICAL dengan respons signifikan.
|
|
50
|
+
- **Ghost Filter** – Hanya confidence ≥98% atau metode timing/OOB.
|
|
51
|
+
|
|
52
|
+
### 🧪 Zombie Deep Scan
|
|
53
|
+
Validasi lanjutan setelah filter: serangan timing untuk konfirmasi SQLi, dsb.
|
|
54
|
+
|
|
55
|
+
### 🔒 Zombie Evidence Locker
|
|
56
|
+
Setiap temuan yang lolos filter disimpan sebagai bukti terenkripsi (format `.zombie`). Tidak bisa dibuka tanpa kunci.
|
|
57
|
+
|
|
58
|
+
### ⏳ Zombie Time Slice
|
|
59
|
+
Zombie hanya beroperasi pada jam yang diizinkan (misal 02:00–05:00 UTC). Di luar jam itu, ia berhenti dan menunggu.
|
|
60
|
+
|
|
61
|
+
### ⚡ Zombie Auto-PoC
|
|
62
|
+
Setiap temuan langsung dilengkapi skrip `curl` dan Python untuk mereproduksi kerentanan. Tim triase tinggal menjalankan.
|
|
63
|
+
|
|
64
|
+
### 🧠 Zombie Memory
|
|
65
|
+
Jika pemindaian terhenti, Zombie mengingat titik terakhir dan bisa melanjutkan tanpa mengulang.
|
|
66
|
+
|
|
67
|
+
### 🕸️ Zombie Crawler & Dorking (Internal)
|
|
68
|
+
Crawler mandiri + penyaring URL berbasis pola (tanpa search engine). 100% pasif, tidak meninggalkan jejak bot.
|
|
69
|
+
|
|
70
|
+
### 🛡️ Scanner Kerentanan Bawaan
|
|
71
|
+
- Reflected XSS
|
|
72
|
+
- SQL Injection (error‑based + timing)
|
|
73
|
+
- Local File Inclusion (LFI)
|
|
74
|
+
- Server‑Side Template Injection (SSTI)
|
|
75
|
+
- Open Redirect
|
|
76
|
+
- Adaptive Fuzzer
|
|
77
|
+
- Secrets Scanner (AWS, GitHub, Slack, JWT)
|
|
78
|
+
|
|
79
|
+
### 📊 Laporan Profesional
|
|
80
|
+
HTML report siap kirim ke program VDP. Ringan, rapi, hanya berisi temuan high/critical.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 🚀 Menjalankan
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python zombie_vdp.py
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# 🧟♂️ Zombie VDP – Ultimate Identity Edition
|
|
2
|
+
|
|
3
|
+
**NASA VDP Compliant · 100% Passive · Triple Filter · Evidence Locker · Time Slice · Auto-PoC · Memory**
|
|
4
|
+
|
|
5
|
+
Zombie VDP adalah kerangka kerja bug bounty pribadi yang menggabungkan seluruh pipeline pengujian keamanan dalam satu nafas. Semua fitur dirancang dari nol, bukan meniru alat lain, melainkan menciptakan identitas sendiri: **Ninja**, **Samurai**, dan **Ghost**.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🔥 Fitur Legendaris
|
|
10
|
+
|
|
11
|
+
### ⚔️ Triple Filter (Ninja · Samurai · Ghost)
|
|
12
|
+
- **Ninja Filter** – Hanya menerima temuan dengan bukti nyata (evidence).
|
|
13
|
+
- **Samurai Filter** – Hanya HIGH/CRITICAL dengan respons signifikan.
|
|
14
|
+
- **Ghost Filter** – Hanya confidence ≥98% atau metode timing/OOB.
|
|
15
|
+
|
|
16
|
+
### 🧪 Zombie Deep Scan
|
|
17
|
+
Validasi lanjutan setelah filter: serangan timing untuk konfirmasi SQLi, dsb.
|
|
18
|
+
|
|
19
|
+
### 🔒 Zombie Evidence Locker
|
|
20
|
+
Setiap temuan yang lolos filter disimpan sebagai bukti terenkripsi (format `.zombie`). Tidak bisa dibuka tanpa kunci.
|
|
21
|
+
|
|
22
|
+
### ⏳ Zombie Time Slice
|
|
23
|
+
Zombie hanya beroperasi pada jam yang diizinkan (misal 02:00–05:00 UTC). Di luar jam itu, ia berhenti dan menunggu.
|
|
24
|
+
|
|
25
|
+
### ⚡ Zombie Auto-PoC
|
|
26
|
+
Setiap temuan langsung dilengkapi skrip `curl` dan Python untuk mereproduksi kerentanan. Tim triase tinggal menjalankan.
|
|
27
|
+
|
|
28
|
+
### 🧠 Zombie Memory
|
|
29
|
+
Jika pemindaian terhenti, Zombie mengingat titik terakhir dan bisa melanjutkan tanpa mengulang.
|
|
30
|
+
|
|
31
|
+
### 🕸️ Zombie Crawler & Dorking (Internal)
|
|
32
|
+
Crawler mandiri + penyaring URL berbasis pola (tanpa search engine). 100% pasif, tidak meninggalkan jejak bot.
|
|
33
|
+
|
|
34
|
+
### 🛡️ Scanner Kerentanan Bawaan
|
|
35
|
+
- Reflected XSS
|
|
36
|
+
- SQL Injection (error‑based + timing)
|
|
37
|
+
- Local File Inclusion (LFI)
|
|
38
|
+
- Server‑Side Template Injection (SSTI)
|
|
39
|
+
- Open Redirect
|
|
40
|
+
- Adaptive Fuzzer
|
|
41
|
+
- Secrets Scanner (AWS, GitHub, Slack, JWT)
|
|
42
|
+
|
|
43
|
+
### 📊 Laporan Profesional
|
|
44
|
+
HTML report siap kirim ke program VDP. Ringan, rapi, hanya berisi temuan high/critical.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 🚀 Menjalankan
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
python zombie_vdp.py
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="zombie-vdp",
|
|
8
|
+
version="1.0.0",
|
|
9
|
+
author="Dikha Pormes",
|
|
10
|
+
author_email="pormesdikha90@gmail.com",
|
|
11
|
+
description="🧟♂️ Zombie VDP – Ultimate Identity Edition",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/pormes-90/ZOMBIE.git",
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
include_package_data=True, # Wajib agar config.yaml ikut terdistribusi
|
|
17
|
+
install_requires=[
|
|
18
|
+
"aiohttp>=3.8",
|
|
19
|
+
"pyyaml>=6.0",
|
|
20
|
+
"dnspython>=2.3",
|
|
21
|
+
"psutil>=5.9",
|
|
22
|
+
"colorama>=0.4",
|
|
23
|
+
"beautifulsoup4>=4.12",
|
|
24
|
+
"cryptography>=41.0",
|
|
25
|
+
],
|
|
26
|
+
extras_require={
|
|
27
|
+
"full": [
|
|
28
|
+
"scikit-learn>=1.3",
|
|
29
|
+
"matplotlib>=3.7",
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
entry_points={
|
|
33
|
+
"console_scripts": [
|
|
34
|
+
"zombie = zombie_vdp:main",
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
classifiers=[
|
|
38
|
+
"Development Status :: 4 - Beta",
|
|
39
|
+
"Intended Audience :: Information Technology",
|
|
40
|
+
"License :: OSI Approved :: MIT License",
|
|
41
|
+
"Programming Language :: Python :: 3",
|
|
42
|
+
"Operating System :: OS Independent",
|
|
43
|
+
"Topic :: Security",
|
|
44
|
+
],
|
|
45
|
+
python_requires=">=3.9",
|
|
46
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# ═══════════════════════════════════════════
|
|
2
|
+
# ZOMBIE VDP – TRIPLE FILTER CONFIG
|
|
3
|
+
# (Mode Lambat – Aman dari Block)
|
|
4
|
+
# ═══════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
# ── TARGET ──────────────────────────────
|
|
7
|
+
target_domains:
|
|
8
|
+
- "nasa.gov"
|
|
9
|
+
- "www.nasa.gov"
|
|
10
|
+
- "mail.nasa.gov"
|
|
11
|
+
- "api.nasa.gov"
|
|
12
|
+
- "dev.nasa.gov"
|
|
13
|
+
- "admin.nasa.gov"
|
|
14
|
+
- "portal.nasa.gov"
|
|
15
|
+
- "cdn.nasa.gov"
|
|
16
|
+
- "static.nasa.gov"
|
|
17
|
+
- "assets.nasa.gov"
|
|
18
|
+
- "staging.nasa.gov"
|
|
19
|
+
- "test.nasa.gov"
|
|
20
|
+
- "*.nasa.gov"
|
|
21
|
+
|
|
22
|
+
# ── KEAMANAN ────────────────────────────
|
|
23
|
+
password: "zombie"
|
|
24
|
+
|
|
25
|
+
# ── OUTPUT & DATABASE ───────────────────
|
|
26
|
+
output_dir: "./output"
|
|
27
|
+
db_name: "zombie.db"
|
|
28
|
+
|
|
29
|
+
# ── WORKFLOW ────────────────────────────
|
|
30
|
+
workflow_steps:
|
|
31
|
+
- "zombie_recon"
|
|
32
|
+
- "zombie_crawl"
|
|
33
|
+
- "zombie_dork"
|
|
34
|
+
- "zombie_scan"
|
|
35
|
+
- "zombie_filter"
|
|
36
|
+
- "zombie_deep_scan"
|
|
37
|
+
- "zombie_evidence"
|
|
38
|
+
- "zombie_poc"
|
|
39
|
+
- "zombie_report"
|
|
40
|
+
|
|
41
|
+
# ── PERFORMANCE (LAMBAT & AMAN) ─────────
|
|
42
|
+
max_workers: 3
|
|
43
|
+
rate_limit: 1.0
|
|
44
|
+
timeout: 30
|
|
45
|
+
enable_ssl_verification: false
|
|
46
|
+
|
|
47
|
+
# ── CRAWLING (LAMBAT) ───────────────────
|
|
48
|
+
crawl_delay: [5.0, 10.0]
|
|
49
|
+
crawl_depth: 2
|
|
50
|
+
max_pages_crawl: 100
|
|
51
|
+
|
|
52
|
+
# ── DORKING (INTERNAL) ──────────────────
|
|
53
|
+
# (tidak pakai search engine, hanya filter URL)
|
|
54
|
+
dork_patterns:
|
|
55
|
+
- "\\.env$"
|
|
56
|
+
- "\\.sql$"
|
|
57
|
+
- "\\.log$"
|
|
58
|
+
- "\\.bak$"
|
|
59
|
+
- "\\.conf$"
|
|
60
|
+
- "password"
|
|
61
|
+
- "secret"
|
|
62
|
+
- "api_key"
|
|
63
|
+
- "token"
|
|
64
|
+
- "/admin"
|
|
65
|
+
- "/\\.git"
|
|
66
|
+
- "/config"
|
|
67
|
+
- "/backup"
|
|
68
|
+
|
|
69
|
+
# ── RECON (SUMBER PASIF) ────────────────
|
|
70
|
+
enable_wayback: true
|
|
71
|
+
enable_commoncrawl: true
|
|
72
|
+
enable_crtsh: true
|
|
73
|
+
enable_otx: true
|
|
74
|
+
enable_urlscan: true
|
|
75
|
+
|
|
76
|
+
# ── API KEYS ────────────────────────────
|
|
77
|
+
otx_api_key: "QM3FO7d0VCZGmsb5yavpubBm-dDQtpnPNPLKj4vpXLzU8fN_dhGv5Ec8cdaG5KBuIiEWrQqLc6T3BlbkFJX-RtvtGQCfzwIaj9SXGPE0nvADgcLvZMQQ3ZD0kpwWKfRIwfd2eDB8fVgpJWxeXUL_55UoOPYA"
|
|
78
|
+
urlscan_api_key: "019e4543-d4dc-70f9-9421-11a1c4030626"
|
|
79
|
+
|
|
80
|
+
# ── FILTER TRIPLE ───────────────────────
|
|
81
|
+
confidence_threshold: 90
|
|
82
|
+
active_filters:
|
|
83
|
+
- "ninja"
|
|
84
|
+
- "samurai"
|
|
85
|
+
- "ghost"
|
|
86
|
+
|
|
87
|
+
# ── FUZZER ──────────────────────────────
|
|
88
|
+
enable_fuzzer: true
|
|
89
|
+
|
|
90
|
+
# ── SECRETS SCANNER ─────────────────────
|
|
91
|
+
enable_secrets_scan: true
|
|
92
|
+
|
|
93
|
+
# ── LEGENDARY FEATURES ──────────────────
|
|
94
|
+
enable_evidence_locker: true
|
|
95
|
+
enable_time_slice: false
|
|
96
|
+
time_slice_start: 2
|
|
97
|
+
time_slice_end: 5
|
|
98
|
+
enable_auto_poc: true
|
|
99
|
+
enable_memory: true
|
|
100
|
+
memory_file: "zombie_memory.json"
|
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
ZOMBIE VDP – TRIPLE FILTER EDITION (Final)
|
|
5
|
+
NASA VDP Compliant | 100% Passive
|
|
6
|
+
Fitur lengkap: Recon, Crawl, Dork, Scan, Fuzz, Secrets, Triple Filter,
|
|
7
|
+
Deep Scan, Evidence Locker, Auto-PoC, Memory, Report.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio, aiohttp, getpass, hashlib, json, logging, os, random, re, socket, ssl, sqlite3, sys, time, yaml
|
|
11
|
+
import dns.resolver, dns.query, dns.zone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import List, Set, Dict, Optional, Tuple, Any
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse, urljoin
|
|
16
|
+
from collections import deque, defaultdict
|
|
17
|
+
from difflib import SequenceMatcher
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
import resource, uuid, secrets, tempfile
|
|
20
|
+
|
|
21
|
+
# ─── Warna & Animasi ────────────────────────────────────────
|
|
22
|
+
try:
|
|
23
|
+
from colorama import Fore, Style, init; init(autoreset=True)
|
|
24
|
+
CYAN=Fore.CYAN; BLUE=Fore.BLUE; YELLOW=Fore.YELLOW; RED=Fore.RED
|
|
25
|
+
GREEN=Fore.GREEN; MAGENTA=Fore.MAGENTA; WHITE=Fore.WHITE; NC=Style.RESET_ALL
|
|
26
|
+
BOLD=Style.BRIGHT; PINK=Fore.MAGENTA
|
|
27
|
+
except:
|
|
28
|
+
CYAN=BLUE=YELLOW=RED=GREEN=MAGENTA=WHITE=NC=BOLD=PINK=""
|
|
29
|
+
|
|
30
|
+
def zombie_loading_bar(duration=2):
|
|
31
|
+
steps=100; bar_len=20; sleep_time=duration/steps
|
|
32
|
+
for i in range(steps+1):
|
|
33
|
+
filled=int(bar_len*i/steps)
|
|
34
|
+
bar='■'*filled+'□'*(bar_len-filled)
|
|
35
|
+
print(f'\r {PINK}[{NC}{bar}{PINK}]{NC} {CYAN}{i}%{NC}', end='', flush=True)
|
|
36
|
+
time.sleep(sleep_time)
|
|
37
|
+
print()
|
|
38
|
+
|
|
39
|
+
async def zombie_progress_bar(phase_name, duration=2):
|
|
40
|
+
steps=100; bar_len=20; sleep_time=duration/steps
|
|
41
|
+
for i in range(steps+1):
|
|
42
|
+
filled=int(bar_len*i/steps)
|
|
43
|
+
bar='■'*filled+'□'*(bar_len-filled)
|
|
44
|
+
print(f'\r {CYAN}{phase_name}{NC} {PINK}[{NC}{bar}{PINK}]{NC} {CYAN}{i}%{NC}', end='', flush=True)
|
|
45
|
+
await asyncio.sleep(sleep_time)
|
|
46
|
+
print()
|
|
47
|
+
|
|
48
|
+
def ninja_animation(): print(f"{PINK}🥷 Ninja Filter: hanya bukti nyata...{NC}"); zombie_loading_bar(1)
|
|
49
|
+
def samurai_animation(): print(f"{RED}⚔️ Samurai Filter: HIGH/CRITICAL + respons signifikan...{NC}"); zombie_loading_bar(1)
|
|
50
|
+
def ghost_animation(): print(f"{CYAN}👻 Ghost Filter: confidence ≥98% atau timing/OOB...{NC}"); zombie_loading_bar(1)
|
|
51
|
+
|
|
52
|
+
# ─── User-Agent Pool ────────────────────────────────────────
|
|
53
|
+
USER_AGENTS = [
|
|
54
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0",
|
|
55
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Version/17.0",
|
|
56
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/119.0.0.0",
|
|
57
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/120.0",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
def get_random_headers():
|
|
61
|
+
return {
|
|
62
|
+
"User-Agent": random.choice(USER_AGENTS),
|
|
63
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
64
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ─── Config ─────────────────────────────────────────────────
|
|
68
|
+
@dataclass
|
|
69
|
+
class Config:
|
|
70
|
+
target_domains: List[str] = field(default_factory=lambda: ["nasa.gov"])
|
|
71
|
+
password: str = ""
|
|
72
|
+
output_dir: str = "./output"
|
|
73
|
+
db_name: str = "zombie.db"
|
|
74
|
+
workflow_steps: List[str] = field(default_factory=lambda: [
|
|
75
|
+
"zombie_recon","zombie_crawl","zombie_dork",
|
|
76
|
+
"zombie_scan","zombie_filter","zombie_deep_scan",
|
|
77
|
+
"zombie_evidence","zombie_poc","zombie_report"
|
|
78
|
+
])
|
|
79
|
+
max_workers: int = 3
|
|
80
|
+
rate_limit: float = 1.0
|
|
81
|
+
timeout: int = 30
|
|
82
|
+
enable_ssl_verification: bool = False
|
|
83
|
+
crawl_delay: Tuple[float, float] = (5.0, 10.0)
|
|
84
|
+
crawl_depth: int = 2
|
|
85
|
+
max_pages_crawl: int = 100
|
|
86
|
+
dork_patterns: List[str] = field(default_factory=lambda: [
|
|
87
|
+
r'\.env$', r'\.sql$', r'\.log$', r'\.bak$', r'\.conf$',
|
|
88
|
+
r'password', r'secret', r'api_key', r'token',
|
|
89
|
+
r'/admin', r'/\.git', r'/config', r'/backup'
|
|
90
|
+
])
|
|
91
|
+
enable_wayback: bool = True
|
|
92
|
+
enable_commoncrawl: bool = True
|
|
93
|
+
enable_crtsh: bool = True
|
|
94
|
+
enable_otx: bool = True
|
|
95
|
+
enable_urlscan: bool = True
|
|
96
|
+
otx_api_key: str = ""
|
|
97
|
+
urlscan_api_key: str = ""
|
|
98
|
+
confidence_threshold: int = 90
|
|
99
|
+
active_filters: List[str] = field(default_factory=lambda: ["ninja","samurai","ghost"])
|
|
100
|
+
enable_fuzzer: bool = True
|
|
101
|
+
enable_secrets_scan: bool = True
|
|
102
|
+
enable_evidence_locker: bool = True
|
|
103
|
+
enable_time_slice: bool = False
|
|
104
|
+
time_slice_start: int = 2
|
|
105
|
+
time_slice_end: int = 5
|
|
106
|
+
enable_auto_poc: bool = True
|
|
107
|
+
enable_memory: bool = True
|
|
108
|
+
memory_file: str = "zombie_memory.json"
|
|
109
|
+
|
|
110
|
+
def __post_init__(self):
|
|
111
|
+
Path(self.output_dir).mkdir(parents=True, exist_ok=True)
|
|
112
|
+
(Path(self.output_dir) / "reports").mkdir(exist_ok=True)
|
|
113
|
+
(Path(self.output_dir) / "evidence").mkdir(exist_ok=True)
|
|
114
|
+
(Path(self.output_dir) / "poc").mkdir(exist_ok=True)
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_yaml(cls, path: str = "config.yaml") -> 'Config':
|
|
118
|
+
# 1. Cek current working directory
|
|
119
|
+
cwd_path = Path.cwd() / path
|
|
120
|
+
if cwd_path.exists():
|
|
121
|
+
with open(cwd_path) as f:
|
|
122
|
+
data = yaml.safe_load(f) or {}
|
|
123
|
+
else:
|
|
124
|
+
# 2. Fallback ke package directory
|
|
125
|
+
package_path = Path(__file__).parent / path
|
|
126
|
+
if package_path.exists():
|
|
127
|
+
with open(package_path) as f:
|
|
128
|
+
data = yaml.safe_load(f) or {}
|
|
129
|
+
else:
|
|
130
|
+
logging.warning(f"Config file not found, using defaults")
|
|
131
|
+
return cls()
|
|
132
|
+
|
|
133
|
+
fields = {f.name for f in cls.__dataclass_fields__.values()}
|
|
134
|
+
filtered = {k: v for k, v in data.items() if k in fields}
|
|
135
|
+
return cls(**filtered)
|
|
136
|
+
|
|
137
|
+
# ─── Session Manager ────────────────────────────────────────
|
|
138
|
+
class SessionManager:
|
|
139
|
+
def __init__(self, config: Config):
|
|
140
|
+
self.config = config
|
|
141
|
+
self.session: Optional[aiohttp.ClientSession] = None
|
|
142
|
+
self.semaphore = asyncio.Semaphore(config.max_workers)
|
|
143
|
+
self.last_request = 0
|
|
144
|
+
|
|
145
|
+
async def __aenter__(self):
|
|
146
|
+
connector = aiohttp.TCPConnector(limit=0, ssl=not self.config.enable_ssl_verification)
|
|
147
|
+
self.session = aiohttp.ClientSession(connector=connector)
|
|
148
|
+
return self
|
|
149
|
+
|
|
150
|
+
async def __aexit__(self, *args):
|
|
151
|
+
if self.session: await self.session.close()
|
|
152
|
+
|
|
153
|
+
async def fetch(self, url: str, method='GET', data=None, headers=None, **kwargs) -> dict:
|
|
154
|
+
async with self.semaphore:
|
|
155
|
+
now = time.monotonic()
|
|
156
|
+
if now - self.last_request < 1/self.config.rate_limit:
|
|
157
|
+
await asyncio.sleep(1/self.config.rate_limit)
|
|
158
|
+
self.last_request = time.monotonic()
|
|
159
|
+
if headers is None: headers = get_random_headers()
|
|
160
|
+
try:
|
|
161
|
+
async with self.session.request(method, url, data=data, headers=headers,
|
|
162
|
+
timeout=self.config.timeout, **kwargs) as resp:
|
|
163
|
+
if resp.status in (429, 403, 503):
|
|
164
|
+
pause = random.uniform(30, 60)
|
|
165
|
+
print(f"\n {YELLOW}⚠ Block terdeteksi ({resp.status}). Auto‑pause {pause:.0f}s...{NC}")
|
|
166
|
+
await asyncio.sleep(pause)
|
|
167
|
+
text = await resp.text()
|
|
168
|
+
return {"status":resp.status,"headers":dict(resp.headers),"text":text,"url":str(resp.url)}
|
|
169
|
+
except Exception as e:
|
|
170
|
+
return {"status":0,"headers":{},"text":"","url":url}
|
|
171
|
+
|
|
172
|
+
# ─── Database ───────────────────────────────────────────────
|
|
173
|
+
class Database:
|
|
174
|
+
def __init__(self, db_path:str):
|
|
175
|
+
self.conn = sqlite3.connect(db_path, check_same_thread=False)
|
|
176
|
+
self.conn.execute("""CREATE TABLE IF NOT EXISTS findings (
|
|
177
|
+
id TEXT PRIMARY KEY, url TEXT, vuln_type TEXT, severity TEXT, param TEXT, confidence INTEGER,
|
|
178
|
+
evidence TEXT, response_length INTEGER, method TEXT, timestamp TEXT
|
|
179
|
+
)""")
|
|
180
|
+
self.conn.commit()
|
|
181
|
+
|
|
182
|
+
def save(self, finding: Dict):
|
|
183
|
+
fid = finding.get("id", str(uuid.uuid4())[:8])
|
|
184
|
+
self.conn.execute("INSERT OR REPLACE INTO findings VALUES (?,?,?,?,?,?,?,?,?,?)",
|
|
185
|
+
(fid, finding["url"], finding["vuln_type"], finding["severity"],
|
|
186
|
+
finding.get("param",""), finding["confidence"],
|
|
187
|
+
finding.get("evidence",""), finding.get("response_length",0),
|
|
188
|
+
finding.get("method",""), datetime.now().isoformat()))
|
|
189
|
+
self.conn.commit()
|
|
190
|
+
return fid
|
|
191
|
+
|
|
192
|
+
def all(self):
|
|
193
|
+
return self.conn.execute("SELECT * FROM findings ORDER BY severity DESC").fetchall()
|
|
194
|
+
|
|
195
|
+
# ─── Recon Functions ────────────────────────────────────────
|
|
196
|
+
async def zombie_wayback(sm, domain):
|
|
197
|
+
urls=set()
|
|
198
|
+
try:
|
|
199
|
+
resp=await sm.fetch(f"http://web.archive.org/cdx/search/cdx?url=*.{domain}/*&output=json&collapse=urlkey&fl=original")
|
|
200
|
+
if resp["status"]==200:
|
|
201
|
+
data=json.loads(resp["text"])
|
|
202
|
+
for row in data[1:500]:
|
|
203
|
+
if domain in row[0]: urls.add(row[0])
|
|
204
|
+
except: pass
|
|
205
|
+
return urls
|
|
206
|
+
|
|
207
|
+
async def zombie_commoncrawl(sm, domain):
|
|
208
|
+
urls=set()
|
|
209
|
+
try:
|
|
210
|
+
idx="CC-MAIN-2024-10"
|
|
211
|
+
resp=await sm.fetch(f"http://index.commoncrawl.org/{idx}-index?url=*.{domain}/*&output=json&matchType=domain&fl=url")
|
|
212
|
+
if resp["status"]==200:
|
|
213
|
+
for line in resp["text"].splitlines():
|
|
214
|
+
if domain in line: urls.add(json.loads(line)["url"])
|
|
215
|
+
except: pass
|
|
216
|
+
return urls
|
|
217
|
+
|
|
218
|
+
async def zombie_crtsh(sm, domain):
|
|
219
|
+
urls=set()
|
|
220
|
+
try:
|
|
221
|
+
resp=await sm.fetch(f"https://crt.sh/?q=%.{domain}&output=json")
|
|
222
|
+
if resp["status"]==200:
|
|
223
|
+
data=json.loads(resp["text"])
|
|
224
|
+
for entry in data[:200]:
|
|
225
|
+
for sub in entry.get("name_value","").split():
|
|
226
|
+
if domain in sub: urls.add(f"https://{sub.strip('*.')}")
|
|
227
|
+
except: pass
|
|
228
|
+
return urls
|
|
229
|
+
|
|
230
|
+
async def zombie_otx(sm, domain, api_key):
|
|
231
|
+
urls=set()
|
|
232
|
+
if not api_key: return urls
|
|
233
|
+
headers={"X-OTX-API-KEY":api_key}
|
|
234
|
+
try:
|
|
235
|
+
async with aiohttp.ClientSession(headers=headers) as sess:
|
|
236
|
+
async with sess.get(f"https://otx.alienvault.com/api/v1/indicators/domain/{domain}/url_list") as resp:
|
|
237
|
+
if resp.status==200:
|
|
238
|
+
data=await resp.json()
|
|
239
|
+
for item in data.get("url_list",[])[:200]:
|
|
240
|
+
if domain in item.get("url",""): urls.add(item["url"])
|
|
241
|
+
except: pass
|
|
242
|
+
return urls
|
|
243
|
+
|
|
244
|
+
async def zombie_urlscan(sm, domain, api_key):
|
|
245
|
+
urls=set()
|
|
246
|
+
if not api_key: return urls
|
|
247
|
+
headers={"API-Key":api_key}
|
|
248
|
+
try:
|
|
249
|
+
async with aiohttp.ClientSession(headers=headers) as sess:
|
|
250
|
+
async with sess.get(f"https://urlscan.io/api/v1/search/?q=domain:{domain}") as resp:
|
|
251
|
+
if resp.status==200:
|
|
252
|
+
data=await resp.json()
|
|
253
|
+
for result in data.get("results",[])[:100]:
|
|
254
|
+
u=result.get("page",{}).get("url")
|
|
255
|
+
if u and domain in u: urls.add(u)
|
|
256
|
+
except: pass
|
|
257
|
+
return urls
|
|
258
|
+
|
|
259
|
+
async def zombie_dns_enum(domain):
|
|
260
|
+
subs=set()
|
|
261
|
+
resolver=dns.resolver.Resolver(); resolver.timeout=2
|
|
262
|
+
for sub in ["www","mail","api","dev","admin","portal","cdn","static","assets"]:
|
|
263
|
+
try:
|
|
264
|
+
resolver.resolve(f"{sub}.{domain}", 'A')
|
|
265
|
+
subs.add(f"{sub}.{domain}")
|
|
266
|
+
except: continue
|
|
267
|
+
return subs
|
|
268
|
+
|
|
269
|
+
# ─── Crawler ────────────────────────────────────────────────
|
|
270
|
+
class ZombieCrawler:
|
|
271
|
+
def __init__(self, config: Config): self.config=config
|
|
272
|
+
async def crawl(self, sm, base_domains, seed_urls=set()):
|
|
273
|
+
visited=set()
|
|
274
|
+
queue=deque([(u,0) for u in seed_urls if any(d in u for d in base_domains)])
|
|
275
|
+
for d in base_domains: queue.append((f"https://{d}",0))
|
|
276
|
+
while queue and len(visited)<self.config.max_pages_crawl:
|
|
277
|
+
url,depth=queue.popleft()
|
|
278
|
+
if url in visited: continue
|
|
279
|
+
visited.add(url)
|
|
280
|
+
if depth>=self.config.crawl_depth: continue
|
|
281
|
+
resp=await sm.fetch(url)
|
|
282
|
+
if resp["status"]!=200: continue
|
|
283
|
+
try:
|
|
284
|
+
from bs4 import BeautifulSoup
|
|
285
|
+
soup=BeautifulSoup(resp["text"],'html.parser')
|
|
286
|
+
for link in soup.find_all('a',href=True):
|
|
287
|
+
full=urljoin(url,link['href'])
|
|
288
|
+
if any(d in full for d in base_domains) and full not in visited:
|
|
289
|
+
queue.append((full,depth+1))
|
|
290
|
+
except: pass
|
|
291
|
+
await asyncio.sleep(random.uniform(*self.config.crawl_delay))
|
|
292
|
+
return visited
|
|
293
|
+
|
|
294
|
+
# ─── Dorker (Internal) ──────────────────────────────────────
|
|
295
|
+
class ZombieDorker:
|
|
296
|
+
def __init__(self, config: Config): self.config=config
|
|
297
|
+
def execute(self, urls: Set[str]) -> Set[str]:
|
|
298
|
+
filtered=set()
|
|
299
|
+
for url in urls:
|
|
300
|
+
for pattern in self.config.dork_patterns:
|
|
301
|
+
if re.search(pattern, url, re.IGNORECASE):
|
|
302
|
+
filtered.add(url); break
|
|
303
|
+
return filtered
|
|
304
|
+
|
|
305
|
+
# ─── Vulnerability Checks ──────────────────────────────────
|
|
306
|
+
def inject_payload(url, param, payload):
|
|
307
|
+
parsed=urlparse(url)
|
|
308
|
+
q=parse_qs(parsed.query); q[param]=[payload]
|
|
309
|
+
return urlunparse(parsed._replace(query=urlencode(q, doseq=True)))
|
|
310
|
+
|
|
311
|
+
async def zombie_xss(sm, url, param, config):
|
|
312
|
+
for p in ['"><script>alert(1)</script>','<img src=x onerror=alert(1)>']:
|
|
313
|
+
test_url=inject_payload(url,param,p)
|
|
314
|
+
resp=await sm.fetch(test_url)
|
|
315
|
+
if p in resp["text"]:
|
|
316
|
+
return {"vuln_type":"XSS","severity":"HIGH","confidence":95,"param":param,
|
|
317
|
+
"evidence":resp["text"][:200],"response_length":len(resp["text"]),
|
|
318
|
+
"method":"reflected","url":url,"payload":p}
|
|
319
|
+
return None
|
|
320
|
+
|
|
321
|
+
async def zombie_sqli(sm, url, param, config):
|
|
322
|
+
errors=["sql syntax","mysql_fetch","unclosed quotation","SQLSTATE"]
|
|
323
|
+
for p in ["' OR '1'='1","1 AND 1=1"]:
|
|
324
|
+
resp=await sm.fetch(inject_payload(url,param,p))
|
|
325
|
+
if any(e in resp["text"].lower() for e in errors):
|
|
326
|
+
return {"vuln_type":"SQLi","severity":"CRITICAL","confidence":92,"param":param,
|
|
327
|
+
"evidence":resp["text"][:200],"response_length":len(resp["text"]),
|
|
328
|
+
"method":"error","url":url,"payload":p}
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
async def zombie_lfi(sm, url, param):
|
|
332
|
+
resp=await sm.fetch(inject_payload(url,param,"../../../../etc/passwd"))
|
|
333
|
+
if "root:x:" in resp["text"]:
|
|
334
|
+
return {"vuln_type":"LFI","severity":"HIGH","confidence":100,"param":param,
|
|
335
|
+
"evidence":resp["text"][:200],"response_length":len(resp["text"]),
|
|
336
|
+
"method":"reflected","url":url,"payload":"../../../../etc/passwd"}
|
|
337
|
+
return None
|
|
338
|
+
|
|
339
|
+
# ─── Fuzzer ─────────────────────────────────────────────────
|
|
340
|
+
class ZombieFuzzer:
|
|
341
|
+
def __init__(self): self.baselines={}
|
|
342
|
+
async def baseline(self, sm, url):
|
|
343
|
+
resp=await sm.fetch(url)
|
|
344
|
+
bl={"length":len(resp["text"]),"hash":hashlib.md5(resp["text"].encode()).hexdigest(),
|
|
345
|
+
"text":resp["text"][:5000],"status":resp["status"],"headers":resp["headers"]}
|
|
346
|
+
self.baselines[url]=bl; return bl
|
|
347
|
+
async def fuzz(self, sm, url, param, payloads):
|
|
348
|
+
baseline=await self.baseline(sm, url); anomalies=[]
|
|
349
|
+
for p in payloads:
|
|
350
|
+
test_url=inject_payload(url,param,p)
|
|
351
|
+
start=time.monotonic(); resp=await sm.fetch(test_url); elapsed=time.monotonic()-start
|
|
352
|
+
len_diff=abs(len(resp["text"])-baseline["length"])/max(baseline["length"],1)
|
|
353
|
+
hash_diff=hashlib.md5(resp["text"].encode()).hexdigest()!=baseline["hash"]
|
|
354
|
+
status_diff=resp["status"]!=baseline["status"]
|
|
355
|
+
similarity=SequenceMatcher(None,resp["text"][:2000],baseline["text"][:2000]).ratio()
|
|
356
|
+
time_anomaly=elapsed>2.0
|
|
357
|
+
error_count=sum(resp["text"].lower().count(kw) for kw in ["sql","syntax","mysql","error","exception","warning"])
|
|
358
|
+
score=0.0
|
|
359
|
+
if len_diff>0.3: score+=0.3
|
|
360
|
+
if hash_diff: score+=0.2
|
|
361
|
+
if status_diff: score+=0.2
|
|
362
|
+
if similarity<0.7: score+=0.3
|
|
363
|
+
if time_anomaly: score+=0.2
|
|
364
|
+
score+=min(error_count*0.05,0.3)
|
|
365
|
+
if score>=0.9:
|
|
366
|
+
anomalies.append({"vuln_type":"XSS (fuzzer)","severity":"HIGH",
|
|
367
|
+
"confidence":int(score*100),"param":param,
|
|
368
|
+
"evidence":resp["text"][:200],"response_length":len(resp["text"]),
|
|
369
|
+
"method":"fuzzer","url":url,"payload":p})
|
|
370
|
+
return anomalies
|
|
371
|
+
|
|
372
|
+
# ─── Secrets Scanner ────────────────────────────────────────
|
|
373
|
+
SECRET_PATTERNS = [
|
|
374
|
+
('AWS Access Key', r'AKIA[0-9A-Z]{16}'),
|
|
375
|
+
('GitHub Token', r'ghp_[0-9a-zA-Z]{36}'),
|
|
376
|
+
('Slack Webhook', r'https://hooks.slack.com/services/[A-Za-z0-9/]+'),
|
|
377
|
+
('Generic JWT', r'eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+'),
|
|
378
|
+
]
|
|
379
|
+
async def zombie_find_secrets(url, text):
|
|
380
|
+
found=[]
|
|
381
|
+
for name,pat in SECRET_PATTERNS:
|
|
382
|
+
for match in re.finditer(pat, text):
|
|
383
|
+
found.append({"vuln_type":f"Secret: {name}","severity":"HIGH","confidence":95,
|
|
384
|
+
"param":match.group(),"evidence":text[match.start()-20:match.end()+20],
|
|
385
|
+
"response_length":0,"method":"pattern","url":url,"payload":match.group()})
|
|
386
|
+
return found
|
|
387
|
+
|
|
388
|
+
# ─── Triple Filter ──────────────────────────────────────────
|
|
389
|
+
class NinjaFilter:
|
|
390
|
+
def apply(self, findings):
|
|
391
|
+
return [f for f in findings if f.get("evidence") and f.get("confidence",0)>=90]
|
|
392
|
+
class SamuraiFilter:
|
|
393
|
+
def apply(self, findings):
|
|
394
|
+
return [f for f in findings if f.get("severity") in ("HIGH","CRITICAL") and f.get("response_length",0)>0 and f.get("confidence",0)>=90]
|
|
395
|
+
class GhostFilter:
|
|
396
|
+
def apply(self, findings):
|
|
397
|
+
return [f for f in findings if (f.get("method") in ("timing","oob") or f.get("confidence",0)>=98) and f.get("confidence",0)>=90]
|
|
398
|
+
class FilterPipeline:
|
|
399
|
+
def __init__(self, config: Config):
|
|
400
|
+
self.filters=[]
|
|
401
|
+
for name in config.active_filters:
|
|
402
|
+
if name=="ninja": self.filters.append(NinjaFilter())
|
|
403
|
+
elif name=="samurai": self.filters.append(SamuraiFilter())
|
|
404
|
+
elif name=="ghost": self.filters.append(GhostFilter())
|
|
405
|
+
def apply(self, findings):
|
|
406
|
+
for f in self.filters: findings=f.apply(findings)
|
|
407
|
+
return findings
|
|
408
|
+
|
|
409
|
+
# ─── Deep Scan ──────────────────────────────────────────────
|
|
410
|
+
async def zombie_deep_scan(sm, filtered_findings):
|
|
411
|
+
for finding in filtered_findings:
|
|
412
|
+
if finding["vuln_type"]=="SQLi" and finding.get("method")!="timing":
|
|
413
|
+
start=time.monotonic()
|
|
414
|
+
await sm.fetch(inject_payload(finding["url"], finding["param"], "' AND SLEEP(3)--"))
|
|
415
|
+
if time.monotonic()-start>2.5:
|
|
416
|
+
finding["confidence"]=99; finding["method"]="timing"
|
|
417
|
+
return filtered_findings
|
|
418
|
+
|
|
419
|
+
# ─── Evidence Locker ────────────────────────────────────────
|
|
420
|
+
try:
|
|
421
|
+
from cryptography.fernet import Fernet
|
|
422
|
+
CRYPTO_OK = True
|
|
423
|
+
except: CRYPTO_OK = False
|
|
424
|
+
|
|
425
|
+
class ZombieEvidenceLocker:
|
|
426
|
+
def __init__(self, config: Config):
|
|
427
|
+
self.config = config
|
|
428
|
+
self.key_file = Path(config.output_dir) / "evidence" / ".key"
|
|
429
|
+
if not self.key_file.exists() and CRYPTO_OK:
|
|
430
|
+
key = Fernet.generate_key()
|
|
431
|
+
self.key_file.write_bytes(key)
|
|
432
|
+
self.cipher = Fernet(self.key_file.read_bytes()) if CRYPTO_OK and self.key_file.exists() else None
|
|
433
|
+
|
|
434
|
+
def lock(self, finding: Dict):
|
|
435
|
+
fid = finding.get("id", str(uuid.uuid4())[:8])
|
|
436
|
+
evidence_path = Path(self.config.output_dir) / "evidence" / f"{fid}.zombie"
|
|
437
|
+
data = json.dumps({
|
|
438
|
+
"url": finding["url"],
|
|
439
|
+
"vuln_type": finding["vuln_type"],
|
|
440
|
+
"payload": finding.get("payload",""),
|
|
441
|
+
"evidence": finding.get("evidence",""),
|
|
442
|
+
}).encode()
|
|
443
|
+
if self.cipher: data = self.cipher.encrypt(data)
|
|
444
|
+
evidence_path.write_bytes(data)
|
|
445
|
+
return str(evidence_path)
|
|
446
|
+
|
|
447
|
+
# ─── Time Slice ─────────────────────────────────────────────
|
|
448
|
+
class ZombieTimeSlice:
|
|
449
|
+
def __init__(self, config: Config): self.config=config
|
|
450
|
+
async def wait_if_needed(self):
|
|
451
|
+
while self.config.enable_time_slice:
|
|
452
|
+
now=datetime.utcnow().hour
|
|
453
|
+
if self.config.time_slice_start <= now < self.config.time_slice_end:
|
|
454
|
+
break
|
|
455
|
+
print(f"{YELLOW}⏳ Zombie Time Slice: di luar jam operasi. Menunggu...{NC}")
|
|
456
|
+
await asyncio.sleep(300)
|
|
457
|
+
|
|
458
|
+
# ─── Auto-PoC ───────────────────────────────────────────────
|
|
459
|
+
class ZombieAutoPoC:
|
|
460
|
+
def __init__(self, config: Config): self.config=config
|
|
461
|
+
def generate(self, finding: Dict):
|
|
462
|
+
fid = finding.get("id", str(uuid.uuid4())[:8])
|
|
463
|
+
poc_dir = Path(self.config.output_dir) / "poc" / fid
|
|
464
|
+
poc_dir.mkdir(exist_ok=True)
|
|
465
|
+
curl_cmd = f"curl -X GET '{finding['url']}' -H 'User-Agent: ZombieVDP/1.0'"
|
|
466
|
+
if finding.get("param"):
|
|
467
|
+
curl_cmd = f"curl -X GET '{inject_payload(finding['url'], finding['param'], finding['payload'])}' -H 'User-Agent: ZombieVDP/1.0'"
|
|
468
|
+
(poc_dir / "reproduce.sh").write_text(f"#!/bin/bash\n{curl_cmd}\n")
|
|
469
|
+
py_script = f"""import requests
|
|
470
|
+
r = requests.get("{inject_payload(finding['url'], finding['param'], finding['payload'])}", headers={{"User-Agent":"ZombieVDP/1.0"}})
|
|
471
|
+
print(r.status_code, len(r.text))
|
|
472
|
+
"""
|
|
473
|
+
(poc_dir / "reproduce.py").write_text(py_script)
|
|
474
|
+
return str(poc_dir)
|
|
475
|
+
|
|
476
|
+
# ─── Memory ─────────────────────────────────────────────────
|
|
477
|
+
class ZombieMemory:
|
|
478
|
+
def __init__(self, config: Config):
|
|
479
|
+
self.config = config
|
|
480
|
+
self.file = Path(config.output_dir) / config.memory_file
|
|
481
|
+
def load(self):
|
|
482
|
+
if self.file.exists(): return json.loads(self.file.read_text())
|
|
483
|
+
return {"scanned_urls":[], "findings_ids":[], "last_step":None}
|
|
484
|
+
def save(self, state: Dict):
|
|
485
|
+
self.file.write_text(json.dumps(state))
|
|
486
|
+
|
|
487
|
+
# ─── Report ─────────────────────────────────────────────────
|
|
488
|
+
def zombie_report(db, output_dir):
|
|
489
|
+
findings=db.all()
|
|
490
|
+
if not findings:
|
|
491
|
+
print(f"{YELLOW}⚠ No high/critical findings to report.{NC}")
|
|
492
|
+
return None
|
|
493
|
+
ts=datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
494
|
+
report_path=output_dir/"reports"/f"zombie_{ts}.html"
|
|
495
|
+
with open(report_path,'w',encoding='utf-8') as f:
|
|
496
|
+
f.write("<html><head><title>Zombie VDP Report</title><style>body{font-family:monospace;background:#0a0e27;color:#ccc;padding:20px}h1{color:#00d4ff}.finding{margin:8px 0;padding:5px;border-left:3px solid #ff8800}</style></head><body>")
|
|
497
|
+
f.write(f"<h1>🧟 Zombie VDP Report</h1><p>Generated: {datetime.now()}</p><h2>Findings ({len(findings)})</h2>")
|
|
498
|
+
for row in findings:
|
|
499
|
+
url,vuln,sev,param,conf=row[1],row[2],row[3],row[4],row[5]
|
|
500
|
+
f.write(f"<div class='finding'><strong>{vuln}</strong> [{sev}] @ <a href='{url}'>{url}</a> | Param: {param} | Confidence: {conf}%</div>")
|
|
501
|
+
f.write("</body></html>")
|
|
502
|
+
logging.info(f"Report saved to {report_path}")
|
|
503
|
+
return report_path
|
|
504
|
+
|
|
505
|
+
# ─── Main ───────────────────────────────────────────────────
|
|
506
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
|
|
507
|
+
logger = logging.getLogger("ZombieVDP")
|
|
508
|
+
|
|
509
|
+
async def main_async():
|
|
510
|
+
config = Config.from_yaml()
|
|
511
|
+
db = Database(config.db_name)
|
|
512
|
+
pipeline = FilterPipeline(config)
|
|
513
|
+
evidence_locker = ZombieEvidenceLocker(config)
|
|
514
|
+
time_slice = ZombieTimeSlice(config)
|
|
515
|
+
auto_poc = ZombieAutoPoC(config)
|
|
516
|
+
memory = ZombieMemory(config)
|
|
517
|
+
|
|
518
|
+
if config.password:
|
|
519
|
+
pwd = getpass.getpass(f"{YELLOW}🔐 Password: {NC}")
|
|
520
|
+
if pwd != config.password:
|
|
521
|
+
print(f"{RED}✗ Wrong password.{NC}"); return
|
|
522
|
+
zombie_art = f"""
|
|
523
|
+
{CYAN}███████╗ ██████╗ ███╗ ███╗██████╗ ██╗███████╗
|
|
524
|
+
╚══███╔╝██╔═══██╗████╗ ████║██╔══██╗██║██╔════╝
|
|
525
|
+
███╔╝ ██║ ██║██╔████╔██║██████╔╝██║█████╗
|
|
526
|
+
███╔╝ ██║ ██║██║╚██╔╝██║██╔══██╗██║██╔══╝
|
|
527
|
+
███████╗╚██████╔╝██║ ╚═╝ ██║██████╔╝██║███████╗
|
|
528
|
+
╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚══════╝{NC}
|
|
529
|
+
{PINK} ☣ Z O M B I E S Y S T E M ☣{NC}
|
|
530
|
+
"""
|
|
531
|
+
print(zombie_art)
|
|
532
|
+
zombie_loading_bar(2)
|
|
533
|
+
else:
|
|
534
|
+
print(f"{YELLOW}⚠ No password set – langsung menjalankan.{NC}")
|
|
535
|
+
zombie_loading_bar(2)
|
|
536
|
+
|
|
537
|
+
await time_slice.wait_if_needed()
|
|
538
|
+
|
|
539
|
+
async with SessionManager(config) as sm:
|
|
540
|
+
ctx = {"all_urls": set(), "subdomains": set(), "raw_findings": [], "final_findings": []}
|
|
541
|
+
state = memory.load()
|
|
542
|
+
if state.get("last_step"):
|
|
543
|
+
print(f"{YELLOW}🧠 Zombie Memory: Melanjutkan dari '{state['last_step']}'...{NC}")
|
|
544
|
+
ctx["all_urls"] = set(state.get("all_urls", []))
|
|
545
|
+
ctx["subdomains"] = set(state.get("subdomains", []))
|
|
546
|
+
|
|
547
|
+
async def zombie_recon_phase():
|
|
548
|
+
urls = set()
|
|
549
|
+
for domain in config.target_domains:
|
|
550
|
+
if config.enable_wayback: urls |= await zombie_wayback(sm, domain)
|
|
551
|
+
if config.enable_commoncrawl: urls |= await zombie_commoncrawl(sm, domain)
|
|
552
|
+
if config.enable_crtsh: urls |= await zombie_crtsh(sm, domain)
|
|
553
|
+
if config.enable_otx and config.otx_api_key: urls |= await zombie_otx(sm, domain, config.otx_api_key)
|
|
554
|
+
if config.enable_urlscan and config.urlscan_api_key: urls |= await zombie_urlscan(sm, domain, config.urlscan_api_key)
|
|
555
|
+
subs = await zombie_dns_enum(domain)
|
|
556
|
+
ctx["subdomains"] |= subs
|
|
557
|
+
urls |= {f"https://{s}" for s in subs}
|
|
558
|
+
ctx["all_urls"] = urls
|
|
559
|
+
memory.save({"last_step":"zombie_recon","all_urls":list(urls),"subdomains":list(ctx["subdomains"])})
|
|
560
|
+
|
|
561
|
+
async def zombie_crawl_phase():
|
|
562
|
+
crawler = ZombieCrawler(config)
|
|
563
|
+
crawled = await crawler.crawl(sm, config.target_domains, ctx["all_urls"])
|
|
564
|
+
ctx["all_urls"] |= crawled
|
|
565
|
+
memory.save({"last_step":"zombie_crawl","all_urls":list(ctx["all_urls"])})
|
|
566
|
+
|
|
567
|
+
async def zombie_dork_phase():
|
|
568
|
+
dorker = ZombieDorker(config)
|
|
569
|
+
dorked = dorker.execute(ctx["all_urls"])
|
|
570
|
+
ctx["all_urls"] = dorked
|
|
571
|
+
memory.save({"last_step":"zombie_dork","all_urls":list(ctx["all_urls"])})
|
|
572
|
+
|
|
573
|
+
async def zombie_scan_phase():
|
|
574
|
+
raw = []
|
|
575
|
+
urls = list(ctx["all_urls"])[:100]
|
|
576
|
+
for url in urls:
|
|
577
|
+
params = set(parse_qs(urlparse(url).query).keys())
|
|
578
|
+
for param in params:
|
|
579
|
+
r = await zombie_xss(sm, url, param, config)
|
|
580
|
+
if r: raw.append(r)
|
|
581
|
+
r = await zombie_sqli(sm, url, param, config)
|
|
582
|
+
if r: raw.append(r)
|
|
583
|
+
r = await zombie_lfi(sm, url, param)
|
|
584
|
+
if r: raw.append(r)
|
|
585
|
+
if config.enable_fuzzer and params:
|
|
586
|
+
fuzzer = ZombieFuzzer()
|
|
587
|
+
for param in params:
|
|
588
|
+
anoms = await fuzzer.fuzz(sm, url, param, ['"><script>alert(1)</script>','<img src=x onerror=alert(1)>'])
|
|
589
|
+
raw.extend(anoms)
|
|
590
|
+
if config.enable_secrets_scan:
|
|
591
|
+
for url in urls[:50]:
|
|
592
|
+
resp = await sm.fetch(url)
|
|
593
|
+
if resp["text"]: raw.extend(await zombie_find_secrets(url, resp["text"]))
|
|
594
|
+
ctx["raw_findings"] = raw
|
|
595
|
+
memory.save({"last_step":"zombie_scan"})
|
|
596
|
+
|
|
597
|
+
async def zombie_filter_phase():
|
|
598
|
+
print(f"\n{PINK}{BOLD}FILTERING WITH TRIPLE WARRIORS...{NC}")
|
|
599
|
+
ninja_animation(); samurai_animation(); ghost_animation()
|
|
600
|
+
filtered = pipeline.apply(ctx["raw_findings"])
|
|
601
|
+
ctx["final_findings"] = filtered
|
|
602
|
+
memory.save({"last_step":"zombie_filter"})
|
|
603
|
+
|
|
604
|
+
async def zombie_deep_scan_phase():
|
|
605
|
+
findings = await zombie_deep_scan(sm, ctx["final_findings"])
|
|
606
|
+
for f in findings:
|
|
607
|
+
fid = db.save(f)
|
|
608
|
+
f["id"] = fid
|
|
609
|
+
ctx["final_findings"] = findings
|
|
610
|
+
memory.save({"last_step":"zombie_deep_scan"})
|
|
611
|
+
|
|
612
|
+
async def zombie_evidence_phase():
|
|
613
|
+
if not config.enable_evidence_locker: return
|
|
614
|
+
for f in ctx["final_findings"]:
|
|
615
|
+
evidence_locker.lock(f)
|
|
616
|
+
print(f"{GREEN}🔒 Evidence locked.{NC}")
|
|
617
|
+
|
|
618
|
+
async def zombie_poc_phase():
|
|
619
|
+
if not config.enable_auto_poc: return
|
|
620
|
+
for f in ctx["final_findings"]:
|
|
621
|
+
auto_poc.generate(f)
|
|
622
|
+
print(f"{GREEN}⚡ Auto-PoC generated.{NC}")
|
|
623
|
+
|
|
624
|
+
async def zombie_report_phase():
|
|
625
|
+
path = zombie_report(db, Path(config.output_dir))
|
|
626
|
+
if path: print(f"{GREEN}✔ Report generated: {path}{NC}")
|
|
627
|
+
|
|
628
|
+
phases = {
|
|
629
|
+
"zombie_recon": zombie_recon_phase,
|
|
630
|
+
"zombie_crawl": zombie_crawl_phase,
|
|
631
|
+
"zombie_dork": zombie_dork_phase,
|
|
632
|
+
"zombie_scan": zombie_scan_phase,
|
|
633
|
+
"zombie_filter": zombie_filter_phase,
|
|
634
|
+
"zombie_deep_scan": zombie_deep_scan_phase,
|
|
635
|
+
"zombie_evidence": zombie_evidence_phase,
|
|
636
|
+
"zombie_poc": zombie_poc_phase,
|
|
637
|
+
"zombie_report": zombie_report_phase,
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
for step in config.workflow_steps:
|
|
641
|
+
if step in phases:
|
|
642
|
+
print(f"\n{CYAN}{'='*60}{NC}")
|
|
643
|
+
print(f"{CYAN}{BOLD}[{step.upper()}]{NC}")
|
|
644
|
+
await zombie_progress_bar(step, 1.5)
|
|
645
|
+
await phases[step]()
|
|
646
|
+
|
|
647
|
+
if __name__ == "__main__":
|
|
648
|
+
import asyncio
|
|
649
|
+
asyncio.run(main_async())
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zombie-vdp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: 🧟♂️ Zombie VDP – Ultimate Identity Edition
|
|
5
|
+
Home-page: https://github.com/pormes-90/ZOMBIE.git
|
|
6
|
+
Author: Dikha Pormes
|
|
7
|
+
Author-email: pormesdikha90@gmail.com
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Information Technology
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Topic :: Security
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: aiohttp>=3.8
|
|
17
|
+
Requires-Dist: pyyaml>=6.0
|
|
18
|
+
Requires-Dist: dnspython>=2.3
|
|
19
|
+
Requires-Dist: psutil>=5.9
|
|
20
|
+
Requires-Dist: colorama>=0.4
|
|
21
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
22
|
+
Requires-Dist: cryptography>=41.0
|
|
23
|
+
Provides-Extra: full
|
|
24
|
+
Requires-Dist: scikit-learn>=1.3; extra == "full"
|
|
25
|
+
Requires-Dist: matplotlib>=3.7; extra == "full"
|
|
26
|
+
Dynamic: author
|
|
27
|
+
Dynamic: author-email
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: description
|
|
30
|
+
Dynamic: description-content-type
|
|
31
|
+
Dynamic: home-page
|
|
32
|
+
Dynamic: provides-extra
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: requires-python
|
|
35
|
+
Dynamic: summary
|
|
36
|
+
|
|
37
|
+
# 🧟♂️ Zombie VDP – Ultimate Identity Edition
|
|
38
|
+
|
|
39
|
+
**NASA VDP Compliant · 100% Passive · Triple Filter · Evidence Locker · Time Slice · Auto-PoC · Memory**
|
|
40
|
+
|
|
41
|
+
Zombie VDP adalah kerangka kerja bug bounty pribadi yang menggabungkan seluruh pipeline pengujian keamanan dalam satu nafas. Semua fitur dirancang dari nol, bukan meniru alat lain, melainkan menciptakan identitas sendiri: **Ninja**, **Samurai**, dan **Ghost**.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 🔥 Fitur Legendaris
|
|
46
|
+
|
|
47
|
+
### ⚔️ Triple Filter (Ninja · Samurai · Ghost)
|
|
48
|
+
- **Ninja Filter** – Hanya menerima temuan dengan bukti nyata (evidence).
|
|
49
|
+
- **Samurai Filter** – Hanya HIGH/CRITICAL dengan respons signifikan.
|
|
50
|
+
- **Ghost Filter** – Hanya confidence ≥98% atau metode timing/OOB.
|
|
51
|
+
|
|
52
|
+
### 🧪 Zombie Deep Scan
|
|
53
|
+
Validasi lanjutan setelah filter: serangan timing untuk konfirmasi SQLi, dsb.
|
|
54
|
+
|
|
55
|
+
### 🔒 Zombie Evidence Locker
|
|
56
|
+
Setiap temuan yang lolos filter disimpan sebagai bukti terenkripsi (format `.zombie`). Tidak bisa dibuka tanpa kunci.
|
|
57
|
+
|
|
58
|
+
### ⏳ Zombie Time Slice
|
|
59
|
+
Zombie hanya beroperasi pada jam yang diizinkan (misal 02:00–05:00 UTC). Di luar jam itu, ia berhenti dan menunggu.
|
|
60
|
+
|
|
61
|
+
### ⚡ Zombie Auto-PoC
|
|
62
|
+
Setiap temuan langsung dilengkapi skrip `curl` dan Python untuk mereproduksi kerentanan. Tim triase tinggal menjalankan.
|
|
63
|
+
|
|
64
|
+
### 🧠 Zombie Memory
|
|
65
|
+
Jika pemindaian terhenti, Zombie mengingat titik terakhir dan bisa melanjutkan tanpa mengulang.
|
|
66
|
+
|
|
67
|
+
### 🕸️ Zombie Crawler & Dorking (Internal)
|
|
68
|
+
Crawler mandiri + penyaring URL berbasis pola (tanpa search engine). 100% pasif, tidak meninggalkan jejak bot.
|
|
69
|
+
|
|
70
|
+
### 🛡️ Scanner Kerentanan Bawaan
|
|
71
|
+
- Reflected XSS
|
|
72
|
+
- SQL Injection (error‑based + timing)
|
|
73
|
+
- Local File Inclusion (LFI)
|
|
74
|
+
- Server‑Side Template Injection (SSTI)
|
|
75
|
+
- Open Redirect
|
|
76
|
+
- Adaptive Fuzzer
|
|
77
|
+
- Secrets Scanner (AWS, GitHub, Slack, JWT)
|
|
78
|
+
|
|
79
|
+
### 📊 Laporan Profesional
|
|
80
|
+
HTML report siap kirim ke program VDP. Ringan, rapi, hanya berisi temuan high/critical.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 🚀 Menjalankan
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python zombie_vdp.py
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
zombie_vdp/__init__.py
|
|
5
|
+
zombie_vdp/config.yaml
|
|
6
|
+
zombie_vdp/zombie.py
|
|
7
|
+
zombie_vdp.egg-info/PKG-INFO
|
|
8
|
+
zombie_vdp.egg-info/SOURCES.txt
|
|
9
|
+
zombie_vdp.egg-info/dependency_links.txt
|
|
10
|
+
zombie_vdp.egg-info/entry_points.txt
|
|
11
|
+
zombie_vdp.egg-info/requires.txt
|
|
12
|
+
zombie_vdp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zombie_vdp
|