Skip to content

Commit 0f21077

Browse files
authored
Merge pull request #255 from blacklanternsecurity/dev
Dev->Main
2 parents 466150f + b2c56b4 commit 0f21077

12 files changed

+131
-81
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Inspired by [Blacklist3r](https://github.com/NotSoSecure/Blacklist3r), with a de
3434
| Express_SignedCookies_ES | Checks express.js express-session middleware for signed cookies and session cookies for known 'session secret' |
3535
| Express_SignedCookies_CS | Checks express.js cookie-session middleware for signed cookies and session cookies for known secret |
3636
| Laravel_SignedCookies | Checks 'laravel_session' cookies for known laravel 'APP_KEY' |
37-
| ASPNET_Vstate | Checks for a once popular custom compressed Viewstate [code snippet](https://blog.sorcery.ie/posts/higherlogic_rce/) vulnerable to RCE|
37+
| ASPNET_Compressedviewstate | Checks for a once popular custom compressed Viewstate [code snippet](https://blog.sorcery.ie/posts/higherlogic_rce/) vulnerable to RCE|
3838
| Rack2_SignedCookies | Checks Rack 2.x signed cookies for known secret keys |
3939
| Yii2_SignedCookies | Checks Yii2 framework signed cookies for known cookie validation keys |
4040

@@ -305,7 +305,7 @@ Symfony_SignedURL = modules_loaded["symfony_signedurl"]
305305
Express_SignedCookies_ES = modules_loaded["express_signedcookies_es"]
306306
Express_SignedCookies_CS = modules_loaded["express_signedcookies_cs"]
307307
Laravel_SignedCookies = modules_loaded["laravel_signedcookies"]
308-
ASPNET_Vstate = modules_loaded["aspnet_vstate"]
308+
ASPNET_Compressed_Viewstate = modules_loaded["aspnet_compressedvstate"]
309309
Rack2_SignedCookies = modules_loaded["rack2_signedcookies"]
310310
Yii2_SignedCookies = modules_loaded["yii2_signedcookies"]
311311
@@ -419,7 +419,7 @@ else:
419419
print("KEY NOT FOUND :(")
420420
421421
422-
x = ASPNET_Vstate()
422+
x = ASPNET_Compressed_Viewstate()
423423
print(f"###{str(x.__class__.__name__)}###")
424424
r = x.check_secret("H4sIAAAAAAAEAPvPyJ/Cz8ppZGpgaWpgZmmYAgAAmCJNEQAAAA==")
425425
if r:

badsecrets/base.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class BadsecretsBase:
2929
}
3030

3131
check_secret_args = 1
32+
validate_carve = True
3233

3334
def __init__(self, custom_resource=None, **kwargs):
3435
self.custom_resource = custom_resource
@@ -129,37 +130,39 @@ def carve(self, body=None, cookies=None, headers=None, requests_response=None, *
129130
elif self.carve_regex():
130131
s = re.search(self.carve_regex(), header_value)
131132
if s:
132-
r = self.carve_to_check_secret(s)
133+
if not self.validate_carve or self.identify(s.groups()[0]):
134+
r = self.carve_to_check_secret(s)
135+
if r:
136+
r["type"] = "SecretFound"
137+
# the carve regex hit but no secret was found
138+
else:
139+
r = {"type": "IdentifyOnly"}
140+
r["hashcat"] = self.get_hashcat_commands(s.groups()[0])
141+
if "product" not in r.keys():
142+
r["product"] = self.get_product_from_carve(s)
143+
r["location"] = "headers"
144+
results.append(r)
145+
146+
if body:
147+
if type(body) != str:
148+
raise badsecrets.errors.CarveException("Body argument must be type str")
149+
if self.carve_regex():
150+
s = re.search(self.carve_regex(), body)
151+
if s:
152+
if not self.validate_carve or self.identify(s.groups()[0]):
153+
r = self.carve_to_check_secret(
154+
s, url=kwargs.get("url", None), body=body, cookies=cookies, headers=headers
155+
)
133156
if r:
134157
r["type"] = "SecretFound"
135-
# the carve regex hit but no secret was found
136158
else:
137159
r = {"type": "IdentifyOnly"}
138160
r["hashcat"] = self.get_hashcat_commands(s.groups()[0])
139161
if "product" not in r.keys():
140162
r["product"] = self.get_product_from_carve(s)
141-
r["location"] = "headers"
163+
r["location"] = "body"
142164
results.append(r)
143165

144-
if body:
145-
if type(body) != str:
146-
raise badsecrets.errors.CarveException("Body argument must be type str")
147-
if self.carve_regex():
148-
s = re.search(self.carve_regex(), body)
149-
if s:
150-
r = self.carve_to_check_secret(
151-
s, url=kwargs.get("url", None), body=body, cookies=cookies, headers=headers
152-
)
153-
if r:
154-
r["type"] = "SecretFound"
155-
else:
156-
r = {"type": "IdentifyOnly"}
157-
r["hashcat"] = self.get_hashcat_commands(s.groups()[0])
158-
if "product" not in r.keys():
159-
r["product"] = self.get_product_from_carve(s)
160-
r["location"] = "body"
161-
results.append(r)
162-
163166
for r in results:
164167
r["description"] = self.get_description()
165168

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import re
2+
3+
from badsecrets.base import BadsecretsBase
4+
from badsecrets.modules.aspnet_viewstate import ASPNET_Viewstate
5+
6+
# Reference: https://blog.sorcery.ie/posts/higherlogic_rce/
7+
8+
9+
class ASPNET_compressedviewstate(BadsecretsBase):
10+
identify_regex = re.compile(r"^H4sI.+$")
11+
description = {"product": "ASP.NET Compressed Viewstate", "secret": "unprotected", "severity": "CRITICAL"}
12+
13+
def carve_regex(self):
14+
return re.compile(r"<input[^>]+__(?:VIEWSTATE|VSTATE|COMPRESSEDVIEWSTATE)\"\s*value=\"(.*?)\"")
15+
16+
def check_secret(self, compressed_viewstate):
17+
if not self.identify(compressed_viewstate):
18+
return None
19+
20+
uncompressed = self.attempt_decompress(compressed_viewstate)
21+
if uncompressed and ASPNET_Viewstate.valid_preamble(uncompressed):
22+
r = {"source": compressed_viewstate, "info": "Custom ASP.NET Viewstate (Unprotected, Compressed)"}
23+
return {"secret": "UNPROTECTED (compressed)", "details": r}

badsecrets/modules/aspnet_viewstate.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class ASPNET_Viewstate(BadsecretsBase):
2222

2323
def carve_regex(self):
2424
return re.compile(
25-
r"<input.+__VIEWSTATE\"\svalue=\"(.+)\"[\S\s]+<input.+__VIEWSTATEGENERATOR\"\svalue=\"(\w+)\""
25+
r"<input.+__VIEWSTATE\"\svalue=\"(.+?)\"[\S\s]+<input.+?__VIEWSTATEGENERATOR\"\svalue=\"(\w+)\""
2626
)
2727

2828
def carve_to_check_secret(self, s, url=None, **kwargs):

badsecrets/modules/aspnet_vstate.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

badsecrets/modules/express_signedcookies_cs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class ExpressSignedCookies_CS(BadsecretsBase):
2323
"secret": "Express.js Secret (cookie-session)",
2424
"severity": "HIGH",
2525
}
26+
validate_carve = False
2627

2728
def carve_regex(self):
2829
return re.compile(r"(\w{1,64})=([^;]{4,512});.{0,100}?\1\.sig=([^;]{27,86})")

badsecrets/modules/jsf_viewstate.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,7 @@ def myfaces_decrypt(self, ct_bytes, password_bytes, dec_algos, hash_sizes):
176176

177177
def get_hashcat_commands(self, jsf_viewstate_value, *args):
178178
commands = []
179-
try:
180-
decoded_viewstate = base64.b64decode(urllib.parse.unquote(jsf_viewstate_value))
181-
except binascii.Error:
182-
return []
179+
decoded_viewstate = base64.b64decode(urllib.parse.unquote(jsf_viewstate_value))
183180
sig = decoded_viewstate[:32]
184181
data = decoded_viewstate[32:]
185182

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "badsecrets"
3-
version = "0.11.0"
3+
version = "0.12.0"
44
description = "About"
55
authors = ["A library for detecting known or weak secrets on across many platforms"]
66
license = "GPL-3.0"
@@ -39,4 +39,4 @@ build-backend = "poetry_dynamic_versioning.backend"
3939
[tool.poetry-dynamic-versioning]
4040
enable = true
4141
metadata = true
42-
format = 'v0.11.{distance}'
42+
format = 'v0.12.{distance}'

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ asgiref==3.9.1 ; python_version >= "3.9" and python_version < "4.0"
22
blinker==1.9.0 ; python_version >= "3.9" and python_version < "4.0"
33
certifi==2025.8.3 ; python_version >= "3.9" and python_version < "4.0"
44
cffi==1.17.1 ; python_version >= "3.9" and python_version < "4.0" and platform_python_implementation != "PyPy"
5-
charset-normalizer==3.4.2 ; python_version >= "3.9" and python_version < "4.0"
5+
charset-normalizer==3.4.3 ; python_version >= "3.9" and python_version < "4.0"
66
click==8.1.8 ; python_version >= "3.9" and python_version < "4.0"
77
colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0"
88
cryptography==45.0.6 ; python_version >= "3.9" and python_version < "4.0"

tests/all_modules_test.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -147,21 +147,28 @@ def test_carve_multiple_vulns():
147147
assert len(r_list) == 2
148148

149149

150-
def test_carve_empty_vstate():
151-
empty_vstate_html = """
152-
<div class="aspNetHidden">
153-
<input type="hidden" name="__VSTATE" id="__VSTATE" value="" />
154-
150+
def test_multiple_identify_only():
151+
multiple_identify_only_html = """
152+
<html>
153+
Sys.Application.add_init(function() {
154+
$create(Telerik.Web.UI.RadDialogOpener, {"_dialogDefinitions":{"ImageManager":{"SerializedParameters":"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","Width":"770px","Height":"588px","Title":"Image Manager"}
155+
});
156+
</script>
157+
</form>
158+
</body>
159+
</html>
155160
"""
156-
157161
with requests_mock.Mocker() as m:
158162
m.get(
159-
f"http://emptyvstate.carve-all.badsecrets.com/",
163+
f"http://multipleidentifyonly.carve-all.badsecrets.com/",
160164
status_code=200,
161-
text=empty_vstate_html,
165+
text=multiple_identify_only_html,
162166
)
163167

164-
res = requests.get(f"http://emptyvstate.carve-all.badsecrets.com/")
168+
res = requests.get(f"http://multipleidentifyonly.carve-all.badsecrets.com/")
165169
r_list = carve_all_modules(requests_response=res)
166-
assert r_list
167-
assert r_list[0]["product"] == "EMPTY '__VSTATE' FORM FIELD"
170+
assert len(r_list) == 2
171+
assert r_list[0]["type"] == "IdentifyOnly"
172+
assert r_list[1]["type"] == "IdentifyOnly"
173+
assert r_list[0]["description"]["product"] in ["Telerik DialogParameters", "Telerik Hash Key Signature"]
174+
assert r_list[1]["description"]["product"] in ["Telerik DialogParameters", "Telerik Hash Key Signature"]

0 commit comments

Comments
 (0)