Support drag and drop file ingestion
parent
d96651635d
commit
f093a6863c
1
main.js
1
main.js
|
@ -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.
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue