This checklist has been updated at Web Developer Checklist V2.
Originally posted at: https://www.sensedeep.com/blog/posts/stories/web-developer-security-checklist.html
Developing secure, robust web applications in the cloud is hard, very hard. If you think it is easy, you are either a higher form of life or you have a painful awakening ahead of you.
If you have drunk the MVP cool-aid and believe that you can create a product in one month that is both valuable and secure — think twice before you launch your “proto-product”.
After you review the checklist below, acknowledge that you are skipping many of these critical security issues. At the very minimum, be honest with your potential users and let them know that you don’t have a complete product yet and are offering a prototype without full security.
This checklist is simple, and by no means complete. I’ve been developing secure web applications for over 14 years and this list contains some of the more important issues that I’ve painfully learned over this period. I hope you will consider them seriously when creating a web application.
Please comment if you have an item I can add to the list.
Database
[ ] Use encryption for data identifying users and sensitive data like access tokens, email addresses or billing details.
[ ] If your database supports low cost encryption at rest (like AWS Aurora), then enable that to secure data on disk. Make sure all backups are stored encrypted as well.
[ ] Use minimal privilege for the database access user account. Don’t use the database root account.
[ ] Store and distribute secrets using a key store designed for the purpose such as Vault or AWS Secret Manager. Don’t hard code secrets in your applications and NEVER check secrets into GitHub.
[ ] Fully prevent SQL injection by only using SQL prepared statements. For example: if using NPM, don’t use npm-mysql, use npm-mysql2 which supports prepared statements.
Development
[ ] Ensure that all components of your software are scanned for vulnerabilities for every version pushed to production. This means O/S, libraries and packages. This should be automated into the CI-CD process.
[ ] Secure development systems with equal vigilance to what you use for production systems. Build the software from secured, isolated development systems.
Authentication
[ ] Ensure all passwords are hashed using appropriate crypto such as bcrypt. Never write your own crypto and correctly initialize crypto with good random data.
[ ] Implement simple but adequate password rules that encourage users to have long, random passwords.
[ ] Use multi-factor authentication for your logins to all your service providers.
Denial of Service Protection
[ ] Make sure that DOS attacks on your APIs won’t cripple your site. At a minimum, have rate limiters on your slower API paths like login and token generation routines.
[ ] Enforce sanity limits on the size and structure of user submitted data and requests.
[ ] Use Distributed Denial of Service (DDOS) mitigation via a global caching proxy service like CloudFlare. This can be turned on if you suffer a DDOS attack and otherwise function as your DNS lookup.
Web Traffic
[ ] Use TLS for the entire site, not just login forms and responses. Never use TLS for just the login form.
[ ] Cookies must be httpOnly and secure and be scoped by path and domain.
[ ] Use CSP without allowing unsafe-* backdoors. It is a pain to configure, but worthwhile.
[ ] Use X-Frame-Option, X-XSS-Protection headers in client responses
[ ] Use HSTS responses to force TLS only access. Redirect all HTTP request to HTTPS on the server as backup.
[ ] Use CSRF tokens in all forms and use the new SameSite Cookie response header which fixes CSRF once and for all newer browsers.
APIs
[ ] Ensure that no resources are enumerable in your public APIs.
[ ] Ensure that users are fully authenticated and authorized appropriately when using your APIs.
[ ] Use canary checks in APIs to detect illegal or abnormal requests that indicate attacks.
Validation
[ ] Do client-side input validation for quick user feedback, but never trust it.
[ ] Validate every last bit of user input using white lists on the server. Never directly inject user content into responses. Never use user input in SQL statements.
Cloud Configuration
[ ] Ensure all services have minimum ports open. While security through obscurity is no protection, using non-standard ports will make it a little bit harder for attackers.
[ ] Host backend database and services on private VPCs that are not visible on any public network. Be very careful when configuring AWS security groups and peering VPCs which can inadvertently make services visible to the public.
[ ] Isolate logical services in separate VPCs and peer VPCs to provide inter-service communication.
[ ] Ensure all services only accept data from a minimal set of IP addresses.
[ ] Restrict outgoing IP and port traffic to minimize APTs and “botification”.
[ ] Always use AWS IAM users and roles and not root credentials. Invest in learning to use IAM effectively.
[ ] Use minimal access privilege for all ops and developer staff. Give IAM users and roles the minimum capabilities required to complete the task.
[ ] Regularly rotate passwords and access keys according to a schedule.
Infrastructure
[ ] Ensure you can do upgrades without downtime. Ensure you can quickly update software in a fully automated manner.
[ ] Create all infrastructure using a tool such as Terraform, and not via the cloud console. Infrastructure should be defined as “code” and be able to be recreated at the push of a button. Have zero tolerance for any resource created in the cloud by hand — Terraform can then audit your configuration.
[ ] Use centralized logging for all services. You should never need SSH to access or retrieve logs.
[ ] Don’t SSH into services except for one-off diagnosis. Using SSH regularly, typically means you have not automated an important task.
[ ] Don’t keep port 22 open on any AWS service groups on a permanent basis.
[ ] Create immutable hosts instead of long-lived servers that you patch and upgrade. (See Immutable Infrastructure Can Be More Secure).
[ ] Use an Intrusion Detection System to minimize APTs.
Operation
[ ] Power off unused services and servers. The most secure server is one that is powered down.
Test
[ ] Audit your design and implementation.
[ ] Do penetration testing — hack yourself, but also have someone other than you pen testing as well.
Finally, have a plan
[ ] Have a threat model that describes what you are defending against. It should list and prioritize the possible threats and actors.
[ ] Have a practiced security incident plan. One day, you will need it.
About SenseDeep
SenseDeep is an observability platform for AWS developers to accelerate the delivery and maintenance of serverless applications.
SenseDeep helps developers through the entire lifecycle to create observable, reliable and maintainable apps via an integrated serverless developer studio that includes design and debugging tools and deep insights into your serverless apps.
To try SenseDeep, navigate your browser to:
To learn more about SenseDeep please see:
Top comments (12)
Love this checklist! Could you explain what the security risks are with "Ensure that no resources are enumerable in your public APIs"?
If you have an API for say, accounts:
/account/ID
and your IDs start at zero. Then this API is enumerable. i.e. any user can try
/account/0
/account/1
/account/2 etc
The public ID value should be from a large space and then be converted to an internal ID.
This is important for public APIs that are accessible before authentication.
Thanks for the response. But, assuming it is a public part of your API, you should assume that anyone can get this data. I agree with you that it is easier if you can just try all numeric ID's from zero and higher. But why take this effort to use obscurity to secure public data? Why is it a security risk when users can get all entities easily in the public part of your API?
The point isn't to secure -- that must be done by other means.
The point is stop people easily determining the set of resources to attack. If the ID is a longish random string, then attackers cannot use brute force to determine the valid API paths. It becomes really tough to get the entities.
For example:
GET /account/ad1f5-bfe56-91eff-79536-d206b-664bc
that then becomes infeasible to explore the ID space.
Thank you for the explanation! I get your point now, although I have yet to come across a good use case where this would add any value. But this is just my personal perspective, and just on the one point, no point in dwelling on it. Thank you for for sharing the checklist, I will definitely use it to benefit my work!
What if your attacker was authenticated to your site? With sequential id's they could possibly access data that was not theirs. Like viewing someone else's profile because they can guess the ID? Sequential IDs open up a host of authorization issues.
Agree, the same rule really applies if authenticated. If an attacker is authenticated, then they have access to their account, but you still don't want them to be able to enumerate other accounts, users etc.
Another thing: never use "===" to check auth tokens -- use a time-secure comparison like npmjs.com/package/secure-compare
EDIT: apparently it's in core now: crypto.timingSafeEqual(a, b)
That is cool. Thank you, I was not aware of that API.
You can also use XOR to compare
Thank you for the post - really useful. Can you recommend practical intro books on the topic? I've been focusing mostly in learning about design/architecture/etc but not that much about security and I believe if I want to level up in the industry I have to understand more about it too.
Sorry, I don't have a good set of books. I'm sure they exist, but I just don't have a curated list to give you.