-
Notifications
You must be signed in to change notification settings - Fork 3
/
main.go
200 lines (161 loc) · 4.77 KB
/
main.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
package main
import (
"crypto/tls"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"strings"
"sync"
"github.com/PuerkitoBio/goquery"
"github.com/fatih/color"
"github.com/schollz/progressbar/v3"
)
func main() {
var protocol, domain, subnet, region string
var limit int
var mu sync.Mutex
var numScanned int
var ipAddresses []string
flag.StringVar(&protocol, "proto", "http", "The protocol used by the site behind CF")
flag.StringVar(&domain, "domain", "example.com", "Domain target")
flag.StringVar(&subnet, "subnet", "", "Subnet to scan")
flag.StringVar(®ion, "region", "", "AWS region to scan (optional)")
flag.IntVar(&limit, "jobs", 20, "Number of parallel jobs")
flag.Parse()
Banner()
color.Cyan("Analyzing Domain: %v", domain)
originalTitle, err := getSiteTitle(protocol, domain)
if err != nil {
log.Fatalf("Error reading site title: %v", err)
}
// If the subnet is specified, add it to the list of IP addresses to scan.
if subnet != "" {
ipAddresses, err = getHosts(subnet)
if err != nil {
log.Fatalf("Error getting IP addresses: %v", err)
}
color.Cyan("Number of IPs to scan: %v", len(ipAddresses))
}
// If the region is specified, get the IP address ranges for that region
// from the AWS IP address ranges URL.
if region != "" {
ipAddresses = AWSRegionSubnet(region)
color.Cyan("Number of IPs to scan: %v", len(ipAddresses))
}
// Create a progress bar with the total number of hosts to scan.
bar := progressbar.Default(int64(len(ipAddresses)))
// The `main` func must not finish before all jobs are done. We use a
// WaitGroup to wait for all of them.
wg := new(sync.WaitGroup)
// We use a buffered channel as a semaphore to limit the number of
// concurrently executing jobs.
sem := make(chan struct{}, limit)
// We run each job in its own goroutine but use the semaphore to limit
// their concurrent execution.
for k, i := range ipAddresses {
// This job must be waited for.
wg.Add(1)
// Acquire the semaphore by writing to the buffered channel. If the
// channel is full, this call will block until another job has released
// it.
sem <- struct{}{}
// Now we have acquired the semaphore and can start a goroutine for
// this job. Note that we must capture `i` as an argument.
go func(k int, i string) {
defer func() { <-sem }()
// When the work of this goroutine has been done, we decrement the
// WaitGroup.
defer wg.Done()
// Do the actual work.
if err := scanHost(k, i, protocol, domain, originalTitle); err != nil {
//log.Printf("Error scanning host %s: %v", i, err)
}
// Update the counter and print progress.
mu.Lock()
numScanned++
bar.Add(1)
if numScanned%len(ipAddresses) == 0 {
color.Cyan("Scanned %d hosts", numScanned)
}
mu.Unlock()
}(k, i)
}
// Wait for all jobs to finish.
wg.Wait()
// Finish the progress bar
bar.Finish()
}
func getSiteTitle(protocol, domain string) (string, error) {
resp, err := http.Get(protocol + "://" + domain)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
doc, _ := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
return doc.Find("title").Eq(0).Text(), nil
}
func getHosts(cidr string) ([]string, error) {
ip, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
var ips []string
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
ips = append(ips, ip.String())
}
// remove network address and broadcast address
lenIPs := len(ips)
switch {
case lenIPs < 2:
return ips, nil
default:
return ips[1 : len(ips)-1], nil
}
}
func inc(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}
func scanHost(k int, ip, protocol, domain, originalTitle string) error {
req, err := http.NewRequest("GET", protocol+"://"+ip, nil)
if err != nil {
return fmt.Errorf("error creating request: %v", err)
}
req.Host = domain
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("HTTP call failed: %v", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("error reading body: %v", err)
}
doc, _ := goquery.NewDocumentFromReader(strings.NewReader(string(body)))
title := doc.Find("title").Eq(0).Text()
switch originalTitle {
case title:
color.Green("##############-HOST FOUND-###################\n")
color.Green("Server IP: %v", ip)
color.Green("HTTP Status: %v", resp.StatusCode)
color.Green("#############################################\n")
}
return nil
}