rack-libinjection 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +55 -0
- data/CHANGELOG.md +112 -0
- data/GET_STARTED.md +418 -0
- data/LICENSE-libinjection.txt +33 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/SECURITY.md +65 -0
- data/ext/libinjection/extconf.rb +113 -0
- data/ext/libinjection/libinjection_ext.c +1132 -0
- data/ext/libinjection/vendor/libinjection/.vendored +5 -0
- data/ext/libinjection/vendor/libinjection/COPYING +33 -0
- data/ext/libinjection/vendor/libinjection/MIGRATION.md +393 -0
- data/ext/libinjection/vendor/libinjection/README.md +251 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection.h +70 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_error.h +26 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_html5.c +830 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_html5.h +56 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_sqli.c +2342 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_sqli.h +297 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_sqli_data.h +9651 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_xss.c +1203 -0
- data/ext/libinjection/vendor/libinjection/src/libinjection_xss.h +23 -0
- data/lib/libinjection/version.rb +6 -0
- data/lib/libinjection.rb +31 -0
- data/lib/rack/libinjection.rb +586 -0
- data/lib/rack-libinjection.rb +3 -0
- data/samples/README.md +67 -0
- data/samples/libinjection_detect_raw_hot_path.rb +161 -0
- data/samples/rack_all_surfaces_hot_path.rb +198 -0
- data/samples/rack_params_hot_path.rb +166 -0
- data/samples/rack_query_hot_path.rb +176 -0
- data/samples/results/.gitkeep +0 -0
- data/script/fuzz_smoke.rb +39 -0
- data/script/vendor_libs.rb +227 -0
- data/test/test_helper.rb +7 -0
- data/test/test_libinjection.rb +223 -0
- data/test/test_middleware.rb +404 -0
- metadata +148 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
class TestRackLibInjection < Minitest::Test
|
|
6
|
+
def app(**options)
|
|
7
|
+
Rack::LibInjection.new(
|
|
8
|
+
->(env) { [200, { "content-type" => "text/plain" }, [env[Rack::LibInjection::ATTACK_ENV_KEY].inspect]] },
|
|
9
|
+
**options
|
|
10
|
+
)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_report_mode_continues_request
|
|
14
|
+
res = Rack::MockRequest.new(app).get("/search?q=1%20OR%201%3D1--")
|
|
15
|
+
assert_equal 200, res.status
|
|
16
|
+
assert_includes res.body, ":sqli"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_block_mode_returns_403
|
|
20
|
+
res = Rack::MockRequest.new(app(mode: :block)).get("/search?q=1%20OR%201%3D1--")
|
|
21
|
+
assert_equal 403, res.status
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_off_mode_does_not_scan_or_set_env_key
|
|
25
|
+
res = Rack::MockRequest.new(app(mode: :off)).get("/search?q=1%20OR%201%3D1--")
|
|
26
|
+
|
|
27
|
+
assert_equal 200, res.status
|
|
28
|
+
assert_equal "nil", res.body
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def test_empty_attack_list_is_mutable_for_downstream_rack_apps
|
|
32
|
+
middleware = Rack::LibInjection.new(
|
|
33
|
+
lambda { |env|
|
|
34
|
+
attacks = env.fetch(Rack::LibInjection::ATTACK_ENV_KEY)
|
|
35
|
+
attacks << :downstream_marker
|
|
36
|
+
[200, { "content-type" => "text/plain" }, [attacks.inspect]]
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
res = Rack::MockRequest.new(middleware).get("/clean?q=ordinary")
|
|
41
|
+
|
|
42
|
+
assert_equal 200, res.status
|
|
43
|
+
assert_includes res.body, ":downstream_marker"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def test_notifier_receives_payload
|
|
47
|
+
events = []
|
|
48
|
+
notifier = ->(event, payload) { events << [event, payload] }
|
|
49
|
+
Rack::MockRequest.new(app(notifier: notifier)).get("/search?q=1%20OR%201%3D1--")
|
|
50
|
+
|
|
51
|
+
assert_equal "rack.libinjection.attack", events.fetch(0).fetch(0)
|
|
52
|
+
assert_equal :sqli, events.fetch(0).fetch(1).fetch(:type)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def test_xss_payload_is_reported
|
|
56
|
+
res = Rack::MockRequest.new(app).get("/search?q=%3Cscript%3Ealert(1)%3C/script%3E")
|
|
57
|
+
|
|
58
|
+
assert_equal 200, res.status
|
|
59
|
+
assert_includes res.body, ":xss"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_ignore_params_skips_key_and_value
|
|
63
|
+
events = []
|
|
64
|
+
notifier = ->(event, payload) { events << [event, payload] }
|
|
65
|
+
|
|
66
|
+
res = Rack::MockRequest.new(app(ignore_params: %w[q], notifier: notifier)).get("/search?q=1%20OR%201%3D1--")
|
|
67
|
+
|
|
68
|
+
assert_equal 200, res.status
|
|
69
|
+
refute_includes res.body, ":sqli"
|
|
70
|
+
assert_empty events.select { |event, _| event == "rack.libinjection.attack" }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_nested_params_are_scanned
|
|
74
|
+
res = Rack::MockRequest.new(app).get("/search?filter[items][]=1%20OR%201%3D1--")
|
|
75
|
+
|
|
76
|
+
assert_equal 200, res.status
|
|
77
|
+
assert_includes res.body, ":sqli"
|
|
78
|
+
assert_includes res.body, "filter.items[0]"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def test_uploaded_filename_is_scanned
|
|
82
|
+
middleware = app
|
|
83
|
+
upload = Struct.new(:original_filename).new("1 OR 1=1--.jpg")
|
|
84
|
+
attacks = []
|
|
85
|
+
|
|
86
|
+
middleware.send(
|
|
87
|
+
:walk_into,
|
|
88
|
+
attacks,
|
|
89
|
+
upload,
|
|
90
|
+
location: :params,
|
|
91
|
+
path: "avatar",
|
|
92
|
+
depth: 0,
|
|
93
|
+
context: { method: "POST", path: "/upload", ip: "127.0.0.1" }
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
assert_equal :sqli, attacks.fetch(0).type
|
|
97
|
+
assert_equal "avatar.filename", attacks.fetch(0).key
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_password_is_scanned_by_default
|
|
101
|
+
res = Rack::MockRequest.new(app).get("/login?password=1%20OR%201%3D1--")
|
|
102
|
+
|
|
103
|
+
assert_equal 200, res.status
|
|
104
|
+
assert_includes res.body, ":sqli"
|
|
105
|
+
assert_includes res.body, "password"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_max_value_bytes_skips_and_notifies
|
|
109
|
+
events = []
|
|
110
|
+
notifier = ->(event, payload) { events << [event, payload] }
|
|
111
|
+
|
|
112
|
+
Rack::MockRequest.new(app(max_value_bytes: 3, notifier: notifier)).get("/search?q=abcd")
|
|
113
|
+
|
|
114
|
+
skipped = events.find { |event, _| event == "rack.libinjection.skipped" }
|
|
115
|
+
assert skipped
|
|
116
|
+
assert_equal :max_value_bytes, skipped.fetch(1).fetch(:reason)
|
|
117
|
+
assert_equal 4, skipped.fetch(1).fetch(:bytes)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_max_value_bytes_blocks_in_block_mode_by_default
|
|
121
|
+
res = Rack::MockRequest.new(app(mode: :block, max_value_bytes: 3)).get("/search?q=abcd")
|
|
122
|
+
|
|
123
|
+
assert_equal 403, res.status
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def test_skipped_inputs_can_allow_oversized_values_in_block_mode
|
|
127
|
+
res = Rack::MockRequest.new(app(mode: :block, max_value_bytes: 3, skipped_inputs: :allow)).get("/search?q=abcd")
|
|
128
|
+
|
|
129
|
+
assert_equal 200, res.status
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_max_depth_skips_and_notifies
|
|
133
|
+
events = []
|
|
134
|
+
notifier = ->(event, payload) { events << [event, payload] }
|
|
135
|
+
|
|
136
|
+
Rack::MockRequest.new(app(max_depth: 0, notifier: notifier)).get("/search?a[b]=1")
|
|
137
|
+
|
|
138
|
+
skipped = events.find { |event, _| event == "rack.libinjection.skipped" }
|
|
139
|
+
assert skipped
|
|
140
|
+
assert_equal :max_depth, skipped.fetch(1).fetch(:reason)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def test_max_depth_blocks_in_block_mode_by_default
|
|
144
|
+
res = Rack::MockRequest.new(app(mode: :block, max_depth: 0)).get("/search?a[b]=1")
|
|
145
|
+
|
|
146
|
+
assert_equal 403, res.status
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def test_notify_skipped_can_be_disabled
|
|
150
|
+
events = []
|
|
151
|
+
notifier = ->(event, payload) { events << [event, payload] }
|
|
152
|
+
|
|
153
|
+
Rack::MockRequest.new(app(max_value_bytes: 3, notify_skipped: false, notifier: notifier)).get("/search?q=abcd")
|
|
154
|
+
|
|
155
|
+
assert_empty events
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def test_path_scan_is_opt_in
|
|
159
|
+
res = Rack::MockRequest.new(app(scan: %i[path])).get("/items/1%20OR%201=1--")
|
|
160
|
+
|
|
161
|
+
assert_equal 200, res.status
|
|
162
|
+
assert_includes res.body, ":sqli"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def test_path_scan_decodes_twice_by_default
|
|
166
|
+
res = Rack::MockRequest.new(app(scan: %i[path])).get("/items/1%2520OR%25201%253D1--")
|
|
167
|
+
|
|
168
|
+
assert_equal 200, res.status
|
|
169
|
+
assert_includes res.body, ":sqli"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def test_path_segment_keys_start_at_zero_for_first_non_empty_segment
|
|
173
|
+
middleware = app(scan: %i[path])
|
|
174
|
+
attacks = []
|
|
175
|
+
|
|
176
|
+
middleware.send(
|
|
177
|
+
:scan_path_segments_into,
|
|
178
|
+
attacks,
|
|
179
|
+
"/1%20OR%201=1--/items",
|
|
180
|
+
{ method: "GET", path: "/1 OR 1=1--/items", ip: "127.0.0.1" }
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
assert_equal "path[0]", attacks.fetch(0).key
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_path_decode_depth_has_hard_cap
|
|
187
|
+
assert_raises ArgumentError do
|
|
188
|
+
app(scan: %i[path], path_decode_depth: Rack::LibInjection::MAX_PATH_DECODE_DEPTH + 1)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def test_path_decode_depth_can_disable_double_decode
|
|
193
|
+
middleware = app(scan: %i[path], path_decode_depth: 1)
|
|
194
|
+
attacks = []
|
|
195
|
+
|
|
196
|
+
middleware.send(
|
|
197
|
+
:scan_path_into,
|
|
198
|
+
attacks,
|
|
199
|
+
"/items/1%2520OR%25201%253D1--",
|
|
200
|
+
{ method: "GET", path: "/items", ip: "127.0.0.1" }
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
assert_empty attacks
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def test_headers_scan_is_opt_in
|
|
207
|
+
res = Rack::MockRequest.new(app(scan: %i[headers])).get("/", "HTTP_USER_AGENT" => "1 OR 1=1--")
|
|
208
|
+
|
|
209
|
+
assert_equal 200, res.status
|
|
210
|
+
assert_includes res.body, ":headers"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_standard_headers_are_ignored_by_default
|
|
214
|
+
res = Rack::MockRequest.new(app(scan: %i[headers])).get("/", "HTTP_ACCEPT" => "1 OR 1=1--")
|
|
215
|
+
|
|
216
|
+
assert_equal 200, res.status
|
|
217
|
+
refute_includes res.body, ":headers"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def test_ignored_headers_can_be_overridden
|
|
221
|
+
res = Rack::MockRequest.new(app(scan: %i[headers], ignore_headers: [])).get("/", "HTTP_ACCEPT" => "1 OR 1=1--")
|
|
222
|
+
|
|
223
|
+
assert_equal 200, res.status
|
|
224
|
+
assert_includes res.body, ":headers"
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_cookies_scan_is_opt_in
|
|
228
|
+
res = Rack::MockRequest.new(app(scan: %i[cookies])).get("/", "HTTP_COOKIE" => "q=1%20OR%201=1--")
|
|
229
|
+
|
|
230
|
+
assert_equal 200, res.status
|
|
231
|
+
assert_includes res.body, ":cookies"
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def test_cookie_names_are_not_scanned_by_default
|
|
235
|
+
middleware = app(scan: %i[cookies])
|
|
236
|
+
req = Struct.new(:cookies).new({ "1 OR 1=1--" => "ordinary" })
|
|
237
|
+
attacks = []
|
|
238
|
+
|
|
239
|
+
middleware.send(:scan_cookies_into, attacks, req, { method: "GET", path: "/", ip: "127.0.0.1" })
|
|
240
|
+
|
|
241
|
+
assert_empty attacks
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def test_cookie_name_scanning_can_be_enabled
|
|
245
|
+
middleware = app(scan: %i[cookies], scan_cookie_names: true)
|
|
246
|
+
req = Struct.new(:cookies).new({ "1 OR 1=1--" => "ordinary" })
|
|
247
|
+
attacks = []
|
|
248
|
+
|
|
249
|
+
middleware.send(:scan_cookies_into, attacks, req, { method: "GET", path: "/", ip: "127.0.0.1" })
|
|
250
|
+
|
|
251
|
+
assert_equal :sqli, attacks.fetch(0).type
|
|
252
|
+
assert attacks.fetch(0).detected_in_key_name?
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def test_query_scan_can_use_minimal_env_without_rack_request
|
|
257
|
+
middleware = app(scan: %i[query])
|
|
258
|
+
env = {
|
|
259
|
+
"REQUEST_METHOD" => "GET",
|
|
260
|
+
"PATH_INFO" => "/search",
|
|
261
|
+
"QUERY_STRING" => "q=1%20OR%201%3D1--",
|
|
262
|
+
"REMOTE_ADDR" => "127.0.0.1"
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
status, = middleware.call(env)
|
|
266
|
+
|
|
267
|
+
assert_equal 200, status
|
|
268
|
+
assert_equal :sqli, env.fetch(Rack::LibInjection::ATTACK_ENV_KEY).fetch(0).type
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def test_query_scan_notifier_metadata_can_come_from_env
|
|
272
|
+
events = []
|
|
273
|
+
middleware = app(scan: %i[query], notifier: ->(event, payload) { events << [event, payload] })
|
|
274
|
+
env = {
|
|
275
|
+
"REQUEST_METHOD" => "GET",
|
|
276
|
+
"PATH_INFO" => "/search",
|
|
277
|
+
"QUERY_STRING" => "q=1%20OR%201%3D1--",
|
|
278
|
+
"REMOTE_ADDR" => "127.0.0.1"
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
middleware.call(env)
|
|
282
|
+
|
|
283
|
+
payload = events.fetch(0).fetch(1)
|
|
284
|
+
assert_equal "GET", payload.fetch(:method)
|
|
285
|
+
assert_equal "/search", payload.fetch(:path)
|
|
286
|
+
assert_equal "127.0.0.1", payload.fetch(:ip)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def test_query_scan_is_fast_raw_surface
|
|
290
|
+
res = Rack::MockRequest.new(app(scan: %i[query])).get("/search?q=1%20OR%201%3D1--")
|
|
291
|
+
|
|
292
|
+
assert_equal 200, res.status
|
|
293
|
+
assert_includes res.body, ":query"
|
|
294
|
+
assert_includes res.body, ":sqli"
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def test_query_scan_decodes_twice
|
|
298
|
+
res = Rack::MockRequest.new(app(scan: %i[query])).get("/search?q=1%2520OR%25201%253D1--")
|
|
299
|
+
|
|
300
|
+
assert_equal 200, res.status
|
|
301
|
+
assert_includes res.body, ":query"
|
|
302
|
+
assert_includes res.body, ":sqli"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def test_query_scan_decodes_plus_as_space_in_native_hot_path
|
|
307
|
+
res = Rack::MockRequest.new(app(scan: %i[query])).get("/search?q=1+OR+1%3D1--")
|
|
308
|
+
|
|
309
|
+
assert_equal 200, res.status
|
|
310
|
+
assert_includes res.body, ":query"
|
|
311
|
+
assert_includes res.body, ":sqli"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def test_query_decode_depth_zero_scans_only_raw_query
|
|
315
|
+
res = Rack::MockRequest.new(app(scan: %i[query], path_decode_depth: 0)).get("/search?q=1%20OR%201%3D1--")
|
|
316
|
+
|
|
317
|
+
assert_equal 200, res.status
|
|
318
|
+
refute_includes res.body, ":sqli"
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def test_threats_can_scan_only_sqli
|
|
322
|
+
res = Rack::MockRequest.new(app(threats: %i[sqli])).get("/search?q=%3Cscript%3Ealert(1)%3C/script%3E")
|
|
323
|
+
|
|
324
|
+
assert_equal 200, res.status
|
|
325
|
+
refute_includes res.body, ":xss"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def test_threats_can_scan_only_xss
|
|
329
|
+
res = Rack::MockRequest.new(app(threats: %i[xss])).get("/search?q=%3Cscript%3Ealert(1)%3C/script%3E")
|
|
330
|
+
|
|
331
|
+
assert_equal 200, res.status
|
|
332
|
+
assert_includes res.body, ":xss"
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def test_parser_errors_block_in_block_mode_by_default
|
|
336
|
+
middleware = app(mode: :block)
|
|
337
|
+
middleware.define_singleton_method(:scan_params_into) do |_attacks, _req, _context|
|
|
338
|
+
raise ::LibInjection::ParserError, "parser failure"
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
res = Rack::MockRequest.new(middleware).get("/search?q=ordinary")
|
|
342
|
+
|
|
343
|
+
assert_equal 403, res.status
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def test_parser_errors_can_raise
|
|
347
|
+
middleware = app(parser_errors: :raise)
|
|
348
|
+
middleware.define_singleton_method(:scan_params_into) do |_attacks, _req, _context|
|
|
349
|
+
raise ::LibInjection::ParserError, "parser failure"
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
assert_raises ::LibInjection::ParserError do
|
|
353
|
+
Rack::MockRequest.new(middleware).get("/search?q=ordinary")
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def test_parameter_parser_errors_block_in_block_mode
|
|
358
|
+
error_class = Rack::LibInjection::PARAMETER_ERRORS.first || skip("Rack parameter error class unavailable")
|
|
359
|
+
req = Object.new
|
|
360
|
+
req.define_singleton_method(:params) { raise error_class, "bad params" }
|
|
361
|
+
middleware = app(mode: :block)
|
|
362
|
+
blocked = Rack::LibInjection.const_get(:ParserBlocked)
|
|
363
|
+
|
|
364
|
+
assert_raises blocked do
|
|
365
|
+
middleware.send(:scan_params_into, [], req, { method: "GET", path: "/", ip: "127.0.0.1" })
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def test_parameter_parser_errors_are_notified
|
|
370
|
+
error_class = Rack::LibInjection::PARAMETER_ERRORS.first || RuntimeError
|
|
371
|
+
req = Object.new
|
|
372
|
+
req.define_singleton_method(:params) { raise error_class, "bad params" }
|
|
373
|
+
events = []
|
|
374
|
+
middleware = app(notifier: ->(event, payload) { events << [event, payload] })
|
|
375
|
+
|
|
376
|
+
middleware.send(:scan_params_into, [], req, { method: "GET", path: "/", ip: "127.0.0.1" })
|
|
377
|
+
|
|
378
|
+
event = events.find { |name, _| name == "rack.libinjection.error" }
|
|
379
|
+
assert event
|
|
380
|
+
assert_equal error_class.name, event.fetch(1).fetch(:error)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def test_notifier_errors_are_ignored_by_default
|
|
384
|
+
res = Rack::MockRequest.new(app(notifier: ->(*) { raise "boom" })).get("/search?q=1%20OR%201%3D1--")
|
|
385
|
+
|
|
386
|
+
assert_equal 200, res.status
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def test_notifier_errors_can_raise
|
|
390
|
+
assert_raises RuntimeError do
|
|
391
|
+
Rack::MockRequest.new(app(notifier: ->(*) { raise "boom" }, notifier_errors: :raise)).get("/search?q=1%20OR%201%3D1--")
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def test_logger_wins_over_active_support_detection
|
|
396
|
+
logger = Object.new
|
|
397
|
+
messages = []
|
|
398
|
+
logger.define_singleton_method(:warn) { |msg| messages << msg }
|
|
399
|
+
|
|
400
|
+
Rack::MockRequest.new(app(logger: logger)).get("/search?q=1%20OR%201%3D1--")
|
|
401
|
+
|
|
402
|
+
refute_empty messages
|
|
403
|
+
end
|
|
404
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rack-libinjection
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Roman Haydarov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rack
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.2'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '4'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '2.2'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '4'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: rake
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '13.0'
|
|
39
|
+
type: :development
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '13.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: rake-compiler
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '1.2'
|
|
53
|
+
type: :development
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '1.2'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: minitest
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '5.0'
|
|
67
|
+
type: :development
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '5.0'
|
|
74
|
+
description: 'Native Ruby binding and Rack middleware for libinjection. Report-only
|
|
75
|
+
by default: detects SQLi/XSS-like payloads, emits structured attack signals, and
|
|
76
|
+
can be combined with Rack::Attack. Ships with a pinned vendoring workflow for libinjection
|
|
77
|
+
v4.0.0.'
|
|
78
|
+
email:
|
|
79
|
+
- romanhajdarov@gmail.com
|
|
80
|
+
executables: []
|
|
81
|
+
extensions:
|
|
82
|
+
- ext/libinjection/extconf.rb
|
|
83
|
+
extra_rdoc_files: []
|
|
84
|
+
files:
|
|
85
|
+
- ".github/workflows/ci.yml"
|
|
86
|
+
- CHANGELOG.md
|
|
87
|
+
- GET_STARTED.md
|
|
88
|
+
- LICENSE-libinjection.txt
|
|
89
|
+
- LICENSE.txt
|
|
90
|
+
- README.md
|
|
91
|
+
- SECURITY.md
|
|
92
|
+
- ext/libinjection/extconf.rb
|
|
93
|
+
- ext/libinjection/libinjection_ext.c
|
|
94
|
+
- ext/libinjection/vendor/libinjection/.vendored
|
|
95
|
+
- ext/libinjection/vendor/libinjection/COPYING
|
|
96
|
+
- ext/libinjection/vendor/libinjection/MIGRATION.md
|
|
97
|
+
- ext/libinjection/vendor/libinjection/README.md
|
|
98
|
+
- ext/libinjection/vendor/libinjection/src/libinjection.h
|
|
99
|
+
- ext/libinjection/vendor/libinjection/src/libinjection_error.h
|
|
100
|
+
- ext/libinjection/vendor/libinjection/src/libinjection_html5.c
|
|
101
|
+
- ext/libinjection/vendor/libinjection/src/libinjection_html5.h
|
|
102
|
+
- ext/libinjection/vendor/libinjection/src/libinjection_sqli.c
|
|
103
|
+
- ext/libinjection/vendor/libinjection/src/libinjection_sqli.h
|
|
104
|
+
- ext/libinjection/vendor/libinjection/src/libinjection_sqli_data.h
|
|
105
|
+
- ext/libinjection/vendor/libinjection/src/libinjection_xss.c
|
|
106
|
+
- ext/libinjection/vendor/libinjection/src/libinjection_xss.h
|
|
107
|
+
- lib/libinjection.rb
|
|
108
|
+
- lib/libinjection/version.rb
|
|
109
|
+
- lib/rack-libinjection.rb
|
|
110
|
+
- lib/rack/libinjection.rb
|
|
111
|
+
- samples/README.md
|
|
112
|
+
- samples/libinjection_detect_raw_hot_path.rb
|
|
113
|
+
- samples/rack_all_surfaces_hot_path.rb
|
|
114
|
+
- samples/rack_params_hot_path.rb
|
|
115
|
+
- samples/rack_query_hot_path.rb
|
|
116
|
+
- samples/results/.gitkeep
|
|
117
|
+
- script/fuzz_smoke.rb
|
|
118
|
+
- script/vendor_libs.rb
|
|
119
|
+
- test/test_helper.rb
|
|
120
|
+
- test/test_libinjection.rb
|
|
121
|
+
- test/test_middleware.rb
|
|
122
|
+
homepage: https://github.com/roman-haidarov/rack-libinjection
|
|
123
|
+
licenses:
|
|
124
|
+
- MIT
|
|
125
|
+
metadata:
|
|
126
|
+
homepage_uri: https://github.com/roman-haidarov/rack-libinjection
|
|
127
|
+
source_code_uri: https://github.com/roman-haidarov/rack-libinjection/tree/main
|
|
128
|
+
changelog_uri: https://github.com/roman-haidarov/rack-libinjection/blob/main/CHANGELOG.md
|
|
129
|
+
bug_tracker_uri: https://github.com/roman-haidarov/rack-libinjection/issues
|
|
130
|
+
security_policy_uri: https://github.com/roman-haidarov/rack-libinjection/blob/main/SECURITY.md
|
|
131
|
+
rdoc_options: []
|
|
132
|
+
require_paths:
|
|
133
|
+
- lib
|
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: 3.3.0
|
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - ">="
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '0'
|
|
144
|
+
requirements: []
|
|
145
|
+
rubygems_version: 3.6.7
|
|
146
|
+
specification_version: 4
|
|
147
|
+
summary: Tokenizer/fingerprint-based attack signal layer for Rack/Rails
|
|
148
|
+
test_files: []
|