sobelow icon indicating copy to clipboard operation
sobelow copied to clipboard

Security checks are run against raw string AST instead against compiled AST

Open skylerparr opened this issue 4 years ago • 3 comments

Due to the way sobelow checks for vulnerabilities, it reads the raw code into a string and converts the string code to elixir AST: https://github.com/nccgroup/sobelow/blob/26589a56dcc6b688d107704ecc6ab8ab437668b0/lib/sobelow/config.ex#L80 https://github.com/nccgroup/sobelow/blob/8702702444397a7df7d8ffeeac3678247519ef06/lib/sobelow/parse.ex#L41

Meaning that it analyzes the raw code as a string and not the final compiled code.

Here is a trivial example that would bypass the XSS checks:

defmodule RouterHack do
    quote do
      plug(:accepts, ["html"])
      plug(:fetch_session)
      plug(:fetch_flash)
      plug(:put_secure_browser_headers)
    end
  end
end

defmodule MyApp.Router do
  use MyApp.Web, :router

  pipeline :browser do
    require RouterHack
    RouterHack.get_browser_pipeline()
  end
end

Since sobelow is only checking the original code as a string, this macro is completely unchecked and is allowing the security vulnerability.

This is an easy way to bypass the sobelow security checks, and macros can live pretty unchecked. With a language like Elixir; which is macro heavy, really devalues what sobelow provides.

I recommend investigating a way to inspect the generated beam files (as the dialyzer works) or see if the core team can add a function that returns the generated AST before beam file generation (if there isn't one already. I dug around for an example and I couldn't find anything, but that doesn't mean it doesn't exist).

skylerparr avatar Jun 11 '20 15:06 skylerparr

I agree that some kind of macro expansion would be nice. Though, I think operating on beam files directly would end up being a worse experience, since you may lose the context you get from operating on AST directly.

This is something I toy with from time to time, but it isn't high priority. The vast majority of the time, with common and semi-common configurations, the lack of expansion will result in false positives rather than false negatives. And someone deliberately attempting to bypass the security checks is not within the scope of the tool.

Aside - in your example, which XSS checks are being bypassed? I think the outcome from your example would be a false positive about secure headers, and a false negative for CSRF.

GriffinMB avatar Jun 12 '20 02:06 GriffinMB

Haha. Sorry about that, I gave you a pretty out of context example unless you memorized the phoenix plugs. The example is missing the plug(:protect_from_forgery) plug.

skylerparr avatar Jun 12 '20 17:06 skylerparr

This is the reason why Sobelow crashes when you use function calls to extend list of socket options, eg:

socket "/gateway", API.Gateway.Socket, API.Sockets.options()

leads to

* (FunctionClauseError) no function clause matching in Sobelow.Config.CSWH.check_socket_options/1    
    
    The following arguments were given to Sobelow.Config.CSWH.check_socket_options/1:
    
        # 1
        {{:., [line: 25, column: 49], [{:__aliases__, [line: 25, column: 38], [:API, :Sockets]}, :options]}, [line: 25, column: 50], []}
    
    Attempted function clauses (showing 3 out of 3):
    
        defp check_socket_options([{:websocket, options} | _]) when is_list(options)
        defp check_socket_options([_ | t])
        defp check_socket_options([])
    
    lib/sobelow/config/cswh.ex:40: Sobelow.Config.CSWH.check_socket_options/1
    lib/sobelow/config/cswh.ex:29: anonymous fn/2 in Sobelow.Config.CSWH.handle_sockets/2
    (elixir 1.14.4) lib/enum.ex:975: Enum."-each/2-lists^foreach/1-0-"/2
    lib/sobelow.ex:94: Sobelow.run/0
    (mix 1.14.4) lib/mix/task.ex:421: anonymous fn/3 in Mix.Task.run_task/4
    (mix 1.14.4) lib/mix/cli.ex:84: Mix.CLI.run_task/2

AndrewDryga avatar Jun 14 '23 19:06 AndrewDryga