Skip to content

Campaign Management

Creating and managing phishing campaigns: email template design, SMTP configuration, landing page creation, site cloning, tracking pixel integration, and campaign metrics analysis.


Campaign Lifecycle

Every phishing campaign follows a defined state progression from creation through delivery:

stateDiagram-v2
    [*] --> draft: Create campaign
    draft --> sending: Send campaign
    sending --> sent: All emails delivered
    sending --> error: All sends failed

Workflow overview:

  1. Create campaign -- Define name, sender identity, SMTP settings, and email template
  2. Configure SMTP -- Choose local relay SMTP or external SMTP server
  3. Select template -- Use a built-in template, import a custom EML file, or design from scratch
  4. Import targets -- Upload CSV with target email addresses, names, and companies
  5. Attach files -- Optionally attach payloads or documents to the campaign email
  6. Preview email -- Render the email with variable substitution to verify appearance
  7. Send campaign -- Dispatch emails asynchronously through the relay
  8. Monitor metrics -- Track opens, clicks, and credential captures in real time

Creating a Campaign

Create a new phishing campaign with POST /api/v1/phishing/campaigns.

Request body:

Field Type Required Description
name string Yes Campaign name
template_id string Yes Built-in template ID (password-reset, invoice, it-support) or custom for imported EML
from_email string Yes Sender email address
from_name string No Sender display name
smtp_host string No External SMTP host (empty = use relay local SMTP)
smtp_port int No External SMTP port
smtp_username string No SMTP authentication username
smtp_password string No SMTP authentication password
smtp_tls bool No Enable STARTTLS
landing_url string No Base URL for tracking/landing page infrastructure
delay_min_ms int No Minimum delay between emails (ms)
delay_max_ms int No Maximum delay between emails (ms)
bounce_to string No Bounce address for NDRs

Delay Settings

Use delay_min_ms and delay_max_ms to add random jitter between emails. This helps avoid rate limiting and makes the sending pattern appear more natural. A common range is 5000-15000 ms (5-15 seconds).

curl -s -X POST https://stentor.app/api/v1/phishing/campaigns \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q1 Password Reset Campaign",
    "template_id": "password-reset",
    "from_email": "[email protected]",
    "from_name": "IT Security Team",
    "landing_url": "https://10.0.0.50:8443",
    "delay_min_ms": 5000,
    "delay_max_ms": 15000
  }'
curl -s -X POST https://stentor.app/api/v1/phishing/campaigns \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Invoice Campaign",
    "template_id": "invoice",
    "from_email": "[email protected]",
    "from_name": "Billing Department",
    "smtp_host": "smtp.external-provider.com",
    "smtp_port": 587,
    "smtp_username": "[email protected]",
    "smtp_password": "app-password-here",
    "smtp_tls": true,
    "landing_url": "https://10.0.0.50:8443",
    "bounce_to": "[email protected]",
    "delay_min_ms": 10000,
    "delay_max_ms": 30000
  }'

Response -- returns the full campaign object with a generated id and status: "draft".


SMTP Configuration

Stentor supports two SMTP delivery paths: the relay's embedded SMTP server (local) or an external SMTP relay.

Local Relay SMTP

When smtp_host is empty in the campaign configuration, emails route through the relay's embedded SMTP server. The relay runs an emersion/go-smtp server on the configured port.

Configuration:

  • Port -- Set via the relay's SMTP_PORT environment variable
  • Authentication -- No authentication required for local delivery (localhost only)
  • STARTTLS -- Supported with auto-generated or provided TLS certificates
  • Max message size -- 10 MB per email
  • Max recipients -- 50 per message

TLS Certificates

When SMTP_ALLOW_INSECURE is not set, the relay auto-generates a TLS certificate for STARTTLS support using the same certificate infrastructure as the C2 listener.

External SMTP

When smtp_host is provided in the campaign, the relay connects to an external SMTP server for delivery.

Connection flow:

  1. TCP connection to smtp_host:smtp_port
  2. EHLO handshake
  3. STARTTLS upgrade (if server supports it)
  4. PLAIN authentication (if smtp_username is provided)
  5. MAIL FROM / RCPT TO / DATA sequence

Key behaviors:

  • Bounce address -- When bounce_to is set, it is used as the SMTP envelope sender (MAIL FROM), while from_email is used in the email From header. This separates NDR delivery from the visible sender identity.
  • TLS validation -- Certificate validation is skipped (InsecureSkipVerify: true) for compatibility with varied SMTP servers and self-signed certificates.

Comparison

Feature Local Relay SMTP External SMTP
Authentication None (localhost) PLAIN via STARTTLS
Setup Zero config Requires credentials
Sender control Full (any address) Limited by provider SPF/DKIM
Deliverability Depends on relay IP reputation Inherits provider reputation
TLS Auto-generated cert Server-provided cert
Bounce handling Direct to relay Configurable bounce address
Rate limiting None (self-hosted) Subject to provider limits

Email Templates

Built-in Templates

Stentor ships with three ready-to-use email templates, rendered using the hermes email generation library for responsive, professional HTML emails.

password-reset -- Password Reset Request

Corporate IT-style password reset email. Simulates a security team requesting the user to reset their password.

  • Default subject: Action Required: Reset Your {{.CompanyName}} Password
  • Call to action: "Reset Password" button (red, links to {{.ActionURL}})
  • Urgency: Link expires in 24 hours

invoice -- Invoice Notification

Finance/billing notification email. Simulates an invoice or payment request.

  • Default subject: Invoice #{{.InvoiceNumber}} from {{.CompanyName}}
  • Call to action: "View Invoice" button (green, links to {{.ActionURL}})
  • Custom variables: {{.InvoiceNumber}} (defaults to INV-YYYYMMDD), {{.Amount}} (defaults to $1,250.00)

it-support -- IT Support Verification

IT department account verification request. Creates urgency around suspicious account activity.

  • Default subject: {{.CompanyName}} IT: Verify Your Account
  • Call to action: "Verify Account" button (blue, links to {{.ActionURL}})
  • Urgency: Failure to verify within 48 hours may result in account suspension

Template Variables

All built-in templates support these variables, which are substituted at render time:

Variable Description Example Value
{{.RecipientName}} Target's name John Smith
{{.RecipientEmail}} Target's email [email protected]
{{.CompanyName}} Company name for branding Acme Corp
{{.ActionURL}} Phishing link (landing page) https://10.0.0.50:8443/click/abc12345
{{.TrackingPixelURL}} Invisible tracking pixel https://10.0.0.50:8443/track/abc12345.gif

Tracking Pixel Injection

All templates automatically inject a 1x1 transparent tracking pixel (<img> tag) before the closing </body> tag. This fires an email_opened event when the recipient's email client loads the image.

Custom Templates (EML Import)

Import a custom email template from an EML file using POST /api/v1/phishing/campaigns/:id/template/import.

The endpoint accepts either:

  • Multipart form upload -- file field with the EML file
  • JSON body -- eml_data field containing the raw EML content as a string

EML parsing behavior:

  • Parses RFC 5322 email format
  • Extracts subject line (with MIME word decoding)
  • Extracts HTML body and plain text body
  • Supports multipart/mixed and multipart/alternative MIME structures
  • Stores extracted content as the campaign's custom template

Custom template variables (substituted when sending):

Variable Description
{{.ToName}} Target's name
{{.ToEmail}} Target's email
{{.FromName}} Sender display name
{{.FromEmail}} Sender email address
{{.CompanyName}} Company name for branding
{{.TrackingURL}} Tracking pixel URL
{{.LandingURL}} Landing page URL
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/template/import" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "eml_data": "From: [email protected]\r\nTo: {{.ToEmail}}\r\nSubject: Important Update from {{.CompanyName}}\r\nMIME-Version: 1.0\r\nContent-Type: text/html\r\n\r\n<html><body><h1>Hello {{.ToName}}</h1><p>Please review this update.</p><a href=\"{{.LandingURL}}\">Click here</a></body></html>"
  }'
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/template/import" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@/path/to/template.eml"

Response:

{
  "subject": "Important Update from {{.CompanyName}}",
  "has_html": true,
  "has_text": false
}

Email Preview

Preview a rendered email before sending with POST /api/v1/phishing/campaigns/:id/preview.

curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/preview" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "target_email": "[email protected]",
    "target_name": "John Smith",
    "company_name": "Acme Corp"
  }'

Response includes the rendered subject, html, and text versions of the email with all variables substituted.

Built-in Template Preview

Built-in templates (password-reset, invoice, it-support) are rendered server-side by the relay using the hermes library. Preview for these templates returns a placeholder indicating the template ID, since full rendering requires an active relay connection. Custom (EML-imported) templates render fully on the server side.


Target Management

Importing Targets

Import targets via CSV with POST /api/v1/phishing/campaigns/:id/targets/import.

CSV format: email,name,company

  • Header row is auto-detected and skipped (if it contains "email" or "name")
  • Each target receives a unique 8-character tracking ID (UUID prefix)
  • Status starts as pending
  • Rows with empty or invalid email addresses (missing @) are silently skipped
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/targets/import" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "csv_data": "email,name,company\[email protected],John Smith,Target Corp\[email protected],Jane Doe,Target Corp\[email protected],Bob Wilson,Target Corp"
  }'

Response:

{
  "imported": 3
}

Listing Targets

Retrieve all targets for a campaign with GET /api/v1/phishing/campaigns/:id/targets.

PhishingTarget fields:

Field Type Description
id UUID Target ID
email string Target email address
name string Target name
company string Target company
tracking_id string 8-character unique tracking ID
status string pending, sent, opened, clicked, captured, error
sent_at timestamp When email was delivered
opened_at timestamp When tracking pixel was loaded
clicked_at timestamp When phishing link was clicked
captured_at timestamp When credentials were submitted
error_message string Error details (if status is error)

File Attachments

Attach files to campaign emails for payload delivery or social engineering.

Upload Attachment

POST /api/v1/phishing/campaigns/:id/attachments -- multipart/form-data with a file field.

curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/attachments" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@/path/to/document.pdf"

Limits:

  • Maximum 10 MB per file
  • Maximum 10 MB total per campaign (across all attachments)
  • Content type is auto-detected from file content (with fallback to the upload's Content-Type header)

List Attachments

GET /api/v1/phishing/campaigns/:id/attachments -- returns attachment metadata (filename, content type, size).

Delete Attachment

DELETE /api/v1/phishing/campaigns/:id/attachments/:attachmentId -- removes an attachment from the campaign.

Email construction: Attachments are included as multipart/mixed MIME parts in the email. Each attachment is Base64-encoded with Content-Disposition: attachment headers. The HTML email body becomes the first MIME part, with attachments following.


Site Cloning

Clone existing websites to create convincing landing pages for credential harvesting or payload delivery.

Clone a Website

POST /api/v1/sites -- clone a target website.

The site cloner uses Colly for web scraping and goquery for HTML manipulation:

  1. Fetches the target page HTML
  2. Collects all referenced assets (CSS, JS, images, fonts, favicon)
  3. Rewrites all URLs (href, src, action attributes) to serve from the listener path
  4. Optionally injects keylogger JavaScript or invisible iframe

Limits:

  • Maximum total site size: 10 MB (HTML + all assets)
  • Crawl depth: 1 (target page only, no deep crawling)
  • Same-domain assets only (external CDN resources are left as-is)

Supported asset types:

Category Extensions
Stylesheets .css
Scripts .js
Images .png, .jpg, .jpeg, .gif, .svg
Fonts .woff, .woff2, .ttf, .eot
Icons .ico

Injection Options

Keylogger Injection

When enabled, a minimal JavaScript keylogger (~400 bytes minified) is injected before </body>:

  • Captures keypress events on the page
  • Buffers keystrokes for 500 ms (debounced)
  • Exfiltrates via Image pixel to /log endpoint (CORS-friendly)
  • Tracking ID correlates keystrokes to the specific cloned site
  • Self-contained IIFE to avoid global namespace pollution

iFrame Injection

When enabled, an invisible iframe is injected before </body>:

  • Positioned off-screen (top: -9999px, left: -9999px)
  • Zero opacity and zero dimensions
  • Loads the specified payload URL silently
  • Useful for drive-by payload delivery alongside a legitimate-looking cloned page

Site Management API

Method Endpoint Description
POST /api/v1/sites Clone a website
GET /api/v1/sites List all cloned sites
GET /api/v1/sites/:id Get cloned site details
DELETE /api/v1/sites/:id Delete a cloned site
GET /api/v1/sites/:id/keystrokes Get captured keystrokes for a site
GET /api/v1/keystrokes/captured Get all captured keystrokes across all sites

Landing Pages & Credential Capture

Landing pages are served by the relay's tracking server and capture credentials submitted by targets.

Built-in Landing Page Templates

Stentor includes three landing page templates that mimic common login portals:

microsoft -- Microsoft 365 Login

  • Styling: Segoe UI font, blue accent (#0067b8), clean white container
  • Fields: Email + Password
  • Elements: "Can't access your account?" link for realism

google -- Google Accounts Login

  • Styling: Google Sans / Roboto font, Material Design colors, rounded container with border
  • Fields: Email + Password
  • Elements: Colored Google logo, "Forgot email?" link, "Next" button

corporate -- Generic Corporate Login

  • Styling: Gradient background (#667eea to #764ba2), rounded card with drop shadow
  • Fields: Username + Password
  • Customization: Configurable company name and logo URL
  • Elements: "Forgot password?" link

URL Format

/landing/{page_id}/{tracking_id}
  • GET renders the login form
  • POST captures submitted credentials

Credential Capture Flow

sequenceDiagram
    participant Target
    participant Relay (Tracking)
    participant Backend
    participant Operator

    Target->>Relay (Tracking): Click link in email
    Note over Relay (Tracking): GET /click/{tracking_id}
    Relay (Tracking)->>Backend: link_clicked event
    Relay (Tracking)->>Target: 302 Redirect to landing page

    Target->>Relay (Tracking): Load landing page
    Note over Relay (Tracking): GET /landing/{page_id}/{tracking_id}
    Relay (Tracking)->>Target: Render login form

    Target->>Relay (Tracking): Submit credentials
    Note over Relay (Tracking): POST /landing/{page_id}/{tracking_id}
    Relay (Tracking)->>Backend: creds_captured event (username + password)

    alt Redirect URL configured
        Relay (Tracking)->>Target: 302 Redirect to configured URL
    else No redirect
        Relay (Tracking)->>Target: "Thank You" page
    end

    Backend->>Operator: Real-time UI update

Landing Page Configuration

Option Description Default
Default template Template used when page_id not found corporate
Company name Brand name shown on corporate template Your Company
Company logo Logo URL for corporate template (none)
Redirect URL Where to send target after credential capture https://www.google.com

Username extraction: The credential capture handler checks multiple common form field names in order: username, email, user. This ensures credentials are captured regardless of which landing page template is used.


Drive-by Download Pages

Serve fake software update pages that trick targets into downloading payloads.

Templates

Three drive-by page templates are available:

Template ID Theme Description
chrome Chrome Browser Update Mimics a Chrome browser update notification
windows Windows Update Mimics a Windows system update prompt
adobe Adobe Flash Update Mimics an Adobe Flash/Reader update page

URL Format

/update/{template_id}/{payload_id}

Query parameters:

Parameter Description Default
auto Set to true for automatic download after page load false
filename Override the displayed download filename Original payload filename

Workflow

  1. Host a payload -- Use the relay's HostPayload() function to register a payload in memory (called via WebSocket command from the backend)
  2. Generate the drive-by URL -- Construct the URL as /update/{template}/{payload_id} with optional query parameters
  3. Distribute the URL -- Include in a phishing email, embed in a cloned site iframe, or use as a redirect target
# Example drive-by URL with auto-download
https://10.0.0.50:8443/update/chrome/abc123-def456?auto=true&filename=ChromeSetup.exe

Payload Hosting

Payloads are stored in-memory on the relay. They persist only while the relay process is running. Restarting the relay clears all hosted payloads.


Tracking & Metrics

The relay's tracking server provides real-time visibility into campaign engagement.

Tracking Endpoints

Endpoint Method Event Fired Description
/track/{tracking_id}.gif GET email_opened Returns 1x1 transparent GIF, fires open event
/click/{tracking_id} GET link_clicked Fires click event, redirects to landing page
/landing/{page_id}/{tracking_id} POST creds_captured Captures username + password from form submission
/payload/{payload_id} GET payload_downloaded Serves hosted payload file

Event Propagation

Events flow from the relay tracking server through the WebSocket connection to the backend:

Relay (Tracking HTTP) --> WebSocket Event --> Backend PhishingService.UpdateTargetEvent() --> Database Update --> UI Real-time Update

Each event includes:

  • Tracking ID -- Correlates to a specific target
  • Timestamp -- UTC time of the event
  • User-Agent -- Target's browser identification
  • IP -- Target's IP address (with X-Forwarded-For / X-Real-IP support)

Target Status Progression

stateDiagram-v2
    [*] --> pending: Target imported
    pending --> sent: Email delivered
    sent --> opened: Tracking pixel loaded
    opened --> clicked: Link clicked
    clicked --> captured: Credentials submitted
    pending --> error: Send failed

Statuses progress forward only -- once a target reaches captured, earlier events (opens, clicks) are already recorded with timestamps.

CNA Script Events

The phishing service dispatches Cobalt Strike-compatible CNA events at each stage of the campaign lifecycle:

Event Arguments Description
sendmail_start $1 campaign_id, $2 target_count, $3 attachment_path, $4 bounce_to, $5 smtp_server, $6 subject, $7 template_id Campaign begins sending
sendmail_pre $1 campaign_id, $2 target_email Before each email is sent
sendmail_post $1 campaign_id, $2 target_email, $3 status, $4 server_message After successful email delivery
sendmail_error $1 campaign_id, $2 target_email, $3 error_message Email delivery failed
sendmail_done $1 campaign_id Campaign sending complete
CNA Hook Example
on sendmail_post {
    println("[Phishing] Email sent to " . $2 . " - Status: " . $3);
}

on sendmail_done {
    println("[Phishing] Campaign " . $1 . " complete");
}

Sending a Campaign

Launch email delivery with POST /api/v1/phishing/campaigns/:id/send.

curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/send" \
  -H "Authorization: Bearer $TOKEN"

Sending behavior:

  • Campaign status changes to sending
  • Emails are dispatched asynchronously via the relay WebSocket connection
  • The backend selects the first connected relay for delivery
  • Random delay between emails (configurable via delay_min_ms / delay_max_ms)
  • Each target's status updates individually as emails are sent (sent or error)
  • Final campaign status: sent (at least one succeeded) or error (all failed)

Relay Required

At least one relay must be connected via WebSocket. If no relays are available, the campaign immediately transitions to error status with the message "no connected relays".

Complete End-to-End Example

Full workflow from campaign creation through monitoring:

# 1. Create campaign
CAMPAIGN_ID=$(curl -s -X POST https://stentor.app/api/v1/phishing/campaigns \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q1 Security Awareness Test",
    "template_id": "password-reset",
    "from_email": "[email protected]",
    "from_name": "IT Security",
    "landing_url": "https://10.0.0.50:8443",
    "delay_min_ms": 5000,
    "delay_max_ms": 15000
  }' | jq -r '.id')

echo "Campaign ID: $CAMPAIGN_ID"

# 2. Import targets
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/targets/import" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "csv_data": "email,name,company\[email protected],John Smith,Target Corp\[email protected],Jane Doe,Target Corp\[email protected],Bob Wilson,Target Corp"
  }'

# 3. Preview email (optional)
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/preview" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "target_email": "[email protected]",
    "target_name": "John Smith",
    "company_name": "Target Corp"
  }' | jq '.subject, .html[:200]'

# 4. Send campaign
curl -s -X POST "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/send" \
  -H "Authorization: Bearer $TOKEN"

# 5. Monitor campaign status
curl -s "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID" \
  -H "Authorization: Bearer $TOKEN" | jq '{status, total_targets, sent_count}'

# 6. Check target engagement
curl -s "https://stentor.app/api/v1/phishing/campaigns/$CAMPAIGN_ID/targets" \
  -H "Authorization: Bearer $TOKEN" | jq '.[] | {email, status, opened_at, clicked_at, captured_at}'

Cross-references