|
6 | 6 | """ |
7 | 7 |
|
8 | 8 | from BaseHTTPServer import BaseHTTPRequestHandler |
| 9 | +from httplib import HTTPResponse |
9 | 10 | from StringIO import StringIO |
| 11 | +import base64 |
| 12 | +import re |
10 | 13 |
|
11 | 14 | from lib.core.data import logger |
12 | 15 | from lib.core.settings import VERSION |
@@ -122,18 +125,68 @@ def toDict(self): |
122 | 125 |
|
123 | 126 | class Response: |
124 | 127 |
|
125 | | - def __init__(self): |
126 | | - pass |
| 128 | + extract_status = re.compile(r'\((\d{3}) (.*)\)') |
| 129 | + |
| 130 | + def __init__(self, httpVersion, status, statusText, headers, content, raw=None, comment=None): |
| 131 | + self.raw = raw |
| 132 | + self.httpVersion = httpVersion |
| 133 | + self.status = status |
| 134 | + self.statusText = statusText |
| 135 | + self.headers = headers |
| 136 | + self.content = content |
| 137 | + self.comment = comment |
127 | 138 |
|
128 | 139 | @classmethod |
129 | 140 | def parse(cls, raw): |
130 | | - return cls() |
| 141 | + altered = raw |
| 142 | + comment = None |
| 143 | + |
| 144 | + if altered.startswith("HTTP response ["): |
| 145 | + io = StringIO(raw) |
| 146 | + first_line = io.readline() |
| 147 | + parts = cls.extract_status.search(first_line) |
| 148 | + status_line = "HTTP/1.0 %s %s" % (parts.group(1), parts.group(2)) |
| 149 | + remain = io.read() |
| 150 | + altered = status_line + "\n" + remain |
| 151 | + comment = first_line |
| 152 | + |
| 153 | + response = HTTPResponse(FakeSocket(altered)) |
| 154 | + response.begin() |
| 155 | + return cls(httpVersion="HTTP/1.1" if response.version == 11 else "HTTP/1.0", |
| 156 | + status=response.status, |
| 157 | + statusText=response.reason, |
| 158 | + headers=response.msg, |
| 159 | + content=response.read(-1), |
| 160 | + comment=comment, |
| 161 | + raw=raw) |
131 | 162 |
|
132 | 163 | def toDict(self): |
133 | 164 | return { |
| 165 | + "httpVersion": self.httpVersion, |
| 166 | + "status": self.status, |
| 167 | + "statusText": self.statusText, |
| 168 | + "headers": [dict(name=key, value=value) for key, value in self.headers.items()], |
| 169 | + "content": { |
| 170 | + "mimeType": self.headers.get('Content-Type'), |
| 171 | + "encoding": "base64", |
| 172 | + "text": base64.b64encode(self.content), |
| 173 | + }, |
| 174 | + "comment": self.comment, |
| 175 | + "_raw": self.raw, |
134 | 176 | } |
135 | 177 |
|
136 | 178 |
|
| 179 | +class FakeSocket: |
| 180 | + # Original source: |
| 181 | + # https://stackoverflow.com/questions/24728088/python-parse-http-response-string |
| 182 | + |
| 183 | + def __init__(self, response_text): |
| 184 | + self._file = StringIO(response_text) |
| 185 | + |
| 186 | + def makefile(self, *args, **kwargs): |
| 187 | + return self._file |
| 188 | + |
| 189 | + |
137 | 190 | class HTTPRequest(BaseHTTPRequestHandler): |
138 | 191 | # Original source: |
139 | 192 | # https://stackoverflow.com/questions/4685217/parse-raw-http-headers |
@@ -171,7 +224,7 @@ def test_basic_request(self): |
171 | 224 | self.assertEqual("HTTP/1.0", req.httpVersion) |
172 | 225 |
|
173 | 226 | def test_with_request_as_logged_by_sqlmap(self): |
174 | | - raw = "HTTP request [#75]:\nPOST /create.php HTTP/1.1\nHost: 240.0.0.2\nAccept-encoding: gzip,deflate\nCache-control: no-cache\nContent-type: application/x-www-form-urlencoded; charset=utf-8\nAccept: */*\nUser-agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10\nCookie: PHPSESSID=65c4a9cfbbe91f2d975d50ce5e8d1026\nContent-length: 138\nConnection: close\n\nname=test%27%29%3BSELECT%20LIKE%28%27ABCDEFG%27%2CUPPER%28HEX%28RANDOMBLOB%28000000000%2F2%29%29%29%29--&csrfmiddlewaretoken=594d26cfa3fad\n" # noqa |
| 227 | + raw = "HTTP request [#75]:\nPOST /create.php HTTP/1.1\nHost: 127.0.0.1\nAccept-encoding: gzip,deflate\nCache-control: no-cache\nContent-type: application/x-www-form-urlencoded; charset=utf-8\nAccept: */*\nUser-agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.215 Safari/534.10\nCookie: PHPSESSID=65c4a9cfbbe91f2d975d50ce5e8d1026\nContent-length: 138\nConnection: close\n\nname=test%27%29%3BSELECT%20LIKE%28%27ABCDEFG%27%2CUPPER%28HEX%28RANDOMBLOB%280.0.10000%2F2%29%29%29%29--&csrfmiddlewaretoken=594d26cfa3fad\n" # noqa |
175 | 228 | req = Request.parse(raw) |
176 | 229 | self.assertEqual("POST", req.method) |
177 | 230 | self.assertEqual("138", req.headers["Content-Length"]) |
@@ -204,4 +257,36 @@ def test_render_with_post_body(self): |
204 | 257 | "text": "name=test&csrfmiddlewaretoken=594d26cfa3fad\n", |
205 | 258 | }) |
206 | 259 |
|
| 260 | + class ResponseParseTest(unittest.TestCase): |
| 261 | + def test_parse_standard_http_response(self): |
| 262 | + raw = "HTTP/1.1 404 Not Found\nContent-length: 518\nX-powered-by: PHP/5.6.30\nContent-encoding: gzip\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\nVary: Accept-Encoding\nUri: http://127.0.0.1/\nServer: Apache/2.4.10 (Debian)\nConnection: close\nPragma: no-cache\nCache-control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\nDate: Fri, 23 Jun 2017 16:18:17 GMT\nContent-type: text/html; charset=UTF-8\n\n<!doctype html>\n<html>Test</html>\n" # noqa |
| 263 | + resp = Response.parse(raw) |
| 264 | + self.assertEqual(resp.status, 404) |
| 265 | + self.assertEqual(resp.statusText, "Not Found") |
| 266 | + |
| 267 | + def test_parse_response_as_logged_by_sqlmap(self): |
| 268 | + raw = "HTTP response [#74] (200 OK):\nContent-length: 518\nX-powered-by: PHP/5.6.30\nContent-encoding: gzip\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\nVary: Accept-Encoding\nUri: http://127.0.0.1/\nServer: Apache/2.4.10 (Debian)\nConnection: close\nPragma: no-cache\nCache-control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\nDate: Fri, 23 Jun 2017 16:18:17 GMT\nContent-type: text/html; charset=UTF-8\n\n<!doctype html>\n<html>Test</html>\n" # noqa |
| 269 | + resp = Response.parse(raw) |
| 270 | + self.assertEqual(resp.status, 200) |
| 271 | + self.assertEqual(resp.statusText, "OK") |
| 272 | + self.assertEqual(resp.headers["Content-Length"], "518") |
| 273 | + self.assertIn("Test", resp.content) |
| 274 | + self.assertEqual("HTTP response [#74] (200 OK):\n", resp.comment) |
| 275 | + |
| 276 | + class ResponseRenderTest(unittest.TestCase): |
| 277 | + def test_simple_page_encoding(self): |
| 278 | + resp = Response(status=200, statusText="OK", |
| 279 | + httpVersion="HTTP/1.1", |
| 280 | + headers={"Content-Type": "text/html"}, |
| 281 | + content="<html><body>Hello</body></html>\n") |
| 282 | + out = resp.toDict() |
| 283 | + self.assertEqual(200, out["status"]) |
| 284 | + self.assertEqual("OK", out["statusText"]) |
| 285 | + self.assertIn({"name": "Content-Type", "value": "text/html"}, out["headers"]) |
| 286 | + self.assertEqual(out["content"], { |
| 287 | + "mimeType": "text/html", |
| 288 | + "encoding": "base64", |
| 289 | + "text": "PGh0bWw+PGJvZHk+SGVsbG88L2JvZHk+PC9odG1sPgo=", |
| 290 | + }) |
| 291 | + |
207 | 292 | unittest.main(buffer=False) |
0 commit comments