Packages

InfluxQL utility/tooling package

Current section

3 Versions

Jump to

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 "\\"¡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…