/
analysis.go
157 lines (137 loc) · 4.81 KB
/
analysis.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
// PhishDetect
// Copyright (c) 2018-2021 Claudio Guarnieri.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package phishdetect
import (
"fmt"
"github.com/phishdetect/phishdetect/brands"
"github.com/phishdetect/phishdetect/browser"
"github.com/phishdetect/phishdetect/checks"
"github.com/phishdetect/phishdetect/link"
"github.com/phishdetect/phishdetect/page"
log "github.com/sirupsen/logrus"
)
// Warning is a converstion of Check containing only results.
type Warning struct {
Score int `json:"score"`
Name string `json:"name"`
Description string `json:"description"`
Matches interface{} `json:"matches"`
}
// Analysis contains information on the outcome of the URL and/or HTML analysis.
type Analysis struct {
URL string `json:"url"`
FinalURL string `json:"final_url"`
HTML string `json:"html"`
Warnings []Warning `json:"warnings"`
Score int `json:"score"`
Safelisted bool `json:"safelisted"`
Dangerous bool `json:"dangerous"`
Brands *brands.Brands `json:"brands"`
}
// LoadYaraRules allows to pre-load Yara rules to be used during analysis.
// NOTE: This is mostly intended to avoid library users from having to import
// the "check" package.
func LoadYaraRules(yaraRulesPath string) error {
return checks.InitializeYara(yaraRulesPath)
}
// AddSafeBrowsingKey allows to pre-load a key for Google SafeBrowsing lookups.
// NOTE: This is mostly intended to avoid library users from having to import
// the "check" package.
func AddSafeBrowsingKey(key string) {
checks.SafeBrowsingKey = key
}
// NewAnalysis instantiates a new Analysis struct.
func NewAnalysis(url, html string) *Analysis {
brandsList := brands.New()
return &Analysis{
URL: url,
FinalURL: url,
HTML: html,
Brands: brandsList,
}
}
func (a *Analysis) analyzeDomainOrURL(checks []checks.Check) error {
log.Debug("Starting to analyze the URL...")
link, err := link.New(a.FinalURL)
if err != nil {
return fmt.Errorf("failed to parse URL, might be invalid: %v", err)
}
for _, check := range checks {
log.Debug("Running domain check ", check.Name, " ...")
matched, matches := check.Call(link, nil, nil, a.Brands)
if matched {
log.Debug("Matched ", check.Name)
a.Score += check.Score
a.Warnings = append(a.Warnings, Warning{
Score: check.Score,
Name: check.Name,
Description: check.Description,
Matches: matches,
})
}
}
a.Safelisted = a.Brands.IsDomainSafelisted(link.TopDomain, "")
// If the domain is marked as safelisted, we check if the link matches
// any dangerous pattern.
if a.Safelisted {
a.Dangerous = a.Brands.IsLinkDangerous(link.URL, "")
}
return nil
}
// AnalyzeDomain performs all the available checks to be run on a URL or domain.
func (a *Analysis) AnalyzeDomain() error {
return a.analyzeDomainOrURL(checks.GetDomainChecks())
}
// AnalyzeURL performs all the available checks to be run on a URL or domain.
func (a *Analysis) AnalyzeURL() error {
return a.analyzeDomainOrURL(checks.GetURLChecks())
}
func (a *Analysis) analyzeHTML(browser *browser.Browser) error {
log.Debug("Starting to analyze HTML...")
link, err := link.New(a.FinalURL)
if err != nil {
return fmt.Errorf("failed parsing the link, it might be invalid: %v",
err)
}
page, err := page.New(a.HTML)
if err != nil {
return err
}
for _, check := range checks.GetHTMLChecks() {
log.Debug("Running HTML check ", check.Name, " ...")
matched, matches := check.Call(link, page, browser, a.Brands)
if matched {
log.Debug("Matched ", check.Name)
a.Score += check.Score
a.Warnings = append(a.Warnings, Warning{
Score: check.Score,
Name: check.Name,
Description: check.Description,
Matches: matches,
})
}
}
return nil
}
// AnalyzeHTML performs all the available checks to be run on an HTML string.
func (a *Analysis) AnalyzeHTML() error {
return a.analyzeHTML(nil)
}
// AnalyzeBrowserResults performs all the available checks to be run on an HTML string
// as well as the provided list of HTTP requests (e.g. downloaded scripts).
func (a *Analysis) AnalyzeBrowserResults(browser *browser.Browser) error {
return a.analyzeHTML(browser)
}