bhshell 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bhshell-0.1.0/LICENSE +21 -0
- bhshell-0.1.0/MANIFEST.in +3 -0
- bhshell-0.1.0/PKG-INFO +134 -0
- bhshell-0.1.0/README.md +99 -0
- bhshell-0.1.0/bhshell/__init__.py +3 -0
- bhshell-0.1.0/bhshell/__main__.py +3 -0
- bhshell-0.1.0/bhshell/cli.py +55 -0
- bhshell-0.1.0/bhshell/client/__init__.py +0 -0
- bhshell-0.1.0/bhshell/client/auth.py +74 -0
- bhshell-0.1.0/bhshell/client/client.py +337 -0
- bhshell-0.1.0/bhshell/config.py +162 -0
- bhshell-0.1.0/bhshell/errors.py +73 -0
- bhshell-0.1.0/bhshell/library/asrep-roastable-users.cyp +3 -0
- bhshell-0.1.0/bhshell/library/dcsync-principals.cyp +3 -0
- bhshell-0.1.0/bhshell/library/domain-admins-members.cyp +3 -0
- bhshell-0.1.0/bhshell/library/domain-trusts.cyp +3 -0
- bhshell-0.1.0/bhshell/library/gpos-linked-to-domain-controllers.cyp +3 -0
- bhshell-0.1.0/bhshell/library/kerberoastable-users.cyp +3 -0
- bhshell-0.1.0/bhshell/library/owned-objects.cyp +3 -0
- bhshell-0.1.0/bhshell/library/owned-to-tier-zero.cyp +3 -0
- bhshell-0.1.0/bhshell/library/sid-history.cyp +3 -0
- bhshell-0.1.0/bhshell/library/tier-zero-users.cyp +3 -0
- bhshell-0.1.0/bhshell/library/unconstrained-delegation.cyp +3 -0
- bhshell-0.1.0/bhshell/models/__init__.py +0 -0
- bhshell-0.1.0/bhshell/models/entities.py +402 -0
- bhshell-0.1.0/bhshell/models/results.py +83 -0
- bhshell-0.1.0/bhshell/queries/__init__.py +0 -0
- bhshell-0.1.0/bhshell/queries/cypher.py +155 -0
- bhshell-0.1.0/bhshell/queries/domains.py +136 -0
- bhshell-0.1.0/bhshell/queries/edges.py +109 -0
- bhshell-0.1.0/bhshell/queries/hunt.py +675 -0
- bhshell-0.1.0/bhshell/queries/library.py +94 -0
- bhshell-0.1.0/bhshell/queries/marking.py +121 -0
- bhshell-0.1.0/bhshell/queries/paths.py +369 -0
- bhshell-0.1.0/bhshell/queries/search.py +196 -0
- bhshell-0.1.0/bhshell/queries/service.py +529 -0
- bhshell-0.1.0/bhshell/render/__init__.py +0 -0
- bhshell-0.1.0/bhshell/render/export.py +79 -0
- bhshell-0.1.0/bhshell/render/graph.py +326 -0
- bhshell-0.1.0/bhshell/render/layout.py +138 -0
- bhshell-0.1.0/bhshell/render/png.py +44 -0
- bhshell-0.1.0/bhshell/render/styles.py +5 -0
- bhshell-0.1.0/bhshell/render/svg.py +495 -0
- bhshell-0.1.0/bhshell/render/views.py +337 -0
- bhshell-0.1.0/bhshell/shell/__init__.py +0 -0
- bhshell-0.1.0/bhshell/shell/app.py +189 -0
- bhshell-0.1.0/bhshell/shell/commands/__init__.py +0 -0
- bhshell-0.1.0/bhshell/shell/commands/base.py +60 -0
- bhshell-0.1.0/bhshell/shell/commands/connection.py +236 -0
- bhshell-0.1.0/bhshell/shell/commands/cypher.py +97 -0
- bhshell-0.1.0/bhshell/shell/commands/domains.py +80 -0
- bhshell-0.1.0/bhshell/shell/commands/entities.py +88 -0
- bhshell-0.1.0/bhshell/shell/commands/hunt.py +151 -0
- bhshell-0.1.0/bhshell/shell/commands/library.py +56 -0
- bhshell-0.1.0/bhshell/shell/commands/marking.py +59 -0
- bhshell-0.1.0/bhshell/shell/commands/paths.py +166 -0
- bhshell-0.1.0/bhshell/shell/commands/search.py +125 -0
- bhshell-0.1.0/bhshell.egg-info/PKG-INFO +134 -0
- bhshell-0.1.0/bhshell.egg-info/SOURCES.txt +76 -0
- bhshell-0.1.0/bhshell.egg-info/dependency_links.txt +1 -0
- bhshell-0.1.0/bhshell.egg-info/entry_points.txt +2 -0
- bhshell-0.1.0/bhshell.egg-info/requires.txt +8 -0
- bhshell-0.1.0/bhshell.egg-info/top_level.txt +1 -0
- bhshell-0.1.0/docs/USAGE.md +317 -0
- bhshell-0.1.0/pyproject.toml +69 -0
- bhshell-0.1.0/setup.cfg +4 -0
- bhshell-0.1.0/tests/__init__.py +0 -0
- bhshell-0.1.0/tests/conftest.py +174 -0
- bhshell-0.1.0/tests/test_analysis.py +339 -0
- bhshell-0.1.0/tests/test_client.py +184 -0
- bhshell-0.1.0/tests/test_disabled_paths.py +162 -0
- bhshell-0.1.0/tests/test_graph.py +48 -0
- bhshell-0.1.0/tests/test_hunt.py +216 -0
- bhshell-0.1.0/tests/test_png.py +61 -0
- bhshell-0.1.0/tests/test_search.py +123 -0
- bhshell-0.1.0/tests/test_service.py +338 -0
- bhshell-0.1.0/tests/test_shell.py +243 -0
- bhshell-0.1.0/tests/test_svg.py +171 -0
bhshell-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kr0no
|
|
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.
|
bhshell-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bhshell
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Interactive shell for BloodHound
|
|
5
|
+
Author-email: kr0no <kr0no@ka0labs.net>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kr0no/bloodhound-shell
|
|
8
|
+
Project-URL: Issues, https://github.com/kr0no/bloodhound-shell/issues
|
|
9
|
+
Project-URL: Author, https://kr0no.me
|
|
10
|
+
Keywords: bloodhound,bloodhound-ce,active-directory,attack-paths,cypher,pentest,red-team,security,cli,shell
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Information Technology
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: cmd2<5,>=4.3
|
|
28
|
+
Requires-Dist: rich>=13.0
|
|
29
|
+
Requires-Dist: requests>=2.31
|
|
30
|
+
Requires-Dist: resvg-py>=0.5
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
33
|
+
Requires-Dist: responses>=0.25; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# bhshell
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/bhshell/)
|
|
39
|
+
[](https://pypi.org/project/bhshell/)
|
|
40
|
+
[](https://github.com/kr0no/bloodhound-shell/actions/workflows/ci.yml)
|
|
41
|
+
[](https://github.com/kr0no/bloodhound-shell/blob/main/LICENSE)
|
|
42
|
+
|
|
43
|
+
An interactive command-line shell for BloodHound. Look up Active Directory objects,
|
|
44
|
+
hunt for weaknesses and find attack paths from the terminal, without opening the web UI.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
Requires Python 3.11+ and a BloodHound CE v9 server (tested on v9.7.1).
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pipx install bhshell # recommended
|
|
52
|
+
# or
|
|
53
|
+
pip install bhshell
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick Start
|
|
57
|
+
|
|
58
|
+
```console
|
|
59
|
+
$ bhshell --url http://127.0.0.1:8080
|
|
60
|
+
|
|
61
|
+
[bh: shell] > login -u admin
|
|
62
|
+
[+] Logged in as admin on 127.0.0.1:8080
|
|
63
|
+
|
|
64
|
+
[bh: shell] > hunt
|
|
65
|
+
Severity Check Count Example Description
|
|
66
|
+
critical dcsync 2 SVC_SQL@CORP.LOCAL Non-Tier-Zero principals with replication rights
|
|
67
|
+
high kerberoastable 2 JDOE@CORP.LOCAL Kerberoastable users
|
|
68
|
+
...
|
|
69
|
+
[*] Details: 'hunt CHECK' (e.g. 'hunt kerberoastable'), or 'hunt --all'.
|
|
70
|
+
|
|
71
|
+
[bh: shell] > user CORP\svc_sql --all -o svc_sql.csv
|
|
72
|
+
|
|
73
|
+
[bh: shell] > path --owned -g
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The same commands run non-interactively for scripting: `bhshell -c "hunt --json" > hunt.json`.
|
|
77
|
+
|
|
78
|
+
## What it does
|
|
79
|
+
|
|
80
|
+
| Command | Shows |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `user`, `computer`, `group`, `lookup` | Kerberoastable / AS-REP roastable, delegation, password age, Tier Zero and owned flags, plus group nesting, inbound/outbound ACL control, sessions and local admin rights |
|
|
83
|
+
| `search` | Objects matching property filters (`search -t user hasspn enabled`), with dates, ages (`90d`) and wildcards |
|
|
84
|
+
| `path` | Shortest paths to a target or to Tier Zero, from everything owned, or through `--via` steps; printed as hop chains, a terminal graph, or SVG/PNG for reports |
|
|
85
|
+
| `hunt` | Domain-wide checks by severity: DCSync, control of Tier Zero, AD CS (ESC*), Kerberoasting, privileged sessions, LAPS/gMSA readers, passwords in attributes, takeover primitives (Shadow Credentials, RBCD), GPO control, NTLM relay, trusts, possible honeytokens...; `hunt --owned` limits them to what your owned objects can do |
|
|
86
|
+
| `cypher`, `query` | Raw Cypher, and a library of saved queries: bundled, your own `.cyp` files (`~/.bhshell/queries/` or any folder set with `config query_dirs`) and those saved on the server |
|
|
87
|
+
| `owned`, `hvt` | Mark or unmark owned and Tier Zero objects |
|
|
88
|
+
| `domains`, `trusts` | Per-domain counts, and the trust map with SID filtering status |
|
|
89
|
+
|
|
90
|
+
To add your own query, save it as a `.cyp` file in `~/.bhshell/queries/` and run it by name:
|
|
91
|
+
|
|
92
|
+
```console
|
|
93
|
+
$ cat ~/.bhshell/queries/stale-admins.cyp
|
|
94
|
+
// description: Enabled adminCount users who have not logged on for a year
|
|
95
|
+
MATCH (u:User) WHERE u.admincount = true AND u.enabled = true
|
|
96
|
+
AND u.lastlogontimestamp < (datetime().epochseconds - 31536000) RETURN u
|
|
97
|
+
|
|
98
|
+
[bh: shell] > query run stale-admins -o stale-admins.csv
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
See [Query library](https://github.com/kr0no/bloodhound-shell/blob/main/docs/USAGE.md#query-library) for the format and for
|
|
102
|
+
keeping queries in other folders (a team repository, one folder per engagement).
|
|
103
|
+
|
|
104
|
+
Most commands take `--json` and `-o FILE` (JSON or CSV). Login works with a password (plus MFA) or
|
|
105
|
+
an API token; secrets are prompted for or read from the environment, never passed as arguments.
|
|
106
|
+
|
|
107
|
+
## Documentation
|
|
108
|
+
|
|
109
|
+
The full usage guide (every command, view flag, search filter, path option, hunt check and
|
|
110
|
+
configuration setting) is in [docs/USAGE.md](https://github.com/kr0no/bloodhound-shell/blob/main/docs/USAGE.md).
|
|
111
|
+
Inside the shell, run `help` or `help <command>`.
|
|
112
|
+
|
|
113
|
+
## Contributing
|
|
114
|
+
|
|
115
|
+
Bug reports and pull requests are welcome at
|
|
116
|
+
[github.com/kr0no/bloodhound-shell/issues](https://github.com/kr0no/bloodhound-shell/issues).
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
git clone https://github.com/kr0no/bloodhound-shell.git
|
|
120
|
+
cd bloodhound-shell
|
|
121
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
122
|
+
pip install -e '.[dev]'
|
|
123
|
+
python -m pytest
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The tests mock the BloodHound API with `responses`, so no BloodHound server is needed.
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
Distributed under the MIT License. See
|
|
131
|
+
[LICENSE](https://github.com/kr0no/bloodhound-shell/blob/main/LICENSE) for details.
|
|
132
|
+
|
|
133
|
+
BloodHound is a trademark of SpecterOps. bhshell is an independent project and is not affiliated
|
|
134
|
+
with or endorsed by SpecterOps.
|
bhshell-0.1.0/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# bhshell
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/bhshell/)
|
|
4
|
+
[](https://pypi.org/project/bhshell/)
|
|
5
|
+
[](https://github.com/kr0no/bloodhound-shell/actions/workflows/ci.yml)
|
|
6
|
+
[](https://github.com/kr0no/bloodhound-shell/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
An interactive command-line shell for BloodHound. Look up Active Directory objects,
|
|
9
|
+
hunt for weaknesses and find attack paths from the terminal, without opening the web UI.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Requires Python 3.11+ and a BloodHound CE v9 server (tested on v9.7.1).
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pipx install bhshell # recommended
|
|
17
|
+
# or
|
|
18
|
+
pip install bhshell
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```console
|
|
24
|
+
$ bhshell --url http://127.0.0.1:8080
|
|
25
|
+
|
|
26
|
+
[bh: shell] > login -u admin
|
|
27
|
+
[+] Logged in as admin on 127.0.0.1:8080
|
|
28
|
+
|
|
29
|
+
[bh: shell] > hunt
|
|
30
|
+
Severity Check Count Example Description
|
|
31
|
+
critical dcsync 2 SVC_SQL@CORP.LOCAL Non-Tier-Zero principals with replication rights
|
|
32
|
+
high kerberoastable 2 JDOE@CORP.LOCAL Kerberoastable users
|
|
33
|
+
...
|
|
34
|
+
[*] Details: 'hunt CHECK' (e.g. 'hunt kerberoastable'), or 'hunt --all'.
|
|
35
|
+
|
|
36
|
+
[bh: shell] > user CORP\svc_sql --all -o svc_sql.csv
|
|
37
|
+
|
|
38
|
+
[bh: shell] > path --owned -g
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The same commands run non-interactively for scripting: `bhshell -c "hunt --json" > hunt.json`.
|
|
42
|
+
|
|
43
|
+
## What it does
|
|
44
|
+
|
|
45
|
+
| Command | Shows |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `user`, `computer`, `group`, `lookup` | Kerberoastable / AS-REP roastable, delegation, password age, Tier Zero and owned flags, plus group nesting, inbound/outbound ACL control, sessions and local admin rights |
|
|
48
|
+
| `search` | Objects matching property filters (`search -t user hasspn enabled`), with dates, ages (`90d`) and wildcards |
|
|
49
|
+
| `path` | Shortest paths to a target or to Tier Zero, from everything owned, or through `--via` steps; printed as hop chains, a terminal graph, or SVG/PNG for reports |
|
|
50
|
+
| `hunt` | Domain-wide checks by severity: DCSync, control of Tier Zero, AD CS (ESC*), Kerberoasting, privileged sessions, LAPS/gMSA readers, passwords in attributes, takeover primitives (Shadow Credentials, RBCD), GPO control, NTLM relay, trusts, possible honeytokens...; `hunt --owned` limits them to what your owned objects can do |
|
|
51
|
+
| `cypher`, `query` | Raw Cypher, and a library of saved queries: bundled, your own `.cyp` files (`~/.bhshell/queries/` or any folder set with `config query_dirs`) and those saved on the server |
|
|
52
|
+
| `owned`, `hvt` | Mark or unmark owned and Tier Zero objects |
|
|
53
|
+
| `domains`, `trusts` | Per-domain counts, and the trust map with SID filtering status |
|
|
54
|
+
|
|
55
|
+
To add your own query, save it as a `.cyp` file in `~/.bhshell/queries/` and run it by name:
|
|
56
|
+
|
|
57
|
+
```console
|
|
58
|
+
$ cat ~/.bhshell/queries/stale-admins.cyp
|
|
59
|
+
// description: Enabled adminCount users who have not logged on for a year
|
|
60
|
+
MATCH (u:User) WHERE u.admincount = true AND u.enabled = true
|
|
61
|
+
AND u.lastlogontimestamp < (datetime().epochseconds - 31536000) RETURN u
|
|
62
|
+
|
|
63
|
+
[bh: shell] > query run stale-admins -o stale-admins.csv
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
See [Query library](https://github.com/kr0no/bloodhound-shell/blob/main/docs/USAGE.md#query-library) for the format and for
|
|
67
|
+
keeping queries in other folders (a team repository, one folder per engagement).
|
|
68
|
+
|
|
69
|
+
Most commands take `--json` and `-o FILE` (JSON or CSV). Login works with a password (plus MFA) or
|
|
70
|
+
an API token; secrets are prompted for or read from the environment, never passed as arguments.
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
The full usage guide (every command, view flag, search filter, path option, hunt check and
|
|
75
|
+
configuration setting) is in [docs/USAGE.md](https://github.com/kr0no/bloodhound-shell/blob/main/docs/USAGE.md).
|
|
76
|
+
Inside the shell, run `help` or `help <command>`.
|
|
77
|
+
|
|
78
|
+
## Contributing
|
|
79
|
+
|
|
80
|
+
Bug reports and pull requests are welcome at
|
|
81
|
+
[github.com/kr0no/bloodhound-shell/issues](https://github.com/kr0no/bloodhound-shell/issues).
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/kr0no/bloodhound-shell.git
|
|
85
|
+
cd bloodhound-shell
|
|
86
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
87
|
+
pip install -e '.[dev]'
|
|
88
|
+
python -m pytest
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The tests mock the BloodHound API with `responses`, so no BloodHound server is needed.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
Distributed under the MIT License. See
|
|
96
|
+
[LICENSE](https://github.com/kr0no/bloodhound-shell/blob/main/LICENSE) for details.
|
|
97
|
+
|
|
98
|
+
BloodHound is a trademark of SpecterOps. bhshell is an independent project and is not affiliated
|
|
99
|
+
with or endorsed by SpecterOps.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Command-line entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import Sequence
|
|
8
|
+
|
|
9
|
+
from cmd2.string_utils import quote_if_needed
|
|
10
|
+
|
|
11
|
+
from bhshell import __version__
|
|
12
|
+
from bhshell.client.client import normalize_url
|
|
13
|
+
from bhshell.config import Config
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
17
|
+
p = argparse.ArgumentParser(prog="bhshell", description="Interactive shell for BloodHound.")
|
|
18
|
+
p.add_argument("--url", help="BH backend URL (persisted; default http://127.0.0.1:8080/)")
|
|
19
|
+
p.add_argument("-k", "--insecure", action="store_true", help="disable TLS certificate verification (persisted; 'tls on' re-enables it)")
|
|
20
|
+
p.add_argument("--no-resume", action="store_true", help="do not resume the cached session")
|
|
21
|
+
p.add_argument("-c", "--command", action="append", default=[], metavar="CMD",
|
|
22
|
+
help="run CMD and exit (repeatable)")
|
|
23
|
+
p.add_argument("-s", "--script", metavar="FILE", help="run commands from FILE and exit")
|
|
24
|
+
p.add_argument("--debug", action="store_true", help="show tracebacks for errors")
|
|
25
|
+
p.add_argument("--version", action="version", version=f"bhshell {__version__}")
|
|
26
|
+
return p
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
30
|
+
args = build_parser().parse_args(argv)
|
|
31
|
+
config = Config.load()
|
|
32
|
+
changed = False
|
|
33
|
+
if args.url:
|
|
34
|
+
try:
|
|
35
|
+
config.url = normalize_url(args.url)
|
|
36
|
+
except ValueError as exc:
|
|
37
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
38
|
+
return 2
|
|
39
|
+
changed = True
|
|
40
|
+
if args.insecure:
|
|
41
|
+
config.verify_tls = False
|
|
42
|
+
changed = True
|
|
43
|
+
if changed:
|
|
44
|
+
config.save()
|
|
45
|
+
|
|
46
|
+
from bhshell.shell.app import BHShell # imported late so '--help' stays fast
|
|
47
|
+
|
|
48
|
+
app = BHShell(config, debug=args.debug, resume=not args.no_resume)
|
|
49
|
+
if args.command or args.script:
|
|
50
|
+
commands = list(args.command)
|
|
51
|
+
if args.script:
|
|
52
|
+
commands.append(f"run_script {quote_if_needed(args.script)}")
|
|
53
|
+
app.runcmds_plus_hooks(commands)
|
|
54
|
+
return app.exit_code or (1 if app.command_failures else 0)
|
|
55
|
+
return app.cmdloop() or 0
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Request authenticators for the BH API.
|
|
2
|
+
|
|
3
|
+
Two schemes are supported:
|
|
4
|
+
|
|
5
|
+
* `JWTAuth` - `Authorization: Bearer <session_token>` obtained from
|
|
6
|
+
`POST /api/v2/login`.
|
|
7
|
+
* `HMACAuth` - BH API tokens (Token ID + Token Key). Each request is
|
|
8
|
+
signed with a chained HMAC-SHA256 as documented by SpecterOps:
|
|
9
|
+
|
|
10
|
+
1. `k1 = HMAC(token_key, METHOD + URI)`
|
|
11
|
+
2. `k2 = HMAC(k1, RequestDate[:13])` (RFC3339 date truncated to the hour)
|
|
12
|
+
3. `signature = HMAC(k2, body)`
|
|
13
|
+
|
|
14
|
+
Headers: `Authorization: bhesignature <token_id>`, `RequestDate` and
|
|
15
|
+
`Signature` (base64).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import base64
|
|
21
|
+
import hashlib
|
|
22
|
+
import hmac
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
from datetime import datetime
|
|
25
|
+
from urllib.parse import urlsplit
|
|
26
|
+
|
|
27
|
+
from requests import PreparedRequest
|
|
28
|
+
from requests.auth import AuthBase
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class JWTAuth(AuthBase):
|
|
32
|
+
def __init__(self, token: str) -> None:
|
|
33
|
+
self.token = token
|
|
34
|
+
|
|
35
|
+
def __call__(self, r: PreparedRequest) -> PreparedRequest:
|
|
36
|
+
r.headers["Authorization"] = f"Bearer {self.token}"
|
|
37
|
+
return r
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class HMACAuth(AuthBase):
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
token_id: str,
|
|
44
|
+
token_key: str,
|
|
45
|
+
*,
|
|
46
|
+
clock: Callable[[], datetime] | None = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
self.token_id = token_id
|
|
49
|
+
self._key = token_key.encode()
|
|
50
|
+
self._clock = clock or (lambda: datetime.now().astimezone())
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def _request_uri(url: str) -> str:
|
|
54
|
+
parts = urlsplit(url)
|
|
55
|
+
return parts.path + (f"?{parts.query}" if parts.query else "")
|
|
56
|
+
|
|
57
|
+
def sign(self, method: str, uri: str, body: bytes | None, request_date: str) -> str:
|
|
58
|
+
digester = hmac.new(self._key, None, hashlib.sha256)
|
|
59
|
+
digester.update(f"{method.upper()}{uri}".encode())
|
|
60
|
+
digester = hmac.new(digester.digest(), None, hashlib.sha256)
|
|
61
|
+
digester.update(request_date[:13].encode())
|
|
62
|
+
digester = hmac.new(digester.digest(), None, hashlib.sha256)
|
|
63
|
+
if body:
|
|
64
|
+
digester.update(body)
|
|
65
|
+
return base64.b64encode(digester.digest()).decode()
|
|
66
|
+
|
|
67
|
+
def __call__(self, r: PreparedRequest) -> PreparedRequest:
|
|
68
|
+
request_date = self._clock().isoformat("T")
|
|
69
|
+
body = r.body.encode() if isinstance(r.body, str) else r.body
|
|
70
|
+
signature = self.sign(r.method or "GET", self._request_uri(r.url or ""), body, request_date)
|
|
71
|
+
r.headers["Authorization"] = f"bhesignature {self.token_id}"
|
|
72
|
+
r.headers["RequestDate"] = request_date
|
|
73
|
+
r.headers["Signature"] = signature
|
|
74
|
+
return r
|