GET /.aws/credentials Requests in Your Logs: Scanners Hunting AWS Keys
Published
An automated scan for the AWS shared credentials and config files, which hold long-lived access keys and profile settings in plain text whenever a web server hands one back.
What it looks like
203.0.113.7 - - [18/Oct/2026:03:51:11 +0000] "GET /.aws/credentials HTTP/1.1" 404 162 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" 198.51.100.42 - - [18/Oct/2026:03:51:12 +0000] "GET /.aws/config HTTP/1.1" 404 162 "-" "python-requests/2.31.0" 192.0.2.88 - - [18/Oct/2026:04:12:03 +0000] "GET /root/.aws/credentials HTTP/1.1" 404 162 "-" "Go-http-client/2.0"
On our own server, the 30 days to 18 October 2026
247
requests · about 8.2 a day
39
source addresses
| Most requested variants | Requests |
|---|---|
| /.aws/credentials | 98 |
| /.aws/config | 64 |
| /root/.aws/config | 9 |
| /root/.aws/credentials | 8 |
| /home/*/.aws/credentials | 8 |
| /config/.aws/credentials | 4 |
| /app/.aws/credentials | 4 |
| /backend/.aws/credentials | 4 |
LogTriage's production API server (Hetzner, Germany): port 22 open to the internet, key-only since 6 October 2026 (password and keyboard-interactive authentication off); nginx in front of the API on ports 80 and 443. Paths as requested, query strings dropped and identifiers redacted; counted weekly from the nginx access log. More in the monthly attack report.
What the request is
~/.aws/credentials and ~/.aws/config are the shared configuration files that the AWS CLI and the AWS SDKs read at startup. The credentials file holds aws_access_key_id, aws_secret_access_key and sometimes aws_session_token, grouped into named profiles, as plain INI-style text with no encryption of its own. The config file holds the matching per-profile settings — region, output format, assumed-role and SSO configuration — which is less immediately usable but names roles and accounts an attacker would otherwise have to guess.
Neither file is protected by anything except its location: it normally lives in a user’s home directory, outside any web root, with filesystem permissions. A scanner asking for /.aws/credentials is testing whether that assumption broke on your host. It usually breaks in one of a few ways — root pointing at a project directory or a home directory rather than at a public/ subdirectory, a container image where the application runs as a user whose home is the same directory it serves, a deployment artifact or backup that carried a .aws folder into the served tree, or an alias that maps a parent directory into the URL space.
That is why the request list is longer than one path. The counted variants here include the bare /.aws/credentials and /.aws/config, home-directory forms such as /root/.aws/credentials, and the same names one level down under the directory names frameworks and containers commonly use: /config/, /app/, /backend/. Each one is a single cheap GET, and only one has to be reachable for the scan to pay off.
Who sends it
This is undirected, internet-wide scanning. The senders are mass scanners sweeping address ranges, credential-harvesting botnets that feed any recovered access key straight into cloud abuse such as resource hijacking or data access, and research crawlers that catalogue exposures. Nothing about a request to /.aws/credentials means your organisation was singled out.
The user agent proves little. Some clients announce an HTTP library such as python-requests or Go-http-client; many copy a current Chrome string. The shape is more telling: several paths from one address within seconds, no Referer, no requests for CSS, JavaScript or images, and a move on to other well-known secret files — .env, .git/config — as soon as these return 404. The same crawler run usually shows up on the /.env and /.git/config pages of your log too.
Is your server exposed?
Check what your logs already answered, then test the live path from the host itself:
# Every .aws path in the log, with the status it received
awk '$7 ~ /\.aws\// {print $9, $7}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head -20
# Anything that was NOT 404/403 — this is the list that matters
awk '$7 ~ /\.aws\// && $9 !~ /^(404|403)$/ {print}' /var/log/nginx/access.log
# Ask your own server, from your own server, and read the status code
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/.aws/credentials
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/.aws/config
# Does an AWS credentials file sit inside a served tree at all?
find /var/www /srv /app -maxdepth 5 -path '*/.aws/*' -type f 2>/dev/null
Run the curl checks with the Host header of each vhost you serve, not just the default one — a dotfile rule added to the main site and forgotten on a staging or internal vhost is the usual way these files stay reachable. Give rotated and compressed logs the same awk pass, since an exposure may have been closed after it was already used.
How to block it
The durable fix is that no .aws directory should exist anywhere under a document root: point root at the application’s public directory, and keep credentials in the home directory of the service account or, better, replace static keys with an instance role or another short-lived credential source so there is no file to leak.
Add a dotfile rule as the second layer, in every server block:
# ACME and other well-known URIs must stay reachable
location ^~ /.well-known/ {
allow all;
}
# Everything else with a dot-prefixed path segment: not found, not logged
location ~ /\. {
access_log off;
log_not_found off;
return 404;
}
The ^~ prefix location is matched ahead of the regular-expression location, so ACME challenges keep working while /.aws/credentials, /root/.aws/config and /app/.aws/credentials all hit the regex — it matches a dot after any slash, not only at the start of the URI. Returning 404 rather than 403 tells the scanner nothing about which paths exist.
If any of these paths ever returned 200, the block is not the end of the job: the keys in that file are disclosed, and they have to be rotated and their use reviewed.
Probes that arrive with it
- GET /.env Requests in Your Logs: Scanners Hunting for App Secrets An automated scan for application .env files, which hold database passwords, API keys and other secrets in plain text whenever the web server hands one back.
- GET /.git/config Requests in Your Logs: Scanners Hunting Exposed Repos An automated check for a .git directory left inside the web root, which hands a scanner the repository's configuration, its history and ultimately the application's source code and committed secrets.
- GET /containers/json Requests in Your Logs: Docker Engine API Scanning An automated probe for the Docker Engine API's container-listing endpoint, which on a daemon reachable over the network answers without any authentication and gives the caller control of the host.
Detect it
Frequently Asked Questions
- Every one of these got a 404 on our edge. Is there anything to investigate?
- Not as an incident — a 404 means no bytes left the server and this is undirected background scanning that will not stop. The useful work is preventive: confirm no .aws directory sits under any document root, confirm the dotfile rule exists in every server block including staging and admin vhosts, and alert on non-404 responses to these paths rather than on the requests themselves.
- Which response codes to /.aws/ paths should wake someone up?
- A 200 or 206 to any path ending in .aws/credentials or .aws/config is a credential disclosure until you prove otherwise, and the response size in the log tells you roughly how much was served. A 301 or 302 is worth one look to see where it points; 403 is acceptable as long as it is a deny and not an error page that echoes a filesystem path.
- We served a real /.aws/credentials. What is the clean-up?
- Treat every key in the file as public. Deactivate and then delete the affected access keys in IAM and issue new ones, rotate any session token the file contained, and review CloudTrail for calls made with those key IDs from the first successful request onward. Then move the file out of the served tree and grep the access log for other 200s from the same addresses.
- Should we block the scanning IP addresses?
- Blocking individual addresses buys little, because the source pool rotates constantly and the traffic is aimed at whole ranges rather than at you. Fix the exposure first; if the request volume costs you anything, drop it cheaply at the edge with a dotfile location rule and turn logging off for it instead of maintaining a blocklist.
See what else is probing your server
Drop an nginx or Apache access log into the free browser analyzer: secret-file sweeps, exploit probes, scanners and credential stuffing are grouped by source with the evidence lines, and every known probe links back here. Nothing is uploaded.