CVE-2020-28360: npm private-ip SSRF Bypass (IP Phone Home)

Versions of npm private-ip including and prior to 1.0.5 are vulnerable to multiple Server Side Request Forgery (SSRF) bypasses. Implemented Regular Expression (RegEx) within the package fail to account for variations of localhost and other Private IP ranges. An attacker can obfuscate payloads, or utilize ranges outside of the block list to successfully execute SSRF bypass techniques, circumventing restrictions.

Published on Nov 23, 2020

Reading time: 4 minutes.


Credits

Sick.Codes
Github: (https://github.com/sickcodes)
Twitter: (https://twitter.com/sickcodes)
John
Github: (https://github.com/johnjhacking)
Nick Sahler
Github: (https://github.com/nicksahler)
Twitter: (https://twitter.com/tensor_bodega)

With Collaboration from:
Harold Hunt

LinkedIn: (https://www.linkedin.com/in/huntharo)

Identification

Over the course of several months, John dealt with remediating a Server-Side Request Forgery vulnerability, several times. A researcher was able to bypass blocking mechanisms, therefore John decided to dig in further to get to the root cause of the vulnerability.

The researcher used the following payload to bypass restrictions:

http://0000.0000.0000.0000:<redactedport#>/<redacted>/<redacted>

Security researchers know that using multiple zeroes is a classic way to bypass localhost blocking when abusing SSRF vulnerabilities. Additionally, it was noted that other variations of 0’s can be used within the 127.0.0.1 and 0.0.0.0 payloads. However, the fundamental issue was a result of localhost skewing, which made it clear to John that the blocking mechanism wasn’t accounting for the variations.

After talking to other colleagues, John got in touch with Harold Hunt who helped him identify the package responsible for the IP blocking mechanism, private-ip.

The code logic was utilizing simple Regular Expression, therefore not accounting for variations of localhost, and other private-ip ranges, as predicted. John and Harold realized that it would be exceedingly difficult to come up with simple regex to cover all of the possibilities, therefore John contacted Nick Sahler and Sick.Codes for their Software Engineering expertise.

Impact

The npm private-ip package has an average of 14,000 downloads weekly - even though the package is roughly four years old. Using the out-of-date version can result in continuous SSRF bypass vulnerabilities, and threat actors can intentionally extract sensitive information and escalate their privileges further. Be advised, Enterprises utilizing this package as a means of preventing SSRF or related vulnerabilities, should upgrade to the latest version immediately.

Broken Logic

The old code was evaluated further, this was the prevention mechanism being used in the index.js:

/^(::f{4}:)?10\\.(\[0-9\]{1,3})\\.(\[0-9\]{1,3})\\.(\[0-9\]{1,3})$/i.test(ip) ||
/^(::f{4}:)?192\\.168\\.(\[0-9\]{1,3})\\.(\[0-9\]{1,3})$/i.test(ip) ||
/^(::f{4}:)?172\\.(1\[6-9\]|2\\d|30|31)\\.(\[0-9\]{1,3})\\.(\[0-9\]{1,3})$/i.test(ip) ||
/^(::f{4}:)?127\\.(\[0-9\]{1,3})\\.(\[0-9\]{1,3})\\.(\[0-9\]{1,3})$/i.test(ip) ||
/^(::f{4}:)?169\\.254\\.(\[0-9\]{1,3})\\.(\[0-9\]{1,3})$/i.test(ip) ||
/^f\[cd\]\[0-9a-f\]{2}:/i.test(ip) ||
/^fe80:/i.test(ip) ||
/^::1$/.test(ip) ||
/^::$/.test(ip)  

There are already several issues within the regex. It’s not comprehensive enough to cover the localhost variations, nor many of the industry standard private IP ranges. Nick and Sick.Codes utilized netmask and rewrote the index.js:

var Netmask = require('netmask').Netmask
function netmaskCheck (params) {
let privateRanges = [
    '0.0.0.0/8',
    '10.0.0.0/8',
    '100.64.0.0/10',
    '127.0.0.0/8',
    '169.254.0.0/16',
    '172.16.0.0/12',
    '192.0.0.0/24',
    '192.0.0.0/29',
    '192.0.0.8/32',
    '192.0.0.9/32',
    '192.0.0.10/32',
    '192.0.0.170/32',
    '192.0.0.171/32',
    '192.0.2.0/24',
    '192.31.196.0/24',
    '192.52.193.0/24',
    '192.88.99.0/24',
    '192.168.0.0/16',
    '192.175.48.0/24',
    '198.18.0.0/15',
    '198.51.100.0/24',
    '203.0.113.0/24',
    '240.0.0.0/4',
    '255.255.255.255/32'
].map(b => new Netmask(b))
for (let r of privateRanges) {
    if (r.contains(params)) return true
  }
  return false
}  

Several other files were modified, but this key-function helped enable the private-ip package to account for the private-ip address ranges and variations, on the byte level. Hackers attempting to bypass SSRF vulnerabilities will now have an exceedingly difficult time because even payloads encoded into hexadecimal, etc, will be recognized as the IP address as if it were not encoded, triggering a conditional block.

Further Analysis and Proofs of Concept

The following Proofs of Concept are by SickCodes and Nick Sahler

Follow Sick.Codes on Twitter: @SickCodes

Follow Nick Sahler on Twitter: @tensor_bodega

Global Impact of CVE-2020-28360

As the time of discovery, there were:

PoC - Using patched private-ip 2.0.0 on tests from 1.0.5

Sick Codes used the ARIN IPv4 Address Space Registry: https://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xhtml

1
2
3
4
5
git clone https://github.com/frenchbread/private-ip.git
cd private-ip
# git checkout master
git checkout 1.0.5 -- ./test.js
npm run test

Running patched private-ip 2.0.0 code against private-ip 1.0.5 test IPs:

  • 17 passed
  • 2 failed

The test ARIN reserved (private) IPs that failed are:

  • 255.38.207.121
  • 250.29.143.180

This indicates that since August 3rd 2016, 2 of the original test IP addresses have actually been reserved addresses.

PoC - Using unpatched private-ip 1.0.5 on new tests from 2.0.0

The new test includes the minimum and maximum of every ipv4 range that is ARIN reserved.

1
2
3
4
5
git clone https://github.com/frenchbread/private-ip.git
cd private-ip
git checkout -f master
git checkout 1.0.5 -- ./src/index.js
npm run test

Using the 1.0.5 RegEx with 2.0.0 test.js

Running private-ip 1.0.5 code against the minimum and maximum of private IP ranges in 2.0.0 test.js:

  • 37 passed
  • 71 failed

This indicates that since August 3rd 2016, a large number of reserved IP ranges have been considered public IP addresses.

An application that relies on private-ip 1.0.5 and below to verify whether an incoming request is to localhost, or a private resource, may perform a request to an local IP address, or an IP address that is reserved, potentially resulting in an SSRF.

Since since August 3rd 2016 the following IP’s, and all of the IP addresses between them, have returned results for private-ip.

The following tests in the updated test.js indicate that these were incorrectly designated as public IPs:

0.0.0.0
0.0.0.1
0.0.0.255
0.0.0.7
0.0.255.255
0.1.255.255
0.15.255.255
0.255.255.254
0.255.255.255
0.63.255.255
100.127.255.254
100.127.255.255
100.64.0.0
100.64.0.1
192.0.0.0
192.0.0.1
192.0.0.10
192.0.0.11
192.0.0.170
192.0.0.171
192.0.0.254
192.0.0.255
192.0.0.6
192.0.0.7
192.0.0.8
192.0.0.9
192.0.2.0
192.0.2.1
192.0.2.254
192.0.2.255
192.175.48.0
192.175.48.1
192.175.48.254
192.175.48.255
192.31.196.0
192.31.196.1
192.31.196.254
192.31.196.255
192.52.193.0
192.52.193.1
192.52.193.254
192.52.193.255
192.88.99.0
192.88.99.1
192.88.99.254
192.88.99.255
198.18.0.0
198.18.0.1
198.19.255.254
198.19.255.255
198.51.100.0
198.51.100.1
198.51.100.254
198.51.100.255
203.0.113.0
203.0.113.1
203.0.113.254
203.0.113.255
240.0.0.0
240.0.0.1
255.0.0.0
255.192.0.0
255.240.0.0
255.254.0.0
255.255.0.0
255.255.255.0
255.255.255.248
255.255.255.254
255.255.255.255
0000.0000.0000.0000

All of the above IPs can result in SSRF in private-ip v1.0.5.

As this is a server-side package, it is difficult to ascertain the exact magnitude of use, as there are an incalculable number of server-side projects that use private-ip internally.

You can find Nick & I (Sick.Codes)’s pull request to fix the private-ip package here: https://github.com/frenchbread/private-ip/pull/2

References

https://www.npmjs.com/package/private-ip
https://johnjhacking.com/blog/cve-2020-28360
https://github.com/sickcodes/security/blob/master/advisories/SICK-2020-022.md
https://sick.codes/sick-2020-022
https://twitter.com/johnjhacking
https://www.linkedin.com/in/huntharo
https://twitter.com/sickcodes
https://twitter.com/tensor_bodega

CVE Links

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-28360
https://nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-28360