Packages
sobelow
0.10.3
0.14.1
0.14.0
0.13.0
0.12.2
0.12.1
0.12.0
0.11.1
0.11.0
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.0
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.12
0.3.11
0.3.10
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
Security-focused static analysis for Elixir & the Phoenix framework
Current section
Files
Jump to
Current section
Files
lib/sobelow/config/csrf_route.ex
defmodule Sobelow.Config.CSRFRoute do
@moduledoc """
# Cross-Site Request Forgery
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.
Read more about CSRF here:
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
This type of CSRF is flagged by `sobelow` when state-changing
routes share an action with GET-based routes. For example:
get "/users", UserController, :new
post "/users", UserController, :new
In this instance, it may be possible to trigger the POST
functionality with a GET request and query parameters.
CSRF checks can be ignored with the following command:
$ mix sobelow -i Config.CSRFRoute
"""
alias Sobelow.Parse
use Sobelow.Finding
@state_changing_methods [:post, :put, :patch, :delete]
@finding_type "Config.CSRFRoute: CSRF via Action Reuse"
def run(router) do
finding = Finding.init(@finding_type, Utils.normalize_path(router))
router
|> Parse.ast()
|> Parse.get_top_level_funs_of_type(:scope)
|> combine_scopes()
|> Stream.flat_map(&route_findings(&1, finding))
# Sort for deterministic txt-format output
|> Enum.sort()
|> Enum.each(&add_finding/1)
end
def route_findings(scope, finding) do
scope
|> Parse.get_funs_of_type([:get | @state_changing_methods])
|> Enum.reduce(%{}, &transform_routes/2)
|> Stream.filter(&get_and_state_changing?/1)
|> Stream.flat_map(&put_finding_details(&1, finding))
end
defp put_finding_details({_, meta}, %Finding{fun_source: nil} = finding) do
src = Enum.map(meta, fn {_, v} -> v end)
Enum.reduce(
meta,
[],
&put_finding_details(&1, &2, %{finding | fun_source: {:__block__, [], src}})
)
end
defp put_finding_details({:get, fun}, acc, finding) do
finding = %{
finding
| vuln_source: fun,
vuln_line_no: Parse.get_fun_line(fun),
fun_name: get_action(fun),
confidence: :high
}
[finding | acc]
end
defp put_finding_details(_, acc, _), do: acc
defp add_finding(%Finding{} = finding) do
finding = Finding.fetch_fingerprint(finding)
file_header = "File: #{finding.filename}"
action_header = "Action: #{finding.fun_name}"
line_header = "Line: #{finding.vuln_line_no}"
case Sobelow.format() do
"json" ->
json_finding = [
type: finding.type,
file: finding.filename,
route: finding.fun_name,
line: finding.vuln_line_no
]
Sobelow.log_finding(json_finding, finding)
"txt" ->
Sobelow.log_finding(finding)
Print.print_custom_finding_metadata(
finding,
[file_header, action_header, line_header]
)
"compact" ->
Print.log_compact_finding(finding)
_ ->
Sobelow.log_finding(finding)
end
end
def combine_scopes(scopes) do
scopes
|> Stream.map(&get_alias_and_list/1)
|> Enum.reduce(%{}, &transform_scopes/2)
|> Map.values()
end
defp get_alias_and_list({:scope, _, [_path, alias, _opts, list]}) do
{get_alias(alias), list[:do]}
end
defp get_alias_and_list({:scope, _, [_path, opts, list]}), do: {get_alias(opts), list[:do]}
defp get_alias_and_list({:scope, _, [opts, list]}), do: {get_alias(opts), list[:do]}
defp get_alias({fun, _, opts}), do: {fun, [], opts}
defp get_alias(opts) when is_list(opts) do
opts[:alias]
end
defp get_alias(_), do: nil
defp get_and_state_changing?({_, meta}) do
has_method?(meta, :get) && Enum.any?(@state_changing_methods, &has_method?(meta, &1))
end
defp has_method?(meta, action) do
Enum.any?(meta, fn {method, _} -> method == action end)
end
defp transform_routes({method, _, opts} = fun, acc) do
value = {method, fun}
Map.update(acc, get_ca(opts), [value], &[value | &1])
end
defp transform_scopes({scope, routes}, acc) do
Map.update(acc, scope, [routes], &[routes | &1])
end
defp get_action({_, _, opts}) when is_list(opts), do: get_action(opts)
defp get_action([_, _, action | _]), do: action
defp get_ca([_path, controller, action | _]) do
[normalize_controller(controller), action]
end
defp normalize_controller({:__aliases__, _, controller}), do: controller
defp normalize_controller({fun, _, opts}), do: {fun, [], opts}
end