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.ex
defmodule Sobelow.Config do
alias Sobelow.Parse
alias Sobelow.Config.CSRF
alias Sobelow.Config.CSRFRoute
alias Sobelow.Config.CSP
alias Sobelow.Config.Headers
alias Sobelow.Config.CSWH
@submodules [
Sobelow.Config.CSRF,
Sobelow.Config.CSRFRoute,
Sobelow.Config.Headers,
Sobelow.Config.CSP,
Sobelow.Config.Secrets,
Sobelow.Config.HTTPS,
Sobelow.Config.HSTS,
Sobelow.Config.CSWH
]
use Sobelow.FindingType
@skip_files ["dev.exs", "test.exs", "dev.secret.exs", "test.secret.exs"]
def fetch(root, router, endpoints) do
allowed = @submodules -- Sobelow.get_ignored()
ignored_files = Sobelow.get_env(:ignored_files)
dir_path = root <> "config/"
Enum.each(allowed, fn mod ->
cond do
mod in [CSRF, CSRFRoute, Headers, CSP] ->
Enum.each(router, fn path ->
apply(mod, :run, [relative_path(path, root)])
end)
mod in [CSWH] ->
Enum.each(endpoints, fn path ->
apply(mod, :run, [relative_path(path, root)])
end)
File.dir?(dir_path) ->
configs =
File.ls!(dir_path)
|> Enum.filter(&want_to_scan?(dir_path <> &1, ignored_files))
apply(mod, :run, [dir_path, configs])
true ->
nil
end
end)
end
defp want_to_scan?(conf, ignored_files) do
if Path.extname(conf) === ".exs" && !Enum.member?(@skip_files, Path.basename(conf)) &&
!Enum.member?(ignored_files, Path.expand(conf)),
do: conf
end
defp relative_path(path, root) do
path = Path.relative_to(path, Path.expand(root))
case Path.type(path) do
:absolute -> path
_ -> root <> path
end
end
def get_configs_by_file(secret, file) do
if File.exists?(file) do
get_configs(secret, file)
else
[]
end
end
# Config utils
def get_pipelines(filepath) do
ast = Parse.ast(filepath)
{_, acc} = Macro.prewalk(ast, [], &Parse.get_funs_of_type(&1, &2, :pipeline))
acc
end
def get_plug_list(block) do
case block do
{:__block__, _, list} -> list
{_, _, _} = list -> [list]
_ -> []
end
|> Enum.reject(fn {type, _, _} -> type !== :plug end)
end
def is_vuln_pipeline?({:pipeline, _, [_name, [do: block]]}, :csrf) do
plugs = get_plug_list(block)
has_csrf? = Enum.any?(plugs, &is_plug?(&1, :protect_from_forgery))
has_session? = Enum.any?(plugs, &is_plug?(&1, :fetch_session))
has_session? and not has_csrf?
end
def is_vuln_pipeline?({:pipeline, _, [_name, [do: block]]}, :headers) do
plugs = get_plug_list(block)
has_headers? = Enum.any?(plugs, &is_plug?(&1, :put_secure_browser_headers))
accepts = Enum.find_value(plugs, &get_plug_accepts/1)
!has_headers? && is_list(accepts) && Enum.member?(accepts, "html")
end
def get_plug_accepts({:plug, _, [:accepts, {:sigil_w, _, opts}]}), do: parse_accepts(opts)
def get_plug_accepts({:plug, _, [:accepts, accepts]}), do: accepts
def get_plug_accepts(_), do: []
def parse_accepts([{:<<>>, _, [accepts | _]}, []]), do: String.split(accepts, " ")
def is_plug?({:plug, _, [type]}, type), do: true
def is_plug?({:plug, _, [type, _]}, type), do: true
def is_plug?(_, _), do: false
def get_fuzzy_configs(key, filepath) do
ast = Parse.ast(filepath)
{_, acc} = Macro.prewalk(ast, [], &extract_fuzzy_configs(&1, &2, key))
acc
end
def get_configs(key, filepath) do
ast = Parse.ast(filepath)
{_, acc} = Macro.prewalk(ast, [], &extract_configs(&1, &2, key))
acc
end
defp extract_fuzzy_configs({:config, _, opts} = ast, acc, key) do
opt = List.last(opts)
vals = if Keyword.keyword?(opt), do: fuzzy_keyword_get(opt, key), else: nil
if is_nil(vals) do
{ast, acc}
else
{ast, [{ast, vals} | acc]}
end
end
defp extract_fuzzy_configs(ast, acc, _key) do
{ast, acc}
end
defp extract_configs({:config, _, opts} = ast, acc, key) do
opt = List.last(opts)
val = if Keyword.keyword?(opt), do: Keyword.get(opt, key), else: nil
if is_nil(val) do
{ast, acc}
else
{ast, [{ast, key, val} | acc]}
end
end
defp extract_configs(ast, acc, _key) do
{ast, acc}
end
defp fuzzy_keyword_get(opt, key) do
keys = Keyword.keys(opt)
Enum.map(keys, fn k ->
if is_atom(k) && k != :secret_key_base do
s = Atom.to_string(k) |> String.downcase()
if String.contains?(s, key), do: {k, Keyword.get(opt, k)}
end
end)
|> Enum.reject(&is_nil/1)
end
def get_version(filepath) do
ast = Parse.ast(filepath)
{_, acc} = Macro.prewalk(ast, [], &get_version(&1, &2))
acc
end
def get_version({:@, _, nil} = ast, acc), do: {ast, acc}
def get_version({:@, _, [{:version, _, [vsn]}]}, _acc) when is_binary(vsn), do: {vsn, vsn}
def get_version(ast, acc), do: {ast, acc}
end