mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-08-07 11:10:19 +03:00
Fix review comments: correct AWS Detective API usage and forensic ordering
- Fix FilterCriteria to use singular Severity/Status with Value objects instead of invalid plural Severities/Statuses arrays (SKILL.md + process.py) - Fix get_entity_history: rename to get_investigation_indicators, use investigation_id instead of entity_arn for InvestigationId parameter - Replace invalid inv-* placeholders with 21-digit numeric IDs - Fix Expected Output to match real API response structure (no embedded Indicators; document separate list-indicators call and indicator types) - Fix CLI --filter-criteria example to use correct format - Update process.py --severity to accept single value with validation - Add --max-results validation (1-100 range) - Add pagination via _collect_all_pages helper for all list API calls - Reorder Response Actions checklist: evidence preservation before containment - Reorder Phase 5 workflow: preserve evidence first when safe
This commit is contained in:
committed by
Julio César Suástegui
parent
41b828e758
commit
a072845a3f
@@ -46,7 +46,7 @@ aws detective list-graphs --output table
|
|||||||
# Get entity profile for an IAM user
|
# Get entity profile for an IAM user
|
||||||
aws detective get-investigation \
|
aws detective get-investigation \
|
||||||
--graph-arn arn:aws:detective:us-east-1:123456789012:graph:a1b2c3d4 \
|
--graph-arn arn:aws:detective:us-east-1:123456789012:graph:a1b2c3d4 \
|
||||||
--investigation-id inv-1234567890
|
--investigation-id 000000000000000000001
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 3: Search Entities Programmatically
|
### Step 3: Search Entities Programmatically
|
||||||
@@ -65,25 +65,22 @@ def list_behavior_graphs():
|
|||||||
response = detective.list_graphs()
|
response = detective.list_graphs()
|
||||||
return response.get('GraphList', [])
|
return response.get('GraphList', [])
|
||||||
|
|
||||||
def get_entity_history(graph_arn, entity_arn, hours=24):
|
def get_investigation_indicators(graph_arn, investigation_id, max_results=50):
|
||||||
"""Get API call history for an entity."""
|
"""Get indicators for a specific investigation."""
|
||||||
end_time = datetime.utcnow()
|
|
||||||
start_time = end_time - timedelta(hours=hours)
|
|
||||||
|
|
||||||
response = detective.list_indicators(
|
response = detective.list_indicators(
|
||||||
GraphArn=graph_arn,
|
GraphArn=graph_arn,
|
||||||
InvestigationId=entity_arn,
|
InvestigationId=investigation_id,
|
||||||
MaxResults=50
|
MaxResults=max_results
|
||||||
)
|
)
|
||||||
return response.get('Indicators', [])
|
return response.get('Indicators', [])
|
||||||
|
|
||||||
def investigate_guardduty_findings(graph_arn):
|
def investigate_guardduty_findings(graph_arn):
|
||||||
"""List finding groups correlated by Detective."""
|
"""List high-severity investigations correlated by Detective."""
|
||||||
response = detective.list_investigations(
|
response = detective.list_investigations(
|
||||||
GraphArn=graph_arn,
|
GraphArn=graph_arn,
|
||||||
FilterCriteria={
|
FilterCriteria={
|
||||||
'Statuses': ['RUNNING', 'FAILED'],
|
'Severity': {'Value': 'CRITICAL'},
|
||||||
'Severities': ['HIGH', 'CRITICAL']
|
'Status': {'Value': 'RUNNING'}
|
||||||
},
|
},
|
||||||
MaxResults=20
|
MaxResults=20
|
||||||
)
|
)
|
||||||
@@ -109,7 +106,7 @@ if __name__ == "__main__":
|
|||||||
# List investigations with high severity
|
# List investigations with high severity
|
||||||
aws detective list-investigations \
|
aws detective list-investigations \
|
||||||
--graph-arn arn:aws:detective:us-east-1:123456789012:graph:a1b2c3d4 \
|
--graph-arn arn:aws:detective:us-east-1:123456789012:graph:a1b2c3d4 \
|
||||||
--filter-criteria '{"Severities":["HIGH","CRITICAL"]}' \
|
--filter-criteria '{"Severity":{"Value":"HIGH"}}' \
|
||||||
--max-results 10
|
--max-results 10
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -119,33 +116,32 @@ aws detective list-investigations \
|
|||||||
# Get indicators for a specific investigation
|
# Get indicators for a specific investigation
|
||||||
aws detective list-indicators \
|
aws detective list-indicators \
|
||||||
--graph-arn arn:aws:detective:us-east-1:123456789012:graph:a1b2c3d4 \
|
--graph-arn arn:aws:detective:us-east-1:123456789012:graph:a1b2c3d4 \
|
||||||
--investigation-id inv-1234567890 \
|
--investigation-id 000000000000000000001 \
|
||||||
--max-results 50
|
--max-results 50
|
||||||
```
|
```
|
||||||
|
|
||||||
## Expected Output
|
## Expected Output
|
||||||
|
|
||||||
|
The `list-investigations` command returns investigation metadata:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"InvestigationDetails": [
|
"InvestigationDetails": [
|
||||||
{
|
{
|
||||||
"InvestigationId": "inv-0a1b2c3d4e5f",
|
"InvestigationId": "000000000000000000001",
|
||||||
"Severity": "CRITICAL",
|
"Severity": "CRITICAL",
|
||||||
"Status": "RUNNING",
|
"Status": "RUNNING",
|
||||||
|
"State": "ACTIVE",
|
||||||
"EntityArn": "arn:aws:iam::123456789012:user/suspicious-user",
|
"EntityArn": "arn:aws:iam::123456789012:user/suspicious-user",
|
||||||
"EntityType": "IAM_USER",
|
"EntityType": "IAM_USER",
|
||||||
"CreatedTime": "2026-03-15T14:30:00Z",
|
"CreatedTime": "2026-03-15T14:30:00Z"
|
||||||
"Indicators": {
|
|
||||||
"ImpossibleTravel": true,
|
|
||||||
"NewGeoLocation": "ru-central-1",
|
|
||||||
"UnusualApiCalls": ["CreateAccessKey", "AttachUserPolicy", "PutBucketPolicy"],
|
|
||||||
"RelatedFindings": 7
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Indicators are retrieved separately via `list-indicators` and include types such as `TTP_OBSERVED`, `IMPOSSIBLE_TRAVEL`, `FLAGGED_IP_ADDRESS`, `NEW_GEOLOCATION`, `NEW_ASO`, `NEW_USER_AGENT`, `RELATED_FINDING`, and `RELATED_FINDING_GROUP`.
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
1. Confirm behavior graph has data: `aws detective list-graphs` returns non-empty list
|
1. Confirm behavior graph has data: `aws detective list-graphs` returns non-empty list
|
||||||
|
|||||||
@@ -27,8 +27,8 @@
|
|||||||
- [ ] Initial access vector identified
|
- [ ] Initial access vector identified
|
||||||
|
|
||||||
## Response Actions
|
## Response Actions
|
||||||
|
- [ ] Evidence preserved (or capture rationale if immediate containment required)
|
||||||
- [ ] Compromised credentials disabled
|
- [ ] Compromised credentials disabled
|
||||||
- [ ] Active sessions revoked
|
- [ ] Active sessions revoked
|
||||||
- [ ] Affected resources isolated
|
- [ ] Affected resources isolated
|
||||||
- [ ] Evidence preserved
|
|
||||||
- [ ] Stakeholders notified
|
- [ ] Stakeholders notified
|
||||||
|
|||||||
+5
-4
@@ -24,7 +24,8 @@
|
|||||||
4. Document indicators of compromise (IOCs)
|
4. Document indicators of compromise (IOCs)
|
||||||
|
|
||||||
## Phase 5: Response
|
## Phase 5: Response
|
||||||
1. Disable compromised credentials
|
1. Preserve evidence (CloudTrail logs, flow logs, snapshots) when safe
|
||||||
2. Revoke active sessions
|
2. Disable compromised credentials
|
||||||
3. Isolate affected resources
|
3. Revoke active sessions
|
||||||
4. Preserve evidence (CloudTrail logs, flow logs)
|
4. Isolate affected resources
|
||||||
|
5. If active impact is ongoing, contain first and document evidence trade-offs
|
||||||
|
|||||||
+30
-13
@@ -13,11 +13,23 @@ import os
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
|
||||||
|
def _collect_all_pages(client_method, result_key, **kwargs):
|
||||||
|
"""Paginate through all pages of an AWS Detective API call."""
|
||||||
|
all_items = []
|
||||||
|
while True:
|
||||||
|
response = client_method(**kwargs)
|
||||||
|
all_items.extend(response.get(result_key, []))
|
||||||
|
next_token = response.get('NextToken')
|
||||||
|
if not next_token:
|
||||||
|
break
|
||||||
|
kwargs['NextToken'] = next_token
|
||||||
|
return all_items
|
||||||
|
|
||||||
|
|
||||||
def list_behavior_graphs(session):
|
def list_behavior_graphs(session):
|
||||||
"""List all Detective behavior graphs in the account."""
|
"""List all Detective behavior graphs in the account."""
|
||||||
client = session.client('detective')
|
client = session.client('detective')
|
||||||
response = client.list_graphs()
|
graphs = _collect_all_pages(client.list_graphs, 'GraphList')
|
||||||
graphs = response.get('GraphList', [])
|
|
||||||
|
|
||||||
if not graphs:
|
if not graphs:
|
||||||
print("[!] No behavior graphs found. Enable Detective first.")
|
print("[!] No behavior graphs found. Enable Detective first.")
|
||||||
@@ -33,13 +45,13 @@ def list_behavior_graphs(session):
|
|||||||
return graphs
|
return graphs
|
||||||
|
|
||||||
|
|
||||||
def list_investigations(session, graph_arn, severities=None, max_results=20):
|
def list_investigations(session, graph_arn, severity=None, max_results=20):
|
||||||
"""List investigations filtered by severity."""
|
"""List investigations filtered by severity."""
|
||||||
client = session.client('detective')
|
client = session.client('detective')
|
||||||
|
|
||||||
filter_criteria = {}
|
filter_criteria = {}
|
||||||
if severities:
|
if severity:
|
||||||
filter_criteria['Severities'] = severities
|
filter_criteria['Severity'] = {'Value': severity}
|
||||||
|
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'GraphArn': graph_arn,
|
'GraphArn': graph_arn,
|
||||||
@@ -48,8 +60,9 @@ def list_investigations(session, graph_arn, severities=None, max_results=20):
|
|||||||
if filter_criteria:
|
if filter_criteria:
|
||||||
kwargs['FilterCriteria'] = filter_criteria
|
kwargs['FilterCriteria'] = filter_criteria
|
||||||
|
|
||||||
response = client.list_investigations(**kwargs)
|
investigations = _collect_all_pages(
|
||||||
investigations = response.get('InvestigationDetails', [])
|
client.list_investigations, 'InvestigationDetails', **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
if not investigations:
|
if not investigations:
|
||||||
print("[+] No investigations found matching criteria")
|
print("[+] No investigations found matching criteria")
|
||||||
@@ -96,13 +109,12 @@ def list_indicators(session, graph_arn, investigation_id, max_results=50):
|
|||||||
"""List indicators for a specific investigation."""
|
"""List indicators for a specific investigation."""
|
||||||
client = session.client('detective')
|
client = session.client('detective')
|
||||||
|
|
||||||
response = client.list_indicators(
|
indicators = _collect_all_pages(
|
||||||
|
client.list_indicators, 'Indicators',
|
||||||
GraphArn=graph_arn,
|
GraphArn=graph_arn,
|
||||||
InvestigationId=investigation_id,
|
InvestigationId=investigation_id,
|
||||||
MaxResults=max_results,
|
MaxResults=max_results,
|
||||||
)
|
)
|
||||||
|
|
||||||
indicators = response.get('Indicators', [])
|
|
||||||
if not indicators:
|
if not indicators:
|
||||||
print("[+] No indicators found for this investigation")
|
print("[+] No indicators found for this investigation")
|
||||||
return []
|
return []
|
||||||
@@ -150,11 +162,13 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--severity",
|
"--severity",
|
||||||
nargs="+",
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="Severity filter (e.g. HIGH CRITICAL)",
|
choices=["INFORMATIONAL", "LOW", "MEDIUM", "HIGH", "CRITICAL"],
|
||||||
|
help="Severity filter (e.g. HIGH)",
|
||||||
)
|
)
|
||||||
parser.add_argument("--max-results", type=int, default=20)
|
parser.add_argument("--max-results", type=int, default=20,
|
||||||
|
help="Max results per API call (1-100)")
|
||||||
parser.add_argument("--region", default="us-east-1")
|
parser.add_argument("--region", default="us-east-1")
|
||||||
parser.add_argument("--profile", type=str, help="AWS profile name")
|
parser.add_argument("--profile", type=str, help="AWS profile name")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -162,6 +176,9 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.max_results < 1 or args.max_results > 100:
|
||||||
|
parser.error("--max-results must be between 1 and 100")
|
||||||
|
|
||||||
kwargs = {"region_name": args.region}
|
kwargs = {"region_name": args.region}
|
||||||
if args.profile:
|
if args.profile:
|
||||||
kwargs["profile_name"] = args.profile
|
kwargs["profile_name"] = args.profile
|
||||||
|
|||||||
Reference in New Issue
Block a user