Skip to content

Siemphony is in beta and still being built. What ships today, and what does not.

Siemphony’s repertoire

Cloud account enumeration cmdlets in a PowerShell script block

Siemphony@siemphonylowT1087.004unverified
Matches AN1089 on the only log source the brief gives it: a PowerShell script block that names a command which lists the accounts in a cloud tenant. Two groups are separated because they are not equally suspicious. The first holds the directory-wide calls — the MSOnline, AzureAD, Microsoft Graph, Microsoft Entra and Az identity cmdlets, and the `az`, `aws iam` and `gcloud` account-listing command lines, which appear in a script block when they are typed at or invoked from a PowerShell host — and it fires on its own. MSOnline and AzureAD are retired modules and are kept only because the technique text names `Get-MsolRoleMember`; the Graph SDK and Entra cmdlets beside them are added here, not taken from MITRE, and are what a current tenant would actually be enumerated with. The second holds `Get-Recipient`, `Get-Mailbox` and `Get-User`, which are the three most-used cmdlets in ordinary Exchange administration and are therefore gated behind a bulk marker (`-ResultSize Unlimited`) so that a single lookup of one mailbox does not match while a whole-tenant sweep does. That gate is also the rule's largest blind spot, and it is authored here rather than derived: those cmdlets return 1000 objects by default, so in any tenant smaller than that a bare `Get-Mailbox` enumerates every account and matches nothing below. The same three cmdlets are equally the observable for T1087.003 Email Account and this branch cannot separate the two; they are included because MITRE names them in AN1089 under this technique's strategy. Everything sits on `ScriptBlockText` under EventID 4104, the only field in this source carrying the script as the engine compiled it: an `-EncodedCommand` payload therefore arrives decoded, but decoded is not deobfuscated, and name splitting (`"Get-Msol"+"User"`), backtick insertion and wildcard command resolution (`&(gcm Get-Msol*)`) all survive compilation and defeat every literal term below. Three limits are structural. MITRE's `CmdletVolume` knob — the real discriminator here, since the abuse differs from administration mainly in how much is enumerated per hour — needs an aggregation lib/sigma does not model, so this rule sees single invocations and its level reflects that; the `UserAgent` and `SessionContext` knobs likewise have no field in this event. The rule can only see enumeration performed on a Windows host whose PowerShell is being logged, so an adversary calling Graph from their own machine, working in the Azure or Microsoft 365 admin portal, or running the same CLI commands from Linux or macOS writes nothing here at all — the tenant audit log is where that lives, and the brief supplies no log source for it. And the rule is written for 4104's `ScriptBlockText`; an estate collecting only 4103 module logging carries the invocation in `ContextInfo`/`Payload` instead and would need its own selection. Prerequisite: EventID 4104 requires *Script Block Logging* to be enabled by policy, which is off in a default install, and with it off this rule returns zero rows — which reads as quiet when it means blind. UNVERIFIED AS AUTHORED — derived from MITRE ATT&CK DET0386, not hand-written and not tested by its author. Any lab result is on this rule's own page.Full description

The detection

The 4 SIEM queries below are previews produced by Siemphony’s own translator, not pySigma, and none has been executed against a real backend — review before you deploy.

detection.yml

Sentinel · KQL

Run this as a search.

Event| where ((EventID == 4104 and (ScriptBlockText contains "Get-MsolUser" or ScriptBlockText contains "Get-MsolRoleMember" or ScriptBlockText contains "Get-AzureADUser" or ScriptBlockText contains "Get-AzureADDirectoryRoleMember" or ScriptBlockText contains "Get-MgUser" or ScriptBlockText contains "Get-MgBetaUser" or ScriptBlockText contains "Get-MgDirectoryRoleMember" or ScriptBlockText contains "Get-EntraUser" or ScriptBlockText contains "Get-EntraDirectoryRoleMember" or ScriptBlockText contains "Get-AzADUser" or ScriptBlockText contains "az ad user list" or ScriptBlockText contains "aws iam list-users" or ScriptBlockText contains "aws iam list-roles" or ScriptBlockText contains "gcloud iam service-accounts list" or ScriptBlockText contains "gcloud projects get-iam-policy")) or ((EventID == 4104 and (ScriptBlockText contains "Get-Recipient" or ScriptBlockText contains "Get-Mailbox" or ScriptBlockText contains "Get-User")) and (EventID == 4104 and (ScriptBlockText contains "ResultSize Unlimited" or ScriptBlockText contains "ResultSize:Unlimited"))))

Splunk · SPL

Run this as a search.

index=* ((EventID="4104" AND (ScriptBlockText="*Get-MsolUser*" OR ScriptBlockText="*Get-MsolRoleMember*" OR ScriptBlockText="*Get-AzureADUser*" OR ScriptBlockText="*Get-AzureADDirectoryRoleMember*" OR ScriptBlockText="*Get-MgUser*" OR ScriptBlockText="*Get-MgBetaUser*" OR ScriptBlockText="*Get-MgDirectoryRoleMember*" OR ScriptBlockText="*Get-EntraUser*" OR ScriptBlockText="*Get-EntraDirectoryRoleMember*" OR ScriptBlockText="*Get-AzADUser*" OR ScriptBlockText="*az ad user list*" OR ScriptBlockText="*aws iam list-users*" OR ScriptBlockText="*aws iam list-roles*" OR ScriptBlockText="*gcloud iam service-accounts list*" OR ScriptBlockText="*gcloud projects get-iam-policy*")) OR ((EventID="4104" AND (ScriptBlockText="*Get-Recipient*" OR ScriptBlockText="*Get-Mailbox*" OR ScriptBlockText="*Get-User*")) AND (EventID="4104" AND (ScriptBlockText="*ResultSize Unlimited*" OR ScriptBlockText="*ResultSize:Unlimited*"))))

Elastic · ES|QL

Run this as a search.

FROM logs-*| WHERE ((event.code == 4104 AND (TO_LOWER(powershell.file.script_block_text) LIKE "*get-msoluser*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-msolrolemember*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-azureaduser*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-azureaddirectoryrolemember*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-mguser*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-mgbetauser*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-mgdirectoryrolemember*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-entrauser*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-entradirectoryrolemember*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-azaduser*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*az ad user list*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*aws iam list-users*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*aws iam list-roles*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*gcloud iam service-accounts list*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*gcloud projects get-iam-policy*")) OR ((event.code == 4104 AND (TO_LOWER(powershell.file.script_block_text) LIKE "*get-recipient*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-mailbox*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*get-user*")) AND (event.code == 4104 AND (TO_LOWER(powershell.file.script_block_text) LIKE "*resultsize unlimited*" OR TO_LOWER(powershell.file.script_block_text) LIKE "*resultsize:unlimited*"))))

Wazuh · XML rule

Deploy to your manager — this is a rule, not a search.

<group name="sigma,windows,">  <!-- Rule ids must be unique on your manager; 100000+ is the user range. -->  <!-- 2 rules: the Sigma condition ORs across different fields,       which one rule cannot express. Any one matching is a hit. -->  <rule id="100000" level="5">    <!-- Set <if_sid> to the decoder/base rule for windows so this only evaluates relevant events. -->    <field name="EventID" type="pcre2">(?i)^4104$</field>    <field name="ScriptBlockText" type="pcre2">(?i)(Get-MsolUser|Get-MsolRoleMember|Get-AzureADUser|Get-AzureADDirectoryRoleMember|Get-MgUser|Get-MgBetaUser|Get-MgDirectoryRoleMember|Get-EntraUser|Get-EntraDirectoryRoleMember|Get-AzADUser|az ad user list|aws iam list-users|aws iam list-roles|gcloud iam service-accounts list|gcloud projects get-iam-policy)</field>    <description>Cloud account enumeration cmdlets in a PowerShell script block (1/2)</description>    <mitre>      <id>T1087.004</id>    </mitre>  </rule>   <rule id="100001" level="5">    <!-- Set <if_sid> to the decoder/base rule for windows so this only evaluates relevant events. -->    <field name="EventID" type="pcre2">(?i)^4104$</field>    <field name="ScriptBlockText" type="pcre2">(?i)(Get-Recipient|Get-Mailbox|Get-User)</field>    <field name="EventID" type="pcre2">(?i)^4104$</field>    <field name="ScriptBlockText" type="pcre2">(?i)(ResultSize Unlimited|ResultSize:Unlimited)</field>    <description>Cloud account enumeration cmdlets in a PowerShell script block (2/2)</description>    <mitre>      <id>T1087.004</id>    </mitre>  </rule></group>

Verdicts · reactions · comments

Community

Verdicts from engineers who actually deployed it, and the conversation around it.

Log in to react
Not yet reported on

Nobody has run this in a real environment and said what happened.

Verdicts from engineers who deployed it

0 cast

No verdicts reported yet. The first one is worth more than the tenth — say what you ran it against.

Log in to report a verdict.

Log in to join the discussion.

No comments yet. Someone who deploys this will have something to say about it.