- [Sick Codes] Hey, everyone.
Welcome to rotten code, aging standards,
and pwning IPv4 parsing across
nearly every mainstream programming language.
Let's get started.
Sort of starting off with a meme here I got from JF
or Joe Slowik's Twitter account.
Everyone, please stay calm.
We're releasing PoCs for the unpatched vulnerabilities
so you can better evaluate security posture.
I thought this was fantastic
and I asked Joe if it was cool
if I could put it in the presentation.
Just as a quick disclaimer.
None of the research today was paid for.
It's all in good faith.
Nothing today represents our employers,
future, past or present.
None of us are under any gag orders
apart from vulnerabilities that are still pending,
which you'll see on the next slide.
All of the content in the slides is creative commons zero,
and all the trademarks logos, blah, blah, blah,
are property of the respective owners.
So what will we be discussing today?
So we'll be discussing today a litany of CVEs.
These are CVEs that we accumulated
over the course of about the last,
I'd say nine months,
with some of them still pending because they're quite large
and they take a bit of time to fix, obviously.
So, two of them we'll be releasing today,
but they've never been seen before.
And we confirm with all the vendors
and everything like that,
that it's cool to talk about them,
including the Oracle one we've mentioned below.
We'll get into that a bit soon.
So the format of today's talk is
how did we find this type of vulnerability?
How you as a researcher
or a hacker can horizontally scale your vulnerabilities,
whether that be for good or for bad.
Some proof of concepts for some of the vulnerabilities.
The exploitability of a vulnerability
of this caliber or type
and further vectors or attacks
or research that can be conducted by anyone
who is willing to get involved.
Some takeaways we want you to take out of this
is that every day is patch Tuesday.
Patch Tuesday is a meme.
No longer exists,
except for maybe at Microsoft and Oracle.
And how do you patch an entire class
of vulnerabilities this size?
And just a quick apologies to anyone
who has to leave the conference early,
because some of these vulnerabilities
might affect some of your code bases.
And lastly, we just wanna show you
how to exponentially magnify some of your research,
threat models or thought models to magnify those attacks,
and once again, horizontally scaling your research,
so applying it across all different types of languages.
And also, the ability to pick up existing vulnerabilities
and apply that across existing code bases,
whether that be finding a vulnerability in one language
and then applying it in the next language
and so on and so forth.
So I just wanna start with a quick picture
of a typical NIST CVE scorecard, basically 9.8.
That's the maximum you can get for a vulnerability,
excluding scoping changed or unchanged.
And this is or what you can expect
from a pretty high caliber vulnerability
where you can get remote code execution
or server side request forgery or things like that.
And just quickly as an intro,
you're listening currently to Sick Codes
and you'll be hearing from Kelly Kaoudis soon.
We'll be presenting work based on some of the work
that we did with Johnjhacking, Nick Sahler,
Victor Viale, Cheng Xu,
and Harold Hunt.
So I just wanna start with the octal numbering system.
Most people will understand this numbering system,
but basically there's no eight or nine.
You can kinda visualize this numbering system
as in counting to 10,
except you're counting with closed fists
and you're counting from zero
and you've only got eight fingers,
therefore you're counting for the maximum of seven,
and then you just start from the next number up.
So that would be 10, which would be one and zero.
And how do you get to these numbering system
from what we know as decimal?
Well, the easiest way to do it is
you just go from left to right
on a number that's being prefixed
with the appropriate octal formatting.
You basically start from left to right,
and then do eight times the power.
So this number here has three digits,
therefore we go two, one, and zero.
So eight times two, eight times one, eight times zero,
and then we multiply that by each digit in each box.
So one times eight to the power of two,
zero times eight to the power of one,
and then zero times eight to the power of zero.
And this one's really easy
because one times eight to the power of two.
Eight to the power of two is 64,
so one times 64.
And the other two multiply by zero or nothing.
Therefore the answer is just adding it all up.
You get 64.
Now let's go to a more interesting case.
We got the one at the front again. So we've got 64.
Second one there, eight times two is 16.
And then the last one there,
anything to the power of zero is one,
therefore seven times one is seven.
Add it all up, you got 87.
And then this is what we traditionally know as decimal
or base 10.
This is what we've got 10 fingers for.
One times, this is exactly what you do
when you're normally counting numbers
as you got your hundreds, your tens, and your ones.
So ten to the power of two is 100,
to the power of one is 10,
10 to the power of zero is zero.
Pardon me, it's one.
And then you got one times a hundred is 100,
and then you got your two times 20,
two times 10 is 20,
and seven times one is seven.
So you get 127.
That's your typical base 10
or your real life numbering system that you currently know
as decimal or regular numbers.
To go backwards from octal between decimal,
to get from decimal to octal,
we do remainders.
So 127 divided by eight is 15 remainder one.
Keep the remainder on the right-hand side.
15 over eight is one remainder seven,
and then one remainder eight is zero reminder seven,
and then you get the octal number is the right-hand digits
basically you lined up next to each other, 177.
So notably from that example,
you would've obviously been looking at the 127,
a really recognizable number.
This is check host.
If anyone doesn't know, it's an awesome website,
Russian website, I believe.
There's no capture or anything like.
You just plug in the IP address.
You can ping it.
You can mainly, one of the things I get out of it
is you can discern between DBIP, MaxMind,
and I think it's IP2address or something like that.
You can maximize your (indistinct) by comparing the three
and calculating where a database
is getting its geolocation from in some cases.
So 0177, if you put that in your address bar,
you'll actually go through to your home address.
But if you put that without the zero in the front,
which we'll allude to later,
you actually get to a Brazilian IP.
And I'm not gonna blur this IP address out
'cause it's pretty well known,
but 177.0.0.1 is somewhere in the middle of Brazil.
And then the reverse of this is the resolution of 0127,
which goes to 87.0.0.1,
and that's some random one in Italy.
I think before we were looking at this,
that was owned by Level Three Communications,
but it looks like someone else has actually
taken up the roster on that IP address there,
which is quite interesting.
So this actually, this vulnerability,
as you can see where it's kind of heading,
is based on the fact that some parsing libraries,
I'd say all of them, pretty much,
have some sort of nuances in the way
that they parse IP addresses,
and that Daniel Stenberg,
who runs the curl project,
he fixed this in 2018 on the Linux or GNU side
where 177 with a zero in the front,
which means octal,
would actually resolve to the funny address
that's located there in Brazil.
And he called it a funny host,
which I thought was quite hilarious.
And the part of this presentation we're alluding to
in the terms of rotten code is that a lot of people
have different ideas of how code works
and implementations and standards and changing schema,
things like that.
It's not a blame game,
but a lot of languages have turned bugs into features
or things that they've added along the way
have actually been the incorrect way to add them
or there's typos or there's manipulation
or even there might be code that's being inserted
maliciously through, I guess, rogue commits.
But another part we wanna allude to is how well tested
is some of the libraries you've been using
and how well do you trust those libraries?
This is interesting excerpt we got from Wikipedia
in the term of octal.
There's a lot of different ways you can prefix numbers
to represent octal numbers.
A lot of people might remember the backslash 73
as being the most common way of referring to octal,
where by 0o is actually typical of octal
and then 0x is typical of hexadecimal,
and that's true in C.
And then furthermore,
that was actually taken up by Python, OCaml,
Haskell, Raku, Ruby, TCL, PHP,
and then we've got ECMAScript,
which is actually a JavaScript space
rule structure or standards.
And we'll get into that a bit later on,
but there's actually some confusion there
based around the standards
so that ECMA 6 and ECMA 3 and ECMA 5 are all different
in the way that they should or shouldn't pass octal values.
And this has been "discouraged," in quotation marks.
That does not mean that it's been employed,
but I'll just quickly say for the record,
you can use use strict to avoid this kind of instance.
So a lot of IoT devices,
they don't have the sort of capability
or even memory allowed
or memory allocated to the device
where it can actually realistically
or reliably parse an IP address.
Parsing IP addresses should actually be
the same sort of meme as passing HTML.
IP addresses are a bit complicated
in that if you do a Wireshark,
you'll see the MAC address pop up,
you'll see the IP address pop up in hexadecimal,
but what you want to see is octal, okay?
So that's a little thing that a lot of people are missing
is the octal usage of numbers.
Everything boils back down to libc.
So depending which operating system you're on,
Windows can, we'll keep Windows aside for the moment,
but all of it's based on translating
network addresses from hexadecimal into something readable,
into all that 16-bit network representation,
32-bit, et cetera, et cetera,
or even into the octal format.
The reason is for using the,
the reason we do that is to obviously
use it in other programs.
So programs to program communication.
So this is really funny.
Me yelling at my computer because my code isn't doing
what I want it to do,
and then on the other side,
my code is doing exactly what it was told to do.
In this picture here, we've got a...
This is just a Wireshark dump just to sort of emphasize
the packet itself
and what you can actually find out of the packet.
It lets you click on the different parts of the packet
and identify what's there.
Obviously if you're a bit more savvy
with the network address translation,
you can just see the bytes in themselves,
or the other way to do it is just to click
on the source address.
For example, 10.8.160.235.
I believe I'm behind a VPN.
And then you can see each individual byte there.
The four bytes are represented
as the actual integer values, 10, 8, 160, 235,
and that's the full IP address.
But where did the dots come in from?
Well, the dots come in from translating.
So they're not actually part of the packet.
That's where kind of the issue lies
is that most of the translational errors
come from people not understanding that
or skipping parts or just human error.
But the only real reason that people use octal
is kind of like just basically for testing out SSRF.
And that brings us to our first entry to the story here,
which is the nodejs project private-ip,
which has got something like 12,000 weekly downloads.
We came across this about nine months ago when John Jackson
and Harold came across this vulnerability
when they were working.
And one of the vulnerability reports they received
was in relation to SSRF.
So someone was able to request server files
belonged to the server when in the real world,
they weren't actually supposed to receive those files.
And the way they used that was using SSRF techniques
involving submitting octal numbers to the module
and getting to a public facing module
and getting back private files that belong to the server.
And just for reiteration here,
this has 12,000 weekly downloads to this project.
The project hadn't made updates for about three years,
and what it previously had
was a bunch of regular expressions
in a form that I would probably say is unmaintainable.
Some people would beg to differ,
but the maintainability of regex in this format
is quite hard for someone to just come along as a newbie,
particularly if they don't know regex
or if they're not that strong or familiar with it.
But there's better ways to do this.
What we thought at the time,
a better way to do it was using big set of ranges.
So you can see here, we got let private ranges,
we've got a bunch of private ranges here that we allocated.
And just here just quickly just showing you
the private internets.
This is just a really classic document from 1996
where they put the reserve ranges as specified.
These were the three basic ones at the start.
As you clearly see, point number six,
security's not addressed in this memo.
Some examples of where public IP addresses may
or may not be used is like arrival
and departure terminal displays at airports,
which we now know clearly need TCP/IP,
and then cash registers.
Obviously they use TCP/IP connected to the web now.
Money machines, all that sort of stuff.
So they got some of this wrong.
Banks weren't supposed to be connected to the net,
and now everyone's literally banking on the net.
So, and then yeah,
two of the addresses that we had in the tests
were actually false.
They're actually incorrect
'cause they're actually private IPs,
so they shouldn't have been
in the public IP address test case.
For the original package private IP,
we removed those, obviously,
and then we added a bunch more test cases there.
This is the new IPv6 rejects that Vasco,
another maintainer from the lib-ptp, I think,
they added IPv6.
As you can see, regex is regex.
You either love it or hate it.
I love it.
However, in this kind of context,
it's not really maintainable to a level that we'd expect
for application that's used by X amount
of thousand dependencies,
and that's probably the reason why the original package
was not updated for three years.
And regex.
So here we're looking at the second module
that we found was netmask.
It's the one we added.
You're looking at 3 million weekly downloads.
This is a big one. This is a lot bigger than that other one.
I'd say in magnitude, an order of magnitude larger.
9.8 critical for this one with Harold and John Jackson.
So here's the first CVE.
We had insufficient regex in private IP 1.0.5
was insufficiently filtering ranges
that were actually supposed to be private ranges.
And that's why people could actually submit URLs
to applications using this package
and actually get files that belong to the server,
which is obviously disastrous.
And then after that,
because we'd actually entered the netmask project in there,
someone actually came and found another vulnerability,
and then one thing led to another,
and we actually found out
that netmask itself was vulnerable,
which was hilarious.
And you can see here that it's pretty much
exactly the same vulnerability.
And then another guy named Ryatack came in midway
or another researcher named Ryatack,
he came in halfway and actually found another CVE,
because the complete fix wasn't actually correct.
And this was only scored a 5.3,
'cause I think it was only out for, like, two days,
although it is the same impact as the first one.
- [Kelly] So we're in tmux in a container.
I just wanted to run through the netmask CVE.
So this is the last vulnerable version
before we started making changes with the maintainer.
And we're just picking an arbitrary CIDR block, 31.0.0.0/8.
First address in this block is 31.0.0.1.
Last address.
And so then this block should contain 31.5.5.5.
However, this block should not contain 031.5.5.5
because 031 is 25 in decimal,
if we convert it properly.
Now we'll see the same thing in coffee.
I wanted to show coffee because netmask itself
was originally written in coffee
and it's transpiled to node.
Okay, that's the same.
And unfortunately that is also the same.
Now, why does this happen?
Underneath in the netmask library,
this function parseInt is being called.
And so parseInt,
if it's not passed a base as the second argument,
will just chop that leading zero right off
and pretend it got a decimal.
When 031 should be 25,
it'll just come out as 31.
And we're gonna see the same thing in coffee, unfortunately,
even though coffee has the advantage
of not accepting leading zero notation otherwise, right?
So we should be using 0o0 if we really want octal in coffee.
All right, so what?
So back in 2018 again,
Nicolas Gregoire responds to curl guy tweeting about octal.
He says, yeah, bad octal conversions are a legit way
that we can use to get around anti-SSRF filters.
If you haven't heard of Nicolas,
he's associated with PortSwigger
as an official Burp Suite trainer.
He teaches people how to use one of the most popular
web app hacking tools in the world.
And he references his talk from AppSec Euro 2015
and says, yeah, if you're interested,
checkout pages 24 to 28 of my slides.
So it's pretty clear that this is an accepted way
to get to a place where we can achieve SSRF.
Once we can bypass input validation,
we can potentially execute local code.
Around the time we were doing this research in March,
the node netmask package had about
238 million lifetime downloads.
So it's a bit more popular than private IP.
It had around 2.8 million people downloading it
directly every week.
About 170 packages public in npm declare it as a dependency.
289,515 packages on GitHub declared it as a dependent
last we checked the dependency graph, as well.
So it's pretty popular for a node package, I'd say.
In Ax's original write-up on bleeping computer,
he mentioned, perhaps rightfully,
the Perl component Net::Netmask,
which inspired node netmask.
Net::Netmask had the identical flaw,
and its maintainer Joelle was very quick to file a CVE
and push out a fix pretty much
as soon as the article came out or slightly before,
which is very commendable.
And it also kind of lit a fire under us.
We were having a good time looking at things,
or at least Sick, John, and I were.
So then we started looking at other CIDR parsers
and trying to see, well, okay,
what else could there be out there in the world
that does this exact same thing
and doesn't quite work the way the maintainer intended?
So the last time this package was updated was 2016,
which is fairly reasonable.
CIDR parsers don't exactly change a whole lot.
This is another package,
a bit like Net::Netmask,
which inspired node netmask within the Perl ecosystem.
So we've installed the vulnerable version.
We've got our proof of concept on the right.
And the deal with this package is
you're supposed to use a guard function is_ipv4
before you call isprivate_ipv4
or ispublic_ipv4.
And we didn't do that because it wasn't documented
that you needed to do that before the fix.
The documentation was changed as part of the fix.
So we're just gonna run it
and show you the output here real quick.
And so like with netmask and everything else,
the idea here is an attacker could submit input data
which is an octal which should be private
and ends up pointing to something
that is a false negative,
which results in SSRF bypass.
And the same thing with things
that should be public and are not.
So there's a couple of things that do work here,
a couple of things that don't work here.
If you wanted to exploit this,
you would just sort of have to experiment
to see what was going on.
But the trick is guard function.
- [Sick Codes] If you're a little bit confused,
you can actually go to sick.codes,
and in the releases section,
you'll find a big writeup
about the original netmask vulnerability
and how it affects you.
It's actually quite a large writeup.
We go into all the details about that vulnerability.
And here's sort of a table
where you can compare netmask to decimal
and then the reverse of that
and what you should expect.
But what I'd like to point out is the comments section
So in the comments section,
"Is there a reason for the double check?"
So that was actually a small zero day
that came through in the comments section.
Another one in the comments section was
padding for an anonymous user.
So IP address, I mean,
these are just zero days getting dropped
into the comments section,
which we, I didn't approve them at start,
but I did actually end up approving them
'cause it's been enough time.
So you can zero pad your octals.
You can do all sorts of crazy stuff.
And then Lorens,
"The only reasonable thing is to consider the IP invalid.
Trying to convince some people
that it should be considered is futile."
And I tend to agree with Lorens
because only IP addresses without eights
are in the octal range alphabet,
so you literally are missing 20%
of your entire address space.
So, yeah.
And one comment from Tony Chung.
I tried to get in contact with Tony.
He was one of the guys that mentioned to us early on
that Java is probably worth looking at,
and we did actually end up looking at that,
and emailed Tony.
And Tony, I hope you hear this,
but yeah, appreciate the heads up.
I couldn't approve your comment at the time
because it was technically a zero day,
but we've been in touch with him.
Hopefully ping back.
- [Kelly] When you're initially learning
about a type of vulnerability,
it may make sense to try and find it everywhere you can.
Like if you're trying to figure out
how reflected XSS works,
it can be interesting to try that
on basically everything you come across.
And so we applied that to octal a bit.
We knew we had some leads to go on
from some of the things people were saying,
and there are just so many vulnerable CIDR parsers
that even when we were putting this talk together,
we were still finding more.
So not only is it possible that there are more out there
that are vulnerable like this,
it is quite likely.
- [Sick Codes] So if you didn't really get
what's going on at the moment,
basically we're just applying the same CVE
to a whole bunch of different languages,
a Python, Perl, Golang,
Java, JavaScript in the form of nodejs,
and another Perl one.
And here's the one by Dave.
So we opened a issue at Chromium project
stating that the V8 engine is wrong
because eight doesn't actually exist in the octal alphabet.
After the number seven is 10,
and that's relating to decimal only.
So it's apparently working as intended.
And we opened up an issue on ECMA,
and they've said they don't wanna fix the issue
because it's too old.
It's not even a bug, apparently.
But yeah, so you have to be, just have to deal with it.
- [Kelly] Going over to the Mozilla developer reference
for a second to check out what it says about these errors
that we were discussing with the Chromium team,
we see a couple of things here.
So firstly, as mentioned by the Chromium team,
octal literals, the leading zero format literals
which represent octal integers,
will throw an error in strict mode,
which is nice.
But this doesn't exactly help us with either netmask
or with the behavior of parseInt,
which still allows zero prefix literals input as strings
or integers even in strict mode.
So the second thing is JavaScript mixes zero prefix literals
that get interpreted as decimal
with zero prefix literals that get interpreted as octal.
And we find this one of the weirdest footguns
we've encountered so far with JavaScript
aside from parseInt's behavior.
To explain this a little bit,
since the only digits in base eight are zero through seven,
this means there are some extra zero prefixed
integer literals allowed by the ECMA spec,
and they get interpreted as decimal.
- [Sick Codes] After the chromium project,
we opened one at nodejs itself
to see if they could do something about it.
Mozilla apparently doesn't treat it
as illegal octal constant.
That's because it isn't.
There's like, there's only seven numbers.
And so V8's rejected it,
nodejs rejected it,
and pretty much then the last opportunity we had
to raise the issue is with ECMA itself.
And I said that these numbers don't exist.
They said websites will break.
And the end of the discussion is no one has claimed
this doesn't make sense,
just that we can't change it
because it will break a bunch of websites.
So the bug has moved into feature status.
So here we'll present a Rust CVE, Golang CVE,
a Python one, and some Oracle stuff.
- [Kelly] So Cheng sort of decided for Rust
that they would just reject zero prefixed octets,
which is likely the right decision.
So leading zero on IP string interpreted as octal literals.
A simple demo here.
We see that 0127 becomes 127 when it shouldn't be.
Yeah, this may cause security vulnerabilities
in certain cases.
The specification also allows hex formats in IP strings.
Yeah, so the end result of this is
only decimal is allowed in Rust,
which is better for user input.
Yeah, it's not at all obvious
that we should parse octal IP addresses.
It seems exceedingly unlikely to come up
outside of security advisories.
Yeah. (laughs)
So this made it into 1.5 3.0,
which is super cool.
Congrats, Cheng.
Fixing the comments,
fixing the code and referencing the same RFC draft
as the Python thread did, interestingly.
So, which says that octal is a rare format
for user input for IP addresses
and we shouldn't parse it.
Yeah, if number starting with zero is not none,
then we don't, you know,
we don't wanna mess with that octet.
So tests to make sure that octal things are error,
hex things are error,
and let's just run the little proof of concept from our CVE.
So this is the version of Rust from before
the one with Cheng's fix in it.
I'm gonna compile it real quick.
And then you'll notice that we're changing
individual octets at a time.
So second octet, third octet, fourth octet,
and then the first octet.
So we've got 026, 026, 093,
and then 099 at the very bottom,
and none of these are translated correctly.
You know, same parsing logic applying to all the octets.
With Golang, folks have been talking about limiting
the size of allowed octets for a while.
So Golang would allow you to just throw a crap ton of zeros
in front of something
and allow it to still be treated as decimal.
So, like, 00000192 would still be 192.
But really all you need is three digits to get to 255,
which is the max size in decimal for an octet.
So when our writeup came out,
people started talking about it in the same thread,
and Golang eventually decided to mimic what Rust did
and return an error on zero prefix input,
which is good because it avoids the ambiguity
of allowing both octal and decimal,
especially when zero prefixed octets are probably just
a footgun that most would rather not have.
So here's the change.
It made it into 1.17.
Thanks, Roland.
Docs change.
We now reject IPv4 addresses
which contain decimal components,
octets with leading zeros.
And then this is just a little check
on that first digit, I guess.
Rejection on zero components with leading zeros.
Boom.
Here's our proof of concept from the CVE.
And we're looking at octets one at a time again.
The last two little sections here
are just trying hexadecimal.
We're trying some of the things
that we knew worked with netmask
and kind of shouldn't have.
So we're trying, you know,
some CIDR pursers will also parse IP addresses
that are just a single integer,
and so that's what the last one is.
Second to last is just throwing a random hexadecimal octet
in there for grins.
So we've got it built.
We're running it.
Yeah, so the hexadecimal ones don't work,
which is encouraging,
but the octal ones do.
That zero on, that leading zero on those octets there
in octal format just gets stripped off,
and so just the same as decimal
is how they end up being treated,
which is not what we want
of all the languages we looked at
that we found to be vulnerable,
I think Java was my favorite.
So we're running 11,
which is the LTS version.
So compiling it real quick.
Yeah. So we're looking at one octet at a time again,
and up to 255,
we are interpreting as decimal.
We're just stripping that leading zero right off.
And then 0256 becomes 174.
Hexadecimal, on the other hand,
seems to be fine.
Fewer dotted quads,
which is a format I noticed in the documentation,
seems to work out fine.
What else? Get by address.
Inet address get by address seems to work fine.
Java net URI seems to be fine.
Java net URL seems to be fine.
It's just inet address get by name.
And funny thing about stopping interpreting
as decimal at 255.
Probably somewhere there's something that's looking
at three digits out of an octet
and trying to figure out what it is.
So after some back and forth
with the Oracle security bug triage team,
where we were like, well, it's broken,
and they were like, well, we're not really sure why,
and also it performs differently in different environments.
We simply weren't sure whether for ourselves the issue
resides with the underlying C libraries per operating system
or with the JVM or with the Java compiler.
- [Sick Codes] And obviously the question comes in there
where Java actually is supposed to run
in a virtual machine, AKA JVM.
The difference is basically that if you run Java code
just on any operating system without a JVM,
obviously you'll get different results,
and the benefit of that is that you can see
the vulnerability being played out between applications.
However, it won't actually play out in the real world
unless you're using Java outside of the JVM.
- [Kelly] So we tested it against a couple of different JVMs
just to see what would happen
and as well across different operating systems.
So JVM wise, we tested using HotSpot and also against Grel,
and we saw our proof of concept code performed consistently.
However, we did see different results
across different operating systems.
- [Sick Codes] That's where obviously the security team
from Oracle decided to just say,
it's not really a bug,
and it's the responsibility.
the responsibility is actually downstream related to libc
and does not affect Oracle's product.
And we asked them if it was cool to actually discuss that,
and they said yes,
so that's where we are.
- [Kelly] So these are just some examples of CIDR parsers
where we took the time to investigate
and discovered unique ways for handling octal.
The point we're trying to make in this talk is
very few people actually handle
the octal IP address format correctly.
Some of the other languages which we didn't look at,
but could have,
include LeWitt and Ruby,
and maybe someone else with interest
can come along behind us
and take a peek at some of the other things out there.
During this work,
we came across a wide variety of articles and blog posts
and just opinions about how IPv4 parsing
should actually happen,
but not so much in the way of a single formal definition,
from a discussion around a reference IPv4 parser
and a blog post that Dave Anderson wrote back in December,
for such a simple seeming idea,
it is hard in general for people to agree
on what an IPv4 address actually should be.
Well, I've read the spec,
but most of it is cursed,
so I'll just implement this subset
which fits my definition of acceptable.
The textual representation of IPv4
still doesn't have a single standard.
Grammars or other specifications for the IPv4 address format
do come up occasionally in RFCs like 1918,
which is the private internet one
that we talked about earlier,
and a variety of drafts that were simply never ratified
for one reason or another,
and incidentally also in the definition for IPv6.
The representation has evolved over time,
and this in addition to people simply not knowing
where to turn has led to a bunch
of slightly different definitions
that mostly interoperate,
but sometimes just clash in fabulous ways.
A handful of the reference definitions out there simply say
something like dotted decimal form
and just leave the rest as an exercise to the reader.
If you want to truly parse IPv4 addresses,
this is the bullshit you have to put up with.
And we do.
IPv4 is still the most widely adopted
IP address format currently.
IPv6 has, according to the Internet Society,
been operationally deployed somewhere since at least 2002
and became standardized just in 2017.
Currently only around 35% of the internet
even supports IPv6,
according to Google stats.
So until IPv6 achieves wider adoption,
we'll just have to keep on with IPv4, as well.
Pretty much anything we use to purse IP addresses,
whether it was originally intended
to parse IP addresses or not,
is just gonna have some interesting
ifs, ands, and gotchas.
So back in Cheng's Rust pull request,
he mentioned RFC draft 6943,
which is from all the way back in 2013.
If the parties involved in a security decision
use different algorithms to compare identifiers,
then failure scenarios ranging from denial of service
to elevation of privilege can result.
The authors do a decent job of walking through
exactly what we've shown in this talk.
And so here's a little table.
They're using a couple of different terms,
false positive and false negative.
A false positive means two identifiers were equal,
but should refer to two different things,
like the octal literals parsing as decimal literals
in netmask and other places.
For example, 0177 equaling 177.
So on the other hand,
a false negative would happen when two things
should have been equal, but were not,
like 0177 not equaling 127 when compared.
This draft has existed since before a lot of the code
that we demoed today.
Not everyone reads spec before trying to implement something
or even knows it would be useful.
But it does come in handy as a researcher
when you're trying to understand
how something would behave ideally.
Back in 2019, Python decided they wanted to remove any
and all checks for octal.
Just, you know, it's a rare IP format.
Who really needs that?
Gosh.
So here's the pull request where it happened.
So here's changing the tests.
No more error message.
No more ambiguous rejection.
Leading zeros are ignored
and are no longer assumed to specify octal octets.
Yeah, that's the good part.
And so we're gonna look at the issue
where this pull request is discussed
and which was reopened following our Net::Netmask report
here again in a second.
And so they're actually referencing an RFC
which rightly says octal is a rare format.
You shouldn't handle it.
Unfortunately, that leads to inconsistencies
with other programming languages,
utilities written in other programming languages,
the underlying operating system.
So you can't really ignore it,
even though you'd rather once,
now that we've got octal, we've sort of all got octal.
So in this case,
having octal off by default would be a better outcome,
but that wasn't what they did.
So we're gonna look at Python 3.8.0,
which is a vulnerable version,
and we're gonna run this little proof of concept
which Victor and Sick wrote here.
We're gonna run it with sudo
so that the network calls ping work.
So you'll notice that Linux is handling 010 correctly,
but Python is not,
just like we saw in the pull request.
Yeah.
We've got our suspect IP, right?
And we've translated it using the IP address call
and then we've fed it into ping twice,
once as itself
and once as translated into the bad IP.
Bad IP stays bad.
Suspect gets translated correctly.
We got this far because we worked as a team
and we were able to pick up across time zones.
None of us got paid to do this.
We've all been collaborating in our off hours,
whenever those happened to be.
We'd love it if people were a bit more skeptical
of the battle hardiness of their dependencies
in the future.
We'd love it if there were a formal definition
for the IPv4 address format,
or at the very least for how to handle
IP address octets in base eight.
We'd love it if ECMA rejected zero prefix literals
containing decimal digits.
But even if those spec changes happened,
there would still be code out there
where something's just slightly wrong.
Spec can guide future researchers or programmers
in understanding the differences between the ideal world
and whatever issues have become features,
but it's not gonna magically make things perfect.
If it's critical for code to behave a certain way,
it may be useful to ensure the behavior
of your underlying dependencies matches your expectations
before you rely on them.
One thing we did find really useful was the ability
to report these issues to the security teams
at all the various organizations
and at least discuss
and get pointed in a different direction if necessary.
If we hadn't been able to get in contact with the folks
at Rust and Golang and Oracle and so on,
these vulnerabilities would continue to be live
in the most current versions
of these languages and libraries.
So thank you so much to everyone
who is receptive to working with us to fix these issues,
and thank you for watching our talk.