Signed, Sealed, Delivered… Secure? (Pre-) Signed AWS URL Hacks
In the fast-paced world of web development, managing access to sensitive resources is crucial. Developers often leverage AWS signed and pre-signed URLs in their web applications to grant users temporary access to files stored in S3 buckets, striking a balance between accessibility and security. However, improper implementation can introduce vulnerabilities that malicious actors can exploit. In this post, we will explore real-world examples where inadequate input validation has led to unauthorized access and exposure of sensitive data in web applications that use signed and pre-signed URLs.
Understanding Signed and Pre-signed URLs
When it comes to accessing objects in an AWS S3 bucket, signed and pre-signed URLs are popular choices. These mechanisms provide a way to securely share files without exposing your AWS credentials. Let’s break them down:
Pre-Signed URL: These are used primarily with Amazon S3, and allow users to perform actions like uploading or downloading S3 objects using the credentials of the AWS user who generated the URL. An example pre-signed URL looks like this:
https://myexamplebucket.s3.amazonaws.com/?X-Amz-Expires=NUM&x-amz-security-token=TOKEN&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=CREDENTIAL&X-Amz-Date=DATE&X-Amz-SignedHeaders=HEADERS&X-Amz-Signature=SIGNATURE
Key parameters are:
X-Amz-Expires
x-amz-security-token
X-Amz-Algorithm
X-Amz-Credential
X-Amz-Date
X-Amz-SignedHeaders
X-Amz-Signature
Signed URLs: These are typically associated with Amazon CloudFront and allow for more granular access control through the use of canned or custom policies. Check out this example signed URL:
https://example123.cloudfront.net/?Expires=DATE&Signature=SIGNATURE&Key-Pair-Id=ID
Key parameters are:
Expires
Signature
Key-Pair-Id
Note: CloudFront signed URLs can also use custom domains instead of the default cloudfront.net
domain.
Both pre-signed URL and signed URL provide secure file access but can lead to vulnerabilities if not implemented correctly.
To better understand how signed and pre-signed URLs function within a web application, take a look at the images below:
This blog post will specifically focus on Steps 1 and 2, which cover the implementation aspects of signed and pre-signed URLs.
A chain is only as strong as its weakest link
The mechanisms for generating AWS signed and pre-signed URLs leverage strong cryptography. Brute-forcing to generate a valid signed URL is extremely difficult due to the unique digital signatures associated with specific parameters and expiration times. Without knowing the exact parameters and the correct signing key, it is practically impossible to forge a valid URL that would work before it expires.
Therefore, the real vulnerabilities often arise from how these URLs are implemented within web applications. Flaws in custom logic used to generate these URLs can create opportunities for attackers to exploit, leading to unauthorized access and data exposure. In this blog post, I will focus on a few ways to exploit signed and pre-signed URLs in web applications, particularly through insufficient validation of user inputs that can trick the server into generating valid links for otherwise inaccessible paths.
Real-World Exploits
Now, let’s dive into the fun part! Here are some alarming scenarios I have uncovered during security assessments of web applications that utilize signed and pre-signed URLs.
- Insecure direct object references (IDOR) via specification of a different path
Here is the scenario: When a user requests a pre-signed URL to access a file in S3, the HTTP POST request used to generate the URL includes the S3 object’s path as a parameter. While directory traversal attacks might come to mind, the actual exploit here was more direct. If a malicious user modified the object path to refer to a different S3 object, the server didn’t reject the altered request. Instead, it generated a pre-signed URL that granted access to that unauthorized S3 object.
Example request:
POST /get-pre-signed-url HTTP/1.1
Host: download.example.com
...
{
"method": "getObject",
"key": "pdfs/123/receipt.pdf",
"type": "pdf"
}
Here the parameter that contains the actual file path is labeled as key
, with user IDs following a sequential pattern (e.g., 123
), and, in this case, if a malicious user changes the key value to pdfs/124/receipt.pdf
, they could view another user’s receipt. This request with key
value being pdfs/124/receipt.pdf
and its redacted response are shown below:
POST /get-pre-signed-url HTTP/1.1
Host: download.example.com
...
{
"method": "getObject",
"key": "pdfs/124/receipt.pdf",
"type": "pdf"
}
--
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
...
{"url":"https://s3.us-west-2.amazonaws.com/pdf.example.com/pdfs/124/receipt.pdf?AWSAccessKeyId=<REDACTED>&Expires=<REDACTED>&Signature=<REDACTED>&x-amz-security-token=<REDACTED>"}
The implications? A serious data breach, exposing sensitive personally identifiable information (PII) en masse.
- AWS S3 bucket directory listing via a slash (
/
)
In situations where the exact file paths and names are difficult to guess, the attacker’s hope lies in triggering an AWS S3 bucket listing. This exploit showcases how a user can do so by supplying /
as the value for the vulnerable parameter. In this web application, the /s3-download.php
endpoint is queried when users attempt to access documents and the filePath
is the vulnerable query parameter used in this endpoint. By passing /
as the value for filePath
, a valid AWS signed URL is generated that lists the objects of the downloads.example.com
AWS S3 bucket. From here, a malicious user could then access any files they’d like within this bucket.
Example request for bucket directory listing and its 302 redirect response to the actual ListBucketResult
content for the the downloads.example.com
AWS S3 bucket:
GET /s3-download.php?filePath=/ HTTP/1.1
Host: example.com
--
HTTP/1.1 302 Found
Connection: keep-alive
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Location: https://downloads.example.com/?Expires=<REDACTED>&Signature=<REDACTED>&Key-Pair-Id=<REDACTED>
...
- Compromising AWS S3 Buckets With Empty Parameter Values
This next technique takes the previous example a step further by allowing users to retrieve all files across all AWS S3 buckets.
The AWS pre-signed URL generated in this web application relies on two parameters: objectKey
and/or bucketName
. I found that by passing an empty value for the bucketName
parameter, users can manipulate the web server into generating a valid link that lists all available buckets. Once the user has access to the list of bucket names, they can then submit an empty value for the objectKey
parameter for each bucket. This two-step process allows users to retrieve all the files across all buckets stored in the client’s Amazon S3, including those that are not accessible through the user interface. This not only exposes sensitive data but also highlights the critical need for proper input validation and access controls.
Example request to retrieve all AWS S3 buckets and its 302 redirect response to the actual ListAllMyBucketsResult
content:
GET /GenPresignedURL?bucketName= HTTP/1.1
Host: portal.example.com
--
HTTP/1.1 302 Found
Connection: keep-alive
Content-Length: 0
Content-Type: text/html
Location: https://s3.us-east-2.amazonaws.com?X-Amz-Expires=<REDACTED>&x-amz-security-token=<REDACTED>&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<REDACTED>&X-Amz-Date=<REDACTED>&X-Amz-SignedHeaders=host;x-amz-security-token&X-Amz-Signature=<REDACTED>
...
Takeaway
The security of web applications utilizing AWS signed and pre-signed URLs heavily depends on how these mechanisms are implemented. While AWS provides robust tools for access control, developers must exercise caution in their implementation. To protect your web application, consider these security practices to minimize the risk of unauthorized access and data exposure:
-
Strict parameter validation and sanitization: Always validate and sanitize user-controlled parameters used in generating signed or pre-signed URLs. Ensure that user-supplied values do not allow unauthorized path access or directory listings.
-
Enforce robust authorization checks: Enforce strict access controls to verify that users have the right to access the requested files before generating URLs. Remember that while these signed or pre-signed URLs authenticate users, they do not authorize access.
-
Handle empty parameters cautiously: Implement checks to prevent the use of empty or null parameters in requests that generate URLs. This can help mitigate risks related to unintended bucket listings or access to all objects in a bucket.