Exploits and vulnerabilities.
Dave Bittner: Hello, everyone, and welcome to the CyberWire's Research Saturday. I'm Dave Bittner. And this is our weekly conversation with researchers and analysts tracking down the threats and vulnerabilities, solving some of the hard problems, and protecting ourselves in a rapidly evolving cyberspace. Thanks for joining us.
Ryan: We had actually all been in a training doing heap exploitation the week before this vulnerability was announced. And so, as soon as we saw a major heap vulnerability come up, we all wanted to take a look and actually try to use what we had just learned and write this exploit.
Dave Bittner: That's Ryan, a security researcher at Bishop Fox. He asks that we not use his full name. The research we're discussing today is titled Building an Exploit For FortiGate Vulnerabilities CVE-2023-27997. Yeah. The timing is everything, right?
Dave Bittner: Yeah. Well, let's walk through it together here. Can you can you take us through step by step? How did you and your colleagues approach this?
Ryan: Yep. So, initially, we -- when we started looking into it, we just went and tried to follow the blog posts that Lexfo, the original researchers, Charles Fol published. So they have a really detailed write-up of all of the steps that are required. And so we just tried to replicate that. So we started by setting up an environment with a vulnerable system. And I used Fortinet on an ESXi system just at home and figured out how to configure that, figured out, okay. I am able to hit this endpoint they're talking about and then worked on just being able to get a crash proof of concept because that, in theory, should be pretty easily. And then, once we actually had a crash of proof of concept, I worked on trying to get a debugging environment set up, meaning installing GDB, being able to have a root shell because Fortinet appliances don't actually -- well, they try not to let the user obtain a Linux shell. They don't even have a proper shell installed. If you run bin sh, it won't drop you into a shell. So there's a process of setting up this debugging environment. And then, once we had the debugging environment and we had something that could at least cause us to crash, then we could continue on with writing the rest of the exploit.
Dave Bittner: Well, let's back up just a little bit. I think, as you pointed out in the research here that this bug is a heap-based buffer overflow. Can you describe that to us for folks who may not be familiar with that.
Ryan: Yeah. So, in most programs, you have two areas that you store data. One area is the stack, which is typically for temporary things. Maybe it's only used for the lifetime of a certain function. But if you want something that's kept around for a long time, or maybe you have a lot of data, you'll allocate in the heap. And so the heap is just a big area of memory, and you have a code that will manage this area. And you can request, okay. I would like 1000 bytes, call a function, and it will return a pointer to an area where you can use 1000 bytes. And then, when you're done with that area, you say, Okay. I no longer need it. Free this area, and it can then be reused. And so with a stack overflow there's a lot of other programs state that is stored on the stack. And so, if you're able to manipulate that, it can often be pretty easy and pretty deterministic how you would turn a stack overflow into a full exploit. But, on the heap, there is a lot of other stuff that is using it. It's a lot less deterministic, and there's a lot more work that needs to be done to manage the layout of the heap. And there's less program state that's directly usable to get code execution. There's often a lot more setup and crafting fake objects and a lot more really deep stuff that you have to do to actually get a successful exploit.
Dave Bittner: Well, walk us through how did you trigger your first crash.
Ryan: Yeah. So the actual bug relies on a difference between using two definitions of the size of the request that you're sending. So one definition is just the length of the request. It's a string, so it can check how long the string is. The other definition is that there's a size field embedded in this message that you're sending. It actually misinterprets the sizes. And so, if you send it a message of one size but then you set the length field to an extra 1000 bytes, you can corrupt a bunch of data that's out of bounds of this buffer. And since it's on the heap, that can just be anything. There's some amount of manipulation you can do to make sure it crashes. Sometimes it won't crash. But if you just send a bunch of requests where the size field is longer than the actual length of the message, eventually you will overwrite something sensitive and cause the application to crash.
Dave Bittner: So you achieve your first crash here. Then what do you set out to do next?
Ryan: So once you're able to achieve a crash, you want to start to make this a more controllable crash. So instead of writing to some unknown area in the heap, you want to actually know that immediately after my buffer is some object where there is a field that would be useful to overwrite. So you try to set up the allocations and deallocations on the heap such that, when you overflow your buffer and write data out of bounds, you're writing to something that you know. And you can overwrite in a much more controlled way. So instead of trying to generate a crash just by throwing random data, you want a crash where you actually know what you're doing, control while you're overriding, and get a more controlled crash.
Dave Bittner: Are you able to read the heap ahead of time to just sort of map out what exists in there and -- and how it's being allocated?
Ryan: No. So, in this case, you would rely on knowing how the program works and understanding that, even though it can feel nondeterministic, there is a level of determinism where you can control exactly which data you're sending and know that in, okay. If I send this data, this will be the actual layout of the heap. So, in this case, each time you make a request, there are two buffers that get allocated in this region that you're -- that you're manipulating. And the first buffer is a structure that is set up for the SSL context. And the second buffer is a structure that contains your actual data. And, through a bit of manipulation, you can swap that order so that you have your buffer followed by this SSL structure. And as long as you do your best to minimize any other noise and control the exact allocations that are occurring, you can make sure that you will reliably have this layout.
Dave Bittner: What is your ultimate goal here in this exercise? What -- what are you looking to achieve?
Ryan: So the ultimate goal is remote code execution. And, with this specific setup, there's a pointer in this SSL structure that is a code pointer. And you want to overwrite this function pointer so that, when it gets called, it jumps to code. And you can hijack that code flow and obtain -- and use it to then create a RUP chain and gain arbitrary code execution.
Dave Bittner: And so, ultimately, how did you end up doing that? What were the rest of the steps that were necessary here?
Ryan: So we use this function pointer to make a call to adjust the stack pointer to the beginning of the SSL structure. From there, we were able to have sort of complicated RUP chain that adjusted the stack pointer a few more times until it eventually pointed to data in our initial buffer, which we entirely control. From there, we could set up arguments for a call to system or popen or any other function. And we could use that to basically run arbitrary commands. And because, like I said earlier, Fortinet doesn't actually have a bin sh or a shell, we were able to use the node binary. So it does use node for a web server. And we were able to create a reverse shell with node where we run node from exec. I believe we use execl specifically.
Dave Bittner: Now, help me understand here because one of the things you point out in your research is that the heap was not mapped to be executable. So you had to get around that. Are there particular details you can highlight with that part of the process?
Ryan: Yeah. So, typically, if you do have controlled writable and executable memory, you're able to just directly jump to that code. So instead of doing this complicated RUP chain, we would just set the function pointer to point to data that we control and execute it as code. But, in this case, we have to do this more complicated setup, where we point the stack pointer to data that we control. And then we're able to reuse different parts of the binary that is already in memory and executable. And we essentially run the last couple instructions of a function and then let it return. And then it'll jump to the next address on the stack, which we also control. And you keep doing that and building up these little RUP gadgets, these gadgets that do one small operation. And by combining a bunch of these gadgets, you're reusing code from the existing executable in memory, which is mapped as executable, you can actually still obtain arbitrary code execution.
Dave Bittner: What was it like for you and your colleagues when you ultimately achieved what you set out to do here? I mean, it sounds to me like this journey was -- was fun. What was the payoff like in the end?
Ryan: Yeah. So it's, of course, always exciting when you get that first connect back with your reverse shell. So I was very happy when I finally got that working. We were also able to actually create a scanner that we could use, and we released to help others identify what's vulnerable. And the payoff for the company was, of course, that we could go test on customer systems and say, Hey. You're vulnerable. Here's proof and get people to patch. So there's really a lot of -- for my team, it was mostly just really exciting. But, for others, there was definitely a positive impact on their security awareness and posture.
Dave Bittner: Well, and based on the information that you gathered here, what are your recommendations for folks out there who are responsible for securing their organizations? Any -- any words of wisdom to take away from this exercise?
Ryan: Patch. Just make sure you're patching. I mean, this is an unauthenticated remote code execution. It is a heap vulnerability, but we were able to develop a working proof of concept within a week. No doubt other threat actors would be able to do the same. The fix is to patch your system, and please do that.
Dave Bittner: Our thanks to Ryan from Bishop Fox for joining us. The research is titled Building an Exploit for FortiGate Vulnerabilities CVE-2023-27997. We'll have a link in the show notes. The CyberWire Research Saturday podcast is a production of N2K Networks, proudly produced in Maryland out of the startup studios of DataTribe where they're cobuilding the next generation of cybersecurity teams and technologies. This episode was produced by Liz Irvin and senior producer Jennifer Eiben. Our mixer is Elliott Peltzman. Our executive editor is Peter Kilpe. And I'm Dave Bittner. Thanks for listening.