Current section
3 Versions
Jump to
Current section
3 Versions
Compare versions
6
files changed
+130
additions
-18
deletions
| @@ -1,5 +1,14 @@ | |
| 1 1 | # Changelog |
| 2 2 | |
| 3 | + ## v0.2.0 (2018-12-30) |
| 4 | + |
| 5 | + - Enhancements |
| 6 | + - Most possibilities (as currently known) of InfluxQL injection via malicious identifier or value arguments should be prevented ([#1](https://github.com/mneudert/influxql/pull/1)) |
| 7 | + - Trying to quote invalid identifier or value types (such as functions) now raises an `ArgumentError` |
| 8 | + |
| 9 | + - Bug fixes |
| 10 | + - Values like atoms or booleans are now properly quoted |
| 11 | + |
| 3 12 | ## v0.1.0 (2018-04-14) |
| 4 13 | |
| 5 14 | - Initial Release |
| @@ -7,12 +7,13 @@ Add the library as a dependency to your `mix.exs` file: | |
| 7 7 | ```elixir |
| 8 8 | defp deps do |
| 9 9 | [ |
| 10 | - {:influxql, "~> 0.1.0"} |
| 10 | + # ... |
| 11 | + {:influxql, "~> 0.1.0"}, |
| 12 | + # ... |
| 11 13 | ] |
| 12 14 | end |
| 13 15 | ``` |
| 14 16 | |
| 15 | - |
| 16 17 | ## License |
| 17 18 | |
| 18 19 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) |
| @@ -4,11 +4,10 @@ | |
| 4 4 | {<<"elixir">>,<<"~> 1.3">>}. |
| 5 5 | {<<"files">>, |
| 6 6 | [<<"CHANGELOG.md">>,<<"LICENSE">>,<<"mix.exs">>,<<"README.md">>,<<"lib">>, |
| 7 | - <<"lib/influxql">>,<<"lib/influxql.ex">>,<<"lib/influxql/quote.ex">>, |
| 8 | - <<"lib/influxql/sanitize.ex">>]}. |
| 7 | + <<"lib/influxql">>,<<"lib/influxql.ex">>,<<"lib/influxql/escape.ex">>, |
| 8 | + <<"lib/influxql/quote.ex">>,<<"lib/influxql/sanitize.ex">>]}. |
| 9 9 | {<<"licenses">>,[<<"Apache 2.0">>]}. |
| 10 10 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/mneudert/influxql">>}]}. |
| 11 | - {<<"maintainers">>,[<<"Marc Neudert">>]}. |
| 12 11 | {<<"name">>,<<"influxql">>}. |
| 13 12 | {<<"requirements">>,[]}. |
| 14 | - {<<"version">>,<<"0.1.0">>}. |
| 13 | + {<<"version">>,<<"0.2.0">>}. |
| @@ -0,0 +1,40 @@ | |
| 1 | + defmodule InfluxQL.Escape do |
| 2 | + @moduledoc """ |
| 3 | + InfluxQL element escaping module. |
| 4 | + """ |
| 5 | + |
| 6 | + @doc """ |
| 7 | + Escapes identifier binaries to prevent InfluxQL injection. |
| 8 | + |
| 9 | + ## Examples |
| 10 | + |
| 11 | + iex> identifier("all_ok") |
| 12 | + "all_ok" |
| 13 | + |
| 14 | + iex> identifier(~S(not"ok)) |
| 15 | + ~S(not\\"ok) |
| 16 | + |
| 17 | + iex> identifier(~S(ignore" WHERE 1=1; SELECT * FROM malicious_query --)) |
| 18 | + ~S(ignore\\" WHERE 1=1; SELECT * FROM malicious_query --) |
| 19 | + """ |
| 20 | + @spec identifier(String.t()) :: String.t() |
| 21 | + def identifier(identifier) when is_binary(identifier), |
| 22 | + do: String.replace(identifier, ~S("), ~S(\")) |
| 23 | + |
| 24 | + @doc """ |
| 25 | + Escapes value binaries to prevent InfluxQL injection. |
| 26 | + |
| 27 | + ## Examples |
| 28 | + |
| 29 | + iex> value("already sane") |
| 30 | + "already sane" |
| 31 | + |
| 32 | + iex> value("wasn't nice") |
| 33 | + ~S(wasn\\'t nice) |
| 34 | + |
| 35 | + iex> value("'; SELECT * FROM malicious_query WHERE 'a'='a") |
| 36 | + ~S(\\'; SELECT * FROM malicious_query WHERE \\'a\\'=\\'a) |
| 37 | + """ |
| 38 | + @spec value(String.t()) :: String.t() |
| 39 | + def value(value) when is_binary(value), do: String.replace(value, "'", "\\'") |
| 40 | + end |
| @@ -3,6 +3,8 @@ defmodule InfluxQL.Quote do | |
| 3 3 | InfluxQL element quoting module. |
| 4 4 | """ |
| 5 5 | |
| 6 | + alias InfluxQL.Escape |
| 7 | + |
| 6 8 | @doc """ |
| 7 9 | Quotes an identifier for use in a query. |
| 8 10 | |
| @@ -28,8 +30,31 @@ defmodule InfluxQL.Quote do | |
| 28 30 | |
| 29 31 | iex> identifier("dáshes-and.stüff") |
| 30 32 | "\\"dáshes-and.stüff\\"" |
| 33 | + |
| 34 | + ## Invalid identifier types |
| 35 | + |
| 36 | + iex> identifier(%{key: :value}) |
| 37 | + ** (ArgumentError) invalid InfluxQL identifier: %{key: :value} |
| 38 | + |
| 39 | + iex> identifier({:key, :value}) |
| 40 | + ** (ArgumentError) invalid InfluxQL identifier: {:key, :value} |
| 41 | + |
| 42 | + iex> identifier(<<1::4>>) |
| 43 | + ** (ArgumentError) invalid InfluxQL identifier: <<1::size(4)>> |
| 44 | + |
| 45 | + ## InfluxQL injection prevention |
| 46 | + |
| 47 | + iex> identifier(~S(wasnot"nice)) |
| 48 | + ~S("wasnot\\"nice") |
| 31 49 | """ |
| 32 | - @spec identifier(String.t()) :: String.t() |
| 50 | + @spec identifier(term) :: String.t() |
| 51 | + def identifier(identifier) |
| 52 | + when is_map(identifier) or is_tuple(identifier) or is_pid(identifier) or is_port(identifier) or |
| 53 | + is_reference(identifier) or is_function(identifier) or |
| 54 | + (is_bitstring(identifier) and not is_binary(identifier)) do |
| 55 | + raise ArgumentError, "invalid InfluxQL identifier: #{inspect(identifier)}" |
| 56 | + end |
| 57 | + |
| 33 58 | for char <- ?0..?9 do |
| 34 59 | def identifier(<<unquote(char), _::binary>> = identifier), do: "\"#{identifier}\"" |
| 35 60 | end |
| @@ -37,7 +62,7 @@ defmodule InfluxQL.Quote do | |
| 37 62 | def identifier(identifier) when is_binary(identifier) do |
| 38 63 | case Regex.match?(~r/([^a-zA-Z0-9_])/, identifier) do |
| 39 64 | false -> identifier |
| 40 | - true -> "\"#{identifier}\"" |
| 65 | + true -> ~s("#{Escape.identifier(identifier)}") |
| 41 66 | end |
| 42 67 | end |
| 43 68 | |
| @@ -52,12 +77,49 @@ defmodule InfluxQL.Quote do | |
| 52 77 | "100" |
| 53 78 | |
| 54 79 | iex> value(:foo) |
| 55 | - "foo" |
| 80 | + "'foo'" |
| 81 | + |
| 82 | + iex> value(false) |
| 83 | + "false" |
| 84 | + |
| 85 | + iex> value(nil) |
| 86 | + "''" |
| 56 87 | |
| 57 88 | iex> value("stringy") |
| 58 89 | "'stringy'" |
| 90 | + |
| 91 | + iex> value('charlisty') |
| 92 | + "'charlisty'" |
| 93 | + |
| 94 | + ## Invalid value types |
| 95 | + |
| 96 | + iex> value(%{key: :value}) |
| 97 | + ** (ArgumentError) invalid InfluxQL value: %{key: :value} |
| 98 | + |
| 99 | + iex> value({:key, :value}) |
| 100 | + ** (ArgumentError) invalid InfluxQL value: {:key, :value} |
| 101 | + |
| 102 | + iex> value(<<1::4>>) |
| 103 | + ** (ArgumentError) invalid InfluxQL value: <<1::size(4)>> |
| 104 | + |
| 105 | + ## InfluxQL injection prevention |
| 106 | + |
| 107 | + iex> value("wasn't nice") |
| 108 | + ~S('wasn\\'t nice') |
| 59 109 | """ |
| 60 | - @spec value(any) :: String.t() |
| 61 | - def value(value) when is_binary(value), do: "'#{value}'" |
| 62 | - def value(value), do: Kernel.to_string(value) |
| 110 | + @spec value(term) :: String.t() |
| 111 | + def value(nil), do: "''" |
| 112 | + def value(value) when is_boolean(value), do: Kernel.to_string(value) |
| 113 | + |
| 114 | + def value(value) when is_atom(value), |
| 115 | + do: "'#{value |> Atom.to_string() |> Escape.value()}'" |
| 116 | + |
| 117 | + def value(value) when is_binary(value), do: "'#{Escape.value(value)}'" |
| 118 | + |
| 119 | + def value(value) when is_list(value), |
| 120 | + do: "'#{value |> List.to_string() |> Escape.value()}'" |
| 121 | + |
| 122 | + def value(value) when is_number(value), do: Kernel.to_string(value) |
| 123 | + |
| 124 | + def value(value), do: raise(ArgumentError, "invalid InfluxQL value: #{inspect(value)}") |
| 63 125 | end |
Loading more files…