OWASP Top 10 2007-2017: The Fall of CSRF
The final version of OWASP Top 10 2017 was recently released and it has changed significantly from the 2013 version. Within various release candidates of the 2017 list, there were significant changes as well. While the significant issues with the RC1 release were well-documented and analyzed, RC2 and the final Top 10 have been a course correction for the project and a net-positive overall. Of the major changes in the final version, one of the biggest debates has centered around the removal of Cross-Site Request Forgery (CSRF) and additions of Insecure Deserialization and XML External Entities (XXE). This post focuses on analyzing why we believe it made sense to demote CSRF and focus on lesser “fixed” areas in our code and frameworks.
The OWASP Top 10 2013 list included additional items that were either removed or consolidated in the 2017 version:
The OWASP Top 10 2017 introduced several new categories as well as removed familar friends such as CSRF and Unvalidated Redirects and forwards:
Once Upon a Time, in Software
The year was 2007. The New York Mets still had not won a World Series since 1986. The world was first learning about the iPhone (but still no App Store until 2008) and we were a year away from Android for the masses. We built software differently and we still threw around terms like Web 2.0. Coincidentally, it was also the year that CSRF was introduced into the OWASP Top 10 2007 as the fifth most serious issue in web application security. The 2007 OWASP Top 10 brought visibility to CSRF, and as a whole, this drove development teams to fix CSRF and led framework teams to offer built-in tools to mitigate against common CSRF attacks. Compared to 2017, 2007 was different in a lot of ways:
- We drove bigger cars.
- We performed server-side rendering for our web templates and relied on APIs less.
- Our frameworks did almost nothing intentional to defend against CSRF, but occasionally defended against it because of dumb luck or fortunate accident.
When the 2010 Top 10 was released, CSRF held steady at #5. When the 2013 version was released, CSRF was dropped down to #8. Based on the data analyzed during the 2017 Top 10 and combined with better framework protections, CSRF has now fallen out of the Top 10 entirely. The data and analysis methods for the Top 10 were made fully public for transparency and peer review.
What’s the Point?
Top 10 lists and the debate and passion they inspire often lead to existential questions for security practitioners. Why are we here and why did I get out of bed today? Do we want our developers to recite the Top 10 when they wake up in the morning or do we want to systemically reduce risks across the software supply chain and across industry verticals? Do we want some kind of cool infographics to tape to our cubicles to never look at ever again? Do we want to have a list of 10 things we can continue to shame software teams about and never fix? I hope not.
We can’t travel back in time to build our systems securely, but we can retroactively bolt security onto our architecture. We can either build security controls from scratch or we can leverage a good implementation through a library or framework protection. Similarly, we can impact newly developed systems by avoiding insecurities out of the gate and eradicating complete classes of bugs through secure design.
With regard to CSRF protections, we have a lot of really good options that do a reasonable job at defending against common CSRF attacks, but that cannot defend against 100% of the scenarios encountered in the real world. We’ve been largely successful at reducing CSRF head on for over a decade. The end result has been improved browser standards for attack mitigation (SameSite cookies), framework features (secure-by-default configurations, common Single-Page Application patterns), and raised awareness for secure development overall. Software is complex and nothing can realistically be considered one-size-fits-all. But getting back to our goals, it’s to raise the bar for the next generation of software, right? We’ve impacted issues at the root level for CSRF, which is at the framework and through browser standards. No, we haven’t solved CSRF and we’re not going to anytime soon. But we’ve made noticeable strides and we’ve raised the bar. Isn’t that the point? It’s time to move on and do that for another class of issues.
While we’ve also gotten better over the past decade at defending against Injection attacks, their potential impact as well as the likelihood of human error (and vulnerable copy-pasted code from Stack Overflow) keeps them rated high year after year. There are quite a few flavors of injection, ranging from SQL to NoSQL, command, LDAP, XML, SMTP, and other types of injection. We are further from fixing Injection than we are from fixing CSRF. Unvalidated Redirects and Forwards were also removed in the 2017 version and has received far less attention and scrutiny, and rightfully so.
What Have We Done?
In 2007, if a “pentester” told you that your Classic ASP application was vulnerable to CSRF, you probably spent the next three months writing spaghetti code to mitigate against this everywhere. In 2017, you have options within the frameworks you use as well as within various gateways, proxies, and middleware solutions. In our experience at nVisium, there is a direct correlation between the technology stack, when the application was developed, and the incidence rate for CSRF. With newer development, we have seen a significant decrease in applications vulnerable to CSRF.
The evolution of framework-level CSRF protections went like this:
- 2003-2007 - Minimal movement in anti-CSRF defenses.
- 2007-2012 - Broad adoption of anti-CSRF defenses.
- 2012-2017 - More frameworks offering secure-by-default settings and some form of protections, SameSite cookie attributes introduced in 2016 and into browsers in 2017.
To illustrate the point, the following chart compares default CSRF protections in popular web frameworks that were in use back in 2007 to their defaults in 2017. The chart also shows new frameworks introduced after CSRF became a fixture in the Top 10. Please note that each implementation varies drastically and may have unique strengths and weaknesses beyond what this chart shows. The chart focuses on the following attributes:
- When the framework was introduced.
- When the framework introduced CSRF protection.
- When the framework turned the CSRF protection on by default.
Looking back in time, the first public security conference talks on CSRF began to pop up in the 2003-2004 timeframe. CVEs for CSRF issues in public software and products began to increase steadily as of 2004. However, it is clear that framework protections for CSRF didn’t advance much between 2004-2007.
When CSRF was added to the OWASP Top 10 in 2007, the same frameworks began to include additional enhancements more steadily over the next five years through 2012. In fact, if we examine our frameworks again in 2012, we see a noticeable trend that many more of the most popular and prevalent frameworks already had common protections in place. By 2013, CSRF fell in the OWASP Top 10 from #5 to #8.
In 2007, we built web applications that did heavy server-side rendering and we were just getting started with the mobile era. A decade later, we’re using more APIs than ever and we’re not always rendering HTML templates. Many mobile and IoT interfaces use native UI components or have no UI, making CSRF less of a risk in those applications. With the steady increase in Single Page Applications (SPA) written with client-side frameworks, such as React or Angular, combined with backend frameworks offering some level of CSRF protections by default, we should see a further reduction in the amount of CSRF being introduced into the software supply chain over the next 5-10 years.
What Does the Data Say?
We’ve spent a decade fighting the good fight with CSRF. Most developers have at least heard the term, even if they don’t fully understand it. Frameworks have adopted controls and many different libraries and implementations have popped up to defend against it. Many security assessment and bug bounty reports have been delivered to development teams for CSRF.
The OWASP Top 10 data set itself is certainly not 100% perfect. While the team appeared to carefully analyze the inbound data with additional dimensions than in previous incarnations, it’s not realistic to assume that the data is perfect or representative of all applications.
The incidence rate for CSRF was 5.27%, ranking in eighth place out of all other risk areas.
|Security Misconfiguration (CWE-2,16)||24.83%|
|Cross-Site Scripting (XSS) (CWE-79)||24.81%|
|Information Leakage/Disclosure (CWE-200)||18.36%|
|SQL Injection (CWE-89)||10.38%|
|Unchecked Redirect (CWE-601)||8.87%|
|XML eXternal Entity Injection (XXE) (CWE-611)||8.41%|
|Cross-Site Request Forgery (CSRF) (CWE-352)||5.27%|
|Session Fixation (CWE-384)||5.05%|
|Cleartext Transmission of Sensitive Information (CWE-319)||5.03%|
|Clickjacking Vulnerabilities Found (No CWE)||4.06%|
|Error Handling (CWE-388)||3.94%|
|Cleartext Storage of Sensitive Information (CWE-312)||3.71%|
|Command Injection (CWE-77,78)||3.74%|
|Missing Authorization (CWE-285)||2.30%|
|Denial of Service (DOS) (CWE-400)||1.78%|
|Path Traversal (CWE-22)||1.74%|
|Insecure Direct Object Reference (CWE-639)||1.64%|
|Improper (Function Level) Access Control (CWE-285)||1.43%|
|Use of Known Vuln Libraries||1.41%|
|Mass Assignment (CWE-915)||1.14%|
|Insufficient Anti-automation (CWE-799)||0.98%|
|Unvalidated Forward (No CWE)||0.75%|
|Hibernate Injection (CWE-564)||0.63%|
|DOM-Based XSS (No CWE)||0.71%|
|Insufficient Security Logging (CWE-778)||0.39%|
|Unrestricted Upload of File with Dangerous Type (CWE-434)||0.45%|
|Expression Language Injection (CWE-917)||0.04%|
|Insufficient Intrusion Detection and Response (No CWE)||0.02%|
|Server-Side Request Forgery (SSRF) (CWE-918)||0.07%|
Your Resistance is not Futile
A decade ago, lack of CSRF defenses meant it was the wild wild west for this vulnerability class. It was everywhere and frustratingly hard to get rid of in legacy applications written with Classic ASP, Java Servlets, or other technologies that we had to wire our CSRF defenses into. CSRF continued to pop up year after year for many organizations as they tested their applications.
How many high-profile attacks can you name off of the top of your head that have been the result of CSRF?
It’s natural to get attached to the classes of bugs we’re familiar with and have been successful at finding everywhere, but are the critics of dropping CSRF the victims of their own biases?
Are you looking at a broad set of tech stacks or do you have a fairly standard tech stack you typically test? Have you had a really good design smack you in the face and prove that secure-by-default = you have one less thing to include in your “pentest” report?
- Are you working with clients on a continuous basis or do you see the untested apps for the first * time and then never again?
- How recent is the data that you’ve used to form your opinions? Are you biased for any of the reasons above, which are lack of diversity across tech stacks or limited visibility across a system’s lifecycle?
While CSRF is clearly dangerous and has been used maliciously with success in the wild, the reality is that the attack surface for CSRF varies with mileage depending on your threat profile as an organization. If you have the user base of Facebook or Twitter, then CSRF attacks are a real threat. If you are a major financial organization, CSRF attacks are a real threat. However, each of those organizations has robust and mature defenses against CSRF more often than not. While we’re not 100% of the way there yet, we’ve made a significant dent in the pile.
If you are a five-person startup with a user base of under 50,000 using a new framework with built-in CSRF protections and you’re using Angular on the client with header-based tokens and SameSite cookies (assuming more browsers support it sooner than later), then I’d argue you have more important things to worry about than CSRF. You’re using cool newer toys like Apache Spark that use serialization technologies, such as Kryo, with insecure-by-default settings. With the weak serialization settings used, in conjunction with an instance of Insecure Deserialization, this lack of attack mitigation increases the impact of Remote Code Execution (RCE), and you probably have no clue your tools, such as Spark, have already made this decision for you by default:
private val registrationRequired = conf.getBoolean(“spark.kryo.registrationRequired”, false) kryo.setRegistrationRequired(registrationRequired)
While we can all agree to disagree on the Top 10 risks chosen this year, the reality is we have other less mature areas to fix. We have a long way to go to get serialization frameworks and the tools using them to ship with security in mind. We’ve also shipped a lot of insecure software over the past decade that will break with major upstream changes. Kryo has been working on implementing a secure-by-default approach to class whitelisting for over a year. This is not due to a lack of care or competence, but more of the reality that software engineering is complex and there are consequences to making a major breaking change to an upstream provider. For an example, take a look at the GitHub issue for requiring type registration in Kryo by default. And note the following comment so that you understand why this isn’t as easy as “flipping on a setting”:
My only concern is: How many existing clients would be broken by this change and would need to update their code either by adding setRegistrationRequired(false) or by registering classes? It would be nice to hear from people who would get affected by such a change.
The Times They Are a-Changin’
Some of us have now been hacking on software for a decade, two decades, or more. The bugs we held close to our hearts a decade ago are slowly going away and changing in shape. This is a positive thing and a reflection that we’re continuing to make progress overall. As the use cases for our software evolve as well as the platforms, technologies, and interfaces, so will the risks and attack surface. Does this mean we’ve “solved” the problem? No, that’s naive. We’ve known how to fix buffer overflows, SQL Injection, and XSS for years, yet we haven’t. At the same time, frameworks and applications have built in protections to mitigate against a variety of common attacks or increase the difficulty of exploitation. We are better off because of these things. At the end of the day, we can argue about our scanners and cool hacker techniques all we want, but if we’re not fixing our software, who cares?