// Ruby for Pentesters · Part 7
Becoming root
The cracked password is root's too — plain password reuse. We spend it to escalate, and learn the handful of ways Ruby runs external programs — system, backticks, Open3 — ending with a PTY to drive an interactive su.
Part 6 handed us monkeybizness — the OFBiz admin’s password. Here’s the payoff:
on this box that’s also root’s login password. Password reuse is the most
common privilege escalation there is, and it’s why cracking one hash so often
ends the whole engagement. All that’s left is to su. We’ll do it from Ruby,
which means learning how Ruby runs other programs — a toolkit you’ll use in
every script you write.
On a box with no Ruby you’d just type
suand the password by hand. The point here is the Ruby toolkit for launching programs; the escalation is the demo.
Three ways to run a command
Ruby gives you a ladder of options, from blunt to precise:
system('id') # run it; output goes to the screen; returns true/false
output = `id` # backticks: capture stdout as a string
output = %x[id] # exactly the same as backticks, different delimiters
require 'open3'
stdout, stderr, status = Open3.capture3('id')
puts stdout if status.success?
systemruns the command, lets its output flow to your terminal, and returnstrue/falsefor success — good when you only care whether it worked.- Backticks (and
%x[], the same thing) run the command and hand back its stdout as a string — good when you want the output. Open3.capture3is the precise one: it returns stdout, stderr, and the exit status separately, so you can tell real output from error noise. Reach for it whenever failure matters.
That’s the whole spectrum. But none of them can escalate us — and it’s worth understanding why.
Why su is different
You’d think echo monkeybizness | su would work. It doesn’t. su reads its
password straight from the terminal — the TTY — not from standard input, on
purpose, so passwords can’t be piped in from a script. Backticks and Open3
hand the child a pipe, not a terminal, so su refuses them.
The fix is to give it a terminal — a fake one we control.
A pseudo-terminal with PTY
Ruby’s pty library allocates a pseudo-terminal, and its expect companion
lets us wait for prompts and answer them — driving su exactly like a human
would:
require 'pty'
require 'expect'
PTY.spawn('su - root') do |reader, writer, _pid|
writer.sync = true
reader.expect(/Password:/, 5)
writer.puts 'monkeybizness'
reader.expect(/[#$] /, 5)
writer.puts 'id'
puts reader.expect(/uid=\d+.*/, 5)&.first
writer.puts 'exit'
rescue Errno::EIO
# the child shell closed — normal once we exit
end
PTY.spawn('su - root')startssuunder a pseudo-terminal and yields areader/writerpair (plus the childpid) — the two ends of that terminal.reader.expect(/Password:/, 5)blocks untilsuprints its prompt, giving up after 5 seconds (returningnilon timeout).writer.puts 'monkeybizness'types the password; we wait for the root shell’s#prompt, runid, and read back the answer.- When the shell exits, the pseudo-terminal raises
Errno::EIO— the normal way a PTY signals end-of-child, so we rescue it.
Run it:
$ ruby root.rb
uid=0(root) gid=0(root) groups=0(root)
uid=0(root) — root, reached entirely from Ruby, on the strength of one reused
password. That’s the box.
Takeaways
- Ruby runs external programs three ways:
system(status, output to screen), backticks/%x[](capture stdout), andOpen3.capture3(stdout, stderr, and status separately — the precise choice). - Some programs,
suamong them, demand a real terminal and ignore piped input — a pipe isn’t a TTY. PTY.spawnallocates a pseudo-terminal andexpectdrives it: wait for a prompt, write a reply. It raisesErrno::EIOwhen the child exits.- Password reuse is a first-class privesc: one cracked credential is worth trying against every account and service on the box.
Exercises
- Rewrite the
idcheck withOpen3.capture3and printstderronly when the exitstatusisn’t successful. - Wrap the escalation in a
root?predicate that returns whether the PTY session reacheduid=0, catching a wrong-password timeout asfalse. - Generalize the PTY block into
as_root(cmd)that escalates, runs one command as root, and returns its output — your own tinysu-runner.
Next — Part 8: eight scripts, one shape. We fold recon, bypass, RCE, and loot into a reusable class with a real command-line interface.