Is Your Web App Secure Enough?

Ali Haydar
The Startup
Published in
9 min readJan 11, 2021

** The views in this post are my own and do NOT represent or reflect the views of my employer or any organizations **

In this post, we will build an insecure web application. The goal is to share some of my learnings building AWS serverless apps. At the same time, I am hoping this would start a discussion that increases the awareness around security when architecting or developing software.

This came to mind as I went through the “Secure development training” program at https://academy.safestack.io/ lately, so thought that could be an opportunity to introduce a couple of security vulnerabilities as part of the app, leaving it to the reader to identify them and suggest fixes. Hopefully, this would be a fun exercise.

Below is the stack used:

  • CloudFormation
  • AWS lambda
  • AWS API Gateway
  • DynamoDB
  • React JS
  • Node JS

Before we start, here is a quick view of the program:

Other than the awesome “Security Fundamentals” course, which covers vulnerability & risk, threat actors & motivations, and explains why security matters, there is the more technical “Finding and Fixing Web Application Security Vulnerabilities” course that has some pretty cool explanations and labs on how to secure web apps. As my words can’t do justice to this program, I would encourage you to give it a try. Oh, and there’s also a new “Threat assessment” course that got added lately. Overall, I highly recommend these courses for all development teams.

What are we building?

The app we’re building will retrieve a list of companies from a dynamo DB and display them on the UI; selecting a company will show its revenue. Note that only companies flagged with “public” in the DB will be displayed on the UI. No details about private companies will be visible.

Please check this link if you’d like to take a gander at the demo web application before reading the steps — Please ignore the styling as it’s not part of this post.

Let us begin with the implementation step by step. The architecture of our app is pretty simple and looks like this:

Create DynamoDB

First, we will setup the dynamo database table using CloudFormation (check my previous article about getting started with CloudFormation).

Below is a summary about DynamoDB to get us started:

**you can skip this section if you are familiar with DynamoDB concepts**

DynamoDB is a schemaless database that has the following components: table, items and attributes. A table is a collection of items, and each item is a collection of attributes.

Each item has a unique primary key, which distinguishes each item from the others.

The primary key can either be simple or composite — When the primary key is simple it will be formed of a single key called partition key . In the composite case, the primary key will be formed of 2 attributes partition key & sort key .

A partition key value is used as an input to an internal hash function in dynamo; this function outputs the actual partition that is the physical storage in which the item will be stored. If the primary key was simple, the partition key must be unique. If the primary key was composite, then multiple items can have the same partition key; these items are stored together in sorted order by the sort key value.

We can have as many attributes as we want in an item. An attribute is a data element that’s doesn’t need to be broken any further

In our use case, we need to access the DB to get the list of companies that are public (this is a scan of the DynamoDB table). We also need to get the attributes of a given public company (this is a query by company id). A possible way to design the table is to have a partition_key that would contain the company id (e.g.company#id), a sort_key that would contain whether the company is private or public. This enables us to scan the table then filter it by the sort key (private/public), and at the same time enable us to query for a single company by the partition key (CompanyId).

This is the CloudFormation template to build the DynamoDB:

Upload the template to CloudFormation using AWS console, create the stack and verify the DynamoDB got created successfully. Create a few items in the companies table — Feel free to create these manually using the AWS console or through the AWS CLI.

In this command, we’re putting a new item that represents a company that has a name and revenue. Create a few items, changing the CompanyId, Type, and details in every request. The M and S letters in the command represent the type of field we’re adding. The full list of data types can be found here.

AWS SAM — Lambda & Api endpoint creation

We will start this section by setting up AWS SAM, which is an open-source framework that you can use to build serverless applications. It will provide us with the necessary resources such as API and Lambda (Sam will help us generate boilerplate code, from which we can start the development of our app)

  • Start by installing SAM client
  • Download a sample SAM application: sam init
  • Answer the questions as they appear; I selected the following:
Which template source would you like to use?  
1 - AWS Quick Start Templates
What package type would you like to use?
1 - Zip (artifact is a zip uploaded to S3)
Which runtime would you like to use?
1 - nodejs12.x
AWS quick start application templates:
1 - Hello World Example

The sam init command creates a directory with the name that you provided as the project name. Full details can be found at https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html

Copy/paste the dynamoDB resource we created earlier into the new template.yaml that got generated, under the Resources section. This will include the companies table in the new stack that we’re creating; it makes sense to delete the old stack in this case. In the AWS console, navigate to Cloud Formation, select the earlier created stack and delete it.

Note in the yaml file that this template outputs the arn of the api gateway endpoint, the function, and the lambda role.

Stack Name [sam-app]: companies-data-demoAWS Region [us-east-1]:
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y #SAM needs permission to be able to create roles to connect to the resources in your templateAllow SAM CLI IAM role creation [Y/n]: Y HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y Save arguments to configuration file [Y/n]: Y SAM configuration file [samconfig.toml]: SAM configuration environment [default]:

A new stack will now be created with the following resources:

Navigate to the AWS console, notice that a new lambda and API gateway endpoint got created. Now, it’s time to update these to reflect what we want.

API & Lambda infrastructure

First, we will start by fixing the name of the lambda and deleting anything related to hello world. Change the HelloWorldLambda under Resources to:

Delete the Outputs section, as we do not need the stack outputs in our example.

Run sam build and sam deploy again to build & deploy the updated stack. The changeset will look like this:

To validate this in the AWS console:

  • Navigate to Lambda, and select the newly created lambda to see how it’s connected to the API gateway
  • Navigate to dynamoDB and validate that the “companies” table got created. Note that it has no data at the moment. Feel free to use the command listed in the section above to load some data

You might be wondering how is the API being created even though our CloudFormation template does not include it. According to the AWS documentation:

An AWS::Serverless::Api resource need not be explicitly added to a AWS Serverless Application Definition template. A resource of this type is implicitly created from the union of Api events defined on AWS::Serverless::Function resources defined in the template that do not refer to an AWS::Serverless::Api resource.

But in our case, we would need to explicitly define it, as we want to access this API from the browser, and we’d want to enable Cors. The template will be as follows (it includes the roles and policies creation as well):

Lambda code

Now that our endpoint is created, we can start with updating the logic of our lambda function.

The lambda function is going to get the items from the “Companies” table where the type of the company is “PUBLIC”.

  • Under the src folder tidy up the package.json file by uninstalling Axios, mocha, and chai: npm uninstall axios mocha chai
  • In the app.js file, remove the data that’s related to hello-world created by the sam init command
  • In your terminal install the client SDK that enables the interaction with dynamo DB npm i @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb
  • Change the implementation in app.js to the following:
exports.lambdaHandler = async (event, context) => { 
const dynamodb = new DynamoDB({ region: 'us-east-1' });
const results = await dynamodb.scan({ TableName: 'companies' });
console.log('the result return is: ', JSON.stringify(results));
};
  • in this previous snippet, we’re just scanning the query DB and listing the results.

This can be tested through multiple methods, one of them is by going to the AWS console → Lambda; select the newly created lambda → Click the ‘Select test events’ dropdown, and create a new event (keep the default values as we’re not relying on the event yet) → Click the test button. Once done, navigate to the CloudWatch log associated with this lambda, and verify that the companies inserted into your ‘companies’ table are printed in the logs.

Build the functionality

In this subsection, we will build the functionality we need, it has a defect (or multiple defects), but that’s the goal — later on, this is where we would be finding one of the security issues. Update the app.js to be as follows (check the inline comments):

Deploy the code again.

We now have a running backend. After you’ve got it deployed, and created a few items in the dynamodb table, navigate to the API Gateway in AWS console and test the newly created endpoint:

Front End

Now that we have everything ready on the backend, we will build a simple React application that consumes the API we created.

Run the following command: npx create-react-app front-end

We would want to change the code in the App.js component to do an HTTP call to the endpoint. In AWS Console, navigate to API Gateway → Stage, select Prod — Notice an “Invoke URL https://nfyhswe7rg.execute-api.us-east-1.amazonaws.com/Prod/companies" at the top of the page. Click that link to request the same data you added in DynamoDB.

Our front end will have 2 components, App.js which contains the list of public companies, and CompanyDetails, which shows each company’s details.

App.js will look like this:

CompanyDetails.js will look like this:

Start your front end by running npm start, a new page will open in your browser at http://localhost:3000.

Notice the following:

  • When we load the page, we send a request that would retrieve all the public companies
  • When we click the “Show revenue” button, we send another request that returns the revenue of the company given the ID passed (of course this can be avoided by directly showing the value which we already have on the front-end, but this defeats the purpose of the blog post)

Please excuse the UI style as I did not spend much time or effort on this part.

What are the security vulnerabilities? or any other vulnerabilities? How can we solve them?

The full source code can be found at https://github.com/AHaydar/companies-data-demo

Thanks for reading.

--

--

Ali Haydar
The Startup

Software engineer (JS | REACT | Node | AWS | Test Automation)