
People have been asking about resources to get started on security as a developer. I’ve decided to start a “security for developers” series. Here are some initial topics I’ve talked about:
This topic of security engineering has gotten a lot of attention in the past few months. The Pragmatic Engineer recently released a two-part series on security engineering: Part 1 and Part 2. They cover a large range of topics and provide a short overview of each. It’s different than the posts I’m creating. My goal is to dive deeper into topics for developers so that they can practically navigate security issues they might face on a daily basis. This way, they can maintain a strong security posture until they are able to hire a dedicated security person.
Today, I’m going to discuss some topics in web security. A few good resources to understand web security threats are OWASP Top 10 and the OWASP Cheat Series. The goal of these resources is to show you the top threats and some basic security configurations and tips to mitigate risk.
In this newsletter, I plan to talk through some common threats and defenses for the following types of common web security attacks:
Distributed Denial of Service (DDoS)
SQL Injection
Credential Stuffing
Cross-site scripting (XSS)
Distributed Denial of Service (DDoS)
This is a common problem that many public-facing websites will face. In a DDoS attack, an attacker floods a website with traffic so that others can no longer access the site. A basic DoS can also happen when a website just gets a lot of traffic as a result of a launch. Either way, we can apply the same defenses to rectify this issue.
Nowadays, companies are better prepared for these types of attacks by pre-emptively having defenses because these defenses have other benefits, such as reducing the overall load on a website by filtering out bad traffic. However, in the past, many companies only put up these defenses once they experienced an attack.
So what do you do if this happens, or how do you prevent this? In the past, infrastructure teams have gone through a variety of techniques to mitigate these attacks. They have done IP-based blocking, rate-limiting, doing challenges, etc. However, nowadays, solving this problem is pretty simple. The solution is a web application firewall (WAF). A WAF is a relatively inexpensive way to block traffic that might take down your website, so there’s no need to come up with techniques to mitigate DDoS attacks.
Compared to the past, these WAFs perform much better than what most teams can implement since they have intelligence from other customers. They have also come down in cost substantially. This is because the vendors have an infrastructure platform where they can provide other products. In addition, they are cheaper compared to the labor costs of having an infrastructure engineer set them up and maintain them. All the cloud providers have their own. For example, AWS has one, and it’s pretty easy to set up with a load balance. As your environments become more sophisticated, you might have certain parts of your site hosted on a third-party service, such as Auth0 or a separately hosted marketing site, having a load balance with the AWS WAF might seem tedious, so you could look into using Cloudflare or Fastly, especially if you are considering using their other services like DNS and CDN.
SQL Injection
A SQL injection attack happens when an application generates and executes a dynamic query that takes in user input. Usually, this occurs because the application creates a SQL query through string concatenation with user input. Here’s an example of vulnerable code:

There are a few types of defenses. The best defense is to use prepared statements with parameterized queries. Most of these are built-in features and libraries of programming languages. A prepared statement is also “cleaner” because they are more understandable. The parameterized query allows for the construction of the SQL statement structure and passes in the parameters later.
Applications should avoid using stored procedures as they are susceptible to SQL injection. However, sometimes, it’s necessary to use stored procedures. To do this, one should use bind variables. Bind variables tell the database that the input is data, not code. Here are some examples.
Finally, sometimes, the code might contain the dangerous string concatenation code described above. It might be tempting to mitigate this through input sanitization, but this is very tough to get right, and the amount of effort is likely commensurate to changing the code to use prepared statements and parameterized statements, which is a better coding standard overall.
Credential Stuffing
Credential stuffing is the testing of usernames and passwords obtained from a breach on another site. There are other related attacks, such as brute-forcing with common passwords and password spraying with a weak password. The defenses to all these attacks are the same because the underlying security issue is the same — the user’s password is weak.
The best defense against password-based attacks like credential stuffing is to have multi-factor authentication (MFA). MFA doesn’t substantially change the login experience, but it can effectively prevent a large majority of account compromises. Of course, some MFA methods are more effective than others. For example, SMS-based MFA is susceptible to SIM-swapping attacks that are becoming increasingly easy. Similarly, push-based MFA notifications are susceptible to user spamming attacks and phishing. The safest way is to use WebAuthn-based MFA, which isn’t susceptible to the same issues as push-based MFA. The complication is that they do require the use of a physical key, such as a Yubikey. In the past, the distribution and management of these keys were difficult, but now, with Apple laptops, a user can use TouchID.
Sometimes, people get caught up in a debate about what type of MFA to use. It’s important to mention that using a weaker form of MFA is better than using no MFA at all.
There are other ways to mitigate credential stuffing by detecting these types of attacks and limiting them. WAFs, discussed above, are relatively good nowadays at detecting and blocking potential credential-stuffing attacks. If you use an external auth service, such as Auth0, they also provide some basic defenses against credential stuffing. As always, if possible, you should use multiple techniques to provide defense in depth.
Cross-site scripting (XSS)
XSS, despite its name, is the injection of any content into a website. It results in serious security issues, such as account impersonation, stolen data, etc. This is a serious issue, but at the same time, the reason it’s at the end of the list is that there’s no single technique that will solve XSS. Thankfully, the use of web frameworks has resulted in fewer XSS bugs as they provide developers with coding best practices that avoid common patterns that lead to these types of bugs. Despite this, bugs still can occur when insecurely using certain functions, such as dangerouslySetInnerHTML
, which is a common one.
Here are some techniques to limit or mitigate the impact of an XSS bug. An XSS bug is commonly exploited through parts of a page that allow for user input.
One technique is to use output encoding. The concept of output encoding is to ensure data is displayed as the user types and not interpreted as code. As a result, there won’t be execution. Most frameworks have built-in encoding libraries, but if you aren’t using a framework or need to fill a gap, you should use an output encoding library. You can easily find them by Googling for a specific language or framework and “output encoding.” There are a variety of contexts where you might need to use output encoding. I won’t go through all of them, and you can find more examples in the OWASP Cheat Sheet.
Here’s an example of output encoding needed for HTML contexts. Consider the following code:
<div> $unsafeVar </div>
Someone can input a script as an argument to the var, such as the following:
<div> <script> alert(‘cookie stolen’) </script> </div>
Without proper output encoding, the browser will interpret this HTML context as a script rather than just data, which was the original intent.
Another common technique is to do HTML sanitization. Using a library such as DOMPurify can strip out dangerous HTML code.
Typically, XSS bugs target cookies. Some ways to protect those are by setting cookie attributes to limit the impact of a stolen cookie.
Content Security Policies (CSP) are another strong defense against XSS. They limit the types of content that can be loaded. They are tricky to implement, but doing some basics can help mitigate the effect of the XSS bug. CSP can prevent defenses, such as restricting inline scripts, restricting bad Javascript, restricting remote scripts, etc. CSP is usually sent in an HTTP header, and it should be done in all HTTP responses to achieve maximum effect. Here’s the OWASP cheat sheet that can help you get started.
Conclusion
Web security issues come in many forms. In this post, I just described a few common attacks and some practical ways to mitigate them. This isn’t meant to be the ultimate guide, but it’s meant for developers to create some common defenses without needing help from a dedicated security person (or until a security person arrives!).