cleaned repo
parent
3e83eb9d96
commit
23dd01a983
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 650 B |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.7 KiB |
@ -1,328 +0,0 @@
|
|||||||
{{define "fields"}}
|
|
||||||
{{range .Fields}}<td><a href="/?search={{.}}">{{.}}</a></td>{{end}}<td><a href="/?search={{index .Fields 0}}" title="{{.Links}} Relationships Share an Identifier connection with this Identifier">{{.Links}}</a></td>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "table"}}
|
|
||||||
<br/>
|
|
||||||
<div id="{{.Title}}" class="row">
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">{{.AltTitle}} linked to {{.SearchTerm}} ({{len .Rows}})</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{{ $length := len .RollupCounts }} {{ if ne $length 0 }}
|
|
||||||
<div class="panel-body text-center">
|
|
||||||
<canvas id="myChart{{.Title}}" style="max-width:300px;max-height:300px;margin:auto;" width="300px" height="300px"></canvas>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var ctx = document.getElementById("myChart{{.Title}}");
|
|
||||||
var myChart = new Chart(ctx, {
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
|
|
||||||
labels: [
|
|
||||||
{{ range $key, $value := .RollupCounts }}
|
|
||||||
"{{$key}}",
|
|
||||||
{{end}}
|
|
||||||
],
|
|
||||||
datasets: [{
|
|
||||||
data: [
|
|
||||||
{{ range $key, $value := .RollupCounts }}
|
|
||||||
{{$value}},
|
|
||||||
{{end}}
|
|
||||||
],
|
|
||||||
borderWidth: 1
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
scales: {
|
|
||||||
yAxes: [{
|
|
||||||
ticks: {
|
|
||||||
beginAtZero:true
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
text: "Breakdown of {{.Title}}s for {{.SearchTerm}}",
|
|
||||||
display:true
|
|
||||||
},
|
|
||||||
legend :{
|
|
||||||
display:false
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{{ range $key, $value := .RollupCounts }}
|
|
||||||
{{if ne $key ""}}
|
|
||||||
|
|
||||||
<button class="btn btn-primary" style="margin: 5px;" type="button">
|
|
||||||
{{$key}} <span class="badge">{{$value}}</span>
|
|
||||||
</button>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<!-- Table -->
|
|
||||||
<table class="table table-bordered table-striped">
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<th>Tag</th>
|
|
||||||
{{range .Heading}}
|
|
||||||
<th>{{.}}</th>
|
|
||||||
{{end}}
|
|
||||||
<th>Other Links</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{{range .Rows}}
|
|
||||||
|
|
||||||
<tr><td><span class="label label-default"><a href="/?search={{.Tag}}">{{.Tag}}</a></span></td>{{template "fields" .}}</tr>
|
|
||||||
{{end}}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br/>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
<!-- ############ SUMMARY ################# -->
|
|
||||||
{{define "summary"}}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<table class="table table-bordered table-striped">
|
|
||||||
|
|
||||||
{{range .Fields}}
|
|
||||||
<tr><th><a href="#{{.Key}}">{{.AltTitle}}</th>
|
|
||||||
<td>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar progress-bar-striped" style="width: {{.Total}}%">
|
|
||||||
{{.Value}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td></tr>
|
|
||||||
{{end}}
|
|
||||||
</table>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<!-- ############ Main Page ################# -->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en"><head>
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<meta name="description" content="">
|
|
||||||
<meta name="author" content="">
|
|
||||||
<link rel="icon" href="/images/favicon.ico">
|
|
||||||
|
|
||||||
<script src="/scripts/chart.bundle.js"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<title>OnionScan Correlations Lab</title>
|
|
||||||
|
|
||||||
<link href="/style/bootstrap.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto Slab';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: local('Roboto Slab Regular'), local('RobotoSlab-Regular'), url(/fonts/RobotoSlab-Regular.woff) format('woff');
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Roboto Slab';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: local('Roboto Slab Bold'), local('RobotoSlab-Bold'), url(/fonts/RobotoSlab-Bold.woff) format('woff');
|
|
||||||
}
|
|
||||||
|
|
||||||
body{
|
|
||||||
font-family: 'Roboto Slab';
|
|
||||||
}
|
|
||||||
.label a {
|
|
||||||
color:#fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin-bottom:5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body role="document">
|
|
||||||
|
|
||||||
|
|
||||||
<nav class="navbar navbar-default navbar-fixed-top">
|
|
||||||
<div class="container">
|
|
||||||
<div class="navbar-header">
|
|
||||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
|
|
||||||
<span class="sr-only">Toggle navigation</span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
<span class="icon-bar"></span>
|
|
||||||
</button>
|
|
||||||
<a class="navbar-brand" href="/"><img style="margin-top: -16px;" width="75px" height="75px" src="/images/logo.png"/></a>
|
|
||||||
</div>
|
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
|
||||||
<ul class="nav navbar-nav">
|
|
||||||
<li><a href="/" style="color:#fff;">Summary</a></li>
|
|
||||||
<li><a href="/saved" style="color:#fff;">Saved Searches</a></li>
|
|
||||||
</ul>
|
|
||||||
</div><!--/.nav-collapse -->
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<br/><br/> <br/><br/>
|
|
||||||
<div class="container theme-showcase" role="main">
|
|
||||||
|
|
||||||
<form action="/">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-12">
|
|
||||||
<div class="input-group">
|
|
||||||
<input name="search" type="text" class="form-control" placeholder="{{.SearchTerm}}" value="{{.SearchTerm}}"/>
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<input class="btn btn-default" type="submit" value="Search!">
|
|
||||||
</span>
|
|
||||||
</div><!-- /input-group -->
|
|
||||||
</div><!-- /.col-lg-6 -->
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
{{if ne .Error ""}}
|
|
||||||
<div class="alert alert-danger" role="alert">{{.Error}}</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{if ne .Success ""}}
|
|
||||||
<div class="alert alert-success" role="alert">{{.Success}}</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
{{if ne .SearchTerm ""}}
|
|
||||||
{{ $length := len .Tables }} {{ if ne $length 0 }}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-lg-3 text-center">
|
|
||||||
|
|
||||||
<h2>Options</h2>
|
|
||||||
<form action="/save" method="post">
|
|
||||||
<input type="hidden" name="search" value="{{.SearchTerm}}"/>
|
|
||||||
<input type="hidden" name="token" value="{{.Token}}"/>
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<input class="btn btn-default" type="submit" value="Save Search">
|
|
||||||
</span>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{{ $lentags := len .UserTags }}
|
|
||||||
{{if ne 0 $lentags}}
|
|
||||||
<h2>Linked Tags</h2>
|
|
||||||
{{ $search := .SearchTerm }}
|
|
||||||
{{ $token := .Token }}
|
|
||||||
{{ range .UserTags }}
|
|
||||||
<form action="/delete-tag" method="post">
|
|
||||||
<input type="hidden" name="search" value="{{$search}}"/>
|
|
||||||
<input type="hidden" name="tag" value="{{.}}"/>
|
|
||||||
<input type="hidden" name="token" value="{{$token}}"/>
|
|
||||||
<div class="btn-group">
|
|
||||||
<button class="btn btn-default" type="button"><a href="/?search={{.}}">{{.}}</a></button>
|
|
||||||
{{if ne . $search}}
|
|
||||||
<button class="btn btn-default" type="submit"><img src="/images/remove.png" width="16px" height="16px" title="remove tag"/></button>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
<h3>Tag Search Term</h3>
|
|
||||||
|
|
||||||
<form action="/tag" method="post">
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" name="tag" class="form-control" placeholder="Enter Tag..."/>
|
|
||||||
<input type="hidden" name="search"value="{{.SearchTerm}}"/>
|
|
||||||
<input type="hidden" name="token" value="{{.Token}}"/>
|
|
||||||
<span class="input-group-btn">
|
|
||||||
<input class="btn btn-default" type="submit" value="Tag!">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-9">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-heading">Summary for {{.SearchTerm}} {{if ne "" .Summary.Title}}({{.Summary.Title}}){{end}}
|
|
||||||
|
|
||||||
{{range .Tags}}
|
|
||||||
<span class="label label-{{if eq . "mod_status"}}danger{{else}}primary{{end}}"><a href="/?search={{.}}">{{.}}</a></span>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "summary" .Summary}}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
{{range .Tables}}
|
|
||||||
{{template "table" .}}
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
|
|
||||||
<div class="alert alert-warning" role="alert">No Relationships Found for <strong>{{.SearchTerm}}</strong></div>
|
|
||||||
{{end}}
|
|
||||||
{{else}}
|
|
||||||
|
|
||||||
{{ $length := len .SearchResults }}
|
|
||||||
|
|
||||||
{{ if eq $length 0 }}
|
|
||||||
|
|
||||||
<div class="jumbotron">
|
|
||||||
<h1>Welcome to your OnionScan Correlation Lab!</h1>
|
|
||||||
<p>You have <strong>{{.RelationshipNum}}</strong> correlations to hunt through!</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{else}}
|
|
||||||
|
|
||||||
<h2>Saved Searches</h2>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
|
|
||||||
{{ if eq $length 1}}
|
|
||||||
<div class="alert alert-warning" role="alert">You don't have any saved searches yet!</div>
|
|
||||||
{{else}}
|
|
||||||
{{range .SearchResults}}
|
|
||||||
{{if ne . "onionscan://dummy"}}
|
|
||||||
<li><a href="/?search={{.}}">{{.}}</a></li>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,459 +0,0 @@
|
|||||||
package webui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/s-rah/onionscan/config"
|
|
||||||
"github.com/s-rah/onionscan/crawldb"
|
|
||||||
"github.com/s-rah/onionscan/utils"
|
|
||||||
"html/template"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WebUI struct {
|
|
||||||
osc *config.OnionScanConfig
|
|
||||||
token string
|
|
||||||
Done chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type SummaryField struct {
|
|
||||||
Key string
|
|
||||||
Value int
|
|
||||||
AltTitle string
|
|
||||||
Total int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Summary struct {
|
|
||||||
Fields []SummaryField
|
|
||||||
Total int
|
|
||||||
Title string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Content struct {
|
|
||||||
SearchTerm string
|
|
||||||
Error string
|
|
||||||
Summary Summary
|
|
||||||
Tables []Table
|
|
||||||
Tags []string
|
|
||||||
RelationshipNum int
|
|
||||||
Token string
|
|
||||||
Success string
|
|
||||||
UserTags []string
|
|
||||||
SearchResults []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Row struct {
|
|
||||||
Fields []string
|
|
||||||
Tag string
|
|
||||||
Links int
|
|
||||||
}
|
|
||||||
|
|
||||||
type Table struct {
|
|
||||||
Title string
|
|
||||||
SearchTerm string
|
|
||||||
Heading []string
|
|
||||||
Rows []Row
|
|
||||||
Rollups []int
|
|
||||||
RollupCounts map[string]int
|
|
||||||
AltTitle string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserDefinedRow returns, from an initial relationship, a complete user
|
|
||||||
// defined relationship row - in the order it is defined in the crawl config.
|
|
||||||
func (wui *WebUI) GetUserDefinedTable(rel crawldb.Relationship) (Table, error) {
|
|
||||||
log.Printf("Loading User Defined Relationship %s", rel.From)
|
|
||||||
config, ok := wui.osc.CrawlConfigs[rel.From]
|
|
||||||
if ok {
|
|
||||||
var table Table
|
|
||||||
crName := strings.SplitN(rel.Type, "/", 2)
|
|
||||||
if len(crName) == 2 {
|
|
||||||
table.Title = crName[0]
|
|
||||||
cr, err := config.GetRelationship(crName[0])
|
|
||||||
if err == nil {
|
|
||||||
for i, er := range cr.ExtraRelationships {
|
|
||||||
table.Heading = append(table.Heading, er.Name)
|
|
||||||
if er.Rollup {
|
|
||||||
table.Rollups = append(table.Rollups, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table.Heading = append(table.Heading, "Onion")
|
|
||||||
log.Printf("Returning User Table Relationship %v", table)
|
|
||||||
return table, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("Could not make Table")
|
|
||||||
return Table{}, errors.New("Invalid Table")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserDefinedRow returns, from an initial relationship, a complete user
|
|
||||||
// defined relationship row - in the order it is defined in the crawl config.
|
|
||||||
func (wui *WebUI) GetUserDefinedRow(rel crawldb.Relationship) (string, []string) {
|
|
||||||
log.Printf("Loading User Defined Relationship %s", rel.From)
|
|
||||||
config, ok := wui.osc.CrawlConfigs[rel.From]
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
userrel, err := wui.osc.Database.GetUserRelationshipFromOnion(rel.Onion, rel.From)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
// We can now construct the user
|
|
||||||
// relationship in the right order.
|
|
||||||
crName := strings.SplitN(rel.Type, "/", 2)
|
|
||||||
if len(crName) == 2 {
|
|
||||||
cr, err := config.GetRelationship(crName[0])
|
|
||||||
row := make([]string, 0)
|
|
||||||
if err == nil {
|
|
||||||
for _, er := range cr.ExtraRelationships {
|
|
||||||
log.Printf("Field Value: %v", userrel[crName[0]+"/"+er.Name].Identifier)
|
|
||||||
row = append(row, userrel[crName[0]+"/"+er.Name].Identifier)
|
|
||||||
}
|
|
||||||
row = append(row, rel.From)
|
|
||||||
log.Printf("Returning User Row Relationship %s %v %s", crName[0], row, rel.Onion)
|
|
||||||
return crName[0], row
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.Printf("Could not derive config relationship from type %s", rel.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("Invalid Row")
|
|
||||||
return "", []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save implements the Saved Searches Feature
|
|
||||||
func (wui *WebUI) Save(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := r.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
http.Redirect(w, r, "/?error=Something Went Very Wrong! Please try again.", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
search := r.PostFormValue("search")
|
|
||||||
token := r.PostFormValue("token")
|
|
||||||
|
|
||||||
if token != wui.token {
|
|
||||||
path := fmt.Sprintf("/?search=%v&error=Invalid random token, Please try again.", search)
|
|
||||||
http.Redirect(w, r, path, http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wui.osc.Database.InsertRelationship(search, "onionscan://user-data", "search", "")
|
|
||||||
path := fmt.Sprintf("/?search=%v&success=Successfully Saved Search", search)
|
|
||||||
http.Redirect(w, r, path, http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tag implements the /tag endpoint.
|
|
||||||
func (wui *WebUI) Tag(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := r.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
http.Redirect(w, r, "/?error=Something Went Very Wrong! Please try again.", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
search := r.PostFormValue("search")
|
|
||||||
tag := r.PostFormValue("tag")
|
|
||||||
token := r.PostFormValue("token")
|
|
||||||
|
|
||||||
if token != wui.token {
|
|
||||||
path := fmt.Sprintf("/?search=%v&error=Invalid random token, Please try again.", search)
|
|
||||||
http.Redirect(w, r, path, http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wui.osc.Database.InsertRelationship(search, "onionscan://user-data", "tag", tag)
|
|
||||||
path := fmt.Sprintf("/?search=%v&success=Successfully Added Tag %v to %v", search, tag, search)
|
|
||||||
http.Redirect(w, r, path, http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete tag implements the /delete-tag endpoint
|
|
||||||
func (wui *WebUI) DeleteTag(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := r.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
http.Redirect(w, r, "/?error=Something Went Very Wrong! Please try again.", http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
search := r.PostFormValue("search")
|
|
||||||
tag := r.PostFormValue("tag")
|
|
||||||
token := r.PostFormValue("token")
|
|
||||||
|
|
||||||
if token != wui.token {
|
|
||||||
path := fmt.Sprintf("/?search=%v&error=Invalid random token, Could not delete tag. Please try again.", search)
|
|
||||||
http.Redirect(w, r, path, http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = wui.osc.Database.DeleteRelationship(search, "onionscan://user-data", "tag", tag)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
http.Redirect(w, r, "/?error=Something Went Very Wrong! Please try again: "+err.Error(), http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
path := fmt.Sprintf("/?search=%v&success=Successfully Deleted Tag %v from %v", search, tag, search)
|
|
||||||
http.Redirect(w, r, path, http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SavedSearches provides the user with a list of searches they have saved.
|
|
||||||
func (wui *WebUI) SavedSearches(w http.ResponseWriter, r *http.Request) {
|
|
||||||
results, _ := wui.osc.Database.GetRelationshipsWithIdentifier("onionscan://user-data")
|
|
||||||
var content Content
|
|
||||||
content.SearchResults = append(content.SearchResults, "onionscan://dummy")
|
|
||||||
for _, rel := range results {
|
|
||||||
if rel.Type == "search" {
|
|
||||||
content.SearchResults = append(content.SearchResults, rel.Onion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var templates = template.Must(template.ParseFiles("templates/index.html"))
|
|
||||||
templates.ExecuteTemplate(w, "index.html", content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Index implements the main search functionality of the webui
|
|
||||||
func (wui *WebUI) Index(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
|
||||||
error := strings.TrimSpace(r.URL.Query().Get("error"))
|
|
||||||
success := strings.TrimSpace(r.URL.Query().Get("success"))
|
|
||||||
var content Content
|
|
||||||
|
|
||||||
mod_status := false
|
|
||||||
pgp := false
|
|
||||||
ssh := false
|
|
||||||
uriCount := 0
|
|
||||||
content.Token = wui.token
|
|
||||||
content.Error = error
|
|
||||||
content.Success = success
|
|
||||||
|
|
||||||
if search != "" {
|
|
||||||
content.SearchTerm = search
|
|
||||||
|
|
||||||
var results []crawldb.Relationship
|
|
||||||
tables := make(map[string]Table)
|
|
||||||
|
|
||||||
results, _ = wui.osc.Database.GetRelationshipsWithOnion(search)
|
|
||||||
results_identifier, _ := wui.osc.Database.GetRelationshipsWithIdentifier(search)
|
|
||||||
results = append(results, results_identifier...)
|
|
||||||
|
|
||||||
for _, rel := range results {
|
|
||||||
if rel.Type == "page-info" {
|
|
||||||
content.Summary.Title = rel.Identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
if rel.From == "onionscan://user-data" {
|
|
||||||
if rel.Type == "tag" {
|
|
||||||
content.UserTags = append(content.UserTags, rel.Identifier)
|
|
||||||
utils.RemoveDuplicates(&content.UserTags)
|
|
||||||
|
|
||||||
if rel.Identifier == search {
|
|
||||||
// We want to surface the onions *not* the tag
|
|
||||||
|
|
||||||
table, ok := tables["search-results"]
|
|
||||||
log.Printf("%v %v", search, ok)
|
|
||||||
if !ok {
|
|
||||||
var newTable Table
|
|
||||||
newTable.Title = rel.Type
|
|
||||||
newTable.Heading = []string{"Onion"}
|
|
||||||
tables["search-results"] = newTable
|
|
||||||
table = newTable
|
|
||||||
}
|
|
||||||
links := wui.osc.Database.GetRelationshipsCount(rel.Identifier) - 1
|
|
||||||
table.Rows = append(table.Rows, Row{Fields: []string{rel.Onion}, Tag: rel.Identifier, Links: links})
|
|
||||||
tables["search-results"] = table
|
|
||||||
} else {
|
|
||||||
table, ok := tables["search-results"]
|
|
||||||
if !ok {
|
|
||||||
var newTable Table
|
|
||||||
newTable.Title = rel.Type
|
|
||||||
newTable.Heading = []string{"Tags"}
|
|
||||||
tables[rel.Type] = newTable
|
|
||||||
table = newTable
|
|
||||||
}
|
|
||||||
links := wui.osc.Database.GetRelationshipsCount(rel.Identifier) - 1
|
|
||||||
table.Rows = append(table.Rows, Row{Fields: []string{rel.Identifier}, Tag: rel.Onion, Links: links})
|
|
||||||
tables[rel.Type] = table
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if utils.IsOnion(rel.Onion) && rel.Type != "database-id" && rel.Type != "user-relationship" {
|
|
||||||
table, ok := tables[rel.Type]
|
|
||||||
if !ok {
|
|
||||||
var newTable Table
|
|
||||||
newTable.Title = rel.Type
|
|
||||||
newTable.Heading = []string{"Identifier", "Onion"}
|
|
||||||
tables[rel.Type] = newTable
|
|
||||||
table = newTable
|
|
||||||
}
|
|
||||||
links := wui.osc.Database.GetRelationshipsCount(rel.Identifier) - 1
|
|
||||||
table.Rows = append(table.Rows, Row{Fields: []string{rel.Identifier, rel.Onion}, Tag: rel.From, Links: links})
|
|
||||||
tables[rel.Type] = table
|
|
||||||
|
|
||||||
if rel.From == "mod_status" {
|
|
||||||
mod_status = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if rel.From == "pgp" {
|
|
||||||
pgp = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if rel.From == "ssh" {
|
|
||||||
ssh = true
|
|
||||||
}
|
|
||||||
} else if utils.IsOnion(rel.From) {
|
|
||||||
tableName, row := wui.GetUserDefinedRow(rel)
|
|
||||||
|
|
||||||
if len(row) > 0 {
|
|
||||||
table, exists := tables[tableName]
|
|
||||||
if !exists {
|
|
||||||
newTable, err := wui.GetUserDefinedTable(rel)
|
|
||||||
if err == nil {
|
|
||||||
tables[tableName] = newTable
|
|
||||||
table = newTable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table.Rows = append(table.Rows, Row{Fields: row})
|
|
||||||
tables[tableName] = table
|
|
||||||
}
|
|
||||||
} else if rel.Type == "user-relationship" {
|
|
||||||
userrel := rel
|
|
||||||
userrel.Onion = rel.Identifier
|
|
||||||
userrel.From = rel.Onion
|
|
||||||
userrel.Type = rel.From + "/parent"
|
|
||||||
tableName, row := wui.GetUserDefinedRow(userrel)
|
|
||||||
|
|
||||||
if len(row) > 0 {
|
|
||||||
table, exists := tables[tableName]
|
|
||||||
if !exists {
|
|
||||||
newTable, err := wui.GetUserDefinedTable(userrel)
|
|
||||||
if err == nil {
|
|
||||||
tables[tableName] = newTable
|
|
||||||
table = newTable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
table.Rows = append(table.Rows, Row{Fields: row})
|
|
||||||
tables[tableName] = table
|
|
||||||
}
|
|
||||||
} else if rel.Type == "database-id" {
|
|
||||||
uriCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AutoTag our content
|
|
||||||
if mod_status {
|
|
||||||
content.Tags = append(content.Tags, "mod_status")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pgp {
|
|
||||||
content.Tags = append(content.Tags, "pgp")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ssh {
|
|
||||||
content.Tags = append(content.Tags, "ssh")
|
|
||||||
}
|
|
||||||
|
|
||||||
// We now have a bunch of tables, keyed by type.
|
|
||||||
// Build a Summary and add the tables to the Content
|
|
||||||
|
|
||||||
for _, v := range tables {
|
|
||||||
content.Summary.Total += len(v.Rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range tables {
|
|
||||||
log.Printf("Adding Table %s %v", k, v)
|
|
||||||
|
|
||||||
// Lazy Plural
|
|
||||||
alt := k + "s"
|
|
||||||
|
|
||||||
switch k {
|
|
||||||
case "ip":
|
|
||||||
alt = "IP Addresses"
|
|
||||||
case "clearnet-link":
|
|
||||||
alt = "Co-Hosted Clearnet Sites"
|
|
||||||
case "uri":
|
|
||||||
alt = "Links to External Sites"
|
|
||||||
case "email-address":
|
|
||||||
alt = "Email Addresses"
|
|
||||||
case "server-version":
|
|
||||||
alt = "Server Information"
|
|
||||||
case "identity":
|
|
||||||
alt = "PGP Identities"
|
|
||||||
case "bitcoin-address":
|
|
||||||
alt = "Bitcoin Addresses"
|
|
||||||
case "software-banner":
|
|
||||||
alt = "Software Banners"
|
|
||||||
case "analytics-id":
|
|
||||||
alt = "Analytics IDs"
|
|
||||||
case "tag":
|
|
||||||
alt = "Tag Relationships"
|
|
||||||
case "onion":
|
|
||||||
alt = "Co-Hosted Onion Sites"
|
|
||||||
case "search-results":
|
|
||||||
alt = "Search Results"
|
|
||||||
case "http-header":
|
|
||||||
alt = "HTTP Headers"
|
|
||||||
case "page-info":
|
|
||||||
alt = "Webpage Information"
|
|
||||||
}
|
|
||||||
|
|
||||||
total := (float32(len(v.Rows)) / float32(content.Summary.Total)) * float32(100)
|
|
||||||
if total < 1 {
|
|
||||||
total = 2 // For Visibility
|
|
||||||
}
|
|
||||||
|
|
||||||
field := SummaryField{k, len(v.Rows), alt, int(total)}
|
|
||||||
content.Summary.Fields = append(content.Summary.Fields, field)
|
|
||||||
|
|
||||||
rollups := make(map[string]int)
|
|
||||||
for _, c := range v.Rollups {
|
|
||||||
for _, rows := range v.Rows {
|
|
||||||
rollups[rows.Fields[c]]++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.RollupCounts = rollups
|
|
||||||
v.SearchTerm = search
|
|
||||||
v.AltTitle = alt
|
|
||||||
content.Tables = append(content.Tables, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
content.RelationshipNum = wui.osc.Database.GetAllRelationshipsCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
var templates = template.Must(template.ParseFiles("templates/index.html"))
|
|
||||||
templates.ExecuteTemplate(w, "index.html", content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wui *WebUI) Listen(osc *config.OnionScanConfig, port int) {
|
|
||||||
wui.osc = osc
|
|
||||||
|
|
||||||
// We generate a random token on startup to mitigate the threat
|
|
||||||
// against CSRF style attacks.
|
|
||||||
token, err := utils.GenerateRandomString(64)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error generating random bytes for CSRF token: %v", err)
|
|
||||||
}
|
|
||||||
wui.token = token
|
|
||||||
|
|
||||||
http.HandleFunc("/", wui.Index)
|
|
||||||
http.HandleFunc("/save", wui.Save)
|
|
||||||
http.HandleFunc("/tag", wui.Tag)
|
|
||||||
http.HandleFunc("/saved", wui.SavedSearches)
|
|
||||||
http.HandleFunc("/delete-tag", wui.DeleteTag)
|
|
||||||
|
|
||||||
fs := http.FileServer(http.Dir("./templates/style"))
|
|
||||||
http.Handle("/style/", http.StripPrefix("/style/", fs))
|
|
||||||
|
|
||||||
fs = http.FileServer(http.Dir("./templates/scripts"))
|
|
||||||
http.Handle("/scripts/", http.StripPrefix("/scripts/", fs))
|
|
||||||
|
|
||||||
fs = http.FileServer(http.Dir("./templates/images"))
|
|
||||||
http.Handle("/images/", http.StripPrefix("/images/", fs))
|
|
||||||
|
|
||||||
fs = http.FileServer(http.Dir("./templates/fonts"))
|
|
||||||
http.Handle("/fonts/", http.StripPrefix("/fonts/", fs))
|
|
||||||
|
|
||||||
portstr := strconv.Itoa(port)
|
|
||||||
http.ListenAndServe("127.0.0.1:"+portstr, nil)
|
|
||||||
}
|
|
Loading…
Reference in New Issue