Skip to content

Commit

Permalink
JKS and PKCS12 storages with default passwords
Browse files Browse the repository at this point in the history
  • Loading branch information
babenek committed Oct 16, 2023
1 parent 4eaa3df commit cc2d02c
Show file tree
Hide file tree
Showing 15 changed files with 321 additions and 4 deletions.
8 changes: 8 additions & 0 deletions credsweeper/deep_scanner/deep_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
from .encoder_scanner import EncoderScanner
from .gzip_scanner import GzipScanner
from .html_scanner import HtmlScanner
from .jks_scanner import JksScanner
from .lang_scanner import LangScanner
from .pdf_scanner import PdfScanner
from .pkcs12_scanner import Pkcs12Scanner
from .tar_scanner import TarScanner
from .xml_scanner import XmlScanner
from .zip_scanner import ZipScanner
Expand All @@ -37,8 +39,10 @@ class DeepScanner(
EncoderScanner, #
GzipScanner, #
HtmlScanner, #
JksScanner, #
LangScanner, #
PdfScanner, #
Pkcs12Scanner, #
TarScanner, #
XmlScanner, #
ZipScanner
Expand Down Expand Up @@ -79,6 +83,10 @@ def get_deep_scanners(data: bytes) -> List[Any]:
deep_scanners.append(GzipScanner)
elif Util.is_pdf(data):
deep_scanners.append(PdfScanner)
elif Util.is_jks(data):
deep_scanners.append(JksScanner)
elif Util.is_asn1(data):
deep_scanners.append(Pkcs12Scanner)
else:
deep_scanners = [ByteScanner, EncoderScanner, HtmlScanner, XmlScanner, LangScanner]
return deep_scanners
Expand Down
38 changes: 38 additions & 0 deletions credsweeper/deep_scanner/jks_scanner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import logging
from abc import ABC
from typing import List

import jks

from credsweeper.credentials import Candidate
from credsweeper.deep_scanner.abstract_scanner import AbstractScanner
from credsweeper.file_handler.data_content_provider import DataContentProvider

logger = logging.getLogger(__name__)


class JksScanner(AbstractScanner, ABC):
"""Implements jks scanning"""

def data_scan(
self, #
data_provider: DataContentProvider, #
depth: int, #
recursive_limit_size: int) -> List[Candidate]:
"""Tries to scan JKS to open with standard password"""
candidates = []
for pw_probe in ["", "changeit", "changeme"]:
try:
keystore = jks.KeyStore.loads(data_provider.data, pw_probe, try_decrypt_keys=True)
if keystore.private_keys or keystore.secret_keys:
candidate = Candidate.get_dummy_candidate(self.config, data_provider.file_path,
data_provider.file_type,
f"{data_provider.info}:'{pw_probe}' - has keys")
else:
candidate = Candidate.get_dummy_candidate(self.config, data_provider.file_path,
data_provider.file_type,
f"{data_provider.info}:'{pw_probe}' - default password")
candidates.append(candidate)
except Exception as jks_exc:
logger.debug(f"{data_provider.file_path}:{pw_probe}:{jks_exc}")
return candidates
44 changes: 44 additions & 0 deletions credsweeper/deep_scanner/pkcs12_scanner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import logging
from abc import ABC
from typing import List

import cryptography.hazmat.primitives.serialization.pkcs12

from credsweeper.credentials import Candidate
from credsweeper.deep_scanner.abstract_scanner import AbstractScanner
from credsweeper.file_handler.data_content_provider import DataContentProvider

logger = logging.getLogger(__name__)


class Pkcs12Scanner(AbstractScanner, ABC):
"""Implements jks scanning"""

def data_scan(
self, #
data_provider: DataContentProvider, #
depth: int, #
recursive_limit_size: int) -> List[Candidate]:
"""Tries to scan JKS to open with standard password"""
candidates = []
for pw_probe in [b"", b"changeit", b"changeme"]:
try:
(private_key, certificate, additional_certificates) \
= cryptography.hazmat.primitives.serialization.pkcs12.load_key_and_certificates(data_provider.data,
pw_probe)
if private_key:
candidate = Candidate.get_dummy_candidate(
self.config, #
data_provider.file_path, #
data_provider.file_type, #
f"{data_provider.info}:'{pw_probe.decode()}' - has keys PKCS12")
else:
candidate = Candidate.get_dummy_candidate(
self.config, #
data_provider.file_path, #
data_provider.file_type, #
f"{data_provider.info}:'{pw_probe.decode()}' - default password PKCS12")
candidates.append(candidate)
except Exception as pkcs_exc:
logger.debug(f"{data_provider.file_path}:{pw_probe.decode()}:{pkcs_exc}")
return candidates
33 changes: 33 additions & 0 deletions credsweeper/utils/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import math
import os
import struct
import tarfile
from dataclasses import dataclass
from pathlib import Path
Expand Down Expand Up @@ -392,6 +393,38 @@ def is_pdf(data: bytes) -> bool:
return True
return False

@staticmethod
def is_jks(data: bytes) -> bool:
"""According https://en.wikipedia.org/wiki/List_of_file_signatures - jks"""
if isinstance(data, bytes) and 4 <= len(data):
if data.startswith(b"\xFE\xED\xFE\xED"):
return True
return False

@staticmethod
def is_asn1(data: bytes) -> bool:
"""Only sequence type 0x30 and size correctness is checked"""
data_length = len(data)
if isinstance(data, bytes) and 4 <= data_length:
# sequence
if 0x30 == data[0]:
# https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/basic-encoding-rules.html#Lengths
length = data[1]
byte_len = (0x7F & length)
if 0x80 == length and data.endswith(b"\x00\x00"):
return True
elif 0x80 < length and byte_len < data_length: # additional check
len_bytes = data[2:2 + byte_len]
try:
long_size = struct.unpack(">h", len_bytes)
except struct.error:
long_size = (-1,) # yapf: disable
length = long_size[0]
else:
byte_len = 0
return data_length == length + 2 + byte_len
return False

@staticmethod
def is_elf(data: Union[bytes, bytearray]) -> bool:
"""According to https://en.wikipedia.org/wiki/Executable_and_Linkable_Format use only 5 bytes"""
Expand Down
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Common requirements
beautifulsoup4==4.12.2
cryptography==41.0.4
GitPython==3.1.37
google-auth-oauthlib==1.1.0
humanfriendly==10.0
Expand All @@ -16,6 +17,7 @@ whatthepatch==1.0.5
pdfminer.six==20221105
password-strength==0.0.3.post2
python-dateutil==2.8.2
pyjks==20.0.0

# ML requirements
numpy==1.24.4
Expand Down Expand Up @@ -45,6 +47,8 @@ types-PyYAML
types-requests
types-oauthlib
types-python-dateutil
types-pyjks
types-regex
types-humanfriendly
yapf

2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

install_requires = [
"beautifulsoup4>=4.11.0", # the lowest version with XMLParsedAsHTMLWarning
"cryptography", #
"GitPython", #
"google_auth_oauthlib", #
"humanfriendly", #
Expand All @@ -24,6 +25,7 @@
"scikit-learn", #
"onnxruntime", #
"python-dateutil", #
"pyjks", #
]

setuptools.setup(
Expand Down
6 changes: 3 additions & 3 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from pathlib import Path

# total number of files in test samples
SAMPLES_FILES_COUNT: int = 114
SAMPLES_FILES_COUNT: int = 117

# credentials count after scan
SAMPLES_CRED_COUNT: int = 129
Expand All @@ -11,10 +11,10 @@
SAMPLES_POST_CRED_COUNT: int = 122

# with option --doc
SAMPLES_IN_DOC = 80
SAMPLES_IN_DOC = 83

# archived credentials that are not found without --depth
SAMPLES_IN_DEEP_1 = SAMPLES_POST_CRED_COUNT + 16
SAMPLES_IN_DEEP_1 = SAMPLES_POST_CRED_COUNT + 19
SAMPLES_IN_DEEP_2 = SAMPLES_IN_DEEP_1 + 16
SAMPLES_IN_DEEP_3 = SAMPLES_IN_DEEP_2 + 3

Expand Down
72 changes: 72 additions & 0 deletions tests/data/depth_3.json
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,78 @@
}
]
},
{
"api_validation": "NOT_AVAILABLE",
"ml_validation": "NOT_AVAILABLE",
"ml_probability": null,
"rule": "Dummy candidate",
"severity": "info",
"line_data_list": [
{
"line": "dummy line",
"line_num": 0,
"path": "tests/samples/changeit_crt.jks",
"info": "tests/samples/changeit_crt.jks:'changeit' - default password",
"value": null,
"value_start": -2,
"value_end": -2,
"variable": null,
"entropy_validation": {
"iterator": null,
"entropy": null,
"valid": null
}
}
]
},
{
"api_validation": "NOT_AVAILABLE",
"ml_validation": "NOT_AVAILABLE",
"ml_probability": null,
"rule": "Dummy candidate",
"severity": "info",
"line_data_list": [
{
"line": "dummy line",
"line_num": 0,
"path": "tests/samples/changeit_crt.pkcs12",
"info": "tests/samples/changeit_crt.pkcs12:'changeit' - default password PKCS12",
"value": null,
"value_start": -2,
"value_end": -2,
"variable": null,
"entropy_validation": {
"iterator": null,
"entropy": null,
"valid": null
}
}
]
},
{
"api_validation": "NOT_AVAILABLE",
"ml_validation": "NOT_AVAILABLE",
"ml_probability": null,
"rule": "Dummy candidate",
"severity": "info",
"line_data_list": [
{
"line": "dummy line",
"line_num": 0,
"path": "tests/samples/changeme_key.jks",
"info": "tests/samples/changeme_key.jks:'changeme' - has keys",
"value": null,
"value_start": -2,
"value_end": -2,
"variable": null,
"entropy_validation": {
"iterator": null,
"entropy": null,
"valid": null
}
}
]
},
{
"api_validation": "NOT_AVAILABLE",
"ml_validation": "VALIDATED_KEY",
Expand Down
72 changes: 72 additions & 0 deletions tests/data/doc.json
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,78 @@
}
]
},
{
"api_validation": "NOT_AVAILABLE",
"ml_validation": "NOT_AVAILABLE",
"ml_probability": null,
"rule": "Dummy candidate",
"severity": "info",
"line_data_list": [
{
"line": "dummy line",
"line_num": 0,
"path": "tests/samples/changeit_crt.jks",
"info": "tests/samples/changeit_crt.jks:'changeit' - default password",
"value": null,
"value_start": -2,
"value_end": -2,
"variable": null,
"entropy_validation": {
"iterator": null,
"entropy": null,
"valid": null
}
}
]
},
{
"api_validation": "NOT_AVAILABLE",
"ml_validation": "NOT_AVAILABLE",
"ml_probability": null,
"rule": "Dummy candidate",
"severity": "info",
"line_data_list": [
{
"line": "dummy line",
"line_num": 0,
"path": "tests/samples/changeit_crt.pkcs12",
"info": "tests/samples/changeit_crt.pkcs12:'changeit' - default password PKCS12",
"value": null,
"value_start": -2,
"value_end": -2,
"variable": null,
"entropy_validation": {
"iterator": null,
"entropy": null,
"valid": null
}
}
]
},
{
"api_validation": "NOT_AVAILABLE",
"ml_validation": "NOT_AVAILABLE",
"ml_probability": null,
"rule": "Dummy candidate",
"severity": "info",
"line_data_list": [
{
"line": "dummy line",
"line_num": 0,
"path": "tests/samples/changeme_key.jks",
"info": "tests/samples/changeme_key.jks:'changeme' - has keys",
"value": null,
"value_start": -2,
"value_end": -2,
"variable": null,
"entropy_validation": {
"iterator": null,
"entropy": null,
"valid": null
}
}
]
},
{
"api_validation": "NOT_AVAILABLE",
"ml_validation": "NOT_AVAILABLE",
Expand Down
Binary file added tests/samples/changeit_crt.jks
Binary file not shown.
Binary file added tests/samples/changeit_crt.pkcs12
Binary file not shown.
Binary file added tests/samples/changeme_key.jks
Binary file not shown.
Binary file modified tests/samples/dummy.jks
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ def test_find_by_ext_p(self) -> None:
content_provider: FilesProvider = TextProvider([SAMPLES_PATH])
cred_sweeper = CredSweeper(find_by_ext=True)
cred_sweeper.run(content_provider=content_provider)
self.assertEqual(SAMPLES_POST_CRED_COUNT + 1, len(cred_sweeper.credential_manager.get_credentials()))
self.assertEqual(SAMPLES_POST_CRED_COUNT + 3, len(cred_sweeper.credential_manager.get_credentials()))

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

Expand Down
Loading

0 comments on commit cc2d02c

Please sign in to comment.