├── 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 | --------------------------------------------------------------------------------