AWS Cloud Security – Protecting Against SSRF
AWS cloud security depends on careful consideration of threats against both the application being built and the AWS infrastructure itself. Application developers often focus on the endpoints which the application will service, but security architects recognize that attackers will also try to find and exploit vulnerabilities that exist in the “empty space” between the intended uses of those endpoints. Server-side request forgery (SSRF) is one example of such a vulnerability.
SSRF is a threat in any public facing website or API which accepts user input and makes back-end requests to protected resources such as databases and complementary services based on that input. Access controls are in place to protect those back-end resources, but the web application is granted significant access because it requires access for normal operation. In SSRF attacks, a vulnerability in the web application enables an attacker to leverage those broad permissions and access protected internal resources through public endpoints in the vulnerable application. In other words, SSRF creates a hole through trust boundaries.
Those trust boundaries define what various users and apps should be allowed to access. They are often well understood for infrastructure that is hosted on-premises or in your data center, because the rules were built from the ground up by your team. Cloud-based hosting is a different story, because there is often implicit or default behavior to consider when configurations are incompletely- or poorly-defined.
AWS Cloud Security Risks
In AWS infrastructure, there are a few services that form a trusted group when provisioned with default configurations. Among these services, any HTTP REST API request from one service to another is implicitly trusted. Given this implicit trust and the fact that it may not be readily apparent to developers, SSRF should be considered a high-risk threat in AWS hosted applications. Teams should pay special attention to the impact and likelihood of exploiting this implicit trust among the services.
One particular concern is the Instance Metadata Service (IMDS). This service exposes a REST API accessible to any EC2 instance via “http://169.254.169.254”, and enables fetching configuration and authentication information for the instance. This information is extremely interesting to an attacker, so AWS security best practices include establishing role-based access controls around how EC2 instances can access other resources in the cloud.
Let’s say our application uses a common architecture consisting of sensitive data stored in a private S3 bucket, and a front-end web application hosted in an EC2 instance. The S3 bucket is locked down, except that it can be accessed by a specific IAM role which is assigned to the EC2 instance so that the web app can access the data. Users can access the web app, and the app controls what those users can access in the S3 bucket.
But there’s a hidden risk: the web app is running in EC2, so the IMDS is present. If an attacker were able to leverage SSRF to access “http://169.254.169.254/latest/meta-data/iam/security-credentials/IAM_Role”, they would get the AccessKeyID, SecretAccessKey, and Token necessary to bypass the web app’s protections and directly retrieve arbitrary data from the private S3 bucket.
If this seems like a security hole to you, keep in mind that IMDS solves an important problem. Without it, resource access would typically require hard-coded credentials or pre-shared keys which are even harder to manage and control. Not all apps require this, but if IMDS is necessary then we must find a way to secure it.
There are actually two versions of IMDS. The first version, IMDSv1 uses a request/response model which makes it easier to exploit through vulnerabilities such as SSRF. After identifying this weakness, AWS released IMDSv2 which is session oriented and adds extra layers to protect against SSRF vulnerabilities. The application acquires a secret token with a PUT request, typically before accepting user requests, and passes that secret token with every subsequent request. IMDSv2 will reject any request that doesn’t include the secret token, and attackers have no way of determining the secret token so this type of dynamic request avoids the weakness of IMDSv1.
But there’s a catch: IMDSv1 is available unless you explicitly specify that IMDSv2 must be used.
Requiring IMDSv2 in Terraform
Terraform is the most common tool for Infrastructure as Code, so let’s consider how to use it to require IMDSv2. If we want to create an EC2 instance in our Terraform file, we might do something like this:
The configuration doesn’t explicitly require IMDSv2, so IMDSv1 will be available and potentially exploitable by SSRF. To require IMDSv2, we need to include a metadata_options block:
Simply adding the block is not sufficient. In order to require IMDSv2, the http_endpoint setting must be enabled (disabled will disable IMDS altogether) and http_tokens must be required. If http_tokens is not present, or set to optional like in the example above then IMDSv2 is not required. More information is available in the Terraform docs.
Policy as Code helps manage the risk
Most cloud native teams are already using Infrastructure as Code tools such as Terraform to automate provisioning of infrastructure and improve the consistency of configurations. Adding Policy as Code tools such as Terrascan enables you to enforce policies such as requiring IMDSv2 and effectively shift cloud security left.
Much as Infrastructure as Code refers to codifying the infrastructure so that it can be referenced, versioned and compared over time, Policy as Code refers to codifying policies so that they can be consistently evaluated and enforced throughout the application lifecycle.
Terrascan is an open source Policy as Code tool which leverages the Open Policy Agent (OPA) from the Cloud Native Computing Foundation. It includes more than 500 policies out of the box, identifying violations of security best practices such as the CIS Benchmarks, and is extensible through the OPA for users that want to create custom policies. As an aside, we accept contributions if you create a policy that you think would be useful to others.
One of the policies included in Terrascan ensures that IMDSv2 is required:
If it identifies aws_instance configurations where the metadata_options block is not present, or http_tokens is not required when http_endpoint is not disabled. Violations of this policy will be reported so that your developers can fix the configuration:
By running Policy as Code tools like Terrascan during development, ideally within automated pipelines, you can establish proactive guardrails that maintain secure configurations long before the infrastructure is deployed. This can harden your infrastructure and ensure that sensitive components are not accessible even if attackers are able to exploit vulnerabilities like SSRF.
Adopting modern development practices such as Infrastructure as Code and Policy as Code enables development teams to increase velocity and predictability through paradigms such as immutable infrastructure and immutable security. This translates into more scalable and resilient deployments by embedding self-healing capabilities into automated development processes.