Skip to content

Commit 638fd68

Browse files
authored
HTTP_Client improvements (custom headers) (#4604)
* Win: HTTP_Client improvements and KKDC proxy in KerberosClient * Fix docstring warning
1 parent c2ce8dc commit 638fd68

File tree

2 files changed

+114
-18
lines changed

2 files changed

+114
-18
lines changed

scapy/layers/http.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -823,9 +823,24 @@ def sr1(self, req, **kwargs):
823823
)
824824
return resp
825825

826-
def request(self, url, data=b"", timeout=5, follow_redirects=True, **headers):
826+
def request(self,
827+
url,
828+
data=b"",
829+
timeout=5,
830+
follow_redirects=True,
831+
http_headers={},
832+
**headers):
827833
"""
828834
Perform a HTTP(s) request.
835+
836+
:param url: the full URL to connect to.
837+
e.g. https://google.com/test
838+
:param data: the data to send as payload
839+
:param follow_redirects: if True, request() will follow 302 return codes
840+
:param http_headers: if specified, overwrites the HTTP headers
841+
(except Host and Path).
842+
:param headers: any additional HTTPRequest parameter to add.
843+
e.g. Method="POST"
829844
"""
830845
# Parse request url
831846
m = re.match(r"(https?)://([^/:]+)(?:\:(\d+))?(/.*)?", url)
@@ -844,14 +859,20 @@ def request(self, url, data=b"", timeout=5, follow_redirects=True, **headers):
844859
self._connect_or_reuse(host, port=port, tls=tls, timeout=timeout)
845860

846861
# Build request
847-
http_headers = {
848-
"Accept_Encoding": b'gzip, deflate',
849-
"Cache_Control": b'no-cache',
850-
"Pragma": b'no-cache',
851-
"Connection": b'keep-alive',
852-
"Host": host,
853-
"Path": path,
854-
}
862+
headers.setdefault("Host", host)
863+
headers.setdefault("Path", path)
864+
865+
if not http_headers:
866+
http_headers = {
867+
"Accept_Encoding": b'gzip, deflate',
868+
"Cache_Control": b'no-cache',
869+
"Pragma": b'no-cache',
870+
"Connection": b'keep-alive',
871+
}
872+
else:
873+
http_headers = {
874+
k.replace("-", "_"): v for k, v in http_headers.items()
875+
}
855876
http_headers.update(headers)
856877
req = HTTP() / HTTPRequest(**http_headers)
857878
if data:

scapy/layers/kerberos.py

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
>>> enc.decrypt(k)
4747
"""
4848

49-
from collections import namedtuple
49+
from collections import namedtuple, deque
5050
from datetime import datetime, timedelta, timezone
5151
from enum import IntEnum
5252

@@ -117,7 +117,7 @@
117117
XStrField,
118118
)
119119
from scapy.packet import Packet, bind_bottom_up, bind_top_down, bind_layers
120-
from scapy.supersocket import StreamSocket
120+
from scapy.supersocket import StreamSocket, SuperSocket
121121
from scapy.utils import strrot, strxor
122122
from scapy.volatile import GeneralizedTime, RandNum, RandBin
123123

@@ -2523,14 +2523,77 @@ class KDC_PROXY_MESSAGE(ASN1_Packet):
25232523
ASN1F_optional(
25242524
ASN1F_FLAGS(
25252525
"dclocatorHint",
2526-
"",
2526+
None,
25272527
FlagsField("", 0, -32, _NV_VERSION).names,
25282528
explicit_tag=0xA2,
25292529
)
25302530
),
25312531
)
25322532

25332533

2534+
class KdcProxySocket(SuperSocket):
2535+
"""
2536+
This is a wrapper of a HTTP_Client that does KKDCP proxying,
2537+
disguised as a SuperSocket to be compatible with the rest of the KerberosClient.
2538+
"""
2539+
2540+
def __init__(
2541+
self,
2542+
url,
2543+
targetDomain,
2544+
dclocatorHint=None,
2545+
no_check_certificate=False,
2546+
**kwargs,
2547+
):
2548+
self.url = url
2549+
self.targetDomain = targetDomain
2550+
self.dclocatorHint = dclocatorHint
2551+
self.no_check_certificate = no_check_certificate
2552+
self.queue = deque()
2553+
super(KdcProxySocket, self).__init__(**kwargs)
2554+
2555+
def recv(self, x=None):
2556+
return self.queue.popleft()
2557+
2558+
def send(self, x, **kwargs):
2559+
from scapy.layers.http import HTTP_Client
2560+
2561+
cli = HTTP_Client(no_check_certificate=self.no_check_certificate)
2562+
try:
2563+
# sr it via the web client
2564+
resp = cli.request(
2565+
self.url,
2566+
Method="POST",
2567+
data=bytes(
2568+
# Wrap request in KDC_PROXY_MESSAGE
2569+
KDC_PROXY_MESSAGE(
2570+
kerbMessage=bytes(x),
2571+
targetDomain=ASN1_GENERAL_STRING(self.targetDomain.encode()),
2572+
# dclocatorHint is optional
2573+
dclocatorHint=self.dclocatorHint,
2574+
)
2575+
),
2576+
http_headers={
2577+
"Cache-Control": "no-cache",
2578+
"Pragma": "no-cache",
2579+
"User-Agent": "kerberos/1.0",
2580+
},
2581+
)
2582+
if resp and conf.raw_layer in resp:
2583+
# Parse the payload
2584+
resp = KDC_PROXY_MESSAGE(resp.load).kerbMessage
2585+
# We have an answer, queue it.
2586+
self.queue.append(resp)
2587+
else:
2588+
raise EOFError
2589+
finally:
2590+
cli.close()
2591+
2592+
@staticmethod
2593+
def select(sockets, remain=None):
2594+
return [x for x in sockets if isinstance(x, KdcProxySocket) and x.queue]
2595+
2596+
25342597
# Util functions
25352598

25362599

@@ -2558,6 +2621,8 @@ def __init__(
25582621
u2u=False,
25592622
for_user=None,
25602623
s4u2proxy=False,
2624+
kdc_proxy=None,
2625+
kdc_proxy_no_check_certificate=False,
25612626
etypes=None,
25622627
key=None,
25632628
port=88,
@@ -2590,7 +2655,7 @@ def __init__(
25902655
if not ticket:
25912656
raise ValueError("Invalid ticket")
25922657

2593-
if not ip:
2658+
if not ip and not kdc_proxy:
25942659
# No KDC IP provided. Find it by querying the DNS
25952660
ip = dclocator(
25962661
realm,
@@ -2630,7 +2695,8 @@ def __init__(
26302695
self._timeout = timeout
26312696
self._ip = ip
26322697
self._port = port
2633-
sock = self._connect()
2698+
self.kdc_proxy = kdc_proxy
2699+
self.kdc_proxy_no_check_certificate = kdc_proxy_no_check_certificate
26342700

26352701
if self.mode in [self.MODE.AS_REQ, self.MODE.GET_SALT]:
26362702
self.host = host.upper()
@@ -2651,16 +2717,25 @@ def __init__(
26512717
# Negotiated parameters
26522718
self.pre_auth = False
26532719
self.fxcookie = None
2720+
2721+
sock = self._connect()
26542722
super(KerberosClient, self).__init__(
26552723
sock=sock,
26562724
**kwargs,
26572725
)
26582726

26592727
def _connect(self):
2660-
sock = socket.socket()
2661-
sock.settimeout(self._timeout)
2662-
sock.connect((self._ip, self._port))
2663-
sock = StreamSocket(sock, KerberosTCPHeader)
2728+
if self.kdc_proxy:
2729+
sock = KdcProxySocket(
2730+
url=self.kdc_proxy,
2731+
targetDomain=self.realm,
2732+
no_check_certificate=self.kdc_proxy_no_check_certificate,
2733+
)
2734+
else:
2735+
sock = socket.socket()
2736+
sock.settimeout(self._timeout)
2737+
sock.connect((self._ip, self._port))
2738+
sock = StreamSocket(sock, KerberosTCPHeader)
26642739
return sock
26652740

26662741
def send(self, pkt):

0 commit comments

Comments
 (0)