 Hi, I am the disembodied voice of Jeff DeLeo. I work at NCC Group. And I am Addison Amiri. I used to work at NCC Group. And while Addison was at NCC, we started this research into DRuby, which is a remote object invocation library API that's built into the Ruby standard library. It's used by a couple of random things, but often sees more use in ad hoc code that's not necessarily open source. So while back, we were working on some job instrumentation stuff that involved JRuby and had a REPL. It was really fancy. And we were testing it, and we found that this DRuby stuff was being used by one of the dependencies. So we started looking at it because this was gonna be used to assess, you know, reverse engineer things. It would be bad if something on the device under test could then compromise the remote system, auditing it. So we found that a lot of different things. It was already kind of known to be insecure from the docs, but you know, high level overview of what we're gonna talk about. Obviously remote method invocation, custom binary protocol, object serialization and client server, really server-server peer-to-peer interactions. So to just kind of jump into a very simple example of the code, you have on the left side here this server code that exposes a front object that is just a Ruby object. It's an instance of a class. And then on the right side, you have the client code which essentially opens up through the DRB API to get a handle onto that object by a URL. And essentially you have a proxy object there that is the time server on the right. And when you call things on it, they magically get transported over to the left side and then it does some stuff, it returns a value that magically makes its way back to the right side and then you have a value. So how this is actually working is that the variable on the right side is not actually that real object. It is in fact a DRB object, which is a proxy wrapper. And so essentially you need this to send calls over things, especially for values that can't be sent back and forth. Generally speaking, it's used for the main interacting API object that's exposed, that front object. It contains two fields, URI, which is the URI for the server where it resides, the actual object backing it resides and then a reference, which is some sort of unique reference identifier on that server process to essentially forward calls and things too. By default, that's just the Ruby object ID and you can actually use this to wrap both local and remote objects. So you yourself could in your own process have a DRB object that points to your own processes DRB server and then call things through it. And the way that it functionally works is that it overrides method missing, which in Ruby is the magic catch all method that essentially anytime you try to call something that isn't defined on an object, it goes to method missing. And if you've overridden that, you can essentially handle all those calls in proxy them. So the interesting thing about this is that the ID values that are passed back and forth, actually there's no keeping track of that. If you happen to know an ID value in a remote process, you can simply create a craft a DRB object for it. And as long as the server is accessible, make calls to it. That's kind of not great. It has no ability to track when it's kind of leased out of handle. And so you can just entirely avoid that front object by guessing IDs. Then the other interesting thing about the code base is this DRB undumped mix in that basically the way that the DRB DRB knows to wrap an object instead of just serialize it, send it over is that the serialization fails. So the easiest way to make that happen is to override the serialization method and force it to fail. And that's how that's quite literally how you annotate your class to not be serialized and get proxied instead. So we see here the distributed object protocol is a distributed object protocol that is unlike most distributed object protocols, like BitTorrent and stuff like that. There's no centralized registry. There's only a client connecting to a server and that's the only connection that's going through. So we see these as unidirectional and the server in this sense will send back a success message, whether the call succeeded or failed and a value for the call that's being happened. Now moving forward, the DRB message that's being called includes a reference to the object that's being called on. Now this will be nil if the object is the front object of the DRB protocol and this will be integer if this is some other object in an object space. The method name is next, the method name is included as a string, the arguments are next, and then forth there is this block that is nil if there is no block. So next we can look at the DRuby sort of code that goes on on both ends and we can see that there's this time server that is like hosting this front object that we talked about that responds to get current time and we see the client, but the client's also hosting a service and we'll get to that in a little, but here we have a get current time call that's happening on this time server project. So here we can see the call flow going and we see that the object reference ID is nil, meaning that we're calling something on the front object, the get current time value is the function name that we're calling. We see args that are nothing because there are no args to this method call and then we see the time.now value getting passed back to the current to the caller. So going forward we can break this up and we can see the actual wire protocol that's happening here and in blue we see what's prefixed a length of every message that's going across. So there's multiple little bits that are coming across and each bit is prefixed with an integer value that determines the number of bytes to read off of the wire. Each of these number of bytes is actually a marshal value. So Ruby marshal value. So we see the first one's three bytes and we can count there's three of them and so on and so forth. Going forward we see that the first one is 040830, this is the marshal value for nil. So the 0408 is actually a prefix that's appended to every marshal value that's the version number of marshal. And then the 30 is actually the marshal value of nil. And then going forward we can see it's just a bunch of straight up marshal values. There's nothing special going on here. First is nil, second is literally the string get current time. Third is zero meaning the number of arguments being passed over the wire which is zero and because there's zero arguments the fourth one is the block which because there's no block there's nil. So and then returned there's true get time instance which is the current time that we called and so on and so forth that's the time instance that we just got back. So going forward there is, you can see that so basically every call that you make on this object is essentially tunneled into this object. So there's nothing special going on. There's nothing stopping you from calling any method you want to. Everything is just sort of tunneled into sent and you can see here that like we pass a bunch of stuff in all of this stuff is decoded off of the wire. This is code in DRB at this point and all of this code is just passed in the send and there's nothing stopping us from just passing whatever we want essentially and as a attacker this is something that like there's really nothing in your way from really doing anything you feel like. There's no protections in place. There's no defenses or anything like that. Quick question for the audiences. Is my slide updating on the screen share? Are we still stuck at remote methodification? You're at send right now. Okay, I can, yeah. So at a high level are we on a load and load and load right now? Yes. Okay, cool. So essentially the code that the works calls this load function over and over again to receive the request and all that is doing is it's reading those four bytes of size and then it uses them to read in the amount to read in for the martial value and then it calls martial load on them and to pull out the ARG V array it just for every number that the ARG C turns out to be it just reads another value over and over again and then for the block it just tries out one more time. So the interesting thing here as we can see is from that code prior you can essentially just call anything on any object effectively once it gets translated from the ID and any message, any method name you want and any arguments you want. So that's pretty dangerous. So basically in addition to being able to send basically anything you want because of how martial works the only real restriction of martial is that the other side has the class loaded so that it can know how to do the unmartialing but other than that you can send any sort of object. So martial has a lot of gadget exploits associated with it that can be particularly troublesome and there are basically no restrictions on the method you can call there and basically you can do this from regular old Ruby code which the documentation recommends a couple of things to lock this down that as we will see do not actually work. So one of the big things with the way that the Ruby code is based in the documentation for how the quote unquote exploit would work is that it does an undeath on the class or the object instance for the given method that you wanna call, the call falls through to be sent over. But as we mentioned, you could just call method missing directly on whatever you want essentially. So taking this a step further if you want to use any sort of unmartial exploit or unmartial gadget here on the left we see a gadget class which is just an example of let's say you had a gadget this is a very trivial gadget that would be implemented in the server. The client would be able to essentially return anything in that exploits the gadget and boom the server runs this code. And there's really nothing stopping the client from returning anything like this that gets unmartialed into whatever the client specifies. And this is essentially the crux of the problem. So we can see in the DRB library you have basically an ID to ref that is the integer that you pass back that basically is any integer you pass back gets turned into whatever that object is on the server side. And if you pass back nil it turns into the front object but if you pass back an integer then that turns into literally any object that's there. So here we can see that there's no checks to make sure that the object you're calling a method on was actually lent out to a remote process. Like this can be any object ever created in the server process. And also because there are many objects that have static object IDs like nil that exist in every Ruby process nil has an object ID of eight. So you can specify eight and because you can even call instance eval on nil which maybe shouldn't be a thing who knows but it allows you to get a code execution through this. So you can specify eight in order to call something on nil and you can specify instance eval as the method and then boom. So here we have this but you see that in this very, very simple example that we're demonstrating dRuby we also have the client starting a dRuby service which may raise some red flags in anyone watching. This is something that is basically like unnecessary through the protocol because if the client is sort of sending any object that's local to the server it needs to represent that local object through a dRuby undumped reference and it needs to allow the server to call methods on that object in case it needs to and through that it needs to start its own dRuby server. So we have this sort of problem where the client is also sort of necessarily vulnerable to anything that we're talking about here. Here we see this fake dRuby server where it just opens up a TCP socket accepts anything and then assuming the, in the simple example assuming the client's running on Mac OS which doesn't randomize port numbers you can just subtract one from the port number. If the client's not running on Mac OS you can scan the host for wherever this dRuby socket is and then get code execution on the client that's actually calling something on you as this pretend dRuby server which is a pretty big problem. So in order to scan for this process like assuming they're not a trivially finding a dRuby server because these port numbers aren't randomized you can pretty easily scan for this. The only problem is that there's no banner like most processes will have what you got to do instead is you just write random stuff to the socket. And when you write random stuff to the socket boom you get back stack traces. So it's pretty easy to figure out you get back stack traces. Just to be sure you can scan the stack traces that you get back for dRuby, dRuby con error and that'll make sure that like, hey you actually are dealing with a dRuby type of thing but it's not the most difficult thing to handle. So to kind of sum it up this is just code execution by the protocol that's the feet how it works. There are no application level vulnerabilities even needed to exploit these things which is kind of already known but what was not known was that you can do it to exploit the client. The particular foot guns here the fact that you can send arbitrarily things like instance eval the actual send through various other means and then the Marshall deserialization and the fact that you can pick an arbitrary object ID that is not the one that the server might try to harden. So basically it's a real code execution as a service working entirely as intended. So what do we do now? Well, one recommendation is not to use dRuby and instead use something like gRPC. Thank you for coming to our TED Talk. Alternatively, you know, there are people who can't get off of it. So there are ways to deal with it. So one thing early on to not do is not rely on safe levels because one they don't exist anymore but this was something that used to be thrown around as a recommendation for dRuby servers. The problem is that none of the safe stuff really works. It breaks all the code. There are still generally speaking ways around it but it doesn't matter anymore because it was removed in Ruby 2.7. But the biggest annoyance with it was that it treated the disk as untrusted meaning that when you loaded like gems all the libraries you might care about to run your application, they just would not work which was why no one used it and why it got pulled. So instead what you're gonna do is you're going to say patch dRuby. Not kidding here. So there are a bunch of things that we want to kind of prevent certain kinds of abuses of. So you can filter method IDs. There are obviously ones that you might want to specifically allow in which case you can restrict the ID filtering to those specific things but alternatively there are also an absolute ton of dangerous methods and things that you probably do not want to allow. Next you're going to wanna deal with marshaling by constraining what can be sent over to say safer types. Then lastly you gotta do something to track all of those object IDs so that people can't start calling things on nil even though you never actually sent nil out because why would you? It's a serializable value. So we wrote a thing called drab. The dRuby, dRuby for the boring. It's a gem, basically a forked version of the dRuby code base from Ruby itself. It's a drop-in replacement but it speaks a different wire protocol. Most of its features are about not having features. So we remove weird things like the block handling. We remove, so we add in method ID filtering to say add in things that you would want to allow to happen specifically. Alternatively we block a whole bunch of super dangerous methods. I'll be enough, dRuby actually does have its own blacklist specifically for the double underscore send, the true send. But given that the way that it works is essentially by calling that true send that kind of doesn't mean anything. You just can't send a send. So that doesn't actually even block the regular send. So yeah, it's not great. So moving on, we restrict marshaling by converting everything over to JSON and overriding how the loading implementation works. And then we validate sort of these structures of bytes to be unmartialed against certain AST patterns that are the core structure of how marshaling works so that we can effectively validate certain types are being passed over and not other types. So things like arrays and strings and very basic objects and numbers and true false booleans and nil but not really anything more dangerous than that. And then additionally we easily track all the ID values back and forth that's pretty simple actually. So the thing that we kind of forgot to mention was that there's another big user category of dRuby users, exploits. So we should add Metasploit to this list of who uses dRuby. So going through the port 8787 is the default port that's recommended the documentation for dRuby and looking at it, you can see that it's pretty open across the internet except that most of these are all HTTP so it's not the biggest deal. And if you look at it, Nmap used to recognize this type of thing but doesn't do anything that we're sort of like we wish it would do in recognizing dRuby so it's a bit unfortunate but nothing really recognizes dRuby on the internet anymore. So basically we're stuck with this writing random nonsense to the socket and scanning the response back in finding out if something is actually running dRuby which maybe is better than it just announcing itself on a port but basically if we can do this if we can scan the internet we can determine better than Shodan or better than Nmap at this point where there is dRuby on the network. Yeah, the interesting thing about all of this is that the internet effectively, collectively lost its ability to scan for dRuby which sort of coincided with us finding ways to write evil dRuby servers that exploit clients so the internet kind of got lucky alas so we're here to change both of those things. So to kind of quickly review the sets the existing exploits. The one from the official Ruby docs does the undeath of InstancyVal and then calls that and this is actually kind of ridiculous payload in general. This comment of unsafe code is actually from the docs which is interesting because the unsafe is so true in so many ways because if you have an auto exploiting gadget that could pop this when it gets the return value from the call to InstancyVal one but also what I think this code was trying to say is that sending an rmrf star command over to the remote server is unsafe and dangerous. And so I guess they tried to mitigate that by picking a non-default port this 89.89 which isn't even the standard dRuby port so you'd be hard pressed to accidentally hit a dRuby server with this and nuke the box. Interesting choice of default payload. I would have gone with like I don't know ID or something or U name. Metasploits on the other hand does functionally the same technique by doing the undeath all the way at the bottom except they do it for send so that they can call send directly with a bunch of different things. So they will send for InstancyVal instead of doing InstancyVal directly over dRuby. They'll over dRuby call send with an argument of InstancyVal which functionally is effectively the same thing. But in slightly newer versions of dRuby or of Metasploit they took out that start service line. We'll get to that in a little bit. So really all these existing exploits are kind of super basic and they all end up going for InstancyVal and none of them really do anything fancy. The fanciest that Metasploit gets is that if I guess someone were to overload InstancyVal on that front object they would default back to calling syscall via send. But they don't do anything interesting like call InstancyVal on nil which wouldn't really be blocked by anyone using this because you basically skip the front object and go for in for the kiloneum the nil object on the remote side. So and also none of these ever attempt to act as evil servers to exploit clients which is kind of sad. There's so much room for improvement. So given all these things I think it's safe to say that we've kind of shown that dRuby is unsafe to use in general. So obviously we can just clearly or we could connect to Metasploit's dRuby server to get code execution. But there's so much more fun to be had with the deserialization gadgets especially because Metasploit uses rails an active record and they're always available and loaded and they have a completely stable reliable gadget that basically can't be fixed at all. So we had this payload that we wrote and sent as our POC to Metasploit and you don't necessarily really need to know all that's going on here. It's basically the quintessential rails gadget which was often used for abuse of serialized signed cookie values. But the interesting thing is that up at the top we detect if instance eval is the symbol being sent from our overridden send and because that's all Metasploit ever calls on us and if it is we raise exceptions they fall back to their next implementation of a payload and then we send back the gadget. The gadget is a limitation that it requires something to be called on it and luckily that second implementation does a bunch of low level syscall IO and it expects that certain things that come back are integers for things to be reading or file descriptors to be reading from. So when it sends that back into DRB it ends up causing the gadget to fire which basically then runs the arbitrary code. So we reached out to Metasploit about all their DRB problems and then they made that fix where they just commented out the server. So we told them specifically that that was a problem but our payload was for the gadget like you would try to target our server as a victim with Metasploit as a client but our payload continued to work. They clearly never ran it against the updated version. We then several weeks later we reached out to them again and they removed the code entirely which was our original recommendation because it wasn't salvageable. So we only have one CVE when arguably there were several versions vulnerable to multiple things so personally I think we should have two CVEs for this but whatever. Then the interesting thing here is that obviously our exploit was actually still vulnerable to all the other attacks we've talked about. So if you really wanna do this properly you have to reimplement the entire wire protocol much more shall we say safely. So the safely is a lot more work involved but basically we rewrote the DRB protocol and library entirely into this DRB RV gem that has now been published which basically gives you as a library user a lot more power and control over what's exactly being sent over the protocol and what's exactly being received from the protocol and then additionally we wrote partial which is this partial implementation of Ruby's Marshall which basically only implements like the smallest amount of Marshall necessary to do talk the Ruby and doesn't instantiate these crazy objects like active record and stuff like that and these are both suitable for messing around with the protocol if you are so inclined to do so. So here we have an example of a DRB RV being used so you open up a TCP socket you tell it using DRB RV like exactly what to write. So here we're taking the object ID of nil where we're giving it the string instance eval we're telling it the exact arguments to put nil because there's no block it's just puts this is a very dangerous payload assuming this is actually a very dangerous payload and that's being passed the instance eval on the server side. And having an eval client is simple everyone's been doing that for years the more interesting thing is having the eval server and so we have an API for easily spinning up a server based off of Ruby blocks which is kind of the quintessential rubious way to do this and effectively it gives you a view of everything and in our payloads it's kind of simpler to fire off gadgets into exceptions as it turns out because often the DRB handling of exceptions will call things on them often triggering them to fire and run their code if they weren't auto run on deserialization first place and the output or I guess the inputs to these handlers are slightly interesting because we want to be able to handle arbitrary data coming in even if we can't deserialize it with our safety serializer which would mean it's probably dangerous to deserialize so essentially for every argument or every piece of data that can come in as an input we have both the deserialized value or nil and the raw encoded form that we read off the wire so you can detect if nil is actually nil or if nil was a failure to decode but everything continues on and you can process as you need to. So overall we find that DRuby is just bad at security all over and as it turns out it's actually much less secure than the documentation would lead you to believe and even the exploit example they gave you in that documentation is itself exploitable so yeah, don't use the real DRuby even in your exploits but there is some hope for people who actually are stuck using DRuby wrote this drop-in wire incompatible replacement the library but every user of it that needs to talk to each other needs to be using drab and then separately we have a low-level wire compatible protocol handler library for it or you just fall back to using something safe and simple like GRPC, modern but at least we have ways to safely write DRuby exploits now which we did not previously DRB, RB and partial are currently published and up on RubyGems we will be publishing our main sort of DRuby exploit CLI script soon and we are currently in the process of finishing up a meta-split module to replace the original implementation that got yanked an interesting thing to note about that is that the author of the original vulnerable DRB exploit that we exploited was actually the same person who wrote the universal, sorry, the Rails deserialization gadget that we used to exploit it so we exploited his own code with his own code that was fun actually when we realized it was the same person and all of our code is going to be up on our repo nccgroup slash drbrb on GitHub so to complete the talk and we're clearly out of time we would like to send out some minor greets to Masatoshi Sekhi who is the creator and author of DRuby and uses it for a bunch of interesting parallel and distributed computing things that seem to be getting picked up by the Japanese astrophysics community which is interesting we might have exploits on very interesting pieces of software as it turns out he is similarly a Pokemon and Magikarp fan like myself so we have ideally some camaraderie but also Sumimasen, we've kind of destroyed DRuby entirely but we are trying to keep the pieces from falling apart by having safe replacements so that people don't just stop using it because it is actually very fun and easy and simple to build up very interesting distributed applications with it just needs more security and that is our talk.