Support drag and drop file ingestion

master
Rohan Vazarkar 2018-07-16 15:00:37 -04:00
parent d96651635d
commit f093a6863c
8 changed files with 96 additions and 34 deletions

View File

@ -2,7 +2,6 @@ const electron = require('electron')
var platform = require('os').platform() var platform = require('os').platform()
// Module to control application life. // Module to control application life.
const app = electron.app const app = electron.app
const Tray = electron.Tray
const Menu = electron.Menu const Menu = electron.Menu
// Module to create native browser window. // Module to create native browser window.

View File

@ -1,6 +1,6 @@
{ {
"name": "bloodhound", "name": "bloodhound",
"version": "2.0", "version": "2.0.0",
"description": "Graph Theory for Active Directory", "description": "Graph Theory for Active Directory",
"keywords": [ "keywords": [
"Graph", "Graph",
@ -58,6 +58,7 @@
"eventemitter2": "^4.1.0", "eventemitter2": "^4.1.0",
"jquery": "^3.2.1", "jquery": "^3.2.1",
"linkurious": "^1.5.1", "linkurious": "^1.5.1",
"magic-number": "^0.1.6",
"mustache": "^2.3.0", "mustache": "^2.3.0",
"neo4j-driver": "^1.6.2", "neo4j-driver": "^1.6.2",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",

View File

@ -20,6 +20,19 @@ import About from "./components/Modals/About.jsx";
import { CSSTransition, TransitionGroup } from "react-transition-group"; import { CSSTransition, TransitionGroup } from "react-transition-group";
export default class AppContainer extends Component { export default class AppContainer extends Component {
componentDidMount(){
document.addEventListener('dragover', function(event){
event.preventDefault();
return false;
}, false)
document.addEventListener('drop', function(event){
emitter.emit("filedrop", event)
event.preventDefault();
return false;
}, false)
}
render() { render() {
return ( return (
<TransitionGroup className="max"> <TransitionGroup className="max">

View File

@ -39,6 +39,8 @@ export default class Login extends Component {
if (this.state.password !== "") { if (this.state.password !== "") {
this.checkDBCreds(); this.checkDBCreds();
}else{
this.checkDBPresence();
} }
} }
@ -59,8 +61,6 @@ export default class Login extends Component {
checkDBPresence() { checkDBPresence() {
var url = this.state.url; var url = this.state.url;
var icon = this.state.icon; var icon = this.state.icon;
var jicon = jQuery(icon);
var btn = jQuery(this.refs.loginButton);
if (url === "") { if (url === "") {
return; return;

View File

@ -12,16 +12,17 @@ import {
buildOuJson buildOuJson
} from "utils"; } from "utils";
import { If, Then, Else } from "react-if"; import { If, Then, Else } from "react-if";
const { dialog, app } = require("electron").remote; import { remote } from "electron";
var fs = require("fs"); const { dialog, app } = remote;
var async = require("async"); import { unlinkSync, createReadStream, createWriteStream } from "fs";
var unzip = require("unzipper"); import { eachSeries } from "async";
var fpath = require("path"); import { Parse } from "unzipper";
import { join } from "path";
const Pick = require("stream-json/filters/Pick"); import { withParser } from "stream-json/filters/Pick";
const { streamArray } = require("stream-json/streamers/StreamArray"); import { streamArray } from "stream-json/streamers/StreamArray";
const { chain } = require("stream-chain"); import { chain } from "stream-chain";
const Asm = require("stream-json/Assembler"); import { connectTo } from "stream-json/Assembler";
export default class MenuContainer extends Component { export default class MenuContainer extends Component {
constructor() { constructor() {
@ -35,6 +36,42 @@ export default class MenuContainer extends Component {
}; };
emitter.on("cancelUpload", this.cancelUpload.bind(this)); emitter.on("cancelUpload", this.cancelUpload.bind(this));
emitter.on("filedrop", this.fileDrop.bind(this))
}
fileDrop(e){
let fileNames = []
$.each(e.dataTransfer.files, function(_, file){
fileNames.push({ path: file.path, name: file.name });
})
this.unzipNecessary(fileNames).then(
function(results) {
eachSeries(
results,
function(file, callback) {
emitter.emit(
"showAlert",
"Processing file {}".format(file.name)
);
this.getFileMeta(file.path, callback);
}.bind(this),
function done() {
setTimeout(
function() {
this.setState({ uploading: false });
}.bind(this),
3000
);
$.each(results, function(_, file) {
if (file.delete) {
unlinkSync(file.path);
}
});
}.bind(this)
);
}.bind(this)
);
} }
cancelUpload() { cancelUpload() {
@ -88,13 +125,13 @@ export default class MenuContainer extends Component {
var input = jQuery(this.refs.fileInput); var input = jQuery(this.refs.fileInput);
var fileNames = []; var fileNames = [];
$.each(input[0].files, function(index, file) { $.each(input[0].files, function(_, file) {
fileNames.push({ path: file.path, name: file.name }); fileNames.push({ path: file.path, name: file.name });
}); });
this.unzipNecessary(fileNames).then( this.unzipNecessary(fileNames).then(
function(results) { function(results) {
async.eachSeries( eachSeries(
results, results,
function(file, callback) { function(file, callback) {
emitter.emit( emitter.emit(
@ -112,7 +149,7 @@ export default class MenuContainer extends Component {
); );
$.each(results, function(index, file) { $.each(results, function(index, file) {
if (file.delete) { if (file.delete) {
fs.unlinkSync(file.path); unlinkSync(file.path);
} }
}); });
}.bind(this) }.bind(this)
@ -132,12 +169,11 @@ export default class MenuContainer extends Component {
var name = files[index].name; var name = files[index].name;
if (path.endsWith(".zip")) { if (path.endsWith(".zip")) {
await fs await createReadStream(path)
.createReadStream(path) .pipe(Parse())
.pipe(unzip.Parse())
.on("entry", function(entry) { .on("entry", function(entry) {
var output = fpath.join(tempPath, entry.path); var output = join(tempPath, entry.path);
entry.pipe(fs.createWriteStream(output)); entry.pipe(createWriteStream(output));
processed.push({ processed.push({
path: output, path: output,
name: entry.path, name: entry.path,
@ -175,11 +211,11 @@ export default class MenuContainer extends Component {
console.log(file); console.log(file);
let pipeline = chain([ let pipeline = chain([
fs.createReadStream(file, { encoding: "utf8" }), createReadStream(file, { encoding: "utf8" }),
Pick.withParser({ filter: "meta" }) withParser({ filter: "meta" })
]); ]);
let asm = Asm.connectTo(pipeline); let asm = connectTo(pipeline);
asm.on( asm.on(
"done", "done",
function(asm) { function(asm) {
@ -199,8 +235,8 @@ export default class MenuContainer extends Component {
processJson(file, callback, count, type) { processJson(file, callback, count, type) {
let pipeline = chain([ let pipeline = chain([
fs.createReadStream(file, { encoding: "utf8" }), createReadStream(file, { encoding: "utf8" }),
Pick.withParser({ filter: type }), withParser({ filter: type }),
streamArray() streamArray()
]); ]);

View File

@ -650,7 +650,7 @@ export default class SearchContainer extends Component {
} }
query += query +=
" WITH m,n MATCH p=allShortestPaths((n)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(m)) RETURN p"; " WITH m,n MATCH p=allShortestPaths((n)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM*1..]->(m)) RETURN p";
emitter.emit("query", query, { aprop: start, bprop: end }); emitter.emit("query", query, { aprop: start, bprop: end });
} }
@ -809,7 +809,7 @@ export default class SearchContainer extends Component {
} }
query += query +=
" WITH m,n MATCH p=allShortestPaths((n)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(m)) RETURN p"; " WITH m,n MATCH p=allShortestPaths((n)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM*1..]->(m)) RETURN p";
emitter.emit("query", query, { aprop: start, bprop: end }); emitter.emit("query", query, { aprop: start, bprop: end });
} }

View File

@ -9,7 +9,7 @@
<li onclick="emitter.emit('setEnd', '{{type}}:{{label}}')"> <li onclick="emitter.emit('setEnd', '{{type}}:{{label}}')">
<i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node <i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node
</li> </li>
<li onclick="emitter.emit('query', 'MATCH (n:User {name:{name}}),(m:User),p=shortestPath((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) WHERE NOT m.name={name} RETURN p', {name: '{{label}}'}, '{{label}}')"> <li onclick="emitter.emit('query', 'MATCH (n:User {name:{name}}),(m:User),p=shortestPath((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM*1..]->(n)) WHERE NOT m.name={name} RETURN p', {name: '{{label}}'}, '{{label}}')">
<i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here <i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here
</li> </li>
{{/type_user}} {{#type_computer}} {{/type_user}} {{#type_computer}}
@ -19,7 +19,7 @@
<li onclick="emitter.emit('setEnd', '{{type}}:{{label}}')"> <li onclick="emitter.emit('setEnd', '{{type}}:{{label}}')">
<i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node <i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node
</li> </li>
<li onclick="emitter.emit('query', 'MATCH (n:Computer {name:{name}}),(m:User),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) WHERE NOT m.name={name} RETURN p', {name: '{{label}}'}, '{{label}}')"> <li onclick="emitter.emit('query', 'MATCH (n:Computer {name:{name}}),(m:User),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM*1..]->(n)) WHERE NOT m.name={name} RETURN p', {name: '{{label}}'}, '{{label}}')">
<i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here <i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here
</li> </li>
{{/type_computer}} {{#type_group}} {{/type_computer}} {{#type_group}}
@ -29,7 +29,7 @@
<li onclick="emitter.emit('setEnd', '{{type}}:{{label}}')"> <li onclick="emitter.emit('setEnd', '{{type}}:{{label}}')">
<i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node <i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node
</li> </li>
<li onclick="emitter.emit('query', 'MATCH (n:Group {name:{name}}),(m:User),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) WHERE NOT m.name={name} RETURN p', {name: '{{label}}'}, '{{label}}')"> <li onclick="emitter.emit('query', 'MATCH (n:Group {name:{name}}),(m:User),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM*1..]->(n)) WHERE NOT m.name={name} RETURN p', {name: '{{label}}'}, '{{label}}')">
<i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here <i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here
</li> </li>
{{/type_group}} {{#type_gpo}} {{/type_group}} {{#type_gpo}}
@ -39,7 +39,7 @@
<li onclick="emitter.emit('setEnd', '{{type}}:{{guid}}')"> <li onclick="emitter.emit('setEnd', '{{type}}:{{guid}}')">
<i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node <i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node
</li> </li>
<li onclick="emitter.emit('query', 'MATCH (n:GPO {name:{name}}),(m),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) WHERE NOT m.guid={guid} RETURN p', {name: '{{name}}'})"> <li onclick="emitter.emit('query', 'MATCH (n:GPO {name:{name}}),(m),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM*1..]->(n)) WHERE NOT m.guid={guid} RETURN p', {name: '{{name}}'})">
<i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here <i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here
</li> </li>
{{/type_gpo}} {{#type_ou}} {{/type_gpo}} {{#type_ou}}
@ -49,7 +49,7 @@
<li onclick="emitter.emit('setEnd', '{{type}}:{{guid}}')"> <li onclick="emitter.emit('setEnd', '{{type}}:{{guid}}')">
<i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node <i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node
</li> </li>
<li onclick="emitter.emit('query', 'MATCH (n:OU {guid:{guid}}),(m),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) WHERE NOT m.guid={guid} RETURN p', {guid: '{{guid}}'})"> <li onclick="emitter.emit('query', 'MATCH (n:OU {guid:{guid}}),(m),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM*1..]->(n)) WHERE NOT m.guid={guid} RETURN p', {guid: '{{guid}}'})">
<i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here <i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here
</li> </li>
{{/type_ou}} {{#type_domain}} {{/type_ou}} {{#type_domain}}
@ -59,7 +59,7 @@
<li onclick="emitter.emit('setEnd', '{{type}}:{{label}}')"> <li onclick="emitter.emit('setEnd', '{{type}}:{{label}}')">
<i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node <i class="glyphicon glyphicon-screenshot"> </i> Set as Ending Node
</li> </li>
<li onclick="emitter.emit('query', 'MATCH (n:Domain {name:{name}}),(m:User),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner*1..]->(n)) WHERE NOT m.name={name} RETURN p', {name: '{{label}}'}, '{{label}} ')"> <li onclick="emitter.emit('query', 'MATCH (n:Domain {name:{name}}),(m:User),p=allShortestPaths((m)-[r:MemberOf|AdminTo|HasSession|Contains|GpLink|Owns|DCSync|AllExtendedRights|ForceChangePassword|GenericAll|GenericWrite|WriteDacl|WriteOwner|CanRDP|ExecuteDCOM*1..]->(n)) WHERE NOT m.name={name} RETURN p', {name: '{{label}}'}, '{{label}} ')">
<i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here <i class="glyphicon glyphicon-screenshot"> </i> Shortest Paths to Here
</li> </li>
{{/type_domain}} {{#expand}} {{/type_domain}} {{#expand}}

View File

@ -593,6 +593,7 @@ export function buildComputerJson(chunk) {
let localadmins = comp.LocalAdmins; let localadmins = comp.LocalAdmins;
let rdpers = comp.RemoteDesktopUsers; let rdpers = comp.RemoteDesktopUsers;
let primarygroup = comp.PrimaryGroup; let primarygroup = comp.PrimaryGroup;
let dcom = comp.DcomUsers;
if (!queries.properties) { if (!queries.properties) {
if (primarygroup === null) { if (primarygroup === null) {
@ -638,6 +639,18 @@ export function buildComputerJson(chunk) {
let p = { name: name, target: aName }; let p = { name: name, target: aName };
insert(queries, hash, statement, p); insert(queries, hash, statement, p);
}); });
$.each(dcom, function(_, dcomu) {
let aType = dcomu.Type;
let aName = dcomu.Name;
let rel = "ExecuteDCOM";
let hash = rel + aType;
let statement = baseQuery.format(aType, rel);
let p = { name: name, target: aName };
insert(queries, hash, statement, p);
});
}); });
return queries; return queries;
} }