Welcome to this talk, When TLS Hacks You. I'm actually going to start with the demo. This is going to be weird. This isn't a concrete attack scenario, but hopefully should make everyone curious about what's going on. For those familiar with SSRF, the implications will be fairly clear, but I'll explain them right after the demo for everyone else. After that, the attack examples are going to gradually move towards real stuff, but I find it's easiest to look at the simplest cases and then start expanding to look at real-world stuff. For now, though, let's see the essence of the dangerous property of TLS that this talk is about. So, here's memcached, a popular cache that commonly runs on port 11211. I have it installed here, so you can first see for the key named Z, there currently is no entry. If you're not familiar with memcached, this is how stuff communicates with it, a newline-delimited TCP protocol. But now I'll do something that you might not expect to affect memcached. I'll make a request to an HTTPS URL. The "-l'' enables redirects, and the "-v'' lets you see all that's going on. So, you can see that the endpoint is redirecting to itself. I sped this up because this is a lightning talk, and the string of redirects takes a couple of minutes. But the redirects don't go on forever. After a bit, we get an error. And now for the magic. Going back to memcached, if we rerun the same git command, we see an entry appear. The odd thing is, that text never showed up in the curl command or in any of the redirects, so somehow the server we were talking to had to do something with TLS to trick curl into putting it in there. So even though I didn't just do anything other than hacking myself, what just happened was weird and potentially useful. But here's where I'm going with this. Because not everyone is into these sorts of attacks, I'm going to start off with exactly what makes this useful as an attacker. I'll then move on to some stuff that shows just how widely these attacks can be applied, and then get to some recommendations for what to do about them. So to start off with, there's an existing class of vulnerability called SSRF. It stands for server-side request forgery. It's where you effectively phish a server into doing something by sending it a URL. Last year at Black Hat, I demonstrated a bunch of these vulnerabilities in Apple Pay and in Webhook receivers. I'm not going to go into those as much here, but if you're curious, you can download these slides later and go to this YouTube link right here. SSRF has a ton of different approaches, but it was actually pretty easy in some cases. Webkit.org had a live sample implementation of Apple Pay, and it was even on AWS with the old Insys metadata service. So all I had to do was pass this IP, and I could poke around an internal API and get some tokens. But not everything was so simple. I had a lot of SSRF vulnerabilities that were almost there, but weren't vulnerable to this easier approach. Sometimes I'd have a Webhook where I could hand it a URL, and the server would even fetch the URL, but not give me a response. This is called blind SSRF. Other times the request in question would be a put request, which restricted what endpoints I could hit. Sometimes you can mess with the request method by setting up an endpoint that redirects the target server around, but that's hit or miss. Other times you might get some data back, but it's validated against a specific content type, or you only get back certain fields. Or even worse, it'll only accept HTTPS URLs. But what we really want in order to exploit this stuff is a generic blind SSRF approach. In other words, we'd like to see in the server a URL, have it fire a payload at something internal like SMTP or Redis, and get a security compromise just from that payload, without needing to see the response. Here are some of the common approaches. The issue is these are extremely platform-specific, to the point where I've never had any luck with these. But there are some useful ideas in here, particularly in SNI injection. You wouldn't think that TLS would be particularly useful, because its packets are mostly just encrypted data, binary flags and other stuff an attacker doesn't have much control over, but it's not all that limited. As OrangeSci noticed, the SNI, which is basically just the domain name, is part of the URL. So if you can trick the victim into accepting a weird domain name and parsing it incorrectly, you can include stuff like arbitrary bytes in there and smuggle stuff like SMTP commands. This approach is dependent on a platform-specific URL parsing book. But what if we had something that was inherent to the TLS protocol? This is where my work starts. Let's look at a TLS packet and see what's in there for us to use. As is often helpful when looking at protocols, we can open up Wireshark to see what happens in a TLS connection. Here's the SNI, or server name indication, that was previously explored in Orange's talk. His work involved putting new lines and SMTP commands into the section pointed to by the arrow here. If you poke around, you'll see stuff like random strings, key shares, and other stuff that isn't useful for SSRF. Why? Because the only part of this we can typically feed a server is a URL, and the only part of the URL that will make it into the client-hello packet is the domain name that we provide the victim machine. But there's a different way that we as an attacker can get payloads into client-hello messages. It involves a couple of steps and is the main focus of this talk. First, we send over a link to our own server, in this case jmatrix.com. Then jmatrix.com responds with a valid server hello, but it contains our payload. Now, it would be ideal if we as an attacker could get that payload that jmatrix.com sent the victim, and then get the victim to send it somewhere internal. But that raises a question. Is there such a field that we can use to deliver these payloads? It might seem too good to be true, but TLS actually provides exactly that in the form of a session ID. Most clients even persist this field for later use. Session IDs are limited to 32 bytes, as you can see here, but depending on the implementation, you might have session tickets. This one is about 200 bytes, but these can be up to around 65 kilobytes. Both session IDs and session tickets are mechanisms for a TLS client to go, hey, remember that cryptographic key exchange we did earlier? Let's just keep using that key in this new connection. TLS 1.3 includes a slightly more complicated but similar mechanism called a pre-shared key identity, which pretty much does the same thing for our purposes. All of these are about optimization, since key exchanges can be time-consuming. But they provide a way for a server to tell whatever is connecting to it to persist some data for later use, almost like a cookie that lives in plain text. So it's perfect for the SSRF attack we're trying to do. Now, you might anticipate a problem here. Obviously, if you go to google.com and then to apple.com, those TLS sessions are going to have distinct data from each other. The same applies between google.com and localhost. So we have a bit of a problem getting the target server to reuse the session when communicating with something internal. But when I looked at both the TLS spec and popular session cache implementations, I saw this wasn't quite the case. I first went to the curl source code, since libcurl is used by a lot of PHP stuff. Sessions are keyed by the four properties here, which belong to this struct. As you can see, none of these are the IP address. This means that no matter what IP address jmatrix.com resolves to, curl will attempt to reuse the session. This happens to be how just about everything else caches TLS sessions. So if you establish a TLS session with jmatrix.com, whether it resolves to something on the internet or your own machine, your HTTPS client will not know the difference. Since I, as an attacker, own jmatrix.com, I can combine all of this TLS stuff with a concept called DNS rebinding. So I resolve the first request to 35.x.x.x, some server I control. Then the target server picks up the payload from 35.x.x.x in the form of a session ticket. After some time, I send the exact same URL, except the DNS entry has expired. Again, since I control the DNS server, this time around I resolve jmatrix.com to something internal that I wouldn't normally have access to from the outside. To simplify things, I'll just use localhost. Once the DNS query finishes, the target server then makes an internal request with that payload. So, like what I did in the opening demo, you can manipulate whatever you're attacking to send a TLS session ID, ticket, or PSK identity like this to itself. Here I'm showing a PSK identity that does a memcache insert. And here's that same TCP stream, but just displayed raw. This view shows the really important property of my approach to combining TLS with DNS rebinding. You can get arbitrary characters to be sent, which includes new lines. This is what allows you to interact with a bunch of local services like memcached, just like people did in the past with Gopher URLs. And here's an exchange more typical of what you'd use to do real damage. This is a value that in most Python memcached libraries will execute a shell command immediately once it's retrieved from a cache. This is because it's quite common in Python and other languages to use built-in serialization methods like pickle to convert values to something that can be stored. Unfortunately, built-in serialization methods are also common RCE targets. We'll get to exploitation later. But since this is pretty common, I ended up testing a fair amount of slides. I'll get to some specific ones in a second, but first I'll go over some testing infrastructure I set up, along with some tips for actually performing these kinds of exploits. So here are all the VMs I have on the internet. Say for maybe some IP whitelisting, they need to be on the open internet so that the target boxes can reach them. I've got a nameserver record pointing ssltest.jmatrix.com queries to the DNS server on the top left, which is just a Python script running on port 53. It alternates between answering with the IP of the custom TLS server and localhost. Then over there on the right, I have my custom TLS server, which is there to deliver my payloads via session tickets. I configure those by just updating values in Redis. But when testing it, it proved to be difficult to just go straight to localhost, because depending on what library is making the requests, I didn't know if SSL sessions were being cached. To address this, I sometimes just ended up adding another box to see if the payload was being persisted. If you're not familiar, Netcat can be set up to just listen for TCP connections and log the results. If I saw my payload making it into the logs, I knew the general approach could work. And then go back to the setup from the previous slide to get the server to start delivering the payload to itself on different ports. And here are the links to all that I actually used. A big chunk of the work was saved by just messing around with a pure Rust implementation of TLS, which is linked to here. A big thanks goes out to everyone mentioned in this slide. I wouldn't have had the time to do any of this work if not for the code they had put out there. But I know with everything I just claimed, I need to back things up by showing what actual real-world attacks can happen with this. I'll start off conceptually and then get to some examples along with another demo. But here's the mullot I've got. If you're in the middle of this diagram, you're generally vulnerable. This top part refers to when you can send something a URL and it will fetch that URL, but you don't know of a way to exploit that behavior. It's interesting because you would hope by now people would try to eliminate anything even remotely looking like SSRF. But this pattern is in fact required by a few popular specs. This is even common enough that I've seen bug bounties mention SSRF with no proven security impact as an excluded category. But that's what this talk is about. Adding the security impact when you have just something that is just almost SSRF. This one on the left sounds weird, but it's getting bigger. It's just a matter of if whatever the SSRF is going through to make requests has implemented this feature according to the latest spec. Unfortunately, there are quite a few HTTPS client libraries out there that support full-blown TLS session caching. Here are a few that you may be wondering about. I reported this as a vulnerability to some of these, but not all of the entries with yes. And I haven't seen much traction. Understandably, since fixing this requires diverging from the TLS spec, not a whole lot of people are eager to do so. The thing you run into the most trouble with is this last circle. Because you have to first do some recon to see what ports might be internally exposed on the server. And then you need to see if what's on those ports is permissive enough to keep the connection open past the opening bytes of the TLS client hello. Here are some examples of what you might be looking for there. It's far from complete, but here are the most notable entries. So now we'll go through a couple of exploitable examples of these TLS session-based SSRF vulnerabilities that I reported and got fixed. Here's one from uTrack. It's rated low because all I did was hit local SMTP, and I didn't try poking around to see if I could exploit the SMTP server further. It originated from uTrack's import feature, which normally wouldn't be easy to exploit, being blind SSRF. But with my rebinding code, here's a section from the TLS packet I got uTrack to send itself. This works because SMTP is a newline-delimited protocol, so just like memcache, you can embed working commands within a TLS ticket. And here's what came of that packet. This alone wasn't super severe because it's not signed quite right, and I didn't dig too deeply because I wanted to just report it ASAP. But it did get initiated by a TCP stream I got uTrack to send itself, so it's still pretty bad. Here's the one I reported in Nextcloud. The idea behind Nextcloud is a cool one. You set up a server and host files, and can even share them with other people's servers using a share URL like this. But as it turns out, you can include port numbers in the share URL. Now, since Nextcloud automatically puts HTTPS in there, normally there wouldn't be much SSRF potential, and it would have to be blind SSRF. But luckily I was able to do just that. So as a non-admin user, I could use my TLS for binding attack infrastructure to perform arbitrary writes on memcached. After reporting this and working through the options, there weren't any great ones. Because not all request libraries allow you to disable TLS sessions. In this case, libcurl has the option, but it isn't exposed by PHP request libraries such as Guzzle. In any case, adding a request timeout made the attack pretty much infeasible, assuming that people don't turn the DNS time to live values super low on their servers. Nextcloud was still generous enough to give me a bounty, even though in my opinion this is libcurl's fault. Since the last couple were just text slides, let's see a real demo now, from the perspective of a Django web developer getting phished. The DNS for binding part has been done in the past, but what's new here is combining DNS for binding with TLS session persistence to make it more powerful. In the past, you had to get people to visit a fully malicious page to get anywhere with DNS for binding. But with my work, you can do the same with just an image tag. Because of that, you can phish people into RCE, even if they just view an email, but don't click any links. Let's see that now. And now for the actual demo. So as you can see, we're pretending I'm a Django application developer running a typical app. If we look at the app, it's pretty simple. We can view a sloth emoji, but if we refresh too fast, the sloth needs to go to sleep. And now for what happens when I get phished. As you can see, I have received some perfectly legitimate emails, and I'm viewing them. Normally, these image tags should be benign, but after viewing them, my sloth now opens a calculator via shell command. That's because those image tags pointed to the domain with my binding setup, and a simple Python pickle payload. So here's most of the source code of the example app I created. You can pretty easily find apps that call django.core.cache or on GitHub, but I didn't want to demo against a specific one, because that seems a little mean, and I wouldn't want people launching actual phishing campaigns against such projects. It's a little tough to assign blame here. Nothing is really breaking from established safe practices, but it's clear that there's a potential to compromise real-world developer laptops. On that note, there's a lot more to do that I couldn't possibly do on my own. These slides will be available online if you want to return to this, and if you do anything interesting, I'd love to hear about it. There are certainly a lot more potential vulnerabilities to explore. So all of that is bad, but what can be done about it? Let's start from the root of the problem, TLS clients. Here's something I recommended to the Chromium team a while back. We still aren't in agreement about whether or not this should be fixed, and my report was pretty hastily written, which probably didn't help things. Essentially, I'd like to remove the ability for sessions to be resumed between servers. But while the Chromium team has marked this as a won't-fix, they did suggest an alternative that maintains support for resuming sessions across servers. They probably care about that more than I do, but we can at least have the best of both worlds. This last option of just partitioning based upon whether the address is something local is a pretty good compromise. It wouldn't affect performance, but would protect against most of the attack scenarios I can think of. But based on my experiences so far, I'm not hopeful that there's going to be much proactive effort on the part of software packages that make HTTPS requests. For most of the audience, it's more useful to know what's susceptible, and what layers of defense can be applied. However, with all of this worth taking a step back to consider, is TLS session resumption even worth it? There are other issues with it, a couple of which are shown here. I'm not going to make the case that the industry as a whole should give up on this optimization, but we should take a step back and see if this is a worthwhile trade-off. We may, just like in the previous slide, find that the costs can be eliminated. But if we find we can't tackle these problems, we need to weigh them against the benefits. And here's the main benefit. You get a sizable performance improvement on the small subset of packets that are TLS handshakes. You probably care about this whenever you're in the position of serving up TLS. Whatever percentage of CPU usage is due to TLS handshakes, you probably want to keep that low to minimize costs. But if you're like me, you don't care. I'm taking on all the security and privacy risk of session resumption, but the amount that it saves on my power bill is pretty minimal. Additionally, if you're running a web application, you might care about session resumption on incoming connections, but when you're making external requests, it's probably not in your interests. You're placing a lot of trust on random websites not to redirect you and on your local services. So how might you turn it off? Here's what I found. It's kind of sad when stuff forces you into this, but at least as an internet user, you can pick Firefox and disable session identifiers if you're paranoid about this stuff. If you're running a web application, you're a bit stuck, though. So what should you do? First off, it's a good idea in general to run your outbound requests through a proxy, especially if you're supporting webhooks or some other SSRF-prone feature. But also reconsider your infrastructure decisions. You'd be surprised to know just how much stuff is still set up in a way that is just one locally initiated TCP stream away from compromise. But here are the general takeaways. The thing I'd like to highlight is if you're just starting out in the offensive side of security, I'd encourage you to look at the random standards and browser features that come out. Even if you don't fully understand them at first, find something that seems interesting and start hacking around in Wireshark. I know I didn't understand TLS at first, and I still don't really understand it very much after all of this. But as an attacker, you really only need to understand the worst parts, and that's what this is all about. And thank you. Here's my Twitter and a link to Latakora, where I work. They're a great place to work and have been amazingly supportive of random research directions I take. Definitely take a look.