Repository navigation
Expand file tree
/
Copy pathsarif.go
More file actions
169 lines (152 loc) · 5.83 KB
/
Copy pathsarif.go
File metadata and controls
169 lines (152 loc) · 5.83 KB
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
package output
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"sort"
"strings"
)
// SARIF (2.1.0) output for takeover candidates (#129), so findings can be
// uploaded to GitHub code scanning (or any SARIF consumer) and tracked,
// triaged and dismissed there. Only names with a takeover hint are results;
// a run with none writes a valid document with no results.
const sarifHelp = "https://tmhsdigital.github.io/subenum/"
type sarifLog struct {
Schema string `json:"$schema"`
Version string `json:"version"`
Runs []sarifRun `json:"runs"`
}
type sarifRun struct {
Tool sarifTool `json:"tool"`
Results []sarifResult `json:"results"`
}
type sarifTool struct {
Driver sarifDriver `json:"driver"`
}
type sarifDriver struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
InformationURI string `json:"informationUri"`
Rules []sarifRule `json:"rules"`
}
type sarifRule struct {
ID string `json:"id"`
Name string `json:"name"`
ShortDescription sarifText `json:"shortDescription"`
FullDescription sarifText `json:"fullDescription"`
HelpURI string `json:"helpUri"`
DefaultConfiguration sarifRuleLevel `json:"defaultConfiguration"`
Properties sarifRuleProps `json:"properties"`
}
type sarifRuleLevel struct {
Level string `json:"level"`
}
type sarifRuleProps struct {
Tags []string `json:"tags"`
SecuritySeverity string `json:"security-severity"`
}
type sarifText struct {
Text string `json:"text"`
}
type sarifResult struct {
RuleID string `json:"ruleId"`
Level string `json:"level"`
Message sarifText `json:"message"`
Locations []sarifLocation `json:"locations"`
PartialFingerprints map[string]string `json:"partialFingerprints"`
}
type sarifLocation struct {
PhysicalLocation sarifPhysical `json:"physicalLocation"`
}
type sarifPhysical struct {
ArtifactLocation sarifArtifact `json:"artifactLocation"`
}
type sarifArtifact struct {
URI string `json:"uri"`
}
// takeoverRule maps a takeover marker ("dangling", "provider:heroku",
// "dangling:heroku") to its SARIF rule.
func takeoverRule(marker string) sarifRule {
kind, provider, _ := strings.Cut(marker, ":")
r := sarifRule{HelpURI: sarifHelp + "cli.html", Properties: sarifRuleProps{Tags: []string{"security", "subdomain-takeover", "dns"}}}
switch {
case kind == "dangling" && provider == "":
r.ID, r.Name = "subenum/dangling-cname", "DanglingCNAME"
r.ShortDescription.Text = "CNAME points at a name that does not exist"
r.FullDescription.Text = "The subdomain is an alias for a name that returns NXDOMAIN. Whoever registers or claims that name may control the subdomain. Remove the record or reclaim the target."
r.DefaultConfiguration.Level, r.Properties.SecuritySeverity = "error", "7.5"
case kind == "dangling":
r.ID, r.Name = "subenum/dangling-cname/"+provider, "DanglingCNAMEAt"+provider
r.ShortDescription.Text = "CNAME points at an unclaimed " + provider + " name"
r.FullDescription.Text = "The subdomain is an alias for a " + provider + " name that returns NXDOMAIN. " + provider + " lets anyone claim such names, so this is a likely subdomain takeover. Remove the record or reclaim the resource."
r.DefaultConfiguration.Level, r.Properties.SecuritySeverity = "error", "8.6"
default:
r.ID, r.Name = "subenum/takeover-prone-provider/"+provider, "TakeoverProneProvider"+provider
r.ShortDescription.Text = "CNAME points at " + provider + ", a takeover-prone service"
r.FullDescription.Text = "The subdomain is an alias for a " + provider + " resource. It resolves today, but if the resource is deleted while the record stays, anyone can claim it. Keep the record and the resource in the same lifecycle."
r.DefaultConfiguration.Level, r.Properties.SecuritySeverity = "note", "3.0"
}
return r
}
// WriteSARIF writes the takeover candidates among results to path as a
// SARIF 2.1.0 log, atomically. version is the tool version.
func WriteSARIF(path, version string, results []Result) error {
rules := map[string]sarifRule{}
var out []sarifResult
for _, r := range results {
if r.Takeover == "" {
continue
}
rule := takeoverRule(r.Takeover)
rules[rule.ID] = rule
target := ""
for _, rec := range r.Records {
if rec.Type == "CNAME" {
target = strings.TrimSuffix(rec.Value, ".")
}
}
msg := fmt.Sprintf("%s: %s", r.Subdomain, rule.ShortDescription.Text)
if target != "" {
msg = fmt.Sprintf("%s is a CNAME for %s. %s.", r.Subdomain, target, rule.ShortDescription.Text)
}
sum := sha256.Sum256([]byte(r.Subdomain + "\x00" + r.Takeover))
out = append(out, sarifResult{
RuleID: rule.ID,
Level: rule.DefaultConfiguration.Level,
Message: sarifText{Text: msg},
Locations: []sarifLocation{{PhysicalLocation: sarifPhysical{ArtifactLocation: sarifArtifact{URI: r.Subdomain}}}},
PartialFingerprints: map[string]string{"subenumTakeover/v1": hex.EncodeToString(sum[:16])},
})
}
ids := make([]string, 0, len(rules))
for id := range rules {
ids = append(ids, id)
}
sort.Strings(ids)
driver := sarifDriver{Name: "subenum", Version: version, InformationURI: sarifHelp, Rules: []sarifRule{}}
for _, id := range ids {
driver.Rules = append(driver.Rules, rules[id])
}
if out == nil {
out = []sarifResult{}
}
doc := sarifLog{
Schema: "https://json.schemastore.org/sarif-2.1.0.json",
Version: "2.1.0",
Runs: []sarifRun{{Tool: sarifTool{Driver: driver}, Results: out}},
}
data, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return err
}
f, err := CreateFile(path)
if err != nil {
return err
}
_, werr := f.Write(append(data, '\n'))
if cerr := f.Close(werr == nil); werr == nil {
werr = cerr
}
return werr
}