Skip to content

Commit a6dfd12

Browse files
committed
Add internal regular expression timeouts
With default timeout from redos_regex_timeout configuration option.
1 parent 79cf4d7 commit a6dfd12

File tree

5 files changed

+23
-11
lines changed

5 files changed

+23
-11
lines changed

lib/aikido/zen/config.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@ class Config
188188
# Defaults to 15 entries.
189189
attr_accessor :attack_wave_max_cache_samples
190190

191+
# @return [Float, nil] the timeout in seconds for regular expression matching.
192+
# Applied to selected internal regular expressions to mitigate ReDoS risks.
193+
# Defaults to 1.0 seconds.
194+
attr_accessor :redos_regexp_timeout
195+
191196
def initialize
192197
self.insert_middleware_after = ::ActionDispatch::RemoteIp
193198
self.disabled = read_boolean_from_env(ENV.fetch("AIKIDO_DISABLE", false)) || read_boolean_from_env(ENV.fetch("AIKIDO_DISABLED", false))
@@ -227,6 +232,7 @@ def initialize
227232
self.attack_wave_min_time_between_events = 20 * 60 * 1000 # 20 min (ms)
228233
self.attack_wave_max_cache_entries = 10_000
229234
self.attack_wave_max_cache_samples = 15
235+
self.redos_regexp_timeout = 1.0
230236
end
231237

232238
# Set the base URL for API requests.

lib/aikido/zen/helpers.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ def self.normalize_path(path)
1919
normalized_path.chomp!("/") unless normalized_path == "/"
2020
normalized_path
2121
end
22+
23+
# Returns a copy of the regexp with the timeout set if timeout is supported.
24+
#
25+
# @param regexp [Regexp] the regexp
26+
# @return [Regexp] the regexp with timeout set
27+
def self.regexp_with_timeout(regexp, timeout: Aikido::Zen.config.redos_regexp_timeout)
28+
return regexp if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2")
29+
30+
Regexp.new(regexp.source, regexp.options, timeout: timeout)
31+
end
2232
end
2333
end
2434
end

lib/aikido/zen/scanners/sql_injection_scanner.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ def attack?
6565
return false unless @query.include?(@input)
6666

6767
# If the input is solely alphanumeric, we can ignore it
68-
return false if /\A[[:alnum:]_]+\z/i.match?(@input)
68+
return false if Aikido::Zen::Helpers.regexp_with_timeout(/\A[[:alnum:]_]+\z/i).match?(@input)
6969

7070
# If the input is a comma-separated list of numbers, ignore it.
71-
return false if /\A[ ,]*\d[ ,\d]*\z/.match?(@input)
71+
return false if Aikido::Zen::Helpers.regexp_with_timeout(/\A[ ,]*\d[ ,\d]*\z/).match?(@input)
7272

7373
Internals.detect_sql_injection(@query, @input, @dialect)
7474
rescue => err

test/aikido/zen/config_test.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Aikido::Zen::ConfigTest < ActiveSupport::TestCase
3737
assert_equal 20, @config.api_schema_collection_max_properties
3838
assert_equal true, @config.stored_ssrf?
3939
assert_equal ["metadata.google.internal", "metadata.goog"], @config.imds_allowed_hosts
40+
assert_equal 1.0, @config.redos_regexp_timeout
4041
end
4142

4243
test "can set AIKIDO_DISABLE to configure if the agent should be turned off" do

test/aikido/zen/scanners/sql_injection_scanner_test.rb

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -278,18 +278,13 @@ def refute_attack(query, input = query, *args)
278278
test "it flags regular expression matching timeouts as attacks" do
279279
skip_if_ruby_lower_than("3.2")
280280

281-
begin
282-
timeout = Regexp.timeout
283-
Regexp.timeout = 0.01
281+
input = "1," * 1 * 1024 * 1024
284282

285-
refute_attack "SELECT * FROM users WHERE id IN (123,)", "123,"
283+
refute_attack "SELECT * FROM users WHERE id IN (#{input})", input
286284

287-
input = "1," * 1 * 1024 * 1024
285+
Aikido::Zen.config.redos_regexp_timeout = 0.001
288286

289-
assert_attack "SELECT * FROM users WHERE id IN (#{input})", input
290-
ensure
291-
Regexp.timeout = timeout
292-
end
287+
assert_attack "SELECT * FROM users WHERE id IN (#{input})", input
293288
end
294289

295290
test "attacks are not prevented if libzen can't be loaded" do

0 commit comments

Comments
 (0)