DEVWorld 2026

Jan Peer Stocklamir - Trust issues: Because Zero Trust Isn't Optional Anymore

25:00 · 07 May 2026 – 08 May 2026 · YouTube

About this talk

This talk addresses the React to Shell vulnerability, a significant common vulnerability and exposure (CVE) that affects Node.js applications utilizing React. The speaker explains how this security flaw allows attackers to execute remote code on servers that handle outbound traffic, emphasizing that access can occur without credentials. The discussion includes the principles of zero trust security, highlighting concepts like least privilege, micro-segmentation, and continuous monitoring. The speaker also explores practical security implementations in modern infrastructures and mentions tools like Distroless images and service meshes like Cilium for enhancing security. He outlines potential mitigations to safeguard systems against such vulnerabilities and emphasizes the importance of proactive security measures, such as Content Security Policies and mutual TLS.

Full transcript

[singing and music] >> All right. Hello everyone. Uh it's nice to be here. Uh before I start with the presentation, I want to say that you can actually win something here in this conversation or in this presentation. Win one of those free ducks. Uh I placed uh a couple of ducks across the presentation. You just count them, go on the QR code, and then you can win

them. Uh those are free natural rubber ducks. Um so just check it out, and then you can place it. By the way, you have to enter enter your email address. I will delete all the email addresses afterwards. Um and the further instructions once you get the email. All right, let's talk about React to Shell. React to Shell was actually one of the highest CVEs uh out there

in the last 6 months. A CVE stands for common vulnerability and exposures. A CVE is usually scored between 0 and 10 with a CVSS. Uh CVSS stands for common vulnerability scoring system. By the way, for everybody who joined later, there's a QR code. You can win one of those free ducks. Just join. so everybody wrote about that. It was a very highly critical in uh very highly

critical CVE. Everybody reported that. So Google Cloud reported that. Cloud Flare and even ourselves said that this was one of the highest CVEs out there. For everybody who does not know what a React to Shell was, let's imagine you have an Ubuntu server. On this Ubuntu server, you're running a Node.js service with a React part on it. This one is actually also exported to the internet. So

everybody who wants to have access to the internet uh to your React application, they can through one of these ports. For some reason, you also have a Python service on this on this Ubuntu server. But this one is actually not connected to the internet, which means nobody has access to this Python service. And that's actually the thing because the inbound traffic is actually only via one port

and nothing else, but the outbound traffic is usually always allowed, which means a Node.js service has always everything allowed on the Also, the Python service has access to All right, with the React to shell vulnerability, an attacker could send an vulnerable payload in form of a React server component and send it to the server. The React server component, what it does is it was just executing everything

on its own and then give you basically access to the machine itself. Because the outbound traffic is always allowed, you can basically add a reverse shell. Usually how a shell works is that you basically just connect to the server and the server listens to it. So, it's inbound to the server itself. A reverse shell is basically the other way around. Which actually means that you as a

user, you listen to a port and the server connects to you, which means it's basically outbound and you have uh server without any credentials and that's actually one of the highest CVEs you can ever have because nobody needs credentials to have access to your server. And credentialless. A friend of mine said actually that they were really lucky that they didn't update their code because they were not

affected. That could be good or bad because there This not the only CVE they had. There were also a lot of other CVEs and if you don't update, you might be affected to other CVEs. So, let's talk a bit uh zero trust. What is zero trust? It originated actually from the networking perspective back in the days. So, just imagine you have a business identity with a lot

of interfaces uh and usually you have a VPN and if you go with the or over the VPN into your system, everything inside the network is considered safe and everything is not harmful. With zero trust, it actually changes, which means that you should never verify anything in your system and you should uh you should never trust anything in the system and always verify that this is really

trustworthy. So, this can be achieved with couple of things. For example, the principle of least privileges. A principle of least privilege means that you only need as much access as you need. For example, you have a service which has access to a database but it only needs read access but it doesn't need write access which means you give the service only read access and not nothing to

write. There's also a thing called micro segmentation. You can imagine it of there are a couple of uh networking pieces and they should not be allowed to communicate with with each other. So, you basically segmentate everything in tiny networking segments. There's also a thing and everybody knows that multi-factor authentication. Uh most of you know it with two-factor authentication. You have a username password and a second authentication.

Most of the time it's a mobile phone with an authenticator. Also in zero trust, you need to have continuous monitoring. Let's assume you have an attacker somewhere on your system and you don't know but with monitoring you immediately know if something happening if something connects to your system. And the last thing of the risk adoption, let's assume you have a service which has read and write access

but in two months you remove the right access and it doesn't need this right access anymore so you go with principle of least privileges and then remove the access to write. That's a lot of dry stuff. So, let's talk about the practice how you actually implement this into your end-to-end stack. Let's start with the front end. Let's assume in this case you have a website which only

has couple of JavaScript files, a CSS file and you also have an external trust vendor where you just add an add-on. Also assume you are very modern, you always try to stay up to up to date. So, on NPM some library got updated. Dependabot, Renovatebot up to date your updates your library and you also automate auto merge everything because your tests are passing, your tests are amazing,

and also you auto deploy straight to production because you want to stay up to date. The bad part is that you actually forgot the first part. A library got actually compromised, and some of you might say, "Yeah, that would never happen to me." And that actually happened just 2 months ago with Axios where one attacker stole the credentials and updated a malicious version of it. So, basically,

yeah, you just deployed something bad. In our case, we could we could imagine, for example, jQuery got compromised because there were there was a new release, version 4 by the way, um and for some reason, it adds now external.com and it loads a malicious version where potentially they could steal credentials of your customers, which is kind of bad because your tests usually don't test for things which

would never happen. There's a thing you can actually do proactively already, and it's called CSPs or strong content security policies where you basically allow list certain things you load on your website. And this is what the browser is doing and the browser is preventing on that. In our case, it could be that you have a CSP and you only allow list certain things. In this case, we

have a a default source. The default source is basically the source which we default for everything. For example, the script source. We also have that here. Also, we allow list our mytrustedvendor.com website, that the external.com is not even loaded, and we remove it by default. So, you're also automatically protected against further attacks. For the Axios one, you also have a second option you can do is basically

you delay your updates basically. By delaying your updates, in this case for Axios, the malicious version was out there only for 3 hours because the community was so great to detect it automatically and then reported that, and the owners were also really fast in uh fixing that. In NPM or PNPM or Yarn, you can add this with a default flag. In PNPM version 11, this is the

default already, so they already protect you by default. This actually does not protect us against the react to shell vulnerability because this one is usually in the backend and not in the frontend. So, let's talk about the backend, and also, because it goes hand in hand, let's talk about the infrastructure. you have a service, or you have a infrastructure that looks like this. You have a node

application that runs react. The a small parenthesis says that you are having shell access, so a real Ubuntu service. This one has access to the database. And also, there's some irrelevant other infrastructure where the node service potentially has access to. On this node service, you have secrets like cookie secrets, database secrets, or chat secrets. Uh with the reverse shell, basically, because they have access to the node

service, they could potentially steal everything. So, if they have access to your node server, they could potentially go to the default port of Postgres in this case, 5432, could go to the database and steal the credentials or user data, whatever. Uh and user usually attackers, they like to chain the attacks, and they have a attack chain. And then they steal more out of it. So, let's make

this a little bit better, right? What we can do is actually, because it's the default port, we just change default port to 5432 uh 431, right? Perfect. Or what we can also do is we have more uh we have some service which it connects to more services, which goes to intermediate server, and then goes to the database. Right? Nothing can happen. This is actually called security through

obscurity, and it's not great to have it because you're getting confused by your system cuz you know, if things hit the fan, uh you have no idea what's going on and also your attacker might get confused just for one more minute, but after that straight access to the database anyways. So, let's make this right. We talked about principle of least privileges and microsegmentation. And microsegmentation refers to

actually an approach to security that involves dividing into an a network into segments and applying specific security segments onto the require uh requirements on these segments on its own. Woof. A lot of segments. How would it look it like here? In our case, we have this node service which has access to some irrelevant other infrastructure, and we basically don't allow any access to the other infrastructure itself,

which means we don't allow it anymore to connect to it. It doesn't mean that this other infrastructure has no access to the outbounds uh world or anything. It just means that these two cannot communicate Now, we can make another thing. For we see a lot of secrets now in the node service and and the React service uh server-side rendered app, which means we don't know about our

trust boundaries because the React application is part of rendering, templating, and there's basically just a lot of attack surface on its own. We can reduce these trust boundaries and we can reduce these attack factors by a lot by just moving one service or just adding one more service. With that, we can move now our secrets, our database secrets, and uh chart secrets just one layer above. All

right. Would that now be more secure? Maybe not, because theoretically, an attacker could put somewhere a malware in your system, and you would not know because you are not monitoring it, and they just sniff to your port, and then they get credentials, they get uh every every payload of your uh of your port. And it's not really nicer than before. What we can do instead is basically

making our internal port so everything in here is internal, our internal port connect encrypted by mTLS. mTLS stands for mutual TLS. It's basically one step further than normal TLS where normal TLS is a one-way encryption where the client says, "I'm encrypted." and sends the data in encrypted fashion. And the mutual TLS is it says the client also says, "Hey, I'm encrypted." but also the server says, "I'm

also encrypted." and it's a two-way safety net of being an encrypted connection. So, in this case the mail where is basically useless. So, we can basically forget about it because the mail where cannot steal anything. In Kubernetes there's a thing called Kibana where you add some policies onto it and Cilium where you have a service mesh where you just add a lot of network policies on it.

There's even another thing you can do on the node service. There's a thing called Distroless and Distroless is basically a a way how make things on your Docker container for example what may way more secure. By default you have maybe a node node image or a node slim image. But there's a thing called Distroless where you basically directly have access to the node binary and the node

binary is connected which means there's no more shell on your container. And theoretically if you don't have a shell, you cannot add a reverse shell because for a reverse shell you need a shell, right? Super clever. There's actually a thing called GTFOBins. Uh for everybody who does not know what GTFO stands for, it stands for get the sorry, get the duck out. Uh so it the thing

is with the GTFOBins.com I think the website it tells you which binaries you can have on how you uh get out of your binary and still open up a reverse shell or any other exploits. So, that's actually a very nice website to check out how you could be potentially be on threat. Uh so, because there is no shell on the distroless container, you might say, "Hey, I

cannot debug anymore because I cannot shell into my container on production, right?" The thing is, you should not debug on production. There's There are tools out there, so you can use Sentry and uh debug any anything because we tell you where the problem actually is. All right. This is everything you can do basically for the Reactor Shell environment in this example. Let's assume we are now on

a Cilium Kubernetes cluster and we are protected with Cilium service mesh and Kivano policies. Now, for our connections from our client to the Reactor server, we can make it more protected by having post requests having a CSRF token. CSRF CSRF stands for cross-site request forgery. It's out there since I don't know, since ever, I think, and just protects you a little bit further from attacks. Because we

already talked about distroless, we can make our other node service, which is only protected by incoming requests of being Yeah, that that I forgot before, the the other node server only accepts incoming requests by being protected with a bearer token. Uh we can also make this one distroless, so the attack surface is even smaller. Also, by the way, the connection to the database is usually by default

always TLS, but if you host your database on your own, which I personally don't recommend because it's tough, especially with sharding and everything, uh just make sure it's really TLS Also, with zero trust, it means that we should not actually have access to certain things where uh we don't need access to. Because we already have the other node server, which has access to the database, we should

go over this route. So, we wouldn't allow any any connection between the React server-side rendered app and the database. It doesn't mean that you're not allowed to, it just means that if you do this, just make sure that you really have another secret and another role basically for doing that. For example, in this case, you can have a read access only. The problem with that is it

needs another secret and it needs another role. And because usually in security you need to update your secrets in a more updated uh fashion, like for example, at least uh 90 days, you need to have another secret you need to update, which means it just adds a uh trouble to update them. Thus, the last thing you can do in this setup, uh on the database, it just

added RLS. It stands for row-level security, and this is basically a security measurement on the database where you just enable uh you just return certain data if you have basically uh access or if you add another layer. If you add another attribute to the query. Ha, now I got it. All right, that's everything what I have for for this presentation, and now let's go to the demo.

Let's see if that works. Oh, no. I don't see anything on my screen anymore. Now I see it. All right, that's a little bit too small That should work a little bit better. Perfect. All right, so what I prepared is basically a vulnerable uh a vulnerable application of react and the kubernetes cluster. So it's basically when I enter some feedback, I go to the react server side

component that that's that goes to some back end for front end which is our other node server and that So in our service mesh, this is now on kubernetes, we have our ingress which is now our uh our person who has access to our react database our react service which goes to the back end for front end and that goes to the database. So a pretty simple

setup. And we said that basically our uh react application is vulnerable to the react to shell. So if you open up kubernetes, we set for a reverse shell, we actually need to have a listener a listener that actually listens to the reverse shell. So in this case, I opened up a listener on my local computer. Uh and go directly so I wrote a script which basically takes

care of this vulnerability and open up a reverse In this case, it automatically connected from somewhere in the world in this case uh on my from my kubernetes cluster to my local machine. So in this case, I connect from so there so I listen to something but because it's two-way, it means that I have access to basically everything, can print the environment where variables and you basically

lost because everything is inside here. The problem is that most of the time environment variables are printed in the directly. Uh security measurement is that you can print or add these uh environment variables not as variable but as a file and then every database connection goes via a file and reads them. So it makes it a little a little bit more secure. The thing is uh because

in zero trust there's active monitoring and HWI is basically here for making it monitorable. Uh we see immediately that there is uh some port going outside and you see the tiny little arrow here, which really goes outside and it's outbound traffic, which goes to the world on a port 4444. We don't know exactly what's going on there, but we see that we are basically sending some data

from our reactive application, so we have to be more careful here. What you can also do now with uh in Kubernetes, uh so if I go back to my listener, uh I also prepared um where is it? Uh a malware payload, where you can basically, because you have access to basically everything, you can or you can spawn a new uh Kubernetes pod and if you if you

go into our Kubernetes cluster, we see a new uh container is actually created, because we have direct access. The good thing is that Kubernetes by default uh disables that, so there's a good security measurement that this does not happen, but in a bad configured uh Kubernetes cluster, this could potentially happen and you have to be really careful that this does not so in our case, to show

you how this could look like uh look like, uh there's a role uh R back usually and a role binding, where I explicitly say my service account has access to creating an account and this could always happen if you use roles and R backs and misconfigure it by for some reason. All right. We also talked about a distroless file. Distroless actually just means for a different container

image. In this case, I just changed now the default uh runner image. In this case, it was a default node uh default node image and we change it to a distroless image uh from from Google itself, so it's a Google project, basically. And because it's a node binary which runs directly, we have to change our command because the entry point is now node, and we change it

to In the meantime, it just deployed. And now we try actually to go and open up a reverse shell because now we are in in a safer environment having a uh, a distroless service. So, let's make this, uh, new again. And now, if we want to have a reverse shell opened, it it, uh, actually says that, uh, okay, it didn't, uh, didn't deploy yet. Uh, so, maybe

it deployed now. Okay. Now it deployed. Uh, so, I have, uh, Skaffold which automatically deploys my Kubernetes cluster into my local environment. It was not deployed before, but now it says when we want to spawn a my script or my listener says that the shell shell is uh, disconnected, and we were not able to open up a reverse shell. It does not mean that the reactor shell

vulnerability was not able, uh, so, was not removed because I also added a POC where it executes basically every command. In this case, we execute who am I, but because it's not a no uh, it's uh, Ubuntu binary anymore, but a node binary, it doesn't make any it doesn't work. It still works that we run node specific commands like os.userinfo, and if we go further, it even

says, or we can even etc/passwords, whatever. So, you can still steal it. You just minimize your attack vector, and the attacker has a harder time to actually go into your server and steal the credentials it needs. let me tell you that there is actually, uh, even better way of doing that. So, we said before that we have a way of making things more secure by with Cilium

and uh Kyverno. So, Cilium is our service mesh mesh and Kyverno is basically a policy uh policy uh tool which adds basic And with Cilium, you can basically default deny anything. Uh so, by default, we just deny everything uh anything because in zero-trust, we say we uh don't trust anything and we always verify. In this case, we have to allow list certain features, for example, from front

end to back end or uh connection from outside to the inbound traffic. So, now, if we if we now upgrade our policies and start our service again, which might take a little while, uh it could be that nothing works anymore, right? So, let's see. Okay. So, this one is running now. And now, let's open up with all these policies I just added with deny denying everything and

just allow listed everything, let's connect again with the Nothing is happening, which is great because now we are not even allowed from the outside to go in, which means that we are now safe by default because we don't trust these ports. So, we are not allow listing them. If we would allow list them, of course, it would work. That is great. I think that's everything what I

prepared for the demo. I think we are also pretty much out of out of the time. Uh so, let's go back to the By the way, you can already submit duck challenge thing for everybody who was here since the beginning. Um there's some TLTR. And that's it. Thanks a lot for joining. >> [applause]

From event

DEVWorld 2026

07 May 2026 – 08 May 2026

All event videos
Back to Watch