-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathelixir_sanitizer_prototype.ex
More file actions
121 lines (98 loc) · 2.6 KB
/
elixir_sanitizer_prototype.ex
File metadata and controls
121 lines (98 loc) · 2.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
defmodule ElixirSanitizerPrototype do
require Logger
use GenServer
@type state :: %__MODULE__{session: :trace.session(),
fire_count: non_neg_integer()}
defstruct session: nil, fire_count: 0
# API
def install() do
{:ok, _pid} = GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
:ok
end
def uninstall() do
GenServer.stop(__MODULE__)
end
def ping() do
GenServer.call(__MODULE__, :ping)
end
def info() do
GenServer.call(__MODULE__, :info)
end
def state() do
:sys.get_state(__MODULE__)
end
@default_sanitizer_slug "__sanitizer__"
defp sanitizer_slug() do
Application.get_env(:elixir_sanitizer_prototype, :sanitizer_slug, @default_sanitizer_slug)
end
@mfa_to_trace {DBConnection, :prepare_execute, 4}
# Callbacks
@impl GenServer
def init(:ok) do
session = :trace.session_create(:elixir_sanitizer_prototype, self(), [])
:trace.process(session, :all, true, [:call])
:trace.function(session, @mfa_to_trace, [], [])
{:ok, %__MODULE__{session: session}}
end
@impl GenServer
def handle_call(:ping, _from, state) do
{:reply, :pong, state}
end
@impl GenServer
def handle_call(:info, _from, state = %__MODULE__{session: session}) do
got_info = :trace.info(session, @mfa_to_trace, :all)
{:reply, got_info, state}
end
@impl GenServer
def terminate(_reason, _state = %__MODULE__{session: session}) do
:trace.session_destroy(session)
end
@impl GenServer
def handle_info(
{:trace, caller, :call,
{DBConnection, :prepare_execute,
_args = [
conn,
%Postgrex.Query{
statement: stmt
},
_params,
_opts
]}},
state
) do
maybe_alert(state, stmt, caller, conn)
end
def should_alert?(stmt) when is_binary(stmt) do
cond do
not String.valid?(stmt) ->
# weird but ok
false
String.contains?(stmt, sanitizer_slug()) ->
true
true ->
false
end
end
def should_alert?(stmt) do
stmt
|> IO.iodata_to_binary()
|> should_alert?()
end
defp maybe_alert(state, stmt, caller, conn) do
if should_alert?(stmt) do
Logger.emergency(
found_injection: stmt,
caller: caller,
caller_live: Process.alive?(caller),
conn: conn,
conn_live: Process.alive?(conn)
)
Process.exit(conn, :kill)
Process.exit(caller, :kill)
{:noreply, %__MODULE__{state | fire_count: state.fire_count + 1}}
else
{:noreply, state}
end
end
end