Skip to content

Commit 1719971

Browse files
committed
SPNEGO: add tests & fix bugs
1 parent 144df4e commit 1719971

File tree

3 files changed

+203
-5
lines changed

3 files changed

+203
-5
lines changed

scapy/layers/spnego.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
from scapy.layers.kerberos import (
8484
Kerberos,
8585
KerberosSSP,
86+
_parse_spn,
8687
_parse_upn,
8788
)
8889
from scapy.layers.ntlm import (
@@ -672,11 +673,9 @@ def from_cli_arguments(
672673
if ":" in target:
673674
if not valid_ip6(target):
674675
hostname = target
675-
target = str(Net6(target))
676676
else:
677677
if not valid_ip(target):
678678
hostname = target
679-
target = str(Net(target))
680679

681680
# Check UPN
682681
try:
@@ -726,13 +725,15 @@ def from_cli_arguments(
726725
# - else a TGT if we got nothing better
727726
tgts = []
728727
for i, (tkt, key, upn, spn) in enumerate(t.iter_tickets()):
728+
spn, _ = _parse_spn(spn)
729+
spn_host = spn.split("/")[-1]
729730
# Check that it's for the correct user
730731
if upn.lower() == UPN.lower():
731732
# Check that it's either a TGT or a ST to the correct service
732733
if spn.lower().startswith("krbtgt/"):
733734
# TGT. Keep it, and see if we don't have a better ST.
734735
tgts.append(t.ssp(i))
735-
elif hostname in spn:
736+
elif hostname.lower() == spn_host.lower():
736737
# ST. We're done !
737738
ssps.append(t.ssp(i))
738739
break
@@ -797,7 +798,11 @@ def from_cli_arguments(
797798
if not kerberos_required:
798799
if HashNt is None and password is not None:
799800
HashNt = MD4le(password)
800-
ssps.append(NTLMSSP(UPN=UPN, HASHNT=HashNt))
801+
if HashNt is not None:
802+
ssps.append(NTLMSSP(UPN=UPN, HASHNT=HashNt))
803+
804+
if not ssps:
805+
raise ValueError("Unexpected case ! Please report.")
801806

802807
# Build the SSP
803808
return cls(ssps)

scapy/libs/rfc3961.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1451,7 +1451,7 @@ def prfplus(key, pepper):
14511451
# RFC 4556 #
14521452
############
14531453

1454-
def octetstring2key(etype: EncryptionType, x: bytes) -> bytes:
1454+
def octetstring2key(etype: EncryptionType, x: bytes) -> Key:
14551455
"""
14561456
RFC4556 octetstring2key::
14571457

test/scapy/layers/spnego.uts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
% SPNEGO unit tests
2+
3+
+ Special SPNEGO tests
4+
5+
= SPNEGOSSP.from_cli_arguments - Utils
6+
7+
from unittest import mock
8+
9+
NTLM = '1.3.6.1.4.1.311.2.2.10'
10+
KERBEROS = '1.2.840.113554.1.2.2'
11+
12+
# Detect password prompts
13+
def password_failure(*args, **kwargs):
14+
raise ValueError("Password was prompted unexpectedly !")
15+
16+
def password_input(*args, **kwargs):
17+
return "Password"
18+
19+
20+
def test_pwfail(**kwargs):
21+
"""Password means failure"""
22+
with mock.patch('prompt_toolkit.prompt', side_effect=password_failure):
23+
return SPNEGOSSP.from_cli_arguments(**kwargs)
24+
25+
26+
def test_pwinput(**kwargs):
27+
"""Password is entered"""
28+
with mock.patch('prompt_toolkit.prompt', side_effect=password_input):
29+
return SPNEGOSSP.from_cli_arguments(**kwargs)
30+
31+
= SPNEGOSSP.from_cli_arguments - Username + Password - With input
32+
33+
ssp = test_pwinput(
34+
UPN="Administrator",
35+
target="machine.domain.local",
36+
)
37+
assert isinstance(ssp, SPNEGOSSP)
38+
assert len(ssp.supported_ssps) == 1
39+
assert ssp.supported_ssps[NTLM].HASHNT == b'\xa4\xf4\x9c@e\x10\xbd\xca\xb6\x82N\xe7\xc3\x0f\xd8R'
40+
41+
= SPNEGOSSP.from_cli_arguments - Username + Password - With prompt
42+
43+
try:
44+
test_pwfail(
45+
UPN="Administrator",
46+
target="machine.domain.local",
47+
)
48+
assert False, "Should have prompted for password !"
49+
except ValueError:
50+
pass
51+
52+
= SPNEGOSSP.from_cli_arguments - Username + Password - No input
53+
54+
ssp = test_pwfail(
55+
UPN="Administrator",
56+
target="machine.domain.local",
57+
password="Password",
58+
)
59+
assert isinstance(ssp, SPNEGOSSP)
60+
assert len(ssp.supported_ssps) == 1
61+
assert ssp.supported_ssps[NTLM].HASHNT == b'\xa4\xf4\x9c@e\x10\xbd\xca\xb6\x82N\xe7\xc3\x0f\xd8R'
62+
63+
= SPNEGOSSP.from_cli_arguments - UPN + Password - With input
64+
65+
ssp = test_pwinput(
66+
UPN="Administrator@domain.local",
67+
target="machine.domain.local",
68+
)
69+
assert isinstance(ssp, SPNEGOSSP)
70+
assert len(ssp.supported_ssps) == 3
71+
assert ssp.supported_ssps[NTLM].HASHNT == b'\xa4\xf4\x9c@e\x10\xbd\xca\xb6\x82N\xe7\xc3\x0f\xd8R'
72+
assert ssp.supported_ssps[KERBEROS].UPN == "Administrator@domain.local"
73+
74+
= SPNEGOSSP.from_cli_arguments - UPN + CCache - Prepare
75+
76+
import os, base64
77+
from scapy.utils import get_temp_file
78+
79+
# Create CCACHE
80+
DATA = """
81+
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
131+
UJ8RjD9bppHbawAAAAA=
132+
"""
133+
ccache_file = get_temp_file()
134+
with open(ccache_file, "wb") as fd:
135+
fd.write(base64.b64decode(DATA.strip()))
136+
137+
os.environ["KRB5CCNAME"] = ccache_file
138+
139+
= SPNEGOSSP.from_cli_arguments - UPN + CCache - TGT from KRB5CCNAME
140+
141+
ssp = test_pwfail(
142+
UPN="Administrator@domain.local",
143+
target="machine.domain.local",
144+
use_krb5ccname=True,
145+
)
146+
assert len(ssp.supported_ssps) == 2
147+
assert ssp.supported_ssps[KERBEROS].TGT
148+
assert not ssp.supported_ssps[KERBEROS].ST
149+
150+
= SPNEGOSSP.from_cli_arguments - UPN + CCache - TGT from ccache
151+
152+
ssp = test_pwfail(
153+
UPN="Administrator@domain.local",
154+
target="machine.domain.local",
155+
ccache=ccache_file
156+
)
157+
assert len(ssp.supported_ssps) == 2
158+
assert ssp.supported_ssps[KERBEROS].TGT
159+
assert not ssp.supported_ssps[KERBEROS].ST
160+
161+
= SPNEGOSSP.from_cli_arguments - UPN + CCache - ST from ccache
162+
163+
ssp = test_pwfail(
164+
UPN="Administrator@domain.local",
165+
target="dc1.domain.local",
166+
ccache=ccache_file
167+
)
168+
assert len(ssp.supported_ssps) == 2
169+
assert ssp.supported_ssps[KERBEROS].ST
170+
assert not ssp.supported_ssps[KERBEROS].TGT
171+
172+
= SPNEGOSSP.from_cli_arguments - UPN + CCache - Failure
173+
174+
try:
175+
test_pwfail(
176+
UPN="Administrator@domain.local",
177+
target="machine.domain.local",
178+
)
179+
assert False, "Should have prompted for password !"
180+
except ValueError:
181+
pass
182+
183+
= SPNEGOSSP.from_cli_arguments - UPN + CCache - Bad UPN
184+
185+
try:
186+
test_pwfail(
187+
UPN="toto@domain.local",
188+
target="machine.domain.local",
189+
ccache=ccache_file
190+
)
191+
assert False, "Should have failed !"
192+
except ValueError:
193+
pass

0 commit comments

Comments
 (0)