Welcome back and thanks for hanging with us here at AppSecVillage at DEF CON 2020. Before we get to our next speaker, I'd like for you to take a moment and take a look at appsecvillage.com. Out there you can find this year's t-shirt, which is incredible. This is last year's t-shirt. This year's has got more color, it's got an awesome design. I just got mine in the mail a couple of days ago. Check it out. It's a great way to support AppSecVillage and make sure that we can bring you the same kind of content and application developer security focus that we've brought the last two years. So, that's appsecvillage.com. And now for our next talk. It's entitled Local Ghost, Escaping the Browser Sandbox Without Zero Days. Parsia Hakimian is a senior security engineer at Electronic Arts. He works mostly in video games and their surprise mechanics. On a different continent, he was a C developer. You can find his rants over at parsia.net or on Twitter at at sign crypto gangsta. Now remember, crypto stands for cryptography, as everybody over in the cryptography village would tell you. So with that, welcome Parsia to the AppSecVillage stage. Hello everyone. Thanks for giving me your time. Today I'm going to talk to you about how we can jump the browser sandbox using localhost web servers without any sort of ODAs. This is part of the AppSecVillage on DEF CON 28 in the year 2020, which has been a very, very long year. My name is Parsia. I am a security engineer at Electronic Arts. Mostly work with video games. I'm the shorter guy in the picture, not the other one. I'm not here representing EA, user of my own, you know, the usual boilerplate. Similar to the boilerplate you see on the coffee cups, you know, that says caution, contents are hot. When I moved to Canada last year from the US, people told me they don't have it in Canada, but they do. So I would like my money back. This is my second time at DEF CON. First time at the village. I was here on DEF CON 2018 where I talked about enterprise blockchain. I play a lot of video games, which is good because I work for a video game company. Now, this is a slide that I put on all my presentations. It is here to tell you what I'm going to talk about. So you can decide if this is a talk for you. So your time is valuable. I don't want you to sit to the end of it and say, this is useless or I already knew this. You know, I would consider this a favor if everyone else did it too. So I'm going to talk about why modern desktop applications use local host servers to do inter-process communications. And how this is bad from a security perspective, because JavaScript and browsers can connect to these websites. Usually there's no authentication and you can get some really easy remote code executions using this. I was also discussing browser concepts and bugs related to them. And finally, I will talk about remediation and how we can fix things. And not just point and say something is bad. So these local web servers are like ghosts in your machine. You think you don't have any, but go to your machine, run netstat and check. And you will have at least a dozen of these. I cropped the screenshot, there were more. As you can see, these are like normal, typical everyday use apps like Discord and Dropbox and Apple and iTunes and NVIDIA. I mean, all of these have them. Why would Discord or Dropbox have a local web server? It's usually used for inter-process communications. Usually you have an Electron frontend. And the Electron frontend talks to a local web server that serves content to it. Sometimes you have a Windows service running a system. Those are fun. And then there's a frontend app that talks to them and they talk with each other. Sometimes you have a local host server that allows you to talk to different applications. Discord and Overvolt talk with each other. Another use case is using seamless transition from a website to a desktop app. Video conferencing apps used to do this. You would go to a meeting page. The JavaScript in the meeting page would talk to the local server from the desktop app. And the web desktop app would bring up the video conferencing app and connect to the meeting. Seamless interaction. Great for user experience. Dropbox also does this. If you have the Dropbox desktop application on your machine and you go to the web interface, sometimes you have an open button in front of a file. Let's say it's a Microsoft Office, like a doc file. You click on the open button in the website. The website talks to the local Dropbox app on your machine. The Dropbox app opens Microsoft Office and opens that doc. Great again. Great user experience. But is this really secure? Let's see. Another thing that websites do and gain a lot of traction was port scanning. Someone went to the eBay website and saw that it's scanning a lot of local ports. For example, to see if you have running a local VNC server or the remote desktop application. Like certain ports. Banks have been doing it for like four or five years. I remember I went to a bank website in 2018. It was doing the same. This is part of the fraud detection thing. They grab the results and feed it to a complex fraud detection algorithm. And it decides if your machine connected to the website is part of a botnet or is being remotely accessed or not. Should they let the transactions go through? Should they lock your account? So on and so forth. Typical stuff. If these websites are doing it, what prevents bad websites from doing it? Your app is internet connected because of that. Raymond Chen is a Microsoft employee. And he has been writing this very, very useful blog called The Old New Think. And one of his blogs, which starts with that quote. It's rather involved beings on the other side of this airtight hatchway. And by the way, this is a quote from Douglas Adams' book, Hitchhiker's Guide to the Galaxy. It's an amazing book. So Raymond talks about three privileged levels on a typical Windows machine. You have a remote attacker who is not on the machine. You have a local attacker who is a standard user. And then finally you have the local admin or system. Now browsers can be remote attackers, although they are running on your machine. So the JavaScript in the browser can talk to the local web server or talk to the local WebSocket server. And then a remote attacker can gain privilege on your machine. He also talks about how your app is internet connected. And this is from 2006. He says, if you have an app and it handles files, people can create network shares on a remote machine somewhere else. And then they can pass it to your app. And now your app is internet connected because it's grabbing a resource from a different site. Although it wasn't in your threat model. We've talked a lot about browsers and how browsers can be remote attackers. Let's talk about how browsers want to keep us safe, but they can, but they cannot. And what protections are there and how we can bypass them. The first thing is called same-origin policy. It's probably the most important part of the browser security model. So an origin has three parts. You have a protocol, which is HTTP, HTTPS. You have a domain, which is something like example.net. And then finally, you have a port, which is optional. If you don't include the port, the default port is used. Some browsers, for example, Internet Explorer, don't care about the port in the origin. The path is almost always ignored. Usually, one origin cannot talk to a different origin and grab resources and read them. So for example, if you open example.net, example.net sends a request to facebook.com and grabs some data. Browser doesn't let example.net read the response. Absent some other things are cores, but we're not going to talk about cores today. And that's a really good thing, because otherwise example.net would have all my Facebook data. So reads are usually not okay, but writes are. So if I open example.net and send a POST request to Facebook to change something, if there's no C-SERV protection, it goes through. As a result, C-SERV exists, which is not really so great for us. So the SOP protects us in some cases, but not in every case. But SOP also has this problem that just because the browser doesn't let you see the response to the request, doesn't mean the request is not sent. So browser sends simple requests. Simple request is a request that has some requirements. The HTTP verb can only be GET, HEAD, and POST. It cannot have any custom headers. And it cannot have every header. Only some headers are allowed. And of those headers, the most important one is content type. The content type for those headers can only be one of those. So one of the tricks that we usually use when bypassing this, if you have an application slash JSON request, you change the content type to text slash plain, see if it becomes a simple request and goes through. And now you have bypassed a bunch of protections in the app. And that brings us to our first bug. This is a bug by Tavish Romandy from 2016. Tavish Romandy is a really smart dude. He's part of the Google Project Zero. He installs Trend Micro Antivirus, and that antivirus has a password manager. So the way password managers usually work is that you have an extension in the browser, and you have a local web server, a WebSocket server. The local server is responsible for storing the passwords, updating, and a bunch of those. The extension in the browser, when, for example, you go to facebook.com, the extension in the browser talks to the web server and says, give me my username and password for facebook.com. And the web server gives it to it. And then the extension fills up the username and password. In the case of Trend Micro, the local web server was unauthenticated, and you could basically do remote code execution with a simple GET request, as you can see on this slide. You could say, OK, run this URL in the default browser, which in this case opens up the calc, runs the calc manually instead of opening it in a browser. If you go and read the bug from the URL, from the link in this slide, you can see that Tavisor says, yeah, we can see the response, but the GET request goes through because it's a simple GET request. So damage is already done. We can't see the response, but who cares about that? Another thing that is used for local IPC are WebSockets. A WebSocket is different from your typical HTTP request. In a typical HTTP request, you create a connection to a server, you send a request, get a response back, and then you close the connection. In a WebSocket, you create a WebSocket and you keep it alive. You can write to it, you can read from it. It's great for real-time data things, real-time data updates, like, you know, chats and video games and, like, stock trackers, I guess. But the WebSocket doesn't come out of the blue. It starts with a GET request that is a normal handshake. So the GET request is your typical GET request with some special headers. Now, remember, the GET request for a WebSocket is a simple request, so it always goes through. On the response side, the other side basically says, here are the things I agreed on. Yes, we can make a connection. Here's our protocol. One mistake that developers usually make is that they think that SEC WebSocket key in the request and the WebSocket ACCEPT key in the response are based on security. They're not. You can usually put something, anything in there and it usually goes through. It doesn't really matter. That's not a security control. So don't care about that. It's always there. It goes through. It doesn't really matter. And if you can see the response, you can see that there's nothing really needed there apart from how we can make a connection. And as a result, the browsers are not bound by the same-origin policy because there's nothing in the response that the browser doesn't already know. So if you send a GET request and there's no cores, you can't see the response, but the browser makes the connection for you anyway. So if you have a local WebSocket server, everyone can connect to it and talk to it. You can't rely on SOP or cores or whatever, any of that. And that brings us to our second Aviso bug. As I said, this guy is smart. This is from 2018, and he wanted to re-bind his mouse key. It was a Logitech mouse, so he installs an app called Logitech Options. Logitech Options runs a local WebSocket server, some port 10134. As I said, WebSockets are not bound by the SOP, and the WebSocket server didn't have any checks. The only authentication it had was when you sent a message to it, you had to provide a process ID of a process that the user owns. A process ID that are like three, four or five digit numbers. And you can brute force them quite easily in a few minutes until you find one that works for you. Fortunately for Logitech Options, you couldn't get remote code execution, you could create crashes. So imagine you have this app running on your machine, and it's always running because you have a Logitech mouse. You go to a website, and the app crashes. You go to the other website, the app crashes. That's not really useful, and that makes your experience really bad. On the plus side, you couldn't get remote code execution, so that's really great. The next iteration of the app, similar app, called Logitech Hub. It runs two local WebSocket servers, on port 9010 and 9100. Now, this iteration checks the origin header, which is really great. The origin header is a forbidden header, which means the JavaScript in the app cannot set it, only the browser can. There are other forbidden headers, like the host header, for example. And the origin header is always set by the browser for every cross-origin request. A request goes from one origin to a different origin, the browser sets the origin header to tell the server on the other side where this request comes from. So what Logitech Hub does in the WebSocket servers is if you send a request with an origin that is not part of their allow list, then they just say, we can't connect. And when the browser sees a response like this, which is not a 200 OK response, it says, well, I can't create a WebSocket connection. So websites cannot talk to Logitech Hub WebSockets, so this is a great thing, and this is a good thing that Logitech has done. We've talked about local WebSockets servers, but we also want to talk about how frameworks can help us get remote code execution. And I'm going to talk about one of them, which is called Electron. Electron is probably the most popular desktop application framework out there. It is based on Chromium. Chromium is the open source version, I'm sorry, the open source base for a bunch of browsers like Google Chrome and Microsoft Edge. So everything in an Electron app is a browser window. So if you open Visual Studio Code, you're looking at a browser window. Yeah, I know, it's silly like that. And Electron is very popular. You can see these apps on this. There are so many of these apps. And these apps that you see on the slide, you probably have at least one of them on your machine. Slack or Discord or Visual Studio Code or Skype or whatever. The problem with Electron apps is that if you get a cross-site scripting, it might lead to remote code execution. So in an Electron app, you can write Node.js code that is run in the browser and can do stuff. The way for this Node.js code to run is you want this Node.js code to run, so you flip a switch. The switch is called node integration, which is off by default for a good reason. So if you set it to true, the JavaScript in your browser window in the Electron app can now talk to the Node.js APIs and, for example, spawn processes on your machine. And as a result, if you have this switch set to true and you get cross-site scripting, now you have remote code execution, which is really bad. If you have an open redirect, which means that you can click on a link and the browser goes to a different link, or you can do something like that. You can embed remote code execution payloads on your website that you control, and then you have remote code execution on someone's machine through an Electron app. This is by default set to off, and as we saw, it's great for a good reason. Now I'm going to talk about one of my own bugs. It's a remote code execution in a Tagged Surface Analyzer, which is an open-source Microsoft application. And we're going to talk about version two of it because it has two versions. When the second version was released, our CISO, who was one of the people who created the original app, said, oh, I worked on the original app, and I went and looked at it and see what kind of app it is. And it's a very useful app for desktop security research. What you can do is you can create runs, which are basically snapshots. So you can tell a Tagged Surface Analyzer to create a snapshot before you install an application on your machine, and then you install the application, and then you create a snapshot or run after you install the application. And now you can compare these two. And you can see what files were added, what registry keys were added, if there were any open ports, if there were any new Windows services. So again, it's very useful for this kind of security research. It comes in two flavors. There's a command line interface, and there's a GUI. I usually want to run it as admin. In fact, if you don't run it as admin, it basically tells you you're not running as admin. You should be running as admin. Because if you're not running as admin, you can't see everything that happens in the computer. You can't see what the application does. You can't get the big picture. It's open source. The GUI is based on electron.net, which is, again, another open source project, which is basically electron, but for .net. Which is... I mean, it works, right? So after I created the... I'm sorry, after I installed the app and ran it for the first time, I saw this pop-up. And this is a Windows firewall pop-up that says, oh, this app wants to listen on all interfaces and open ports in the firewall. And I was wondering, why would a local application, a tag surface analyzer, want to do that? So I looked at the console logs for the app, and I saw that, yeah, it is listening on all interfaces, 0, 0, 0, 0, port 8001. That sounds really great. So I started to dig further. I mean, this was the cue to tell me that there might be something wrong with this application. So I go to port 8001 on my own, on the... I'm sorry, on the machine that is running the tag surface analyzer app, and I see the GUI. So the way the app works is it has an Electron frontend and a web server in the back. Electron frontend just opens this website, similar to what is in the browser, as you can see, and it does REST, which is, I guess, normal for Electron applications. Next thing I did, I tried to connect to it from outside, because it's opening the port and listening on all interfaces. I might be able to talk to it. So this is from the virtual machine host. I went to the virtual machine on 8001, and it didn't work. I saw this error that says invalid hosting. So I was looking at it, what is wrong with that? I went and saw it's because the web server that is serving content is a Kestrel web server, which is an ASP.NET web server. I'm sorry, a web server written in ASP.NET. It has a host header filtering. So when you see the request, it looks at the host header. If it's not local host, it says, I can't do it. But here's the thing, because we are connecting from outside, and we are not bound by being in a browser on the machine, I can change the host header because I can create a tool that talks to this and changes the host header. To do this, to simulate this, I added a match-replace ruling verb that whenever it saw the host header, it would replace it with local host. And that means I would be able to connect to it. So this is me connecting to the app from outside. And doing quest.scripting. The next thing I did is I wanted to check if there was a way that I can do inject JavaScript, because I want to get remote code execution. Fortunately, the app only has one place to do user input, and that's the name of the run or snapshot. Again, fortunately for me, it was vulnerable to vanilla script, alert one script JavaScript. So when you start a run, you type the name of the run, script alert one script, and then you start it. At the bottom of the picture, you can see where it says status report for dot. The place before dot is where the script was injected, and the alert pops up. So this is in the web application from outside, but I want to run JavaScript code on the Electron app inside, and I got really lucky. Because when you go to the Electron GUI inside the app, and someone has already started a run from outside, the alert pops up, which is really interesting and really useful for us, because I can start a run from outside, and I can get a pop-up on a user inside. And the way to create a run is a simple GET request. And as you can see, it's a simple request, so it always goes through, which is really great again for me. So I started looking at it, and I was like, okay, so what can I do? How can I use this? So I created a payload and a proof of concept. I start the Electron app in the virtual machine, then I go to it from the virtual machine host from outside. I start a new run, I paste the payload, that pops calc, which is, again, a simple GET request. Now the run is completed, I go to the Electron app, and I click on scan, and the calc pops up, which is the payload that I injected into it. And now, remember, attack surface analyzer is running as admin. That means someone from outside can connect to your machine and get remote code execution. Nice, right? So I reported it to Microsoft. I did responsible disclosure, they fixed it, and then I did a write-up after they said something is fixed, and it gained a little bit of traction. But what happened is Taviso replied to the tweet and said, I hope they actually fixed the injection point, and not just the single null interfaces, because the browser can connect to it. And I was thinking and said, yeah, that is exactly it. So I created a web page that sends this GET request, which is basically a way to pop calc in Node.js, and I want to do another proof of concept and see what is up. So I created a web page with that payload, and then opened the web page in the browser on the virtual machine. To the right you have the browser, to the left you have the Electron GUI. I open it, and the browser says, oh, I can't see the response because there's no cores. It doesn't really matter, does it? The GET request has already gone through, the damage is done, the run has been started. And now you click on the Electron app and go to scan, and calc pops up. Yay! So if the injection hadn't been fixed, and only the listening on all interfaces had been fixed, then we would have had a problem. Imagine you're running a tag surface analyzer on a machine, running it as admin, you open a website, and that website runs code on your machine, literally jumping the browser sandbox and getting back to your machine. So really fun. One of the funnest bugs I have found. Fortunately, I found a different bug that was much better than this. Unfortunately, it hasn't been disclosed yet, so I cannot talk about it today. But life is full of disappointments anyway, so that's one on top of the other ones. As a product security engineer, I do a lot of remediation. We have talked a lot about bugs and pointing at things and saying, this is bad, this is bad, and this is bad. But half of my job is talking to developers and telling them how to fix things. Or better yet, how we can eliminate an entire class of bugs together. This picture is from a YouTube video from a game design college. This is pretty old. These two guys are playing video games, and the boss comes in and says, have you guys finished this game? I need another video game designed. This guy says, we just finished level 3, so we need to tighten up the graphics a little bit. I don't know, for some reason I love it. Every time someone asks me what I do at my job, I tell them that I write how to tighten up the graphics a little bit of level 3. So it's a great thing. It's from Westwood College, which has nothing to do with the Westwood studios who made the Command & Conquer applications. I'm sorry, Command & Conquer games, which are now part of EA. So what can help us? Our most trustworthy friend, in this case, is the browser. I talked about how the browser doesn't help us, but the browsers can help us through the origin header. So origin header is set by the browsers on every cross-origin request. Example.net to facebook.com, you have an origin header. Example.net to example.net, don't have an origin header. It's a forbidden header, so JavaScript in the browsers cannot change it. So if you're a local web server, you see a request that comes in, you do not process the request before checking the origin header. You have an allow list of headers that are good. You check. If it works, then you get to let the request go through. Do not rely on same-origin policy. We already know that the simple GET requests are not going to go through. Do not learn cross-origin resource sharing. We don't need to see the response. We sent a GET request, we got remote code execution. Check the origin header before processing the request. Same thing can help us with WebSockets. Again, with WebSockets, they are not bound by the same-origin policy, but the handshake was a GET request. So when you see a handshake in your WebSocket server, you do not process it, you do not respond. You check the origin header. If it's something that you want, then you send a correct response. If it's not, do something like what Logitech Hub did, return a 403, 404, or whatever. Anything that tells the browser that this request was not successful. That means the WebSocket is not going to get created. One thing that I've seen developers do that is not useful is checking the remote address. That's where the request is coming from. This works to some extent if you have something listening along interfaces and don't want local address, you only want local addresses. But that's not useful in browsers, because if a request is coming from your browser, the browser is running on your machine, the JavaScript is running in the browser, and as a result, the remote address of the request coming from the browser from a website, example.net, is actually local host because the browser is running on your machine. That's really useful. Don't do that. I know, it took a little bit of time to wrap my head around this, but I finally got it. It's a simple check, so check the origin header. With Electron, there's a lot of advice, but I'm going to give you some generic XSS advice. Don't trust user input because you don't want XSS. But more importantly, in order to eliminate an entire class of bugs, you set node integration to false, which is off by default. Don't set it to true if you don't absolutely need it. A lot of developers set it to true because the Electron app needs to run Node.js code in the back. And I don't really blame them because it's very convenient. But what you can do is you can run your Node.js code in preload. So preload is a script that gets loaded in the browser window before everything else. Even if you have set node integration to false, the JavaScript in the preload has access to the Node.js API. So that means that you can't run your own Node.js code and you can't have node integration set to false. Even if you have XSS in this case, you have XSS, which is really not great. But on the plus side, you won't have remote code execution. If you click on the link in the Electron.js website, they have a lot of useful information on how to prevent these attacks, how to do things properly. If you use a local web server to serve content to an Electron app, disable cores. So if you have a local web server, so there was this app I was looking at, a third-party app, and it was running a local web server system that had a front-end Electron. They had enabled cores on the web server, so basically access control allow origin was star, which is not really great, because that means any browser could talk to the web server and grab the results. There was no sort of checking the path, so any website could read all files on your machine. And the server was running a system, which means that it could read everything, and that's not really great. That's not something you want to do. So disable cores, that means you are not leaking information to websites when they talk to it. And last but not least, you want to add browsers to your thread models. So this is Crypto. He's a hacker character in Apex Legends, the video game. Crypto doesn't need to be in an internet cafe somewhere and directly connected to a machine to be enabled as a hacker machine. If you have one of these servers on your machine, and you go to a website where Crypto controls, or if Crypto has injected JavaScript into, and he might get remote code execution on your machine, and as a result, he's now listening to what you are saying on the machine, listening to a Discord conversation, so on and so forth. So don't do that. If you have a local server, think browsers can connect to it and add them to a thread model, have your proper checks. As I said, checks are really simple and we can defeat it and we'll be fine. So I've talked a lot about how to find things, how to fix things, but your next question is, how do I get started in this niche? Practice is the best thing. Start a virtual machine, install a bunch of these client apps, run Headstat, see what servers you have. Start poking at those servers, look at the cores, look at the APIs they expose. The peripheral utility apps are really great candidates. If you have a mouse that comes with a utility to control it, they are usually good candidates. Or you can go to that link on Electron.js website, which has all the Electron apps. Electron apps are usually to have the same thing. If you want to learn more about hacking Electron, there is a GitHub repository with a bunch of links, a lot of useful information, a lot of great research, a lot of great books. And last but not least, read Taviso books. The awesome book that I told you about that hasn't been disclosed, it was two Taviso books mixed together. So I combined two of them and it worked. So that was great. And finally, thanks for giving me your time. Because this is recorded, so I can't answer your questions on the fly in this presentation. What's going to happen is I'm going to be on the Discord and I will answer your questions there and then. To find more about me or if you want to ask questions, you can contact me on those social media links, Twitter or my website or on GitHub. If you have any questions, I'll be happy to answer. Again, thanks for listening to my presentation and giving me your time. And fingers crossed, 2020 won't get worse than this. We can get through this. Have a great day.