Skip to content

Commit bf73627

Browse files
committed
Initial commit
1 parent b4f8140 commit bf73627

File tree

4 files changed

+477
-0
lines changed

4 files changed

+477
-0
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/subdomains/*
2+
/testfiles-gocrt/*
3+
combined.txt
4+
domains.txt
5+
gocrt
6+
*.tgz
7+
*.zip
8+
*.swp
9+
*.exe

README.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# gocrt
2+
`gocrt` is a command line client for [crt.sh](https://crt.sh/) written in golang.
3+
```text
4+
$ gocrt -o gitlab-demo < domains.txt
5+
Get subdomains from: test.de
6+
Get subdomains from: example.com
7+
Save subdomains from: test.de -> saved
8+
Save subdomains from: example.com -> saved
9+
[✓] Done
10+
11+
$ ls -ls gitlab-demo
12+
drwxr-xr-x 2 qwertty qwertty 4096 16. Okt 14:29 .
13+
drwxr-xr-x 5 qewrtty qwertty 4096 16. Okt 14:29 ..
14+
-rw-r--r-- 1 qewrtty qwertty 133 16. Okt 14:29 example.com
15+
-rw-r--r-- 1 qwertty qwertty 473 16. Okt 14:29 test.de
16+
```
17+
18+
## Installation
19+
`gocrt` has no runtime dependencies. You can just [download a binary](https://gitlab.com/qwertty/gocrt/releases) for Linux, Mac, Windows or FreeBSD and run it.
20+
Put the binary in your `$PATH` (e.g. in `/usr/local/bin`) to make it easy to use:
21+
```bash
22+
$ tar xzf gocrt-linux-amd64-0.0.1.tgz
23+
$ sudo mv gocrt /usr/local/bin/
24+
```
25+
26+
If you've got Go installed and configured you can install `gocrt` with:
27+
```bash
28+
$ go get -u gitlab.com/qwertty/gocrt
29+
```
30+
31+
## Usage
32+
Get domain from `command line`:
33+
```bash
34+
$ gocrt example.com
35+
```
36+
37+
Get domain from `stdin`:
38+
```bash
39+
$ cat domains.txt | gocrt
40+
# OR
41+
$ gocrt < domains.txt
42+
```
43+
44+
Pipe found subdomains to other tools:
45+
```bash
46+
$ gocrt -s < domains.txt | httprobe
47+
# OR
48+
$ gocrt -s < domains.txt | tee combined.txt | httprobe
49+
# OR
50+
$ cat domains.txt | gocrt -s | httprobe
51+
# OR
52+
$ gocrt --stdout example.com | httprobe
53+
```
54+
55+
Store subdomains to custom directory:
56+
```bash
57+
$ cat domains.txt | gocrt -o my-custom-dir
58+
# OR
59+
$ gocrt --output my-custom-dir < domains.txt
60+
```
61+
62+
## Get Help
63+
```text
64+
$ gocrt --help
65+
gocrt is a command line client for crt.sh written in golang.
66+
67+
Usage:
68+
gocrt [OPTIONS] [FILE|URL|-]
69+
70+
Options:
71+
-h, --help Print help/usage informations
72+
-o, --output Custom output directory for all found subdomains of given domains, DEFAULT: 'subdomains'
73+
-c, --combine Additionally combine output for all found subdomains of given domains in one file
74+
-s, --stdout Print only subdomains to STDOUT so they can be piped directly to other tools, they will not be saved into files
75+
--version Print version information
76+
77+
Examples:
78+
cat domains.txt | gocrt -o domains-crt
79+
gocrt -o domains-crt example.com
80+
gocrt < domains.txt
81+
gocrt -s < domains.txt | tee combined.txt | httprobe
82+
gocrt example.com
83+
84+
```

main.go

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"strings"
6+
"fmt"
7+
"os"
8+
"log"
9+
"bufio"
10+
"sync"
11+
"io/ioutil"
12+
"net/url"
13+
"net/http"
14+
"encoding/json"
15+
"regexp"
16+
)
17+
18+
// gocrt version
19+
var gocrtVersion = "0.0.1-dev"
20+
21+
// Wrapper for printing messages
22+
// - used to print only subdomains to STDOUT
23+
func printMessage(silent bool, message string, arguments ...interface{}) {
24+
if ! silent {
25+
fmt.Printf(message, arguments...)
26+
}
27+
}
28+
29+
// filter invalid domains/subdomains
30+
func filterInvalidDomains(domains []string) ([]string) {
31+
var filtered []string
32+
regex, _ := regexp.Compile(`^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$`)
33+
for _, domain := range domains {
34+
if regex.Match([]byte(domain)) {
35+
filtered = append(filtered, domain)
36+
}
37+
}
38+
39+
return filtered
40+
}
41+
42+
// make given list unique
43+
func unique(list []string) ([]string) {
44+
allKeys := make(map[string]bool)
45+
uniqueList := []string{}
46+
for _, item := range list{
47+
if _, value := allKeys[item]; !value {
48+
allKeys[item] = true
49+
uniqueList = append(uniqueList, item)
50+
}
51+
}
52+
return uniqueList
53+
}
54+
55+
// Extract domain from possible link
56+
func extractDomain(link string) (string) {
57+
parsedUrl, err := url.Parse(link)
58+
59+
if err == nil && len(parsedUrl.Hostname()) != 0 {
60+
link = parsedUrl.Hostname()
61+
}
62+
63+
link = strings.TrimSpace(link)
64+
return url.QueryEscape(
65+
strings.ToLower(link))
66+
}
67+
68+
// Get domains from stdin/pipe/command line argument
69+
func getDomains() ([]string, error) {
70+
var domain string
71+
var domains []string
72+
var err error
73+
74+
if flag.NArg() == 0 { // read from stdin/pipe
75+
76+
scanner := bufio.NewScanner(os.Stdin)
77+
for scanner.Scan() {
78+
domains = append(domains, extractDomain(scanner.Text()))
79+
}
80+
81+
err = scanner.Err()
82+
83+
} else { // read from argument
84+
85+
domain = extractDomain(os.Args[len(os.Args) - 1])
86+
domains = append(domains, domain)
87+
88+
}
89+
90+
return unique(domains), err
91+
}
92+
93+
// Get JSON-data from crt.sh
94+
func getCrtShJson(domain string) (string) {
95+
crtUrl := fmt.Sprintf("https://crt.sh?q=%s&output=json", domain)
96+
97+
response, err := http.Get(crtUrl)
98+
if err != nil {
99+
log.Fatal(err)
100+
}
101+
102+
defer response.Body.Close()
103+
data, err := ioutil.ReadAll(response.Body)
104+
if err != nil {
105+
log.Fatal(err)
106+
}
107+
108+
return string(data)
109+
}
110+
111+
// Extract subdomains from JSON-data
112+
func extractSubdomainsFromJson(jsonData string) ([]string){
113+
var subdomains []string
114+
var entries []map[string]interface{}
115+
116+
json.Unmarshal([]byte(jsonData), &entries)
117+
for _, entry := range entries {
118+
commonName := entry["common_name"].(string)
119+
commonNameList := strings.Split(commonName, "\n")
120+
subdomains = append(subdomains, commonNameList...)
121+
122+
nameValue := entry["name_value"].(string)
123+
nameValueList := strings.Split(nameValue, "\n")
124+
subdomains = append(subdomains, nameValueList...)
125+
}
126+
127+
subdomains = filterInvalidDomains(subdomains)
128+
return unique(subdomains)
129+
}
130+
131+
// Write subdomains into file
132+
func saveSubdomains(dir string, domain string,
133+
subdomains []string, fileFlags int) (bool){
134+
outputDir := "./" + dir
135+
os.Mkdir(outputDir, 0755)
136+
137+
filePath := outputDir + "/" + domain
138+
file, err := os.OpenFile(filePath, fileFlags, 0644)
139+
140+
if err != nil {
141+
log.Fatalf("Could not create file to save subdomains: %s", err)
142+
return false
143+
}
144+
145+
writer := bufio.NewWriter(file)
146+
for _, subdomain := range subdomains {
147+
writer.WriteString(subdomain + "\n")
148+
}
149+
writer.Flush()
150+
file.Close()
151+
152+
return true
153+
}
154+
155+
// init, get called automatic before main()
156+
func init() {
157+
flag.Usage = func() {
158+
h := "gocrt is a command line client for crt.sh written in golang.\n\n"
159+
160+
h += "Usage:\n"
161+
h += " gocrt [OPTIONS] [FILE|URL|-]\n\n"
162+
163+
h += "Options:\n"
164+
h += " -h, --help Print help/usage informations\n"
165+
h += " -o, --output Custom output directory for all found subdomains of given domains, DEFAULT: 'subdomains'\n"
166+
h += " -c, --combine Additionally combine output for all found subdomains of given domains in one file\n"
167+
h += " -s, --stdout Print only subdomains to STDOUT so they can be piped directly to other tools, they will not be saved into files\n"
168+
h += " --version Print version information\n"
169+
h += "\n"
170+
171+
h += "Examples:\n"
172+
h += " cat domains.txt | gocrt -o domains-crt\n"
173+
h += " gocrt -o domains-crt example.com \n"
174+
h += " gocrt < domains.txt\n"
175+
h += " gocrt -s < domains.txt | tee combined.txt | httprobe\n"
176+
h += " gocrt example.com\n"
177+
178+
fmt.Fprintf(os.Stderr, h)
179+
}
180+
}
181+
182+
// main, magic happens here
183+
func main() {
184+
// "version" command line argument
185+
var version bool
186+
flag.BoolVar(&version, "version", false, "")
187+
188+
// "output" command line argument
189+
var output string
190+
flag.StringVar(&output, "output", "subdomains", "")
191+
flag.StringVar(&output, "o", "subdomains", "")
192+
193+
// "combine" command line argument
194+
var combine bool
195+
flag.BoolVar(&combine, "combine", false, "")
196+
flag.BoolVar(&combine, "c", false, "")
197+
198+
// "stdout" command line argument
199+
var stdout bool
200+
flag.BoolVar(&stdout, "stdout", false, "")
201+
flag.BoolVar(&stdout, "s", false, "")
202+
203+
flag.Parse()
204+
205+
// Print version
206+
if version {
207+
printMessage(stdout, "gocrt version: %s\n", gocrtVersion)
208+
os.Exit(0)
209+
}
210+
211+
// Get domains to request
212+
domains, err := getDomains()
213+
if err != nil {
214+
log.Fatal(err)
215+
os.Exit(3)
216+
}
217+
218+
// Get JSON-data from crt.sh
219+
var worker sync.WaitGroup
220+
for _, domain := range domains {
221+
worker.Add(1)
222+
go func(domain string) {
223+
defer worker.Done()
224+
225+
printMessage(stdout, "Get subdomains from: %s\n", domain)
226+
jsonData := getCrtShJson(domain)
227+
subdomains := extractSubdomainsFromJson(jsonData)
228+
229+
if stdout { // Print subdomains to STDOUT
230+
for _, subdomain := range subdomains {
231+
printMessage(!stdout, "%s\n", subdomain)
232+
}
233+
} else { // Print messages to STDOUT and save subdomains to files
234+
printMessage(stdout, "Save subdomains from: %s", domain)
235+
saved := saveSubdomains(output, domain,
236+
subdomains, os.O_CREATE|os.O_RDWR|os.O_TRUNC)
237+
if saved {
238+
printMessage(stdout, " -> saved\n")
239+
}
240+
241+
if combine { // additionally combine all domains
242+
saveSubdomains(output, "combined.txt", subdomains,
243+
os.O_CREATE|os.O_RDWR|os.O_APPEND)
244+
}
245+
}
246+
}(domain)
247+
}
248+
worker.Wait()
249+
250+
if combine {
251+
printMessage(stdout, "Additionally saved subdomains combined in one file\n")
252+
}
253+
printMessage(stdout, "[\u2713] Done\n")
254+
}

0 commit comments

Comments
 (0)