├── go.mod
├── .gitignore
├── README.md
├── LICENSE
├── secrets.json
└── findsecret.go
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/burak0x01/findsecret
2 |
3 | go 1.17
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 | build/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # findsecret
2 | Find secret keys from JS file
3 |
4 | ## Installation
5 | ### From Source
6 | 1. Install Go on your system (min v17.0.0 and GO111MODULE env should be "on")
7 | 2. `go install github.com/burak0x01/findsecret@latest`
8 |
9 | ### From Binary
10 | You can download the pre-built binaries from the releases page and run. For example:
11 | `unzip findsecret_linux_amd64.zip`
12 | `mv findsecret /usr/bin`
13 | `findsecret -h`
14 |
15 | ## Usage
16 | Findsecret requires 2 parameters to run: -i (input), -o (output).
17 |
18 | #### Input
19 | `findsecret -i https://domain.tld`
20 | `findsecret -i ./local/path/main.js`
21 | `findsecret -i https://somedomain/something.js`
22 |
23 | #### Output
24 | `findsecret -i https://domain.tld -o cli (default)`
25 | `findsecret -i https://domain.tld -o result.txt`
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Burak
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/secrets.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "auth basic",
4 | "patterns": ["basic [a-zA-Z0-9_\\-:\\.=]+"]
5 | },
6 | {
7 | "name": "auth bearer",
8 | "patterns": ["bearer [a-zA-Z0-9_\\-\\.=]+"]
9 | },
10 | {
11 | "name": "auth http",
12 | "patterns": ["(https?://)[a-zA-Z0-9]+:[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z]+"]
13 | },
14 | {
15 | "name": "aws client id",
16 | "patterns": ["(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}"]
17 | },
18 | {
19 | "name": "aws keys",
20 | "patterns": ["([^A-Z0-9]|^)(AKIA|A3T|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{12,}"]
21 | },
22 | {
23 | "name": "aws mvs key",
24 | "patterns": ["amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"]
25 | },
26 | {
27 | "name": "aws secret key",
28 | "patterns": ["(?i)aws(.{0,20})?(?-i)['\"][0-9a-zA-Z/+]{40}['\"]"]
29 | },
30 | {
31 | "name": "base64",
32 | "patterns": ["(eyJ|YTo|Tzo|PD[89]|aHR0cHM6L|aHR0cDo|rO0)[a-zA-Z0-9+/]+={0,2}"]
33 | },
34 | {
35 | "name": "cloudinary-basic-auth",
36 | "patterns": ["cloudinary://[0-9]{15}:[0-9A-Za-z]+@[a-z]+"]
37 | },
38 | {
39 | "name": "cors",
40 | "patterns": ["Access-Control-Allow"]
41 | },
42 | {
43 | "name": "facebook access token",
44 | "patterns": ["EAACEdEose0cBA[0-9A-Za-z]+"]
45 | },
46 | {
47 | "name": "facebook client id",
48 | "patterns": ["(?i)(facebook|fb)(.{0,20})?['\"][0-9]{13,17}"]
49 | },
50 | {
51 | "name": "facebook oauth",
52 | "patterns": ["[f|F][a|A][c|C][e|E][b|B][o|O][o|O][k|K].*['|\"][0-9a-f]{32}['|\"]"]
53 | },
54 | {
55 | "name": "facebook secret key",
56 | "patterns": ["(?i)(facebook|fb)(.{0,20})?(?-i)['\"][0-9a-f]{32}"]
57 | },
58 | {
59 | "name": "firebase",
60 | "patterns": ["firebaseio.com"]
61 | },
62 | {
63 | "name": "github",
64 | "patterns": ["(?i)github(.{0,20})?(?-i)['\"][0-9a-zA-Z]{35,40}"]
65 | },
66 | {
67 | "name": "google api|drive|youtube key",
68 | "patterns": ["AIza[0-9A-Za-z\\-_]{35}"]
69 | },
70 | {
71 | "name": "google cloud key",
72 | "patterns": ["(?i)(google|gcp|youtube|drive|yt)(.{0,20})?['\"][AIza[0-9a-z\\-_]{35}]['\"]"]
73 | },
74 | {
75 | "name": "google oauth token",
76 | "patterns": ["ya29.[0-9A-Za-z\\-_]+"]
77 | },
78 | {
79 | "name": "heroku api key",
80 | "patterns": ["[h|H][e|E][r|R][o|O][k|K][u|U].{0,30}[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}"]
81 | },
82 | {
83 | "name": "ipv4",
84 | "patterns": ["(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}"]
85 | },
86 | {
87 | "name": "json sec",
88 | "patterns": ["(\\\\?\"|"|%22)[a-z0-9_-]*(api[_-]?key|S3|aws_|secret|passw|auth)[a-z0-9_-]*(\\\\?\"|"|%22): ?(\\\\?\"|"|%22)[^\"&]+(\\\\?\"|"|%22)"]
89 | },
90 | {
91 | "name": "linkedin",
92 | "patterns": ["(?i)linkedin(.{0,20})?(?-i)['\"][0-9a-z]{12}['\"]"]
93 | },
94 | {
95 | "name": "linkedin secret",
96 | "patterns": ["(?i)linkedin(.{0,20})?['\"][0-9a-z]{16}['\"]"]
97 | },
98 | {
99 | "name": "mailchamp",
100 | "patterns": ["[0-9a-f]{32}-us[0-9]{1,2}"]
101 | },
102 | {
103 | "name": "mailgun api key",
104 | "patterns": ["key-[0-9a-zA-Z]{32}"]
105 | },
106 | {
107 | "name": "md5",
108 | "patterns": ["[a-f0-9]{32}"]
109 | },
110 | {
111 | "name": "picatic api",
112 | "patterns": ["sk_live_[0-9a-z]{32}"]
113 | },
114 | {
115 | "name": "s3 buckets",
116 | "patterns": [
117 | "[a-z0-9.-]+\\.s3\\.amazonaws\\.com",
118 | "[a-z0-9.-]+\\.s3-[a-z0-9-]\\.amazonaws\\.com",
119 | "[a-z0-9.-]+\\.s3-website[.-](eu|ap|us|ca|sa|cn)",
120 | "//s3\\.amazonaws\\.com/[a-z0-9._-]+",
121 | "//s3-[a-z0-9-]+\\.amazonaws\\.com/[a-z0-9._-]+"
122 | ]
123 | },
124 | {
125 | "name": "slack token",
126 | "patterns": ["xox[baprs]-([0-9a-zA-Z]{10,48})?"]
127 | },
128 | {
129 | "name": "slack webhook",
130 | "patterns": ["https://hooks\\.slack\\.com/services/T[a-zA-Z0-9_]{10}/B[a-zA-Z0-9_]{10}/[a-zA-Z0-9_]{24}"]
131 | },
132 | {
133 | "name": "square secret",
134 | "patterns": ["sq0csp-[ 0-9A-Za-z\\-_]{43}"]
135 | },
136 | {
137 | "name": "square token",
138 | "patterns": ["sqOatp-[0-9A-Za-z\\-_]{22}"]
139 | },
140 | {
141 | "name": "stripe key",
142 | "patterns": ["(?:r|s)k_live_[0-9a-zA-Z]{24}"]
143 | },
144 | {
145 | "name": "twilio keys",
146 | "patterns": ["SK[0-9a-fA-F]{32}"]
147 | },
148 | {
149 | "name": "twitter key",
150 | "patterns": ["(?i)twitter(.{0,20})?['\"][0-9a-z]{18,25}"]
151 | },
152 | {
153 | "name": "twitter oauth",
154 | "patterns": ["[t|T][w|W][i|I][t|T][t|T][e|E][r|R].{0,30}['\"\\s][0-9a-zA-Z]{35,44}['\"\\s]"]
155 | },
156 | {
157 | "name": "twitter secret",
158 | "patterns": ["(?i)twitter(.{0,20})?['\"][0-9a-z]{35,44}"]
159 | }
160 | ]
161 |
--------------------------------------------------------------------------------
/findsecret.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "encoding/json"
6 | "flag"
7 | "io"
8 | "net/http"
9 | "os"
10 | "regexp"
11 | )
12 |
13 | type Secrets struct {
14 | Name string `json:"name"`
15 | Patterns []string `json:"patterns"`
16 | }
17 |
18 | var JsFile string
19 | var secrets []Secrets
20 | var matchedSecrets []string
21 |
22 | func main() {
23 |
24 | println("\n[!] Working\n")
25 |
26 | checkSecret()
27 |
28 | var input string
29 | var output string
30 | flag.StringVar(&input, "i", "", "-i local.js \n-i https://domain.tld/external.js \n-i https://domain.tld")
31 | flag.StringVar(&output, "o", "cli", "-o cli \n-o .txt")
32 | flag.Parse()
33 |
34 | switch checkInput(input) {
35 | case "url":
36 | JsFile = getExternalJsFile(input)
37 | matchedSecrets = matchSecret(secrets, JsFile)
38 | checkOutput(input, output, matchedSecrets)
39 | println("[!] Completed\n")
40 | case "local":
41 | JsFile = getLocalJsFile(input)
42 | matchedSecrets = matchSecret(secrets, JsFile)
43 | checkOutput(input, output, matchedSecrets)
44 | println("[!] Completed\n")
45 | case "domain":
46 | JsFileList := getScripts(input)
47 | println("[!] Found", len(JsFileList), "JS file\n")
48 |
49 | for _, v := range JsFileList {
50 |
51 | JsFile = getExternalJsFile(v)
52 | matchedSecrets = matchSecret(secrets, JsFile)
53 | checkOutput(v, output, matchedSecrets)
54 | }
55 | println("[!] Completed\n")
56 |
57 | case "list":
58 |
59 | for _, url := range getDomainList(input) {
60 |
61 | JsFileList := getScripts(url)
62 | println("[!] Found", len(JsFileList), "JS file\n")
63 |
64 | for _, v := range JsFileList {
65 | JsFile = getExternalJsFile(v)
66 | matchedSecrets = matchSecret(secrets, JsFile)
67 | checkOutput(v, output, matchedSecrets)
68 | }
69 | }
70 |
71 | case "not valid":
72 | println("[!] unrecognized input\n")
73 | }
74 | }
75 |
76 | func check(e error) {
77 | if e != nil {
78 | panic(e)
79 | }
80 | }
81 |
82 | func checkSecret() {
83 | homeDir, err := os.UserHomeDir()
84 | check(err)
85 | if _, err := os.Stat(homeDir + "/findsecret/secrets.json"); err != nil {
86 | println("[!] secrets.json downloading...")
87 | println("[!] secrets.json download complate")
88 | println("[!] ready to Go")
89 | downloadSecret()
90 | secrets = readSecrets()
91 | } else {
92 | secrets = readSecrets()
93 | }
94 | }
95 |
96 | func checkInput(input string) string {
97 |
98 | if len(input) >= 4 {
99 | isJS, err := regexp.MatchString(`^.*\.js(\?.*=?.*)?$`, input)
100 | check(err)
101 |
102 | isURL, err := regexp.MatchString(`https?://.*`, input)
103 | check(err)
104 |
105 | isTxt, err := regexp.MatchString(`^.*\.txt$`, input)
106 | check(err)
107 |
108 | if isJS {
109 | if isURL {
110 | return "url"
111 | } else {
112 | return "local"
113 | }
114 | } else {
115 | if isTxt {
116 | return "list"
117 | } else {
118 | return "domain"
119 | }
120 | }
121 | } else {
122 | return "not valid"
123 | }
124 |
125 | }
126 |
127 | func checkOutput(input, output string, result []string) {
128 |
129 | if output == "cli" {
130 | writeCli(input, result)
131 | } else {
132 | writeFile(input, output, result)
133 | }
134 | }
135 |
136 | func getDomainList(input string) []string {
137 | lines := []string{}
138 |
139 | file, err := os.Open(input)
140 | check(err)
141 | defer file.Close()
142 |
143 | scanner := bufio.NewScanner(file)
144 | err2 := scanner.Err()
145 | check(err2)
146 |
147 | for scanner.Scan() {
148 | lines = append(lines, scanner.Text())
149 | }
150 |
151 | return lines
152 | }
153 |
154 | func downloadSecret() {
155 |
156 | homeDir, err := os.UserHomeDir()
157 | check(err)
158 |
159 | err2 := os.Mkdir(homeDir+"/findsecret", 0777)
160 | check(err2)
161 |
162 | jsonFile, err3 := os.Create(homeDir + "/findsecret/secrets.json")
163 | check(err3)
164 | defer jsonFile.Close()
165 |
166 | resp, err4 := http.Get("https://raw.githubusercontent.com/burak0x01/findsecret/main/secrets.json")
167 | check(err4)
168 | defer resp.Body.Close()
169 |
170 | _, err5 := io.Copy(jsonFile, resp.Body)
171 | check(err5)
172 | }
173 |
174 | func readSecrets() []Secrets {
175 |
176 | var secrets []Secrets
177 |
178 | homeDir, err := os.UserHomeDir()
179 | check(err)
180 |
181 | jsonFile, err := os.Open(homeDir + "/findsecret/secrets.json")
182 | check(err)
183 | defer jsonFile.Close()
184 |
185 | byteValue, _ := io.ReadAll(jsonFile)
186 | json.Unmarshal(byteValue, &secrets)
187 |
188 | return secrets
189 | }
190 |
191 | func matchSecret(secrets []Secrets, data string) []string {
192 |
193 | matchedArray := []string{}
194 |
195 | // each secret object
196 | for _, secret := range secrets {
197 |
198 | // each pattern
199 | for _, pattern := range secret.Patterns {
200 | re := regexp.MustCompile(pattern)
201 | match := re.FindAllString(data, -1)
202 |
203 | // save multiple matches
204 | for _, matchVal := range match {
205 | if matchVal != "" {
206 | s := secret.Name + " => " + matchVal
207 | matchedArray = append(matchedArray, s)
208 | }
209 | }
210 | }
211 | }
212 |
213 | return matchedArray
214 | }
215 |
216 | func getExternalJsFile(url string) string {
217 |
218 | resp, err := http.Get(url)
219 |
220 | if err == nil {
221 | defer resp.Body.Close()
222 |
223 | if resp.StatusCode == 200 {
224 | body, err := io.ReadAll(resp.Body)
225 | check(err)
226 | return string(body)
227 | } else {
228 | return "Not Found"
229 | }
230 | } else {
231 | return "Not Found"
232 | }
233 | }
234 |
235 | func getLocalJsFile(input string) string {
236 |
237 | data, err := os.ReadFile(input)
238 | check(err)
239 |
240 | return string(data)
241 | }
242 |
243 | func writeFile(input, path string, data []string) {
244 |
245 | if len(data) != 0 {
246 | file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
247 | check(err)
248 | defer file.Close()
249 |
250 | file.WriteString(input + "\n")
251 |
252 | for _, line := range data {
253 | file.WriteString("\t" + line + "\n")
254 | }
255 | file.WriteString("\n")
256 | }
257 | }
258 |
259 | func writeCli(input string, data []string) {
260 |
261 | if len(data) != 0 {
262 | println("[+]", input)
263 |
264 | for _, v := range data {
265 | println("\t" + v)
266 | }
267 | println()
268 | }
269 | }
270 |
271 | func getScripts(url string) []string {
272 |
273 | var jsFileList []string
274 |
275 | resp, err := http.Get(url)
276 | check(err)
277 |
278 | if resp.StatusCode == 200 {
279 | body, err2 := io.ReadAll(resp.Body)
280 | check(err2)
281 |
282 | re := regexp.MustCompile(`https?://.*\.js`)
283 | match := re.FindAllString(string(body), -1)
284 |
285 | for _, url := range match {
286 |
287 | resp2, err3 := http.Get(url)
288 | if err3 != nil {
289 | continue
290 | }
291 |
292 | if resp2.StatusCode == 200 {
293 | jsFileList = append(jsFileList, url)
294 | }
295 | }
296 |
297 | return jsFileList
298 | } else {
299 | jsFileList = append(jsFileList, "check the url please !!!")
300 |
301 | return jsFileList
302 | }
303 | }
304 |
--------------------------------------------------------------------------------