Hi. I hope you are not very much hungover after the third day of the conference, so I guess people can be a little bit tired. Can you hear me well? Ah, thank you. Okay, a few words about myself. This is like a living proof that I can also wear a shirt if forced to do so. So you can find me on LinkedIn when I have lots of connections. You can also find me on Twitter when I don't have that many, but if you would take a look at who those people are, you will see that this is fairly an elite club, so I encourage you all to join this elite club. And a little bit of self-promotion. I started a small project when I tried to like teach me how to do this. I started a small project when I tried to like teach me how to do this. I started a small project when I tried to like teach people about application security. If you would like to take a look, I would be very happy. But okay, you came here for something else, and this something else is a story that I have to you. So let me tell you a story. This is going to be a story about going down the rabbit hole to find new attack surfaces. And I would like to today encourage you to take a similar journey with software that you are responsible for, because I I believe that there is a likelihood that you're going to find similar things that I have found when I did this. So first, we'll be, yeah, our industry has a thing for skulls, does it? So I try to indulge you here. So we'll be looking for a pattern of death in our application. So what is it going to be? So first ingredient of the pattern of death is going to be untrusted input. No big surprises there, right? Secondly, we'll be looking for an input that will be accepting complex data structures. So by complex data structures, I mean exactly what I like computer science 101 would define it, right? So files and anything that is not simple. So if your application allows to, for instance, import data from XML file or allows to upload an image or any kind of file attachment, this is going to be. This is going to be complex data structure. So the third necessary ingredient here is that this component, who's going to touch this complex data structure, is going to be written in memory and safe language. So I am thinking about C or C++, because I don't think there are people around who develop in assembly anymore. And finally, the set component probably was not security assessed by a security professional. Before. And you might think that this is not happening very often. But I want to convince you now that it is super likely that this kind of patterns are present in the software that you are responsible for. Let's take a quick look at those. So untrusted input, if you are dealing with web applications, basically most of your inputs are going to be untrusted inputs. Accepting complex data structures, again, it's just enough that you allow, for instance, for users to upload an avatar or a picture, and that's it. Or any kind of other file. Now, I will spend a little bit more on the third and the fourth points here. Because first of all, I am older than I look. And in my generation, it was normal for people when they started to learn development to start with low-level languages. C, C++, assembly. My first computer was Commodore 64, by the way. So if you didn't know assembly, you couldn't write anything that was sufficiently fast. But I found that the new wave of developers, they basically start with Python. And they never learn C, C++. And therefore, they are not familiar with the kind of challenges that you have in those languages. But on the other hand, this is the hard reality that we have. You know? And it's not just Python. It's basically every high-level language. It's fantastic. It's generally memory safe by design, which is fantastic. But it's based on memory-safe languages. And you can find them everywhere. So let's take a look at this, for instance. So I started with Python as an example. But let's keep in mind that it's going to be true for probably every other. high-level language out there. So see, it's Python itself. The reference implementation of Python is called CPython. And it's called this way because it's written in C. And as you can see in the repository of Python, around 1 third of the code there is written in C. And many popular libraries have substantial amount of code written in C or C++. I gave you several examples here, but you can look. At the components that you are using in your companies or in the software that you are particularly interested in. And you will find something very similar. You will find that a lot of code is, in fact, written in C or C++. And Python is only used as a glue language for them. And the security consequences of that are interesting, right? Because we have, on one hand, developers who have never been taught to deal with memory safety issues. Because on the first lesson of Python, or if they ever had any kind of Python security introduction, it would be said that you don't have to take care about memory. Not just safety, but basically all kinds of memory issues. Because Python deals with that for you, right? Yeah, but in reality, it could be slightly worse. So that's the first part of my talk. I want to convince you. I want to convince you that even though you might be using or your developers might be using so-called memory-safe languages, you can still have memory-safe issues in your stacks. So what I would encourage you to do is to take a look at the inputs that you have and try to look for the pattern of death, as I call it. So analyze your attack surface and look at what kind of inputs do you have, and what happens with the data as they are going through the stack. Which exactly components are touching it? And what kind of components are that? Have you written it? Are they old? Have you taken it from GitHub? What is their dependencies? Because it could be a dependency of a dependency that could have some interesting vulnerabilities. And of course, check how old are they. Because I don't know. I don't know how your developers are good with updating the dependencies. The developers I am working with are not very good with that, sorry to say. And sometimes they need to be reminded that this is super important. And if you are working on the red side of things, then you can basically have vulnerabilities for free. Because all you have to do there is check what kind of reports were submitted to the database. To the libraries in question. And you basically can just write an exploit for that. And you get to be the hero who hacked the application. But if, let's say, you are dealing with maybe less known code, there is no CVE associated with it, it still doesn't mean that it's secure, right? It just means that no one has tried to hack it. And this is where we are going to the second part of this talk, when I will tell you how to deal with this exact situation. So, let's say that you have analysed your attack surface of your web applications that are written in something like Python or Ruby or something like that, and you have found libraries that are using memory unsafe languages. Because this is exactly what I had. What can you do then? Well, you know that there is this thing called fuzzing. Which is like number one technique for finding vulnerabilities. This is like a superpower. This is a super power. Super power to have and very important competence of every security professional, I would say. So I really encourage you guys to learn it. If you don't already, or if you have some basic understanding of what it is, then work on this skill. Because this is going to work magic for you. It's absolutely fantastic. Okay, so I have a special relationship with mathematics, kind of similar to my relationship with women. I don't understand much, but I appreciate the beauty. So I try to like show you successful fuzzing as a form of mathematical equation. So successful fuzzing consists of, and you have like two tactical goals to achieve and one strategic goal. So the first tactical goal is to achieve good coverage. The second tactical goal is to achieve good coverage. The third tactical goal is to achieve sufficient speed. And finally, we have a strategic goal is to find actual bugs, right? So we need detection criteria for this. And those are very measurable goals. And I will give you three numbers that matter for a fuzzing campaign. And those numbers are going to be measuring how close you are to reaching those goals. So the coverage typically will be counted in percentage. I will speak a little bit more about that in future. It could be also basic blocks. If you are dealing with, if you don't have access to source code, you are dealing with assembly. Speed is going to be number of tests per second. And detection criteria, like the eventual KPI that you are going for is the number of unique bugs that you have found, right? So this is the formula for successful fuzzing and the respective numbers that matter. Here. Okay. So first let's take a look at coverage. This is a shot from a campaign that I did. So there's a software that you can use to measure how much code have you covered with your fuzzing attack. Now I have a question for you guys. Do you think that those numbers here are good or bad? No. Okay. Any? Bad. Bad. Okay. I have this saying that in infosec, for every general question, the answer is going to begin with, it depends. Because it depends. It could be horrible result, in fact, and it could be a very good one. It depends. I was dealing with very, like, sophisticated software that had a lot of inputs and it was basically impossible. It was impossible to cover it with a single fuzzing test. So if you will ever try to fuzz something sophisticated, like, for instance, a JavaScript engine, it is completely impossible to cover everything with one fuzzing campaign. Because the software simply has too many, like, paths through it. So even if the software is much simpler but has multiple inputs, each input is accepting a different kind of data. Okay. So if, like, one accepts, let's say, something that has to be grammatically sound, then it's going to need a completely different approach than an input that accepts, let's say, integers, and that's it. Right? So what I want to say is that it's not that easy to say if your coverage is good or bad. It depends on your, let's say, current priorities and what your, like, current approach is. If you want to cover a specific module, then, of course, the coverage of this module is going to be important for you. But achieving, like, 100% coverage for a huge application is very difficult. By the way, guys, who of you would tell me what is the code coverage of a fuzzing of Google Chrome? Do you know it? 25? Okay. Who will give you more? 40? Yeah, it's around 40 overall. Yeah. 40-something. I don't remember the exact number. But just to give you an example that it's... Even... Even... When you have, like, much bigger security team that, for instance, I have, it's super difficult to get to, like, large numbers. Okay. So at the end of the day, what you have to do is manually review this kind of report and see if you are hitting the kind of code that you want to hit, and make some adjustments. Because there are ways in which you can, like, improve your coverage. Like, for instance, you can add specific test cases to corpus of your fuzzing attack. Or maybe you need to figure out a different mutation strategy or a different test case generation strategy, in fact. And that's another slide that will show you how difficult it is to get, like, 100% coverage. Because, for instance, you're going to have a code that handles some very, very rare, very niche situations. Like, some... Error messages, error cases, when, like, reaching them from, like, regular input could be very difficult. Because you don't cover everything. Like, if you have, like, out of memory condition, and there is a code that's covering that, then you would need to take some special steps to cover this code. It's not, like, easily accessible. Okay. So basically, the road to good coverage is that you need to have a good test... Test case generation strategy. And there are, like, at least two ways of going about that. One is basically, like, the standard way, which, for instance, my friend Hardik Shah was delivering a fuzzing workshop on this very DEF CON on the first two days. Is there anyone in the room who was on this workshop, maybe? Oh, hello. Hello, sir. Okay. So I'm sure he... Well, he had two days, right? So... So he got much more into the details of how to do that. But basically, in, like, broad strokes, the idea of, like, the most typical way of going about fuzzing is to prepare a so-called corpus. So corpus is a, like, collection of test cases that should be somewhat representative, right? So they should be hitting the main functionality of the application. And then you have mutators, so-called, right? So mutation essentially is, like, taking one of the inputs... From the corpus and changing it somehow. It could be, like, changing one byte, flipping one bit, adding something from the dictionary, or there are all kinds of, like, random changes that could be applied to it. And so if you want to improve your coverage, then you can do it manually by simply adding test cases to the corpus that will be hitting those particular lines of code that you are interested in. Okay. But if your application is, for instance, taking something that has to be, like, grammatically sound, like if you are attacking, for instance, SQL engine or JavaScript engine or something like that, then you need something else, because the approach with corpus and mutation is not going to work. And there is software, like, for instance, Darma, done by Mozilla and a couple others, which allows you to create a grammar, and then this software will create test cases from this grammar. So it is somewhat grammatically correct. And this way you can... Because otherwise, you know, if the first line of the code is checking if the provided data is, like, for instance, it's XML, and there is a check if this XML is, you know, it has some, like, basic... It passes some basic validity tests, then you will be, like, testing those first couple of ifs very hard, but nothing else, right? And this is not what you want to do. Okay. Let's move to speed. We have a need for speed with fuzzing, and we have it because fuzzing is essentially a brute force technique, right? So with brute forcing anything, speed is super important. And if you look at... Because another way of thinking about fuzzing and basically any kind of automated testing is that you want to explore this input space of a program, right? And this input space is going to be super large. So it's... Like, the... The faster you can make your fuzzer, the more likely is that you are going to hit those cases that produce you bugs. Okay. There's going to be a quiz question. I showed those slides to one of my friends, and he told me that this must be a picture of me when I still had hair. You know? Can you believe it? Friendship is such a beautiful thing. And anyways, so I'm referring here to a piece of mathematics. And the question is, if any of you can guess what kind of piece of mathematics that is? There's a theorem, mathematical theorem. Infinite monkey. Infinite monkey. Yeah, exactly. So sock to this guy. The guy in the first row also. Yeah. AC is... These guys need socks. Okay. So... So there is this thing called infinite monkey theorem. So for those of you who don't know it, the idea is essentially if you take a monkey and you give it a typewriter, it will eventually, of course, if you give it enough time, it will eventually type every single book in the world. And this is mathematics, so you... Well, there's this concept on infinity that kind of plays a large role here. And so we assume that the monkey, of course, will have infinite lifetime. Anyways. How does it apply to fuzzing? Because this is essentially the same thing. The fuzzer is this monkey, and it's going to be figuring out new test cases. So according to this theorem, it will eventually figure out... Of course, with some assumptions, right? So the first assumption here is that we have figured out this coverage well, right? This is very important. And second very important assumption is that we also figured the detectability problem, which I will speak about in a moment. Detectability is also a very hard problem to fix. But if those two problems are fixed, then this fine gentleman kind of represents the truth that eventually you will find it. But we're talking about speed here. Do we really have infinite time on our hands? Well, I don't. This is why we need something better. So this is a screenshot from my campaign. And in the beginning, as you can see, I was not very successful at making it fast, unfortunately. Okay. So I tried every trick in the book. I Googled all of the suggestions from Michal Zalewski, also known as El Camtuv, the original author of American Fuzzy Loop, which I use here. Fantastic piece of software, by the way. I will recommend it. So he had a number of ideas on how to make the software faster. And unfortunately, in my case, most of them didn't work very well. I mean, it was slightly faster, but not the kind of... Not like order of magnitude faster, right? So one thing I did try for a while, until I understood that this is not going to work with me, is I tried to decompose the software into smaller parts and fuzz them independently. Because that was also suggested as something very smart thing to do. But in this case, unfortunately, the software was very monolithic, and it maintained a lot of shared state. And that state was super difficult to mock. And I figured that, okay, if I tried really hard, of course, I could decompose it eventually. But the question is, how many other bugs I would create? Because then I would find them, of course, but they wouldn't work in the original software. So what's the point? So this is why, even though this approach is generally, I really recommend it, it only works with software that is kind of, let's say, modular. And not very monolithic. So, again, if you want to try to fuzz JavaScript engines, this software is not very, question? American fuzz loop? Yes, in the yellow letters. Yeah. Okay. So what eventually worked in my case is so-called persistent mode. Again, I cannot recommend it enough. It's an absolutely fantastic approach to fuzzing. But in this case, it was very difficult to set up. I was not smart enough to do it, but luckily, there was a guy who was smart enough, and he did it. Yeah. So basically, there is a way, right? If you cannot, there are multiple approaches to this, and you need to figure out which of them is going to work in your case. So those are like the most important game changers when it comes to speed. Okay. So first of all, access to source code. You can fuzz anything. You can fuzz software that you do not have source code for. But this is going to be, first of all, much more difficult to set up. And it's going to be very slow. And also on the detectability side, it's going to be difficult to, way more, you're going to have fewer options to choose from. Modular architecture helps a lot. This is basically, like, when I look at very well set up projects. They all use, like, multiple entries for fuzzing, as opposed to having, like, one fuzzing session for the entire program. This is good only when you have very small programs. But the bigger it is, the more it makes sense for having, like, multiple small fuzzers instead of one big one. Then persistent mode, obviously. This is super fun thing. And parallelism. So basically having multiple CPUs work at once. AFL has this nice feature. This functionality that allows those independent fuzzers to share, compare nodes, kind of, right? So if any of them will figure out a new path throughout the program, it will tell the others, guys, I found this. You can also try it. So this is, like, one way of using parallelism. Another one is, and we'll cover this a little bit in the next section, which is about detectability. You can have different detectability criteria in a cluster. So different fuzzers that run independently will be looking for different kind of bugs. And this will overall improve your chances of finding something significant. So again, it's important to have, like, a lot of bandwidth. The more CPUs you have, the better off you are. Again, computer science 101. I'm not sure if you guys, if you like mathematics, fun fact, if you have infinite number of computers, you will find, and assuming all of the other conditions were met. Then, you will find all of the bugs within, basically, one step. Okay. Detection criteria. And this is actually the most difficult bit, I would say. So let's say that we have solved, we solved our tactical problems, right? So we figured out how to hit the parts of code that we want to hit. So we have coverage. And we did what we could have to make this. This is as fast as could be. So we have solved speed. So what else? And well, the problem is that when compared to unit testing, for instance, right? When you write a unit test, you simply make the program run with some input, and then compare it to some magical value that you happen to know is true. Right? That's super easy to set up. But with fuzzers, it's worse, because you don't know all of the test cases that the fuzzer will figure out. So also, it's difficult. It's difficult to tell if the behavior of the program was correct or not. So this is why detection, detectability generally is a problem. It's a big one. And when it comes to memory safety, it's also a problem, because software written in C or C++, it's not necessarily going to crash just because we entered an input that causes something that shouldn't have happened. Software generally is quite resilient. And we can put some small buffer overflows to it, and it will not crash. And the only way the fuzzer knows that the bug was hit is by the software crashing. So this is like number one detection criterion. There could be other ones. Like for instance, you can be looking at timeouts. And this way, you can be looking at inputs that will cause the software to run particularly long. So this could be like denial of service. Right? So this is a big vector. And of course, there are other ways, which I will cover shortly. So when it comes to memory safety, this is what I was particularly after, right? Because I found code written in C, and this code was parsing untrusted inputs that you could upload files to. So I was after memory safety. Memory safety is a big problem. I will not cover it fully today. It's a big problem. But like the most important issues are like spatial issues. So spatial issues are essentially out of bounds reads and writes. So buffer overflows, underflows, and stuff like that. Temporal memory safety refers to use after freeze. This is like number one bug in the industry when it comes to exploitation probably. So if you look like a typical browser exploit, it's typically going to be use after freeze. You could create a lot of Ctrl til, Ctrl ll alt literal. It will tell you whenhost now, exactly what to do. Now, you can go to the portal, there is a Googleugi, so it will .. This is the source code in the browser. And you can just type n huge in the browser. And it does this kind of thing to download the AR Jupyter under the browser. And you could click on the google junker метal of the browser. This is the program you are using in your browser.. And that's five or that these very nice things in your browser that you are using in your sanitizer also it will find some use after freeze not all of them unfortunately also not not all buffer overshoes quite frankly there are some limitations to to these technologies threat sanitizer is going to help you find race conditions so and memory sanitizer is going to help you find uninitialized reads and this is what i said earlier about parallelism you can compile multiple versions of your target and have the fuzzers test for it concurrently at the same time right looking for different criterions so sanitizers first thing that that you should be using second thing is assertions this is if you because like memory safety is not the only thing that you could be looking for with fuzzing you can be looking for logic bugs as well and for that the the thing that that actually works is assertion so if your developers are happy with using assertions you are a lucky person because it's gonna be much easier to to fuss and much more likely to to like figure out that we have hit uh interesting states that should never happen and this is obviously a bug of some sorts i mentioned timeouts earlier and like the hard truth here is that there is no silver bullets to this it's really complex to to set it up right and you and this last problem is actually the the biggest of them all and you need you you can spend a lot of time figuring out uh how to define different categories of bugs so even if you have fast quite a lot uh this software it's it there's still some capacity for additional bugs and that could be there which is also a good news for you if you are bug hunters or you you participate in bug bounty even if software that you are looking at was already fast right but it was fast for instance years ago there is a lot of progress being made in this technology so those sanitizers gets gets better and better you can take a look at a piece of software that was fast before like eight years before or five years before and you can still find something also you know people modify software so they introduce new bugs so there is plenty of opportunity for you to find something interesting okay so that's a trophy slide i i was able to to find some stuff with these approaches and this is one of my fathers that that found on one of the sessions 11 so-called unique bugs but quite frankly afl is not particularly good at the duplication so this number is not really true but overall me and my friend we have found 30 zero days with this very approach in the software that we have found so this really works i you you can really try it and this is what i actually encourage you go find those needles in those haystacks look at the attack surface of your application look for memory safety issues there it's much more likely than you think that you will find something and when you do you can use this very approach to get rid of those bugs thank you very much questions let's if you have a question you can come up to the mic here and congratulations for the talk could you explain a little bit this persistent mode that you know you talked about before yeah so basically when you do a standard type of fuzzing every time you your father will figure out a new test case it will run application and a lot of software have pretty heavy initializations when they start so you will waste a lot of cpu by making it like start over every time so basically the persistent mode is like an idea to solve this so basically you start the software and for instance you then generate like 10 000 test cases and then you start the software again because of course if you keep it in memory too long then it's gonna you you may find some issues that are false positive source so you you should clean the memory every once in a while so that's basically the the approach here there is also a similar approach called the fork server in the original afl but i think uh persistent mode is much more flexible than this hi thank you for the talk have you compared results of like static analysis tools to the findings you found with dynamic approaches like this you may be able to duplicate them with static analysis yes i i love static analysis in theory and people who are laughing they know why and i i can give you a quick i i will not like tell you what kind of vendor was that but there was a vendor pitching software that is supposedly like finding memory safety issues in cc plus plus code and i i got like a commercial license to test it i wrote some snippets in cc plus plus with very obvious very hard to find buffer overflows use after freeze and like this software found like maybe two out of ten of them even though all of them were super you know obvious so with fuzzing you can find the kind of stuff that you never dreamed about it's uh i'm not saying that there's anything wrong with static analysis in general for static analysis i would say that there are very special special use cases like for instance once one use case that i like is let's say that you want to eradicate command injection let's say in your code right and you figure out what kind of in any programming language there will be like a limited amount of ways in which you can run a shell command and you can say to your developers that there is your company's policy that it is forbidden to use any of them and then you create a rule in sast like you know set it up with cicd and it will work brilliantly right and if you have this in any of your dependencies you can make an exception for that so this is a perfect use case for static analysis when it works brilliantly and there are like other like for instance one of other approaches at memory safety that microsoft take in this original libraries for cc plus plus you have a lot of potentially vulnerable code and they wrote safer versions of it and again they used sas to make developers use the safer versions of functions not the unsafe version of actions so again very good use case in my so i would say those two methods are kind of different and they were yeah they work together quite well as well next question uh do you have any comments on doing asynchronous fuzzing like doing a limited set inside the ci pipeline just to give feedback immediately to the developer versus doing a full set fuzzing asynchronous from what the developer actually sees and then gives reports later on after can you repeat please so to get uh faster feedback to the developers uh when they're actively working on the product uh they don't want to do the full fuzzing just yet they just want to do like a small subset of fuzzing just to get some basic feedback but then the the ci or the build system will do a full uh fuzzing asynchronous from what the developer is paying attention pay attention to like get a report like a day or two later yeah i'm not sure what's yeah i understand you but i i don't exactly know what the question is here but uh i okay like if you were doing a smaller set what would you be looking for okay hard to say really but i i also when i was doing that i i was having a similar approach so i was doing small attacks as opposed to one big attack and i was delivering those bugs to developers in batches it worked very well and there is software that could be used to do that so it's very similar to what you just said like for instance for python it's going to be hypotheses i'm not sure if you guys are familiar with this library it's quite nice so it's it's maybe not fuzzing per se what it is doing but it's something between fuzzing so it's somewhat dynamic but and not as static as unit tests for instance um so it's kind of nice and it's so fast that you can use it uh exactly in the motor that as you as you suggested right so you can you can run it all the time it's better than unit tests but not as good as fuzzing but much easier to set up so something in between so i could recommend this and fuzzing generally you would set it up somewhere in some completely different infrastructure completely separate from cicd and you would like keep it running 24 7. this is like the traditional approach you set up some cluster and it just runs some companies have made it like fully automatic so it's like having another qa team on your stuff and this cluster when it finds something he will create a an issue in jira or whatever system you you use and then when it will be fixed the same cluster will check if that fix actually fixes this attack vectors that that were working before so it could be made fully automatic although it takes some work it's not like out of the box solution that that you can use all right let's go ahead and put our hands together and i'll talk about that in a moment so so so