TeamFiltration Cloud Identity Spray and Pivot
An adversary is using the TeamFiltration framework to spray M365 service accounts with default passwords from AWS infrastructure, subsequently harvesting data via the Graph API and probing internal VPN endpoints.
Based on research by Proofpoint 2026-10-10 12 steps · 5 queries T1041 T1087.004 T1110 T1110.003 T1110.004 T1133 T1213
Brief
Why Now
Proofpoint recently detailed a campaign where the TeamFiltration framework compromised several Microsoft 365 accounts. The adversary targeted service accounts using default passwords, bypassing standard identity protections. This hunt provides a method to find similar activity in your environment by looking for the specific patterns of TeamFiltration spraying and post-compromise data harvesting. TeamFiltration Campaign Compromises Seven Microsoft 365 Accounts Using Default Passwords: https://www.proofpoint.com/us/newsroom/news/teamfiltration-campaign-compromises-seven-microsoft-365-accounts-using-default
Phase 1: Scoping VPN Gateways
The first phase identifies your VPN and proxy gateways. By searching for devices handling SAML authentication and specific VPN paths, the hunt builds a scoping list. This list ensures later steps can accurately detect if an attacker pivoted from a cloud compromise to probing your internal perimeter.
Phase 2: Correlating Spray Patterns
The hunt then correlates authentication patterns in Entra ID. It runs two parallel queries: one to find source IPs with high failure rates and another to find successful logins to service accounts without MFA. This phase identifies the broad spraying activity and the specific accounts that failed to stop the breach.
Phase 3: Assessing Compromise Impact
An analyst or automated agent assesses the impact by joining these results. The hunt identifies a beachhead when a single source IP shows both high-volume failures across multiple accounts and a successful login to a vulnerable service account. This step filters out noise and focuses the investigation on confirmed compromises.
Phase 4: Investigating Post-Breach Activity
The final phase tracks post-breach activity across cloud and network surfaces. It queries for Microsoft Graph API token requests and file discovery in SharePoint or OneDrive. Simultaneously, it checks for HTTP requests from the attacker IPs to the VPN gateways identified in the first phase, uncovering attempts to move deeper into the network.
Hunt Blind Spots
This hunt faces two primary blind spots. First, cloud audit logs often have ingestion latency, meaning an attacker might harvest data before the activity appears in your telemetry. Second, if your proxy does not inspect TLS traffic to VPN gateways, the hunt cannot see the specific URI paths used during probing, limiting the visibility of the internal pivot attempts.
Running the Playbook
You can run this hunt using any hunt.md-aware runtime or by importing it into Huntbase. The playbook guides you through each phase, from initial scoping to automated compromise assessment. After running the queries, you can use the built-in decision steps to suspend compromised accounts and initiate a manual review of accessed files.
Steps
-
Identify VPN and proxy gateways
Query · scopingFind the hosts responsible for serving VPN authentication or proxying web traffic to identify where the adversary might probe internal infrastructure.
reads hb_http_activitysqlSELECT DISTINCT device_hostname FROM hb_http_activity WHERE (instr(',' || '{{vpn_paths}}' || ',', ',' || LOWER(url_path) || ',') > 0 OR LOWER(url_path) LIKE '%saml%') AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. A list of hostnames acting as VPN gateways or proxies. These will be used to scope later queries.
-
High-volume authentication failures
Query · baselineIdentify source IPs attempting to authenticate against numerous accounts, which is indicative of a password spray.
reads hb_auth_signinsqlSELECT src_endpoint_ip, COUNT(*) AS failures, COUNT(DISTINCT actor_user_name) AS distinct_accounts, MIN(time) AS first_attempt, MAX(time) AS last_attempt FROM hb_auth_signin WHERE activity_id = 5 AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY src_endpoint_ip HAVING failures >= {{failure_threshold}} ORDER BY failures DESCWhat a hit looks like. A list of IP addresses exhibiting spraying behavior. If silence, no high-volume spraying was detected from single IPs in the window.
-
Successful logins without MFA
Query · triageIdentify successful logins to service or functional accounts where MFA was not used, as these are the primary targets of this campaign.
reads hb_auth_signinsqlSELECT actor_user_name, src_endpoint_ip, provider, mfa, time FROM hb_auth_signin WHERE activity_id = 1 AND (mfa = 'false' OR mfa IS NULL) AND (LOWER(actor_user_name) LIKE '%svc%' OR LOWER(actor_user_name) LIKE '%service%' OR LOWER(actor_user_name) LIKE '%functional%') AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. Logins to vulnerable accounts. An analyst or agent will later correlate these with the spraying IPs.
-
Assess spraying impact
Agent triageCorrelate the failure patterns from AWS IPs with successful logins to service accounts to identify the beachhead.
-
Cloud resource and Graph API harvesting
Query · enrichmentIdentify data exfiltration attempts by searching for Graph API token requests and SharePoint/OneDrive access.
reads hb_cloud_api_activitysqlSELECT actor_user_name, api_operation, api_service_name, resource_name, src_endpoint_ip, time FROM hb_cloud_api_activity WHERE (LOWER(api_operation) LIKE '%token%' OR LOWER(api_service_name) IN ('sharepoint', 'onedrive', 'microsoft teams')) AND time >= datetime('now', '-{{lookback_days}} days') AND ('{{scope_users}}' = '' OR instr(',' || '{{scope_users}}' || ',', ',' || actor_user_name || ',') > 0) AND ('{{scope_ips}}' = '' OR instr(',' || '{{scope_ips}}' || ',', ',' || src_endpoint_ip || ',') > 0)What a hit looks like. Tokens being requested or files being browsed by the suspected compromised accounts. Silence suggests no harvesting was detected.
-
VPN endpoint probing from attacker IPs
Query · enrichmentCheck if the attacker IPs leveraged their foothold to probe internal VPN endpoints for further access.
reads hb_http_activitysqlSELECT device_hostname, src_endpoint_ip, url_path, user_agent, time FROM hb_http_activity WHERE (instr(',' || '{{vpn_paths}}' || ',', ',' || LOWER(url_path) || ',') > 0 OR LOWER(url_path) LIKE '%saml%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND ('{{scope_ips}}' = '' OR instr(',' || '{{scope_ips}}' || ',', ',' || src_endpoint_ip || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')What a hit looks like. HTTP requests to VPN auth paths from IPs identified in the compromise phase. Silence means no such probing was visible.
-
Analyze full attack chain
Agent triageWeigh the compromise verdict alongside subsequent discovery and probing to confirm a malicious intrusion.
-
Decide on intrusion response
DecisionRoute the hunt to remediation if an intrusion is confirmed.
-
Suspend compromised service account
Response actionIsolate the threat by disabling the compromised credentials.
-
Manual incident review
Analyst taskConduct a deeper investigation into data exfiltration and rotate any exposed secrets.
-
Hunt close-out
Analyst taskDocument the findings and recommend security hygiene improvements.
Coverage
Scenario coverage
| Stage | Covered | How, or why not |
|---|---|---|
| Password spraying from AWS infrastructure T1110.003 · T1110 |
Yes | high-volume-failures |
| Service account compromise T1110 |
Yes | successful-logins-no-mfa, assess-compromise |
| Cloud resource discovery and Graph API usage T1087.004 |
Yes | resource-harvesting |
| VPN node pivot and probing T1133 |
Yes | scoping-vpn-gateways, vpn-probing |
| Cloud data harvesting T1213 · T1041 |
Yes | resource-harvesting, analyze-attack-chain |
Blind spots
- Needs Real-time Entra ID Audit Logs. A 2-minute pivot window after compromise may be faster than the ingestion latency of cloud audit logs. It would answer whether exfiltration occurred before the logs were ingested.
- Needs hb_http_activity with decrypted URI paths. If the proxy does not inspect TLS traffic to the VPN gateway, the specific probing paths (e.g., /SAML20/SP) will not be visible. It would answer whether specific SAML endpoints were probed on the VPN gateway.
Parameters & data
Parameters
| Parameter | Type | Default | What it is |
|---|---|---|---|
failure_threshold | number | 100 | Minimum authentication failures from a single IP to consider it a spraying source. |
lookback_days | number | 14 | Days of history to examine. |
scope_hosts | list[host] | — | VPN gateway or proxy hosts to narrow the follow-on probing search. |
scope_ips | list[ip] | — | Attacker source IPs to narrow follow-on queries; populate from the first agent verdict. |
scope_users | list[string] | — | Usernames to narrow follow-on harvesting queries; populate from the first agent verdict. |
vpn_paths | list[path] | /saml20/sp, /vpn/saml | Known VPN authentication paths to monitor for probing. |
Telemetry
| Source | Category | Telemetry |
|---|---|---|
| Endpoint telemetry (hb_ surfaces) | endpoint | endpoint |
| Identity / sign-in telemetry | identity | identity |
| Web server / proxy logs | siem | network |
Source
---
analysis: While a single rule might fire on 'high-volume failures', this hunt correlates
failures from 1,400+ AWS IPs to a single successful login, then immediately pivots
across Cloud API and HTTP surfaces to confirm the post-breach chain within minutes.
blind_spots:
- id: cloud-audit-latency
question: whether exfiltration occurred before the logs were ingested
requires: Real-time Entra ID Audit Logs
risk: A 2-minute pivot window after compromise may be faster than the ingestion
latency of cloud audit logs.
stage: cloud-resource-and-graph-api-discovery
- id: vpn-tls-inspection
question: whether specific SAML endpoints were probed on the VPN gateway
requires: hb_http_activity with decrypted URI paths
risk: If the proxy does not inspect TLS traffic to the VPN gateway, the specific
probing paths (e.g., /SAML20/SP) will not be visible.
stage: vpn-probing-and-pivot
coverage:
- stage: aws-sourced-password-spraying
status: covered
steps:
- high-volume-failures
- stage: m365-account-compromise
status: covered
steps:
- successful-logins-no-mfa
- assess-compromise
- stage: cloud-resource-and-graph-api-discovery
status: covered
steps:
- resource-harvesting
- stage: vpn-probing-and-pivot
status: covered
steps:
- scoping-vpn-gateways
- vpn-probing
- stage: cloud-data-harvesting
status: covered
steps:
- resource-harvesting
- analyze-attack-chain
guardrails:
claims: no_unsupported
evidence: citation_required
missing_data: not_benign
telemetry: untrusted
hunt:
applicability: campaign-specific
handoff: keep-as-periodic-hunt
justification: The TeamFiltration campaign demonstrates that forgotten service accounts
with default credentials are a primary entry vector for cloud intrusions. A systematic
hunt for these breaches is required to identify compromises that standard perimeter
controls often miss.
methodology: model-assisted
trigger: intel-report
hypothesis: An adversary is using the TeamFiltration framework to spray M365 service
accounts with default passwords from AWS infrastructure, subsequently harvesting
data via the Graph API and probing internal VPN endpoints.
labels:
- hunt
- attack.t1110
- attack.t1110.003
- attack.t1110.004
- attack.t1133
- attack.t1041
- attack.t1213
- attack.t1087.004
- collection
- credential access
- discovery
- initial access
- lateral movement
name: TeamFiltration Cloud Identity Spray and Pivot
parameters:
failure_threshold:
default: '100'
description: Minimum authentication failures from a single IP to consider it a
spraying source.
type: number
lookback_days:
default: '14'
description: Days of history to examine.
type: number
scope_hosts:
default: []
description: VPN gateway or proxy hosts to narrow the follow-on probing search.
type: list[host]
scope_ips:
default: []
description: Attacker source IPs to narrow follow-on queries; populate from the
first agent verdict.
from:
kind: article
observed: '2026-09-24'
ref: https://www.proofpoint.com/us/newsroom/news/teamfiltration-campaign-compromises-seven-microsoft-365-accounts-using-default
type: list[ip]
scope_users:
default: []
description: Usernames to narrow follow-on harvesting queries; populate from the
first agent verdict.
type: list[string]
vpn_paths:
default:
- /saml20/sp
- /vpn/saml
description: Known VPN authentication paths to monitor for probing.
from:
kind: article
observed: '2026-09-24'
ref: proofpoint-teamfiltration
type: list[path]
provenance:
authors:
- name: Huntbase hunt generation
org: huntbase.io
generated:
by: huntbase-hunt-generation
from: https://www.proofpoint.com/us/newsroom/news/teamfiltration-campaign-compromises-seven-microsoft-365-accounts-using-default
gates:
- dry-run
- lint
model: hb_google/gemini-3-flash-preview
rationale: The hunt scopes to VPN gateways first to satisfy host-based telemetry requirements,
then pivots into M365 authentication logs and Cloud API activity. Focus specifically
on service/functional accounts which are the primary targets of the UNK_CondorFiltration
campaign.
references:
- name: TeamFiltration Campaign Compromises Seven Microsoft 365 Accounts Using Default
Passwords
url: https://www.proofpoint.com/us/newsroom/news/teamfiltration-campaign-compromises-seven-microsoft-365-accounts-using-default
related:
- hunt: mfa-push-spam-detection
reason: This hunt focuses on accounts with NO MFA; accounts with MFA would experience
push spamming instead of direct default-password compromise.
relation: out-of-scope-alternative
scenario:
stages:
- name: Password spraying from AWS infrastructure
observables:
- 1,487 unique AWS EC2 source IP addresses
- TeamFiltration offensive framework
- Targeting of ~1,500 accounts per day in bursts
- Attempts against dormant service accounts
slug: aws-sourced-password-spraying
tactic: credential-access
techniques:
- T1110.003
- T1110
- name: Service account compromise
observables:
- Success within 7 minutes of initial attempt
- Successful login to accounts with default passwords
- Absence of MFA challenges on compromised accounts
- Targeting of 'functional' or service accounts
slug: m365-account-compromise
tactic: initial-access
techniques:
- T1110
- name: Cloud resource discovery and Graph API usage
observables:
- Microsoft Graph API token requests
- Azure Portal access
- TeamFiltration OneDrive interaction
- Accessing Microsoft Teams and Office apps
slug: cloud-resource-and-graph-api-discovery
tactic: discovery
techniques:
- T1087.004
- name: VPN node pivot and probing
observables:
- Pivoting to German VPN nodes less than 2 minutes after compromise
- 'Probing of corporate VPN endpoints: vpn.[redacted].cl/SAML20/SP'
- SharePoint Online browsing
slug: vpn-probing-and-pivot
tactic: lateral-movement
techniques:
- T1133
- name: Cloud data harvesting
observables:
- Harvesting of sensitive data via TeamFiltration
- OneDrive data access
- SharePoint Online resource access
slug: cloud-data-harvesting
tactic: collection
techniques:
- T1213
- T1041
summary: The UNK_CondorFiltration campaign utilized the TeamFiltration offensive
framework to execute a massive password spraying attack against Microsoft 365
tenants from AWS EC2 infrastructure. By targeting unmanaged service accounts with
default passwords and no MFA, the actor gained access to cloud resources including
SharePoint and OneDrive, subsequently pivoting through VPN nodes to probe corporate
remote access infrastructure.
severity: high
targets:
analyst:
name: Tier-2 analyst
role: analyst
endpoint:
category: endpoint
name: Endpoint telemetry (hb_ surfaces)
telemetry:
- endpoint
hunter:
agent: true
name: Hunt agent
identity:
category: identity
name: Identity / sign-in telemetry
telemetry:
- identity
web:
category: siem
name: Web server / proxy logs
telemetry:
- network
tlp: clear
type: investigation
---
# TeamFiltration Cloud Identity Spray and Pivot
This hunt identifies the full lifecycle of a TeamFiltration campaign, from infrastructure-driven password spraying to post-compromise data harvesting. It targets unmanaged functional and service accounts that lack MFA and use default credentials. The hunt starts by identifying the infrastructure involved in the spray, pivots to successful breaches, and then examines follow-on activity such as Microsoft Graph API token requests and VPN endpoint probing. It uses a phased approach to correlate high-volume failures with successful post-breach resource access.
## scoping-vpn-gateways
<!-- Identify VPN and proxy gateways -->
Find the hosts responsible for serving VPN authentication or proxying web traffic to identify where the adversary might probe internal infrastructure.
```sqlite target=web role=scoping params=(vpn_paths=vpn_paths, lookback_days=lookback_days)
~~~yaml
expected: A list of hostnames acting as VPN gateways or proxies. These will be used
to scope later queries.
reads:
- device_hostname
- url_path
- time
silence: not_evidence_of_absence
source: hb_http_activity
verified: dry-run
verified_at: '2026-10-10'
~~~
SELECT DISTINCT device_hostname FROM hb_http_activity WHERE (instr(',' || '{{vpn_paths}}' || ',', ',' || LOWER(url_path) || ',') > 0 OR LOWER(url_path) LIKE '%saml%') AND time >= datetime('now', '-{{lookback_days}} days')
```
## early-stage-parallel
<!-- Correlate spray patterns -->
parallel:
- → high-volume-failures
- → successful-logins-no-mfa
join: → assess-compromise
## high-volume-failures
<!-- High-volume authentication failures -->
Identify source IPs attempting to authenticate against numerous accounts, which is indicative of a password spray.
```sqlite target=identity role=baseline params=(lookback_days=lookback_days, failure_threshold=failure_threshold)
~~~yaml
baseline:
compare: new_this_window
window: '{{lookback_days}}d'
expected: A list of IP addresses exhibiting spraying behavior. If silence, no high-volume
spraying was detected from single IPs in the window.
prevalence:
by: actor_user_name
key:
- src_endpoint_ip
rare_below: 10
reads:
- src_endpoint_ip
- activity_id
- actor_user_name
- time
silence: not_evidence_of_absence
source: hb_auth_signin
verified: dry-run
verified_at: '2026-10-10'
~~~
SELECT src_endpoint_ip, COUNT(*) AS failures, COUNT(DISTINCT actor_user_name) AS distinct_accounts, MIN(time) AS first_attempt, MAX(time) AS last_attempt FROM hb_auth_signin WHERE activity_id = 5 AND time >= datetime('now', '-{{lookback_days}} days') GROUP BY src_endpoint_ip HAVING failures >= {{failure_threshold}} ORDER BY failures DESC
```
## successful-logins-no-mfa
<!-- Successful logins without MFA -->
Identify successful logins to service or functional accounts where MFA was not used, as these are the primary targets of this campaign.
```sqlite target=identity role=triage params=(lookback_days=lookback_days)
~~~yaml
expected: Logins to vulnerable accounts. An analyst or agent will later correlate
these with the spraying IPs.
reads:
- actor_user_name
- src_endpoint_ip
- provider
- mfa
- time
silence: not_evidence_of_absence
source: hb_auth_signin
verified: dry-run
verified_at: '2026-10-10'
~~~
SELECT actor_user_name, src_endpoint_ip, provider, mfa, time FROM hb_auth_signin WHERE activity_id = 1 AND (mfa = 'false' OR mfa IS NULL) AND (LOWER(actor_user_name) LIKE '%svc%' OR LOWER(actor_user_name) LIKE '%service%' OR LOWER(actor_user_name) LIKE '%functional%') AND time >= datetime('now', '-{{lookback_days}} days')
```
## assess-compromise
<!-- Assess spraying impact -->
```agent target=hunter
cite: required
context:
- high-volume-failures
- successful-logins-no-mfa
max_iterations: 4
objective: Determine which service accounts were successfully compromised by identifying
logins from source IPs that also performed high-volume spraying.
success_criteria: A verdict of malicious | suspicious for any account successfully
logged into from a spraying IP.
tools:
- endpoint
- identity
- web
```
## follow-on-parallel
<!-- Investigate post-breach activity -->
parallel:
- → resource-harvesting
- → vpn-probing
join: → analyze-attack-chain
## resource-harvesting
<!-- Cloud resource and Graph API harvesting -->
Identify data exfiltration attempts by searching for Graph API token requests and SharePoint/OneDrive access.
```sqlite target=endpoint role=enrichment params=(lookback_days=lookback_days, scope_users=scope_users, scope_ips=scope_ips)
~~~yaml
expected: Tokens being requested or files being browsed by the suspected compromised
accounts. Silence suggests no harvesting was detected.
reads:
- actor_user_name
- api_operation
- api_service_name
- resource_name
- src_endpoint_ip
- time
silence: not_evidence_of_absence
source: hb_cloud_api_activity
verified: dry-run
verified_at: '2026-10-10'
~~~
SELECT actor_user_name, api_operation, api_service_name, resource_name, src_endpoint_ip, time FROM hb_cloud_api_activity WHERE (LOWER(api_operation) LIKE '%token%' OR LOWER(api_service_name) IN ('sharepoint', 'onedrive', 'microsoft teams')) AND time >= datetime('now', '-{{lookback_days}} days') AND ('{{scope_users}}' = '' OR instr(',' || '{{scope_users}}' || ',', ',' || actor_user_name || ',') > 0) AND ('{{scope_ips}}' = '' OR instr(',' || '{{scope_ips}}' || ',', ',' || src_endpoint_ip || ',') > 0)
```
## vpn-probing
<!-- VPN endpoint probing from attacker IPs -->
Check if the attacker IPs leveraged their foothold to probe internal VPN endpoints for further access.
```sqlite target=web role=enrichment params=(lookback_days=lookback_days, vpn_paths=vpn_paths, scope_hosts=scope_hosts, scope_ips=scope_ips)
~~~yaml
expected: HTTP requests to VPN auth paths from IPs identified in the compromise phase.
Silence means no such probing was visible.
reads:
- device_hostname
- src_endpoint_ip
- url_path
- user_agent
- time
silence: not_evidence_of_absence
source: hb_http_activity
verified: dry-run
verified_at: '2026-10-10'
~~~
SELECT device_hostname, src_endpoint_ip, url_path, user_agent, time FROM hb_http_activity WHERE (instr(',' || '{{vpn_paths}}' || ',', ',' || LOWER(url_path) || ',') > 0 OR LOWER(url_path) LIKE '%saml%') AND ('{{scope_hosts}}' = '' OR instr(',' || '{{scope_hosts}}' || ',', ',' || device_hostname || ',') > 0) AND ('{{scope_ips}}' = '' OR instr(',' || '{{scope_ips}}' || ',', ',' || src_endpoint_ip || ',') > 0) AND time >= datetime('now', '-{{lookback_days}} days')
```
## analyze-attack-chain
<!-- Analyze full attack chain -->
```agent target=hunter
cite: required
context:
- assess-compromise
- resource-harvesting
- vpn-probing
max_iterations: 4
objective: Determine if the service account activity constitutes a confirmed compromise
based on the combination of spraying source IPs, successful logins, and post-breach
discovery activity.
success_criteria: A final verdict citing the specific API calls and VPN probes that
confirm the compromise.
tools:
- endpoint
- identity
- web
```
## intrusion-decision
<!-- Decide on intrusion response -->
if~: "the analyze-attack-chain verdict is malicious for at least one service account exhibiting follow-on activity" (confidence: high, judge=hunter)
then: → suspend-account
indeterminate: → manual-incident-review
unavailable: → manual-incident-review (blind_spot: cloud-audit-latency)
else: → close-out
## suspend-account
<!-- Suspend compromised service account -->
```action target=identity
~~~yaml
approval: required
~~~
Disable the compromised service account immediately and revoke all active OAuth sessions and tokens.
```
→ manual-incident-review
## manual-incident-review
<!-- Manual incident review -->
```manual target=analyst
Review SharePoint and OneDrive access logs for the compromised account; determine if sensitive files were downloaded and rotate any application secrets found in accessed repositories.
```
→ end
## close-out
<!-- Hunt close-out -->
```manual target=analyst
Record the results of the hunt; identify all active service accounts without MFA for immediate remediation and password rotation.
```
→ end
Run it
Take this hunt into your environment.
Open it in Huntbase to run every step against your own connections, with Scout weighing the evidence and your analysts in command. Or take the open hunt.md file anywhere that reads the format.
Machine-drafted by huntbase-hunt-generation using hb_google/gemini-3-flash-preview, gated by dry-run, lint, then reviewed by a person.