 Welcome back, and thanks for hanging with us here at AppSecVillage at Defconn 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. Parsea Hakameum 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 sea developer. You can find his rants over at parsea.net or on Twitter at atsigncryptogangsta. Now remember, crypto stands for cryptography as everybody over in the cryptography village would tell you. So with that, welcome Parsea 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 old days. 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 Parsea. I am a security engineer at Electronic Arts. Mostly work with video games. 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. Now when I moved to Canada last year from U.S., 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 28 team or 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 is no authentication and it 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 net stat and check and you will have at least a dozen of these. I cropped the screenshot, there were more. I can see and 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. So usually you have an electron front end and the electron front end talk to a local web server and that serves content to it. Sometimes you have a Windows service running as system, those are fun and then there is a front end 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 like Discord and Overvolt talk with each other. Another use case is using seamless inter-transition from a web site 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 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 website, open button in the website. The website talks to the local Dropbox app on your machine, the Dropbox apps 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 learning 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 and was doing the same. This is part of the fraud detection thing. It grabbed 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 configured, 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 use very, very useful blog called The Old New Thing. 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 Dr. Saddam's book Harker's Guide to the Galaxy. It's an amazing book. So Raymond talks about three privileged levels and a typical Windows machine. You have a remote attacker who's 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 web socket server. And then the 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 website, I'm sorry, 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 the resource from a different side, 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 that 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 your origin. Pat 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, sends a request to facebook.com and grab some data. Browser doesn't let example.net read the response, absent some other things, or 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 sends a post request to Facebook to change something, if there's no C-surf protection, it goes through, as a result, C-surf 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 time 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 it. And then now you have bypassed a bunch of protections in the app. And that brings us to our first bug. This is a bug by Tavis Romandi from 2016. Tavis Romandi is a really smart dude. He's part of the Google Project Zero. He installs Trend Micro antivirus. 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 web socket server. The local server is responsible for storing the passwords, updating, and a bunch of those. The extension in the browser, for example, you go to Facebook.com. The extension in the browser talks to the web server and says, gimme 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 regress 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 the browser. If you go and read the bug from the URL, from the link on this slide, you can see that Taviso 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 see the response, but who cares about that? Another thing that is used for local IPC are web sockets. A web socket is different from your typical HTTP request. In a typical HTTP request, you create a connection to a server. You send a request, get your response back, and then you close the connection. In a web socket, you create a web socket, and you keep it alive. You can write to it. You can read from it. It's great for real-time data thing, real-time data updates, like chats, video games, and stock trackers, I guess. But the web socket doesn't come out of the blue. It comes, 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 web socket 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. You can, yes, we can make a connection. Here's a protocol. One mistake that developers usually make is that they think that sec web socket key in the request and the web socket 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. 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 see the response, but the browser makes a connection for you anyway. So if you have a local web socket server, everyone can connect to it and talk to it. We can't rely on SOP or cores or whatever, any of that. And that brings us to our second Abysso bug. As I said, this guy is smart. This is from 2018. And he wanted to rebind his mouse key. It was a Logitech mouse, so he installs an app called Logitech Options. Logitech Options runs a local web socket servers on point 10,134. As I said, web sockets are not bound by the SOP. And the web socket 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 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, he couldn't get remote code execution. You could create crashes. So imagine you have this app running on your machine. 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, you have crashes. That's not really useful. And that makes our experience really bad. On the plus side, he couldn't get remote code execution, so that's really great. The next situation of the app, similar app called Logitech Hub, it runs two local web socket 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. So request goes from one origin to a different origin. 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 web socket 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 web socket connection. So websites cannot talk to Logitech Hub web sockets, so this is a great thing, and this is a good thing that Logitech has done. We've talked about local web servers, local web socket 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 that 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. As 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, none of Slack or Discord or Visual Studio Coders or Skype or whatever. The problem with Electron apps is that if you get across that 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 if you want this Node.js code to run, so you flip a switch. The switch is called Node integration, which is set, 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 set 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 tag 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 version second version was released, our CISO, who was one of the people who created the original lab, said, oh, I created and I worked on the original lab and I went and looked at 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 tag 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 like what files were added, what registry keys were added if there were any open ports, if there were any new window 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 this admin. In fact, if you don't run it as admin, it basically tells you you're not running an admin. You should be running as admin because if you're not running an 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 dot net, which is, I mean, it works, right? So after I created, 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 interface and open ports in the firewall. And I was wondering why would a local application or a tax service analyzer want to do that? So I looked at the console logs for the app and I saw that here. It is listening on all interfaces, 0, 0, 0, 0 and port 8001. That's not 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, I'm sorry, on the machine that is running the tax service analyzer app and I see the GUI. So the way the app works is it has an electron front-end and a web server in the back. Electron front-end which opens this website similar to what is in the browser as we can see and it does the rest, which is I guess normal for electron applications. The next thing I did, I tried to connect to it from outside because it's opening the ports 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're connecting from outside. I mean, you're 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 changed, 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. And next, so this is me connecting to the app from outside and doing cross-out scripting. The next thing I did is I wanted to check if there was a way that I can do inject drawer script because I wanna 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 your vanilla script alert one script, Java script. So when you start a run, you type the name of the run with script alert one script and then you start it at the bottom of the picture, you can see what it says status report for dot that the place before dot is where the script was injected and the alert pops up. So this is the 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, 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's 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'll start the electron app in the virtual machine and I go through it from the virtual machine host from outside, I'll 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, at XFS Analyzer was running as admin, that means someone from outside could 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 synodinal interfaces because the browser can connect to it. And I was thinking and said, yeah, that's 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 open the web page in the browser on the virtual machine to the right you have the browser, to the left you have the electron, gooey. 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 is already go through, the damage is done, the run has been started. Now you click on the electron up and go to scan and calc pops up, yay. So if the injection had been fixed and only the webbing, I'm sorry, the listening on all interfaces had been fixed, then we would have had a problem. Imagine you're running a tax surface analyzer on the machine, we're buying it as admin, you open the website and the website runs code on your machine, literally jumping the browser sandbox and getting that to your machine. So really fun, one of the funnest bug I have found. Unfortunately, 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 drew a lot of remediation. We have talked a lot about bugs and pointing at things and says, 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 ad, this is pretty old. So 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 three, so we need to tighten up the graphics a little bit. Every time, 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 three. So it's a great thing. It's from a Westwood college, which has nothing to do with the Westwood studios who made the Command and Conqueror applications. I'm sorry, Command and Conqueror Games, which are now part of EA. So what can help us? Our most trustworthy friend in this case is a browser. 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 goes through. Do not rely on same Origin policy. You already know that the simple get requests are not gonna 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 web sockets. Again, with web sockets, they're not banned by the same Origin policy, but the handshake was a get request. So when you see a handshake in your web socket 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 hopped it, return a four or three, four or four or whatever. Anything that tells the browser that this request was not successful. That means a web socket is not gonna get created. One thing that I've seen developers do that is not useful is checking the remote address that's coming, that where the request is coming from. This works to some extent. If you have something listening alone 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 request is running in 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 localhost 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 gonna 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, in the back. And I don't really blame them because it's very convenient. 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, JavaScript in the preload has access to the Node.js API. So that means that you can run your own Node 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. But click on the link in the Electron.js website. They have a lot of useful information on how to prevent these attacks, on how to do things properly. If you use a local web server to serve content to an Electron app, enable, disable cores. So if you have a local web server, so there was this app I was looking at, 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 they could read everything. And that's not really great. That's not something you want to want to do. So disabled 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 threat 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 an important hacker, 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 your 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 threat model, have your proper checks. As I said, checks are really simple. We can defeat it, it would be fine. So I've talked a lot about how to find things, how to fix things, but your next question is how do we get started in this niche? Practice is the best thing. Start a virtual machine, install a bunch of these decline tabs, run that stat, 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. So 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 the Electron.js website which has all the Electron apps. Electron apps are usually to have the same thing. If we want to learn more about hacking Electron, there is a GitHub repository with a bunch of links, a lot of thought of useful information, a lot of great research, a lot of great bugs. And last but not least, read Taviso bugs. The awesome bug that I told you about that hasn't been this close, it was two Taviso bugs 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 gonna happen is I'm gonna be on the Discord and I will answer your questions there and then. To find more about me or if you wanna 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.