Prerequisites¶
projectx-prod-vpchas been created with subnets configured.- CloudTrail has been configured and is delivering logs to your S3 security datalake (see CloudTrail & Integration).
- Wazuh is configured to ingest CloudTrail logs from S3.
project-x-sec-boxconfigured with Wazuh.
Important
Enable S3 Data Events for CloudTrail to detect GetObject and ListBucket calls. In your trail, under Event type → Data events, add S3 bucket-level data events (read/write) for the buckets you want to monitor. Without this, S3 object access will not appear in CloudTrail.
Important
Enable API Gateway Data Events for CloudTrail to detect Invoke calls to your APIs. In your trail, under Event type → Data events, add execute-api.amazonaws.com data events. Without this, unauthenticated API invocations will not appear in CloudTrail.
Note
This guide builds on these attack scenarios: - Misconfigured S3 Bucket — public bucket access - Insecure API Gateway — public API access without authentication
Network Topology¶
Overview¶
CloudTrail records API calls across your AWS account such as who made the call, when, from where, and what action was performed.
By creating Wazuh detection rules, you can identify patterns consistent with the Misconfigured S3 Bucket and Insecure API Gateway attacks—such as anonymous access to S3 objects, public API invocations without credentials, and configuration changes that grant overly permissive access.
This guide provides custom Wazuh detection monitors to deploy via the Alerting capability.
Detection Strategy¶
How would we detect malicious activity?
We have the visibility through our logs via AWS CloudTrail. This is one of the primary use cases for this AWS service.
We can start with the attack and work backward...
Attackers discover public S3 buckets through reconnaissance (s3scanner, bucket enumeration), then list and download objects with --no-sign-request.
Similarly, attackers discover exposed API Gateways and invoke them with curl, if opened, API keys, IAM auth will be allowed.
In CloudTrail, we look for events where eventSource (which service), eventName (what action), and userIdentity (who—and crucially, whether they're authenticated) tell the story.
Legitimate access uses IAM users, roles, or root. Anonymous or unauthenticated principals accessing S3 objects or invoking APIs is a strong signal of misconfiguration.
Configuration changes that grant public access, such as PutBucketPolicy, PutBucketAcl, or overly permissive API Gateway resource policies are also indicators we can detect.
So here's what we can come up with...
| Attack Phase | CloudTrail Indicator | Detection Approach |
|---|---|---|
| Misconfigured S3 Bucket | S3 GetObject/ListBucket from Anonymous principal | Anonymous S3 access |
| Misconfigured S3 Bucket | PutBucketPolicy/PutBucketAcl that grants public access | Bucket policy misconfiguration |
| Insecure API Gateway | API Gateway Invoke from Anonymous principal | Anonymous API invocation |
| Insecure API Gateway | PutRestApi/UpdateRestApiPolicy with permissive resource policy | API Gateway policy misconfiguration |
CloudTrail fields used in our rules include:
- eventSource: The service that received the request (e.g.,
s3.amazonaws.com,execute-api.amazonaws.com) - eventName: The API action (e.g.,
GetObject,ListBucket,Invoke) - userIdentity.type / userIdentity.principalId: Who made the request—
Anonymousindicates unauthenticated access - sourceIPAddress: Where the request originated
Create Wazuh Rule Extractors¶
<group name="aws,cloudtrail,s3,data_events">
<rule id="119500" level="5">
<decoded_as>json</decoded_as>
<!-- This matches the JSON field, not the metadata location -->
<field name="location">Wazuh-AWS</field>
<field name="data.integration">aws</field>
<field name="data.aws.source">cloudtrail</field>
<field name="data.aws.eventSource">s3.amazonaws.com</field>
<field name="data.aws.eventCategory">Data</field>
<description>AWS CloudTrail S3 Data Event detected</description>
</rule>
</group>
Create Wazuh Detection Rule¶
Anonymous S3 Access (Misconfigured S3 Bucket)¶
We want to detect when someone accesses S3 without credentials.
The Misconfigured S3 Bucket scenario allows public read access—attackers use aws s3 cp ... --no-sign-request or s3scanner to enumerate and exfiltrate data.
CloudTrail records these as GetObject and ListBucket/ListObjectsV2 with userIdentity.type of Anonymous. Let's build the rule step by step.
Rule Description In Layman Terms¶
Our condition is: "Alert when we see S3 GetObject or ListBucket from an Anonymous principal." We express this with three requirements:
- The event must be a CloudTrail event (we query the index that contains CloudTrail alerts).
- The event source must be S3 and the event name must be
GetObject,ListBucket, orListObjectsV2. - The user identity must indicate Anonymous (unauthenticated) access.
Wazuh Query Syntax Walkthrough¶
Each monitor uses an Query DSL. Here's what each part does:
| Element | Purpose | Example |
|---|---|---|
term on data.aws.eventSource |
Match the AWS service. | s3.amazonaws.com = S3 |
term on data.aws.eventName |
Match the API action. | GetObject = object download |
match_phrase / term on data.aws.userIdentity |
Match the identity. | Anonymous = no credentials |
bool + filter |
Combine conditions (all must match). | AND logic |
Create a Monitor¶
If you can recall from Enterprise 101, we used the "Alerting" capability inside Wazuh. Under Alerting, we can create a Monitor, which will run a pre-defined query against all incoming logs. If the log matches the query content, an alert is generated.
We will use the Monitoring and Alerting capability to capture anonymous S3 and API Gateway access.
Navigate to Alerting ➔ Monitors.
Monitor Details:
-
Monitor name: Name your monitor. Select "Per query monitor".
-
Monitor type: Choose "Extraction query editor".
Schedule:
-
By interval.
-
Run every 1 Minute(s).
Select Data:
- Indexes:
wazuh-alerts-4.x-*
Query: Paste in Query.
Triggers:
-
Trigger Name: Name Trigger.
-
Severity level: Choose severity level.
S3 GetObject from Anonymous Principal¶
{
"size": 0,
"query": {
"bool": {
"filter": [
{
"term": {
"data.aws.eventSource": {
"value": "s3.amazonaws.com",
"boost": 1
}
}
},
{
"term": {
"data.aws.eventName": {
"value": "GetObject",
"boost": 1
}
}
},
{
"query_string": {
"query": "data.aws.userIdentity.type:Anonymous OR data.aws.userIdentity.principalId:Anonymous",
"boost": 1
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
}
}
S3 ListBucket / ListObjectsV2 from Anonymous Principal¶
Use the same structure as above, but change the event name filter to capture list operations:
{
"size": 0,
"query": {
"bool": {
"filter": [
{
"term": {
"data.aws.eventSource": {
"value": "s3.amazonaws.com",
"boost": 1
}
}
},
{
"terms": {
"data.aws.eventName": ["ListBucket", "ListObjectsV2", "ListObjects"],
"boost": 1
}
},
{
"query_string": {
"query": "data.aws.userIdentity.type:Anonymous OR data.aws.userIdentity.principalId:Anonymous",
"boost": 1
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
}
}
Anonymous API Gateway Invocation (Insecure API Gateway)¶
When an attacker invokes an API Gateway endpoint with curl—no API key, no IAM auth—CloudTrail logs the request with eventSource of execute-api.amazonaws.com and userIdentity.type of Anonymous. This indicates someone is calling your API without authentication, consistent with the Insecure API Gateway scenario.
Rule Description In Layman Terms¶
"Alert when we see an API Gateway Invoke from an Anonymous principal."
- The event source must be
execute-api.amazonaws.com(data plane API invocation). - The event name must be
Invoke(the actual HTTP request to your API). - The user identity must indicate Anonymous access.
Create a Monitor¶
Follow the same workflow as above (Navigate to Alerting ➔ Monitors, configure Monitor Details, Schedule, Select Data, Query, Triggers).
API Gateway Invoke from Anonymous Principal¶
{
"size": 0,
"query": {
"bool": {
"filter": [
{
"term": {
"data.aws.eventSource": {
"value": "execute-api.amazonaws.com",
"boost": 1
}
}
},
{
"term": {
"data.aws.eventName": {
"value": "Invoke",
"boost": 1
}
}
},
{
"query_string": {
"query": "data.aws.userIdentity.type:Anonymous OR data.aws.userIdentity.principalId:Anonymous",
"boost": 1
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
}
}
Note
CloudTrail must have API Gateway data events enabled for execute-api.amazonaws.com to log Invoke operations. In your trail, under Event type → Data events, add a data event type for API Gateway and select the appropriate logging template.
Bucket Policy / ACL Misconfiguration (Prevention Signal)¶
Detect configuration changes that could expose S3 buckets to the public. These are management events and are logged by default.
Rule Description In Layman Terms¶
"Alert when we see PutBucketPolicy or PutBucketAcl—especially if the policy grants public access."
These events indicate someone changed bucket permissions. A follow-up review should verify whether public access was granted.
S3 PutBucketPolicy¶
{
"size": 0,
"query": {
"bool": {
"filter": [
{
"term": {
"data.aws.eventSource": {
"value": "s3.amazonaws.com",
"boost": 1
}
}
},
{
"term": {
"data.aws.eventName": {
"value": "PutBucketPolicy",
"boost": 1
}
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
}
}
S3 PutBucketAcl with Public Access¶
To detect PutBucketAcl that includes public-read or AllUsers, we use a query string to match policy text:
{
"size": 0,
"query": {
"bool": {
"filter": [
{
"term": {
"data.aws.eventSource": {
"value": "s3.amazonaws.com",
"boost": 1
}
}
},
{
"term": {
"data.aws.eventName": {
"value": "PutBucketAcl",
"boost": 1
}
}
},
{
"query_string": {
"query": "AllUsers OR public-read OR PublicRead",
"boost": 1
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
}
}
API Gateway Resource Policy Changes (Prevention Signal)¶
Detect API Gateway configuration changes that could expose APIs to the public—such as PutRestApi or UpdateRestApiPolicy with a resource policy allowing Principal: '*'.
Rule Description In Layman Terms¶
"Alert when we see PutRestApi or UpdateRestApiPolicy."
These events indicate the API Gateway resource policy was modified. A follow-up review should verify whether the policy allows unauthenticated access.
API Gateway PutRestApi / UpdateRestApiPolicy¶
{
"size": 0,
"query": {
"bool": {
"filter": [
{
"term": {
"data.aws.eventSource": {
"value": "apigateway.amazonaws.com",
"boost": 1
}
}
},
{
"terms": {
"data.aws.eventName": ["PutRestApi", "UpdateRestApiPolicy"],
"boost": 1
}
}
],
"adjust_pure_negative": true,
"boost": 1
}
}
}
Verify Detections¶
Test Misconfigured S3 Detection¶
- Deploy the Misconfigured S3 Bucket CloudFormation stack.
- Populate the bucket with sample data (see the attack guide).
- From any machine (no AWS credentials), run:
aws s3 ls s3://delete-me-projectx-leaky-bucket --no-sign-request
aws s3 cp s3://delete-me-projectx-leaky-bucket/config/database-credentials.json . --no-sign-request
- Wait 5–15 minutes for CloudTrail to deliver logs and Wazuh to ingest them.
- Navigate to Alerting in Wazuh—you should see alerts for anonymous S3 GetObject and ListBucket.
Test Insecure API Gateway Detection¶
- Deploy the Insecure API Gateway CloudFormation stack.
- Get the API Gateway invoke URL from CloudFormation outputs.
- From any machine (no AWS credentials), run:
curl https://<api-id>.execute-api.<region>.amazonaws.com/prod/test
curl https://<api-id>.execute-api.<region>.amazonaws.com/prod/users
- Wait 5–15 minutes for CloudTrail to deliver logs and Wazuh to ingest them.
- Navigate to Alerting in Wazuh—you should see alerts for anonymous API Gateway Invoke.
View Alerts¶
Navigate to Alerting.
You should see new alerts generated when anonymous S3 access or anonymous API Gateway invocations are detected.
CloudTrail Configuration Reminders¶
| Requirement | Purpose |
|---|---|
| S3 Data Events | Log GetObject, ListBucket, etc. for anonymous S3 access detection |
| API Gateway Data Events (execute-api) | Log Invoke operations for anonymous API access detection |
| Management Events (Read + Write) | Log PutBucketPolicy, PutBucketAcl, PutRestApi, etc. |
| Multi-Region | Capture activity across all regions |