Skip to content

Commit

Permalink
added scheduling for data collection
Browse files Browse the repository at this point in the history
  • Loading branch information
dekelpaz committed Oct 20, 2022
1 parent 3a26532 commit 9d8440e
Show file tree
Hide file tree
Showing 16 changed files with 418 additions and 76 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ BlueHound supports presenting your data as tables, graphs, bar charts, line char
4. **Easy Customization**: Any custom collection method can be added into BlueHound. Users can even add their own custom parameters or even custom icons for their graphs.

## Getting Started
### ROST ISO
BlueHound can be used as part of the [ROST image](https://zeronetworks.com/ROST-iso.zip), which comes pre-configured with everything you need (BlueHound, Neo4j, BloodHound, and a sample dataset).
<br>To load ROST, create a new virtual machine, and install it from the ISO like you would for a new Windows host.
### BlueHound Binary
If you already have a Neo4j instance running, you can download a pre-compiled version of BlueHound from our [release page](https://github.com/zeronetworks/BlueHound/releases). Just download the zip file suitable to your OS version, extract it, and run the binary.
### Using BlueHound
1. Connect to your Neo4j server
2. Download [SharpHound](https://github.com/BloodHoundAD/BloodHound/blob/master/Collectors/SharpHound.exe), [ShotHound](https://github.com/zeronetworks/BloodHound-Tools/tree/main/ShotHound) and the [Vulnerability Scanner report parser](https://github.com/zeronetworks/BloodHound-Tools/tree/main/VulnerabilitiesDataImport)
3. Use the **Data Import** section to collect & import data into your Neo4j database.
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "bluehound",
"productName": "BlueHound",
"version": "1.0.0",
"version": "1.1.0",
"description": "BlueHound",
"neo4jDesktop": {
"apiVersion": "^1.2.0"
Expand Down Expand Up @@ -88,7 +88,8 @@
"async-mutex": "^0.3.2",
"jszip": "^3.10.0",
"file-saver": "^2.0.5",
"react-lottie-player": "^1.4.3"
"react-lottie-player": "^1.4.3",
"dayjs": "^1.11.5"
},
"devDependencies": {
"@babel/cli": "^7.16.8",
Expand Down
Binary file added public/zero-networks-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/zero-networks-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/application/Application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ const Application = ({ connection, connected, hasCachedDashboard, oldDashboard,
createConnection(connection.protocol, connection.url, connection.port, connection.database, connection.username, connection.password);
}


if (window.electron != undefined) { // running in Electron
window.electron.appLoaded();
}
}

// Only render the dashboard component if we have an active Neo4j connection.
Expand Down
18 changes: 18 additions & 0 deletions src/application/ApplicationActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ export const setParallelQueries = (amount: number) => ({
payload: { amount },
});

export const SET_COLLECTION_HOURS = 'APPLICATION/SET_COLLECTION_HOURS';
export const setCollectionHours = (amount: number) => ({
type: SET_COLLECTION_HOURS,
payload: { amount },
});

export const SET_SHARPHOUND_UPLOAD_RESULTS = 'APPLICATION/SET_SHARPHOUND_UPLOAD_RESULTS';
export const setSharpHoundUploadResults = (status: boolean) => ({
type: SET_SHARPHOUND_UPLOAD_RESULTS,
Expand Down Expand Up @@ -191,4 +197,16 @@ export const SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING = 'APPLICATION/SET_DASHBOARD
export const setDashboardToLoadAfterConnecting = (id: any) => ({
type: SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING,
payload: { id },
});

export const SET_ZERO_ALERT_SHOWN = 'APPLICATION/SET_ZERO_ALERT_SHOWN';
export const setZeroAlertShown = (alert_state: boolean) => ({
type: SET_ZERO_ALERT_SHOWN,
payload: { alert_state}
});

export const SET_ZERO_ALERT_OPEN = 'APPLICATION/SET_ZERO_ALERT_OPEN';
export const setZeroAlertOpen = (alert_state: boolean) => ({
type: SET_ZERO_ALERT_OPEN,
payload: { alert_state}
});
28 changes: 26 additions & 2 deletions src/application/ApplicationReducer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
SET_CONNECTION_MODAL_OPEN,
SET_CONNECTION_PROPERTIES,
SET_DASHBOARD_TO_LOAD_AFTER_CONNECTING,
SET_ZERO_ALERT_SHOWN,
SET_DESKTOP_CONNECTION_PROPERTIES,
SET_OLD_DASHBOARD,
SET_SHARE_DETAILS_FROM_URL,
Expand All @@ -31,7 +32,12 @@ import {
SET_SHARPHOUND_UPLOAD_RESULTS,
SET_SHARPHOUND_CLEAR_RESULTS,
SET_FILTER_MODAL_OPEN,
SET_QUERY_MODAL_OPEN, SET_PARALLEL_QUERIES, SET_DB_VERSION, SET_CACHED_QUERIES
SET_QUERY_MODAL_OPEN,
SET_PARALLEL_QUERIES,
SET_DB_VERSION,
SET_CACHED_QUERIES,
SET_ZERO_ALERT_OPEN,
SET_COLLECTION_HOURS
} from "./ApplicationActions";

const update = (state, mutations) =>
Expand Down Expand Up @@ -63,9 +69,12 @@ const initialState =
standalone: false,
toolsParallel: false,
parallelQueries: 5,
collectionHours: 0,
sharphoundUploadResults: true,
sharphoundClearResults: true,
cachedQueries: {}
cachedQueries: {},
zeroAlertShown: true,
zeroAlertOpen: false
}

export const applicationReducer = (state = initialState, action: { type: any; payload: any; }) => {
Expand Down Expand Up @@ -151,6 +160,11 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
state = update(state, { parallelQueries: amount })
return state
}
case SET_COLLECTION_HOURS: {
const { amount } = payload;
state = update(state, { collectionHours: amount })
return state
}
case SET_SHARPHOUND_UPLOAD_RESULTS: {
const { status } = payload;
state = update(state, { sharphoundUploadResults: status })
Expand Down Expand Up @@ -211,6 +225,16 @@ export const applicationReducer = (state = initialState, action: { type: any; pa
state = update(state, { dashboardToLoadAfterConnecting: id })
return state;
}
case SET_ZERO_ALERT_SHOWN: {
const { alert_state } = payload;
state = update(state, { zeroAlertShown: alert_state })
return state;
}
case SET_ZERO_ALERT_OPEN: {
const { alert_state } = payload;
state = update(state, { zeroAlertOpen: alert_state })
return state;
}
case SET_CONNECTION_PROPERTIES: {
const { protocol, url, port, database, username, password, successful } = payload;
state = update(state, {
Expand Down
12 changes: 12 additions & 0 deletions src/application/ApplicationSelectors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,22 @@ export const getParallelQueries = (state: any) => {
return state.application.parallelQueries;
}

export const getCollectionHours = (state: any) => {
return state.application.collectionHours;
}

export const getSharpHoundUploadResults = (state: any) => {
return state.application.sharphoundUploadResults;
}

export const getSharpHoundClearResults = (state: any) => {
return state.application.sharphoundClearResults;
}

export const getZeroAlertShown = (state: any) => {
return state.application.zeroAlertShown;
}

export const getZeroAlertOpen = (state: any) => {
return state.application.zeroAlertOpen;
}
6 changes: 4 additions & 2 deletions src/chart/GraphChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ const getNodeLabel = (labels) => {
for (let bloodhoundLabel of BLOODHOUND_NODE_LABELS) {
const foundIndex = labels.indexOf(bloodhoundLabel);
if (foundIndex != -1) {
return labels[foundIndex]
return labels[foundIndex];
}
}
return labels[-1]

return labels[labels.length-1];
}

const update = (state, mutations) =>
Expand Down Expand Up @@ -283,6 +284,7 @@ const NeoGraphChart = (props: ChartProps) => {
const newImage = new Image();
newImage.src = node.lastLabel + '.png';
if (imageExists(newImage)) {
console.log(newImage.src)
imgs[node.lastLabel] = newImage;
image = newImage;
}
Expand Down
4 changes: 1 addition & 3 deletions src/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, {useCallback, useEffect} from 'react';
import NeoPage from '../page/Page';
import Container from '@material-ui/core/Container';
import NeoDrawer from './DashboardDrawer';
Expand All @@ -12,8 +12,6 @@ import { forceRefreshPage } from '../page/PageActions';
import { getPageNumber } from '../settings/SettingsSelectors';
import { createNotification } from '../application/ApplicationActions';



const Dashboard = ({ pagenumber, connection, onConnectionUpdate }) => {
const [open, setOpen] = React.useState(false);
const driver = createDriver(connection.protocol, connection.url, connection.port, connection.username, connection.password);
Expand Down
85 changes: 82 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const {app, BrowserWindow, ipcMain, dialog, shell, Menu } = require('electron');
const {app, BrowserWindow, ipcMain, dialog, shell, Menu, ipcRenderer } = require('electron');
const path = require('path');
const os = require('os');
const { spawn } = require('child_process');
const { handleSharpHoundResultsUpload } = require('./collectors/BloodHoundUploader.tsx');
const logMessages = [];

let mainWindow;
const gotTheLock = app.requestSingleInstanceLock();
let shellEnvironments;
let logLevel = {
0: "verbose",
Expand Down Expand Up @@ -57,10 +58,45 @@ function createWindow () {
});
}

app.on('ready', createWindow);
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, commandLine, workingDirectory, additionalData) => {
if (commandLine.includes('--collection-only')) {
mainWindow.webContents.send('run-all-collection');
};

if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore()
mainWindow.focus()
}
})

app.whenReady().then(() => {
createWindow();
})
}

//app.on('ready', createWindow);

const runningProcesses = {};

const isCollectionOnly = () => {
return process.argv.includes('--collection-only');
}

ipcMain.handle("app-loaded", async (event) => {
if (isCollectionOnly()) {
dialog.showMessageBox(mainWindow, {
title: 'Collection-only mode',
buttons: ['Dismiss'],
type: 'info',
message: 'BlueHound is running in collection-only mode…',
});
event.sender.send('run-all-collection', '');
}
});

ipcMain.handle("open-dev-tools", async (event) => {
await mainWindow.webContents.openDevTools();
});
Expand Down Expand Up @@ -154,7 +190,7 @@ const runToolsInSerial = async (event, toolsData) => {
result.on('close', (code) => {
event.sender.send("tool-data-done", tool.toolId, code, path.dirname(toolPath))
delete runningProcesses[tool.toolId];
if (toolsData.length > 1) { runToolsInSerial(event, toolsData.slice(1)) };
if (toolsData.length > 1) { runToolsInSerial(event, toolsData.slice(1)) } ;
});
}

Expand Down Expand Up @@ -195,4 +231,47 @@ ipcMain.handle("kill-process", async (event, toolId) => {

ipcMain.handle("upload-sharphound-results", async (event, toolId, resultsPath, connectionProperties, clearResults) => {
await handleSharpHoundResultsUpload(event, toolId, resultsPath, connectionProperties, clearResults);
})

ipcMain.handle("tools-finished-running", async (event) => {
if (isCollectionOnly()) {
app.quit();
}
})

ipcMain.handle("add-scheduled-task", async (event, scheduleFrequency, dayOfWeek, dayOfMonth, scheduleTime) => {
if (os.platform() != 'win32') {
dialog.showMessageBox(mainWindow, {
title: 'OS not supported',
buttons: ['Dismiss'],
type: 'error',
message: 'Adding scheduled task is currently only supported on Windows.\n\n' +
'The --collection-only argument can be used to manually schedule BlueHound on non-Windows hosts.',
});
return;
}

let args = [];
const taskName = '"BlueHound Collection"';
const appPathWithArgs = `"'${path.resolve('.', 'BlueHound.exe')}' --collection-only"`;

if (scheduleFrequency == 'DAILY') {
args = ['/CREATE', '/F', '/SC DAILY', '/TN ' + taskName, '/TR ' + appPathWithArgs, '/ST ' + scheduleTime];
} else if (scheduleFrequency == 'WEEKLY') {
args = ['/CREATE', '/F', '/SC WEEKLY', '/D ' + dayOfWeek, '/TN ' + taskName, '/TR ' + appPathWithArgs, '/ST ' + scheduleTime];
} else if (scheduleFrequency == 'MONTHLY') {
args = ['/CREATE', '/F', '/SC MONTHLY', '/D ' + dayOfMonth, '/TN ' + taskName, '/TR ' + appPathWithArgs, '/ST ' + scheduleTime];
}

const result = spawn("SCHTASKS", args, { shell: true });

result.on('error', function (err) { // needed for catching ENOENT
event.sender.send("tool-notification", err)
});

result.on('close', (code) => {
if (code == 0) {
event.sender.send("tool-notification", "Scheduled task added successfully.")
}
});
})
57 changes: 27 additions & 30 deletions src/modal/AboutModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {applicationGetDebugState} from "../application/ApplicationSelectors";
import JSZip from 'jszip';
import FileSaver from 'file-saver';

const version = "1.0.0";
const version = "1.1.0";

if (window.electron != undefined) {
window.electron.receive("console-messages", (consoleData) => {
Expand Down Expand Up @@ -142,35 +142,32 @@ export const NeoAboutModal = ({ open, handleClose, getDebugState }) => {
so if you have ideas, don’t hesitate to reach out to us at <a href={"mailto:" + email}>{email}</a> or via the Github repository.
<br />
<hr></hr>
<table style={{width: "100%", paddingTop: 2, paddingBottom: 2}}>
<tr>
<td>
<Button
component="label"
onClick={downloadDebugFile}
style={{ backgroundColor: "white" }}
color="default"
variant="contained"
size="small"
endIcon={<BugReportIcon />}>
Debug Report
</Button>
<Button
component="label"
onClick={() => setRestoreDialogOpen(true) }
style={{ backgroundColor: "white", marginLeft: 24 }}
color="default"
variant="contained"
size="small"
endIcon={<SettingsBackupRestore />}>
Restore Default
</Button>
</td>
<td>
<i style={{ float: "right", fontSize: "11px" }}>v{version}</i>
</td>
</tr>
</table>
<div style={{display: "flex"}}>
<Button
component="label"
onClick={downloadDebugFile}
style={{ backgroundColor: "white" }}
color="default"
variant="contained"
size="small"
endIcon={<BugReportIcon />}>
Debug Report
</Button>
<Button
component="label"
onClick={() => setRestoreDialogOpen(true) }
style={{ backgroundColor: "white", marginLeft: 24 }}
color="default"
variant="contained"
size="small"
endIcon={<SettingsBackupRestore />}>
Restore Default
</Button>
<i style={{fontSize: "11px", marginLeft: "auto", marginRight: 16, marginTop: "auto", marginBottom: "auto"}}>v{version}</i>
<a href={"https://zeronetworks.com/"} style={{marginRight: 0, height: 24, marginTop: "auto", marginBottom: "auto"}}>
<img src={'./zero-networks-logo.svg'} style={{marginRight: 0, height: 24, marginTop: "auto", marginBottom: "auto"}}/>
</a>
</div>
</div></DialogContent>
</Dialog>
</div >
Expand Down
Loading

0 comments on commit 9d8440e

Please sign in to comment.