What’s it sound like when a million scanners all send an invalid payload at once? And to make it doubly interesting: what happens when everybody thinks it’s real (except, of course, the actual target)? Feels a bit too much like Spiderman
The vulnerability
I ran into this vulnerability totally at random: CVE-2021-32030, which is an authentication bypass on ASUS GT-AC2900 devices before 3.0.0.4.386.42643. I don’t actually have access to one of those, so this is all based on public write-ups or proofs of concept.
Atredis posted a blog on “May 6” (they don’t say which year, but the metadata says 2021) detailing this vulnerability. The blog is awesomely detailed, but the important part is:
I found that the validation of this cookie fails when the following occurs:
The submitted
asus_token
starts with a Null (0x0)The request User-Agent matches an internal service UA (
asusrouter--
)The device has not been configured with an
ifttt_token
(default state)
The first of those points is the important one, so we’re just going to focus on that.
What’s a “null byte”?
So, “null bytes” or (as I prefer) “NUL bytes” are tricksy little creatureses. They hide in plain site. You can’t actually see them. They do a lot of fun stuff, though, like terminating strings in low-level languages (like C). Because some languages (like PHP, Ruby, Java, etc) see a NUL byte as a normal character whereas other languages (like C) treat them specially, they can often lead to fun bypasses when multiple languages are used together.
You can create a NUL byte on a terminal using echo -e
and \0
or \x00
(they’re equivalent). But when you do, you’ll see nothing:
$ echo -e "Hello\x00World" HelloWorld
Is there a NUL byte there? I dunno, I don’t know if it copied/pasted correctly. If I copy/paste it back into a terminal, it doesn’t look like it kept the NUL.
It looks like if I pipe the echo
directly onto my clipboard, it works:
$ echo -e "Hello\x00World" | xclip
And I can paste it here:
HelloWorld
I can see it in my editor (vim
) as a ^@
symbol, but what’s that going to look like when this renders? Will our software strip it? Terminate the post? Crash? Who knows! I’m kinda hoping I break our pipeline so I can add a new paragraph about how meta this all gets. If you’re reading this, I guess it worked okay.
Anyhoo, when you’re doing a write-up for the public about an exploit that requires a NUL byte, what do you do! How do you indicate something invisible is there?
Well, Atredis makes the completely reasonable decision to use a literal \0
character, since that’s how it’s typically represented:
GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: 192.168.1.107:8443
Content-Length: 0
User-Agent: asusrouter--
Connection: close
Referer: https://192.168.1.107:8443/ Cookie: asus_token=\0Invalid; clickedItem_tab=0
That \0
is a backslash and then a zero, but it’s supposed to represent a NUL byte. If an experienced human reads that, they’ll probably know it’s supposed to mean. But if somebody’s quickly copying/pasting it, or an LLM is stealing it, or whatever, they probably don’t know that it’s supposed to represent an invisible byte.
How exploits go pro
Can you see what’s going to happen? After this was published on May 6, 2021, somebody published a Nuclei template for it on August 16, 2021. In the PR, they helpfully indicate that it was not “validated locally”.
The template, of course (we wouldn’t be writing this blog otherwise), includes the \0
literally:
requests:
- raw:
- |
GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: {{Hostname}}
User-Agent: asusrouter--
Connection: close
Referer: {{BaseURL}} Cookie: asus_token=\0Invalid; clickedItem_tab=0
I’m not super familiar with how Nuclei does things, but I don’t think that it renders that as a proper hex escape. The reason I don’t think so is because we run honeypots - like, a lot of honeypots (it’s kind of our thing). And we’ve seen 230,999 hits (with the backslash) in the past week across our legacy fleet, and 148,869 hits on our newer (currently much smaller but more targeted) fleet.
Even better, some of them have one backslash like the template:
GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: <snip>
User-Agent: asusrouter--
Connection: close
Cookie: asus_token=\0Invalid; clickedItem_tab=0
Referer: <snip> Accept-Encoding: gzip
While others pick up a second backslash from somewhere (presumably the request passed through something that re-escaped it):
GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: <snip>
User-Agent: asusrouter--
Connection: close
Cookie: asus_token=\\0Invalid; clickedItem_tab=0
Referer: <snip> Accept-Encoding: gzip
I guess the poorly-escaped NUL byte check also got poorly escaped! Maybe if we leave this long enough, we’ll start seeing \\\\0
and \\\\\\\\0
in payloads. Who knows? Cookies can be up to 4096 bytes long, so there’s lots of room for more backslashes! log2(4096) = 12
, so we can double the number of backslashes like 11 times before running out of space.
I did the same query for the real exploit (ie, one that actually contains a NUL byte) - which, by the way, is quite difficult because, as discussed, you can’t just type one! - and I found zero hits. Not a single scanner is using this exploit correctly - that’s actually kinda remarkable!
So in case you’re just tuning in, the score for the past week is:
- Poorly copied/pasted exploit: 379,868 total hits
- Correctly escaped real exploit that might work: 0 total hits
Intent?
We have a tag for this. Until I peered under this rock, our tag was also looking for a literal \0
; however, I updated it to match the actual exploit.
The people sending the broken payload still probably have malicious intent, but we don’t really want to tag payloads that don’t actually exploit things.
It kinda puts us in a weird situation, and I’m still trying to figure out the best way to handle this!
Conclusion
Maybe test your exploits before contributing them to an open source tool, eh?