Sobelow is a security-focused static analysis tool for the Phoenix framework. It is produced by NCC Group, a cyber-security firm whose roots go back to June 1999.

At the time of this writing, the current version is 0.3.6 and it has been gaining traction in the Elixir/Phoenix communities.

Why Run Security Scans?

First, it should be a matter of personal responsibility to your product and your users that you try to protect your application and your user’s data.

Second, various certifications like PCI Compliance can require things like automated static-analysis security scans or security code reviews of all changes to the system.

Third, what coding habits do you have that might be a security risk? Sobelow can be that third-party reviewer to challenge your long-held assumptions.

I work at a fast-paced FinTech startup with agile workflows. The best option is to have security scans automatically performed on PRs before they are merged. Some benefits to having them automated is you don’t forget to run the checks and it helps identify new code changes that could have security problems.

Install Options

The project recommends installing it outside of the project to be scanned like this.

$ mix archive.install hex sobelow

This works well when you want to scan other people’s projects without modifying their dependencies.

It also works to add it to your project’s mix.exs if that makes more sense for your situation.

def deps do
  [{:sobelow, "~> 0.3", only: [:dev]}]
end

Running a Scan

Running your first scan is easy.

$ mix sobelow

There are a number of command-line options and new options were recently added so it is worth checking for current documentation.

We Need Code to Scan!

I did a quick Github search for OpenSource Phoenix applications that I could use for this post and found one under active development. This will work perfectly!

Setup steps before we can test sobelow on the project.

  • Clone the project locally
  • Make it able to compile
  • Nothing more needed! It’s “static-analysis” remember?

Run sobelow on the project…

    $ mix sobelow

    ##############################################
    #                                            #
    #          Running Sobelow - v0.3.6          #
    #  Created by Griffin Byatt - @griffinbyatt  #
    #     NCC Group - https://nccgroup.trust     #
    #                                            #
    ##############################################

    Missing CSRF Protections - High Confidence
    Pipeline: browser:4

    -----------------------------------------------

    HTTPS Not Enabled - High Confidence

    -----------------------------------------------

    Known Vulnerable Dependency - Plug v1.3.4

    -----------------------------------------------

    Known Vulnerable Dependency - Phoenix v1.2.1

    -----------------------------------------------

    Directory Traversal in `File.read!` - High Confidence
    File: web/controllers/contact_controller.ex - create_nested_data:175
    Variable: temp_file

    -----------------------------------------------

    Directory Traversal in `File.rm!` - High Confidence
    File: web/controllers/contact_controller.ex - create_nested_data:175
    Variable: temp_file

    -----------------------------------------------

    Directory Traversal in `File.read!` - High Confidence
    File: web/controllers/contact_controller.ex - view_uploaded_data:159
    Variable: temp_file

    -----------------------------------------------

    Directory Traversal in `File.read!` - Medium Confidence
    File: web/controllers/contact_controller.ex - import_data:135
    Variable: temp_file

    -----------------------------------------------

    Directory Traversal in `File.rm!` - Medium Confidence
    File: web/controllers/contact_controller.ex - import_data:135
    Variable: temp_file

    -----------------------------------------------

    Insecure use of `File` and `Path` - Low Confidence
    File: web/controllers/contact_controller.ex - import_data:135
    Variable: upload

    -----------------------------------------------

    ... SCAN COMPLETE ...

Note: Unfortunately, the color coding doesn’t translate to my copy of the text here. So just note that it is sorted with “High Confidence” items higher up then descending to “Medium” and “Low”. The colors change accordingly.

Interesting output! Now what does it mean? It is worth noting what the Sobelow project says about “False Positives”:

Sobelow favors over-reporting versus under-reporting. As such, you may find a number of false positives in a typical scan. These findings may be individually ignored by adding a @sobelow_skip mark, along with a list of modules, before the function definition.

So we need to check them personally and determine if it is a problem or not.

Let’s try out the command-line option --with-code and see if that helps give context to the warnings. I won’t include the full output for brevity’s sake.

    Missing CSRF Protections - High Confidence
    Pipeline: browser:4


    pipeline(:browser) do
      plug(:accepts, ["html"])
      plug(:fetch_session)
      plug(:fetch_flash)
      plug(:put_secure_browser_headers)
    end

I double-checked the router file and yes, it is missing the plug :protect_from_forgery call you would otherwise expect.

    Known Vulnerable Dependency - Plug v1.3.4
    Details: Header Injection
    CVE: TBA

    -----------------------------------------------

    Known Vulnerable Dependency - Phoenix v1.2.1
    Details: Arbitrary URL Redirect
    CVE: TBA

Well that’s nice! A recent sobelow feature is checking for known vulnerabilities in the project’s dependencies. And yes, the project’s mix.exs file is pinned to an older phoenix version. Time to upgrade!

{:phoenix, "~> 1.2.1"}

Here’s another one with a good code sample.

    Directory Traversal in `File.read!` - High Confidence
    File: web/controllers/contact_controller.ex - view_uploaded_data:159
    Variable: temp_file

    def(view_uploaded_data(conn, %{"mapping" => mapping, "temp_file" => temp_file})) do
      table = File.read!("tmp/#{temp_file}.csv") |> ExCsv.parse!() |> ExCsv.with_headings() |> Enum.to_list()
      total_rows = Enum.count(table) - 1
      contact_headers = Map.keys(mapping["contact"])
      organization_headers = Map.keys(mapping["organization"])
      first_row = Enum.at(table, 0)
      contact_values = for({db_col, csv_col} <- mapping["contact"]) do
        first_row[csv_col]
      end
      organization_values = for({db_col, csv_col} <- mapping["organization"]) do
        first_row[csv_col]
      end
      json(conn, %{contact_headers: contact_headers, organization_headers: organization_headers, contact_values: contact_values, organization_values: organization_values, temp_file: temp_file})
    end

Again, the coloring doesn’t come across here. But the code being pointed out is this…

File.read!("tmp/#{temp_file}.csv")

After checking the router, it is clear that this is indeed taking user submitted data and interpolating that text into a command-line path. This means submitting a “temp_file” value like “../somewhere/else” poses a risk of accessing data from anywhere on disk. I verified in the router that in order to even make this call, you must be an authenticated user. This prompts the question, “How much do you trust all of your users?”

I notified the project on Github of the issues and they have already addressed them.

It is also interesting how sobelow displays the function as def(view_uploaded_data(...)). Looking at the actual file, it was as you’d expect with def view_uploaded_data(...). I guess that’s exposing the reality that def is an Elixir macro. :)

Now you’ve seen some real-world examples of how sobelow can help your project.

Umbrella Projects

Sobelow is only designed for scanning Phoenix applications. So it doesn’t scan a standard mix new project. I follow the pattern where my projects are umbrella applications with the Phoenix project being generated without Ecto. This really reduces the utility of sobelow.

Running it from the root of my project it returns this…

$ mix sobelow
This does not appear to be a Phoenix application.

Luckily, the --root option lets me specify the path to the Phoenix app in my project.

    $ mix sobelow --root apps/my_web/

    ##############################################
    #                                            #
    #          Running Sobelow - v0.3.6          #
    #  Created by Griffin Byatt - @griffinbyatt  #
    #     NCC Group - https://nccgroup.trust     #
    #                                            #
    ##############################################

    HTTPS Not Enabled - High Confidence

    -----------------------------------------------

    ... SCAN COMPLETE ...

It’s totally true. My personal project that lives only on my machine hasn’t been setup for HTTPS yet. But, it’s a nice reminder that this should be a priority before I deploy outside of my home.

But it found nothing else. Since all my Ecto queries are done in other apps where the business logic lives, how comfortable should I be that this didn’t find anything? I’m not sure. Sobelow has been advancing quickly recently. I’m hopeful that it will grow in sophistication over time.

Work Project Scan Results

I started using Sobelow on our backend system at work. Now, I got the same “HTTPS Not Enabled”. However, in this case it isn’t a problem. Our production deployment has the web application behind load-balancers that are terminating the HTTPS connection. So our app doesn’t have that responsibility. How do we tell Sobelow this is okay?

Disable a Check

To disable the “HTTPS Not Enabled” check, I searched the sobelow project code for some error text and found it easily enough.

This one is not a specific line of code to disable, it is turning off the check. The -i is to “ignore” that check.

$ mix sobelow -i Config.HTTPS

Valid Finds

Sobelow found a valid “Unsafe String.to_atom - High Confidence” issue. At work, many of the backend developers are new to Elixir. You can’t expect everyone to be familiar with the details of a new language and its runtime. A member of our team took client provided raw param data and performed String.to_atom on it. Atoms in Elixir are like Symbols in Ruby. Elixir atoms are not garbage collected. This allows a malicious user to arbitrarily create them through an HTTP POST which can be abused to consume system resources until the server crashes. Resulting in a “denial of service” attack.

This find lets us have an “education moment”. So it works!

Automated Scanning

The option --exit returns a non-zero return code when problems are found. This makes it easy for an automated system to “fail” the process when a new vulnerability is introduced.

From the docs:

The exit option accepts a confidence threshold (low, medium, or high), and will return a non-zero exit status at or above that threshold.

$ mix sobelow --exit Low

So our project at work can run the tests on the multiple phoenix applications in the umbrella and automatically fail the code checks when new problems are introduced.

Conclusion

Sobelow is the first static-analysis security scanner for Phoenix that I’m aware of. I looked around a lot. At work, we have PCI Compliance requirements in addition to our desire to have a completely secure system. Sobelow came along at the perfect time for us. It has become part of our automated processes for testing code quality.

Give it a try on your Phoenix projects and share your results!