Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev->Main Push #106

Merged
merged 7 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion badsecrets/examples/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
def print_version():
version = pkg_resources.get_distribution("badsecrets").version
if version == "0.0.0":
version = "Version Unknown (Running w/poetry?)"
version = "ersion Unknown (Running w/poetry?)"
print(f"v{version}\n")


Expand Down
4 changes: 2 additions & 2 deletions badsecrets/modules/express_signedcookies_es.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ def no_padding_urlsafe_base64_encode_es(enc):


class ExpressSignedCookies_ES(BadsecretsBase):
identify_regex = re.compile(r"^s%3[Aa][^\.]+\.[a-zA-Z0-9%]{20,90}$")
identify_regex = re.compile(r"^s%3[Aa][^\.]+\.(?!.*%20|.*%22)[a-zA-Z0-9%]{20,90}$")
description = {
"product": "Express.js Signed Cookie (express-session)",
"secret": "Express.js SESSION_SECRET (express-session)",
"severity": "LOW",
}

def carve_regex(self):
return re.compile(r"(s%3[Aa][^\.]+\.[a-zA-Z0-9%]{20,90})")
return re.compile(r"(s%3[Aa][^\.]+\.(?!.*%20|.*%22)[a-zA-Z0-9%]{20,90})")

def expressHMAC(self, payload, secret, hash_algorithm):
return no_padding_urlsafe_base64_encode_es(
Expand Down
87 changes: 46 additions & 41 deletions badsecrets/modules/jsf_viewstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import hashlib
import binascii
import urllib.parse
from Crypto.Cipher import DES3, AES, DES
from contextlib import suppress
from Crypto.Util.Padding import unpad
from Crypto.Cipher import DES3, AES, DES
from badsecrets.helpers import Java_sha1prng
from badsecrets.base import BadsecretsBase

Expand Down Expand Up @@ -230,39 +231,41 @@ def check_secret(self, jsf_viewstate_value):
jsf_viewstate_value = base64.b64encode(uncompressed)

for l in set(list(self.load_resources(["jsf_viewstate_passwords.txt", "top_10000_passwords.txt"]))):
password = l.rstrip()
if self.DES3_decrypt(jsf_viewstate_value, password):
return {
"secret": password,
"details": {
"source": jsf_viewstate_value,
"info": "JSF Viewstate (Mojarra 1.2.x - 2.0.3) 3DES Encrypted",
"compression": True if uncompressed else False,
},
}

# Mojarra decryption
for l in self.load_resources(["jsf_viewstate_passwords_b64.txt"]):
password_bytes = base64.b64decode(l.rstrip())
decrypted = self.AES_decrypt(jsf_viewstate_value, password_bytes)

if decrypted:
uncompressed = self.attempt_decompress(base64.b64encode(decrypted))
if uncompressed:
if b"java." in uncompressed:
decrypted = uncompressed

decrypted_b64 = base64.b64encode(decrypted).decode()
if decrypted_b64.startswith("rO0"):
with suppress(ValueError):
password = l.rstrip()
if self.DES3_decrypt(jsf_viewstate_value, password):
return {
"secret": base64.b64encode(password_bytes).decode(),
"secret": password,
"details": {
"source": jsf_viewstate_value,
"info": "JSF Viewstate (Mojarra 2.2.6 - 2.3.x) AES Encrypted",
"info": "JSF Viewstate (Mojarra 1.2.x - 2.0.3) 3DES Encrypted",
"compression": True if uncompressed else False,
},
}

# Mojarra decryption
for l in self.load_resources(["jsf_viewstate_passwords_b64.txt"]):
with suppress(ValueError):
password_bytes = base64.b64decode(l.rstrip())
decrypted = self.AES_decrypt(jsf_viewstate_value, password_bytes)

if decrypted:
uncompressed = self.attempt_decompress(base64.b64encode(decrypted))
if uncompressed:
if b"java." in uncompressed:
decrypted = uncompressed

decrypted_b64 = base64.b64encode(decrypted).decode()
if decrypted_b64.startswith("rO0"):
return {
"secret": base64.b64encode(password_bytes).decode(),
"details": {
"source": jsf_viewstate_value,
"info": "JSF Viewstate (Mojarra 2.2.6 - 2.3.x) AES Encrypted",
"compression": True if uncompressed else False,
},
}

# myfaces decryption / mac

myfaces_solved_mac_key = None
Expand All @@ -279,10 +282,11 @@ def check_secret(self, jsf_viewstate_value):

# Attempt to solve mac_key
for l in self.load_resources(["jsf_viewstate_passwords_b64.txt"]):
password_bytes = base64.b64decode(l.rstrip())
myfaces_solved_mac_key, myfaces_solved_mac_algo = self.myfaces_mac(ct_bytes, password_bytes)
if myfaces_solved_mac_key:
break
with suppress(ValueError):
password_bytes = base64.b64decode(l.rstrip())
myfaces_solved_mac_key, myfaces_solved_mac_algo = self.myfaces_mac(ct_bytes, password_bytes)
if myfaces_solved_mac_key:
break

# Attempt to solve encryption_key
dec_algos = set()
Expand All @@ -297,16 +301,17 @@ def check_secret(self, jsf_viewstate_value):
hash_sizes = self.hash_sizes.values()

for l in self.load_resources(["jsf_viewstate_passwords_b64.txt"]):
password_bytes = base64.b64decode(l.rstrip())
(
myfaces_solved_decryption_key,
myfaces_solved_decryption_algo,
myfaces_solved_decryption_mode,
myfaces_solved_decryption_iv,
compression,
) = self.myfaces_decrypt(ct_bytes, password_bytes, dec_algos, hash_sizes)
if myfaces_solved_decryption_key:
break
with suppress(ValueError):
password_bytes = base64.b64decode(l.rstrip())
(
myfaces_solved_decryption_key,
myfaces_solved_decryption_algo,
myfaces_solved_decryption_mode,
myfaces_solved_decryption_iv,
compression,
) = self.myfaces_decrypt(ct_bytes, password_bytes, dec_algos, hash_sizes)
if myfaces_solved_decryption_key:
break

if myfaces_solved_mac_key or myfaces_solved_decryption_key:
if myfaces_solved_decryption_key:
Expand Down
35 changes: 35 additions & 0 deletions tests/examples_cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,41 @@ def test_example_cli_customsecrets_toolarge(monkeypatch, capsys):
assert "exceeds the maximum limit of 100KB!" in captured.out


def test_example_cli_customsecrets_urlmode_expressbase64(monkeypatch, capsys):
base_vulnerable_page_jsf_custom = """
<p><input type="hidden" name="javax.faces.ViewState" id="j_id__v_0:javax.faces.ViewState:1" value="AHo0wmLu5ceItIi+I7XkEi1GAb4h12WZ894pA+Z4OH7bco2jXEy1RSCWwjtJcZNbWPcvPqL5zzfl03DoeMZfGGX7a9PSv+fUT8MAeKNouAGj1dZuO8srXt8xZIGg+wPCWWCzcX6IhWOtgWUwiXeSojCDTKXklsYt+kzlVBk5wOsXvb2lTJoO0Q==" autocomplete="off" />
"""

with tempfile.NamedTemporaryFile("w+t", delete=False) as f:
f.write("base64:aGFja3RoZXBsYW5ldA==")
f.flush()

with requests_mock.Mocker() as m:
m.get(
f"http://example.com/vulnerablejsf.html",
status_code=200,
text=base_vulnerable_page_jsf_custom,
)

monkeypatch.setattr(
"sys.argv",
[
"python",
"--url",
"http://example.com/vulnerablejsf.html",
"-c",
f.name,
],
)
cli.main()
captured = capsys.readouterr()
print(captured)
assert ("Including custom secrets list") in captured.out
assert (
"e496c62dfa4ce5541939c0eb17bdbda54c9a0ed1:007a34c262eee5c788b488be23b5e4122d4601be21d76599f3de2903e678387edb728da35c4cb5452096c23b4971935b58f72f3ea2f9cf37e5d370e878c65f1865fb6bd3d2bfe7d44fc30078a368b801a3d5d66e3bcb2b5edf316481a0fb03c25960b3717e888563ad816530897792a230834ca5"
) in captured.out


def test_example_cli_customsecrets_urlmode(monkeypatch, capsys):
base_vulnerable_page_aspnet_custom = """
<form method="post" action="./form.aspx" id="ctl00">
Expand Down