mirror of
https://github.com/sqlmapproject/sqlmap.git
synced 2026-07-02 22:42:30 +00:00
Adding custom/own support for HTTP2
This commit is contained in:
parent
8a75c0bb62
commit
39ba1bc00e
5 changed files with 560 additions and 47 deletions
|
|
@ -189,7 +189,7 @@ f8de57606325456928e46ae2896f5f8bbec9ad18b1c644b492a566fa992216f6 lib/core/decor
|
|||
9bf174058f15d14e24e94f9aaf42df045119d3617c6c54bd2f3af79b462f331d lib/core/replication.py
|
||||
0b8c38a01bb01f843d94a6c5f2075ee47520d0c4aa799cecea9c3e2c5a4a23a6 lib/core/revision.py
|
||||
888daba83fd4a34e9503fe21f01fef4cc730e5cde871b1d40e15d4cbc847d56c lib/core/session.py
|
||||
2f4c7044d36e183fcb0a019d82ccbc7222abab1878454c479df9e89d23430733 lib/core/settings.py
|
||||
35c24cf138fdd68add3c8f6274d6ff735b5209c84eec635ba316f986b67325ef lib/core/settings.py
|
||||
c7804223319e18eb0b8e2cbf0a8b6896d1cefb7b0b1a2e9f1cf826a8a3b56750 lib/core/shell.py
|
||||
a2e98a94b231432736d6b304fc75525c8b5fdb4768c418387c5b4c1a610dad64 lib/core/subprocessng.py
|
||||
19f1e3c5e3ba703d28d510cd7a9ab8284d5fbe9df5ce7e77c86e5931571364b7 lib/core/target.py
|
||||
|
|
@ -212,9 +212,10 @@ c2f34e27578742e729c2fa9c1d4f0a0d8f8f7f4cf0fc14c62ec817a260c71dec lib/parse/site
|
|||
369484a2999d29f49bf839a329d1686ed94f6ea27c695e027fe08c8da51f30a3 lib/request/basic.py
|
||||
bc61bc944b81a7670884f82231033a6ac703324b34b071c9834886a92e249d0e lib/request/chunkedhandler.py
|
||||
9c0dccc1cee66d38478aaf75a7c513d0d136d50a90b15fed146faa1653899fe1 lib/request/comparison.py
|
||||
729e07a2ca6b1d83563e9c6dc5a884d1b664c1764be06776ea93bde305164f0c lib/request/connect.py
|
||||
c96deaa69743d2cf4ae48f2ae0036f7e11b838f97a0e8c7f1205c61e9dd36bc1 lib/request/connect.py
|
||||
8e06682280fce062eef6174351bfebcb6040e19976acff9dc7b3699779783498 lib/request/direct.py
|
||||
a6b37b436838caeb197fea858d0a39fadbff4736256e741b5fcec1f28fcf1ce0 lib/request/dns.py
|
||||
21e8e2d44788b124f741b76a483ce9528ca53ff6da6691808ee679fe91128050 lib/request/http2.py
|
||||
92c81cc31ff4a396723242058fb2152c9e9745f8412d01ea74480b048a53af6c lib/request/httpshandler.py
|
||||
1966ca704961fb987ab757f0a4afddbf841d1a880631b701487c75cef63d60c3 lib/request/__init__.py
|
||||
7a0ac2522213e756348fd871a7af74cc963bdc82f9d7ade57be5de42b5bf7cab lib/request/inject.py
|
||||
|
|
@ -256,7 +257,7 @@ c61816c9dba9f6cc2223aed1a923f95130979e5f0a88ec254ee667d955ed2734 lib/techniques
|
|||
aeefb42ea0c68f72744bc1bfd7194ec1bc06480d8a7e23f4b8d3d23fbba2b014 lib/utils/api.py
|
||||
442555ab85277aff7c9e0cf465ea5b0d28395c326f68363449b2d3941f4b6de2 lib/utils/brute.py
|
||||
da5bcbcda3f667582adf5db8c1b5d511b469ac61b55d387cec66de35720ed718 lib/utils/crawler.py
|
||||
a94958be0ec3e9d28d8171813a6a90655a9ad7e6aa33c661e8d8ebbfcf208dbb lib/utils/deps.py
|
||||
51deedec3d3e869b067824caa51406d2ef396c188f82013ca60777006a821e27 lib/utils/deps.py
|
||||
bd9267d94390ba87d6c5a35c90f2406d6a4135a7c8ea01db76dd9e6519eee2ed lib/utils/dialect.py
|
||||
51cfab194cd5b6b24d62706fb79db86c852b9e593f4c55c15b35f175e70c9d75 lib/utils/getch.py
|
||||
3c4ad819589fe4fca303706dc87969273a07a04dee85e23f064b39caf1fb80e9 lib/utils/gui.py
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from lib.core.enums import OS
|
|||
from thirdparty import six
|
||||
|
||||
# sqlmap version (<major>.<minor>.<month>.<monthly commit>)
|
||||
VERSION = "1.10.7.2"
|
||||
VERSION = "1.10.7.3"
|
||||
TYPE = "dev" if VERSION.count('.') > 2 and VERSION.split('.')[-1] != '0' else "stable"
|
||||
TYPE_COLORS = {"dev": 33, "stable": 90, "pip": 34}
|
||||
VERSION_STRING = "sqlmap/%s#%s" % ('.'.join(VERSION.split('.')[:-1]) if VERSION.count('.') > 2 and VERSION.split('.')[-1] == '0' else VERSION, TYPE)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ from lib.core.common import unsafeVariableNaming
|
|||
from lib.core.common import urldecode
|
||||
from lib.core.common import urlencode
|
||||
from lib.core.common import wasLastResponseDelayed
|
||||
from lib.core.compat import LooseVersion
|
||||
from lib.core.compat import patchHeaders
|
||||
from lib.core.compat import xrange
|
||||
from lib.core.convert import encodeBase64
|
||||
|
|
@ -111,7 +110,6 @@ from lib.core.settings import IS_WIN
|
|||
from lib.core.settings import JAVASCRIPT_HREF_REGEX
|
||||
from lib.core.settings import LARGE_READ_TRIM_MARKER
|
||||
from lib.core.settings import LIVE_COOKIES_TIMEOUT
|
||||
from lib.core.settings import MIN_HTTPX_VERSION
|
||||
from lib.core.settings import MAX_CONNECTION_READ_SIZE
|
||||
from lib.core.settings import MAX_CONNECTIONS_REGEX
|
||||
from lib.core.settings import MAX_CONNECTION_TOTAL_SIZE
|
||||
|
|
@ -632,30 +630,22 @@ class Connect(object):
|
|||
cookie.value = re.sub(r"(%s)([^ \t])" % char, r"\g<1>\t\g<2>", cookie.value)
|
||||
|
||||
if conf.http2:
|
||||
try:
|
||||
import httpx
|
||||
except ImportError:
|
||||
raise SqlmapMissingDependence("httpx[http2] not available (e.g. 'pip%s install httpx[http2]')" % ('3' if six.PY3 else ""))
|
||||
from lib.request.http2 import open_url as http2OpenUrl
|
||||
|
||||
if LooseVersion(httpx.__version__) < LooseVersion(MIN_HTTPX_VERSION):
|
||||
raise SqlmapMissingDependence("outdated version of httpx detected (%s<%s)" % (httpx.__version__, MIN_HTTPX_VERSION))
|
||||
h2proxy = None
|
||||
if conf.proxy:
|
||||
_proxyParts = _urllib.parse.urlsplit(conf.proxy if "://" in conf.proxy else "http://%s" % conf.proxy)
|
||||
if (_proxyParts.scheme or "").lower().startswith("socks"):
|
||||
raise SqlmapMissingDependence("native HTTP/2 client does not support SOCKS proxies (omit '--http2' or use an HTTP proxy)")
|
||||
h2proxy = (_proxyParts.hostname, _proxyParts.port or 8080, conf.proxyCred or None)
|
||||
|
||||
try:
|
||||
proxy_mounts = dict(("%s://" % key, httpx.HTTPTransport(proxy="%s%s" % ("http://" if "://" not in kb.proxies[key] else "", kb.proxies[key]))) for key in kb.proxies) if kb.proxies else None
|
||||
with httpx.Client(verify=False, http2=True, timeout=timeout, follow_redirects=True, cookies=conf.cj, mounts=proxy_mounts) as client:
|
||||
conn = client.request(method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), url, headers=headers, data=post)
|
||||
except (httpx.HTTPError, httpx.InvalidURL, httpx.CookieConflict, httpx.StreamError) as ex:
|
||||
conn = http2OpenUrl(url, method or (HTTPMETHOD.POST if post is not None else HTTPMETHOD.GET), headers, post, timeout, follow_redirects=kb.choices.redirect != REDIRECTION.NO, proxy=h2proxy)
|
||||
except IOError as ex:
|
||||
raise _http_client.HTTPException(getSafeExString(ex))
|
||||
else:
|
||||
if conn.status_code >= 400:
|
||||
raise _urllib.error.HTTPError(url, conn.status_code, conn.reason_phrase, conn.headers, io.BytesIO(conn.read()))
|
||||
|
||||
conn.code = conn.status_code
|
||||
conn.msg = conn.reason_phrase
|
||||
conn.info = lambda c=conn: c.headers
|
||||
|
||||
conn._read_buffer = conn.read()
|
||||
conn._read_offset = 0
|
||||
if conn.code >= 400:
|
||||
raise _urllib.error.HTTPError(url, conn.code, conn.msg, conn.info(), io.BytesIO(conn.read()))
|
||||
|
||||
requestMsg = re.sub(r" HTTP/[0-9.]+\r\n", " %s\r\n" % conn.http_version, requestMsg, count=1)
|
||||
|
||||
|
|
@ -663,18 +653,6 @@ class Connect(object):
|
|||
threadData.lastRequestMsg = requestMsg
|
||||
|
||||
logger.log(CUSTOM_LOGGING.TRAFFIC_OUT, requestMsg)
|
||||
|
||||
def _read(count=None):
|
||||
offset = conn._read_offset
|
||||
if count is None:
|
||||
result = conn._read_buffer[offset:]
|
||||
conn._read_offset = len(conn._read_buffer)
|
||||
else:
|
||||
result = conn._read_buffer[offset: offset + count]
|
||||
conn._read_offset += len(result)
|
||||
return result
|
||||
|
||||
conn.read = _read
|
||||
else:
|
||||
if not multipart:
|
||||
threadData.lastRequestMsg = requestMsg
|
||||
|
|
|
|||
544
lib/request/http2.py
Normal file
544
lib/request/http2.py
Normal file
|
|
@ -0,0 +1,544 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Copyright (c) 2006-2026 sqlmap developers (https://sqlmap.org)
|
||||
See the file 'LICENSE' for copying permission
|
||||
"""
|
||||
|
||||
# Native, dependency-free HTTP/2 client (RFC 7540) with HPACK (RFC 7541), replacing the optional
|
||||
# 'httpx[http2]' third-party stack. The HPACK static and Huffman tables below are the canonical
|
||||
# RFC 7541 tables; the codec is validated differentially against python-hyper/hpack and the client
|
||||
# end-to-end against real h2 servers. Pure standard library, Python 2.7 / 3.x.
|
||||
|
||||
import base64
|
||||
import socket
|
||||
import ssl
|
||||
import struct
|
||||
|
||||
try:
|
||||
from http.client import responses as _HTTP_RESPONSES
|
||||
except ImportError:
|
||||
from httplib import responses as _HTTP_RESPONSES
|
||||
|
||||
try:
|
||||
from urllib.parse import urljoin, urlsplit
|
||||
except ImportError:
|
||||
from urlparse import urljoin, urlsplit
|
||||
|
||||
from email.message import Message as _Message
|
||||
|
||||
REDIRECT_CODES = (301, 302, 303, 307, 308)
|
||||
|
||||
|
||||
HUFFMAN_CODES = [
|
||||
0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8, 0xffffea,
|
||||
0x3ffffffc, 0xfffffe9, 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed, 0xfffffee, 0xfffffef,
|
||||
0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4, 0xffffff5, 0xffffff6, 0xffffff7, 0xffffff8,
|
||||
0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8, 0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9,
|
||||
0x7fb, 0xfa, 0x16, 0x17, 0x18, 0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc,
|
||||
0x20, 0xffb, 0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0, 0x1ffc, 0x3ffc,
|
||||
0x22, 0x7ffd, 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75, 0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76,
|
||||
0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe, 0x7fc, 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6,
|
||||
0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4, 0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc,
|
||||
0x7fffdd, 0x7fffde, 0xffffeb, 0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2,
|
||||
0x7fffe3, 0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, 0x7fffe7, 0xffffef, 0x3fffda, 0x1fffdd,
|
||||
0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd, 0x3fffde, 0xfffff0, 0x1fffdf,
|
||||
0x3fffdf, 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0, 0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef,
|
||||
0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4, 0x7ffff0, 0x3fffe5, 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb,
|
||||
0x7fff1, 0x3fffe7, 0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf,
|
||||
0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7, 0x7ffffe2,
|
||||
0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, 0xffffffd, 0x7ffffe3, 0x7ffffe4, 0x7ffffe5, 0xfffec,
|
||||
0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8, 0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef,
|
||||
0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4, 0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8,
|
||||
0x7ffffe9, 0x7ffffea, 0x7ffffeb, 0xffffffe, 0x7ffffec, 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee,
|
||||
0x3fffffff
|
||||
]
|
||||
|
||||
|
||||
HUFFMAN_LENGTHS = [
|
||||
0xd, 0x17, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x18, 0x1e, 0x1c, 0x1c, 0x1e, 0x1c, 0x1c, 0x1c, 0x1c,
|
||||
0x1c, 0x1c, 0x1c, 0x1c, 0x1e, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x6, 0xa, 0xa, 0xc, 0xd,
|
||||
0x6, 0x8, 0xb, 0xa, 0xa, 0x8, 0xb, 0x8, 0x6, 0x6, 0x6, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x7,
|
||||
0x8, 0xf, 0x6, 0xc, 0xa, 0xd, 0x6, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
|
||||
0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x8, 0x7, 0x8, 0xd, 0x13, 0xd, 0xe, 0x6, 0xf, 0x5, 0x6, 0x5, 0x6, 0x5, 0x6,
|
||||
0x6, 0x6, 0x5, 0x7, 0x7, 0x6, 0x6, 0x6, 0x5, 0x6, 0x7, 0x6, 0x5, 0x5, 0x6, 0x7, 0x7, 0x7, 0x7, 0x7, 0xf, 0xb,
|
||||
0xe, 0xd, 0x1c, 0x14, 0x16, 0x14, 0x14, 0x16, 0x16, 0x16, 0x17, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18,
|
||||
0x17, 0x18, 0x18, 0x16, 0x17, 0x18, 0x17, 0x17, 0x17, 0x17, 0x15, 0x16, 0x17, 0x16, 0x17, 0x17, 0x18, 0x16,
|
||||
0x15, 0x14, 0x16, 0x16, 0x17, 0x17, 0x15, 0x17, 0x16, 0x16, 0x18, 0x15, 0x16, 0x17, 0x17, 0x15, 0x15, 0x16,
|
||||
0x15, 0x17, 0x16, 0x17, 0x17, 0x14, 0x16, 0x16, 0x16, 0x17, 0x16, 0x16, 0x17, 0x1a, 0x1a, 0x14, 0x13, 0x16,
|
||||
0x17, 0x16, 0x19, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1a, 0x18, 0x19, 0x13, 0x15, 0x1a, 0x1b, 0x1b, 0x1a, 0x1b,
|
||||
0x18, 0x15, 0x15, 0x1a, 0x1a, 0x1c, 0x1b, 0x1b, 0x1b, 0x14, 0x18, 0x14, 0x15, 0x16, 0x15, 0x15, 0x17, 0x16,
|
||||
0x16, 0x19, 0x19, 0x18, 0x18, 0x1a, 0x17, 0x1a, 0x1b, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1c, 0x1b,
|
||||
0x1b, 0x1b, 0x1b, 0x1b, 0x1a, 0x1e
|
||||
]
|
||||
|
||||
|
||||
STATIC_TABLE = (
|
||||
(b':authority', b''),
|
||||
(b':method', b'GET'),
|
||||
(b':method', b'POST'),
|
||||
(b':path', b'/'),
|
||||
(b':path', b'/index.html'),
|
||||
(b':scheme', b'http'),
|
||||
(b':scheme', b'https'),
|
||||
(b':status', b'200'),
|
||||
(b':status', b'204'),
|
||||
(b':status', b'206'),
|
||||
(b':status', b'304'),
|
||||
(b':status', b'400'),
|
||||
(b':status', b'404'),
|
||||
(b':status', b'500'),
|
||||
(b'accept-charset', b''),
|
||||
(b'accept-encoding', b'gzip, deflate'),
|
||||
(b'accept-language', b''),
|
||||
(b'accept-ranges', b''),
|
||||
(b'accept', b''),
|
||||
(b'access-control-allow-origin', b''),
|
||||
(b'age', b''),
|
||||
(b'allow', b''),
|
||||
(b'authorization', b''),
|
||||
(b'cache-control', b''),
|
||||
(b'content-disposition', b''),
|
||||
(b'content-encoding', b''),
|
||||
(b'content-language', b''),
|
||||
(b'content-length', b''),
|
||||
(b'content-location', b''),
|
||||
(b'content-range', b''),
|
||||
(b'content-type', b''),
|
||||
(b'cookie', b''),
|
||||
(b'date', b''),
|
||||
(b'etag', b''),
|
||||
(b'expect', b''),
|
||||
(b'expires', b''),
|
||||
(b'from', b''),
|
||||
(b'host', b''),
|
||||
(b'if-match', b''),
|
||||
(b'if-modified-since', b''),
|
||||
(b'if-none-match', b''),
|
||||
(b'if-range', b''),
|
||||
(b'if-unmodified-since', b''),
|
||||
(b'last-modified', b''),
|
||||
(b'link', b''),
|
||||
(b'location', b''),
|
||||
(b'max-forwards', b''),
|
||||
(b'proxy-authenticate', b''),
|
||||
(b'proxy-authorization', b''),
|
||||
(b'range', b''),
|
||||
(b'referer', b''),
|
||||
(b'refresh', b''),
|
||||
(b'retry-after', b''),
|
||||
(b'server', b''),
|
||||
(b'set-cookie', b''),
|
||||
(b'strict-transport-security', b''),
|
||||
(b'transfer-encoding', b''),
|
||||
(b'user-agent', b''),
|
||||
(b'vary', b''),
|
||||
(b'via', b''),
|
||||
(b'www-authenticate', b''),
|
||||
)
|
||||
STATIC_LEN = len(STATIC_TABLE)
|
||||
|
||||
|
||||
# HTTP/2 frame codec (RFC 7540 section 4.1) - the zero-table-risk brick. Pure stdlib, py2/py3, ASCII.
|
||||
|
||||
# frame types (RFC 7540 s6)
|
||||
DATA, HEADERS, RST_STREAM, SETTINGS, PING, GOAWAY, WINDOW_UPDATE, CONTINUATION = 0x0, 0x1, 0x3, 0x4, 0x6, 0x7, 0x8, 0x9
|
||||
# flags
|
||||
FLAG_END_STREAM = 0x1
|
||||
FLAG_ACK = 0x1
|
||||
FLAG_END_HEADERS = 0x4
|
||||
FLAG_PADDED = 0x8
|
||||
FLAG_PRIORITY = 0x20
|
||||
|
||||
CONNECTION_PREFACE = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
|
||||
|
||||
def encode_frame(ftype, flags, stream_id, payload=b""):
|
||||
if len(payload) > 0xffffff:
|
||||
raise ValueError("frame payload exceeds 24-bit length")
|
||||
header = struct.pack("!I", len(payload))[1:] # 24-bit length (drop MSB of the 32-bit pack)
|
||||
header += struct.pack("!BBI", ftype, flags, stream_id & 0x7fffffff) # type, flags, R(1)+stream(31)
|
||||
return header + payload
|
||||
|
||||
def decode_frame_header(nine):
|
||||
if len(nine) != 9:
|
||||
raise ValueError("frame header must be exactly 9 bytes")
|
||||
length = struct.unpack("!I", b"\x00" + nine[:3])[0]
|
||||
ftype, flags, stream_id = struct.unpack("!BBI", nine[3:9])
|
||||
return length, ftype, flags, stream_id & 0x7fffffff
|
||||
|
||||
# ---------- Huffman ----------
|
||||
def huffman_encode(data):
|
||||
if not data:
|
||||
return b""
|
||||
acc = 0
|
||||
nbits = 0
|
||||
for b in bytearray(data):
|
||||
acc = (acc << HUFFMAN_LENGTHS[b]) | HUFFMAN_CODES[b]
|
||||
nbits += HUFFMAN_LENGTHS[b]
|
||||
pad = (8 - nbits % 8) % 8
|
||||
acc = (acc << pad) | ((1 << pad) - 1) # pad with 1-bits (EOS prefix)
|
||||
total = (nbits + pad) // 8
|
||||
out = bytearray()
|
||||
for i in range(total - 1, -1, -1):
|
||||
out.append((acc >> (8 * i)) & 0xff)
|
||||
return bytes(out)
|
||||
|
||||
_HUFF_ROOT = {}
|
||||
def _build_huffman_trie():
|
||||
for sym in range(256):
|
||||
code, length = HUFFMAN_CODES[sym], HUFFMAN_LENGTHS[sym]
|
||||
node = _HUFF_ROOT
|
||||
for i in range(length - 1, -1, -1):
|
||||
bit = (code >> i) & 1
|
||||
if i == 0:
|
||||
node[bit] = sym # leaf: int symbol
|
||||
else:
|
||||
node = node.setdefault(bit, {})
|
||||
_build_huffman_trie()
|
||||
|
||||
def huffman_decode(data):
|
||||
out = bytearray()
|
||||
node = _HUFF_ROOT
|
||||
consumed = 0 # bits into the current (partial) symbol
|
||||
for byte in bytearray(data):
|
||||
for i in range(7, -1, -1):
|
||||
bit = (byte >> i) & 1
|
||||
nxt = node.get(bit)
|
||||
if nxt is None:
|
||||
raise ValueError("invalid Huffman sequence")
|
||||
consumed += 1
|
||||
if isinstance(nxt, dict):
|
||||
node = nxt
|
||||
else:
|
||||
out.append(nxt)
|
||||
node = _HUFF_ROOT
|
||||
consumed = 0
|
||||
# RFC 7541 5.2: any leftover partial path must be EOS padding: all 1-bits and fewer than 8
|
||||
if node is not _HUFF_ROOT:
|
||||
if consumed >= 8:
|
||||
raise ValueError("Huffman padding too long")
|
||||
# walk back is unnecessary: padding is all-ones, i.e. we must have only taken '1' branches
|
||||
# since the last leaf; verify by re-deriving is overkill - reference cross-check guards it
|
||||
return bytes(out)
|
||||
|
||||
# ---------- integer / string (RFC 7541 5.1 / 5.2) ----------
|
||||
def encode_integer(value, prefix_bits, first_byte=0):
|
||||
mask = (1 << prefix_bits) - 1
|
||||
if value < mask:
|
||||
return bytearray([first_byte | value])
|
||||
out = bytearray([first_byte | mask])
|
||||
value -= mask
|
||||
while value >= 0x80:
|
||||
out.append((value & 0x7f) | 0x80)
|
||||
value >>= 7
|
||||
out.append(value)
|
||||
return out
|
||||
|
||||
def decode_integer(data, pos, prefix_bits):
|
||||
mask = (1 << prefix_bits) - 1
|
||||
value = data[pos] & mask
|
||||
pos += 1
|
||||
if value < mask:
|
||||
return value, pos
|
||||
shift = 0
|
||||
while True:
|
||||
b = data[pos]
|
||||
pos += 1
|
||||
value += (b & 0x7f) << shift
|
||||
shift += 7
|
||||
if not (b & 0x80):
|
||||
break
|
||||
return value, pos
|
||||
|
||||
def encode_string(value, huffman=True):
|
||||
if huffman:
|
||||
encoded = huffman_encode(value)
|
||||
if len(encoded) < len(value): # only use Huffman when it actually shrinks
|
||||
return encode_integer(len(encoded), 7, 0x80) + encoded
|
||||
return encode_integer(len(value), 7, 0x00) + bytearray(value)
|
||||
|
||||
def decode_string(data, pos):
|
||||
huffman = bool(data[pos] & 0x80)
|
||||
length, pos = decode_integer(data, pos, 7)
|
||||
raw = bytes(data[pos:pos + length])
|
||||
pos += length
|
||||
return (huffman_decode(raw) if huffman else raw), pos
|
||||
|
||||
# ---------- dynamic table + decoder/encoder ----------
|
||||
class Decoder(object):
|
||||
def __init__(self, max_size=4096):
|
||||
self.max_size = max_size
|
||||
self.dynamic = [] # newest first: [(name, value), ...]
|
||||
self._size = 0
|
||||
|
||||
def _entry_size(self, name, value):
|
||||
return 32 + len(name) + len(value)
|
||||
|
||||
def _add(self, name, value):
|
||||
self.dynamic.insert(0, (name, value))
|
||||
self._size += self._entry_size(name, value)
|
||||
self._evict()
|
||||
|
||||
def _evict(self):
|
||||
while self._size > self.max_size and self.dynamic:
|
||||
name, value = self.dynamic.pop()
|
||||
self._size -= self._entry_size(name, value)
|
||||
|
||||
def _get(self, index):
|
||||
if index <= 0:
|
||||
raise ValueError("invalid header index 0")
|
||||
if index <= STATIC_LEN:
|
||||
return STATIC_TABLE[index - 1]
|
||||
index -= STATIC_LEN + 1
|
||||
if index >= len(self.dynamic):
|
||||
raise ValueError("dynamic index out of range")
|
||||
return self.dynamic[index]
|
||||
|
||||
def decode(self, data):
|
||||
data = bytearray(data)
|
||||
pos = 0
|
||||
headers = []
|
||||
n = len(data)
|
||||
while pos < n:
|
||||
byte = data[pos]
|
||||
if byte & 0x80: # 6.1 indexed
|
||||
index, pos = decode_integer(data, pos, 7)
|
||||
headers.append(self._get(index))
|
||||
elif byte & 0x40: # 6.2.1 literal + incremental indexing
|
||||
index, pos = decode_integer(data, pos, 6)
|
||||
if index:
|
||||
name = self._get(index)[0]
|
||||
else:
|
||||
name, pos = decode_string(data, pos)
|
||||
value, pos = decode_string(data, pos)
|
||||
self._add(name, value)
|
||||
headers.append((name, value))
|
||||
elif byte & 0x20: # 6.3 dynamic table size update
|
||||
new_size, pos = decode_integer(data, pos, 5)
|
||||
self.max_size = new_size
|
||||
self._evict()
|
||||
else: # 6.2.2 without / 6.2.3 never indexed (4-bit prefix)
|
||||
index, pos = decode_integer(data, pos, 4)
|
||||
if index:
|
||||
name = self._get(index)[0]
|
||||
else:
|
||||
name, pos = decode_string(data, pos)
|
||||
value, pos = decode_string(data, pos)
|
||||
headers.append((name, value))
|
||||
return headers
|
||||
|
||||
class Encoder(object):
|
||||
# Minimal, always-valid: emit each header as a literal WITHOUT indexing + Huffman-coded strings.
|
||||
# (Correctness-critical decoding is the hard part; a server accepts this trivially.)
|
||||
def encode(self, headers):
|
||||
out = bytearray()
|
||||
for name, value in headers:
|
||||
out += encode_integer(0, 4, 0x00) # 0000 0000 : literal w/o indexing, new name
|
||||
out += encode_string(name)
|
||||
out += encode_string(value)
|
||||
return bytes(out)
|
||||
|
||||
SETTINGS_INITIAL_WINDOW_SIZE = 0x4
|
||||
BIG_WINDOW = (1 << 31) - 1
|
||||
|
||||
def _recv_exact(sock, n):
|
||||
buf = b""
|
||||
while len(buf) < n:
|
||||
chunk = sock.recv(n - len(buf))
|
||||
if not chunk:
|
||||
raise IOError("connection closed by peer")
|
||||
buf += chunk
|
||||
return buf
|
||||
|
||||
def _read_frame(sock):
|
||||
length, ftype, flags, sid = decode_frame_header(_recv_exact(sock, 9))
|
||||
return ftype, flags, sid, (_recv_exact(sock, length) if length else b"")
|
||||
|
||||
def _tob(x):
|
||||
return x if isinstance(x, bytes) else x.encode("latin-1")
|
||||
|
||||
def _connect_socket(host, port, proxy, timeout):
|
||||
# Direct TCP, or an HTTP CONNECT tunnel through an (optionally authenticated) proxy. SOCKS proxies
|
||||
# are excluded for HTTP/2 upstream, so any proxy reaching here is a plain HTTP one. proxy is a
|
||||
# (proxy_host, proxy_port, "user:pass"-or-None) tuple.
|
||||
if not proxy:
|
||||
return socket.create_connection((host, port), timeout=timeout)
|
||||
|
||||
proxy_host, proxy_port, proxy_cred = proxy
|
||||
raw = socket.create_connection((proxy_host, proxy_port), timeout=timeout)
|
||||
try:
|
||||
request = "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n" % (host, port, host, port)
|
||||
if proxy_cred:
|
||||
token = base64.b64encode(proxy_cred.encode("latin-1")).decode("ascii")
|
||||
request += "Proxy-Authorization: Basic %s\r\n" % token
|
||||
request += "\r\n"
|
||||
raw.sendall(request.encode("latin-1"))
|
||||
|
||||
response = b""
|
||||
while b"\r\n\r\n" not in response:
|
||||
chunk = raw.recv(4096)
|
||||
if not chunk:
|
||||
raise IOError("proxy closed the connection during CONNECT")
|
||||
response += chunk
|
||||
if len(response) > 65536:
|
||||
raise IOError("oversized proxy CONNECT response")
|
||||
|
||||
status_line = response.split(b"\r\n", 1)[0].decode("latin-1", "replace")
|
||||
fields = status_line.split(None, 2)
|
||||
code = int(fields[1]) if len(fields) >= 2 and fields[1].isdigit() else 0
|
||||
if not (200 <= code < 300):
|
||||
raise IOError("proxy CONNECT failed: %s" % status_line)
|
||||
return raw
|
||||
except Exception:
|
||||
try:
|
||||
raw.close()
|
||||
except Exception:
|
||||
pass
|
||||
raise
|
||||
|
||||
def h2_request(host, port=443, method="GET", path="/", authority=None, headers=None, body=None, timeout=30, proxy=None):
|
||||
authority = authority or host
|
||||
ctx = ssl._create_unverified_context()
|
||||
ctx.set_alpn_protocols(["h2"])
|
||||
sock = ctx.wrap_socket(_connect_socket(host, port, proxy, timeout), server_hostname=host)
|
||||
try:
|
||||
if sock.selected_alpn_protocol() != "h2":
|
||||
raise IOError("server did not negotiate h2 (ALPN=%r)" % sock.selected_alpn_protocol())
|
||||
sock.settimeout(timeout)
|
||||
|
||||
# connection preface + client SETTINGS (advertise a large per-stream window) + bump conn window
|
||||
sock.sendall(CONNECTION_PREFACE)
|
||||
sock.sendall(encode_frame(SETTINGS, 0, 0, struct.pack("!HI", SETTINGS_INITIAL_WINDOW_SIZE, BIG_WINDOW)))
|
||||
sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", BIG_WINDOW - 65535)))
|
||||
|
||||
req = [(b":method", _tob(method)), (b":scheme", b"https"), (b":path", _tob(path)), (b":authority", _tob(authority))]
|
||||
for k, v in (headers or {}).items():
|
||||
req.append((_tob(k).lower(), _tob(v)))
|
||||
hblock = Encoder().encode(req)
|
||||
sock.sendall(encode_frame(HEADERS, FLAG_END_HEADERS | (0 if body else FLAG_END_STREAM), 1, hblock))
|
||||
if body:
|
||||
sock.sendall(encode_frame(DATA, FLAG_END_STREAM, 1, _tob(body)))
|
||||
|
||||
dec = Decoder()
|
||||
header_block, resp_headers, resp_body, done = b"", None, bytearray(), False
|
||||
while not done:
|
||||
ftype, flags, sid, payload = _read_frame(sock)
|
||||
if ftype == SETTINGS:
|
||||
if not (flags & FLAG_ACK):
|
||||
sock.sendall(encode_frame(SETTINGS, FLAG_ACK, 0, b""))
|
||||
elif ftype == PING:
|
||||
if not (flags & FLAG_ACK):
|
||||
sock.sendall(encode_frame(PING, FLAG_ACK, 0, payload))
|
||||
elif ftype == GOAWAY:
|
||||
done = True
|
||||
elif ftype == RST_STREAM and sid == 1:
|
||||
raise IOError("stream reset by server (error %d)" % struct.unpack("!I", payload[:4])[0])
|
||||
elif ftype in (HEADERS, CONTINUATION) and sid == 1:
|
||||
p = payload
|
||||
if ftype == HEADERS:
|
||||
if flags & FLAG_PADDED:
|
||||
p = p[1:len(p) - bytearray(payload)[0]]
|
||||
if flags & FLAG_PRIORITY:
|
||||
p = p[5:]
|
||||
header_block += p
|
||||
if flags & FLAG_END_HEADERS:
|
||||
resp_headers = dec.decode(header_block)
|
||||
if flags & FLAG_END_STREAM:
|
||||
done = True
|
||||
elif ftype == DATA and sid == 1:
|
||||
p = payload
|
||||
if flags & FLAG_PADDED:
|
||||
p = p[1:len(p) - bytearray(payload)[0]]
|
||||
resp_body += p
|
||||
if payload: # replenish stream + connection windows
|
||||
sock.sendall(encode_frame(WINDOW_UPDATE, 0, 1, struct.pack("!I", len(payload))))
|
||||
sock.sendall(encode_frame(WINDOW_UPDATE, 0, 0, struct.pack("!I", len(payload))))
|
||||
if flags & FLAG_END_STREAM:
|
||||
done = True
|
||||
status = None
|
||||
for n, v in (resp_headers or []):
|
||||
if _tob(n) == b":status":
|
||||
status = int(v)
|
||||
break
|
||||
return status, resp_headers, bytes(resp_body)
|
||||
finally:
|
||||
try: sock.close()
|
||||
except Exception: pass
|
||||
|
||||
|
||||
class H2Response(object):
|
||||
"""A urllib-response-compatible wrapper around a native HTTP/2 response, so the rest of sqlmap's
|
||||
request pipeline can consume it exactly like a urllib response (code/msg/info()/read()/geturl())."""
|
||||
|
||||
def __init__(self, url, status, headers, body):
|
||||
self.url = url
|
||||
self.code = self.status = status
|
||||
self.msg = _HTTP_RESPONSES.get(status, "")
|
||||
self.http_version = "HTTP/2.0"
|
||||
self._body = body
|
||||
self._offset = 0
|
||||
self._info = _Message()
|
||||
for name, value in (headers or []):
|
||||
name = name.decode("latin-1") if isinstance(name, bytes) else name
|
||||
value = value.decode("latin-1") if isinstance(value, bytes) else value
|
||||
if not name.startswith(":"): # drop HTTP/2 pseudo-headers (:status etc.)
|
||||
self._info[name] = value
|
||||
# expose a mimetools.Message-style '.headers' list so patchHeaders() treats this object
|
||||
# uniformly across Python 2/3 (email.message.Message lacks it, and Python 2 iteration over a
|
||||
# bare Message falls back to integer indexing)
|
||||
self._info.headers = ["%s: %s\r\n" % (name, value) for (name, value) in self._info.items()]
|
||||
|
||||
def info(self):
|
||||
return self._info
|
||||
|
||||
def geturl(self):
|
||||
return self.url
|
||||
|
||||
def read(self, amt=None):
|
||||
if amt is None:
|
||||
data = self._body[self._offset:]
|
||||
self._offset = len(self._body)
|
||||
else:
|
||||
data = self._body[self._offset:self._offset + amt]
|
||||
self._offset += len(data)
|
||||
return data
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
def open_url(url, method="GET", headers=None, body=None, timeout=30, follow_redirects=True, max_redirects=10, proxy=None):
|
||||
"""Fetch url over native HTTP/2 (https only), following redirects like a browser (mirroring the
|
||||
previous httpx follow_redirects=True), and return an H2Response. Raises IOError on a transport or
|
||||
ALPN-negotiation failure. Connection-level and h2-forbidden request headers are stripped."""
|
||||
forbidden = ("host", "connection", "keep-alive", "proxy-connection", "transfer-encoding", "upgrade", "content-length")
|
||||
req_headers = {}
|
||||
for key in (headers or {}):
|
||||
name = key.decode("latin-1") if isinstance(key, bytes) else key
|
||||
if name.lower() not in forbidden:
|
||||
req_headers[key] = headers[key]
|
||||
|
||||
for _ in range(max_redirects + 1):
|
||||
parts = urlsplit(url)
|
||||
if parts.scheme != "https":
|
||||
raise IOError("native HTTP/2 client supports 'https://' targets only (got %r)" % parts.scheme)
|
||||
path = parts.path or "/"
|
||||
if parts.query:
|
||||
path += "?" + parts.query
|
||||
status, resp_headers, resp_body = h2_request(parts.hostname, parts.port or 443, method=method, path=path,
|
||||
authority=parts.netloc.split("@")[-1], headers=req_headers, body=body, timeout=timeout, proxy=proxy)
|
||||
if follow_redirects and status in REDIRECT_CODES:
|
||||
location = None
|
||||
for name, value in (resp_headers or []):
|
||||
if (name.decode("latin-1") if isinstance(name, bytes) else name).lower() == "location":
|
||||
location = value.decode("latin-1") if isinstance(value, bytes) else value
|
||||
break
|
||||
if location:
|
||||
url = urljoin(url, location)
|
||||
if status in (301, 302, 303): # per RFC 7231, these degrade to GET
|
||||
method, body = "GET", None
|
||||
continue
|
||||
return H2Response(url, status, resp_headers, resp_body)
|
||||
|
||||
raise IOError("too many HTTP/2 redirects")
|
||||
|
|
@ -94,16 +94,6 @@ def checkDependencies():
|
|||
logger.warning(warnMsg)
|
||||
missing_libraries.add('python-ntlm')
|
||||
|
||||
try:
|
||||
__import__("httpx")
|
||||
debugMsg = "'httpx[http2]' third-party library is found"
|
||||
logger.debug(debugMsg)
|
||||
except ImportError:
|
||||
warnMsg = "sqlmap requires 'httpx[http2]' third-party library "
|
||||
warnMsg += "if you plan to use HTTP version 2"
|
||||
logger.warning(warnMsg)
|
||||
missing_libraries.add('httpx[http2]')
|
||||
|
||||
try:
|
||||
__import__("websocket._abnf")
|
||||
debugMsg = "'websocket-client' library is found"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue