mirror of
https://github.com/cubixle/assetfinder.git
synced 2026-04-24 23:04:47 +01:00
initial
This commit is contained in:
32
bufferoverrun.go
Normal file
32
bufferoverrun.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package assetfinder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BufferOverrun struct{}
|
||||
|
||||
func (BufferOverrun) FetchSubDomains(domain string) ([]string, error) {
|
||||
out := make([]string, 0)
|
||||
|
||||
fetchURL := fmt.Sprintf("https://dns.bufferover.run/dns?q=.%s", domain)
|
||||
|
||||
wrapper := struct {
|
||||
Records []string `json:"FDNS_A"`
|
||||
}{}
|
||||
err := fetchJSON(fetchURL, &wrapper)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
for _, r := range wrapper.Records {
|
||||
parts := strings.SplitN(r, ",", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
out = append(out, parts[1])
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
25
certspotter.go
Normal file
25
certspotter.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package assetfinder
|
||||
|
||||
import "fmt"
|
||||
|
||||
type CertSpotter struct{}
|
||||
|
||||
func (CertSpotter) FetchSubDomains(domain string) ([]string, error) {
|
||||
out := make([]string, 0)
|
||||
|
||||
fetchURL := fmt.Sprintf("https://certspotter.com/api/v0/certs?domain=%s", domain)
|
||||
|
||||
wrapper := []struct {
|
||||
DNSNames []string `json:"dns_names"`
|
||||
}{}
|
||||
err := fetchJSON(fetchURL, &wrapper)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
for _, w := range wrapper {
|
||||
out = append(out, w.DNSNames...)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
26
cmd/assetfinder/main.go
Normal file
26
cmd/assetfinder/main.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/cubixle/assetfinder"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// subsOnly := flag.Bool("subs-only", false, "Only search for subdomains.")
|
||||
disableStatusCheck := flag.Bool("status", false, "Enable/Disable checking the status code for each sub domain found.")
|
||||
checkHTTPS := flag.Bool("https", true, "Enable/Disable the checking of status of https on a domain.")
|
||||
outfile := flag.String("output", "", "Where to save results to.")
|
||||
verbose := flag.Bool("verbose", false, "Turns on verbose logging. Mainly used for debugging development.")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
domain := flag.Arg(0)
|
||||
if domain == "" {
|
||||
log.Fatal("no domain given")
|
||||
}
|
||||
if err := assetfinder.Scanner(*verbose, *checkHTTPS, *disableStatusCheck, *outfile, domain); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
39
crtsh.go
Normal file
39
crtsh.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package assetfinder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type CrtShResult struct {
|
||||
Name string `json:"name_value"`
|
||||
}
|
||||
|
||||
type CrtSh struct{}
|
||||
|
||||
func (CrtSh) FetchSubDomains(domain string) ([]string, error) {
|
||||
var results []CrtShResult
|
||||
|
||||
resp, err := http.Get(
|
||||
fmt.Sprintf("https://crt.sh/?q=%%25.%s&output=json", domain),
|
||||
)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
output := make([]string, 0)
|
||||
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err := json.Unmarshal(body, &results); err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
for _, res := range results {
|
||||
output = append(output, res.Name)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
126
scanner.go
Normal file
126
scanner.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package assetfinder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Scanner(verboseLogging, checkHTTPS, disableStatusCheck bool, outputFile, domain string) error {
|
||||
results := []string{}
|
||||
for _, source := range LoadedSources {
|
||||
subdomains, err := source.FetchSubDomains(domain)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
results = append(results, subdomains...)
|
||||
}
|
||||
sortedDomains := sortDomains(results)
|
||||
|
||||
ress := []Result{}
|
||||
if !disableStatusCheck {
|
||||
for _, r := range sortedDomains {
|
||||
// http
|
||||
u, err := url.Parse("http://" + r)
|
||||
if err != nil {
|
||||
if verboseLogging {
|
||||
log.Println(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
re := fetchStatus(u.String())
|
||||
ress = append(ress, re)
|
||||
|
||||
if checkHTTPS && re.StatusCode != 0 {
|
||||
u.Scheme = "https"
|
||||
re := fetchStatus(u.String())
|
||||
ress = append(ress, re)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ress = convertToResults(results)
|
||||
}
|
||||
|
||||
if outputFile == "" {
|
||||
for _, r := range ress {
|
||||
if r.StatusCode == 0 || r.Error != nil {
|
||||
continue
|
||||
}
|
||||
log.Printf("| %d | %s\n", r.StatusCode, r.Domain)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return writeFile(outputFile, ress)
|
||||
}
|
||||
|
||||
func writeFile(outputFile string, res []Result) error {
|
||||
resBytes := []byte("domain, status code")
|
||||
for _, r := range res {
|
||||
if r.StatusCode == 0 || r.Error != nil {
|
||||
continue
|
||||
}
|
||||
resBytes = append(resBytes, []byte(fmt.Sprintf("%s, %d\n", r.Domain, r.StatusCode))...)
|
||||
}
|
||||
|
||||
err := ioutil.WriteFile(outputFile, resBytes, 0775)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortDomains(domains []string) []string {
|
||||
encountered := map[string]string{}
|
||||
fixedList := []string{}
|
||||
for _, d := range domains {
|
||||
if strings.Contains(d, "\n") {
|
||||
for _, d := range strings.Split(d, "\n") {
|
||||
d = cleanDomain(d)
|
||||
fixedList = append(fixedList, d)
|
||||
}
|
||||
} else {
|
||||
d = cleanDomain(d)
|
||||
fixedList = append(fixedList, d)
|
||||
}
|
||||
}
|
||||
|
||||
// dedup
|
||||
newList := []string{}
|
||||
for _, d := range fixedList {
|
||||
_, ok := encountered[d]
|
||||
if !ok {
|
||||
encountered[d] = d
|
||||
newList = append(newList, d)
|
||||
}
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
func convertToResults(strRes []string) []Result {
|
||||
ress := []Result{}
|
||||
for _, r := range strRes {
|
||||
ress = append(ress, Result{
|
||||
Domain: r,
|
||||
})
|
||||
}
|
||||
return ress
|
||||
}
|
||||
|
||||
func cleanDomain(d string) string {
|
||||
d = strings.ToLower(d)
|
||||
if len(d) < 2 {
|
||||
return d
|
||||
}
|
||||
if d[0] == '*' || d[0] == '%' {
|
||||
d = d[1:]
|
||||
}
|
||||
|
||||
if d[0] == '.' {
|
||||
d = d[1:]
|
||||
}
|
||||
return d
|
||||
}
|
||||
67
sources.go
Normal file
67
sources.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package assetfinder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var LoadedSources = []Source{
|
||||
BufferOverrun{},
|
||||
CertSpotter{},
|
||||
CrtSh{},
|
||||
URLScan{},
|
||||
}
|
||||
|
||||
// Source is an interface that defines the methods required by a source.
|
||||
type Source interface {
|
||||
FetchSubDomains(domain string) ([]string, error)
|
||||
}
|
||||
|
||||
// Result is the basic type that should be return from the sources.Fetch method.
|
||||
type Result struct {
|
||||
Domain string
|
||||
StatusCode int
|
||||
Error error
|
||||
}
|
||||
|
||||
func fetchJSON(url string, i interface{}) error {
|
||||
client := httpClient()
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
return dec.Decode(i)
|
||||
}
|
||||
|
||||
func fetchStatus(url string) Result {
|
||||
res := Result{Domain: url}
|
||||
request, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
res.Error = err
|
||||
return res
|
||||
}
|
||||
|
||||
client := httpClient()
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
res.Error = err
|
||||
return res
|
||||
}
|
||||
res.StatusCode = resp.StatusCode
|
||||
return res
|
||||
}
|
||||
|
||||
func httpClient() http.Client {
|
||||
return http.Client{
|
||||
Timeout: time.Duration(5 * time.Second),
|
||||
}
|
||||
}
|
||||
52
urlscan.go
Normal file
52
urlscan.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package assetfinder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type URLScan struct{}
|
||||
|
||||
type URLScanResults struct {
|
||||
Results []struct {
|
||||
Task struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"task"`
|
||||
|
||||
Page struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"page"`
|
||||
} `json:"results"`
|
||||
}
|
||||
|
||||
func (URLScan) FetchSubDomains(domain string) ([]string, error) {
|
||||
resp := URLScanResults{}
|
||||
err := fetchJSON(
|
||||
fmt.Sprintf("https://urlscan.io/api/v1/search/?q=domain:%s", domain),
|
||||
resp,
|
||||
)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
output := make([]string, 0)
|
||||
|
||||
for _, r := range resp.Results {
|
||||
u, err := url.Parse(r.Task.URL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
output = append(output, u.Hostname())
|
||||
}
|
||||
|
||||
for _, r := range resp.Results {
|
||||
u, err := url.Parse(r.Page.URL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
output = append(output, u.Hostname())
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
Reference in New Issue
Block a user