Skip to content

Commit dc8e8ac

Browse files
authored
Merge pull request #231 from AD-Security/dev
Release
2 parents 289b1ce + 31a8cf9 commit dc8e8ac

79 files changed

Lines changed: 2028 additions & 1481 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ AD_Miner/sources/modules/temporary*
55
*node_modules*
66
/tests
77
/test
8-
8+
evolution_data/
99

1010
# Byte-compiled / optimized / DLL files
1111
__pycache__/
@@ -170,6 +170,3 @@ cython_debug/
170170
# and can be added to the global gitignore or merged into this file. For a more nuclear
171171
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
172172
#.idea/
173-
174-
# VS Code
175-
.vscode/

Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM python:3.11-slim
2+
3+
# Set the working directory in the container
4+
WORKDIR /tmp
5+
6+
# Install necessary system dependencies for Git
7+
RUN apt-get update && apt-get install -y git \
8+
&& rm -rf /var/lib/apt/lists/*
9+
10+
# Install AD-Miner from the Git repository
11+
RUN pip install --no-cache-dir 'git+https://github.com/AD-Security/AD_Miner.git'

README.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ To run AD Miner, you first need a neo4j database which contains the Active Direc
8282

8383
The easier way is to do the following command using `pipx`:
8484
```shell
85-
pipx install 'git+https://github.com/Mazars-Tech/AD_Miner.git'
85+
pipx install 'git+https://github.com/AD-Security/AD_Miner.git'
8686
```
8787

8888
ADMiner is also available on some Linux distributions:
@@ -92,6 +92,26 @@ ADMiner is also available on some Linux distributions:
9292
- BlackArch: `pacman -S ad-miner`
9393
- NixOS: `nix-env -iA nixos.ad-miner`
9494

95+
A Docker image is available to build. Build the image with the following commmand:
96+
97+
```sh
98+
docker build -t ad-miner .
99+
```
100+
101+
To run this on Windows with the BloodHound Community Edition data, use the commands below:
102+
103+
```sh
104+
docker run -v ${PWD}:/tmp ad-miner AD-miner -b bolt://host.docker.internal:7687 -u neo4j -p mypassword -cf YOUR_PREFIX
105+
```
106+
107+
To run this on Linux with the BloodHound Community Edition data, use the commands below:
108+
109+
```sh
110+
docker run -v ${PWD}:/tmp --network host ad-miner AD-miner -b bolt://localhost:7687 -u neo4j -p mypassword -cf YOUR_PREFIX
111+
```
112+
113+
Note that mounting the volume with `-v` is critical to get the output of the data. This assumes that the BHCE server is running on the Docker host with default settings.
114+
95115
## Usage ##
96116

97117
Run the tool:
@@ -154,13 +174,15 @@ In the graph pages, you can right-click on the graph nodes to cluster them or to
154174

155175
If you have multiple AD-Miner reports over time, you can easily track the evolution with the `--evolution` argument: each AD-Miner report generates a JSON data file alongside the `index.html` file. You just need to gather these different JSON files into a single folder and specify the path to that folder after the `--evolution` argument.
156176

157-
A tab called 'Evolution over time' then appears on the main page.
177+
AD-miner -c -cf My_Report -b bolt://server:7687 -u neo4j -p mypassword -r 180 --evolution evolution_folder/
178+
179+
An 'Evolution over time' tab appears on the main page, providing evolution graphs for each category (Permissions, Passwords, Kerberos, and Misc).
158180

159181
<p align="center">
160182
<img src="doc/img/evolution2.png" style="height:400px">
161183
</p>
162184

163-
Also, views by categories 'permissions,' 'passwords,' 'kerberos' also allow you to track changes over time.
185+
Detailed evolution for each control is also available and can be accessed via the “Show evolution” button for each category. A logarithmic scale is available to better highlight subtle variations over time.
164186

165187
<p align="center">
166188
<img src="doc/img/evol.gif" style="height:200px">

ad_miner/__main__.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
import traceback
99
import signal
1010
import sys
11+
import subprocess
12+
import requests
13+
from importlib.metadata import version, PackageNotFoundError
1114

1215
# Local library imports
1316
from ad_miner.sources.modules import logger, utils, generic_formating, main_page
@@ -142,6 +145,48 @@ def prepare_render(arguments) -> None:
142145
shutil.copy2(js_file, folder_name / "js")
143146

144147

148+
def get_version_and_commit():
149+
# Either using pip(x) you get the version number or in dev environment the git commit
150+
try:
151+
ver = version("ad-miner")
152+
except PackageNotFoundError:
153+
ver = "unknown"
154+
155+
commit = ""
156+
try:
157+
root = Path(__file__).resolve().parent
158+
commit = subprocess.check_output(
159+
["git", "rev-parse", "--short", "HEAD"], cwd=root, stderr=subprocess.DEVNULL, text=True
160+
).strip()
161+
except Exception:
162+
commit = "unknown"
163+
return ver, commit
164+
165+
166+
def get_last_version():
167+
try:
168+
r = requests.get("https://www.ad-miner.com/version.json", timeout=0.5)
169+
data = r.json()
170+
return data["lastversion"]
171+
except Exception:
172+
return "unreachable"
173+
174+
175+
def check_version(lastversion, currentversion):
176+
try:
177+
last = tuple(map(int, lastversion.lstrip("v").split(".")))
178+
current = tuple(map(int, currentversion.lstrip("v").split(".")))
179+
180+
if last > current:
181+
logger.print_error(f"New AD Miner version {lastversion} available.")
182+
logger.print_error(
183+
"Update with pipx or manually on https://github.com/AD-Security/AD_Miner"
184+
)
185+
return
186+
except Exception:
187+
return
188+
189+
145190
def main() -> None:
146191
"""Main execution function for the script."""
147192
start = time.time()
@@ -163,6 +208,12 @@ def main() -> None:
163208

164209
prepare_render(arguments)
165210

211+
AD_miner_version, AD_miner_commit = get_version_and_commit()
212+
arguments.version = AD_miner_version
213+
arguments.commit = AD_miner_commit
214+
215+
check_version(get_last_version(), AD_miner_version)
216+
166217
neo4j_version, extract_date, total_objects, number_relations, boolean_azure = pre_request(
167218
arguments
168219
)

ad_miner/sources/html/bootstrap/css/custom.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,4 +389,9 @@ a:hover{
389389
.percentage-evolution {
390390
font-size: small;
391391
font-weight: 800;
392+
}
393+
394+
.ag-cell-value i.bi {
395+
margin-right: 4px;
396+
vertical-align: middle;
392397
}

ad_miner/sources/html/components/grid/grid_template.html

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,18 @@
9999
rowData2.push(new_dico);
100100
}
101101
}
102+
else if (window.location.href.includes('computers_with_administrators_details')) {
103+
if (rowData[i]['Computer'] == parameter) {
104+
var new_dico = {};
105+
new_dico['Users'] = rowData[i]['Users'];
106+
new_dico['Distinguished Name'] = rowData[i]['Distinguished Name'];
107+
rowData2.push(new_dico);
108+
}
109+
}
102110
else if (window.location.href.includes('users_rdp_access') || window.location.href.includes('computers_list_of_rdp_users')) {
103111

104112
if (rowData[i][keys[0]].includes(parameter)) {
105-
const startSubstring = "<p style='visibility:hidden;'>";
113+
const startSubstring = "<p style='display: none;'>";
106114
const endSubstring = "</p>";
107115

108116
const startIndex = rowData[i][keys[1]].indexOf(startSubstring) + startSubstring.length;
@@ -130,7 +138,12 @@
130138
}
131139
}
132140
}
133-
columnDefs2 = [{field: parameter}];
141+
if (window.location.href.includes('computers_with_administrators_details')) {
142+
columnDefs2 = [{field: 'Users'},{field: 'Distinguished Name'}]
143+
}
144+
else {
145+
columnDefs2 = [{field: parameter}];
146+
}
134147
return [rowData2, columnDefs2];
135148
}
136149

ad_miner/sources/html/templates/main_header.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ <h6 class="card-subtitle mb-2 text-muted">
125125
<div class="card main-card-category shadow mb-3" id="global-rating-azure" style="display:none">
126126
<div class="card-body">
127127
<h5 class="card-title">
128-
Azure Risk rating
128+
Entra ID Risk rating
129129
<i class="bi bi-info-circle" data-bs-toggle="tooltip"
130130
data-bs-title="This rating is an overall evaluation of the security level of your Active Directory based on the estimated risk of each indicator of exposure"></i>
131131
</h5>
@@ -931,7 +931,7 @@ <h5 class="card-title chart-details-title">
931931
<div class="col-graph-details">
932932
<div>
933933
<canvas id="chart-details-2-azure" class="chart-details"></canvas>
934-
<h5 class="card-title chart-details-title">Azure AD Connect</h5>
934+
<h5 class="card-title chart-details-title">Microsoft Entra Connect</h5>
935935
</div>
936936
<div>{{azure|ad_connect_graph_summary}}</div>
937937
</div>

ad_miner/sources/js/graph.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -386,18 +386,28 @@ function getAllEndNodes() {
386386
}
387387
return ends;
388388
}
389+
function reachableNodesFrom(selectedNode, visited = new Set()) {
390+
// Get all reachable nodes from on start node
391+
if (visited.has(selectedNode)) return [];
392+
visited.add(selectedNode);
389393

390-
function display_only_path_from_node(node_parameter) {
391-
// Get all nodes leading to all ends from a specific node
392-
ends = getAllEndNodes();
393-
394-
allPaths = [];
394+
if (dico_nodes[selectedNode].group.includes('end')) {
395+
return [selectedNode];
396+
}
395397

396-
for (var i = 0; i < ends.length; i++) {
397-
allPaths = allPaths.concat(
398-
shortestpathToChosenEnd(node_parameter, ends[i]),
399-
);
398+
var reachableNodes = [selectedNode];
399+
var connectedNodes = dico_edges_children[selectedNode];
400+
401+
for (let i = 0; i < connectedNodes.length; i++) {
402+
reachableNodes = reachableNodes.concat(reachableNodesFrom(connectedNodes[i], visited))
400403
}
404+
return reachableNodes;
405+
}
406+
407+
function display_only_path_from_node(node_parameter) {
408+
// Get all nodes leading from a specific node
409+
410+
allPaths = reachableNodesFrom(node_parameter);
401411

402412
selected_nodes_id = [...new Set(allPaths)];
403413

0 commit comments

Comments
 (0)