Merge pull request #2240 from guardicore/2217-pydantic-credentials

2217 pydantic credentials
This commit is contained in:
Mike Salvatore 2022-09-07 09:10:56 -04:00 committed by GitHub
commit d39d6ea10f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 918 additions and 1305 deletions

View File

@ -1,7 +1,7 @@
import json import json
import logging import logging
import time import time
from typing import Sequence, Union from typing import List, Sequence, Union
from bson import json_util from bson import json_util
@ -30,7 +30,7 @@ class MonkeyIslandClient(object):
def get_propagation_credentials(self) -> Sequence[Credentials]: def get_propagation_credentials(self) -> Sequence[Credentials]:
response = self.requests.get("api/propagation-credentials") response = self.requests.get("api/propagation-credentials")
return [Credentials.from_mapping(credentials) for credentials in response.json()] return [Credentials(**credentials) for credentials in response.json()]
@avoid_race_condition @avoid_race_condition
def import_config(self, test_configuration: TestConfiguration): def import_config(self, test_configuration: TestConfiguration):
@ -59,9 +59,9 @@ class MonkeyIslandClient(object):
assert False assert False
@avoid_race_condition @avoid_race_condition
def _import_credentials(self, propagation_credentials: Credentials): def _import_credentials(self, propagation_credentials: List[Credentials]):
serialized_propagation_credentials = [ serialized_propagation_credentials = [
Credentials.to_mapping(credentials) for credentials in propagation_credentials credentials.dict(simplify=True) for credentials in propagation_credentials
] ]
response = self.requests.put_json( response = self.requests.put_json(
"/api/propagation-credentials/configured-credentials", "/api/propagation-credentials/configured-credentials",

View File

@ -1,8 +1,3 @@
from .credential_component_type import CredentialComponentType
from .i_credential_component import ICredentialComponent
from .validators import InvalidCredentialComponentError, InvalidCredentialsError
from .lm_hash import LMHash from .lm_hash import LMHash
from .nt_hash import NTHash from .nt_hash import NTHash
from .password import Password from .password import Password

View File

@ -1,20 +0,0 @@
from marshmallow import Schema, post_load, validate
from marshmallow_enum import EnumField
from common.utils.code_utils import del_key
from . import CredentialComponentType
class CredentialTypeField(EnumField):
def __init__(self, credential_component_type: CredentialComponentType):
super().__init__(
CredentialComponentType, validate=validate.Equal(credential_component_type)
)
class CredentialComponentSchema(Schema):
@post_load
def _strip_credential_type(self, data, **kwargs):
del_key(data, "credential_type")
return data

View File

@ -1,9 +0,0 @@
from enum import Enum, auto
class CredentialComponentType(Enum):
USERNAME = auto()
PASSWORD = auto()
NT_HASH = auto()
LM_HASH = auto()
SSH_KEYPAIR = auto()

View File

@ -1,190 +1,35 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from typing import Optional, Union
from typing import Any, Mapping, Optional, Type
from marshmallow import Schema, fields, post_load, pre_dump from pydantic import SecretBytes, SecretStr
from marshmallow.exceptions import MarshmallowError
from ..utils import IJSONSerializable from ..base_models import InfectionMonkeyBaseModel
from . import ( from . import LMHash, NTHash, Password, SSHKeypair, Username
CredentialComponentType,
InvalidCredentialComponentError,
InvalidCredentialsError,
LMHash,
NTHash,
Password,
SSHKeypair,
Username,
)
from .i_credential_component import ICredentialComponent
from .lm_hash import LMHashSchema
from .nt_hash import NTHashSchema
from .password import PasswordSchema
from .ssh_keypair import SSHKeypairSchema
from .username import UsernameSchema
CREDENTIAL_COMPONENT_TYPE_TO_CLASS: Mapping[CredentialComponentType, Type[ICredentialComponent]] = { Secret = Union[Password, LMHash, NTHash, SSHKeypair]
CredentialComponentType.LM_HASH: LMHash, Identity = Username
CredentialComponentType.NT_HASH: NTHash,
CredentialComponentType.PASSWORD: Password,
CredentialComponentType.SSH_KEYPAIR: SSHKeypair,
CredentialComponentType.USERNAME: Username,
}
CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA: Mapping[CredentialComponentType, Schema] = {
CredentialComponentType.LM_HASH: LMHashSchema(),
CredentialComponentType.NT_HASH: NTHashSchema(),
CredentialComponentType.PASSWORD: PasswordSchema(),
CredentialComponentType.SSH_KEYPAIR: SSHKeypairSchema(),
CredentialComponentType.USERNAME: UsernameSchema(),
}
CredentialComponentMapping = Optional[Mapping[str, Any]]
CredentialsMapping = Mapping[str, CredentialComponentMapping]
class CredentialsSchema(Schema): def get_plaintext(secret: Union[SecretStr, SecretBytes, None, str]) -> Optional[str]:
identity = fields.Mapping(allow_none=True) if isinstance(secret, (SecretStr, SecretBytes)):
secret = fields.Mapping(allow_none=True) return secret.get_secret_value()
else:
return secret
@post_load
def _make_credentials(
self,
credentials: CredentialsMapping,
**kwargs: Mapping[str, Any],
) -> Mapping[str, Optional[ICredentialComponent]]:
if not any(credentials.values()):
raise InvalidCredentialsError("At least one credentials component must be defined")
return { class Credentials(InfectionMonkeyBaseModel):
key: CredentialsSchema._build_credential_component(credential_component_mapping) """Represents a credential pair (an identity and a secret)"""
for key, credential_component_mapping in credentials.items()
identity: Optional[Identity]
"""Identity part of credentials, like a username or an email"""
secret: Optional[Secret]
"""Secret part of credentials, like a password or a hash"""
class Config:
json_encoders = {
# This makes secrets dumpable to json, but not loggable
SecretStr: get_plaintext,
SecretBytes: get_plaintext,
} }
@staticmethod
def _build_credential_component(
credential_component: CredentialComponentMapping,
) -> Optional[ICredentialComponent]:
if credential_component is None:
return None
try:
credential_component_type = CredentialComponentType[
credential_component["credential_type"]
]
except KeyError as err:
raise InvalidCredentialsError(f"Unknown credential component type {err}")
credential_component_class = CREDENTIAL_COMPONENT_TYPE_TO_CLASS[credential_component_type]
credential_component_schema = CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA[
credential_component_type
]
try:
return credential_component_class(
**credential_component_schema.load(credential_component)
)
except MarshmallowError as err:
raise InvalidCredentialComponentError(credential_component_class, str(err))
@pre_dump
def _serialize_credentials(self, credentials: Credentials, **kwargs) -> CredentialsMapping:
return {
"identity": CredentialsSchema._serialize_credential_component(credentials.identity),
"secret": CredentialsSchema._serialize_credential_component(credentials.secret),
}
@staticmethod
def _serialize_credential_component(
credential_component: Optional[ICredentialComponent],
) -> CredentialComponentMapping:
if credential_component is None:
return None
credential_component_schema = CREDENTIAL_COMPONENT_TYPE_TO_CLASS_SCHEMA[
credential_component.credential_type
]
return credential_component_schema.dump(credential_component)
@dataclass(frozen=True)
class Credentials(IJSONSerializable):
identity: Optional[ICredentialComponent]
secret: Optional[ICredentialComponent]
def __post_init__(self):
schema = CredentialsSchema()
try:
serialized_data = schema.dump(self)
# This will raise an exception if the object is invalid. Calling this in __post__init()
# makes it impossible to construct an invalid object
schema.load(serialized_data)
except Exception as err:
raise InvalidCredentialsError(err)
@staticmethod
def from_mapping(credentials: CredentialsMapping) -> Credentials:
"""
Construct a Credentials object from a Mapping
:param credentials: A mapping that represents a Credentials object
:return: A Credentials object
:raises InvalidCredentialsError: If the provided Mapping does not represent a valid
Credentials object
:raises InvalidCredentialComponentError: If any of the contents of `identities` or `secrets`
are not a valid ICredentialComponent
"""
try:
deserialized_data = CredentialsSchema().load(credentials)
return Credentials(**deserialized_data)
except (InvalidCredentialsError, InvalidCredentialComponentError) as err:
raise err
except MarshmallowError as err:
raise InvalidCredentialsError(str(err))
@classmethod
def from_json(cls, credentials: str) -> Credentials:
"""
Construct a Credentials object from a JSON string
:param credentials: A JSON string that represents a Credentials object
:return: A Credentials object
:raises InvalidCredentialsError: If the provided JSON does not represent a valid
Credentials object
:raises InvalidCredentialComponentError: If any of the contents of `identities` or `secrets`
are not a valid ICredentialComponent
"""
try:
deserialized_data = CredentialsSchema().loads(credentials)
return Credentials(**deserialized_data)
except (InvalidCredentialsError, InvalidCredentialComponentError) as err:
raise err
except MarshmallowError as err:
raise InvalidCredentialsError(str(err))
@staticmethod
def to_mapping(credentials: Credentials) -> CredentialsMapping:
"""
Serialize a Credentials object to a Mapping
:param credentials: A Credentials object
:return: A mapping representing a Credentials object
"""
return CredentialsSchema().dump(credentials)
@classmethod
def to_json(cls, credentials: Credentials) -> str:
"""
Serialize a Credentials object to JSON
:param credentials: A Credentials object
:return: A JSON string representing a Credentials object
"""
return CredentialsSchema().dumps(credentials)

View File

@ -1,10 +0,0 @@
from abc import ABC, abstractmethod
from . import CredentialComponentType
class ICredentialComponent(ABC):
@property
@abstractmethod
def credential_type(self) -> CredentialComponentType:
pass

View File

@ -1,23 +1,16 @@
from dataclasses import dataclass, field import re
from marshmallow import fields from pydantic import SecretStr, validator
from . import CredentialComponentType, ICredentialComponent from ..base_models import InfectionMonkeyBaseModel
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField from .validators import ntlm_hash_regex
from .validators import credential_component_validator, ntlm_hash_validator
class LMHashSchema(CredentialComponentSchema): class LMHash(InfectionMonkeyBaseModel):
credential_type = CredentialTypeField(CredentialComponentType.LM_HASH) lm_hash: SecretStr
lm_hash = fields.Str(validate=ntlm_hash_validator)
@validator("lm_hash")
@dataclass(frozen=True) def validate_hash_format(cls, lm_hash):
class LMHash(ICredentialComponent): if not re.match(ntlm_hash_regex, lm_hash.get_secret_value()):
credential_type: CredentialComponentType = field( raise ValueError("Invalid LM hash provided")
default=CredentialComponentType.LM_HASH, init=False return lm_hash
)
lm_hash: str
def __post_init__(self):
credential_component_validator(LMHashSchema(), self)

View File

@ -1,23 +1,16 @@
from dataclasses import dataclass, field import re
from marshmallow import fields from pydantic import SecretStr, validator
from . import CredentialComponentType, ICredentialComponent from ..base_models import InfectionMonkeyBaseModel
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField from .validators import ntlm_hash_regex
from .validators import credential_component_validator, ntlm_hash_validator
class NTHashSchema(CredentialComponentSchema): class NTHash(InfectionMonkeyBaseModel):
credential_type = CredentialTypeField(CredentialComponentType.NT_HASH) nt_hash: SecretStr
nt_hash = fields.Str(validate=ntlm_hash_validator)
@validator("nt_hash")
@dataclass(frozen=True) def validate_hash_format(cls, nt_hash):
class NTHash(ICredentialComponent): if not re.match(ntlm_hash_regex, nt_hash.get_secret_value()):
credential_type: CredentialComponentType = field( raise ValueError("Invalid NT hash provided")
default=CredentialComponentType.NT_HASH, init=False return nt_hash
)
nt_hash: str
def __post_init__(self):
credential_component_validator(NTHashSchema(), self)

View File

@ -1,19 +1,7 @@
from dataclasses import dataclass, field from pydantic import SecretStr
from marshmallow import fields from ..base_models import InfectionMonkeyBaseModel
from . import CredentialComponentType, ICredentialComponent
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
class PasswordSchema(CredentialComponentSchema): class Password(InfectionMonkeyBaseModel):
credential_type = CredentialTypeField(CredentialComponentType.PASSWORD) password: SecretStr
password = fields.Str()
@dataclass(frozen=True)
class Password(ICredentialComponent):
credential_type: CredentialComponentType = field(
default=CredentialComponentType.PASSWORD, init=False
)
password: str

View File

@ -1,23 +1,8 @@
from dataclasses import dataclass, field from pydantic import SecretStr
from marshmallow import fields from ..base_models import InfectionMonkeyBaseModel
from . import CredentialComponentType, ICredentialComponent
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
class SSHKeypairSchema(CredentialComponentSchema): class SSHKeypair(InfectionMonkeyBaseModel):
credential_type = CredentialTypeField(CredentialComponentType.SSH_KEYPAIR) private_key: SecretStr
# TODO: Find a list of valid formats for ssh keys and add validators.
# See https://github.com/nemchik/ssh-key-regex
private_key = fields.Str()
public_key = fields.Str()
@dataclass(frozen=True)
class SSHKeypair(ICredentialComponent):
credential_type: CredentialComponentType = field(
default=CredentialComponentType.SSH_KEYPAIR, init=False
)
private_key: str
public_key: str public_key: str

View File

@ -1,19 +1,5 @@
from dataclasses import dataclass, field from ..base_models import InfectionMonkeyBaseModel
from marshmallow import fields
from . import CredentialComponentType, ICredentialComponent
from .credential_component_schema import CredentialComponentSchema, CredentialTypeField
class UsernameSchema(CredentialComponentSchema): class Username(InfectionMonkeyBaseModel):
credential_type = CredentialTypeField(CredentialComponentType.USERNAME)
username = fields.Str()
@dataclass(frozen=True)
class Username(ICredentialComponent):
credential_type: CredentialComponentType = field(
default=CredentialComponentType.USERNAME, init=False
)
username: str username: str

View File

@ -1,50 +1,3 @@
import re import re
from typing import Type
from marshmallow import Schema, validate ntlm_hash_regex = re.compile(r"^[a-fA-F0-9]{32}$")
from . import ICredentialComponent
_ntlm_hash_regex = re.compile(r"^[a-fA-F0-9]{32}$")
ntlm_hash_validator = validate.Regexp(regex=_ntlm_hash_regex)
class InvalidCredentialComponentError(Exception):
def __init__(self, credential_component_class: Type[ICredentialComponent], message: str):
self._credential_component_name = credential_component_class.__name__
self._message = message
def __str__(self) -> str:
return (
f"Cannot construct a {self._credential_component_name} object with the supplied, "
f"invalid data: {self._message}"
)
class InvalidCredentialsError(Exception):
def __init__(self, message: str):
self._message = message
def __str__(self) -> str:
return (
f"Cannot construct a Credentials object with the supplied, "
f"invalid data: {self._message}"
)
def credential_component_validator(schema: Schema, credential_component: ICredentialComponent):
"""
Validate a credential component
:param schema: A marshmallow schema used for validating the component
:param credential_component: A credential component to be validated
:raises InvalidCredentialComponent: if the credential_component contains invalid data
"""
try:
serialized_data = schema.dump(credential_component)
# This will raise an exception if the object is invalid. Calling this in __post__init()
# makes it impossible to construct an invalid object
schema.load(serialized_data)
except Exception as err:
raise InvalidCredentialComponentError(credential_component.__class__, err)

View File

@ -18,11 +18,11 @@
"default": { "default": {
"aiosmb": { "aiosmb": {
"hashes": [ "hashes": [
"sha256:0afa901093f0ad91d0b8421dec66c80bd2e9cb237a8da405984413a5d7475398", "sha256:2668d63ae6e6ca30a999696c444d4afc349f85a872a204994394aa6abbf5673d",
"sha256:0e98390ba00fdc4190e698f184dfcf72b02b592cdfe9274e03cc7316ac4ee368" "sha256:b0b0e7068e757b8d3a8e4be2ebf2b499933d9fa7853bc3a8e198d6a57dc77131"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==0.3.8" "version": "==0.4.0"
}, },
"aiowinreg": { "aiowinreg": {
"hashes": [ "hashes": [
@ -46,13 +46,21 @@
], ],
"version": "==1.5.1" "version": "==1.5.1"
}, },
"asyauth": {
"hashes": [
"sha256:a7a7998dcfa672ebae9837ba4996df237412a7f335c0f8291bb14091d0e6d19a",
"sha256:f20c497b02d17f25298d3701fb65c9a337893b68a9c7dddecc09ab956b09f828"
],
"markers": "python_version >= '3.7'",
"version": "==0.0.2"
},
"asysocks": { "asysocks": {
"hashes": [ "hashes": [
"sha256:23d5fcfae71a75826c3ed787bd9b1bc3b189ec37658961bce83c9e99455e354c", "sha256:8f4516088ebec7f08d8c549e5e75e7a86b41e6043af920ba4895cf3f6c654150",
"sha256:731eda25d41783c5243153d3cb4f9357fef337c7317135488afab9ecd6b7f1a1" "sha256:d4c619c9d2e8be0cbdd21fa635a7eaf5886809edc948e2f76625b33b3cccfc47"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==0.1.7" "version": "==0.2.1"
}, },
"attrs": { "attrs": {
"hashes": [ "hashes": [
@ -71,20 +79,21 @@
}, },
"bcrypt": { "bcrypt": {
"hashes": [ "hashes": [
"sha256:2b02d6bfc6336d1094276f3f588aa1225a598e27f8e3388f4db9948cb707b521", "sha256:0b0f0c7141622a31e9734b7f649451147c04ebb5122327ac0bd23744df84be90",
"sha256:433c410c2177057705da2a9f2cd01dd157493b2a7ac14c8593a16b3dab6b6bfb", "sha256:1c3334446fac200499e8bc04a530ce3cf0b3d7151e0e4ac5c0dddd3d95e97843",
"sha256:4e029cef560967fb0cf4a802bcf4d562d3d6b4b1bf81de5ec1abbe0f1adb027e", "sha256:2d0dd19aad87e4ab882ef1d12df505f4c52b28b69666ce83c528f42c07379227",
"sha256:61bae49580dce88095d669226d5076d0b9d927754cedbdf76c6c9f5099ad6f26", "sha256:594780b364fb45f2634c46ec8d3e61c1c0f1811c4f2da60e8eb15594ecbf93ed",
"sha256:6d2cb9d969bfca5bc08e45864137276e4c3d3d7de2b162171def3d188bf9d34a", "sha256:7c7dd6c1f05bf89e65261d97ac3a6520f34c2acb369afb57e3ea4449be6ff8fd",
"sha256:7180d98a96f00b1050e93f5b0f556e658605dd9f524d0b0e68ae7944673f525e", "sha256:845b1daf4df2dd94d2fdbc9454953ca9dd0e12970a0bfc9f3dcc6faea3fa96e4",
"sha256:7d9ba2e41e330d2af4af6b1b6ec9e6128e91343d0b4afb9282e54e5508f31baa", "sha256:8780e69f9deec9d60f947b169507d2c9816e4f11548f1f7ebee2af38b9b22ae4",
"sha256:7ff2069240c6bbe49109fe84ca80508773a904f5a8cb960e02a977f7f519b129", "sha256:bf413f2a9b0a2950fc750998899013f2e718d20fa4a58b85ca50b6df5ed1bbf9",
"sha256:88273d806ab3a50d06bc6a2fc7c87d737dd669b76ad955f449c43095389bc8fb", "sha256:bfb67f6a6c72dfb0a02f3df51550aa1862708e55128b22543e2b42c74f3620d7",
"sha256:a2c46100e315c3a5b90fdc53e429c006c5f962529bc27e1dfd656292c20ccc40", "sha256:c59c170fc9225faad04dde1ba61d85b413946e8ce2e5f5f5ff30dfd67283f319",
"sha256:cd43303d6b8a165c29ec6756afd169faba9396a9472cdff753fe9f19b96ce2fa" "sha256:dc6ec3dc19b1c193b2f7cf279d3e32e7caf447532fbcb7af0906fe4398900c33",
"sha256:ede0f506554571c8eda80db22b83c139303ec6b595b8f60c4c8157bdd0bdee36"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==3.2.2" "version": "==4.0.0"
}, },
"certifi": { "certifi": {
"hashes": [ "hashes": [
@ -173,11 +182,11 @@
}, },
"charset-normalizer": { "charset-normalizer": {
"hashes": [ "hashes": [
"sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2.1.0" "version": "==2.1.1"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@ -204,47 +213,51 @@
}, },
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
"sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", "sha256:03363b92b6d76e4292ace708dcc8fd31b28ce325714f5af9f72afca6e7ac4070",
"sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", "sha256:144c9b09135caf6580fb8bf03594760a351dcb1bb6df3a4ed871527a90597e31",
"sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", "sha256:18253e7aeb7a6a12d1af7548a70349ab41da099addc0bfdc8f980c0a55624206",
"sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", "sha256:24d4137f3118900db02a2ec9a585d6dec2e79697fc90e84f19e5462dd1eeca44",
"sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", "sha256:2cbd0989e8b2bc7182f4eaa85145e1264a139da94b78adf9440be0d9ccce8898",
"sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", "sha256:3f1b0207dd0ec3167d5faec10d72368b7e1ba9eb2e3d54718963f6319254102b",
"sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", "sha256:634f38529aac216844df4df8632cdc1d7fff11c1ba2440c7299c38c1aa7a3e19",
"sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", "sha256:684134063c108b6a8af59c1869e09400a1c078336e79fa8cc1cbfb82e67d992a",
"sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", "sha256:7208cf3fa37e4c2d3ec9b49ac7721c457b793c64d548c4f7f2989ded0405f700",
"sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", "sha256:723581cbcfe89b83f86ed710b591520b6b148186b555dad2f84f717ad136f9e5",
"sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", "sha256:7ac685fe7c1103f5802e52a49f90f010dd768fd1cffd797fe01df2c674f3862a",
"sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", "sha256:7ed89815ea3483f92d6ca934ca8b2a8c35579453c1e2472a960a5086996981dd",
"sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", "sha256:88fbfc4703987ed17990a0a5b6f465447e8996cc4c4117db08afce9d0f75ff82",
"sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", "sha256:8fe3f1df20286d50dc5ab201cce4bf5c326e813d7fa39a62ed41bc01d46cd3ab",
"sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", "sha256:959cce516bacb775fb52ff122f26e23178c76ca708d68dc353e177cb7ee6f167",
"sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", "sha256:aaba2a47f0ce5c795d7caf46ff9c9ecc5d42b8ca5cecd11c0bf03d3df4626f9c",
"sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", "sha256:b224d7e73676a3ea27cd1a726591c7984db32ceb63a801e11d80669eb9964f37",
"sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", "sha256:b69f3f316711567f03eab907713a2c076f43fc84f92a6d7ce5ffc8c26e6709bc",
"sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", "sha256:c27e541b532d13a15fe969977c55753d0f187696280ba073273efb0c733e522b",
"sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", "sha256:cec962ac6db460a22ac78c1e316c7238b6979a9e369b9b1e3810af10c7445b1e",
"sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", "sha256:d2700d1593e5a03bc32d1a9e9fda9b136bf7e67e7ae4aeefcfeac5a5090c200e",
"sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" "sha256:d89a685ce8dd2c6869e9b9b884c9e0944cc74d67ca53a5bf4cd69f262fca0c3f",
"sha256:db58dcd824d67bc7f9735f3b0f402add8b33687994f0e822cbe16868d68aa2a1",
"sha256:ebe9e75f9a759e1019bdd2a823b51c8d4002bed006d60c025e3d6b9dff33ebaa",
"sha256:efa101918a6b332e1db0d3c69085217eb754b85124ab8ca94bba9b1e8d079f84",
"sha256:ff8709fcd3e49c41bc3da5720ab39882b76af8d15e268807e6d906f8c3a252fc"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==37.0.4" "version": "==38.0.0"
}, },
"dnspython": { "dnspython": {
"hashes": [ "hashes": [
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
], ],
"markers": "python_version >= '3.6' and python_version < '4'", "markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==2.2.1" "version": "==2.2.1"
}, },
"flask": { "flask": {
"hashes": [ "hashes": [
"sha256:3c604c48c3d5b4c63e72134044c0b4fe90ff01ef65280b9fe2d38c8860d99fe5", "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b",
"sha256:9c2b81b9b1edcc835af72d600f1955e713a065e7cb41d7e51ee762b449d9c65d" "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2.2.1" "version": "==2.2.2"
}, },
"future": { "future": {
"hashes": [ "hashes": [
@ -388,11 +401,11 @@
}, },
"marshmallow": { "marshmallow": {
"hashes": [ "hashes": [
"sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb", "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408",
"sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7" "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.17.0" "version": "==3.17.1"
}, },
"marshmallow-enum": { "marshmallow-enum": {
"hashes": [ "hashes": [
@ -412,19 +425,19 @@
}, },
"minikerberos": { "minikerberos": {
"hashes": [ "hashes": [
"sha256:3c383f67ebcf6f28105ed54f623a6a5c677a24e3f0c9ad69ed453f77e569d714", "sha256:c05cfcd846b1973b2b0d501e9e9fa2a263543d7762b052fc803fc1de849286a3",
"sha256:789f802263fa1882f701b123f6eec048b45cd731bf1b528870005daf07402047" "sha256:e912eb4bea899e1875707e7998001ed1047e1b32d5d7bf74d8b6137acf9614d3"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==0.2.20" "version": "==0.3.1"
}, },
"msldap": { "msldap": {
"hashes": [ "hashes": [
"sha256:ac8174ed7e0162eb64b3e9dfeff13b2e1021612d0a4b2cfc6b8e5bed7c00ffe0", "sha256:236eacd04b0d2886e71b2890ec6c67fc626e1b9812c93a0fe21e998697415927",
"sha256:e2c22a6e396b4d7d65d73863ed44612120e8e2570ff895b5421ddf6a350085bb" "sha256:ccb5c1f40de165141931659eb71d4bbad326665aaff7bf23dd0dccb410dfa78c"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==0.3.40" "version": "==0.4.0"
}, },
"netifaces": { "netifaces": {
"hashes": [ "hashes": [
@ -506,49 +519,49 @@
}, },
"prompt-toolkit": { "prompt-toolkit": {
"hashes": [ "hashes": [
"sha256:859b283c50bde45f5f97829f77a4674d1c1fcd88539364f1b28a37805cfd89c0", "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d",
"sha256:d8916d3f62a7b67ab353a952ce4ced6a1d2587dfe9ef8ebc30dd7c386751f289" "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148"
], ],
"markers": "python_full_version >= '3.6.2'", "markers": "python_full_version >= '3.6.2'",
"version": "==3.0.30" "version": "==3.0.31"
}, },
"psutil": { "psutil": {
"hashes": [ "hashes": [
"sha256:068935df39055bf27a29824b95c801c7a5130f118b806eee663cad28dca97685", "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97",
"sha256:0904727e0b0a038830b019551cf3204dd48ef5c6868adc776e06e93d615fc5fc", "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84",
"sha256:0f15a19a05f39a09327345bc279c1ba4a8cfb0172cc0d3c7f7d16c813b2e7d36", "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969",
"sha256:19f36c16012ba9cfc742604df189f2f28d2720e23ff7d1e81602dbe066be9fd1", "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf",
"sha256:20b27771b077dcaa0de1de3ad52d22538fe101f9946d6dc7869e6f694f079329", "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32",
"sha256:28976df6c64ddd6320d281128817f32c29b539a52bdae5e192537bc338a9ec81", "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12",
"sha256:29a442e25fab1f4d05e2655bb1b8ab6887981838d22effa2396d584b740194de", "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727",
"sha256:3054e923204b8e9c23a55b23b6df73a8089ae1d075cb0bf711d3e9da1724ded4", "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06",
"sha256:32c52611756096ae91f5d1499fe6c53b86f4a9ada147ee42db4991ba1520e574", "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c",
"sha256:3a76ad658641172d9c6e593de6fe248ddde825b5866464c3b2ee26c35da9d237", "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9",
"sha256:44d1826150d49ffd62035785a9e2c56afcea66e55b43b8b630d7706276e87f22", "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea",
"sha256:4b6750a73a9c4a4e689490ccb862d53c7b976a2a35c4e1846d049dcc3f17d83b", "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8",
"sha256:56960b9e8edcca1456f8c86a196f0c3d8e3e361320071c93378d41445ffd28b0", "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f",
"sha256:57f1819b5d9e95cdfb0c881a8a5b7d542ed0b7c522d575706a80bedc848c8954", "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c",
"sha256:58678bbadae12e0db55186dc58f2888839228ac9f41cc7848853539b70490021", "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d",
"sha256:645bd4f7bb5b8633803e0b6746ff1628724668681a434482546887d22c7a9537", "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339",
"sha256:799759d809c31aab5fe4579e50addf84565e71c1dc9f1c31258f159ff70d3f87", "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444",
"sha256:79c9108d9aa7fa6fba6e668b61b82facc067a6b81517cab34d07a84aa89f3df0", "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab",
"sha256:91c7ff2a40c373d0cc9121d54bc5f31c4fa09c346528e6a08d1845bce5771ffc", "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb",
"sha256:9272167b5f5fbfe16945be3db475b3ce8d792386907e673a209da686176552af", "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b",
"sha256:944c4b4b82dc4a1b805329c980f270f170fdc9945464223f2ec8e57563139cf4", "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8",
"sha256:a6a11e48cb93a5fa606306493f439b4aa7c56cb03fc9ace7f6bfa21aaf07c453", "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec",
"sha256:a8746bfe4e8f659528c5c7e9af5090c5a7d252f32b2e859c584ef7d8efb1e689", "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8",
"sha256:abd9246e4cdd5b554a2ddd97c157e292ac11ef3e7af25ac56b08b455c829dca8", "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d",
"sha256:b14ee12da9338f5e5b3a3ef7ca58b3cba30f5b66f7662159762932e6d0b8f680", "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34",
"sha256:b88f75005586131276634027f4219d06e0561292be8bd6bc7f2f00bdabd63c4e", "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85",
"sha256:c7be9d7f5b0d206f0bbc3794b8e16fb7dbc53ec9e40bbe8787c6f2d38efcf6c9", "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1",
"sha256:d2d006286fbcb60f0b391741f520862e9b69f4019b4d738a2a45728c7e952f1b", "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9",
"sha256:db417f0865f90bdc07fa30e1aadc69b6f4cad7f86324b02aa842034efe8d8c4d", "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1",
"sha256:e7e10454cb1ab62cc6ce776e1c135a64045a11ec4c6d254d3f7689c16eb3efd2", "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d",
"sha256:f65f9a46d984b8cd9b3750c2bdb419b2996895b005aefa6cbaba9a143b1ce2c5", "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5",
"sha256:fea896b54f3a4ae6f790ac1d017101252c93f6fe075d0e7571543510f11d2676" "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.9.1" "version": "==5.9.2"
}, },
"pyasn1": { "pyasn1": {
"hashes": [ "hashes": [
@ -632,44 +645,45 @@
}, },
"pydantic": { "pydantic": {
"hashes": [ "hashes": [
"sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44", "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42",
"sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d", "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624",
"sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84", "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e",
"sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555", "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559",
"sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7", "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709",
"sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131", "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9",
"sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8", "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d",
"sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3", "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52",
"sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56", "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda",
"sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0", "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912",
"sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4", "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c",
"sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453", "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525",
"sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044", "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe",
"sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e", "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41",
"sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15", "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b",
"sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb", "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283",
"sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001", "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965",
"sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d", "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c",
"sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3", "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410",
"sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e", "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5",
"sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f", "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116",
"sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c", "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98",
"sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b", "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f",
"sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8", "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644",
"sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567", "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13",
"sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979", "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd",
"sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326", "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254",
"sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb", "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6",
"sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f", "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488",
"sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa", "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5",
"sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747", "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c",
"sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801", "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1",
"sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55", "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a",
"sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08", "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2",
"sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76" "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d",
"sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.9.2" "version": "==1.10.2"
}, },
"pyinstaller": { "pyinstaller": {
"hashes": [ "hashes": [
@ -690,11 +704,11 @@
}, },
"pyinstaller-hooks-contrib": { "pyinstaller-hooks-contrib": {
"hashes": [ "hashes": [
"sha256:c4210fc50282c9c6a918e485e0bfae9405592390508e3be9fde19acc2213da56", "sha256:d1dd6ea059dc30e77813cc12a5efa8b1d228e7da8f5b884fe11775f946db1784",
"sha256:e46f099934dd4577fb1ddcf37a99fa04027c92f8f5291c8802f326345988d001" "sha256:e5edd4094175e78c178ef987b61be19efff6caa23d266ade456fc753e847f62e"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2022.8" "version": "==2022.10"
}, },
"pymssql": { "pymssql": {
"hashes": [ "hashes": [
@ -795,21 +809,19 @@
}, },
"pyspnego": { "pyspnego": {
"hashes": [ "hashes": [
"sha256:1ee612f20c843365fbc6cf7f95c526b4ee8795281641a9bb87083622a2f87939", "sha256:15cd6d3fc4d18b4b7f80259bfab1580c87dc9677d47e7cf801dad71dc23d1afc",
"sha256:284ca7a6218344bb90aeae02fb1d2ed73e5c991d6e4c16c0df404aeab5eb58a3", "sha256:210a2248060a2d789a333f7553a1a478d21812f675c507393169143cbf038d9b",
"sha256:416fd2d67e82b44ba3d2d9062485056e4dde3c141630170e9190379d6b19972a", "sha256:4e967f29c099c196cbf4622587cd27e8c61f20adf78b1d3007b72596e60c9f23",
"sha256:4c1be83e0aca12d64f5eec638259c77eaa8bf552c89ac69f0af2322a3be9afeb", "sha256:4fab51afb757be21d543ddf78aaeb83db600a7e7daec773568db90d4b7499a2c",
"sha256:4d1ea987b9c2539457235793014e0d9c5e4766da9e4e028d4b6b596cfbe53828", "sha256:53d30afbef1255cb1a8930c14604184b07f989b6ac295a1397eac8c27fd59d8b",
"sha256:725df2030e5d1b1155bb696eca3d684f403164da8e6a6b0bee3eb02f8748f72b", "sha256:838f875ee55004a274f6470460e62b7713237ae8b66a02680a2f31e43b3b5387",
"sha256:7320539f45da463029e12f3742102085d2a0343bfe77ac550c11d2fdac1d34f5", "sha256:b78a3370ace76209a52dc7816636a8c8437e323637eefe86a2193cc4ec352b3b",
"sha256:77b86002082f278c3f5935d8b428a0d0659ea709e305537294ba95fc49907339", "sha256:e08709c4e0838bf37d4ef8ceff2163a51abe2b071e285bb5774de5b73eab214f",
"sha256:aa93d94775d01bf70d16840426d1ddd58c11a6a71c4d0d1d7e529ad541fa0a60", "sha256:ea8570c5363e5dd359aaf599eac6f70116e0ada734ebe557e17cc608c8bb93fc",
"sha256:c2abca03b6d3c71d7ec9678c7b2220d99d9a29ef204b4c52549080169e586310", "sha256:fa2946ba5059f79d13cb8c47e83474de55569c16ed8f953cc47a24dda6f38f57"
"sha256:e6645107f200fb7bf6698512d04ea0790b292028861ce169eb97e5ad8eba14ed",
"sha256:f4784d9f8e9c50a36109d715a140150add1990fce16805a56217e8ccaf69d234"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.7'",
"version": "==0.5.3" "version": "==0.6.0"
}, },
"pywin32": { "pywin32": {
"hashes": [ "hashes": [
@ -858,11 +870,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca", "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82",
"sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6" "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==63.4.1" "version": "==65.3.0"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -874,11 +886,11 @@
}, },
"tqdm": { "tqdm": {
"hashes": [ "hashes": [
"sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4",
"sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==4.64.0" "version": "==4.64.1"
}, },
"twisted": { "twisted": {
"extras": [ "extras": [
@ -919,11 +931,11 @@
}, },
"unicrypto": { "unicrypto": {
"hashes": [ "hashes": [
"sha256:0487f9dd9009c326ee9531a79412ae18ad673425a1c800d64947b96fdeb04cdf", "sha256:4d1de0f0a379bb4c213302ae61d927eb8f98179bde9a0ffb8e120998a0c882a6",
"sha256:fab49ee41926bb31be49552aa135f7aedc04436b49c8fe326d7b6a823810575e" "sha256:9d5dd858ad5ad608fa524987b17e8855d64d6d2450ca0ca11638f4d92fc6c80b"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==0.0.8" "version": "==0.0.9"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
@ -942,11 +954,11 @@
}, },
"werkzeug": { "werkzeug": {
"hashes": [ "hashes": [
"sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a", "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f",
"sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a" "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2.2.1" "version": "==2.2.2"
}, },
"winacl": { "winacl": {
"hashes": [ "hashes": [
@ -956,14 +968,6 @@
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==0.1.3" "version": "==0.1.3"
}, },
"winsspi": {
"hashes": [
"sha256:2f5a8d2c4b9f459144426909e26a74e550512e23b6cf9af52c2a00003c7c3fdb",
"sha256:59b7c7595f91414528cfd80c6cfc77ec6f5e4e28185ebd6418f8368ddc7aca82"
],
"markers": "python_version >= '3.6'",
"version": "==0.0.10"
},
"winsys-3.x": { "winsys-3.x": {
"hashes": [ "hashes": [
"sha256:cef3df1dce2a5a71efa46446e6007ad9f7dbae31e83ffcc2ea3485c00c914cc3" "sha256:cef3df1dce2a5a71efa46446e6007ad9f7dbae31e83ffcc2ea3485c00c914cc3"
@ -976,6 +980,7 @@
"sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942", "sha256:1d6b085e5c445141c475476000b661f60fff1aaa19f76bf82b7abb92e0ff4942",
"sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6" "sha256:b6a6be5711b1b6c8d55bda7a8befd75c48c12b770b9d227d31c1737dbf0d40a6"
], ],
"index": "pypi",
"markers": "sys_platform == 'win32'", "markers": "sys_platform == 'win32'",
"version": "==1.5.1" "version": "==1.5.1"
}, },

View File

@ -55,22 +55,22 @@ class MimikatzCredentialCollector(ICredentialCollector):
identity = None identity = None
if wc.username: if wc.username:
identity = Username(wc.username) identity = Username(username=wc.username)
if wc.password: if wc.password:
password = Password(wc.password) password = Password(password=wc.password)
credentials.append(Credentials(identity, password)) credentials.append(Credentials(identity=identity, secret=password))
if wc.lm_hash: if wc.lm_hash:
lm_hash = LMHash(lm_hash=wc.lm_hash) lm_hash = LMHash(lm_hash=wc.lm_hash)
credentials.append(Credentials(identity, lm_hash)) credentials.append(Credentials(identity=identity, secret=lm_hash))
if wc.ntlm_hash: if wc.ntlm_hash:
ntlm_hash = NTHash(nt_hash=wc.ntlm_hash) ntlm_hash = NTHash(nt_hash=wc.ntlm_hash)
credentials.append(Credentials(identity, ntlm_hash)) credentials.append(Credentials(identity=identity, secret=ntlm_hash))
if len(credentials) == 0 and identity is not None: if len(credentials) == 0 and identity is not None:
credentials.append(Credentials(identity, None)) credentials.append(Credentials(identity=identity, secret=None))
return credentials return credentials

View File

@ -149,7 +149,7 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]:
secret = None secret = None
if info.get("name", ""): if info.get("name", ""):
identity = Username(info["name"]) identity = Username(username=info["name"])
ssh_keypair = {} ssh_keypair = {}
for key in ["public_key", "private_key"]: for key in ["public_key", "private_key"]:
@ -158,11 +158,12 @@ def to_credentials(ssh_info: Iterable[Dict]) -> Sequence[Credentials]:
if len(ssh_keypair): if len(ssh_keypair):
secret = SSHKeypair( secret = SSHKeypair(
ssh_keypair.get("private_key", ""), ssh_keypair.get("public_key", "") private_key=ssh_keypair.get("private_key", ""),
public_key=ssh_keypair.get("public_key", ""),
) )
if any([identity, secret]): if any([identity, secret]):
ssh_credentials.append(Credentials(identity, secret)) ssh_credentials.append(Credentials(identity=identity, secret=secret))
return ssh_credentials return ssh_credentials

View File

@ -1,7 +1,8 @@
import logging import logging
from typing import Any, Iterable from typing import Any, Iterable
from common.credentials import CredentialComponentType, Credentials, ICredentialComponent from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username
from common.credentials.credentials import Identity, Secret
from infection_monkey.custom_types import PropagationCredentials from infection_monkey.custom_types import PropagationCredentials
from infection_monkey.i_control_channel import IControlChannel from infection_monkey.i_control_channel import IControlChannel
from infection_monkey.utils.decorators import request_cache from infection_monkey.utils.decorators import request_cache
@ -43,18 +44,18 @@ class AggregatingPropagationCredentialsRepository(IPropagationCredentialsReposit
if credentials.secret: if credentials.secret:
self._add_secret(credentials.secret) self._add_secret(credentials.secret)
def _add_identity(self, identity: ICredentialComponent): def _add_identity(self, identity: Identity):
if identity.credential_type is CredentialComponentType.USERNAME: if isinstance(identity, Username):
self._stored_credentials.setdefault("exploit_user_list", set()).add(identity.username) self._stored_credentials.setdefault("exploit_user_list", set()).add(identity.username)
def _add_secret(self, secret: ICredentialComponent): def _add_secret(self, secret: Secret):
if secret.credential_type is CredentialComponentType.PASSWORD: if isinstance(secret, Password):
self._stored_credentials.setdefault("exploit_password_list", set()).add(secret.password) self._stored_credentials.setdefault("exploit_password_list", set()).add(secret.password)
elif secret.credential_type is CredentialComponentType.LM_HASH: elif isinstance(secret, LMHash):
self._stored_credentials.setdefault("exploit_lm_hash_list", set()).add(secret.lm_hash) self._stored_credentials.setdefault("exploit_lm_hash_list", set()).add(secret.lm_hash)
elif secret.credential_type is CredentialComponentType.NT_HASH: elif isinstance(secret, NTHash):
self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).add(secret.nt_hash) self._stored_credentials.setdefault("exploit_ntlm_hash_list", set()).add(secret.nt_hash)
elif secret.credential_type is CredentialComponentType.SSH_KEYPAIR: elif isinstance(secret, SSHKeypair):
self._set_attribute( self._set_attribute(
"exploit_ssh_keys", "exploit_ssh_keys",
[{"public_key": secret.public_key, "private_key": secret.private_key}], [{"public_key": secret.public_key, "private_key": secret.private_key}],

View File

@ -6,6 +6,7 @@ from typing import Sequence, Tuple
import pymssql import pymssql
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
from common.credentials.credentials import get_plaintext
from common.utils.exceptions import FailedExploitationError from common.utils.exceptions import FailedExploitationError
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path from infection_monkey.exploit.tools.helpers import get_agent_dst_path
@ -111,7 +112,7 @@ class MSSQLExploiter(HostExploiter):
conn = pymssql.connect( conn = pymssql.connect(
host, host,
user, user,
password, get_plaintext(password),
port=port, port=port,
login_timeout=self.LOGIN_TIMEOUT, login_timeout=self.LOGIN_TIMEOUT,
timeout=self.QUERY_TIMEOUT, timeout=self.QUERY_TIMEOUT,

View File

@ -11,6 +11,7 @@ from pypsrp.powershell import PowerShell, RunspacePool
from typing_extensions import Protocol from typing_extensions import Protocol
from urllib3 import connectionpool from urllib3 import connectionpool
from common.credentials.credentials import get_plaintext
from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions from infection_monkey.exploit.powershell_utils.auth_options import AuthOptions
from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
@ -42,14 +43,16 @@ def format_password(credentials: Credentials) -> Optional[str]:
if credentials.secret_type == SecretType.CACHED: if credentials.secret_type == SecretType.CACHED:
return None return None
plaintext_secret = get_plaintext(credentials.secret)
if credentials.secret_type == SecretType.PASSWORD: if credentials.secret_type == SecretType.PASSWORD:
return credentials.secret return plaintext_secret
if credentials.secret_type == SecretType.LM_HASH: if credentials.secret_type == SecretType.LM_HASH:
return f"{credentials.secret}:00000000000000000000000000000000" return f"{plaintext_secret}:00000000000000000000000000000000"
if credentials.secret_type == SecretType.NT_HASH: if credentials.secret_type == SecretType.NT_HASH:
return f"00000000000000000000000000000000:{credentials.secret}" return f"00000000000000000000000000000000:{plaintext_secret}"
raise ValueError(f"Unknown secret type {credentials.secret_type}") raise ValueError(f"Unknown secret type {credentials.secret_type}")

View File

@ -4,6 +4,7 @@ from impacket.dcerpc.v5 import scmr, transport
from impacket.dcerpc.v5.scmr import DCERPCSessionError from impacket.dcerpc.v5.scmr import DCERPCSessionError
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT
from common.credentials.credentials import get_plaintext
from common.utils.attack_utils import ScanStatus, UsageEnum from common.utils.attack_utils import ScanStatus, UsageEnum
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path from infection_monkey.exploit.tools.helpers import get_agent_dst_path
@ -107,7 +108,14 @@ class SMBExploiter(HostExploiter):
rpctransport.setRemoteHost(self.host.ip_addr) rpctransport.setRemoteHost(self.host.ip_addr)
if hasattr(rpctransport, "set_credentials"): if hasattr(rpctransport, "set_credentials"):
# This method exists only for selected protocol sequences. # This method exists only for selected protocol sequences.
rpctransport.set_credentials(user, password, "", lm_hash, ntlm_hash, None) rpctransport.set_credentials(
user,
get_plaintext(password),
"",
get_plaintext(lm_hash),
get_plaintext(ntlm_hash),
None,
)
rpctransport.set_kerberos(SMBExploiter.USE_KERBEROS) rpctransport.set_kerberos(SMBExploiter.USE_KERBEROS)
scmr_rpc = rpctransport.get_dce_rpc() scmr_rpc = rpctransport.get_dce_rpc()
@ -116,7 +124,8 @@ class SMBExploiter(HostExploiter):
scmr_rpc.connect() scmr_rpc.connect()
except Exception as exc: except Exception as exc:
logger.debug( logger.debug(
f"Can't connect to SCM on exploited machine {self.host}, port {port} : {exc}" f"Can't connect to SCM on exploited machine {self.host}, port {port} : "
f"{exc}"
) )
continue continue

View File

@ -5,6 +5,7 @@ from pathlib import PurePath
import paramiko import paramiko
from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT
from common.credentials.credentials import get_plaintext
from common.utils import Timer from common.utils import Timer
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from common.utils.exceptions import FailedExploitationError from common.utils.exceptions import FailedExploitationError
@ -59,7 +60,7 @@ class SSHExploiter(HostExploiter):
for user, ssh_key_pair in ssh_key_pairs_iterator: for user, ssh_key_pair in ssh_key_pairs_iterator:
# Creating file-like private key for paramiko # Creating file-like private key for paramiko
pkey = io.StringIO(ssh_key_pair["private_key"]) pkey = io.StringIO(get_plaintext(ssh_key_pair["private_key"]))
ssh_string = "%s@%s" % (user, self.host.ip_addr) ssh_string = "%s@%s" % (user, self.host.ip_addr)
ssh = paramiko.SSHClient() ssh = paramiko.SSHClient()

View File

@ -8,7 +8,9 @@ from typing import Optional
from impacket.dcerpc.v5 import srvs, transport from impacket.dcerpc.v5 import srvs, transport
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21 from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMB_DIALECT, SMBConnection from impacket.smbconnection import SMB_DIALECT, SMBConnection
from pydantic import SecretStr
from common.credentials.credentials import get_plaintext
from common.utils.attack_utils import ScanStatus from common.utils.attack_utils import ScanStatus
from infection_monkey.network.tools import get_interface_to_target from infection_monkey.network.tools import get_interface_to_target
from infection_monkey.telemetry.attack.t1105_telem import T1105Telem from infection_monkey.telemetry.attack.t1105_telem import T1105Telem
@ -26,8 +28,8 @@ class SmbTools(object):
host, host,
agent_file: BytesIO, agent_file: BytesIO,
dst_path: PurePath, dst_path: PurePath,
username, username: str,
password, password: SecretStr,
lm_hash="", lm_hash="",
ntlm_hash="", ntlm_hash="",
timeout=30, timeout=30,
@ -190,7 +192,9 @@ class SmbTools(object):
return remote_full_path return remote_full_path
@staticmethod @staticmethod
def new_smb_connection(host, username, password, lm_hash="", ntlm_hash="", timeout=30): def new_smb_connection(
host, username: str, password: SecretStr, lm_hash="", ntlm_hash="", timeout=30
):
try: try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445) smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception as exc: except Exception as exc:
@ -212,7 +216,13 @@ class SmbTools(object):
# we know this should work because the WMI connection worked # we know this should work because the WMI connection worked
try: try:
smb.login(username, password, "", lm_hash, ntlm_hash) smb.login(
username,
get_plaintext(password),
"",
get_plaintext(lm_hash),
get_plaintext(ntlm_hash),
)
except Exception as exc: except Exception as exc:
logger.error(f'Error while logging into {host} using user "{username}": {exc}') logger.error(f'Error while logging into {host} using user "{username}": {exc}')
return None, dialect return None, dialect

View File

@ -5,6 +5,7 @@ import traceback
from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5.rpcrt import DCERPCException
from common.credentials.credentials import get_plaintext
from infection_monkey.exploit.HostExploiter import HostExploiter from infection_monkey.exploit.HostExploiter import HostExploiter
from infection_monkey.exploit.tools.helpers import get_agent_dst_path from infection_monkey.exploit.tools.helpers import get_agent_dst_path
from infection_monkey.exploit.tools.smb_tools import SmbTools from infection_monkey.exploit.tools.smb_tools import SmbTools
@ -44,7 +45,14 @@ class WmiExploiter(HostExploiter):
wmi_connection = WmiTools.WmiConnection() wmi_connection = WmiTools.WmiConnection()
try: try:
wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash) wmi_connection.connect(
self.host,
user,
get_plaintext(password),
None,
get_plaintext(lm_hash),
get_plaintext(ntlm_hash),
)
except AccessDeniedException: except AccessDeniedException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash) self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
logger.debug(f"Failed connecting to {self.host} using WMI") logger.debug(f"Failed connecting to {self.host} using WMI")

View File

@ -299,8 +299,8 @@ class ZerologonExploiter(HostExploiter):
self, user: str, lmhash: str, nthash: str self, user: str, lmhash: str, nthash: str
) -> None: ) -> None:
extracted_credentials = [ extracted_credentials = [
Credentials(Username(user), LMHash(lmhash)), Credentials(identity=Username(username=user), secret=LMHash(lm_hash=lmhash)),
Credentials(Username(user), NTHash(nthash)), Credentials(identity=Username(username=user), secret=NTHash(nt_hash=nthash)),
] ]
self.telemetry_messenger.send_telemetry(CredentialsTelem(extracted_credentials)) self.telemetry_messenger.send_telemetry(CredentialsTelem(extracted_credentials))

View File

@ -121,7 +121,7 @@ class ControlChannel(IControlChannel):
) )
response.raise_for_status() response.raise_for_status()
return [Credentials.from_mapping(credentials) for credentials in response.json()] return [Credentials(**credentials) for credentials in response.json()]
except ( except (
requests.exceptions.JSONDecodeError, requests.exceptions.JSONDecodeError,
requests.exceptions.ConnectionError, requests.exceptions.ConnectionError,

View File

@ -1,4 +1,3 @@
import json
from typing import Iterable from typing import Iterable
from common.common_consts.telem_categories import TelemCategoryEnum from common.common_consts.telem_categories import TelemCategoryEnum
@ -24,4 +23,4 @@ class CredentialsTelem(BaseTelem):
super().send(log_data=False) super().send(log_data=False)
def get_data(self): def get_data(self):
return [json.loads(Credentials.to_json(c)) for c in self._credentials] return [c.dict(simplify=True) for c in self._credentials]

View File

@ -1,5 +1,7 @@
import json import json
from pydantic import BaseModel, SecretBytes, SecretStr
from common import OperatingSystem from common import OperatingSystem
@ -7,4 +9,8 @@ class TelemetryJSONEncoder(json.JSONEncoder):
def default(self, obj): def default(self, obj):
if isinstance(obj, OperatingSystem): if isinstance(obj, OperatingSystem):
return obj.name return obj.name
if issubclass(type(obj), BaseModel):
return obj.dict(simplify=True)
if issubclass(type(obj), (SecretStr, SecretBytes)):
return obj.get_secret_value()
return json.JSONEncoder.default(self, obj) return json.JSONEncoder.default(self, obj)

View File

@ -150,11 +150,11 @@
}, },
"charset-normalizer": { "charset-normalizer": {
"hashes": [ "hashes": [
"sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2.1.0" "version": "==2.1.1"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@ -174,31 +174,35 @@
}, },
"cryptography": { "cryptography": {
"hashes": [ "hashes": [
"sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59", "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a",
"sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596", "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f",
"sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3", "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0",
"sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5", "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407",
"sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab", "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7",
"sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884", "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6",
"sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82", "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153",
"sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b", "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750",
"sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441", "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad",
"sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa", "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6",
"sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d", "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b",
"sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b", "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5",
"sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a", "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a",
"sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6", "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d",
"sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157", "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d",
"sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280", "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294",
"sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282", "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0",
"sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67", "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a",
"sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8", "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac",
"sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046", "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61",
"sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327", "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013",
"sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9" "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e",
"sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb",
"sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9",
"sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd",
"sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"
], ],
"index": "pypi", "index": "pypi",
"version": "==37.0.4" "version": "==38.0.1"
}, },
"dpath": { "dpath": {
"hashes": [ "hashes": [
@ -210,19 +214,19 @@
}, },
"flask": { "flask": {
"hashes": [ "hashes": [
"sha256:3c604c48c3d5b4c63e72134044c0b4fe90ff01ef65280b9fe2d38c8860d99fe5", "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b",
"sha256:9c2b81b9b1edcc835af72d600f1955e713a065e7cb41d7e51ee762b449d9c65d" "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.2.1" "version": "==2.2.2"
}, },
"flask-jwt-extended": { "flask-jwt-extended": {
"hashes": [ "hashes": [
"sha256:188907ea9332bdd123a95a457e7487556770480264ce3b78c8835b4347e324cc", "sha256:62b521d75494c290a646ae8acc77123721e4364790f1e64af0038d823961fbf0",
"sha256:a2571df484c5635ad996d364242ec28fc69f386915cd69b1842639712b84c36d" "sha256:a85eebfa17c339a7260c4643475af444784ba6de5588adda67406f0a75599553"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.4.3" "version": "==4.4.4"
}, },
"flask-pymongo": { "flask-pymongo": {
"hashes": [ "hashes": [
@ -288,64 +292,63 @@
}, },
"greenlet": { "greenlet": {
"hashes": [ "hashes": [
"sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3", "sha256:0118817c9341ef2b0f75f5af79ac377e4da6ff637e5ee4ac91802c0e379dadb4",
"sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711", "sha256:048d2bed76c2aa6de7af500ae0ea51dd2267aec0e0f2a436981159053d0bc7cc",
"sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd", "sha256:07c58e169bbe1e87b8bbf15a5c1b779a7616df9fd3e61cadc9d691740015b4f8",
"sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073", "sha256:095a980288fe05adf3d002fbb180c99bdcf0f930e220aa66fcd56e7914a38202",
"sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708", "sha256:0b181e9aa6cb2f5ec0cacc8cee6e5a3093416c841ba32c185c30c160487f0380",
"sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67", "sha256:1626185d938d7381631e48e6f7713e8d4b964be246073e1a1d15c2f061ac9f08",
"sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23", "sha256:184416e481295832350a4bf731ba619a92f5689bf5d0fa4341e98b98b1265bd7",
"sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1", "sha256:1dd51d2650e70c6c4af37f454737bf4a11e568945b27f74b471e8e2a9fd21268",
"sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08", "sha256:1ec2779774d8e42ed0440cf8bc55540175187e8e934f2be25199bf4ed948cd9e",
"sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd", "sha256:2cf45e339cabea16c07586306a31cfcc5a3b5e1626d365714d283732afed6809",
"sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2", "sha256:2fb0aa7f6996879551fd67461d5d3ab0c3c0245da98be90c89fcb7a18d437403",
"sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa", "sha256:44b4817c34c9272c65550b788913620f1fdc80362b209bc9d7dd2f40d8793080",
"sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8", "sha256:466ce0928e33421ee84ae04c4ac6f253a3a3e6b8d600a79bd43fd4403e0a7a76",
"sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40", "sha256:4f166b4aca8d7d489e82d74627a7069ab34211ef5ebb57c300ec4b9337b60fc0",
"sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab", "sha256:510c3b15587afce9800198b4b142202b323bf4b4b5f9d6c79cb9a35e5e3c30d2",
"sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6", "sha256:5b756e6730ea59b2745072e28ad27f4c837084688e6a6b3633c8b1e509e6ae0e",
"sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc", "sha256:5fbe1ab72b998ca77ceabbae63a9b2e2dc2d963f4299b9b278252ddba142d3f1",
"sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b", "sha256:6200a11f003ec26815f7e3d2ded01b43a3810be3528dd760d2f1fa777490c3cd",
"sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e", "sha256:65ad1a7a463a2a6f863661329a944a5802c7129f7ad33583dcc11069c17e622c",
"sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963", "sha256:694ffa7144fa5cc526c8f4512665003a39fa09ef00d19bbca5c8d3406db72fbe",
"sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3", "sha256:6f5d4b2280ceea76c55c893827961ed0a6eadd5a584a7c4e6e6dd7bc10dfdd96",
"sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d", "sha256:7532a46505470be30cbf1dbadb20379fb481244f1ca54207d7df3bf0bbab6a20",
"sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d", "sha256:76a53bfa10b367ee734b95988bd82a9a5f0038a25030f9f23bbbc005010ca600",
"sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe", "sha256:77e41db75f9958f2083e03e9dd39da12247b3430c92267df3af77c83d8ff9eed",
"sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28", "sha256:7a43bbfa9b6cfdfaeefbd91038dde65ea2c421dc387ed171613df340650874f2",
"sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3", "sha256:7b41d19c0cfe5c259fe6c539fd75051cd39a5d33d05482f885faf43f7f5e7d26",
"sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e", "sha256:7c5227963409551ae4a6938beb70d56bf1918c554a287d3da6853526212fbe0a",
"sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c", "sha256:870a48007872d12e95a996fca3c03a64290d3ea2e61076aa35d3b253cf34cd32",
"sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d", "sha256:88b04e12c9b041a1e0bcb886fec709c488192638a9a7a3677513ac6ba81d8e79",
"sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0", "sha256:8c287ae7ac921dfde88b1c125bd9590b7ec3c900c2d3db5197f1286e144e712b",
"sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497", "sha256:903fa5716b8fbb21019268b44f73f3748c41d1a30d71b4a49c84b642c2fed5fa",
"sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee", "sha256:9537e4baf0db67f382eb29255a03154fcd4984638303ff9baaa738b10371fa57",
"sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713", "sha256:9951dcbd37850da32b2cb6e391f621c1ee456191c6ae5528af4a34afe357c30e",
"sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58", "sha256:9b2f7d0408ddeb8ea1fd43d3db79a8cefaccadd2a812f021333b338ed6b10aba",
"sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a", "sha256:9c88e134d51d5e82315a7c32b914a58751b7353eb5268dbd02eabf020b4c4700",
"sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06", "sha256:9fae214f6c43cd47f7bef98c56919b9222481e833be2915f6857a1e9e8a15318",
"sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88", "sha256:a3a669f11289a8995d24fbfc0e63f8289dd03c9aaa0cc8f1eab31d18ca61a382",
"sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965", "sha256:aa741c1a8a8cc25eb3a3a01a62bdb5095a773d8c6a86470bde7f607a447e7905",
"sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f", "sha256:b0877a9a2129a2c56a2eae2da016743db7d9d6a05d5e1c198f1b7808c602a30e",
"sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4", "sha256:bcb6c6dd1d6be6d38d6db283747d07fda089ff8c559a835236560a4410340455",
"sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5", "sha256:caff52cb5cd7626872d9696aee5b794abe172804beb7db52eed1fd5824b63910",
"sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c", "sha256:cbc1eb55342cbac8f7ec159088d54e2cfdd5ddf61c87b8bbe682d113789331b2",
"sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a", "sha256:cd16a89efe3a003029c87ff19e9fba635864e064da646bc749fc1908a4af18f3",
"sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1", "sha256:ce5b64dfe8d0cca407d88b0ee619d80d4215a2612c1af8c98a92180e7109f4b5",
"sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43", "sha256:d58a5a71c4c37354f9e0c24c9c8321f0185f6945ef027460b809f4bb474bfe41",
"sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627", "sha256:db41f3845eb579b544c962864cce2c2a0257fe30f0f1e18e51b1e8cbb4e0ac6d",
"sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b", "sha256:db5b25265010a1b3dca6a174a443a0ed4c4ab12d5e2883a11c97d6e6d59b12f9",
"sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168", "sha256:dd0404d154084a371e6d2bafc787201612a1359c2dee688ae334f9118aa0bf47",
"sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d", "sha256:de431765bd5fe62119e0bc6bc6e7b17ac53017ae1782acf88fcf6b7eae475a49",
"sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5", "sha256:df02fdec0c533301497acb0bc0f27f479a3a63dcdc3a099ae33a902857f07477",
"sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478", "sha256:e8533f5111704d75de3139bf0b8136d3a6c1642c55c067866fa0a51c2155ee33",
"sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf", "sha256:f2f908239b7098799b8845e5936c2ccb91d8c2323be02e82f8dcb4a80dcf4a25",
"sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce", "sha256:f8bfd36f368efe0ab2a6aa3db7f14598aac454b06849fb633b762ddbede1db90",
"sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c", "sha256:ffe73f9e7aea404722058405ff24041e59d31ca23d1da0895af48050a07b6932"
"sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b"
], ],
"markers": "platform_python_implementation == 'CPython'", "markers": "platform_python_implementation == 'CPython'",
"version": "==1.1.2" "version": "==1.1.3"
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
@ -451,11 +454,11 @@
}, },
"marshmallow": { "marshmallow": {
"hashes": [ "hashes": [
"sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb", "sha256:1172ce82765bf26c24a3f9299ed6dbeeca4d213f638eaa39a37772656d7ce408",
"sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7" "sha256:48e2d88d4ab431ad5a17c25556d9da529ea6e966876f2a38d274082e270287f0"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.17.0" "version": "==3.17.1"
}, },
"marshmallow-enum": { "marshmallow-enum": {
"hashes": [ "hashes": [
@ -542,44 +545,45 @@
}, },
"pydantic": { "pydantic": {
"hashes": [ "hashes": [
"sha256:1061c6ee6204f4f5a27133126854948e3b3d51fcc16ead2e5d04378c199b2f44", "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42",
"sha256:19b5686387ea0d1ea52ecc4cffb71abb21702c5e5b2ac626fd4dbaa0834aa49d", "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624",
"sha256:2bd446bdb7755c3a94e56d7bdfd3ee92396070efa8ef3a34fab9579fe6aa1d84", "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e",
"sha256:328558c9f2eed77bd8fffad3cef39dbbe3edc7044517f4625a769d45d4cf7555", "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559",
"sha256:32e0b4fb13ad4db4058a7c3c80e2569adbd810c25e6ca3bbd8b2a9cc2cc871d7", "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709",
"sha256:3ee0d69b2a5b341fc7927e92cae7ddcfd95e624dfc4870b32a85568bd65e6131", "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9",
"sha256:4aafd4e55e8ad5bd1b19572ea2df546ccace7945853832bb99422a79c70ce9b8", "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d",
"sha256:4b3946f87e5cef3ba2e7bd3a4eb5a20385fe36521d6cc1ebf3c08a6697c6cfb3", "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52",
"sha256:4de71c718c9756d679420c69f216776c2e977459f77e8f679a4a961dc7304a56", "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda",
"sha256:5565a49effe38d51882cb7bac18bda013cdb34d80ac336428e8908f0b72499b0", "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912",
"sha256:5803ad846cdd1ed0d97eb00292b870c29c1f03732a010e66908ff48a762f20e4", "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c",
"sha256:5da164119602212a3fe7e3bc08911a89db4710ae51444b4224c2382fd09ad453", "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525",
"sha256:615661bfc37e82ac677543704437ff737418e4ea04bef9cf11c6d27346606044", "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe",
"sha256:78a4d6bdfd116a559aeec9a4cfe77dda62acc6233f8b56a716edad2651023e5e", "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41",
"sha256:7d0f183b305629765910eaad707800d2f47c6ac5bcfb8c6397abdc30b69eeb15", "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b",
"sha256:7ead3cd020d526f75b4188e0a8d71c0dbbe1b4b6b5dc0ea775a93aca16256aeb", "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283",
"sha256:84d76ecc908d917f4684b354a39fd885d69dd0491be175f3465fe4b59811c001", "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965",
"sha256:8cb0bc509bfb71305d7a59d00163d5f9fc4530f0881ea32c74ff4f74c85f3d3d", "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c",
"sha256:91089b2e281713f3893cd01d8e576771cd5bfdfbff5d0ed95969f47ef6d676c3", "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410",
"sha256:9c9e04a6cdb7a363d7cb3ccf0efea51e0abb48e180c0d31dca8d247967d85c6e", "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5",
"sha256:a8c5360a0297a713b4123608a7909e6869e1b56d0e96eb0d792c27585d40757f", "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116",
"sha256:afacf6d2a41ed91fc631bade88b1d319c51ab5418870802cedb590b709c5ae3c", "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98",
"sha256:b34ba24f3e2d0b39b43f0ca62008f7ba962cff51efa56e64ee25c4af6eed987b", "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f",
"sha256:bd67cb2c2d9602ad159389c29e4ca964b86fa2f35c2faef54c3eb28b4efd36c8", "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644",
"sha256:c0f5e142ef8217019e3eef6ae1b6b55f09a7a15972958d44fbd228214cede567", "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13",
"sha256:cdb4272678db803ddf94caa4f94f8672e9a46bae4a44f167095e4d06fec12979", "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd",
"sha256:d70916235d478404a3fa8c997b003b5f33aeac4686ac1baa767234a0f8ac2326", "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254",
"sha256:d8ce3fb0841763a89322ea0432f1f59a2d3feae07a63ea2c958b2315e1ae8adb", "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6",
"sha256:e0b214e57623a535936005797567231a12d0da0c29711eb3514bc2b3cd008d0f", "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488",
"sha256:e631c70c9280e3129f071635b81207cad85e6c08e253539467e4ead0e5b219aa", "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5",
"sha256:e78578f0c7481c850d1c969aca9a65405887003484d24f6110458fb02cca7747", "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c",
"sha256:f0ca86b525264daa5f6b192f216a0d1e860b7383e3da1c65a1908f9c02f42801", "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1",
"sha256:f1a68f4f65a9ee64b6ccccb5bf7e17db07caebd2730109cb8a95863cfa9c4e55", "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a",
"sha256:fafe841be1103f340a24977f61dee76172e4ae5f647ab9e7fd1e1fca51524f08", "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2",
"sha256:ff68fc85355532ea77559ede81f35fff79a6a5543477e168ab3a381887caea76" "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d",
"sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.9.2" "version": "==1.10.2"
}, },
"pyinstaller": { "pyinstaller": {
"hashes": [ "hashes": [
@ -600,11 +604,11 @@
}, },
"pyinstaller-hooks-contrib": { "pyinstaller-hooks-contrib": {
"hashes": [ "hashes": [
"sha256:c4210fc50282c9c6a918e485e0bfae9405592390508e3be9fde19acc2213da56", "sha256:d1dd6ea059dc30e77813cc12a5efa8b1d228e7da8f5b884fe11775f946db1784",
"sha256:e46f099934dd4577fb1ddcf37a99fa04027c92f8f5291c8802f326345988d001" "sha256:e5edd4094175e78c178ef987b61be19efff6caa23d266ade456fc753e847f62e"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==2022.8" "version": "==2022.10"
}, },
"pyjwt": { "pyjwt": {
"hashes": [ "hashes": [
@ -779,10 +783,10 @@
}, },
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
"sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
], ],
"version": "==2022.1" "version": "==2022.2.1"
}, },
"pywin32": { "pywin32": {
"hashes": [ "hashes": [
@ -855,11 +859,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca", "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82",
"sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6" "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==63.4.1" "version": "==65.3.0"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -874,24 +878,24 @@
"sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
"sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version < '3.8'",
"version": "==4.3.0" "version": "==4.3.0"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc", "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e",
"sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a" "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
"version": "==1.26.11" "version": "==1.26.12"
}, },
"werkzeug": { "werkzeug": {
"hashes": [ "hashes": [
"sha256:4d7013ef96fd197d1cdeb03e066c6c5a491ccb44758a5b2b91137319383e5a5a", "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f",
"sha256:7e1db6a5ba6b9a8be061e47e900456355b8714c0f238b0313f53afce1a55a79a" "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.2.1" "version": "==2.2.2"
}, },
"wirerope": { "wirerope": {
"hashes": [ "hashes": [
@ -980,13 +984,6 @@
], ],
"version": "==0.7.12" "version": "==0.7.12"
}, },
"atomicwrites": {
"hashes": [
"sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"
],
"markers": "sys_platform == 'win32'",
"version": "==1.4.1"
},
"attrs": { "attrs": {
"hashes": [ "hashes": [
"sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
@ -1000,7 +997,7 @@
"sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51", "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51",
"sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb" "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==2.10.3" "version": "==2.10.3"
}, },
"black": { "black": {
@ -1042,11 +1039,11 @@
}, },
"charset-normalizer": { "charset-normalizer": {
"hashes": [ "hashes": [
"sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5", "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413" "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
], ],
"markers": "python_version >= '3.6'", "markers": "python_version >= '3.6'",
"version": "==2.1.0" "version": "==2.1.1"
}, },
"click": { "click": {
"hashes": [ "hashes": [
@ -1066,57 +1063,66 @@
}, },
"coverage": { "coverage": {
"hashes": [ "hashes": [
"sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32", "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2",
"sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7", "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820",
"sha256:147605e1702d996279bb3cc3b164f408698850011210d133a2cb96a73a2f7996", "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827",
"sha256:24b04d305ea172ccb21bee5bacd559383cba2c6fcdef85b7701cf2de4188aa55", "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3",
"sha256:25b7ec944f114f70803d6529394b64f8749e93cbfac0fe6c5ea1b7e6c14e8a46", "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d",
"sha256:2b20286c2b726f94e766e86a3fddb7b7e37af5d0c635bdfa7e4399bc523563de", "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145",
"sha256:2dff52b3e7f76ada36f82124703f4953186d9029d00d6287f17c68a75e2e6039", "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875",
"sha256:2f8553878a24b00d5ab04b7a92a2af50409247ca5c4b7a2bf4eabe94ed20d3ee", "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2",
"sha256:3def6791adf580d66f025223078dc84c64696a26f174131059ce8e91452584e1", "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74",
"sha256:422fa44070b42fef9fb8dabd5af03861708cdd6deb69463adc2130b7bf81332f", "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f",
"sha256:4f89d8e03c8a3757aae65570d14033e8edf192ee9298303db15955cadcff0c63", "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c",
"sha256:5336e0352c0b12c7e72727d50ff02557005f79a0b8dcad9219c7c4940a930083", "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973",
"sha256:54d8d0e073a7f238f0666d3c7c0d37469b2aa43311e4024c925ee14f5d5a1cbe", "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1",
"sha256:5ef42e1db047ca42827a85e34abe973971c635f83aed49611b7f3ab49d0130f0", "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782",
"sha256:5f65e5d3ff2d895dab76b1faca4586b970a99b5d4b24e9aafffc0ce94a6022d6", "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0",
"sha256:6c3ccfe89c36f3e5b9837b9ee507472310164f352c9fe332120b764c9d60adbe", "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760",
"sha256:6d0b48aff8e9720bdec315d67723f0babd936a7211dc5df453ddf76f89c59933", "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a",
"sha256:6fe75dcfcb889b6800f072f2af5a331342d63d0c1b3d2bf0f7b4f6c353e8c9c0", "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3",
"sha256:79419370d6a637cb18553ecb25228893966bd7935a9120fa454e7076f13b627c", "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7",
"sha256:7bb00521ab4f99fdce2d5c05a91bddc0280f0afaee0e0a00425e28e209d4af07", "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a",
"sha256:80db4a47a199c4563d4a25919ff29c97c87569130375beca3483b41ad5f698e8", "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f",
"sha256:866ebf42b4c5dbafd64455b0a1cd5aa7b4837a894809413b930026c91e18090b", "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e",
"sha256:8af6c26ba8df6338e57bedbf916d76bdae6308e57fc8f14397f03b5da8622b4e", "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86",
"sha256:a13772c19619118903d65a91f1d5fea84be494d12fd406d06c849b00d31bf120", "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa",
"sha256:a697977157adc052284a7160569b36a8bbec09db3c3220642e6323b47cec090f", "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa",
"sha256:a9032f9b7d38bdf882ac9f66ebde3afb8145f0d4c24b2e600bc4c6304aafb87e", "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796",
"sha256:b5e28db9199dd3833cc8a07fa6cf429a01227b5d429facb56eccd765050c26cd", "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a",
"sha256:c77943ef768276b61c96a3eb854eba55633c7a3fddf0a79f82805f232326d33f", "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928",
"sha256:d230d333b0be8042ac34808ad722eabba30036232e7a6fb3e317c49f61c93386", "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0",
"sha256:d4548be38a1c810d79e097a38107b6bf2ff42151900e47d49635be69943763d8", "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac",
"sha256:d4e7ced84a11c10160c0697a6cc0b214a5d7ab21dfec1cd46e89fbf77cc66fae", "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c",
"sha256:d56f105592188ce7a797b2bd94b4a8cb2e36d5d9b0d8a1d2060ff2a71e6b9bbc", "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685",
"sha256:d714af0bdba67739598849c9f18efdcc5a0412f4993914a0ec5ce0f1e864d783", "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d",
"sha256:d774d9e97007b018a651eadc1b3970ed20237395527e22cbeb743d8e73e0563d", "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e",
"sha256:e0524adb49c716ca763dbc1d27bedce36b14f33e6b8af6dba56886476b42957c", "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f",
"sha256:e2618cb2cf5a7cc8d698306e42ebcacd02fb7ef8cfc18485c59394152c70be97", "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558",
"sha256:e36750fbbc422c1c46c9d13b937ab437138b998fe74a635ec88989afb57a3978", "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58",
"sha256:edfdabe7aa4f97ed2b9dd5dde52d2bb29cb466993bb9d612ddd10d0085a683cf", "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781",
"sha256:f22325010d8824594820d6ce84fa830838f581a7fd86a9235f0d2ed6deb61e29", "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a",
"sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39", "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa",
"sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452" "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc",
"sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892",
"sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d",
"sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817",
"sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1",
"sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c",
"sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908",
"sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19",
"sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60",
"sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b"
], ],
"index": "pypi", "index": "pypi",
"version": "==6.4.2" "version": "==6.4.4"
}, },
"distlib": { "distlib": {
"hashes": [ "hashes": [
"sha256:a7f75737c70be3b25e2bee06288cec4e4c221de18455b2dd037fe2a795cab2fe", "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46",
"sha256:b710088c59f06338ca514800ad795a132da19fda270e3ce4affc74abf955a26c" "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"
], ],
"version": "==0.3.5" "version": "==0.3.6"
}, },
"dlint": { "dlint": {
"hashes": [ "hashes": [
@ -1135,11 +1141,11 @@
}, },
"filelock": { "filelock": {
"hashes": [ "hashes": [
"sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404", "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc",
"sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04" "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==3.7.1" "version": "==3.8.0"
}, },
"flake8": { "flake8": {
"hashes": [ "hashes": [
@ -1274,10 +1280,11 @@
}, },
"pathspec": { "pathspec": {
"hashes": [ "hashes": [
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93",
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"
], ],
"version": "==0.9.0" "markers": "python_version >= '3.7'",
"version": "==0.10.1"
}, },
"platformdirs": { "platformdirs": {
"hashes": [ "hashes": [
@ -1292,7 +1299,7 @@
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==1.0.0" "version": "==1.0.0"
}, },
"py": { "py": {
@ -1321,11 +1328,11 @@
}, },
"pygments": { "pygments": {
"hashes": [ "hashes": [
"sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb", "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1",
"sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519" "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==2.12.0" "version": "==2.13.0"
}, },
"pyparsing": { "pyparsing": {
"hashes": [ "hashes": [
@ -1337,11 +1344,11 @@
}, },
"pytest": { "pytest": {
"hashes": [ "hashes": [
"sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7",
"sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45" "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"
], ],
"index": "pypi", "index": "pypi",
"version": "==7.1.2" "version": "==7.1.3"
}, },
"pytest-cov": { "pytest-cov": {
"hashes": [ "hashes": [
@ -1353,10 +1360,10 @@
}, },
"pytz": { "pytz": {
"hashes": [ "hashes": [
"sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197",
"sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5"
], ],
"version": "==2022.1" "version": "==2022.2.1"
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
@ -1382,11 +1389,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:7c7854ee1429a240090297628dc9f75b35318d193537968e2dc14010ee2f5bca", "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82",
"sha256:dc2662692f47d99cb8ae15a784529adeed535bcd7c277fee0beccf961522baf6" "sha256:7732871f4f7fa58fb6bdcaeadb0161b2bd046c85905dbaa066bdcbcc81953b57"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==63.4.1" "version": "==65.3.0"
}, },
"six": { "six": {
"hashes": [ "hashes": [
@ -1448,7 +1455,7 @@
"sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07",
"sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"
], ],
"markers": "python_full_version >= '3.6.0'", "markers": "python_version >= '3.6'",
"version": "==2.0.0" "version": "==2.0.0"
}, },
"sphinxcontrib-jsmath": { "sphinxcontrib-jsmath": {
@ -1493,11 +1500,11 @@
}, },
"tqdm": { "tqdm": {
"hashes": [ "hashes": [
"sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4",
"sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"
], ],
"index": "pypi", "index": "pypi",
"version": "==4.64.0" "version": "==4.64.1"
}, },
"typed-ast": { "typed-ast": {
"hashes": [ "hashes": [
@ -1534,16 +1541,16 @@
"sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
"sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version < '3.8'",
"version": "==4.3.0" "version": "==4.3.0"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:c33ccba33c819596124764c23a97d25f32b28433ba0dedeb77d873a38722c9bc", "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e",
"sha256:ea6e8fb210b19d950fab93b60c9009226c63a28808bc8386e05301e25883ac0a" "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"
], ],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
"version": "==1.26.11" "version": "==1.26.12"
}, },
"virtualenv": { "virtualenv": {
"hashes": [ "hashes": [

View File

@ -59,7 +59,7 @@ class MongoCredentialsRepository(ICredentialsRepository):
for encrypted_credentials in list_collection_result: for encrypted_credentials in list_collection_result:
del encrypted_credentials[MONGO_OBJECT_ID_KEY] del encrypted_credentials[MONGO_OBJECT_ID_KEY]
plaintext_credentials = self._decrypt_credentials_mapping(encrypted_credentials) plaintext_credentials = self._decrypt_credentials_mapping(encrypted_credentials)
collection_result.append(Credentials.from_mapping(plaintext_credentials)) collection_result.append(Credentials(**plaintext_credentials))
return collection_result return collection_result
except Exception as err: except Exception as err:
@ -68,7 +68,7 @@ class MongoCredentialsRepository(ICredentialsRepository):
def _save_credentials_to_collection(self, credentials: Sequence[Credentials], collection): def _save_credentials_to_collection(self, credentials: Sequence[Credentials], collection):
try: try:
for c in credentials: for c in credentials:
encrypted_credentials = self._encrypt_credentials_mapping(Credentials.to_mapping(c)) encrypted_credentials = self._encrypt_credentials_mapping(c.dict(simplify=True))
collection.insert_one(encrypted_credentials) collection.insert_one(encrypted_credentials)
except Exception as err: except Exception as err:
raise StorageError(err) raise StorageError(err)

View File

@ -29,7 +29,7 @@ class PropagationCredentials(AbstractResource):
return propagation_credentials, HTTPStatus.OK return propagation_credentials, HTTPStatus.OK
def put(self, collection=None): def put(self, collection=None):
credentials = [Credentials.from_mapping(c) for c in request.json] credentials = [Credentials(**c) for c in request.json]
if collection == _configured_collection: if collection == _configured_collection:
self._credentials_repository.remove_configured_credentials() self._credentials_repository.remove_configured_credentials()
self._credentials_repository.save_configured_credentials(credentials) self._credentials_repository.save_configured_credentials(credentials)

View File

@ -1,33 +1,30 @@
import logging import logging
from typing import Mapping, Sequence from typing import Mapping, Sequence
from common.credentials import CredentialComponentType, Credentials from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def format_creds_for_reporting(credentials: Sequence[Credentials]) -> Sequence[Mapping]: def format_creds_for_reporting(credentials: Sequence[Credentials]) -> Sequence[Mapping]:
logger.info("Stolen creds generated for reporting")
formatted_creds = [] formatted_creds = []
cred_type_dict = { cred_type_dict = {
CredentialComponentType.PASSWORD: "Clear Password", Password: "Clear Password",
CredentialComponentType.LM_HASH: "LM hash", LMHash: "LM hash",
CredentialComponentType.NT_HASH: "NTLM hash", NTHash: "NTLM hash",
CredentialComponentType.SSH_KEYPAIR: "Clear SSH private key", SSHKeypair: "Clear SSH private key",
} }
for cred in credentials: for cred in credentials:
secret = cred.secret secret = cred.secret
if secret is None: if secret is None:
continue continue
if secret.credential_type not in cred_type_dict: if type(secret) not in cred_type_dict:
continue continue
username = _get_username(cred) username = _get_username(cred)
cred_row = { cred_row = {
"username": username, "username": username,
"_type": secret.credential_type.name, "type": cred_type_dict[type(secret)],
"type": cred_type_dict[secret.credential_type],
} }
if cred_row not in formatted_creds: if cred_row not in formatted_creds:
formatted_creds.append(cred_row) formatted_creds.append(cred_row)

View File

@ -5,6 +5,7 @@ from typing import Any
import bson import bson
from flask import make_response from flask import make_response
from pydantic import BaseModel
from common.utils import IJSONSerializable from common.utils import IJSONSerializable
@ -23,6 +24,8 @@ class APIEncoder(JSONEncoder):
return loads(value.__class__.to_json(value)) return loads(value.__class__.to_json(value))
if issubclass(type(value), set): if issubclass(type(value), set):
return list(value) return list(value)
if issubclass(type(value), BaseModel):
return value.dict(simplify=True)
try: try:
return JSONEncoder.default(self, value) return JSONEncoder.default(self, value)
except TypeError: except TypeError:

View File

@ -19,8 +19,6 @@ class CredentialsParser:
self._parse_credentials(telemetry_dict, _agent_configuration) self._parse_credentials(telemetry_dict, _agent_configuration)
def _parse_credentials(self, telemetry_dict: Mapping, _agent_configuration): def _parse_credentials(self, telemetry_dict: Mapping, _agent_configuration):
credentials = [ credentials = [Credentials(**credential) for credential in telemetry_dict["data"]]
Credentials.from_mapping(credential) for credential in telemetry_dict["data"]
]
self._credentials_repository.save_stolen_credentials(credentials) self._credentials_repository.save_stolen_credentials(credentials)

View File

@ -1,20 +1,26 @@
import {defaultCredentials} from '../../services/configuration/propagation/credentials'; import {defaultCredentials} from '../../services/configuration/propagation/credentials';
import {PlaintextTypes, SecretTypes} from '../utils/CredentialTitles.js';
import _ from 'lodash'; import _ from 'lodash';
export function reformatConfig(config, reverse = false) { export function reformatConfig(config, reverse = false) {
let formattedConfig = _.clone(config); let formattedConfig = _.clone(config);
if (reverse) { if (reverse) {
if(formattedConfig['payloads'].length === 1){ if (formattedConfig['payloads'].length === 1) {
// Second click on Export // Second click on Export
formattedConfig['payloads'] = [{'name': 'ransomware', 'options': formattedConfig['payloads'][0]['options']}]; formattedConfig['payloads'] = [{
'name': 'ransomware',
'options': formattedConfig['payloads'][0]['options']
}];
} else { } else {
formattedConfig['payloads'] = [{'name': 'ransomware', 'options': formattedConfig['payloads']}]; formattedConfig['payloads'] = [{
'name': 'ransomware',
'options': formattedConfig['payloads']
}];
} }
formattedConfig['keep_tunnel_open_time'] = formattedConfig['advanced']['keep_tunnel_open_time']; formattedConfig['keep_tunnel_open_time'] = formattedConfig['advanced']['keep_tunnel_open_time'];
} else { } else {
if(formattedConfig['payloads'].length !== 0) if (formattedConfig['payloads'].length !== 0) {
{
formattedConfig['payloads'] = formattedConfig['payloads'][0]['options']; formattedConfig['payloads'] = formattedConfig['payloads'][0]['options'];
} else { } else {
formattedConfig['payloads'] = {'encryption': {}, 'other_behaviors': {}} formattedConfig['payloads'] = {'encryption': {}, 'other_behaviors': {}}
@ -29,23 +35,26 @@ export function formatCredentialsForForm(credentials) {
let formattedCredentials = _.clone(defaultCredentials); let formattedCredentials = _.clone(defaultCredentials);
for (let i = 0; i < credentials.length; i++) { for (let i = 0; i < credentials.length; i++) {
let identity = credentials[i]['identity']; let identity = credentials[i]['identity'];
if(identity !== null) { if (identity !== null) {
formattedCredentials['exploit_user_list'].push(identity.username) formattedCredentials['exploit_user_list'].push(identity.username)
} }
let secret = credentials[i]['secret']; let secret = credentials[i]['secret'];
if(secret !== null){ if (secret !== null) {
if (secret['credential_type'] === 'PASSWORD') { if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.Password)) {
formattedCredentials['exploit_password_list'].push(secret['password']) formattedCredentials['exploit_password_list'].push(secret[SecretTypes.Password])
} }
if (secret['credential_type'] === 'NT_HASH') { if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.NTHash)) {
formattedCredentials['exploit_ntlm_hash_list'].push(secret['nt_hash']) formattedCredentials['exploit_ntlm_hash_list'].push(secret[SecretTypes.NTHash])
} }
if (secret['credential_type'] === 'LM_HASH') { if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.LMHash)) {
formattedCredentials['exploit_lm_hash_list'].push(secret['lm_hash']) formattedCredentials['exploit_lm_hash_list'].push(secret[SecretTypes.LMHash])
} }
if (secret['credential_type'] === 'SSH_KEY') { if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.PrivateKey)) {
let keypair = {'public_key': secret['public_key'], 'private_key': secret['private_key']} let keypair = {
'public_key': secret[PlaintextTypes.PublicKey],
'private_key': secret[SecretTypes.PrivateKey]
}
formattedCredentials['exploit_ssh_keys'].push(keypair) formattedCredentials['exploit_ssh_keys'].push(keypair)
} }
} }
@ -64,43 +73,36 @@ export function formatCredentialsForIsland(credentials) {
let usernames = credentials['exploit_user_list']; let usernames = credentials['exploit_user_list'];
for (let i = 0; i < usernames.length; i++) { for (let i = 0; i < usernames.length; i++) {
formattedCredentials.push({ formattedCredentials.push({
'identity': {'username': usernames[i], 'credential_type': 'USERNAME'}, 'identity': {'username': usernames[i]},
'secret': null 'secret': null
}) })
} }
let passwords = credentials['exploit_password_list']; formattedCredentials.push(...getFormattedCredentials(credentials['exploit_password_list'], SecretTypes.Password))
for (let i = 0; i < passwords.length; i++) { formattedCredentials.push(...getFormattedCredentials(credentials['exploit_ntlm_hash_list'], SecretTypes.NTHash))
formattedCredentials.push({ formattedCredentials.push(...getFormattedCredentials(credentials['exploit_lm_hash_list'], SecretTypes.LMHash))
'identity': null,
'secret': {'credential_type': 'PASSWORD', 'password': passwords[i]}
})
}
let nt_hashes = credentials['exploit_ntlm_hash_list'];
for (let i = 0; i < nt_hashes.length; i++) {
formattedCredentials.push({
'identity': null,
'secret': {'credential_type': 'NT_HASH', 'nt_hash': nt_hashes[i]}
})
}
let lm_hashes = credentials['exploit_lm_hash_list'];
for (let i = 0; i < lm_hashes.length; i++) {
formattedCredentials.push({
'identity': null,
'secret': {'credential_type': 'LM_HASH', 'lm_hash': lm_hashes[i]}
})
}
let ssh_keys = credentials['exploit_ssh_keys']; let ssh_keys = credentials['exploit_ssh_keys'];
for (let i = 0; i < ssh_keys.length; i++) { for (let i = 0; i < ssh_keys.length; i++) {
formattedCredentials.push({ formattedCredentials.push({
'identity': null, 'identity': null,
'secret': {'credential_type': 'SSH_KEYPAIR', 'private_key': ssh_keys[i]['private_key'], 'secret': {
'public_key': ssh_keys[i]['public_key']} 'private_key': ssh_keys[i]['private_key'],
'public_key': ssh_keys[i]['public_key']
}
}) })
} }
return formattedCredentials; return formattedCredentials;
} }
function getFormattedCredentials(credentials, keyOfSecret) {
let formattedCredentials = [];
for (let i = 0; i < credentials.length; i++) {
formattedCredentials.push({
'identity': null,
'secret': {[keyOfSecret]: credentials[i]}
})
}
return formattedCredentials;
}

View File

@ -1,66 +1,70 @@
export function getAllUsernames(stolen, configured){ import {CredentialTitles, SecretTypes} from '../utils/CredentialTitles.js';
let usernames = [];
usernames.push(...getCredentialsUsernames(stolen)); export function getAllUsernames(stolen, configured) {
usernames.push(...getCredentialsUsernames(configured)); let usernames = new Set();
return usernames; usernames.add(...getCredentialsUsernames(stolen));
usernames.add(...getCredentialsUsernames(configured));
return Array.from(usernames);
} }
export function getCredentialsUsernames(credentials) { export function getCredentialsUsernames(credentials) {
let usernames = []; let usernames = [];
for(let i = 0; i < credentials.length; i++){ for (let i = 0; i < credentials.length; i++) {
let username = credentials[i]['identity']; let username = credentials[i]['identity'];
if(username !== null) { if (username !== null) {
usernames.push(username['username']); usernames.push(username['username']);
} }
} }
return usernames; return usernames;
} }
export function getAllSecrets(stolen, configured){ export function getAllSecrets(stolen, configured) {
let secrets = []; let secrets = new Set();
for(let i = 0; i < stolen.length; i++){ for (let i = 0; i < stolen.length; i++) {
let secret = stolen[i]['secret']; let secret = stolen[i]['secret'];
if(secret !== null){ if (secret !== null) {
secrets.push(getSecretsFromCredential(secret)); secrets.add(reformatSecret(secret));
} }
} }
for(let i = 0; i < configured.length; i++){ for (let i = 0; i < configured.length; i++) {
let secret = configured[i]['secret']; let secret = configured[i]['secret'];
if(secret !== null){ if (secret !== null) {
secrets.push(getSecretsFromCredential(secret)); secrets.add(reformatSecret(secret));
} }
} }
return secrets; return Array.from(secrets);
} }
function getSecretsFromCredential(credential) { function reformatSecret(secret) {
if(credential['credential_type'] === 'SSH_KEYPAIR'){ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.Password)) {
return {'type': 'SSH keypair', 'content': credential['private_key']} return {'title': CredentialTitles.Password, 'content': secret[SecretTypes.Password]}
} }
if(credential['credential_type'] === 'NT_HASH'){ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.NTHash)) {
return {'type': 'NT hash', 'content': credential['nt_hash']} return {'title': CredentialTitles.NTHash, 'content': secret[SecretTypes.NTHash]}
} }
if(credential['credential_type'] === 'LM_HASH'){ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.LMHash)) {
return {'type': 'LM hash', 'content': credential['lm_hash']} return {'title': CredentialTitles.LMHash, 'content': secret[SecretTypes.LMHash]}
} }
if(credential['credential_type'] === 'PASSWORD'){ if (Object.prototype.hasOwnProperty.call(secret, SecretTypes.PrivateKey)) {
return {'type': 'Password', 'content': credential['password']} return {
'title': CredentialTitles.SSHKeys,
'content': secret[SecretTypes.PrivateKey]
}
} }
} }
export function getCredentialsTableData(credentials) { export function getCredentialsTableData(credentials) {
let table_data = [];
let table_data = []; let identites = getCredentialsUsernames(credentials);
let secrets = getAllSecrets(credentials, [])
let identites = getCredentialsUsernames(credentials); for (let i = 0; i < credentials.length; i++) {
let secrets = getAllSecrets(credentials, []) let row_data = {};
row_data['username'] = identites[i];
row_data['title'] = secrets[i]['title'];
table_data.push(row_data);
}
for(let i=0; i<credentials.length; i++) { return table_data;
let row_data = {};
row_data['username'] = identites[i];
row_data['type'] = secrets[i]['type'];
table_data.push(row_data);
}
return table_data;
} }

View File

@ -7,7 +7,7 @@ const columns = [
Header: 'Stolen Credentials', Header: 'Stolen Credentials',
columns: [ columns: [
{Header: 'Username', accessor: 'username'}, {Header: 'Username', accessor: 'username'},
{Header: 'Type', accessor: 'type'} {Header: 'Type', accessor: 'title'}
] ]
} }
]; ];

View File

@ -24,7 +24,7 @@ class UsedCredentials extends React.Component {
</p> </p>
<ul> <ul>
{allSecrets.map((x, index) => <li {allSecrets.map((x, index) => <li
key={index}>{x['type']}: {x['content'].substr(0, 3) + '******'}</li>)} key={index}>{x['title']}: {x['content'].substr(0, 3) + '******'}</li>)}
</ul> </ul>
</> </>
: :

View File

@ -0,0 +1,18 @@
export const CredentialTitles = {
Password: 'Clear Password',
SSHKeys: 'Clear SSH private key',
LMHash: 'LM hash',
NTHash: 'NT hash',
Username: 'Username'
}
export const SecretTypes = {
Password: 'password',
PrivateKey: 'private_key',
LMHash: 'lm_hash',
NTHash: 'nt_hash'
}
export const PlaintextTypes = {
PublicKey: 'public_key'
}

View File

@ -1,31 +1,64 @@
from itertools import product
from pydantic import SecretStr
from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username
USERNAME = "m0nk3y_user" USERNAME = "m0nk3y_user"
SPECIAL_USERNAME = "m0nk3y.user" SPECIAL_USERNAME = "m0nk3y.user"
NT_HASH = "C1C58F96CDF212B50837BC11A00BE47C" PLAINTEXT_NT_HASH = "C1C58F96CDF212B50837BC11A00BE47C"
LM_HASH = "299BD128C1101FD6299BD128C1101FD6" PLAINTEXT_LM_HASH = "299BD128C1101FD6299BD128C1101FD6"
PASSWORD_1 = "trytostealthis" PLAINTEXT_PASSWORD = "trytostealthis"
PASSWORD_2 = "password!" PLAINTEXT_PRIVATE_KEY = "MY_PRIVATE_KEY"
PASSWORD_3 = "rubberbabybuggybumpers" NT_HASH = SecretStr(PLAINTEXT_NT_HASH)
LM_HASH = SecretStr(PLAINTEXT_LM_HASH)
PASSWORD_1 = SecretStr(PLAINTEXT_PASSWORD)
PASSWORD_2 = SecretStr("password!")
PASSWORD_3 = SecretStr("rubberbabybuggybumpers")
PUBLIC_KEY = "MY_PUBLIC_KEY" PUBLIC_KEY = "MY_PUBLIC_KEY"
PRIVATE_KEY = "MY_PRIVATE_KEY" PRIVATE_KEY = SecretStr(PLAINTEXT_PRIVATE_KEY)
PASSWORD_CREDENTIALS_1 = Credentials(identity=Username(USERNAME), secret=Password(PASSWORD_1)) IDENTITIES = [Username(username=USERNAME), None, Username(username=SPECIAL_USERNAME)]
PASSWORD_CREDENTIALS_2 = Credentials(identity=Username(USERNAME), secret=Password(PASSWORD_2)) IDENTITY_DICTS = [{"username": USERNAME}, None]
LM_HASH_CREDENTIALS = Credentials(identity=Username(SPECIAL_USERNAME), secret=LMHash(LM_HASH))
NT_HASH_CREDENTIALS = Credentials(identity=Username(USERNAME), secret=NTHash(NT_HASH)) SECRETS = (
SSH_KEY_CREDENTIALS = Credentials( Password(password=PASSWORD_1),
identity=Username(USERNAME), secret=SSHKeypair(PRIVATE_KEY, PUBLIC_KEY) Password(password=PASSWORD_2),
Password(password=PASSWORD_3),
LMHash(lm_hash=LM_HASH),
NTHash(nt_hash=NT_HASH),
SSHKeypair(private_key=PRIVATE_KEY, public_key=PUBLIC_KEY),
None,
) )
EMPTY_SECRET_CREDENTIALS = Credentials(identity=Username(USERNAME), secret=None) SECRET_DICTS = [
EMPTY_IDENTITY_CREDENTIALS = Credentials(identity=None, secret=Password(PASSWORD_3)) {"password": PASSWORD_1},
{"lm_hash": LM_HASH},
PROPAGATION_CREDENTIALS = [ {"nt_hash": NT_HASH},
PASSWORD_CREDENTIALS_1, {
LM_HASH_CREDENTIALS, "public_key": PUBLIC_KEY,
NT_HASH_CREDENTIALS, "private_key": PRIVATE_KEY,
PASSWORD_CREDENTIALS_2, },
SSH_KEY_CREDENTIALS, None,
EMPTY_SECRET_CREDENTIALS, ]
EMPTY_IDENTITY_CREDENTIALS,
CREDENTIALS = [
Credentials(identity=identity, secret=secret)
for identity, secret in product(IDENTITIES, SECRETS)
]
FULL_CREDENTIALS = [
credentials
for credentials in CREDENTIALS
if not (credentials.identity is None and credentials.secret is None)
]
CREDENTIALS_DICTS = [
{"identity": identity, "secret": secret}
for identity, secret in product(IDENTITY_DICTS, SECRET_DICTS)
]
FULL_CREDENTIALS_DICTS = [
credentials
for credentials in CREDENTIALS_DICTS
if not (credentials["identity"] is None and credentials["secret"] is None)
] ]

View File

@ -0,0 +1,20 @@
from typing import List, Union
import pytest
@pytest.fixture(scope="session")
def valid_ntlm_hash() -> str:
return "E520AC67419A9A224A3B108F3FA6CB6D"
@pytest.fixture(scope="session")
def invalid_ntlm_hashes() -> List[Union[str, int, float]]:
return [
0,
1,
2.0,
"invalid",
"0123456789012345678901234568901",
"E52GAC67419A9A224A3B108F3FA6CB6D",
]

View File

@ -1,148 +0,0 @@
from typing import Any, Mapping
import pytest
from marshmallow.exceptions import ValidationError
from common.credentials import (
CredentialComponentType,
LMHash,
NTHash,
Password,
SSHKeypair,
Username,
)
from common.credentials.lm_hash import LMHashSchema
from common.credentials.nt_hash import NTHashSchema
from common.credentials.password import PasswordSchema
from common.credentials.ssh_keypair import SSHKeypairSchema
from common.credentials.username import UsernameSchema
PARAMETRIZED_PARAMETER_NAMES = (
"credential_component_class, schema_class, credential_component_type, credential_component_data"
)
PARAMETRIZED_PARAMETER_VALUES = [
(Username, UsernameSchema, CredentialComponentType.USERNAME, {"username": "test_user"}),
(Password, PasswordSchema, CredentialComponentType.PASSWORD, {"password": "123456"}),
(
LMHash,
LMHashSchema,
CredentialComponentType.LM_HASH,
{"lm_hash": "E52CAC67419A9A224A3B108F3FA6CB6D"},
),
(
NTHash,
NTHashSchema,
CredentialComponentType.NT_HASH,
{"nt_hash": "E52CAC67419A9A224A3B108F3FA6CB6D"},
),
(
SSHKeypair,
SSHKeypairSchema,
CredentialComponentType.SSH_KEYPAIR,
{"public_key": "TEST_PUBLIC_KEY", "private_key": "TEST_PRIVATE_KEY"},
),
]
INVALID_COMPONENT_DATA = {
CredentialComponentType.USERNAME: ({"username": None}, {"username": 1}, {"username": 2.0}),
CredentialComponentType.PASSWORD: ({"password": None}, {"password": 1}, {"password": 2.0}),
CredentialComponentType.LM_HASH: (
{"lm_hash": None},
{"lm_hash": 1},
{"lm_hash": 2.0},
{"lm_hash": "0123456789012345678901234568901"},
{"lm_hash": "E52GAC67419A9A224A3B108F3FA6CB6D"},
),
CredentialComponentType.NT_HASH: (
{"nt_hash": None},
{"nt_hash": 1},
{"nt_hash": 2.0},
{"nt_hash": "0123456789012345678901234568901"},
{"nt_hash": "E52GAC67419A9A224A3B108F3FA6CB6D"},
),
CredentialComponentType.SSH_KEYPAIR: (
{"public_key": None, "private_key": "TEST_PRIVATE_KEY"},
{"public_key": "TEST_PUBLIC_KEY", "private_key": None},
{"public_key": 1, "private_key": "TEST_PRIVATE_KEY"},
{"public_key": "TEST_PUBLIC_KEY", "private_key": 999},
),
}
def build_component_dict(
credential_component_type: CredentialComponentType, credential_component_data: Mapping[str, Any]
):
return {"credential_type": credential_component_type.name, **credential_component_data}
@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
def test_credential_component_serialize(
credential_component_class, schema_class, credential_component_type, credential_component_data
):
schema = schema_class()
constructed_object = credential_component_class(**credential_component_data)
serialized_object = schema.dump(constructed_object)
assert serialized_object == build_component_dict(
credential_component_type, credential_component_data
)
@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
def test_credential_component_deserialize(
credential_component_class, schema_class, credential_component_type, credential_component_data
):
schema = schema_class()
credential_dict = build_component_dict(credential_component_type, credential_component_data)
expected_deserialized_object = credential_component_class(**credential_component_data)
deserialized_object = credential_component_class(**schema.load(credential_dict))
assert deserialized_object == expected_deserialized_object
@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
def test_invalid_credential_type(
credential_component_class, schema_class, credential_component_type, credential_component_data
):
invalid_component_dict = build_component_dict(
credential_component_type, credential_component_data
)
invalid_component_dict["credential_type"] = "INVALID"
schema = schema_class()
with pytest.raises(ValidationError):
credential_component_class(**schema.load(invalid_component_dict))
@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
def test_encorrect_credential_type(
credential_component_class, schema_class, credential_component_type, credential_component_data
):
incorrect_component_dict = build_component_dict(
credential_component_type, credential_component_data
)
incorrect_component_dict["credential_type"] = (
CredentialComponentType.USERNAME.name
if credential_component_type != CredentialComponentType.USERNAME
else CredentialComponentType.PASSWORD
)
schema = schema_class()
with pytest.raises(ValidationError):
credential_component_class(**schema.load(incorrect_component_dict))
@pytest.mark.parametrize(PARAMETRIZED_PARAMETER_NAMES, PARAMETRIZED_PARAMETER_VALUES)
def test_invalid_values(
credential_component_class, schema_class, credential_component_type, credential_component_data
):
schema = schema_class()
for invalid_component_data in INVALID_COMPONENT_DATA[credential_component_type]:
component_dict = build_component_dict(credential_component_type, invalid_component_data)
with pytest.raises(ValidationError):
credential_component_class(**schema.load(component_dict))

View File

@ -1,122 +1,72 @@
import json import logging
from itertools import product from pathlib import Path
import pytest import pytest
from pydantic import SecretBytes
from pydantic.types import SecretStr
from tests.data_for_tests.propagation_credentials import ( from tests.data_for_tests.propagation_credentials import (
CREDENTIALS,
CREDENTIALS_DICTS,
LM_HASH, LM_HASH,
NT_HASH,
PASSWORD_1, PASSWORD_1,
PLAINTEXT_LM_HASH,
PLAINTEXT_PASSWORD,
PLAINTEXT_PRIVATE_KEY,
PRIVATE_KEY, PRIVATE_KEY,
PUBLIC_KEY,
USERNAME,
) )
from common.credentials import ( from common.base_models import InfectionMonkeyBaseModel
Credentials, from common.credentials import Credentials
InvalidCredentialComponentError, from common.credentials.credentials import get_plaintext
InvalidCredentialsError,
LMHash,
NTHash,
Password,
SSHKeypair,
Username,
)
IDENTITIES = [Username(USERNAME), None]
IDENTITY_DICTS = [{"credential_type": "USERNAME", "username": USERNAME}, None]
SECRETS = (
Password(PASSWORD_1),
LMHash(LM_HASH),
NTHash(NT_HASH),
SSHKeypair(PRIVATE_KEY, PUBLIC_KEY),
None,
)
SECRET_DICTS = [
{"credential_type": "PASSWORD", "password": PASSWORD_1},
{"credential_type": "LM_HASH", "lm_hash": LM_HASH},
{"credential_type": "NT_HASH", "nt_hash": NT_HASH},
{
"credential_type": "SSH_KEYPAIR",
"public_key": PUBLIC_KEY,
"private_key": PRIVATE_KEY,
},
None,
]
CREDENTIALS = [
Credentials(identity, secret)
for identity, secret in product(IDENTITIES, SECRETS)
if not (identity is None and secret is None)
]
CREDENTIALS_DICTS = [
{"identity": identity, "secret": secret}
for identity, secret in product(IDENTITY_DICTS, SECRET_DICTS)
if not (identity is None and secret is None)
]
@pytest.mark.parametrize( @pytest.mark.parametrize(
"credentials, expected_credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS) "credentials, expected_credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS)
) )
def test_credentials_serialization_json(credentials, expected_credentials_dict): def test_credentials_serialization_json(credentials, expected_credentials_dict):
serialized_credentials = Credentials.to_json(credentials) serialized_credentials = credentials.json()
deserialized_credentials = Credentials.parse_raw(serialized_credentials)
assert json.loads(serialized_credentials) == expected_credentials_dict assert credentials == deserialized_credentials
@pytest.mark.parametrize( logger = logging.getLogger()
"credentials, expected_credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS) logger.level = logging.DEBUG
)
def test_credentials_serialization_mapping(credentials, expected_credentials_dict):
serialized_credentials = Credentials.to_mapping(credentials)
assert serialized_credentials == expected_credentials_dict
@pytest.mark.parametrize( def test_credentials_secrets_not_logged(caplog):
"expected_credentials, credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS) class TestSecret(InfectionMonkeyBaseModel):
) some_secret: SecretStr
def test_credentials_deserialization__from_mapping(expected_credentials, credentials_dict): some_secret_in_bytes: SecretBytes
deserialized_credentials = Credentials.from_mapping(credentials_dict)
assert deserialized_credentials == expected_credentials class TestCredentials(Credentials):
secret: TestSecret
sensitive = "super_secret"
creds = TestCredentials(
identity=None,
secret=TestSecret(some_secret=sensitive, some_secret_in_bytes=sensitive.encode()),
)
logging.getLogger().info(
f"{creds.secret.some_secret} and" f" {creds.secret.some_secret_in_bytes}"
)
assert sensitive not in caplog.text
@pytest.mark.parametrize( _plaintext = [
"expected_credentials, credentials_dict", zip(CREDENTIALS, CREDENTIALS_DICTS) PLAINTEXT_PASSWORD,
) PLAINTEXT_PRIVATE_KEY,
def test_credentials_deserialization__from_json(expected_credentials, credentials_dict): PLAINTEXT_LM_HASH,
deserialized_credentials = Credentials.from_json(json.dumps(credentials_dict)) "",
"already_plaintext",
assert deserialized_credentials == expected_credentials Path("C:\\jolly_fella"),
None,
]
_hidden = [PASSWORD_1, PRIVATE_KEY, LM_HASH, "", "already_plaintext", Path("C:\\jolly_fella"), None]
def test_credentials_deserialization__invalid_credentials(): @pytest.mark.parametrize("expected, hidden", list(zip(_plaintext, _hidden)))
invalid_data = {"secret": SECRET_DICTS[0], "unknown_key": []} def test_get_plain_text(expected, hidden):
with pytest.raises(InvalidCredentialsError): assert expected == get_plaintext(hidden)
Credentials.from_mapping(invalid_data)
def test_credentials_deserialization__invalid_component_type():
invalid_data = {
"secret": SECRET_DICTS[0],
"identity": {"credential_type": "FAKE", "username": "user1"},
}
with pytest.raises(InvalidCredentialsError):
Credentials.from_mapping(invalid_data)
def test_credentials_deserialization__invalid_component():
invalid_data = {
"secret": SECRET_DICTS[0],
"identity": {"credential_type": "USERNAME", "unknown_field": "user1"},
}
with pytest.raises(InvalidCredentialComponentError):
Credentials.from_mapping(invalid_data)
def test_create_credentials__none_none():
with pytest.raises(InvalidCredentialsError):
Credentials(None, None)

View File

@ -0,0 +1,14 @@
import pytest
from common.credentials import LMHash
def test_construct_valid_nt_hash(valid_ntlm_hash):
# This test will fail if an exception is raised
LMHash(lm_hash=valid_ntlm_hash)
def test_construct_invalid_nt_hash(invalid_ntlm_hashes):
for invalid_hash in invalid_ntlm_hashes:
with pytest.raises(ValueError):
LMHash(lm_hash=invalid_hash)

View File

@ -0,0 +1,14 @@
import pytest
from common.credentials import NTHash
def test_construct_valid_nt_hash(valid_ntlm_hash):
# This test will fail if an exception is raised
NTHash(nt_hash=valid_ntlm_hash)
def test_construct_invalid_nt_hash(invalid_ntlm_hashes):
for invalid_hash in invalid_ntlm_hashes:
with pytest.raises(ValueError):
NTHash(nt_hash=invalid_hash)

View File

@ -1,26 +0,0 @@
import pytest
from common.credentials import InvalidCredentialComponentError, LMHash, NTHash
VALID_HASH = "E520AC67419A9A224A3B108F3FA6CB6D"
INVALID_HASHES = (
0,
1,
2.0,
"invalid",
"0123456789012345678901234568901",
"E52GAC67419A9A224A3B108F3FA6CB6D",
)
@pytest.mark.parametrize("ntlm_hash_class", (LMHash, NTHash))
def test_construct_valid_ntlm_hash(ntlm_hash_class):
# This test will fail if an exception is raised
ntlm_hash_class(VALID_HASH)
@pytest.mark.parametrize("ntlm_hash_class", (LMHash, NTHash))
def test_construct_invalid_ntlm_hash(ntlm_hash_class):
for invalid_hash in INVALID_HASHES:
with pytest.raises(InvalidCredentialComponentError):
ntlm_hash_class(invalid_hash)

View File

@ -41,9 +41,9 @@ def test_pypykatz_result_parsing(monkeypatch):
win_creds = [WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash="")] win_creds = [WindowsCredentials(username="user", password="secret", ntlm_hash="", lm_hash="")]
patch_pypykatz(win_creds, monkeypatch) patch_pypykatz(win_creds, monkeypatch)
username = Username("user") username = Username(username="user")
password = Password("secret") password = Password(password="secret")
expected_credentials = Credentials(username, password) expected_credentials = Credentials(identity=username, secret=password)
collected_credentials = collect_credentials() collected_credentials = collect_credentials()
assert len(collected_credentials) == 1 assert len(collected_credentials) == 1
@ -70,10 +70,13 @@ def test_pypykatz_result_parsing_defaults(monkeypatch):
patch_pypykatz(win_creds, monkeypatch) patch_pypykatz(win_creds, monkeypatch)
# Expected credentials # Expected credentials
username = Username("user2") username = Username(username="user2")
password = Password("secret2") password = Password(password="secret2")
lm_hash = LMHash("0182BD0BD4444BF8FC83B5D9042EED2E") lm_hash = LMHash(lm_hash="0182BD0BD4444BF8FC83B5D9042EED2E")
expected_credentials = [Credentials(username, password), Credentials(username, lm_hash)] expected_credentials = [
Credentials(identity=username, secret=password),
Credentials(identity=username, secret=lm_hash),
]
collected_credentials = collect_credentials() collected_credentials = collect_credentials()
assert len(collected_credentials) == 2 assert len(collected_credentials) == 2
@ -91,9 +94,12 @@ def test_pypykatz_result_parsing_no_identities(monkeypatch):
] ]
patch_pypykatz(win_creds, monkeypatch) patch_pypykatz(win_creds, monkeypatch)
lm_hash = LMHash("0182BD0BD4444BF8FC83B5D9042EED2E") lm_hash = LMHash(lm_hash="0182BD0BD4444BF8FC83B5D9042EED2E")
nt_hash = NTHash("E9F85516721DDC218359AD5280DB4450") nt_hash = NTHash(nt_hash="E9F85516721DDC218359AD5280DB4450")
expected_credentials = [Credentials(None, lm_hash), Credentials(None, nt_hash)] expected_credentials = [
Credentials(identity=None, secret=lm_hash),
Credentials(identity=None, secret=nt_hash),
]
collected_credentials = collect_credentials() collected_credentials = collect_credentials()
assert len(collected_credentials) == 2 assert len(collected_credentials) == 2
@ -112,7 +118,7 @@ def test_pypykatz_result_parsing_no_secrets(monkeypatch):
] ]
patch_pypykatz(win_creds, monkeypatch) patch_pypykatz(win_creds, monkeypatch)
expected_credentials = [Credentials(Username(username), None)] expected_credentials = [Credentials(identity=Username(username=username), secret=None)]
collected_credentials = collect_credentials() collected_credentials = collect_credentials()
assert len(collected_credentials) == 1 assert len(collected_credentials) == 1

View File

@ -31,7 +31,6 @@ def test_ssh_credentials_empty_results(monkeypatch, ssh_creds, patch_telemetry_m
def test_ssh_info_result_parsing(monkeypatch, patch_telemetry_messenger): def test_ssh_info_result_parsing(monkeypatch, patch_telemetry_messenger):
ssh_creds = [ ssh_creds = [
{ {
"name": "ubuntu", "name": "ubuntu",
@ -56,13 +55,15 @@ def test_ssh_info_result_parsing(monkeypatch, patch_telemetry_messenger):
patch_ssh_handler(ssh_creds, monkeypatch) patch_ssh_handler(ssh_creds, monkeypatch)
# Expected credentials # Expected credentials
username = Username("ubuntu") username = Username(username="ubuntu")
username2 = Username("mcus") username2 = Username(username="mcus")
username3 = Username("guest") username3 = Username(username="guest")
ssh_keypair1 = SSHKeypair("ExtremelyGoodPrivateKey", "SomePublicKeyUbuntu") ssh_keypair1 = SSHKeypair(
ssh_keypair2 = SSHKeypair("", "AnotherPublicKey") private_key="ExtremelyGoodPrivateKey", public_key="SomePublicKeyUbuntu"
ssh_keypair3 = SSHKeypair("PrivKey", "PubKey") )
ssh_keypair2 = SSHKeypair(private_key="", public_key="AnotherPublicKey")
ssh_keypair3 = SSHKeypair(private_key="PrivKey", public_key="PubKey")
expected = [ expected = [
Credentials(identity=username, secret=ssh_keypair1), Credentials(identity=username, secret=ssh_keypair1),

View File

@ -8,7 +8,11 @@ from infection_monkey.credential_repository import (
add_credentials_from_event_to_propagation_credentials_repository, add_credentials_from_event_to_propagation_credentials_repository,
) )
credentials = [Credentials(identity=Username("test_username"), secret=Password("some_password"))] credentials = [
Credentials(
identity=Username(username="test_username"), secret=Password(password="some_password")
)
]
credentials_stolen_event = CredentialsStolenEvent( credentials_stolen_event = CredentialsStolenEvent(
source=UUID("f811ad00-5a68-4437-bd51-7b5cc1768ad5"), source=UUID("f811ad00-5a68-4437-bd51-7b5cc1768ad5"),

View File

@ -1,14 +1,15 @@
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from pydantic import SecretStr
from tests.data_for_tests.propagation_credentials import ( from tests.data_for_tests.propagation_credentials import (
CREDENTIALS,
LM_HASH, LM_HASH,
NT_HASH, NT_HASH,
PASSWORD_1, PASSWORD_1,
PASSWORD_2, PASSWORD_2,
PASSWORD_3, PASSWORD_3,
PRIVATE_KEY, PRIVATE_KEY,
PROPAGATION_CREDENTIALS,
PUBLIC_KEY, PUBLIC_KEY,
SPECIAL_USERNAME, SPECIAL_USERNAME,
USERNAME, USERNAME,
@ -17,7 +18,6 @@ from tests.data_for_tests.propagation_credentials import (
from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username from common.credentials import Credentials, LMHash, NTHash, Password, SSHKeypair, Username
from infection_monkey.credential_repository import AggregatingPropagationCredentialsRepository from infection_monkey.credential_repository import AggregatingPropagationCredentialsRepository
CONTROL_CHANNEL_CREDENTIALS = PROPAGATION_CREDENTIALS
TRANSFORMED_CONTROL_CHANNEL_CREDENTIALS = { TRANSFORMED_CONTROL_CHANNEL_CREDENTIALS = {
"exploit_user_list": {USERNAME, SPECIAL_USERNAME}, "exploit_user_list": {USERNAME, SPECIAL_USERNAME},
"exploit_password_list": {PASSWORD_1, PASSWORD_2, PASSWORD_3}, "exploit_password_list": {PASSWORD_1, PASSWORD_2, PASSWORD_3},
@ -31,37 +31,43 @@ EMPTY_CHANNEL_CREDENTIALS = []
STOLEN_USERNAME_1 = "user1" STOLEN_USERNAME_1 = "user1"
STOLEN_USERNAME_2 = "user2" STOLEN_USERNAME_2 = "user2"
STOLEN_USERNAME_3 = "user3" STOLEN_USERNAME_3 = "user3"
STOLEN_PASSWORD_1 = "abcdefg" STOLEN_PASSWORD_1 = SecretStr("abcdefg")
STOLEN_PASSWORD_2 = "super_secret" STOLEN_PASSWORD_2 = SecretStr("super_secret")
STOLEN_PUBLIC_KEY_1 = "some_public_key_1" STOLEN_PUBLIC_KEY_1 = "some_public_key_1"
STOLEN_PUBLIC_KEY_2 = "some_public_key_2" STOLEN_PUBLIC_KEY_2 = "some_public_key_2"
STOLEN_LM_HASH = "AAD3B435B51404EEAAD3B435B51404EE" STOLEN_LM_HASH = SecretStr("AAD3B435B51404EEAAD3B435B51404EE")
STOLEN_NT_HASH = "C0172DFF622FE29B5327CB79DC12D24C" STOLEN_NT_HASH = SecretStr("C0172DFF622FE29B5327CB79DC12D24C")
STOLEN_PRIVATE_KEY_1 = "some_private_key_1" STOLEN_PRIVATE_KEY_1 = SecretStr("some_private_key_1")
STOLEN_PRIVATE_KEY_2 = "some_private_key_2" STOLEN_PRIVATE_KEY_2 = SecretStr("some_private_key_2")
STOLEN_CREDENTIALS = [ STOLEN_CREDENTIALS = [
Credentials( Credentials(
identity=Username(STOLEN_USERNAME_1), identity=Username(username=STOLEN_USERNAME_1),
secret=Password(PASSWORD_1), secret=Password(password=PASSWORD_1),
), ),
Credentials(identity=Username(STOLEN_USERNAME_1), secret=Password(STOLEN_PASSWORD_1)),
Credentials( Credentials(
identity=Username(STOLEN_USERNAME_2), identity=Username(username=STOLEN_USERNAME_1), secret=Password(password=STOLEN_PASSWORD_1)
),
Credentials(
identity=Username(username=STOLEN_USERNAME_2),
secret=SSHKeypair(public_key=STOLEN_PUBLIC_KEY_1, private_key=STOLEN_PRIVATE_KEY_1), secret=SSHKeypair(public_key=STOLEN_PUBLIC_KEY_1, private_key=STOLEN_PRIVATE_KEY_1),
), ),
Credentials( Credentials(
identity=None, identity=None,
secret=Password(STOLEN_PASSWORD_2), secret=Password(password=STOLEN_PASSWORD_2),
), ),
Credentials(identity=Username(STOLEN_USERNAME_2), secret=LMHash(STOLEN_LM_HASH)), Credentials(
Credentials(identity=Username(STOLEN_USERNAME_2), secret=NTHash(STOLEN_NT_HASH)), identity=Username(username=STOLEN_USERNAME_2), secret=LMHash(lm_hash=STOLEN_LM_HASH)
Credentials(identity=Username(STOLEN_USERNAME_3), secret=None), ),
Credentials(
identity=Username(username=STOLEN_USERNAME_2), secret=NTHash(nt_hash=STOLEN_NT_HASH)
),
Credentials(identity=Username(username=STOLEN_USERNAME_3), secret=None),
] ]
STOLEN_SSH_KEYS_CREDENTIALS = [ STOLEN_SSH_KEYS_CREDENTIALS = [
Credentials( Credentials(
Username(USERNAME), identity=Username(username=USERNAME),
SSHKeypair(public_key=STOLEN_PUBLIC_KEY_2, private_key=STOLEN_PRIVATE_KEY_2), secret=SSHKeypair(public_key=STOLEN_PUBLIC_KEY_2, private_key=STOLEN_PRIVATE_KEY_2),
) )
] ]
@ -69,7 +75,7 @@ STOLEN_SSH_KEYS_CREDENTIALS = [
@pytest.fixture @pytest.fixture
def aggregating_credentials_repository() -> AggregatingPropagationCredentialsRepository: def aggregating_credentials_repository() -> AggregatingPropagationCredentialsRepository:
control_channel = MagicMock() control_channel = MagicMock()
control_channel.get_credentials_for_propagation.return_value = CONTROL_CHANNEL_CREDENTIALS control_channel.get_credentials_for_propagation.return_value = CREDENTIALS
return AggregatingPropagationCredentialsRepository(control_channel) return AggregatingPropagationCredentialsRepository(control_channel)

View File

@ -1,4 +1,5 @@
import pytest import pytest
from pydantic import SecretStr
from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType from infection_monkey.exploit.powershell_utils.credentials import Credentials, SecretType
from infection_monkey.exploit.powershell_utils.powershell_client import format_password from infection_monkey.exploit.powershell_utils.powershell_client import format_password
@ -14,17 +15,17 @@ def test_format_cached_credentials():
def test_format_password(): def test_format_password():
expected = "test_password" expected = SecretStr("test_password")
creds = Credentials("test_user", expected, SecretType.PASSWORD) creds = Credentials("test_user", expected, SecretType.PASSWORD)
actual = format_password(creds) actual = format_password(creds)
assert expected == actual assert expected.get_secret_value() == actual
def test_format_lm_hash(): def test_format_lm_hash():
lm_hash = "c080132b6f2a0c4e5d1029cc06f48a92" lm_hash = SecretStr("c080132b6f2a0c4e5d1029cc06f48a92")
expected = f"{lm_hash}:00000000000000000000000000000000" expected = f"{lm_hash.get_secret_value()}:00000000000000000000000000000000"
creds = Credentials("test_user", lm_hash, SecretType.LM_HASH) creds = Credentials("test_user", lm_hash, SecretType.LM_HASH)
@ -34,8 +35,8 @@ def test_format_lm_hash():
def test_format_nt_hash(): def test_format_nt_hash():
nt_hash = "c080132b6f2a0c4e5d1029cc06f48a92" nt_hash = SecretStr("c080132b6f2a0c4e5d1029cc06f48a92")
expected = f"00000000000000000000000000000000:{nt_hash}" expected = f"00000000000000000000000000000000:{nt_hash.get_secret_value()}"
creds = Credentials("test_user", nt_hash, SecretType.NT_HASH) creds = Credentials("test_user", nt_hash, SecretType.NT_HASH)

View File

@ -13,13 +13,12 @@ PRIVATE_KEY = "priv_key"
@pytest.fixture @pytest.fixture
def credentials_for_test(): def credentials_for_test():
return Credentials(identity=Username(username=USERNAME), secret=Password(password=PASSWORD))
return Credentials(Username(USERNAME), Password(PASSWORD))
def test_credential_telem_send(spy_send_telemetry, credentials_for_test): def test_credential_telem_send(spy_send_telemetry, credentials_for_test):
expected_data = [Credentials.to_mapping(credentials_for_test)] expected_data = [credentials_for_test.dict(simplify=True)]
telem = CredentialsTelem([credentials_for_test]) telem = CredentialsTelem([credentials_for_test])
telem.send() telem.send()

View File

@ -6,14 +6,14 @@ import pytest
from pymongo import MongoClient from pymongo import MongoClient
from pymongo.collection import Collection from pymongo.collection import Collection
from pymongo.database import Database from pymongo.database import Database
from tests.data_for_tests.propagation_credentials import PROPAGATION_CREDENTIALS from tests.data_for_tests.propagation_credentials import CREDENTIALS
from common.credentials import Credentials from common.credentials import Credentials
from monkey_island.cc.repository import MongoCredentialsRepository from monkey_island.cc.repository import MongoCredentialsRepository
from monkey_island.cc.server_utils.encryption import ILockableEncryptor from monkey_island.cc.server_utils.encryption import ILockableEncryptor
CONFIGURED_CREDENTIALS = PROPAGATION_CREDENTIALS[0:3] CONFIGURED_CREDENTIALS = CREDENTIALS[0:3]
STOLEN_CREDENTIALS = PROPAGATION_CREDENTIALS[3:] STOLEN_CREDENTIALS = CREDENTIALS[3:]
def reverse(data: bytes) -> bytes: def reverse(data: bytes) -> bytes:
@ -59,9 +59,9 @@ def test_mongo_repository_get_all(mongo_repository):
def test_mongo_repository_configured(mongo_repository): def test_mongo_repository_configured(mongo_repository):
mongo_repository.save_configured_credentials(PROPAGATION_CREDENTIALS) mongo_repository.save_configured_credentials(CREDENTIALS)
actual_configured_credentials = mongo_repository.get_configured_credentials() actual_configured_credentials = mongo_repository.get_configured_credentials()
assert actual_configured_credentials == PROPAGATION_CREDENTIALS assert actual_configured_credentials == CREDENTIALS
mongo_repository.remove_configured_credentials() mongo_repository.remove_configured_credentials()
actual_configured_credentials = mongo_repository.get_configured_credentials() actual_configured_credentials = mongo_repository.get_configured_credentials()
@ -82,7 +82,7 @@ def test_mongo_repository_all(mongo_repository):
mongo_repository.save_configured_credentials(CONFIGURED_CREDENTIALS) mongo_repository.save_configured_credentials(CONFIGURED_CREDENTIALS)
mongo_repository.save_stolen_credentials(STOLEN_CREDENTIALS) mongo_repository.save_stolen_credentials(STOLEN_CREDENTIALS)
actual_credentials = mongo_repository.get_all_credentials() actual_credentials = mongo_repository.get_all_credentials()
assert actual_credentials == PROPAGATION_CREDENTIALS assert actual_credentials == CREDENTIALS
mongo_repository.remove_all_credentials() mongo_repository.remove_all_credentials()
@ -91,7 +91,7 @@ def test_mongo_repository_all(mongo_repository):
assert mongo_repository.get_configured_credentials() == [] assert mongo_repository.get_configured_credentials() == []
@pytest.mark.parametrize("credentials", PROPAGATION_CREDENTIALS) @pytest.mark.parametrize("credentials", CREDENTIALS)
def test_configured_secrets_encrypted( def test_configured_secrets_encrypted(
mongo_repository: MongoCredentialsRepository, mongo_repository: MongoCredentialsRepository,
mongo_client: MongoClient, mongo_client: MongoClient,
@ -101,14 +101,14 @@ def test_configured_secrets_encrypted(
check_if_stored_credentials_encrypted(mongo_client, credentials) check_if_stored_credentials_encrypted(mongo_client, credentials)
@pytest.mark.parametrize("credentials", PROPAGATION_CREDENTIALS) @pytest.mark.parametrize("credentials", CREDENTIALS)
def test_stolen_secrets_encrypted(mongo_repository, mongo_client, credentials: Credentials): def test_stolen_secrets_encrypted(mongo_repository, mongo_client, credentials: Credentials):
mongo_repository.save_stolen_credentials([credentials]) mongo_repository.save_stolen_credentials([credentials])
check_if_stored_credentials_encrypted(mongo_client, credentials) check_if_stored_credentials_encrypted(mongo_client, credentials)
def check_if_stored_credentials_encrypted(mongo_client: MongoClient, original_credentials): def check_if_stored_credentials_encrypted(mongo_client: MongoClient, original_credentials):
original_credentials_mapping = Credentials.to_mapping(original_credentials) original_credentials_mapping = original_credentials.dict(simplify=True)
raw_credentials = get_all_credentials_in_mongo(mongo_client) raw_credentials = get_all_credentials_in_mongo(mongo_client)
for rc in raw_credentials: for rc in raw_credentials:
@ -119,6 +119,10 @@ def check_if_stored_credentials_encrypted(mongo_client: MongoClient, original_cr
for key, value in credentials_component.items(): for key, value in credentials_component.items():
assert original_credentials_mapping[identity_or_secret][key] != value.decode() assert original_credentials_mapping[identity_or_secret][key] != value.decode()
# Since secrets use the pydantic.SecretType, make sure we're not just storing
# all '*' characters.
assert "***" not in value.decode()
def get_all_credentials_in_mongo( def get_all_credentials_in_mongo(
mongo_client: MongoClient, mongo_client: MongoClient,

View File

@ -5,15 +5,10 @@ from urllib.parse import urljoin
import pytest import pytest
from tests.common import StubDIContainer from tests.common import StubDIContainer
from tests.data_for_tests.propagation_credentials import ( from tests.data_for_tests.propagation_credentials import LM_HASH, NT_HASH, PASSWORD_1, PASSWORD_2
LM_HASH_CREDENTIALS,
NT_HASH_CREDENTIALS,
PASSWORD_CREDENTIALS_1,
PASSWORD_CREDENTIALS_2,
)
from tests.monkey_island import InMemoryCredentialsRepository from tests.monkey_island import InMemoryCredentialsRepository
from common.credentials import Credentials from common.credentials import Credentials, LMHash, NTHash, Password
from monkey_island.cc.repository import ICredentialsRepository from monkey_island.cc.repository import ICredentialsRepository
from monkey_island.cc.resources import PropagationCredentials from monkey_island.cc.resources import PropagationCredentials
from monkey_island.cc.resources.propagation_credentials import ( from monkey_island.cc.resources.propagation_credentials import (
@ -24,6 +19,10 @@ from monkey_island.cc.resources.propagation_credentials import (
ALL_CREDENTIALS_URL = PropagationCredentials.urls[0] ALL_CREDENTIALS_URL = PropagationCredentials.urls[0]
CONFIGURED_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL + "/", _configured_collection) CONFIGURED_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL + "/", _configured_collection)
STOLEN_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL + "/", _stolen_collection) STOLEN_CREDENTIALS_URL = urljoin(ALL_CREDENTIALS_URL + "/", _stolen_collection)
CREDENTIALS_1 = Credentials(identity=None, secret=Password(password=PASSWORD_1))
CREDENTIALS_2 = Credentials(identity=None, secret=LMHash(lm_hash=LM_HASH))
CREDENTIALS_3 = Credentials(identity=None, secret=NTHash(nt_hash=NT_HASH))
CREDENTIALS_4 = Credentials(identity=None, secret=Password(password=PASSWORD_2))
@pytest.fixture @pytest.fixture
@ -42,20 +41,18 @@ def flask_client(build_flask_client, credentials_repository):
def test_propagation_credentials_endpoint_get(flask_client, credentials_repository): def test_propagation_credentials_endpoint_get(flask_client, credentials_repository):
credentials_repository.save_configured_credentials( credentials_repository.save_configured_credentials([CREDENTIALS_1, CREDENTIALS_2])
[PASSWORD_CREDENTIALS_1, NT_HASH_CREDENTIALS] credentials_repository.save_stolen_credentials([CREDENTIALS_3, CREDENTIALS_4])
)
credentials_repository.save_stolen_credentials([LM_HASH_CREDENTIALS, PASSWORD_CREDENTIALS_2])
resp = flask_client.get(ALL_CREDENTIALS_URL) resp = flask_client.get(ALL_CREDENTIALS_URL)
actual_propagation_credentials = [Credentials.from_mapping(creds) for creds in resp.json] actual_propagation_credentials = [Credentials(**creds) for creds in resp.json]
assert resp.status_code == HTTPStatus.OK assert resp.status_code == HTTPStatus.OK
assert len(actual_propagation_credentials) == 4 assert len(actual_propagation_credentials) == 4
assert PASSWORD_CREDENTIALS_1 in actual_propagation_credentials assert CREDENTIALS_1 in actual_propagation_credentials
assert LM_HASH_CREDENTIALS in actual_propagation_credentials assert CREDENTIALS_2 in actual_propagation_credentials
assert NT_HASH_CREDENTIALS in actual_propagation_credentials assert CREDENTIALS_3 in actual_propagation_credentials
assert PASSWORD_CREDENTIALS_2 in actual_propagation_credentials assert CREDENTIALS_4 in actual_propagation_credentials
def pre_populate_repository( def pre_populate_repository(
@ -69,24 +66,22 @@ def pre_populate_repository(
@pytest.mark.parametrize("url", [CONFIGURED_CREDENTIALS_URL, STOLEN_CREDENTIALS_URL]) @pytest.mark.parametrize("url", [CONFIGURED_CREDENTIALS_URL, STOLEN_CREDENTIALS_URL])
def test_propagation_credentials_endpoint__get_stolen(flask_client, credentials_repository, url): def test_propagation_credentials_endpoint__get_stolen(flask_client, credentials_repository, url):
pre_populate_repository( pre_populate_repository(url, credentials_repository, [CREDENTIALS_1, CREDENTIALS_2])
url, credentials_repository, [PASSWORD_CREDENTIALS_1, LM_HASH_CREDENTIALS]
)
resp = flask_client.get(url) resp = flask_client.get(url)
actual_propagation_credentials = [Credentials.from_mapping(creds) for creds in resp.json] actual_propagation_credentials = [Credentials(**creds) for creds in resp.json]
assert resp.status_code == HTTPStatus.OK assert resp.status_code == HTTPStatus.OK
assert len(actual_propagation_credentials) == 2 assert len(actual_propagation_credentials) == 2
assert actual_propagation_credentials[0] == PASSWORD_CREDENTIALS_1 assert actual_propagation_credentials[0].secret.password == PASSWORD_1
assert actual_propagation_credentials[1] == LM_HASH_CREDENTIALS assert actual_propagation_credentials[1].secret.lm_hash == LM_HASH
def test_configured_propagation_credentials_endpoint_put(flask_client, credentials_repository): def test_configured_propagation_credentials_endpoint_put(flask_client, credentials_repository):
pre_populate_repository( pre_populate_repository(
CONFIGURED_CREDENTIALS_URL, CONFIGURED_CREDENTIALS_URL,
credentials_repository, credentials_repository,
[PASSWORD_CREDENTIALS_1, LM_HASH_CREDENTIALS], [CREDENTIALS_1, CREDENTIALS_2],
) )
resp = flask_client.put(CONFIGURED_CREDENTIALS_URL, json=[]) resp = flask_client.put(CONFIGURED_CREDENTIALS_URL, json=[])
assert resp.status_code == HTTPStatus.NO_CONTENT assert resp.status_code == HTTPStatus.NO_CONTENT

View File

@ -1,64 +1,29 @@
from common.credentials import ( from tests.data_for_tests.propagation_credentials import FULL_CREDENTIALS, USERNAME
CredentialComponentType,
Credentials,
LMHash,
NTHash,
Password,
SSHKeypair,
Username,
)
from monkey_island.cc.services.reporting import format_creds_for_reporting from monkey_island.cc.services.reporting import format_creds_for_reporting
monkey_hostname = "fake_hostname"
fake_monkey_guid = "abc"
fake_username = Username("m0nk3y_user")
fake_nt_hash = NTHash("AEBD4DE384C7EC43AAD3B435B51404EE")
fake_lm_hash = LMHash("7A21990FCD3D759941E45C490F143D5F")
fake_password = Password("trytostealthis")
fake_ssh_public_key = "RSA_public_key"
fake_ssh_private_key = "RSA_private_key"
fake_ssh_key = SSHKeypair(fake_ssh_private_key, fake_ssh_public_key)
identities = (fake_username,)
secrets = (fake_nt_hash, fake_lm_hash, fake_password, fake_ssh_key)
fake_credentials = [
Credentials(fake_username, fake_nt_hash),
Credentials(fake_username, fake_lm_hash),
Credentials(fake_username, fake_password),
Credentials(fake_username, fake_ssh_key),
Credentials(None, fake_ssh_key),
Credentials(fake_username, None),
]
def test_formatting_credentials_for_report(): def test_formatting_credentials_for_report():
credentials = format_creds_for_reporting(fake_credentials) credentials = format_creds_for_reporting(FULL_CREDENTIALS)
result1 = { result1 = {
"_type": CredentialComponentType.NT_HASH.name,
"type": "NTLM hash", "type": "NTLM hash",
"username": fake_username.username, "username": USERNAME,
} }
result2 = { result2 = {
"_type": CredentialComponentType.LM_HASH.name,
"type": "LM hash", "type": "LM hash",
"username": fake_username.username, "username": USERNAME,
} }
result3 = { result3 = {
"_type": CredentialComponentType.PASSWORD.name,
"type": "Clear Password", "type": "Clear Password",
"username": fake_username.username, "username": USERNAME,
} }
result4 = { result4 = {
"_type": CredentialComponentType.SSH_KEYPAIR.name,
"type": "Clear SSH private key", "type": "Clear SSH private key",
"username": fake_username.username, "username": USERNAME,
} }
result5 = { result5 = {
"_type": CredentialComponentType.SSH_KEYPAIR.name,
"type": "Clear SSH private key", "type": "Clear SSH private key",
"username": "", "username": "",
} }

View File

@ -7,8 +7,7 @@ from common.agent_configuration.agent_sub_configurations import (
CustomPBAConfiguration, CustomPBAConfiguration,
ScanTargetConfiguration, ScanTargetConfiguration,
) )
from common.credentials import Credentials from common.credentials import Credentials, LMHash, NTHash
from common.utils import IJSONSerializable
from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory
from monkey_island.cc.event_queue import IslandEventTopic, PyPubSubIslandEventQueue from monkey_island.cc.event_queue import IslandEventTopic, PyPubSubIslandEventQueue
from monkey_island.cc.models import Report from monkey_island.cc.models import Report
@ -166,6 +165,9 @@ LDAPServerFactory.buildProtocol
get_file_sha256_hash get_file_sha256_hash
strict_slashes # unused attribute (monkey/monkey_island/cc/app.py:96) strict_slashes # unused attribute (monkey/monkey_island/cc/app.py:96)
post_breach_actions # unused variable (monkey\infection_monkey\config.py:95) post_breach_actions # unused variable (monkey\infection_monkey\config.py:95)
LMHash.validate_hash_format
NTHash.validate_hash_format
Credentials.Config.json_encoders
# Deployments # Deployments
DEVELOP # unused variable (monkey/monkey/monkey_island/cc/deployment.py:5) DEVELOP # unused variable (monkey/monkey/monkey_island/cc/deployment.py:5)
@ -314,8 +316,9 @@ EXPLOITED
CC CC
CC_TUNNEL CC_TUNNEL
Credentials.from_json # TODO Remove with #2217
IJSONSerializable.from_json IJSONSerializable
from_json
IslandEventTopic.AGENT_CONNECTED IslandEventTopic.AGENT_CONNECTED
IslandEventTopic.CLEAR_SIMULATION_DATA IslandEventTopic.CLEAR_SIMULATION_DATA