From 1f904224b016c9c2782cac6c68936d70a6776624 Mon Sep 17 00:00:00 2001 From: Holden Oullette <6202965+houllette@users.noreply.github.com> Date: Mon, 8 May 2023 13:55:17 -0700 Subject: [PATCH] Added Credo (#132) --- .credo.exs | 220 ++++++++++++++++++ .github/workflows/elixir.yml | 26 ++- CHANGELOG.md | 16 ++ lib/mix/tasks/sobelow.ex | 18 +- lib/sobelow.ex | 58 +++-- lib/sobelow/ci.ex | 2 +- lib/sobelow/ci/os.ex | 14 +- lib/sobelow/ci/system.ex | 14 +- lib/sobelow/config.ex | 22 +- lib/sobelow/config/hsts.ex | 2 +- lib/sobelow/config/https.ex | 2 +- lib/sobelow/dos.ex | 15 ++ lib/sobelow/finding.ex | 19 +- lib/sobelow/finding_log.ex | 10 +- lib/sobelow/fingerprint.ex | 2 +- lib/sobelow/meta_log.ex | 6 +- lib/sobelow/misc.ex | 12 + lib/sobelow/print.ex | 2 +- lib/sobelow/rce.ex | 2 +- lib/sobelow/rce/code_module.ex | 18 +- lib/sobelow/sql.ex | 2 +- lib/sobelow/sql/query.ex | 14 +- lib/sobelow/sql/stream.ex | 14 +- lib/sobelow/traversal.ex | 2 +- lib/sobelow/traversal/file_module.ex | 14 +- lib/sobelow/traversal/send_download.ex | 14 +- lib/sobelow/traversal/send_file.ex | 14 +- lib/sobelow/utils.ex | 2 +- lib/sobelow/vuln.ex | 4 +- lib/sobelow/vuln/coherence.ex | 14 +- lib/sobelow/vuln/cookie_rce.ex | 15 +- lib/sobelow/vuln/ecto.ex | 14 +- lib/sobelow/vuln/header_inject.ex | 24 +- lib/sobelow/vuln/plug_null.ex | 15 +- lib/sobelow/vuln/redirect.ex | 24 +- lib/sobelow/xss.ex | 2 +- lib/sobelow/xss/html.ex | 12 +- lib/sobelow/xss/raw.ex | 12 +- lib/sobelow/xss/send_resp.ex | 14 +- mix.exs | 25 +- mix.lock | 3 + test/config/csrf_route_test.exs | 2 +- test/fixtures/csp/bad_router.ex | 2 + test/fixtures/csp/bad_router_attr.ex | 2 + test/fixtures/csp/good_router.ex | 2 + test/fixtures/csp/good_router_attr.ex | 2 + test/fixtures/csrf/bad_router.ex | 2 + test/fixtures/csrf/good_router.ex | 2 + .../csrf/good_router_with_session_key.ex | 2 + test/fixtures/csrf/good_router_with_with.ex | 2 + test/fixtures/cswh/bad_endpoint.ex | 2 + test/fixtures/cswh/good_endpoint.ex | 2 + test/fixtures/cswh/good_endpoint_2.ex | 2 + test/fixtures/cswh/soso_endpoint.ex | 2 + test/fixtures/headers/bad_router.ex | 2 + test/fixtures/headers/good_router.ex | 2 + .../headers/good_router_with_headers.ex | 2 + 57 files changed, 664 insertions(+), 102 deletions(-) create mode 100644 .credo.exs diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..6d7f4a2 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,220 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [ + ~r"/_build/", + ~r"/deps/", + ~r"/node_modules/", + "test/" + ] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagFIXME, []}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, false}, # should re-enable at some point + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, false}, # should re-enable at some point + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, false}, # should re-enable at some point + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.WrongTestFileExtension, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.UnsafeExec, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now, just replace `false` with `[]`) + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Readability.OnePipePerLine, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PassAsyncInTestCases, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 30f105b..fc24f33 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -12,15 +12,11 @@ env: jobs: mix_test: name: mix test (Elixir ${{matrix.elixir}} | OTP ${{matrix.otp}}) - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: include: - - elixir: '1.5.x' - otp: 20.3.8.26 - - elixir: '1.6.6' - otp: 21.3.8.24 - elixir: '1.7.x' otp: 22.3.4.26 - elixir: '1.8.x' @@ -37,15 +33,13 @@ jobs: otp: 25.1 - elixir: '1.14.x' otp: 25.1 - #warnings_as_errors: true # temporarily disabled due to separate issue that requires fixing, see: https://github.com/nccgroup/sobelow/issues/115 - check_formatted: true steps: - name: Setup Elixir uses: erlef/setup-beam@v1 with: - otp-version: ${{matrix.otp}} - elixir-version: ${{matrix.elixir}} + otp-version: ${{ matrix.otp }} + elixir-version: ${{ matrix.elixir }} - name: Checkout Code uses: actions/checkout@v3 @@ -56,5 +50,19 @@ jobs: mix local.rebar --force mix deps.get --only test + - name: Hex Audit + run: mix hex.audit + + - name: Check Formatting + if: ${{ matrix.elixir == '1.14.x' }} # we only care about formatting for latest version of Elixir + run: mix format --check-formatted + + - name: Compiles w/o Warnings + if: ${{ matrix.elixir == '1.14.x' }} # we only care about warnings for latest version of Elixir + run: mix compile --warnings-as-errors + + - name: Credo + run: mix credo --all --strict + - name: Run Tests run: mix test diff --git a/CHANGELOG.md b/CHANGELOG.md index 57bb5b1..5e3c2a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## v0.13.0 + * Removed + * Support for minimum Elixir versions 1.5 & 1.6 (**POTENTIALLY BREAKING** - only applies if you relied on Elixir 1.5 or 1.6, 1.7+ is still supported) + * Enhancements + * Fixed all `credo` warnings + * Implemented all `credo` "Code Readability" adjustments + * Took advantage of _some_ `credo` refactoring opportunities + * Added (sub)module documentation that was missing for some vulnerabilities and unified presentation of others + * Misc + * Added `mix credo --strict` to project + * Improvements to GitHub CI + * Hex Audit + * Compiler Warnings as Errors + * Checks Formatting + * Added helper `mix test.all` alias + ## v0.12.2 * Bug fixes * Removed `:castore` and introduced `:verify_none` to quiet warning and unblock escript usage, see [#133](https://github.com/nccgroup/sobelow/issues/133) for more context on why this is necessary diff --git a/lib/mix/tasks/sobelow.ex b/lib/mix/tasks/sobelow.ex index fc7bb05..3d5ae59 100644 --- a/lib/mix/tasks/sobelow.ex +++ b/lib/mix/tasks/sobelow.ex @@ -192,14 +192,17 @@ defmodule Mix.Tasks.Sobelow do # This diff check is strictly used for testing/debugging and # isn't meant for general use. + # + # Useful for comapring the output of two different runs of Sobelow def run_diff(argv) do diff_idx = Enum.find_index(argv, fn i -> i === "--diff" end) {_, list} = List.pop_at(argv, diff_idx) {diff_target, list} = List.pop_at(list, diff_idx) - args = Enum.join(list, " ") |> to_charlist() - diff_target = to_charlist(diff_target) - :os.cmd('mix sobelow ' ++ args ++ ' > sobelow.tempdiff') - IO.puts(:os.cmd('diff sobelow.tempdiff ' ++ diff_target)) + args = Enum.join(list, " ") + diff_target = to_string(diff_target) + System.shell("mix sobelow #{args} > sobelow.tempdiff") + {diff, _} = System.shell("diff sobelow.tempdiff #{diff_target}") + IO.puts(diff) end def set_env(key, value) do @@ -279,9 +282,10 @@ defmodule Mix.Tasks.Sobelow do defp out_format("", format), do: format defp out_format(_out, format) do - cond do - format in ["json", "quiet", "sarif"] -> format - true -> "json" + if format in ["json", "quiet", "sarif"] do + format + else + "json" end end end diff --git a/lib/sobelow.ex b/lib/sobelow.ex index 4f164bc..e37da43 100644 --- a/lib/sobelow.ex +++ b/lib/sobelow.ex @@ -19,17 +19,17 @@ defmodule Sobelow do Sobelow.Vuln ] - alias Sobelow.Utils alias Sobelow.Config - alias Sobelow.Parse - alias Sobelow.Vuln alias Sobelow.Finding alias Sobelow.FindingLog - alias Sobelow.MetaLog alias Sobelow.Fingerprint alias Sobelow.IO, as: MixIO + alias Sobelow.MetaLog + alias Sobelow.Parse + alias Sobelow.Utils + alias Sobelow.Vuln - def run() do + def run do project_root = get_env(:root) <> "/" version_check() @@ -59,13 +59,13 @@ defmodule Sobelow do template_meta_files = get_meta_templates(lib_root) {libroot_meta_files, tmp_default_router} = - if !phx_post_1_2? do + if phx_post_1_2? do + {[], ""} + else libroot_meta_files = get_meta_files(project_root <> "lib") default_router = project_root <> "/web/router.ex" {libroot_meta_files, default_router} - else - {[], ""} end default_router = get_router(tmp_default_router, phx_post_1_2?) @@ -135,7 +135,7 @@ defmodule Sobelow do MetaLog.add_templates(template_meta_files) end - defp print_output() do + defp print_output do details = case output_format() do "json" -> @@ -164,7 +164,7 @@ defmodule Sobelow do end end - defp exit_with_status() do + defp exit_with_status do exit_on = get_env(:exit_on) finding_logs = FindingLog.log() @@ -192,7 +192,7 @@ defmodule Sobelow do end end - def details() do + def details do mod = get_env(:details) |> get_mod @@ -220,19 +220,19 @@ defmodule Sobelow do meets_threshold?(severity) end - def all_details() do + def all_details do @submodules |> Enum.map(&apply(&1, :details, [])) |> List.flatten() |> Enum.each(&IO.puts(&1)) end - def rules() do + def rules do @submodules |> Enum.flat_map(&apply(&1, :rules, [])) end - def finding_modules() do + def finding_modules do @submodules |> Enum.flat_map(&apply(&1, :finding_modules, [])) end @@ -276,14 +276,14 @@ defmodule Sobelow do severity in threshold end - def format() do + def format do case get_env(:format) do "sarif" -> "json" format -> format end end - def output_format() do + def output_format do get_env(:format) end @@ -291,7 +291,7 @@ defmodule Sobelow do Application.get_env(:sobelow, key) end - defp print_banner() do + defp print_banner do """ ############################################## # # @@ -431,7 +431,7 @@ defmodule Sobelow do [prev | combine_skips(h, t)] end - defp no_router() do + defp no_router do message = """ WARNING: Sobelow cannot find the router. If this is a Phoenix application please use the `--router` flag to specify the router's location. @@ -447,7 +447,7 @@ defmodule Sobelow do ) end - defp file_error() do + defp file_error do message = """ This does not appear to be a Phoenix application. If this is an Umbrella application, each application should be scanned separately. @@ -501,7 +501,7 @@ defmodule Sobelow do defp load_ignored_fingerprints(:eof, _), do: nil defp load_ignored_fingerprints(_, _), do: nil - defp version_check() do + defp version_check do config = System.get_env("SOBELOW_HOME") || @home @@ -540,7 +540,7 @@ defmodule Sobelow do end end - defp get_sobelow_version() do + defp get_sobelow_version do {:ok, _} = Application.ensure_all_started(:ssl) {:ok, _} = Application.ensure_all_started(:inets) @@ -555,7 +555,7 @@ defmodule Sobelow do # verify: :verify_peer, # cacertfile: :public_key.cacerts_get() ], - timeout: 10000 + timeout: 10_000 ] IO.puts(:stderr, "Checking Sobelow version...\n") @@ -651,18 +651,16 @@ defmodule Sobelow do end end - def get_ignored() do + def get_ignored do get_env(:ignored) |> Enum.map(&get_mod/1) end def is_vuln?({vars, _, _}) do - cond do - length(vars) == 0 -> - false - - true -> - true + if Enum.empty?(vars) do + false + else + true end end @@ -672,7 +670,7 @@ defmodule Sobelow do end) end - def version() do + def version do @v |> IO.puts() end diff --git a/lib/sobelow/ci.ex b/lib/sobelow/ci.ex index 9ce054d..5bb7eb2 100644 --- a/lib/sobelow/ci.ex +++ b/lib/sobelow/ci.ex @@ -25,7 +25,7 @@ defmodule Sobelow.CI do end) end - def details() do + def details do @moduledoc end end diff --git a/lib/sobelow/ci/os.ex b/lib/sobelow/ci/os.ex index 602d7a7..118011e 100644 --- a/lib/sobelow/ci/os.ex +++ b/lib/sobelow/ci/os.ex @@ -1,4 +1,16 @@ defmodule Sobelow.CI.OS do + @moduledoc """ + # Command Injection in `:os.cmd` + + This submodule of the `CI` module checks for Command Injection + vulnerabilities through usage of the `:os.cmd` function. + + Ensure the the command passed to `:os.cmd` is not user-controlled. + + `:os.cmd` Injection checks can be ignored with the following command: + + $ mix sobelow -i CI.OS + """ @uid 1 @finding_type "CI.OS: Command Injection in `:os.cmd`" @@ -16,7 +28,7 @@ defmodule Sobelow.CI.OS do Parse.get_erlang_fun_vars_and_meta(fun, 0, :cmd, :os) end - def details() do + def details do Sobelow.CI.details() end end diff --git a/lib/sobelow/ci/system.ex b/lib/sobelow/ci/system.ex index a84ff3e..e5a2f10 100644 --- a/lib/sobelow/ci/system.ex +++ b/lib/sobelow/ci/system.ex @@ -1,4 +1,16 @@ defmodule Sobelow.CI.System do + @moduledoc """ + # Command Injection in `System.cmd` + + This submodule of the `CI` module checks for Command Injection + vulnerabilities through usage of the `System.cmd` function. + + Ensure the the command passed to `System.cmd` is not user-controlled. + + `System.cmd` Injection checks can be ignored with the following command: + + $ mix sobelow -i CI.System + """ @uid 2 @finding_type "CI.System: Command Injection in `System.cmd`" @@ -16,7 +28,7 @@ defmodule Sobelow.CI.System do Parse.get_fun_vars_and_meta(fun, 0, :cmd, [:System]) end - def details() do + def details do Sobelow.CI.details() end end diff --git a/lib/sobelow/config.ex b/lib/sobelow/config.ex index bd3d416..c5cc109 100644 --- a/lib/sobelow/config.ex +++ b/lib/sobelow/config.ex @@ -1,10 +1,26 @@ defmodule Sobelow.Config do - alias Sobelow.Parse + @moduledoc """ + # Configuration + + Submodules contained within this vulnerability type + are related to common insecurities found in how + Phoenix applications are configured. + + This can include things like missing headers, + insecure cookies, and more. + + Configuration checks can be ignored with the + following command: + + $ mix sobelow -i Config + """ + + alias Sobelow.Config.CSP alias Sobelow.Config.CSRF alias Sobelow.Config.CSRFRoute - alias Sobelow.Config.CSP - alias Sobelow.Config.Headers alias Sobelow.Config.CSWH + alias Sobelow.Config.Headers + alias Sobelow.Parse @submodules [ Sobelow.Config.CSRF, diff --git a/lib/sobelow/config/hsts.ex b/lib/sobelow/config/hsts.ex index 9f69772..103c4ba 100644 --- a/lib/sobelow/config/hsts.ex +++ b/lib/sobelow/config/hsts.ex @@ -29,7 +29,7 @@ defmodule Sobelow.Config.HSTS do defp handle_https(opts, file) do # If HTTPS configs were found in any config file and there # are no accompanying HSTS configs, add an HSTS finding. - if length(opts) > 0 && length(Config.get_configs(:force_ssl, file)) === 0 do + if length(opts) > 0 && Enum.empty?(Config.get_configs(:force_ssl, file)) do add_finding(file) end end diff --git a/lib/sobelow/config/https.ex b/lib/sobelow/config/https.ex index 28dc9ac..457aaed 100644 --- a/lib/sobelow/config/https.ex +++ b/lib/sobelow/config/https.ex @@ -31,7 +31,7 @@ defmodule Sobelow.Config.HTTPS do end defp handle_https(opts, path) do - if length(opts) === 0 do + if Enum.empty?(opts) do add_finding(path) end end diff --git a/lib/sobelow/dos.ex b/lib/sobelow/dos.ex index a2c4f62..512c57e 100644 --- a/lib/sobelow/dos.ex +++ b/lib/sobelow/dos.ex @@ -1,4 +1,19 @@ defmodule Sobelow.DOS do + @moduledoc """ + # Denial of Service + + The Denial of Service (DOS) attack is focused on making a + resource (site, application, server) unavailable for the + purpose it was designed. + + Read more about Denial of Service here: + https://owasp.org/www-community/attacks/Denial_of_Service + + Denial of Service checks can be ignored with the + following command: + + $ mix sobelow -i DOS + """ @submodules [Sobelow.DOS.StringToAtom, Sobelow.DOS.ListToAtom, Sobelow.DOS.BinToAtom] use Sobelow.FindingType diff --git a/lib/sobelow/finding.ex b/lib/sobelow/finding.ex index cb6924c..69e7b4c 100644 --- a/lib/sobelow/finding.ex +++ b/lib/sobelow/finding.ex @@ -1,4 +1,6 @@ defmodule Sobelow.Finding do + @moduledoc false + defstruct [ :type, :confidence, @@ -75,15 +77,15 @@ defmodule Sobelow.Finding do alias Sobelow.Print alias Sobelow.Utils - def details() do + def details do @moduledoc end - def id() do + def id do "SBLW" <> String.pad_leading("#{@uid}", 3, "0") end - def rule() do + def rule do [name, description] = String.split(@finding_type, ":", parts: 2) description = String.trim(description) @@ -91,8 +93,7 @@ defmodule Sobelow.Finding do rule_details = details() |> String.split("\n\n") - |> Enum.map(fn para -> String.replace(para, "\n", " ") end) - |> Enum.join("\n\n") + |> Enum.map_join("\n\n", fn para -> String.replace(para, "\n", " ") end) %{ id: id(), @@ -112,19 +113,21 @@ defmodule Sobelow.Finding do end defmodule Sobelow.FindingType do + @moduledoc false + defmacro __using__(_) do quote do - def finding_modules() do + def finding_modules do @submodules end - def details() do + def details do Enum.map(@submodules, fn sub -> apply(sub, :details, []) end) end - def rules() do + def rules do Enum.map(@submodules, fn sub -> apply(sub, :rule, []) end) diff --git a/lib/sobelow/finding_log.ex b/lib/sobelow/finding_log.ex index 4f4c6dc..702ec69 100644 --- a/lib/sobelow/finding_log.ex +++ b/lib/sobelow/finding_log.ex @@ -1,7 +1,9 @@ defmodule Sobelow.FindingLog do + @moduledoc false + use GenServer - def start_link() do + def start_link do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) end @@ -9,7 +11,7 @@ defmodule Sobelow.FindingLog do GenServer.cast(__MODULE__, {:add, finding, severity}) end - def log() do + def log do GenServer.call(__MODULE__, :log) end @@ -53,7 +55,7 @@ defmodule Sobelow.FindingLog do ) end - def sarif_results() do + def sarif_results do %{high: highs, medium: meds, low: lows} = log() highs = normalize_sarif_log(highs) @@ -64,7 +66,7 @@ defmodule Sobelow.FindingLog do Enum.map(meds, &format_sarif/1) ++ Enum.map(lows, &format_sarif/1) end - def quiet() do + def quiet do total = total(log()) findings = if total > 1, do: "findings", else: "finding" diff --git a/lib/sobelow/fingerprint.ex b/lib/sobelow/fingerprint.ex index 928b7f7..70effe3 100644 --- a/lib/sobelow/fingerprint.ex +++ b/lib/sobelow/fingerprint.ex @@ -5,7 +5,7 @@ defmodule Sobelow.Fingerprint do use Agent end - def start_link() do + def start_link do Agent.start_link(fn -> {MapSet.new(), MapSet.new()} end, name: __MODULE__) end diff --git a/lib/sobelow/meta_log.ex b/lib/sobelow/meta_log.ex index f753f39..fb83fe1 100644 --- a/lib/sobelow/meta_log.ex +++ b/lib/sobelow/meta_log.ex @@ -1,8 +1,10 @@ defmodule Sobelow.MetaLog do + @moduledoc false + use GenServer alias Sobelow.Parse - def start_link() do + def start_link do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) end @@ -10,7 +12,7 @@ defmodule Sobelow.MetaLog do GenServer.cast(__MODULE__, {:add, templates}) end - def get_templates() do + def get_templates do GenServer.call(__MODULE__, :get_templates) end diff --git a/lib/sobelow/misc.ex b/lib/sobelow/misc.ex index 446eab3..421bec8 100644 --- a/lib/sobelow/misc.ex +++ b/lib/sobelow/misc.ex @@ -1,4 +1,16 @@ defmodule Sobelow.Misc do + @moduledoc """ + # Miscellaneous + + This suite of tests is to be a catch-all for + checks that don't fall neatly into the other + detection categories. + + Miscellaneous checks can be ignored with the + following command: + + $ mix sobelow -i Misc + """ @submodules [Sobelow.Misc.BinToTerm] use Sobelow.FindingType diff --git a/lib/sobelow/print.ex b/lib/sobelow/print.ex index dfe122a..6d8d501 100644 --- a/lib/sobelow/print.ex +++ b/lib/sobelow/print.ex @@ -159,7 +159,7 @@ defmodule Sobelow.Print do end end - def finding_break() do + def finding_break do "\n-----------------------------------------------\n" end diff --git a/lib/sobelow/rce.ex b/lib/sobelow/rce.ex index 3e9a426..4b4cb6a 100644 --- a/lib/sobelow/rce.ex +++ b/lib/sobelow/rce.ex @@ -22,7 +22,7 @@ defmodule Sobelow.RCE do end) end - def details() do + def details do @moduledoc end end diff --git a/lib/sobelow/rce/code_module.ex b/lib/sobelow/rce/code_module.ex index ae453d6..e286a85 100644 --- a/lib/sobelow/rce/code_module.ex +++ b/lib/sobelow/rce/code_module.ex @@ -1,4 +1,20 @@ defmodule Sobelow.RCE.CodeModule do + @moduledoc """ + # Code Execution in `eval` function + + Arbitrary strings passed to the `Code.eval_*` functions can be + executed as malicious code. + + Ensure the the code passed to the function is not user-controlled + or remove the function call completely. + + Read more about Elixir RCE here: + https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/sandboxing + + Code Execution checks can be ignored with the following command: + + $ mix sobelow -i RCE.CodeModule + """ @uid 15 @finding_type "RCE.CodeModule: Code execution in eval function" @@ -20,7 +36,7 @@ defmodule Sobelow.RCE.CodeModule do Parse.get_fun_vars_and_meta(fun, 0, code_fun, [:Code]) end - def details() do + def details do Sobelow.RCE.details() end end diff --git a/lib/sobelow/sql.ex b/lib/sobelow/sql.ex index e87eaad..93a8e69 100644 --- a/lib/sobelow/sql.ex +++ b/lib/sobelow/sql.ex @@ -25,7 +25,7 @@ defmodule Sobelow.SQL do end) end - def details() do + def details do @moduledoc end end diff --git a/lib/sobelow/sql/query.ex b/lib/sobelow/sql/query.ex index db43b28..0b9c60e 100644 --- a/lib/sobelow/sql/query.ex +++ b/lib/sobelow/sql/query.ex @@ -1,4 +1,16 @@ defmodule Sobelow.SQL.Query do + @moduledoc """ + # SQL Injection in Query + + This submodule of the `SQL` module checks for SQL injection + vulnerabilities through usage of the `Ecto.Adapters.SQL.query`. + + Ensure that the query is parameterized and not user-controlled. + + SQLi Query checks can be ignored with the following command: + + $ mix sobelow -i SQL.Query + """ @uid 17 @finding_type "SQL.Query: SQL injection" @@ -25,7 +37,7 @@ defmodule Sobelow.SQL.Query do Parse.get_fun_vars_and_meta(fun, 0, :query, :Repo) end - def details() do + def details do Sobelow.SQL.details() end end diff --git a/lib/sobelow/sql/stream.ex b/lib/sobelow/sql/stream.ex index 034c223..0694ab4 100644 --- a/lib/sobelow/sql/stream.ex +++ b/lib/sobelow/sql/stream.ex @@ -1,4 +1,16 @@ defmodule Sobelow.SQL.Stream do + @moduledoc """ + # SQL Injection in Stream + + This submodule of the `SQL` module checks for SQL injection + vulnerabilities through usage of the `Ecto.Adapters.SQL.stream`. + + Ensure that the query is parameterized and not user-controlled. + + SQLi Stream checks can be ignored with the following command: + + $ mix sobelow -i SQL.Stream + """ @uid 18 @finding_type "SQL.Stream: SQL injection" @@ -17,7 +29,7 @@ defmodule Sobelow.SQL.Stream do Parse.get_fun_vars_and_meta(fun, 1, :stream, {:required, :SQL}) end - def details() do + def details do Sobelow.SQL.details() end end diff --git a/lib/sobelow/traversal.ex b/lib/sobelow/traversal.ex index 224d2a8..b786a47 100644 --- a/lib/sobelow/traversal.ex +++ b/lib/sobelow/traversal.ex @@ -30,7 +30,7 @@ defmodule Sobelow.Traversal do end) end - def details() do + def details do @moduledoc end end diff --git a/lib/sobelow/traversal/file_module.ex b/lib/sobelow/traversal/file_module.ex index e8f9f3f..f585806 100644 --- a/lib/sobelow/traversal/file_module.ex +++ b/lib/sobelow/traversal/file_module.ex @@ -1,4 +1,16 @@ defmodule Sobelow.Traversal.FileModule do + @moduledoc """ + # Directory Traversal in `File` function + + This submodule checks for directory traversal vulnerabilities in the `File` + module. + + Ensure that the path passed to `File` functions is not user-controlled. + + File checks can be ignored with the following command: + + $ mix sobelow -i Traversal.FileModule + """ @uid 19 @finding_type "Traversal.FileModule: Directory Traversal in `File` function" @@ -51,7 +63,7 @@ defmodule Sobelow.Traversal.FileModule do Parse.get_fun_vars_and_meta(fun, 1, type, [:File]) end - def details() do + def details do Sobelow.Traversal.details() end end diff --git a/lib/sobelow/traversal/send_download.ex b/lib/sobelow/traversal/send_download.ex index 198caff..a668c37 100644 --- a/lib/sobelow/traversal/send_download.ex +++ b/lib/sobelow/traversal/send_download.ex @@ -1,4 +1,16 @@ defmodule Sobelow.Traversal.SendDownload do + @moduledoc """ + # Directory Traversal in `send_download` + + This submodule checks for directory traversal vulnerabilities in the + `send_download` function of a Phoenix Controller. + + Ensure that the path passed to `send_download` is not user-controlled. + + Send Download checks can be ignored with the following command: + + $ mix sobelow -i Traversal.SendDownload + """ @uid 20 @finding_type "Traversal.SendDownload: Directory Traversal in `send_download`" @@ -25,7 +37,7 @@ defmodule Sobelow.Traversal.SendDownload do {findings, params, {fun_name, line_no}} end - def details() do + def details do Sobelow.Traversal.details() end diff --git a/lib/sobelow/traversal/send_file.ex b/lib/sobelow/traversal/send_file.ex index 10fd1c1..6618a42 100644 --- a/lib/sobelow/traversal/send_file.ex +++ b/lib/sobelow/traversal/send_file.ex @@ -1,4 +1,16 @@ defmodule Sobelow.Traversal.SendFile do + @moduledoc """ + # Directory Traversal in `send_file` + + This submodule checks for directory traversal vulnerabilities in the + `send_file` function. + + Ensure that the path passed to `send_file` is not user-controlled. + + Send File checks can be ignored with the following command: + + $ mix sobelow -i Traversal.SendFile + """ @uid 21 @finding_type "Traversal.SendFile: Directory Traversal in `send_file`" @@ -17,7 +29,7 @@ defmodule Sobelow.Traversal.SendFile do Parse.get_fun_vars_and_meta(fun, 2, :send_file, :Conn) end - def details() do + def details do Sobelow.Traversal.details() end end diff --git a/lib/sobelow/utils.ex b/lib/sobelow/utils.ex index bbe5a03..f42b7fa 100644 --- a/lib/sobelow/utils.ex +++ b/lib/sobelow/utils.ex @@ -101,7 +101,7 @@ defmodule Sobelow.Utils do end end - def get_root() do + def get_root do root = Sobelow.get_env(:root) if is_nil(root), do: "", else: root end diff --git a/lib/sobelow/vuln.ex b/lib/sobelow/vuln.ex index 9dbd2ac..402b45f 100644 --- a/lib/sobelow/vuln.ex +++ b/lib/sobelow/vuln.ex @@ -18,7 +18,7 @@ defmodule Sobelow.Vuln do Sobelow.Vuln.Ecto ] - alias Sobelow.{Finding, Utils, Print} + alias Sobelow.{Finding, Print, Utils} use Sobelow.FindingType def get_vulns(root) do @@ -76,7 +76,7 @@ defmodule Sobelow.Vuln do end end - def details() do + def details do @moduledoc end end diff --git a/lib/sobelow/vuln/coherence.ex b/lib/sobelow/vuln/coherence.ex index e860120..fd7421c 100644 --- a/lib/sobelow/vuln/coherence.ex +++ b/lib/sobelow/vuln/coherence.ex @@ -1,4 +1,14 @@ defmodule Sobelow.Vuln.Coherence do + @moduledoc """ + # Coherence Version Vulnerable to Privilege Escalation + + For more information visit: + https://github.com/advisories/GHSA-mrq8-53r4-3j5m + + Coherence checks can be ignored with the following command: + + $ mix sobelow -i Vuln.Coherence + """ alias Sobelow.Config alias Sobelow.Vuln @@ -23,7 +33,7 @@ defmodule Sobelow.Vuln.Coherence do vsn, "Coherence", "Permissive parameters and privilege escalation", - "TBA - https://github.com/smpallen99/coherence/issues/270", + "CVE-2018-20301", "Coherence" ) end @@ -34,7 +44,7 @@ defmodule Sobelow.Vuln.Coherence do end end - def details() do + def details do Sobelow.Vuln.details() end end diff --git a/lib/sobelow/vuln/cookie_rce.ex b/lib/sobelow/vuln/cookie_rce.ex index 8e9f816..5fdfb32 100644 --- a/lib/sobelow/vuln/cookie_rce.ex +++ b/lib/sobelow/vuln/cookie_rce.ex @@ -1,4 +1,14 @@ defmodule Sobelow.Vuln.CookieRCE do + @moduledoc """ + # Plug Version Vulnerable to Arbitrary Code Execution in Cookie Serialization + + For more information visit: + https://github.com/advisories/GHSA-5v4m-c73v-c7gq + + Cookie RCE checks can be ignored with the following command: + + $ mix sobelow -i Vuln.CookieRCE + """ alias Sobelow.Config alias Sobelow.Vuln @@ -7,6 +17,9 @@ defmodule Sobelow.Vuln.CookieRCE do use Sobelow.Finding + # we could _probably_ remove some of these versions since if Sobelow is running, + # it means there is a minimum version of Elixir on the system which the lower + # versions of Plug wouldn't support - will leave for now to reflect CVE @vuln_vsn ~w(1.3.1 1.3.0 1.2.2 1.2.1 1.2.0 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.3 1.0.2 1.0.1 1.0.0) def run(root) do @@ -28,7 +41,7 @@ defmodule Sobelow.Vuln.CookieRCE do end end - def details() do + def details do Sobelow.Vuln.details() end end diff --git a/lib/sobelow/vuln/ecto.ex b/lib/sobelow/vuln/ecto.ex index 4ce3617..0648e08 100644 --- a/lib/sobelow/vuln/ecto.ex +++ b/lib/sobelow/vuln/ecto.ex @@ -1,4 +1,14 @@ defmodule Sobelow.Vuln.Ecto do + @moduledoc """ + # Ecto Version Lacks Protection Mechanism + + For more information visit: + https://github.com/advisories/GHSA-2xxx-fhc8-9qvq + + Ecto checks can be ignored with the following command: + + $ mix sobelow -i Vuln.Ecto + """ alias Sobelow.Config alias Sobelow.Vuln @@ -23,7 +33,7 @@ defmodule Sobelow.Vuln.Ecto do vsn, "Ecto", "Missing `is_nil` requirement", - "TBA - https://groups.google.com/forum/#!topic/elixir-ecto/0m4NPfg_MMU", + "CVE-2017-20166", "Ecto" ) end @@ -34,7 +44,7 @@ defmodule Sobelow.Vuln.Ecto do end end - def details() do + def details do Sobelow.Vuln.details() end end diff --git a/lib/sobelow/vuln/header_inject.ex b/lib/sobelow/vuln/header_inject.ex index 389f717..e419710 100644 --- a/lib/sobelow/vuln/header_inject.ex +++ b/lib/sobelow/vuln/header_inject.ex @@ -1,4 +1,14 @@ defmodule Sobelow.Vuln.HeaderInject do + @moduledoc """ + # Plug Version Vulnerable to Header Injection + + For more information visit: + https://github.com/advisories/GHSA-9h73-w7ch-rh73 + + Header Injection checks can be ignored with the following command: + + $ mix sobelow -i Vuln.HeaderInject + """ alias Sobelow.Config alias Sobelow.Vuln @@ -7,6 +17,9 @@ defmodule Sobelow.Vuln.HeaderInject do use Sobelow.Finding + # we could _probably_ remove some of these versions since if Sobelow is running, + # it means there is a minimum version of Elixir on the system which the lower + # versions of Plug wouldn't support - will leave for now to reflect CVE @vuln_vsn ["<=1.3.4 and >=1.3.0", "<=1.2.4 and >=1.2.0", "<=1.1.8 and >=1.1.0", "<=1.0.5"] def run(root) do @@ -18,7 +31,14 @@ defmodule Sobelow.Vuln.HeaderInject do case Version.parse(vsn) do {:ok, vsn} -> if Enum.any?(@vuln_vsn, fn v -> Version.match?(vsn, v) end) do - Vuln.print_finding(plug_conf, vsn, "Plug", "Header Injection", "HeaderInject") + Vuln.print_finding( + plug_conf, + vsn, + "Plug", + "Header Injection", + "CVE-2018-1000883", + "HeaderInject" + ) end _ -> @@ -27,7 +47,7 @@ defmodule Sobelow.Vuln.HeaderInject do end end - def details() do + def details do Sobelow.Vuln.details() end end diff --git a/lib/sobelow/vuln/plug_null.ex b/lib/sobelow/vuln/plug_null.ex index 5729b32..33cbe67 100644 --- a/lib/sobelow/vuln/plug_null.ex +++ b/lib/sobelow/vuln/plug_null.ex @@ -1,4 +1,14 @@ defmodule Sobelow.Vuln.PlugNull do + @moduledoc """ + # Plug Version Vulnerable to Null Byte Injection + + For more information visit: + https://github.com/advisories/GHSA-2q6v-32mr-8p8x + + Null Byte Injection checks can be ignored with the following command: + + $ mix sobelow -i Vuln.PlugNull + """ alias Sobelow.Config alias Sobelow.Vuln @@ -7,6 +17,9 @@ defmodule Sobelow.Vuln.PlugNull do use Sobelow.Finding + # we could _probably_ remove some of these versions since if Sobelow is running, + # it means there is a minimum version of Elixir on the system which the lower + # versions of Plug wouldn't support - will leave for now to reflect CVE @vuln_vsn ~w(1.3.1 1.3.0 1.2.2 1.2.1 1.2.0 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.3 1.0.2 1.0.1 1.0.0) def run(root) do @@ -28,7 +41,7 @@ defmodule Sobelow.Vuln.PlugNull do end end - def details() do + def details do Sobelow.Vuln.details() end end diff --git a/lib/sobelow/vuln/redirect.ex b/lib/sobelow/vuln/redirect.ex index 186c79d..aab39c0 100644 --- a/lib/sobelow/vuln/redirect.ex +++ b/lib/sobelow/vuln/redirect.ex @@ -1,4 +1,14 @@ defmodule Sobelow.Vuln.Redirect do + @moduledoc """ + # Phoenix Version Vulnerable to Arbitrary URL Redirection + + For more information visit: + https://github.com/advisories/GHSA-cmfh-8f8r-fj96 + + URL Redirection checks can be ignored with the following command: + + $ mix sobelow -i Vuln.Redirect + """ alias Sobelow.Config alias Sobelow.Vuln @@ -7,6 +17,9 @@ defmodule Sobelow.Vuln.Redirect do use Sobelow.Finding + # we could _probably_ remove some of these versions since if Sobelow is running, + # it means there is a minimum version of Elixir on the system which the lower + # versions of Phoenix wouldn't support - will leave for now to reflect CVE @vuln_vsn ~w(1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.2.0 1.2.1 1.3.0-rc.0) def run(root) do @@ -16,12 +29,19 @@ defmodule Sobelow.Vuln.Redirect do vsn = Config.get_version(plug_conf) if Enum.member?(@vuln_vsn, vsn) do - Vuln.print_finding(plug_conf, vsn, "Phoenix", "Arbitrary URL Redirect", "Redirect") + Vuln.print_finding( + plug_conf, + vsn, + "Phoenix", + "Arbitrary URL Redirect", + "CVE-2017-1000163", + "Redirect" + ) end end end - def details() do + def details do Sobelow.Vuln.details() end end diff --git a/lib/sobelow/xss.ex b/lib/sobelow/xss.ex index 228065c..192326e 100644 --- a/lib/sobelow/xss.ex +++ b/lib/sobelow/xss.ex @@ -48,7 +48,7 @@ defmodule Sobelow.XSS do end end - def details() do + def details do @moduledoc end end diff --git a/lib/sobelow/xss/html.ex b/lib/sobelow/xss/html.ex index 34a19ad..d6aed2f 100644 --- a/lib/sobelow/xss/html.ex +++ b/lib/sobelow/xss/html.ex @@ -1,4 +1,14 @@ defmodule Sobelow.XSS.HTML do + @moduledoc """ + # XSS in `html` + + This submodule looks for XSS vulnerabilities in `html` + calls from the Phoenix Controller. + + HTML checks can be ignored with the following command: + + $ mix sobelow -i XSS.HTML + """ @uid 29 @finding_type "XSS.HTML: XSS in `html`" @@ -16,7 +26,7 @@ defmodule Sobelow.XSS.HTML do Parse.get_fun_vars_and_meta(fun, 1, :html, :Controller) end - def details() do + def details do Sobelow.XSS.details() end end diff --git a/lib/sobelow/xss/raw.ex b/lib/sobelow/xss/raw.ex index 5300bc7..32251c5 100644 --- a/lib/sobelow/xss/raw.ex +++ b/lib/sobelow/xss/raw.ex @@ -1,4 +1,14 @@ defmodule Sobelow.XSS.Raw do + @moduledoc """ + # XSS in `raw` + + This submodule checks for the use of `raw` in templates + as this can lead to XSS vulnerabilities if taking user input. + + Raw checks can be ignored with the following command: + + $ mix sobelow -i XSS.Raw + """ @uid 30 @finding_type "XSS.Raw: XSS" @@ -101,7 +111,7 @@ defmodule Sobelow.XSS.Raw do Parse.get_fun_vars_and_meta(fun, 0, :raw, :HTML) end - def details() do + def details do Sobelow.XSS.details() end diff --git a/lib/sobelow/xss/send_resp.ex b/lib/sobelow/xss/send_resp.ex index 9801c30..e61d2e7 100644 --- a/lib/sobelow/xss/send_resp.ex +++ b/lib/sobelow/xss/send_resp.ex @@ -1,4 +1,14 @@ defmodule Sobelow.XSS.SendResp do + @moduledoc """ + # XSS in `send_resp` + + This submodule looks for XSS vulnerabilities in the `body` + argument of `Conn.send_resp`. + + SendResp checks can be ignored with the following command: + + $ mix sobelow -i XSS.SendResp + """ @uid 31 @finding_type "XSS.SendResp: XSS in `send_resp`" @@ -16,7 +26,7 @@ defmodule Sobelow.XSS.SendResp do Parse.get_fun_vars_and_meta(fun, 2, :send_resp, :Conn) end - def details() do + def details do Sobelow.XSS.details() end @@ -39,7 +49,7 @@ defmodule Sobelow.XSS.SendResp do defp get_confidence(finding, content_types) do cond do - length(content_types) == 0 -> + Enum.empty?(content_types) -> finding.confidence Enum.any?(content_types, &(!is_binary(&1))) -> diff --git a/mix.exs b/mix.exs index 6f2132c..f2105af 100644 --- a/mix.exs +++ b/mix.exs @@ -8,7 +8,7 @@ defmodule Sobelow.Mixfile do [ app: :sobelow, version: @version, - elixir: "~> 1.5", + elixir: "~> 1.7", build_embedded: Mix.env() == :prod, start_permanent: Mix.env() == :prod, deps: deps(), @@ -17,6 +17,7 @@ defmodule Sobelow.Mixfile do name: "Sobelow", homepage_url: "https://sobelow.io", docs: docs(), + aliases: aliases(), escript: [main_module: Mix.Tasks.Sobelow] ] end @@ -27,12 +28,16 @@ defmodule Sobelow.Mixfile do defp deps do [ + # "Prod" Dependencies + {:jason, "~> 1.0"}, + + # Dev / Test Dependencies {:ex_doc, "~> 0.20", only: :dev}, - {:jason, "~> 1.0"} + {:credo, "~> 1.6 or ~> 1.7", only: [:dev, :test], runtime: false} ] end - defp package() do + defp package do [ licenses: ["Apache-2.0"], maintainers: ["Griffin Byatt", "Holden Oullette"], @@ -43,7 +48,7 @@ defmodule Sobelow.Mixfile do ] end - defp docs() do + defp docs do [ main: "readme", source_url: @source_url, @@ -51,4 +56,16 @@ defmodule Sobelow.Mixfile do extras: ["README.md", "CHANGELOG.md"] ] end + + defp aliases do + [ + "test.all": [ + "hex.audit", + "format --check-formatted", + "compile --warnings-as-errors", + "deps.unlock --check-unused", + "credo --all --strict" + ] + ] + end end diff --git a/mix.lock b/mix.lock index 48e89a0..4f56b97 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,9 @@ %{ + "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, diff --git a/test/config/csrf_route_test.exs b/test/config/csrf_route_test.exs index a1d366a..c1e79d1 100644 --- a/test/config/csrf_route_test.exs +++ b/test/config/csrf_route_test.exs @@ -63,7 +63,7 @@ defmodule SobelowTest.Config.CSRFRouteTest do |> CSRFRoute.combine_scopes() |> Enum.flat_map(&CSRFRoute.route_findings(&1, %Finding{})) - assert length(vulns) == 0 + assert Enum.empty?(vulns) end test "flags vulnerable routes in nested scopes" do diff --git a/test/fixtures/csp/bad_router.ex b/test/fixtures/csp/bad_router.ex index 6ae0426..c967074 100644 --- a/test/fixtures/csp/bad_router.ex +++ b/test/fixtures/csp/bad_router.ex @@ -1,4 +1,6 @@ defmodule BadRouter do + @moduledoc false + pipeline :browser do plug(:accepts, ["html"]) plug(:put_secure_browser_headers) diff --git a/test/fixtures/csp/bad_router_attr.ex b/test/fixtures/csp/bad_router_attr.ex index d91a78a..2e1e85b 100644 --- a/test/fixtures/csp/bad_router_attr.ex +++ b/test/fixtures/csp/bad_router_attr.ex @@ -1,4 +1,6 @@ defmodule BadRouter do + @moduledoc false + @csp %{"Not-Content-Security-Policy" => "default-src 'self'"} pipeline :browser do diff --git a/test/fixtures/csp/good_router.ex b/test/fixtures/csp/good_router.ex index 873d01c..767c40e 100644 --- a/test/fixtures/csp/good_router.ex +++ b/test/fixtures/csp/good_router.ex @@ -1,4 +1,6 @@ defmodule GoodRouter do + @moduledoc false + pipeline :browser do plug(:accepts, ["html"]) plug(:put_secure_browser_headers, %{"content-security-policy" => "default-src 'self'"}) diff --git a/test/fixtures/csp/good_router_attr.ex b/test/fixtures/csp/good_router_attr.ex index df41b9c..66e8c39 100644 --- a/test/fixtures/csp/good_router_attr.ex +++ b/test/fixtures/csp/good_router_attr.ex @@ -1,4 +1,6 @@ defmodule GoodRouter do + @moduledoc false + @csp %{"content-security-policy" => "default-src 'self'"} pipeline :browser do diff --git a/test/fixtures/csrf/bad_router.ex b/test/fixtures/csrf/bad_router.ex index 6e20d48..b29c5a3 100644 --- a/test/fixtures/csrf/bad_router.ex +++ b/test/fixtures/csrf/bad_router.ex @@ -1,5 +1,7 @@ # Router is missing plug :protect_from_forgery defmodule BadRouter do + @moduledoc false + pipeline :browser do plug(:accepts, ["html"]) plug(:fetch_session) diff --git a/test/fixtures/csrf/good_router.ex b/test/fixtures/csrf/good_router.ex index 86cd32f..f0ebb2b 100644 --- a/test/fixtures/csrf/good_router.ex +++ b/test/fixtures/csrf/good_router.ex @@ -1,4 +1,6 @@ defmodule GoodRouter do + @moduledoc false + pipeline :browser do plug(:accepts, ["html"]) plug(:protect_from_forgery) diff --git a/test/fixtures/csrf/good_router_with_session_key.ex b/test/fixtures/csrf/good_router_with_session_key.ex index ee03bc1..922c49b 100644 --- a/test/fixtures/csrf/good_router_with_session_key.ex +++ b/test/fixtures/csrf/good_router_with_session_key.ex @@ -1,4 +1,6 @@ defmodule GoodRouterWithSessionKey do + @moduledoc false + pipeline :browser do plug(:accepts, ["html"]) plug(:protect_from_forgery, session_key: "_phoenix_csrf_token") diff --git a/test/fixtures/csrf/good_router_with_with.ex b/test/fixtures/csrf/good_router_with_with.ex index 7f76935..0cbb812 100644 --- a/test/fixtures/csrf/good_router_with_with.ex +++ b/test/fixtures/csrf/good_router_with_with.ex @@ -1,4 +1,6 @@ defmodule GoodRouterWithWith do + @moduledoc false + pipeline :browser do plug(:accepts, ["html"]) plug(:protect_from_forgery, with: :clear_session) diff --git a/test/fixtures/cswh/bad_endpoint.ex b/test/fixtures/cswh/bad_endpoint.ex index a160fd0..7618945 100644 --- a/test/fixtures/cswh/bad_endpoint.ex +++ b/test/fixtures/cswh/bad_endpoint.ex @@ -1,4 +1,6 @@ defmodule PhoenixWeb.Endpoint do + @moduledoc false + use Phoenix.Endpoint, otp_app: :phoenix socket( diff --git a/test/fixtures/cswh/good_endpoint.ex b/test/fixtures/cswh/good_endpoint.ex index ce20882..673b207 100644 --- a/test/fixtures/cswh/good_endpoint.ex +++ b/test/fixtures/cswh/good_endpoint.ex @@ -1,4 +1,6 @@ defmodule PhoenixWeb.Endpoint do + @moduledoc false + use Phoenix.Endpoint, otp_app: :phoenix socket("/socket", PhoenixInternalsWeb.UserSocket, websocket: true, longpoll: false) diff --git a/test/fixtures/cswh/good_endpoint_2.ex b/test/fixtures/cswh/good_endpoint_2.ex index 4d2e434..ae92e9f 100644 --- a/test/fixtures/cswh/good_endpoint_2.ex +++ b/test/fixtures/cswh/good_endpoint_2.ex @@ -1,4 +1,6 @@ defmodule PhoenixWeb.Endpoint do + @moduledoc false + use Phoenix.Endpoint, otp_app: :phoenix socket("/socket", PhoenixInternalsWeb.UserSocket, diff --git a/test/fixtures/cswh/soso_endpoint.ex b/test/fixtures/cswh/soso_endpoint.ex index 88ab714..fac8279 100644 --- a/test/fixtures/cswh/soso_endpoint.ex +++ b/test/fixtures/cswh/soso_endpoint.ex @@ -1,4 +1,6 @@ defmodule PhoenixWeb.Endpoint do + @moduledoc false + use Phoenix.Endpoint, otp_app: :phoenix socket( diff --git a/test/fixtures/headers/bad_router.ex b/test/fixtures/headers/bad_router.ex index a6d2b9a..bba86ed 100644 --- a/test/fixtures/headers/bad_router.ex +++ b/test/fixtures/headers/bad_router.ex @@ -1,4 +1,6 @@ defmodule BadRouter do + @moduledoc false + pipeline :browser do plug(:accepts, ["html"]) plug(:protect_from_forgery) diff --git a/test/fixtures/headers/good_router.ex b/test/fixtures/headers/good_router.ex index 7862f1f..bb9ce37 100644 --- a/test/fixtures/headers/good_router.ex +++ b/test/fixtures/headers/good_router.ex @@ -1,4 +1,6 @@ defmodule GoodRouter do + @moduledoc false + pipeline :browser do plug(:accepts, ["html"]) plug(:put_secure_browser_headers) diff --git a/test/fixtures/headers/good_router_with_headers.ex b/test/fixtures/headers/good_router_with_headers.ex index b2a9e6c..b12766a 100644 --- a/test/fixtures/headers/good_router_with_headers.ex +++ b/test/fixtures/headers/good_router_with_headers.ex @@ -1,4 +1,6 @@ defmodule GoodRouter do + @moduledoc false + pipeline :browser do plug(:accepts, ["html"]) plug(:put_secure_browser_headers, %{"additional" => "header"})