├── images
└── paraxial_logo.png
├── LICENSE
└── README.md
/images/paraxial_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paraxialio/sobelow_guide/HEAD/images/paraxial_logo.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Paraxial.io
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sobelow Findings Guide
2 |
3 | When [Sobelow](https://github.com/nccgroup/sobelow) reports a finding for an Elixir application, there are three key questions the developer should ask:
4 |
5 | 1. Is this finding a true positive or false positive?
6 |
7 | 2. What is the security impact of the vulnerability?
8 |
9 | 3. What is the correct method to fix the vulnerability?
10 |
11 | Sobelow is a useful tool, able to analyze the details of a code base much faster than a human. However, it only has access to source code. Sobelow does not understand how the application is being used, and the true security impact of each reported finding. The expertise of a human is required to use Sobelow effectively.
12 |
13 | This document provides guidelines for evaluating each possible Sobelow finding. The severity ranking for each finding is subject to debate, and ultimately up to the judgement of the application owner. Guidelines on severity are still useful, for example consider a banking application where Sobelow reports two findings:
14 |
15 | 1. `Misc.BinToTerm`, Severity: High
16 | 2. `DOS.StringToAtom`, Severity: Medium
17 |
18 | Both these findings are true positives. The `Misc.BinToTerm` finding means the application may be vulnerable to remote code execution, where an attacker could gain the equivalent of production SSH access to the web server. From this foothold, the attacker can issue financial transactions, modify customer records, and further compromise the bank's network. Contrast this finding to the `DOS.StringToAtom` finding. An attacker can use this vulnerability to crash the banking server. This is not ideal, and is a security issue, however the attacker does not get read/write/execute access to the server, so the severity is lower.
19 |
20 | ---
21 |
22 |
23 |
24 | This guide is sponsored by [Paraxial.io](https://paraxial.io/), an application security platform for Elixir and Phoenix. Paraxial.io Application Secure manages Sobelow scanning to ensure automated compliance. Enterprise customers can request expert help in fixing vulnerabilities reported by Sobelow.
25 |
26 | This document is not considered part of the [Sobelow](https://github.com/nccgroup/sobelow) official documentation.
27 |
28 | ---
29 |
30 | ## Severity Ratings
31 |
32 | There are three possible severity ratings for each finding:
33 |
34 | 1. High
35 |
36 | 2. Medium
37 |
38 | 3. Low
39 |
40 | Note that these ratings are meant as a guideline, and the true impact of each vulnerability is dependent on how the application is being used. For example, a cross site scripting (XSS) issue, where the payload can only be viewed by the user who submitted it, would be classified as low. A XSS issues in a social media website would be high, because many people can view the payload, and it can lead to a worm attack, where each person who views the payload spreads it to their friends. See the [MySpace Samy worm](https://en.wikipedia.org/wiki/Samy_(computer_worm)) for a real world example.
41 |
42 | Given the above context, XSS is classified as high severity in these guidelines.
43 |
44 |
45 | ## UID 1, CI.OS: Command Injection in `:os.cmd`
46 |
47 | Command Injection vulnerabilities are a result of passing untrusted input to an operating system shell, and may result in complete system compromise.
48 |
49 | ### Severity
50 |
51 | This is a high severity finding. An attacker can exploit this vulnerability to take over your entire web server, stealing your database, and causing a major data breach incident.
52 |
53 | ### How to verify this finding
54 |
55 | The danger of command injection is that an attacker can send a malicious string which is passed to a call to `:os.cmd`. This function requires a charlist, for example:
56 |
57 | ```
58 | iex(11)> :os.cmd(user_input)
59 | 'CHANGELOG.md\nLICENSE\nREADME.md\nlib\nmix.exs\nmix.lock\ntest\n'
60 | ```
61 |
62 | Follow this checklist to determine if user input can reach this function:
63 |
64 | 1. Start at the call to `:os.cmd(user_input)` in your code base. Can you determine the source of this variable?
65 |
66 | 2. If it is hard-coded in the source code, user input does not change the variable, so the finding is a false-positive.
67 |
68 | 3. If the variable comes from user input, for example a GET or POST request, the function is vulnerable.
69 |
70 | 4. If the variable comes from a database, or other data store, you need to determine if it set by a user.
71 |
72 | If you are able to provide some input to your application, which changes the variable passed to `:os.cmd`, you have verified this finding as a true positive.
73 |
74 | ### How to fix a true positive
75 |
76 | 1. Consider removing the call to `:os.cmd`. Can you accomplish the same task without using this dangerous function?
77 |
78 | 2. If you must use `:os.cmd`, do not pass arbitrary user input to this function. Each input to this function should be pre-defined, not created dynamically with user input.
79 |
80 |
81 | ## UID 2, CI.System: Command Injection in `System.cmd`
82 |
83 | Command Injection vulnerabilities are a result of passing untrusted input to an operating system shell, and may result in complete system compromise.
84 |
85 | ### Severity
86 |
87 | This is a high severity finding. An attacker can exploit this vulnerability to take over your entire web server, stealing your database, and causing a major data breach incident.
88 |
89 | ### How to verify this finding
90 |
91 | The danger of command injection is that an attacker can send a malicious string which is passed to a call to `System.cmd`. For example:
92 |
93 | ```
94 | iex(16)> System.cmd(user_input, [])
95 | {"CHANGELOG.md\nLICENSE\nREADME.md\nlib\nmix.exs\nmix.lock\ntest\n", 0}
96 | ```
97 |
98 | Follow this checklist to determine if user input can reach this function:
99 |
100 | 1. Start at the call to `System.cmd` in your code base. Can you determine the source of the variable passed to this function?
101 |
102 | 2. If it is hard-coded in the source code, user input does not change the variable, so the finding is a false-positive.
103 |
104 | 3. If the variable comes from user input, for example a GET or POST request, the function is vulnerable.
105 |
106 | 4. If the variable comes from a database, or other data store, you need to determine if it set by a user.
107 |
108 | If you are able to provide some input to your application, which changes the variable passed to `System.cmd`, you have verified this finding as a true positive.
109 |
110 | ### How to fix a true positive
111 |
112 | 1. Consider removing the call to `System.cmd`. Can you accomplish the same task without using this dangerous function?
113 |
114 | 2. If you must use `System.cmd`, do not pass arbitrary user input to this function. Each input to this function should be pre-defined, not created dynamically with user input.
115 |
116 |
117 | ## UID 3, Config.CSP: Missing Content-Security-Policy
118 |
119 | Content-Security-Policy is an HTTP header that helps mitigate a number of attacks, including Cross-Site Scripting.
120 |
121 | ### Severity
122 |
123 | This is a low severity finding. Missing CSP is not a vulnerability, it is a layer of defense to stop XSS and data injection attacks. An attacker must exploit a XSS vulnerability that already exists for CSP to be relevant.
124 |
125 | ### How to verify this finding
126 |
127 | Check the HTTP response from your web server for the `content-security-policy` header.
128 |
129 | ### How to fix a true positive
130 |
131 | Use `plug :put_secure_browser_headers` in your pipeline. Documentation on the `put_secure_browser_headers` plug functioncan be found here: https://hexdocs.pm/phoenix/Phoenix.Controller.html#put_secure_browser_headers/2
132 |
133 | Example policy:
134 |
135 | `plug :put_secure_browser_headers, %{"content-security-policy" => "default-src 'self'"}`
136 |
137 | *Warning: Note that adding a restrictive CSP header will improve security, but may break your application's JavaScript. Read https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP for more details.*
138 |
139 |
140 | ## UID 4, Config.CSRFRoute: CSRF via Action Reuse
141 |
142 | In a Cross-Site Request Forgery (CSRF) attack, an untrusted application can cause a user's browser to submit requests or perform actions on the user's behalf.
143 |
144 | ### Severity
145 |
146 | This is a medium severity finding. An attacker can create a malicious web page, and when a victim visits the web page, the attacker can force the victim's browser to perform an action in the web application. The attacker cannot leak data from the victim through CSRF, it is a write-only attack. For example, a banking application vulnerable to CSRF where an attacker can force the victim to make a POST request, transferring money to the attacker.
147 |
148 | ### How to verify this finding
149 |
150 | In your Phoenix router, there should be two routes that use the same action, for example:
151 |
152 | ```
153 | get "/users/settings/edit_bio", UserSettingsController, :edit_bio
154 | post "/users/settings/edit_bio", UserSettingsController, :edit_bio
155 | ```
156 |
157 | Note that both the GET and POST request are sent to the same function, `:edit_bio`. A user can update their bio with a POST request, where the POST body contains `user[bio]=This+is+some+info+about+my+profile+page`. This is safe, state changing actions should use POST, because they can be protected from CSRF. This same state changing action can also be triggered via a GET request, which is unsafe, because GET requests are always vulnerable to CSRF. For example, if the victim visits the route:
158 |
159 | http://potionshop.url/users/settings/edit_bio?user%5Bbio%5D=Hacked+LOL
160 |
161 | Their bio will be updated.
162 |
163 | The POST request to `/users/settings/edit_bio` is not vulnerable to CSRF. Rather, it's the GET request to `/users/settings/edit_bio`, which contains the same parameters as the POST request in the URL, which is the source of this vulnerability. If you can issue a GET request that triggers the same functionality as the POST request, this finding is a true positive.
164 |
165 | Additional details - https://paraxial.io/blog/action-reuse-csrf
166 |
167 | ### How to fix a true positive
168 |
169 | 1. Create different controller actions for each route.
170 |
171 | 2. In the GET request route, do not allow state changing actions for authenticated user associated with a POST request. Examples of state changing actions are transferring money in a banking application, adding an admin user to a business management portal, or creating a new post on a social media website.
172 |
173 | ## UID 5, Config.CSRF: Missing CSRF Protections
174 |
175 | In a Cross-Site Request Forgery (CSRF) attack, an untrusted application can cause a user's browser to submit requests or perform actions on the user's behalf.
176 |
177 | ### Severity
178 |
179 | This is a medium severity finding. An attacker can create a malicious web page, and when a victim visits the web page, the attacker can force the victim's browser to perform an action in the web application.
180 |
181 | ### How to verify this finding
182 |
183 | The root cause of this finding is a pipeline in your application's router file that fetches a session, but does not implement the `:protect_from_forgery` plug.
184 |
185 | Locate an HTML form, which makes a POST request, matching this pipeline. For example:
186 |
187 | ```
188 |
201 | ```
202 |
203 | This form is vulnerable to CSRF because there is no CSRF token. Note that even if there is a CSRF token in the form, such as:
204 |
205 | ```
206 |
207 | ```
208 |
209 | The form may still be vulnerable if the plug `:protect_from_forgery` is not present.
210 |
211 | To verify if the vulnerability is present, create a new file, `poc.html`, and enter the following:
212 |
213 | ```
214 |
224 | ```
225 |
226 | Ensure you are logged into the vulnerable application with a valid session, then open `poc.html` in the same web browser. If submitting this form performs the action in your account, in this case creating a post, the vulnerability exists.
227 |
228 |
229 | ### How to fix a true positive
230 |
231 | 1. Ensure you are using the `:protect_from_forgery` plug in pipelines that fetch a session. Even if the HTML form has a CSRF token, the vulnerability still exists if the backend application is not checking if the token is valid. `:protect_from_forgery` performs the check.
232 |
233 | 2. The HTML form should be created with a Phoenix helper, such as `form_for`, because it automatically includes the CSRF token - https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#form_for/4 If the matching form does not have a CSRF token, the vulnerability is not fixed.
234 |
235 |
236 | ## UID 6, Config.CSWH: Cross-Site Websocket Hijacking
237 |
238 | Websocket connections are not bound by the same-origin policy. Connections that do not validate the origin may leak information to an attacker.
239 |
240 | ### Severity
241 |
242 | This is a medium severity vulnerability. Exploiting CSWH requires an attacker to setup a malicious website, then get the victim to browse to the site, while also logged into their current session. CSWH does allow the attacker to take over the victim's account, the requirement for user interaction reduces the severity.
243 |
244 | Details on CSWH - https://christian-schneider.net/CrossSiteWebSocketHijacking.html
245 |
246 | ### How to verify this finding
247 |
248 | Example of a bad endpoint:
249 |
250 | ```elixir
251 | defmodule PhoenixWeb.Endpoint do
252 | use Phoenix.Endpoint, otp_app: :phoenix
253 |
254 | socket("/socket", PhoenixInternalsWeb.UserSocket,
255 | websocket: [check_origin: false],
256 | longpoll: false
257 | )
258 | end
259 | ```
260 |
261 | Example of a good endpoint:
262 |
263 | ```elixir
264 | defmodule PhoenixWeb.Endpoint do
265 | use Phoenix.Endpoint, otp_app: :phoenix
266 |
267 | socket("/socket", PhoenixInternalsWeb.UserSocket,
268 | websocket: true,
269 | longpoll: false
270 | )
271 | end
272 | ```
273 |
274 | ### How to fix a true positive
275 |
276 | Ensure `:check_origin` is enabled. It defaults to true.
277 |
278 | Phoenix Docs - https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#socket/3
279 |
280 |
281 | ## UID 7, Config.Headers: Missing Secure Browser Headers
282 |
283 | By default, Phoenix HTTP responses contain a number of secure HTTP headers that attempt to mitigate XSS, click-jacking, and content-sniffing attacks.
284 |
285 | Missing Secure HTTP Headers is flagged by `sobelow` when a pipeline accepts "html" requests, but does not implement the `:put_secure_browser_headers` plug.
286 |
287 | ### Severity
288 |
289 | This is a low severity finding. Missing secure browser headers is not a vulnerability. Having these headers is considered a best practice.
290 |
291 | ### How to verify this finding
292 |
293 | Most Phoenix applications set these headers by default. This finding is triggered when a router pipeline accepts `html` requests, but does not implement the `:put_secure_browser_headers` plug.
294 |
295 | Check the response of a request in this pipeline for the headers:
296 |
297 | ```
298 | referrer-policy
299 | x-frame-options
300 | x-content-type-options
301 | x-download-options
302 | x-permitted-cross-domain-policies
303 | ```
304 |
305 | https://hexdocs.pm/phoenix/Phoenix.Controller.html#put_secure_browser_headers/2
306 |
307 | ### How to fix a true positive
308 |
309 | Add the plug `:put_secure_browser_headers` to the pipeline.
310 |
311 |
312 | ## UID 8, Config.HSTS: HSTS Not Enabled
313 |
314 | The HTTP Strict Transport Security (HSTS) header helps defend against man-in-the-middle attacks by preventing unencrypted connections.
315 |
316 | ### Severity
317 |
318 | This is a low severity finding. The HSTS header is used to defend against man-in-the-middle attacks by preventing unencrypted connections.
319 |
320 | ### How to verify this finding
321 |
322 | Check your config file for:
323 |
324 | ```elixir
325 | config :havana, HavanaWeb.Endpoint,
326 | force_ssl: [hsts: true]
327 | ```
328 |
329 | Remember that Sobelow is limited to your Phoenix application code. Your deployed application may be using HSTS correctly, due to a server level configuration. Use https://www.ssllabs.com/ssltest/ to verify your deployed settings.
330 |
331 | https://hexdocs.pm/phoenix/using_ssl.html#hsts
332 |
333 | https://hexdocs.pm/plug/Plug.SSL.html
334 |
335 | ### How to fix a true positive
336 |
337 | Set the following config:
338 |
339 | ```
340 | config :havana, HavanaWeb.Endpoint,
341 | force_ssl: [hsts: true]
342 | ```
343 |
344 | Replace "Havana" with your application name. Use https://www.ssllabs.com/ssltest/ to verify your deployed settings.
345 |
346 |
347 | ## UID 9, Config.HTTPS: HTTPS Not Enabled
348 |
349 | Without HTTPS, attackers in a privileged network position can intercept and modify traffic. Sobelow detects missing HTTPS by checking the prod configuration.
350 |
351 | ### Severity
352 |
353 | This is a high severity finding. Using HTTPS is a requirement if your application handles user data.
354 |
355 | ### How to verify this finding
356 |
357 | This finding is often a false positive, because HTTPS configuration may be set at a different layer in the application stack. For example, your web server may be configured to force HTTPS.
358 |
359 | Test if your application serves traffic from `http://` and `https://`. If you are able to send data to your server over `http://`, this is a true positive.
360 |
361 | ### How to fix a true positive
362 |
363 | Configure your Phoenix application to use HTTPS - https://hexdocs.pm/phoenix/using_ssl.html#content
364 |
365 |
366 | ## UID 10, Config.Secrets: Hardcoded Secret
367 |
368 | ### Severity
369 |
370 | This is a medium severity finding. Hard coding secrets in source code is not recommended.
371 |
372 | ### How to verify this finding
373 |
374 | Sobelow checks for configuration variables such as `secret_key_base`, `password`, and `secret` with a matching string. Read the finding, and determine if the value stored in source code is a true secret.
375 |
376 | ### How to fix a true positive
377 |
378 | The best practice for secrets is to store them as environment variables.
379 |
380 |
381 |
382 | ## UID 11, DOS.BinToAtom: Unsafe atom interpolation
383 |
384 | In Elixir, atoms are not garbage collected. As such, if user input is used to create atoms (as in `:"foo\#{bar}"`, or in `:erlang.binary_to_atom`), it may result in memory exhaustion. Prefer the `String.to_existing_atom` function for untrusted user input.
385 |
386 | ### Severity
387 |
388 | This is a medium severity finding. It does not allow the attacker to access private data, or performed unauthorized actions. Atom DoS allows an attacker to trigger a crash of the Erlang virtual machine. There are two possible outcomes:
389 |
390 | 1. The application crashes and restarts. There will be some downtime during the restart, but overall the impact will be low.
391 |
392 | 2. The application crashes and remains down. This is a higher severity incident.
393 |
394 | The behavior of your application depends on the deployment environment. Instructions for testing your environment - https://paraxial.io/blog/atom-dos-impact
395 |
396 | ### How to verify this finding
397 |
398 | Find the line of code where the atom is being created. Is the atom created from user input? For example:
399 |
400 | `:new_atom_#{a}`
401 |
402 | Can the variable `a` be set through user input? If it can be, this is a true positive.
403 |
404 | ### How to fix a true positive
405 |
406 | Do not create new atoms at runtime. Restructure your code so that atoms do not need to be created from user input.
407 |
408 | https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/atom_exhaustion
409 |
410 |
411 | ## UID 12, DOS.ListToAtom: Unsafe `List.to_atom`
412 |
413 | In Elixir, atoms are not garbage collected. As such, if user input is passed to the `List.to_atom` function, it may result in memory exhaustion. Prefer the `List.to_existing_atom` function for untrusted user input.
414 |
415 | ### Severity
416 |
417 | This is a medium severity finding. It does not allow the attacker to access private data, or performed unauthorized actions. Atom DoS allows an attacker to trigger a crash of the Erlang virtual machine. There are two possible outcomes:
418 |
419 | 1. The application crashes and restarts. There will be some downtime during the restart, but overall the impact will be low.
420 |
421 | 2. The application crashes and remains down. This is a higher severity incident.
422 |
423 | The behavior of your application depends on the deployment environment. Instructions for testing your environment - https://paraxial.io/blog/atom-dos-impact
424 |
425 | ### How to verify this finding
426 |
427 | Find the line of code where the atom is being created. Is the atom created from user input? For example:
428 |
429 | `List.to_atom(a)`
430 |
431 | Can the variable `a` be set through user input? If it can be, this is a true positive.
432 |
433 | ### How to fix a true positive
434 |
435 | Do not create new atoms at runtime. Restructure your code so that atoms do not need to be created from user input.
436 |
437 | https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/atom_exhaustion
438 |
439 |
440 | ## UID 13, DOS.StringToAtom: Unsafe `String.to_atom`
441 |
442 | In Elixir, atoms are not garbage collected. As such, if user input is passed to the `String.to_atom` function, it may result in memory exhaustion. Prefer the `String.to_existing_atom` function for untrusted user input.
443 |
444 | ### Severity
445 |
446 | This is a medium severity finding. It does not allow the attacker to access private data, or performed unauthorized actions. Atom DoS allows an attacker to trigger a crash of the Erlang virtual machine. There are two possible outcomes:
447 |
448 | 1. The application crashes and restarts. There will be some downtime during the restart, but overall the impact will be low.
449 |
450 | 2. The application crashes and remains down. This is a higher severity incident.
451 |
452 | The behavior of your application depends on the deployment environment. Instructions for testing your environment - https://paraxial.io/blog/atom-dos-impact
453 |
454 | ### How to verify this finding
455 |
456 | Find the line of code where the atom is being created. Is the atom created from user input? For example:
457 |
458 | `String.to_atom(a)`
459 |
460 | Can the variable `a` be set through user input? If it can be, this is a true positive.
461 |
462 | ### How to fix a true positive
463 |
464 | Do not create new atoms at runtime. Restructure your code so that atoms do not need to be created from user input.
465 |
466 | https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/atom_exhaustion
467 |
468 |
469 | ## UID 14, Misc.BinToTerm: Unsafe `binary_to_term`
470 |
471 | If user input is passed to Erlang's `binary_to_term` function it may result in memory exhaustion or code execution. Even with the `:safe` option, `binary_to_term` will deserialize functions, and shouldn't be considered safe to use with untrusted input.
472 |
473 | ### Severity
474 |
475 | This is a high severity finding. Unsafe usage of `binary_to_term` can lead to a remote code execution vulnerability, which allows an attacker to take over your web server.
476 |
477 | ### How to verify this finding
478 |
479 | Is user input being passed to `binary_to_term`? For example:
480 |
481 | `:erlang.binary_to_term(user_input, [:safe])`
482 |
483 | The `[:safe]` option is misleading, this function is vulnerable. If user input is being passed to `binary_to_term`, this is a true positive.
484 |
485 | Additional details - https://paraxial.io/blog/elixir-rce
486 |
487 |
488 | ### How to fix a true positive
489 |
490 | 1. Do not pass user input to `:erlang.binary_to_term/2` if you can avoid it.
491 |
492 | 2. Use `Plug.Crypto.non_executable_binary_to_term` instead.
493 |
494 | https://hexdocs.pm/plug_crypto/Plug.Crypto.html#non_executable_binary_to_term/2
495 |
496 |
497 | ## UID 15, RCE.CodeModule: Code execution in eval function
498 |
499 | ### Severity
500 |
501 | This is a high severity finding. Calling the `Code` functions `eval_string`, `eval_file`, or `eval_quoted` on external user input may allow an attacker to take over your web server.
502 |
503 | ### How to verify this finding
504 |
505 | Is user input being passed to the function that Sobelow flagged? For example:
506 |
507 | `Code.eval_string(user_input)`
508 |
509 | If the function is receiving user input, this is a true positive.
510 |
511 | ### How to fix a true positive
512 |
513 | Do not allow user input to reach the `Code` functions `eval_string`, `eval_file`, or `eval_quoted`. Input to these functions should never come from the network.
514 |
515 | https://hexdocs.pm/elixir/Code.html
516 |
517 | ## UID 16, RCE.EEx: Code Execution in EEx template eval
518 |
519 | If user input is passed to EEx eval functions, it may result in arbitrary code execution. The root cause of these issues is often directory traversal.
520 |
521 | ### Severity
522 |
523 | This is a high severity finding. Calling the EEx functions `eval_string` and `eval_file` on external user input may allow an attacker to take over your web server.
524 |
525 | ### How to verify this finding
526 |
527 | Is user input being passed to the function that Sobelow flagged? For example:
528 |
529 | ```
530 | > user_input = "<%= 2 + 3 %>"
531 | "<%= 2 + 3 %>"
532 | > EEx.eval_string(user_input)
533 | "5"
534 | ```
535 |
536 | If the function is receiving user input, this is a true positive.
537 |
538 | ### How to fix a true positive
539 |
540 | Do not allow user input to reach the `EEx` functions `eval_string` and `eval_file`. Input to these functions should never come from the network.
541 |
542 | https://hexdocs.pm/eex/1.14/EEx.html
543 |
544 | ## UID 17, SQL.Query: SQL injection
545 |
546 | ### Severity
547 |
548 | This is a high severity finding. SQL injection can be used by an attacker to run authorized database commands, leading to data being stolen, modified, or deleted.
549 |
550 | ### How to verify this finding
551 |
552 | Below is an example of code that is vulnerable to SQL injection:
553 |
554 | ```
555 | def e_get_fruit(min_q) do
556 | q = """
557 | SELECT f.id, f.name, f.quantity, f.secret
558 | FROM fruits AS f
559 | WHERE f.quantity > #{min_q} AND f.secret = FALSE
560 | """
561 | {:ok, %{rows: rows}} =
562 | Ecto.Adapters.SQL.query(Repo, q)
563 | end
564 | ```
565 |
566 | The key line is `WHERE f.quantity > #{min_q} AND f.secret = FALSE`, where `min_q` is user input. You should never construct an SQL query from user input. Rather, external input should be passed as a parameter to the query. For example:
567 |
568 | `Ecto.Adapters.SQL.query(MyRepo, "SELECT $1::integer + $2", [user_in_a, user_in_b])`
569 |
570 | is not vulnerable, because the user input `(user_in_a, user_in_b)` is being passed as parameters to the SQL query.
571 |
572 | Additional details - https://paraxial.io/blog/sql-injection
573 |
574 | ### How to fix a true positive
575 |
576 | Ensure that all user input is passed as a parameter to the `query` function.
577 |
578 | Not safe:
579 |
580 | `Ecto.Adapters.SQL.query(Repo, "SELECT * FROM potions WHERE name = #{user_input}")`
581 |
582 | Not safe:
583 |
584 | `Ecto.Adapters.SQL.query(Repo, "SELECT * FROM potions WHERE name = " <> user_input)`
585 |
586 | Safe:
587 |
588 | `Ecto.Adapters.SQL.query(Repo, "SELECT * FROM potions WHERE name = $1", [user_input])`
589 |
590 |
591 | ## UID 18, SQL.Stream: SQL injection
592 |
593 | ### Severity
594 |
595 | This is a high severity finding. SQL injection can be used by an attacker to run authorized database commands, leading to data being stolen, modified, or deleted.
596 |
597 | ### How to verify this finding
598 |
599 | Below is an example of code that is vulnerable to SQL injection:
600 |
601 | ```
602 | def e_get_fruit(min_q) do
603 | q = """
604 | SELECT f.id, f.name, f.quantity, f.secret
605 | FROM fruits AS f
606 | WHERE f.quantity > #{min_q} AND f.secret = FALSE
607 | """
608 | Ecto.Adapters.SQL.stream(Repo, q)
609 | end
610 | ```
611 |
612 | The key line is `WHERE f.quantity > #{min_q} AND f.secret = FALSE`, where `min_q` is user input. You should never construct an SQL query from user input. Rather, external input should be passed as a parameter to the query. For example:
613 |
614 | `Ecto.Adapters.SQL.stream(MyRepo, "SELECT $1::integer + $2", [user_in_a, user_in_b])`
615 |
616 | is not vulnerable, because the user input `(user_in_a, user_in_b)` is being passed as parameters to the SQL query.
617 |
618 | Additional details - https://paraxial.io/blog/sql-injection
619 |
620 | ### How to fix a true positive
621 |
622 | Ensure that all user input is passed as a parameter to the `query` function.
623 |
624 | Not safe:
625 |
626 | `Ecto.Adapters.SQL.stream(Repo, "SELECT * FROM potions WHERE name = #{user_input}")`
627 |
628 | Not safe:
629 |
630 | `Ecto.Adapters.SQL.stream(Repo, "SELECT * FROM potions WHERE name = " <> user_input)`
631 |
632 | Safe:
633 |
634 | `Ecto.Adapters.SQL.stream(Repo, "SELECT * FROM potions WHERE name = $1", [user_input])`
635 |
636 |
637 | ## UID 19, Traversal.FileModule: Directory Traversal in `File` function
638 |
639 | ### Severity
640 |
641 | This is a high severity finding. If user input is passed to a `File` function, an attacker may be able to read unauthorized files, such as `../config/prod.secrets.exs`, and make unauthorized changes to the filesystem.
642 |
643 | ### How to verify this finding
644 |
645 | 1. Start at the call to the `File` function in your code base. Can you determine the source of this variable?
646 |
647 | 2. If it is hard-coded in the source code, user input does not change the variable, so the finding is a false-positive.
648 |
649 | 3. If the variable comes from user input, for example a GET or POST request, the function is vulnerable.
650 |
651 | 4. If the variable comes from a database, or other data store, you need to determine if it set by a user.
652 |
653 | If you are able to provide some input to your application, which changes the variable passed to the `File` function, you have verified this finding as a true positive.
654 |
655 | ### How to fix a true positive
656 |
657 | Do not pass user input to `File` functions. The input should be system generated, for example:
658 |
659 | `%Plug.Upload{filename: filename, path: path} = upload`
660 |
661 | When `upload` is set by the user, it is not safe to pass the `filename` variable. The `path` variable is generated by plug, for example:
662 |
663 | `/var/folders/0m/d5lzvxvs181cl_f5m1x3wrx40000gn/T/plug-1681/multipart-1681411044-723629749623759-3`
664 |
665 | and is safe to pass as a variable.
666 |
667 | If you must combine user input to get a path, make sure it's sanitized by [`Path.safe_relative/1`](https://hexdocs.pm/elixir/Path.html#safe_relative/1) before passing to `File` functions.
668 |
669 |
670 | ## UID 20, Traversal.SendDownload: Directory Traversal in `send_download`
671 |
672 | ### Severity
673 |
674 | This is a high severity finding. If user input is passed to `send_download`, an attacker may be able to read unauthorized files, such as `../config/prod.secrets.exs`, and make unauthorized changes to the filesystem.
675 |
676 | ### How to verify this finding
677 |
678 | Consider the example function in a Phoenix controlelr:
679 |
680 | ```elixir
681 | def user_pfp(conn, %{"file_name" => file_name}) do
682 | send_download(conn, {:file, file_name})
683 | end
684 | ```
685 |
686 | When `file_name` is controlled by the user, this is a true positive.
687 |
688 | 1. Start at the call to the `send_download` function in your code base. Can you determine the source of the function input?
689 |
690 | 2. If the function input is hard-coded in source, it is a false-positive.
691 |
692 | 3. If the variable comes from user input, for example a GET or POST request, the function is vulnerable.
693 |
694 | 4. If the variable comes from a database, or other data store, you need to determine if it set by a user.
695 |
696 | If you are able to provide some input to your application, which changes the variable passed to the `send_download` function, you have verified this finding as a true positive.
697 |
698 | ### How to fix a true positive
699 |
700 | Do not pass user input to `send_download`. Inputs to `send_download` should be pre-defined in the source code, not created dynamically by user input.
701 |
702 |
703 | ## UID 21, Traversal.SendFile: Directory Traversal in `send_file`
704 |
705 | ### Severity
706 |
707 | This is a high severity finding. If user input is passed to `send_file`, an attacker may be able to read unauthorized files, such as `../config/prod.secrets.exs`, and make unauthorized changes to the filesystem.
708 |
709 | ### How to verify this finding
710 |
711 | Consider the example function in a Phoenix controlelr:
712 |
713 | ```elixir
714 | def user_pfp(conn, %{"file_name" => file_name}) do
715 | send_file(conn, 200, file_name)
716 | end
717 | ```
718 |
719 | When `file_name` is controlled by the user, this is a true positive.
720 |
721 | 1. Start at the call to the `send_file` function in your code base. Can you determine the source of the function input?
722 |
723 | 2. If the function input is hard-coded in source, it is a false-positive.
724 |
725 | 3. If the variable comes from user input, for example a GET or POST request, the function is vulnerable.
726 |
727 | 4. If the variable comes from a database, or other data store, you need to determine if it set by a user.
728 |
729 | If you are able to provide some input to your application, which changes the variable passed to the `send_file` function, you have verified this finding as a true positive.
730 |
731 | ### How to fix a true positive
732 |
733 | Do not pass user input to `send_file`. Inputs to `send_file` should be pre-defined in the source code, not created dynamically by user input.
734 |
735 |
736 | ## UID 22, Vuln.Coherence: Known Vulnerable Dependency - Update Coherence
737 |
738 | ### Severity
739 |
740 | This is a high severity vulnerability. An attacker can add themselves as an admin user by exploiting coherence. https://github.com/advisories/GHSA-mrq8-53r4-3j5m
741 |
742 | ### How to verify this finding
743 |
744 | ```
745 | Affected versions
746 | < 0.5.2
747 | ```
748 |
749 | ### How to fix a true positive
750 |
751 | ```
752 | Patched versions
753 | 0.5.2
754 | ```
755 |
756 | ## UID 23, Vuln.Plug: Known Vulnerable Dependency - Update Plug
757 |
758 | ### Severity
759 |
760 | This is a high severity vulnerability in Plug, "Arbitrary Code Execution in Cookie Serialization". https://github.com/advisories/GHSA-5v4m-c73v-c7gq
761 |
762 | ### How to verify this finding
763 |
764 | ```
765 | Affected versions
766 | < 1.0.4
767 | >= 1.1.0, < 1.1.7
768 | >= 1.2.0, < 1.2.3
769 | >= 1.3.0, < 1.3.2
770 | ```
771 |
772 | ### How to fix a true positive
773 |
774 | ```
775 | Patched versions
776 | 1.0.4
777 | 1.1.7
778 | 1.2.3
779 | 1.3.2
780 | ```
781 |
782 | ## UID 24, Vuln.Ecto: Known Vulnerable Dependency - Update Ecto
783 |
784 | ### Severity
785 |
786 | This is a high severity finding. Ecto `2.2.0` does not enforce the `is_nil` requirement. For an example of why this is dangerous, see this example from Jose Valim:
787 |
788 | Imagine you write this query:
789 |
790 | `from User, where: [api_token: ^params["token"]], limit: 1`
791 |
792 | Now if someone passes no token, you will accidentally login as any of the users without a token.
793 |
794 | https://elixirforum.com/t/why-does-ecto-require-the-use-of-is-nil-1/49241
795 |
796 | https://github.com/advisories/GHSA-4r2f-6fm9-2qgh
797 |
798 | ### How to verify this finding
799 |
800 | ```
801 | Affected versions
802 | = 2.2.0
803 | ```
804 |
805 | ### How to fix a true positive
806 |
807 | ```
808 | Patched versions
809 | 2.2.1
810 | ```
811 |
812 | ## UID 25, Vuln.HeaderInject: Known Vulnerable Dependency - Update Plug
813 |
814 | ### Severity
815 |
816 | This is a high severity vulnerability in Plug, "Header Injection". https://github.com/advisories/GHSA-9h73-w7ch-rh73
817 |
818 | ### How to verify this finding
819 |
820 | ```
821 | Affected versions
822 | < 1.0.6
823 | >= 1.1.0, < 1.1.9
824 | >= 1.2.0, < 1.2.5
825 | >= 1.3.0, < 1.3.5
826 | ```
827 |
828 | ### How to fix a true positive
829 |
830 | ```
831 | Patched versions
832 | 1.0.6
833 | 1.1.9
834 | 1.2.5
835 | 1.3.5
836 | ```
837 |
838 | ## UID 26, Vuln.PlugNull: Known Vulnerable Dependency - Update Plug
839 |
840 | ### Severity
841 |
842 | This is a high severity vulnerability in Plug, "Null Byte Injection in Plug.Static". https://github.com/advisories/GHSA-2q6v-32mr-8p8x
843 |
844 | ### How to verify this finding
845 |
846 | ```
847 | Affected versions
848 | < 1.0.4
849 | >= 1.1.0, < 1.1.7
850 | >= 1.2.0, < 1.2.3
851 | >= 1.3.0, < 1.3.2
852 | ```
853 |
854 | ### How to fix a true positive
855 |
856 | ```
857 | Patched versions
858 | 1.0.4
859 | 1.1.7
860 | 1.2.3
861 | 1.3.2
862 | ```
863 |
864 |
865 | ## UID 27, Vuln.Redirect: Known Vulnerable Dependency - Update Phoenix
866 |
867 | ### Severity
868 |
869 | This is a low severity vulnerability in Phoenix, "Arbitrary URL Redirect". https://github.com/advisories/GHSA-cmfh-8f8r-fj96
870 |
871 | "An attacker can use this vulnerability to aid in social engineering attacks. The most common use would be to create highly believable phishing attacks."
872 |
873 | ### How to verify this finding
874 |
875 | ```
876 | Affected versions
877 | < 1.0.6
878 | >= 1.1.0, < 1.1.8
879 | >= 1.2.0, < 1.2.3
880 | ```
881 |
882 | ### How to fix a true positive
883 |
884 | ```
885 | Patched versions
886 | 1.0.6
887 | 1.1.8
888 | 1.2.3
889 | ```
890 |
891 | ## UID 28, XSS.ContentType: XSS in `put_resp_content_type`
892 |
893 | If an attacker is able to set arbitrary content types for an HTTP response containing user input, the attacker is likely to be able to leverage this for cross-site scripting (XSS).
894 |
895 | For example, consider an endpoint that returns JSON with user input:
896 |
897 | `{"json": "user_input"}`
898 |
899 | If an attacker can control the content type set in the HTTP response, they can set it to "text/html" and update the JSON to the following in order to cause XSS:
900 |
901 | `{"json": ""}`
902 |
903 | ### Severity
904 |
905 | This is a high severity finding. XSS can lead to user account compromise and a malicious worm spreading via JavaScript. See the [MySpace Samy worm](https://en.wikipedia.org/wiki/Samy_(computer_worm)) for a real world example.
906 |
907 | ### How to verify this finding
908 |
909 | Consider a file upload function in a Phoenix application, where the `content-type` of the uploaded image is set by the user.
910 |
911 | ```elixir
912 | def view_photo(conn, %{"filename" => filename}) do
913 | case ImgServer.get(filename) do
914 | %{content_type: content_type, bin: bin} ->
915 | conn
916 | |> put_resp_content_type(content_type)
917 | |> send_resp(200, bin)
918 | _ ->
919 | conn
920 | |> put_resp_content_type("text/html")
921 | |> send_resp(404, "Not Found")
922 | end
923 | end
924 | ```
925 |
926 | `view_photo` is vulnerable to XSS, because an attacker can upload an HTML document, for example:
927 |
928 | ``
929 |
930 | With the content-type `text/html`. When a user visits the page for the uploaded file, the attacker controlled JavaScript will execute.
931 |
932 | Additional details - https://paraxial.io/blog/xss-phoenix
933 |
934 | ### How to fix a true positive
935 |
936 | Do not allow users to upload HTML documents, which are then shown to users. If you are implementing a file upload system, where only images are expected, do not allow `content-type` to be set by users. Restrict the allowed `content-type` values to a pre-defined list, for example `image/jpeg`, `image/png`, etc.
937 |
938 |
939 | ## UID 29, XSS.HTML: XSS in `html`
940 |
941 | ### Severity
942 |
943 | This is a high severity finding. User input should not be passed to the `Phoenix.Controller.html/2` function, due to the risk of XSS - https://hexdocs.pm/phoenix/Phoenix.Controller.html#html/2
944 |
945 | ### How to verify this finding
946 |
947 | Are you passing user input to the `Phoenix.Controller.html/2` function? Consider an example Phoenix controller:
948 |
949 | ```elixir
950 | def html_resp(conn, %{"i" => i}) do
951 | html(conn, "#{i}")
952 | end
953 | ```
954 |
955 | This function is vulnerable to XSS, because user input is being passed directly into the HTML document.
956 |
957 | Additional details - https://paraxial.io/blog/xss-phoenix
958 |
959 | ### How to fix a true positive
960 |
961 | Use the `Phoenix.Controller.render/3` function, which is the standard way to handle user input in HTML documents in Phoenix. The `render` function is the standard pattern seen in Phoenix applications, because it protects against XSS by default.
962 |
963 | https://hexdocs.pm/phoenix/Phoenix.Controller.html#render/3
964 |
965 |
966 | ## UID 30, XSS.Raw: XSS
967 |
968 | ### Severity
969 |
970 | This is a high severity finding. User input should not be passed to the `Phoenix.HTML.raw` function, due to the risk of XSS - https://hexdocs.pm/phoenix_html/Phoenix.HTML.html
971 |
972 | ### How to verify this finding
973 |
974 | Consider the following code:
975 |
976 | ```
977 | lib/cross_web/templates/page/render_b.html.eex
978 |
979 |
User input (vulnerable due to Phoenix.HTML.raw/1):
980 | <%= raw @i %>
981 | ```
982 |
983 | The `i` variable is controlled by user input, and is being passed to the `raw` function. Submit a request that sets `i` to ``, and see how the alert box is rendered. If external user input results in JavaScript being executed, this is a true positive.
984 |
985 | If the variable passed to `raw` is not controlled by the user, this is a false positive.
986 |
987 | ### How to fix a true positive
988 |
989 | Do not pass user input to the `raw` function. Ideally you should avoid using `raw`, but if you must, ensure that data created at runtime from user input is not passed to `raw`.
990 |
991 |
992 | ## UID 31, XSS.SendResp: XSS in `send_resp`
993 |
994 | ### Severity
995 |
996 | This is a high severity finding. XSS can lead to user account compromise and a malicious worm spreading via JavaScript. See the [MySpace Samy worm](https://en.wikipedia.org/wiki/Samy_(computer_worm)) for a real world example.
997 |
998 | ### How to verify this finding
999 |
1000 | In Phoenix you can pass HTML directly to `send_resp`.
1001 |
1002 | ```elixir
1003 | def send_resp_html(conn, %{"i" => i}) do
1004 | conn
1005 | |> put_resp_content_type("text/html")
1006 | |> send_resp(200, "#{i}")
1007 | end
1008 | ```
1009 |
1010 | Note that an attacker can set `i` to ``. However, the above example is unlikely to be seen in real code.
1011 |
1012 | Consider a file upload function in a Phoenix application, where the `content-type` of the uploaded image is set by the user.
1013 |
1014 | ```elixir
1015 | def view_photo(conn, %{"filename" => filename}) do
1016 | case ImgServer.get(filename) do
1017 | %{content_type: content_type, bin: bin} ->
1018 | conn
1019 | |> put_resp_content_type(content_type)
1020 | |> send_resp(200, bin)
1021 | _ ->
1022 | conn
1023 | |> put_resp_content_type("text/html")
1024 | |> send_resp(404, "Not Found")
1025 | end
1026 | end
1027 | ```
1028 |
1029 | `view_photo` is vulnerable to XSS, because an attacker can upload an HTML document, for example:
1030 |
1031 | ``
1032 |
1033 | With the content-type `text/html`. When a user visits the page for the uploaded file, the attacker controlled JavaScript will execute.
1034 |
1035 | Additional details - https://paraxial.io/blog/xss-phoenix
1036 |
1037 | ### How to fix a true positive
1038 |
1039 | Consider how user input is being passed to `send_resp`. If user input can be used to build HTML elements on the page, the function is vulnerable.
1040 |
1041 | Use the `Phoenix.Controller.render/3` function, which is the standard way to handle user input in HTML documents in Phoenix. The `render` function is the standard pattern seen in Phoenix applications, because it protects against XSS by default.
1042 |
1043 | https://hexdocs.pm/phoenix/Phoenix.Controller.html#render/3
1044 |
1045 |
--------------------------------------------------------------------------------