forked from s-rah/onionscan
-
Notifications
You must be signed in to change notification settings - Fork 0
/
simple_report.go
250 lines (212 loc) · 8.16 KB
/
simple_report.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package report
import (
"bytes"
"encoding/json"
"fmt"
"github.com/s-rah/onionscan/utils"
)
const SEV_INFO = "info"
const SEV_LOW = "low"
const SEV_MEDIUM = "medium"
const SEV_HIGH = "high"
const SEV_CRITICAL = "critical"
type Risk struct {
Severity string `json:"severity"`
Title string `json:"title"`
Description string `json:"description"`
Fix string `json:"fix"`
Items []string `json:"items"`
}
type SimpleReport struct {
HiddenService string `json:"hiddenService"`
Risks []Risk `json:"risks"`
}
func (osr *SimpleReport) AddRisk(severity string, title string, description string, fix string, items []string) {
osr.Risks = append(osr.Risks, Risk{severity, title, description, fix, items})
}
// Format as JSON
func (osr *SimpleReport) Serialize() (string, error) {
report, err := json.Marshal(osr)
if err != nil {
return "", err
}
return string(report), nil
}
var risk_levels = map[string]string{
SEV_INFO: "\033[094mInfo:\033[0m",
SEV_LOW: "\033[093mLow Risk:\033[0m",
SEV_MEDIUM: "\033[093mMedium Risk:\033[0m",
SEV_HIGH: "\033[091mHigh Risk:\033[0m",
SEV_CRITICAL: "\033[091mCritical Risk:\033[0m",
}
// Format as human-readable text to be printed to console
func (osr *SimpleReport) Format(width int) (string, error) {
buffer := bytes.NewBuffer(nil)
buffer.WriteString("--------------- OnionScan Report ---------------\n")
buffer.WriteString(fmt.Sprintf("Generating Report for: %s\n\n", osr.HiddenService))
const indent = " "
for _, risk := range osr.Risks {
buffer.WriteString(risk_levels[risk.Severity] + " " + risk.Title + "\n")
if len(risk.Description) > 0 {
buffer.WriteString(indent + utils.FormatParagraphs(risk.Description, width, len(indent)) + "\n")
}
if len(risk.Fix) > 0 {
buffer.WriteString(indent + utils.FormatParagraphs(risk.Fix, width, len(indent)) + "\n")
}
if len(risk.Items) > 0 {
buffer.WriteString(indent + "Items Identified:\n")
buffer.WriteString("\n")
for _, item := range risk.Items {
buffer.WriteString(indent + item + "\n")
}
}
buffer.WriteString("\n")
}
if len(osr.Risks) == 0 {
buffer.WriteString("No risks were found.\n")
}
return buffer.String(), nil
}
// Interface for SimpleReport checks
type SimpleReportCheck interface {
Check(out *SimpleReport, report *AnonymityReport)
}
// EmailAddressCheck implementation
type EmailAddressCheck struct{}
func (srt *EmailAddressCheck) Check(out *SimpleReport, report *AnonymityReport) {
if len(report.EmailAddresses) > 0 {
out.AddRisk(SEV_INFO, "Found Identities", "", "", report.EmailAddresses)
}
}
// IPAddressCheck implementation
type IPAddressCheck struct{}
func (srt *IPAddressCheck) Check(out *SimpleReport, report *AnonymityReport) {
if len(report.IPAddresses) > 0 {
out.AddRisk(SEV_INFO, "Found IP Addresses", "", "", report.IPAddresses)
}
}
// AnalyticsIDsCheck implementation
type AnalyticsIDsCheck struct{}
func (srt *AnalyticsIDsCheck) Check(out *SimpleReport, report *AnonymityReport) {
if len(report.AnalyticsIDs) > 0 {
out.AddRisk(SEV_INFO, "Found Analytics IDs", "", "", report.AnalyticsIDs)
}
}
// BitcoinAddressesCheck implementation
type BitcoinAddressesCheck struct{}
func (srt *BitcoinAddressesCheck) Check(out *SimpleReport, report *AnonymityReport) {
if len(report.BitcoinAddresses) > 0 {
out.AddRisk(SEV_INFO, "Found Bitcoin Addresses", "", "", report.BitcoinAddresses)
}
}
// ApacheModStatusCheck implementation
type ApacheModStatusCheck struct{}
func (srt *ApacheModStatusCheck) Check(out *SimpleReport, report *AnonymityReport) {
if report.FoundApacheModStatus {
out.AddRisk(SEV_HIGH, "Apache mod_status is enabled and accessible",
"Why this is bad: An attacker can gain very valuable information from this internal status page including IP addresses, co-hosted services and user activity.",
"To fix, disable mod_status or serve it on a different port than the configured hidden service.",
nil)
}
}
// RelatedClearnetDomainsCheck implementation
type RelatedClearnetDomainsCheck struct{}
func (srt *RelatedClearnetDomainsCheck) Check(out *SimpleReport, report *AnonymityReport) {
if len(report.RelatedClearnetDomains) > 0 {
out.AddRisk(SEV_HIGH, "You are hosting a clearnet site on the same server as this onion service!",
"Why this is bad: This may be intentional, but often isn't. Services are best operated in isolation such that a compromise of one does not mean a compromise of the other.",
"To fix, host all services on separate infrastructure.",
report.RelatedClearnetDomains)
}
}
// RelatedOnionDomainsCheck implementation
type RelatedOnionServicesCheck struct{}
func (srt *RelatedOnionServicesCheck) Check(out *SimpleReport, report *AnonymityReport) {
if len(report.RelatedOnionServices) > 0 {
out.AddRisk(SEV_MEDIUM, "You are hosting multiple onion services on the same server as this onion service!",
"Why this is bad: This may be intentional, but often isn't. Hidden services are best operated in isolation such that a compromise of one does not mean a compromise of the other.",
"To fix, host all services on separate infrastructure.",
report.RelatedOnionServices)
}
}
// OpenDirectoriesCheck implementation
type OpenDirectoriesCheck struct{}
func (srt *OpenDirectoriesCheck) Check(out *SimpleReport, report *AnonymityReport) {
if len(report.OpenDirectories) > 0 {
var severity string
var title string
if len(report.OpenDirectories) > 10 {
severity = SEV_MEDIUM
title = "Large number of open directories were discovered!"
} else {
severity = SEV_LOW
title = "Small number of open directories were discovered!"
}
out.AddRisk(severity, title,
"Why this is bad: Open directories can reveal the existence of files not linked from the sites source code. Most of the time this is benign, but sometimes operators forget to clean up more sensitive folders.",
"To fix, use .htaccess rules or equivalent to make reading directories listings forbidden. Quick Fix (Disable indexing globally) for Debian / Ubuntu running Apache: a2dismod autoindex as root.",
report.OpenDirectories)
}
}
// ExifImagesCheck implementation
type ExifImagesCheck struct{}
func (srt *ExifImagesCheck) Check(out *SimpleReport, report *AnonymityReport) {
if len(report.ExifImages) > 0 {
var severity string
var title string
if len(report.OpenDirectories) > 10 {
severity = SEV_HIGH
title = "Large number of images with EXIF metadata were discovered!"
} else {
severity = SEV_MEDIUM
title = "Small number of images with EXIF metadata were discovered!"
}
items := []string{}
for _, image := range report.ExifImages {
items = append(items, image.Location)
}
out.AddRisk(severity, title,
"Why this is bad: EXIF metadata can itself deanonymize a user or service operator (e.g. GPS location, Name etc.). Or, when combined, can be used to link anonymous identities together.",
"To fix, re-encode all images to strip EXIF and other metadata.",
items)
}
}
// PrivateKeyCheck implementation
type PrivateKeyCheck struct{}
func (srt *PrivateKeyCheck) Check(out *SimpleReport, report *AnonymityReport) {
if report.PrivateKeyDetected {
out.AddRisk(SEV_CRITICAL, "Hidden service private key is accessible!",
"Why this is bad: This can be used to impersonate the service at any point in the future.",
"To fix, generate a new hidden service and make sure the private_key file is not reachable from the web root.",
nil)
}
}
// Standard checks performed for SimpleReport generation
// Plugins can extend this list by calling RegisterSimpleReportCheck
var checks = []SimpleReportCheck{
&EmailAddressCheck{},
&IPAddressCheck{},
&AnalyticsIDsCheck{},
&BitcoinAddressesCheck{},
&ApacheModStatusCheck{},
&RelatedClearnetDomainsCheck{},
&RelatedOnionServicesCheck{},
&OpenDirectoriesCheck{},
&ExifImagesCheck{},
&PrivateKeyCheck{},
}
func SummarizeToSimpleReport(hiddenService string, report *AnonymityReport) *SimpleReport {
var out = NewSimpleReport(hiddenService)
for _, check := range checks {
check.Check(out, report)
}
return out
}
func NewSimpleReport(hiddenService string) *SimpleReport {
var osr = new(SimpleReport)
osr.HiddenService = hiddenService
return osr
}
func RegisterSimpleReportCheck(check SimpleReportCheck) {
checks = append(checks, check)
}