On January 23, 2026, Maromalix’s finance department received an alarming call from TechCorp Industries, a long-standing client. TechCorp’s accounts payable team reported processing a wire transfer to what they believed was Maromalix’s bank account after receiving an invoice for services rendered — but Maromalix had not issued any such invoice.
During the week prior, Maromalix onboarded a new cloud administrator. Several IAM permission adjustments were made in response to access issues during this transition, some of which resulted in overly permissive policies. The investigation uses AWS CloudWatch Logs Insights to query CloudTrail and Lambda execution logs, reconstruct the attack timeline, and identify indicators of compromise.
The first step is confirming what log sources are available. The CloudWatch log group selector reveals four groups relevant to the investigation:

/aws/cloudtrail/maromalix — primary CloudTrail audit log source/aws/lambda/maromalix-daily-backup/aws/lambda/maromalix-email-notifications/aws/lambda/maromalix-health-checkBefore querying blind, a limit 1 against the CloudTrail log group confirms the CloudTrail event structure — sourceIPAddress, userAgent, eventName, eventSource, userIdentity.* fields are all present:
fields @timestamp, @message
| limit 1

All queries in this investigation run in CloudWatch Logs Insights (CWLI) — a proprietary query language distinct from Kusto or Kibana KQL, using filter instead of where and stats instead of summarize.
Initial threat hunting groups activity by source IP and User-Agent to surface anomalous tooling. Filtering out AWS-internal traffic and ranking by distinct identity count exposes the attacker immediately:
fields @timestamp, sourceIPAddress, userAgent, userIdentity.userName, userIdentity.type
| filter sourceIPAddress not like "amazonaws.com"
| stats count(*) as calls, count_distinct(userIdentity.userName) as identities by sourceIPAddress, userAgent
| sort identities desc, calls desc

52.59.194.168 appears across multiple rows with multiple tools — TruffleHog (secret scanning and credential validation) and Pacu/1.5.2 (open-source AWS exploitation framework). The User-Agent field in CloudTrail is the critical signal here — legitimate AWS tooling stamps itself as Boto3, aws-cli, or Terraform; offensive tools identify themselves just as clearly if you look.
With the attacker IP confirmed, filtering CloudTrail for S3 activity from that IP reveals the first action taken:
fields @timestamp, eventName, requestParameters.bucketName, sourceIPAddress
| filter sourceIPAddress = "52.59.194.168"
| filter eventSource = "s3.amazonaws.com"
| sort @timestamp asc

The attacker issued ListObjects against maromalix-website-assets-prod-83c9fdc8 at 12:53 UTC — a publicly accessible bucket containing exposed credentials. TruffleHog scanned the bucket contents, discovered the svc-jenkins service account keys, and automatically validated them against the AWS STS API. The GetCallerIdentity calls visible at 12:51 are TruffleHog’s validation step — confirming the keys are live before proceeding.
With validated svc-jenkins credentials, Pacu begins systematic IAM enumeration. Filtering all attacker activity in chronological order surfaces the full identity chain:
fields @timestamp, eventName, userIdentity.userName, userIdentity.type, userIdentity.arn
| filter sourceIPAddress = "52.59.194.168"
| sort @timestamp asc
| limit 20

Between 12:54 and 12:57, Pacu fires GetRole, ListUserPolicies, ListAttachedUserPolicies, ListPolicies, ListRoles, ListUsers, and ListGroups in rapid succession — the standard permission enumeration sequence to map privilege escalation paths. At 13:01, the attacker identifies an overly permissive trust policy on Maromalix-DevOps-Role and assumes it with the custom session name DevOpsing_maromalix.
Custom session names are an attacker tell — they allow the operator to track their own sessions across API calls. In this case it also inadvertently creates a consistent filter target for defenders.
Filtering activity under the DevOpsing_maromalix assumed role session shows the full post-escalation enumeration sequence:
fields @timestamp, eventName, eventSource, userIdentity.arn
| filter userIdentity.arn like "DevOpsing_maromalix"
| sort @timestamp asc

KMS Decrypt calls at 13:03, DescribeInstances at 13:03, then ListSecrets against Secrets Manager at 13:04 — Pacu is methodically working through services. GetSecretValue is called multiple times at 13:07, with maromalix/automation/ssm-credentials being the first secret retrieved. SSM credentials from Secrets Manager are a high-value target — they provide direct access to EC2 instances via AWS Systems Manager without requiring network exposure.

This maps to T1555.006 — Credentials from Password Stores: Cloud Secrets Management Stores.
With the SSM credentials in hand, the attacker assumes a second role. Querying all AssumeRole events confirms the pivot:
fields @timestamp, eventName, requestParameters.roleArn, requestParameters.roleSessionName, userIdentity.arn, sourceIPAddress
| filter eventName = "AssumeRole"
| sort @timestamp asc
The Maromalix-SSM-Automation-Role is the pivot role, confirmed by the SSM activity that follows immediately after. The attacker then uses that role to execute commands remotely on an EC2 instance:
fields @timestamp, eventName, requestParameters.instanceIds.0, sourceIPAddress
| filter sourceIPAddress = "52.59.194.168"
| filter eventSource = "ssm.amazonaws.com"
| sort @timestamp asc

SendCommand is issued against instance i-0afb277aeec0e6fa4 at 13:24 UTC. The command runs on the instance and curls the Instance Metadata Service endpoint (http://169.254.169.254/latest/meta-data/iam/security-credentials/) to retrieve the temporary IAM credentials attached to the instance profile. This is a well-known IMDS credential theft technique — no network exposure required, no credentials stored on disk, and the API call originates from the instance itself making attribution harder.

The stolen instance credentials belong to Maromalix-EC2-WebApp-Role.
Operating as the EC2 WebApp role, the attacker pivots to Lambda enumeration. All Lambda API calls from the attacker IP surface the function names and the final invocation:
fields @timestamp, eventName, requestParameters.functionName, userIdentity.arn, sourceIPAddress
| filter eventSource = "lambda.amazonaws.com"
| filter sourceIPAddress = "52.59.194.168"
| sort @timestamp asc

ListFunctions at 13:04 under the DevOps role gives the attacker the full function inventory. Then under the WebApp role, GetFunction20150331v2 is called against maromalix-daily-backup first at 13:28, then maromalix-email-notifications. The attacker downloads both function configurations and code packages to inspect them — maromalix-daily-backup is discarded as unsuitable, while maromalix-email-notifications is identified as capable of sending emails via the configured SES identity. The function is then invoked twice.
Pivoting to the /aws/lambda/maromalix-email-notifications log group, the invocation payloads are visible in the execution logs:
fields @timestamp, @message
| filter @message like "EMAIL_SEND_ATTEMPT"
| sort @timestamp asc

The first invocation at 13:49:46 UTC sends to the internal address billing@maromalix.cloud — placing a copy inside the victim organisation’s own email system to create a paper trail and add legitimacy. The second at 13:50 sends to billing@techcorp.live — the actual target.

The invoice body impersonates Maromalix Financial Technologies, references invoice #11000074454 for $49,900.00 in fabricated services, includes real-looking ACH banking details, and directs any queries to two attacker-controlled contact addresses: j.hepzibah@cfp-impactaction.com and ar@zoominfopay.com. Both domains are part of the TruffleNet BEC infrastructure documented by Fortinet FortiGuard Labs.

This maps to T1657 — Financial Theft.
| Phase | Action |
|---|---|
| Reconnaissance | TruffleHog scans public S3 bucket maromalix-website-assets-prod-83c9fdc8, discovers svc-jenkins credentials |
| Validation | GetCallerIdentity confirms stolen keys are live |
| Enumeration | Pacu/1.5.2 maps IAM roles, policies, and users from svc-jenkins context |
| Privilege Escalation | svc-jenkins assumes Maromalix-DevOps-Role with session name DevOpsing_maromalix |
| Credential Exfiltration | ListSecrets + GetSecretValue against Secrets Manager — maromalix/automation/ssm-credentials retrieved |
| Lateral Movement | SSM credentials used to assume Maromalix-SSM-Automation-Role |
| IMDS Abuse | SendCommand on i-0afb277aeec0e6fa4 curls IMDS to steal EC2 instance credentials |
| Discovery | GetFunction on maromalix-daily-backup and maromalix-email-notifications — code inspected |
| Impact | maromalix-email-notifications invoked twice — fraudulent $49,900 invoice sent to billing@maromalix.cloud and billing@techcorp.live |
| Type | Value |
|---|---|
| Attacker IP | 52[.]59[.]194[.]168 |
| Compromised S3 Bucket | maromalix-website-assets-prod-83c9fdc8 |
| Compromised IAM User | svc-jenkins |
| Role (First Pivot) | Maromalix-DevOps-Role |
| Session Name | DevOpsing_maromalix |
| Role (Second Pivot) | Maromalix-SSM-Automation-Role |
| Role (IMDS Stolen) | Maromalix-EC2-WebApp-Role |
| EC2 Instance | i-0afb277aeec0e6fa4 |
| Secret ID | maromalix/automation/ssm-credentials |
| Lambda (Weaponised) | maromalix-email-notifications |
| Internal Recipient | billing@maromalix[.]cloud |
| External Victim | billing@techcorp[.]live |
| Attacker Domain | cfp-impactaction[.]com |
| Attacker Domain | zoominfopay[.]com |
| Invoice Amount | $49,900.00 |
| Invoice Number | #11000074454 |
| Campaign | TruffleNet |
| Technique | ID | Description |
|---|---|---|
| Cloud Service Discovery | T1526 | Pacu enumerates IAM roles, policies, Lambda functions, and Secrets Manager inventory |
| Data from Cloud Storage | T1530 | TruffleHog retrieves credential files from public S3 bucket |
| Valid Accounts: Cloud Accounts | T1078.004 | Stolen svc-jenkins keys used to authenticate to AWS |
| Use Alternate Authentication Material | T1550.001 | Temporary credentials from IMDS used to operate as EC2 instance role |
| Credentials from Password Stores: Cloud Secrets Management Stores | T1555.006 | GetSecretValue against AWS Secrets Manager to retrieve SSM credentials |
| Cloud Administration Command | T1059.009 | SSM SendCommand used to execute commands on EC2 instance |
| Financial Theft | T1657 | Fraudulent invoice sent via hijacked Lambda function to deceive TechCorp into wire transfer |
S3 buckets should never store credentials. The entire attack chain originates from a single exposed key in a public S3 bucket. Secrets belong in Secrets Manager or Parameter Store — not flat files in object storage. An S3 bucket policy blocking public access and an AWS Config rule enforcing this would have eliminated the initial access vector entirely.
IAM roles need scoped trust policies. Maromalix-DevOps-Role had a trust policy permissive enough for svc-jenkins — a CI service account — to assume it. Trust policies should explicitly enumerate which principals can assume a role, and service account permissions should be scoped to only what CI/CD pipelines require. The new administrator’s permission changes during onboarding directly created this path.
IMDS v2 should be enforced on all EC2 instances. IMDSv2 requires a session token obtained via a PUT request before credentials are accessible, which prevents the simple curl technique used here. Enforcing IMDSv2 via instance metadata options or an SCP makes IMDS credential theft significantly harder. Additionally, monitoring for GetMetadata calls in conjunction with SSM SendCommand events is a high-fidelity detection opportunity.
CloudTrail User-Agent strings are a detection primitive. TruffleHog and Pacu/1.5.2 both stamp their names in the userAgent field. An alerting rule matching known offensive tool names in CloudTrail User-Agents would have flagged the attack within minutes of the first API call. This is a low-effort, high-signal detection with minimal false positive risk.
Lambda functions that send email are high-value targets. Any function with SES permissions and the ability to accept external invocation payloads is a BEC primitive waiting to be weaponised. Lambda functions that send email should validate recipients and content server-side against an allowlist, and invocations should be logged and alerted on when the invoking identity is unexpected — particularly when an EC2 instance role invokes an email function directly.