Attack scenario
The attack scenario is quite simple. If Node.js (<15.2.1,<14.15.1,<12.19.1) invokes one of the wonderful functions in the "dns" core library with a user-controlled name.
When DNS request is invoked in Node.JS (i.e: "dns.resolve4/6/Any" are called) an attacker crafts a DNS response with several DNS records (several > 256).
Node.Js will crash upon parsing the response.
For example, my PoC (uses "AAAA" records in the reply):
- If the response contains "AAAA" records -> it will trigger assertion failure, before OOB read at cares_wrap.cc:1256. This is exactly what we've got at the PoC, source-code here:
- If the response contains "A" records -> it will give an OOB read of the TTL records, as shown in cares_wrap.cc:756. it will then place back the resulting TTL's with that leak back to the caller function, one can observer it here:
Why it happens?
In short, the problem is actually with the c-ares library, which executes the async DNS requests and parse responses. when parsing DNS responses, some parsers compute, incorrectly, the length of the returned array of TTL values. This leads to incorrect size in the function output, i.e: the "naddrttls" value is incorrect. this will trigger the CHECK seen before.
Intro to c-ares library
For example take the function "ares_parse_aaaa_reply". This function is responsible for parsing the AAAA replies when getting DNS response in c-ares:
This function gets char* abuffer - that contains the response to parse, alen - the response length, host - a ptr to struct to return with dns answer, addrttls / naddrttls - that contains the TTL's array (for the returned addresses, how much time till they expired) and length of this array correspondingly.
Now, note that addrttls - is actually a pointer to array that should be filled with the returned addresses TTL's.
Also note that naddrttls - is actually also a pointer that is both input and output, at start, it contains the maximal values of TTL's to return.
So, what's the problem? what happens if the number of replies in the response is bigger than the array we can consume of addrttls. specifically: the addrttls (the TTLs array of the response packet) is bigger than the input addrttls?
Great question indeed, Regarding addrttls, it is actually truncated in some manner. "naddrttls" is being checked and our function doesn't overwrite "addrttls". So, the response is "truncated" - and only the first 256 TTL's will be considered and returned.
But, wait, how about naddrttls (which counts how many values was written)?
They incorrectly count only the number of replies in the response and not the number of TTL's actually being written...
The c-ares problem: step-by-step
1. First time counting naddrs (doesn't consider naddrttls given):
2. Writing the real TTL's back to the array (the addrttls, truncated according the naddrttls given):
3. Writing the naddrttls back - placing incorrectly just "naddrs" without checking if it's value was larger than what was really put there.
so, it means the returned naddrttls will be bigger than the real number of replies TTL's that have been written.
To fix it, they've just truncate the naddrs to the maximal size available in the response array. you can see it here in the fixed source-code:
From Node.JS code to OOB Read/Fail-Check
The flow in server side starts after getting DNS response, with one of the "Parse" functions according to the dns request made with the dns library.
For example, "dns.resolve4" in Node.js will trigger the parser function named "QueryAWrap::Parse" with the DNS-server reply. The parsing functions are in: /src/cares_wrap.cc in the node-js sources.
Some flow for example:
Parse->ParseGeneralReply->ares_parse_aaaa_reply(...).
Conclusion & Future ideas
CVE-2020-8277 poses a great threat to web-servers deployed over Node.Js with versions < 15.2.1, < 14.15.1, and < 12.19.1. It can cause an easy DOS where user deploys its own DNS servers/responding with arbitrary DNS responses.
Also, in some rare cases, one can think about malicious ways to leak arbitrary heap data.
That's because the TTL will be dependant on that data with respect to A records, so triggering this vuln with A records and observing the timeouts in which the server yields another DNS request might give attacker ability to predict some heaps vals / characteristics as well. cool cool :)