CVE-2019-16764 (published 2019-11-25T17:15Z)

The use of `String.to_atom/1` in PowAssent is susceptible to denial of service attacks. In `PowAssent.Phoenix.AuthorizationController` a value is fetched from the user provided params, and `String.to_atom/1` is used to convert the binary value to an atom so it can be used to fetch the provider configuration value. This is unsafe as it is user provided data, and can be used to fill up the whole atom table of ~1M which will cause the app to crash.

Severity: LOW
CVSS v2 base score: 2.1 (AV:L/AC:L/Au:N/C:N/I:N/A:P)
CVSS v3 base score: 5.5, MEDIUM (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/pow-auth/pow_assent
commit_details: repo_url: https://github.com/pow-auth/pow_assent
author: Dan Schultzer
author_date: 2019-11-22 11:21:24-08:00
msg: Prevent `String.to_atom/1` calls
repository: repo_url: https://github.com/pow-auth/pow_assent
repo_name: pow-auth/pow_assent
description: Multi-provider authentication for your Pow enabled app
repo_language: Elixir
owner: pow-auth
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,11 @@
 # Changelog
 
+## v0.4.4 (TBA)
+
+* [`PowAssent.Plug`] Now uses `String.to_existing_atom/1` in `PowAssent.Plug.providers_for_current_user/1`
+* [`PowAssent.Plug`] Fixed security issue by removing `String.to_atom/1` for user provided binary in `PowAssent.Plug.authorize_url/3` and `PowAssent.Plug.callback/4`
+* [`PowAssent.Config`] `PowAssent.Config.get_provider_config/2` now accepts binary provider
+
 ## v0.4.3 (2019-11-20)
 
 * Removed `:phoenix_html` dependency requirement

programming_language: Markdown
filename: config.ex
change_type: ModificationType.MODIFY
diff: @@ -50,15 +50,27 @@ defmodule PowAssent.Config do
   @doc """
   Gets the provider configuration from the provided configuration.
   """
-  @spec get_provider_config(t(), atom()) :: t() | no_return
+  @spec get_provider_config(t(), atom() | binary()) :: t() | no_return
   def get_provider_config(config, provider) do
     config
     |> get_providers()
-    |> get(provider)
+    |> get_for_provider(provider)
     |> Kernel.||(raise_error("No provider configuration available for #{provider}."))
     |> add_global_config(config)
   end
 
+  defp get_for_provider(providers_config, provider) when is_atom(provider) do
+    get(providers_config, provider)
+  end
+  defp get_for_provider(providers_config, provider) when is_binary(provider) do
+    Enum.find_value(providers_config, fn {key, value} ->
+      case Atom.to_string(key) do
+        ^provider -> value
+        _any      -> false
+      end
+    end)
+  end
+
   defp add_global_config(provider_config, config) do
     [
       :http_adapter,

programming_language: Ruby
filename: plug.ex
change_type: ModificationType.MODIFY
diff: @@ -197,7 +197,7 @@ defmodule PowAssent.Plug do
     conn
     |> Pow.Plug.current_user()
     |> get_all_providers_for_user(config)
-    |> Enum.map(&String.to_atom(&1.provider))
+    |> Enum.map(&String.to_existing_atom(&1.provider))
   end
 
   defp get_all_providers_for_user(nil, _config), do: []
@@ -232,7 +232,6 @@ defmodule PowAssent.Plug do
     |> get_provider_config(provider, redirect_uri)
   end
   defp get_provider_config(config, provider, redirect_uri) do
-    provider        = String.to_atom(provider)
     config          = Config.get_provider_config(config, provider)
     strategy        = config[:strategy]
     provider_config =

programming_language: Ruby
filename: config_test.exs
change_type: ModificationType.MODIFY
diff: @@ -28,4 +28,14 @@ defmodule PowAssent.ConfigTest do
     assert Config.get_provider_config([http_adapter: HTTPAdapater, json_adapter: JSONAdapter, jwt_adapter: JWTAdapter], :provider1) ==
       [http_adapter: HTTPAdapater, json_adapter: JSONAdapter, jwt_adapter: JWTAdapter, a: 1]
   end
+
+  test "get_provider_config/2 with binary provider" do
+    config = [providers: [provider1: [a: 1], provider2: [b: 2]]]
+
+    assert Config.get_provider_config(config, "provider1") == [a: 1]
+
+    assert_raise PowAssent.Config.ConfigError, "No provider configuration available for non_existent.", fn ->
+      refute Config.get_provider_config(config, "non_existent")
+    end
+  end
 end

programming_language: Ruby