Skip to content

Category: AWS

AWS Lambda (Python) – Preventing Command Injection

When writing a AWS lambda function, we need to be careful about secure coding issues.

If your lambda function takes an input and use it to run a command, you need to avoid using os.system(...). You should use subprocess.run(...) but without the shell. The commands provided to subprocess should be in arguments.

Explaining the injection

I will use an example of AWS Lambda function that takes an input from event and uses it in a subprocess to run a command. Notice the command is used in a f-string.

import subprocess
import uuid

def lambda_handler(event, context):
    rand_value = uuid.uuid4().hex[:6]
    file_name = f'{event['file_name']}_{rand_value}'
    print('Creating:', file_name)

    result = subprocess.run(f'cd /tmp && touch {file_name} && ls -la', shell=True, stdout=subprocess.PIPE)
    print(result.stdout.decode('utf-8'))

We can inject a malicious filename to steal the values in env

ubuntu@ubuntu-virtual-machine:~$ python3 command_injection.py
Enter file name: hello && env | base64 #
Creating: hello && env | base64 #_526feb
R0pTX0RFQlVHX1RPUElDUz1KUyBFUlJPUjtKUyBMT0cKTEVTU09QRU49fCAvdXNyL2Jpbi9sZXNzcGlwZSAlcwpVU0VSPXVidW50dQpTU0hfQUdFTlRfUElEPTE0MDIKWERHX1NFU1NJT05fVFlQRT14MTEKU0hMVkw9MQpIT01FPS9ob21lL3VidW50dQpPTERQV0Q9L2hvbWUvdWJ1bnR1CkRFU0tUT1BfU0VTU0lPTj11YnVudHUKR05PTUVfU0hFTExfU0VTU0lPTl9NT0RFPXVidW50dQpHVEtfTU9EVUxFUz1nYWlsOmF0ay1icmlkZ2UKTUFOQUdFUlBJRD0xMDI5CkRCVVNfU0VTU0lPTl9CVVNfQUREUkVTUz11bml4OnBhdGg9L[...]

Defence

We should refactor the vulnerable code by adding two defences:

Input validation

Dependings on the context, try to whitelist the allowed characters.

If you do not know in advanced then it is crucial that you follow the next defence of using arguments command for subprocess.

Usage of arguments in subprocess

Instead of providing the commands in concatenated string or string format, provide an argument format ['touch', file_name]. This means you need to break down the command in different `subprocess` calls. You cannot run the multiple commands in one single subprocess.

One common routine is that you need to run a sequence of command at a specific same directory. With the use of cwd argument in subprocess.run(...), you can specify where the command should be executed.

import subprocess
import uuid

def lambda_handler(event, context):
    rand_value = uuid.uuid4().hex[:6]
    file_name = f'{event['file_name']}_{rand_value}'
    print('Creating:', file_name)

    working_dir = '/tmp'
    subprocess.run(['touch', file_name], cwd=working_dir, stdout=subprocess.PIPE)
    result = subprocess.run(['ls', '-la'], cwd=working_dir, stdout=subprocess.PIPE)
    print(result.stdout.decode('utf-8'))

Canary Token is great…but beware of a flaw when using Thinkst’s free service for Canary AWS token


Introduction

Canary Token is a great idea for early detection of possible intrusion in your system.

But be careful when you are using the free service from Thinkst Canary (https://canarytokens.org/generate) to generate Canary AWS tokens. You shouldn’t use it for real life production.

A threat actor may notice a common pattern in the AWS access key id (generated from Thinkst Canary) and avoid triggering your honeypot detection.

AWS Access Key ID Format

Aidan Steele wrote an in-depth article on AWS Access Key ID format. Highly recommend to read it.

Things to note from Aidan’s article:

  • The first 4 chars of the AWS Access Key will be indicate the resource types (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids)
  • The next 8 chars of the access key will be mapped to a specific AWS account.

We can get the AWS account id from the access key by using the aws-cli

$ aws sts get-access-key-info --access-key-id <<ACCESS_KEY_ID>> --query Account --no-cli-pager
xxxxxxxxxxxxxxxxxxxxxxxxxxx

Flaws from the free service

Let’s generate a few Canary tokens from https://canarytokens.org/generate

[default]
aws_access_key_id = AKIAYVP4CIPPxxxxxxxU
aws_secret_access_key = xxxxxxxx
output = json
region = us-east-2
[default]
aws_access_key_id = AKIAYVP4CIPPxxxxxxxW
aws_secret_access_key = xxxxxxxxx
output = json
region = us-east-2

As you can see from the two generated Canary token, the four chars of the access key id show that this is an access key. Then the next crucial pattern (next 8 chars) will expose the information that this access key belongs to the AWS account from the free Canary token service.

A smart threat actor will detect this pattern and avoid using it. As a result, your Canary Token honeypot will likely to fail.

Takeaways

  • Avoid using Canary Token services which provide predictable access key pattern. A threat actor can identify the pattern and avoid triggering the canary token.
  • Canary Token is one of the detectors that we should use, but beware of confirmation biases (Canary token not triggered does not mean there is no intrusion).