MITM Demo using AWS API Gateway and Lambda deployed via Terraform

Imagine wanting to modify an API request or response of a production system without actually touching that production system. One of the easiest ways to do that is to put a man-in-the-middle (MITM) device between the device making the request and the production API system. There are several ways to implement a MITM, this is a ‘hello world’-like implementation utilizing AWS API Gateway with Lambda running Python 3.6 and deployed via Terraform.

How it works:

The overview above transpires in under 500 µs, impressive to say the least. Furthermore the cost savings over running a dedicated EC2 instance is substantial.

Cost savings

Ignoring data costs as they will be similar between the two solutions. The smallest EC2 (t2.nano) instance will cost $50.81/year to run. Compare that to the API-Gateway/Lambda solution which will cost $17.90 for the same application. Below is how those cost break out .

EC2 solution (sans bandwidth costs): 24 (hours) * 365 (days) * 0.0058 (Price per hour) = 50.808

API Gateway / Lambda solution (sans bandwidth costs):

60 (seconds) * 60 (minutes) * 24 (hours) * 365 (days) = 31,536,000 seconds in a year

31536000 / 8 (request every 8 seconds) = 3,942,000 requests the application will make in a year

Lambda specific cost:

$0.00000000208 (Price per µs in a 128MB env) * 500 (max processing time in µs) * 3942000 (requests per year) = 4.09968

API Gateway specific cost:

$0.0000035 (Price per API call to API Gateway) * 3942000 = 13.797

Costs per year:

$13.797 (API Gateway) + $4.09968 (Lambda) = 17.89668 (plus data)

Example

For simplicity sake, this will concept will be demonstrated using a simple API endpoint. At ip-api.com requests can be made to various endpoints which respond with geo-IP information from the requestor’s IP in the format of the endpoint name. For example if a request is made to the /json endpoint, the response is in JSON. Below is the base line response* when I hit that API endpoint from my local machine.

justin@local-machine$ curl -o- -q http://ip-api.com/json
{
    "as": "AS209 Qwest Communications Company, LLC",
    "city": "Salt Lake City",
    "country": "United States",
    "countryCode": "US",
    "isp": "CenturyLink",
    "lat": 40.7079,
    "lon": -111.8555,
    "org": "CenturyLink",
    "query": "65.1.2.3",
    "region": "UT",
    "regionName": "Utah",
    "status": "success",
    "timezone": "America/Denver",
    "zip": "84106"
}
justin@local-machine$

After I deploy via Terraform, I can now hit the MITM proxy endpoint setup in AWS. You will notice that the ‘org’ field has been modified by the Lambda environment.

justin@local-machine$ curl -o- -q http://mitm-demo.justinholcomb.me/json
{
    "as": "AS16509 Amazon.com, Inc.",
    "city": "Columbus",
    "country": "United States",
    "countryCode": "US",
    "isp": "Amazon.com",
    "lat": 39.9653,
    "lon": -83.0235,
    "org": "Amazon.com ThisHasBeenManipulatedDict",
    "query": "18.221.15.77",
    "region": "OH",
    "regionName": "Ohio",
    "status": "success",
    "timezone": "America/New_York",
    "zip": "43215"
}
justin@local-machine$

As you can see above, because the requestor is the Lambda environment, the Lambda’s IP is returned. The API at ip-api.com accepts using /{format}/{ip} endpoint to return IP information for a different IP address. See below:

justin@local-machine$ curl -o- -q http://mitm-demo.justinholcomb.me/json/65.1.2.3
{
    "as": "AS209 Qwest Communications Company, LLC",
    "city": "Salt Lake City",
    "country": "United States",
    "countryCode": "US",
    "isp": "CenturyLink",
    "lat": 40.7079,
    "lon": -111.8555,
    "org": "CenturyLink ThisHasBeenManipulatedDict",
    "query": "65.1.2.3",
    "region": "UT",
    "regionName": "Utah",
    "status": "success",
    "timezone": "America/Denver",
    "zip": "84106"
}
justin@local-machine$

Furthermore, the code has some commented out lines to utilize the ‘X-Forwarded-For’ request header and this endpoint. However it was omitted in this for clarity.

As demonstrated, this is an easy and cost effective way to implement a man-in-the-middle for a production system to observe and manipulate request/response traffic for legitimate/nefarious reasons. This can be very powerful as anything within the request or response can be modified from the headers, parameters, and or body. This ability can be particularly helpful for QA teams trying to push the limits of an application to the point of breaking. Furthermore, Terraform makes it easy to deploy within minutes and because the infrastructure is built using code (IaC) rather than direct human interaction, changes are easy to document to pin to a particular behavior characteristic and then revert if necessary.