5oMZ2P42<^UprK@6uLfk1b8JLL~q
z=00YX0J^;8Xo&)h#X~m8#CK*gH#uSpDG1-RnLAq`)*J@K(!-z
zTPgdF@ibMACUa51%4=daTY8-|2JyQcQEeriKU97tvCz%3)Z9Sk>Q&LLMnGVKA|g~f
zlwR@XF;UIgU%64)L!Bgpfmvf&QSjZ-=qPxwrhLRN&E9RTxEvXx{4v8+LyZQP@rT}7
z7noF>>yYcuwO6Uib8RMZ#!qFb!3=RV@j1y75Z*DfX)%97*;*#-hnlkUVlLH?JgQUN
z?Y6tsLN4
z6(Z#$)NExZvYo&Vvq_b0n@OIIQ{am{ks_pJ7|$rIA0<_6&FRw3uCgoou+
z6IV0sPwLsCw9c_glG}+BXDfY{GbLGtHW#lN
zGD#L{F3@`#xeMLE8)E$%+519n?We#1^Rwwp;5rpv0x`F`g58~`X6(v#KMwGQRqB=`
zy7aBWNXT!AGt;&tZ=DaKmK2wthKTbDt^0||dznTqh@6TWl$!Nt9
zoiTfwNFb2i(P_sutNNydln%OzP{Lo7f2E9mSTBVUw>VpRgX#u(9MuU*66Q}QfCbM)
zo&>>bHs%q%Ktl;M1j0Ws=#>t)r4$WMjQPY^+o%y`2B|BNT5%y~-dM1O
zu!&&b%c=o=i#5AzBvnLJzsMF|cc~zaTmc|NEX{e(r?E
zHBXfSaZh`wkcHQ%Ew_s`EF#%y%-UK;YV#VV;m}khm>3#3tqH&=!J+`|qT9=S!f$>N
z|5~9yYPy&WL6x~ge%a;Vbi|evb%xJ?v)*T8VeUPnU!2nG-atHm{JnRCcRPjnhK)V&
zXa+?leP-e%M-qO=n@RUU%)1u1n595YWcVuStv1%TlH6zV5|bc86DrEESdK|?YS%=z
zrig;}9nb>$E@y2^M-z>0mG%=Bk?oi1SiQOAGmf&Qxmc+=
z{1L0P+!dAqt2Lgh7<3iU)Qt6_;9gCqoC&cD?
zKu4$lgG=?r^wA7*Qm0?xhn#9KVQOaCDXq**u@dk5#P?i75d{_QHjxF6E@tO!=ahG
zD#uwa(lP+z(iqF>yC$u#c{jmJz69DXt7|k?9L%bHY=L`oh_80p_nqsC&gaUKu`)WYW276IXmK)MePpG>1MBb0yDF08CZsnXBSZ*)&2Yj_bIW0#v&?4=VKN8CAbt2bJpmQ{&^
zOs8kP@F0sPdp#LlfVy2|v%ftPW}z#$+$FzGLfu;V#?S14M@26ho_ZT;
zDuQtL6AL;L_M_md#h?)y))^ml;jXw0Qw0F|z0WHwihjFG)YGq8$U*cgXO*whc;B20
q1mnC;=hYzwP`!EcwebJX29Avl{hL1Y3DWuBF~~|ONmh!Rg!~T$SBi-M
literal 0
HcmV?d00001
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..b71af0b
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,16 @@
+module github.com/Nemesis0U/JSRecon
+
+go 1.21.3
+
+require (
+ github.com/PuerkitoBio/goquery v1.8.1
+ github.com/urfave/cli/v2 v2.25.7
+)
+
+require (
+ github.com/andybalholm/cascadia v1.3.1 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
+ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
+ golang.org/x/net v0.7.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..f67e017
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,43 @@
+github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
+github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
+github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
+github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
+github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..e5fef18
--- /dev/null
+++ b/main.go
@@ -0,0 +1,264 @@
+// Created by Nemesis
+// Contact: nemesisuks@protonmail.com
+
+package main
+
+import (
+ "bufio"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+ "regexp"
+ "strings"
+ "sync"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/urfave/cli/v2"
+)
+
+// Defines regex patterns as global constants
+const (
+ urlPattern = `https?://[^\s'"]+`
+ emailPattern = `\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b`
+ apiKeyPattern = `(?i)\b[0-9a-f]{32,64}\b`
+ ipAddressPattern = `(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})`
+ credentialPattern = `(?i)\b(?:username|password|token|secret)\b`
+)
+
+func main() {
+ app := &cli.App{
+ Name: "JSRecon",
+ Usage: "Scan and extract endpoint URLs and sensitive data from JS files on a website",
+ Flags: []cli.Flag{
+ &cli.StringFlag{
+ Name: "url",
+ Aliases: []string{"u"},
+ Usage: "URL of the website to scan (required)",
+ Required: true,
+ },
+ &cli.StringFlag{
+ Name: "keyword",
+ Usage: "Keyword to search for in JavaScript code (optional)",
+ },
+ &cli.StringFlag{
+ Name: "output",
+ Aliases: []string{"o"},
+ Usage: "Output file to save the links (optional)",
+ },
+ &cli.BoolFlag{
+ Name: "show-as-domain",
+ Usage: "Show results as domains instead of full URLs (optional)",
+ },
+ &cli.BoolFlag{
+ Name: "show-sensitive",
+ Usage: "Show sensitive data found in JS files (optional)",
+ },
+ &cli.StringFlag{
+ Name: "cookie",
+ Usage: "Custom cookie to include in the request (optional)",
+ },
+ },
+ Action: func(c *cli.Context) error {
+ websiteURL := c.String("url")
+ keyword := c.String("keyword")
+ showAsDomain := c.Bool("show-as-domain")
+ outputFile := c.String("output")
+ showSensitive := c.Bool("show-sensitive")
+ customCookie := c.String("cookie")
+
+ client := &http.Client{}
+ crawlWebsite(client, websiteURL, keyword, outputFile, showAsDomain, showSensitive, customCookie)
+
+ return nil
+ },
+ }
+
+ err := app.Run(os.Args)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
+
+func crawlWebsite(client *http.Client, websiteURL, keyword, outputFile string, showAsDomain, showSensitive bool, customCookie string) {
+ req, err := http.NewRequest("GET", websiteURL, nil)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if customCookie != "" {
+ req.Header.Add("Cookie", customCookie)
+ }
+
+ response, err := client.Do(req)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ log.Fatalf("Request failed with status code %d", response.StatusCode)
+ }
+
+ doc, err := goquery.NewDocumentFromReader(response.Body)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ var links []string
+ var sensitiveData []string
+ var wg sync.WaitGroup
+
+ // Defines regex patterns for sensitive data
+ regexPatterns := map[*regexp.Regexp]string{
+ regexp.MustCompile(emailPattern): "Email Address",
+ regexp.MustCompile(apiKeyPattern): "API Key",
+ regexp.MustCompile(ipAddressPattern): "IP Address",
+ regexp.MustCompile(credentialPattern): "Credential",
+ }
+
+ doc.Find("script").Each(func(index int, scriptElement *goquery.Selection) {
+ wg.Add(1)
+ go func(script *goquery.Selection) {
+ defer wg.Done()
+ scriptLinks, sensitiveScriptData := processScript(script, keyword, regexPatterns)
+ links = append(links, scriptLinks...)
+ sensitiveData = append(sensitiveData, sensitiveScriptData...)
+ }(scriptElement)
+ })
+
+ wg.Wait()
+
+ if outputFile != "" {
+ dataToSave := make([]string, 0)
+
+ if showSensitive {
+ for _, data := range sensitiveData {
+ dataToSave = append(dataToSave, data)
+ }
+ }
+
+ for _, link := range links {
+ if !showSensitive || (showSensitive && !strings.Contains(link, "@")) {
+ if showAsDomain {
+ parts := strings.Split(link, "//")
+ if len(parts) > 1 {
+ domainParts := strings.Split(parts[1], "/")
+ if len(domainParts) > 0 {
+ dataToSave = append(dataToSave, domainParts[0])
+ }
+ }
+ } else {
+ dataToSave = append(dataToSave, link)
+ }
+ }
+ }
+
+ if err := saveDataToFile(outputFile, dataToSave); err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("\nData saved to %s\n", outputFile)
+ }
+
+ printJavaScriptLinks(outputFile, links, sensitiveData, showAsDomain, showSensitive)
+}
+
+func processScript(scriptElement *goquery.Selection, keyword string, regexPatterns map[*regexp.Regexp]string) ([]string, []string) {
+ scriptText := scriptElement.Text()
+ scriptLinks := findJavaScriptLinks(scriptText, keyword)
+ sensitiveScriptData := extractSensitiveData(scriptText, regexPatterns)
+ return scriptLinks, sensitiveScriptData
+}
+
+func extractSensitiveData(scriptText string, regexPatterns map[*regexp.Regexp]string) []string {
+ var sensitiveData []string
+ for pattern, dataType := range regexPatterns {
+ matches := pattern.FindAllString(scriptText, -1)
+ for _, match := range matches {
+ cleanMatch := strings.Trim(match, `" '`)
+ sensitiveData = append(sensitiveData, dataType+": "+cleanMatch)
+ }
+ }
+ return sensitiveData
+}
+
+func findMatches(text string, pattern *regexp.Regexp) []string {
+ matches := pattern.FindAllString(text, -1)
+ var cleanMatches []string
+ for _, match := range matches {
+ cleanMatch := strings.Trim(match, `" '`)
+ cleanMatches = append(cleanMatches, cleanMatch)
+ }
+ return cleanMatches
+}
+
+func saveDataToFile(filename string, data []string) error {
+ file, err := os.Create(filename)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ writer := bufio.NewWriter(file)
+ for _, entry := range data {
+ _, err := writer.WriteString(entry + "\n")
+ if err != nil {
+ return err
+ }
+ }
+ writer.Flush()
+
+ return nil
+}
+
+func printJavaScriptLinks(outputFile string, links, sensitiveData []string, showAsDomain, showSensitive bool) {
+ printedDomains := make(map[string]bool)
+ uniqueLinks := make(map[string]bool)
+ uniqueSensitiveData := make(map[string]bool)
+
+ if showSensitive {
+ for _, data := range sensitiveData {
+ if !uniqueSensitiveData[data] {
+ fmt.Println(data)
+ uniqueSensitiveData[data] = true
+ }
+ }
+ }
+
+ for _, link := range links {
+ if !showSensitive || (showSensitive && !strings.Contains(link, "@")) {
+ if showAsDomain {
+ parts := strings.Split(link, "//")
+ if len(parts) > 1 {
+ domainParts := strings.Split(parts[1], "/")
+ if len(domainParts) > 0 && !printedDomains[domainParts[0]] {
+ fmt.Println(domainParts[0])
+ printedDomains[domainParts[0]] = true
+ }
+ }
+ } else {
+ if !uniqueLinks[link] {
+ fmt.Println(link)
+ uniqueLinks[link] = true
+ }
+ }
+ }
+ }
+}
+
+func findJavaScriptLinks(scriptText, keyword string) []string {
+ re := regexp.MustCompile(urlPattern)
+ matches := re.FindAllString(scriptText, -1)
+
+ var links []string
+
+ for _, match := range matches {
+ if keyword == "" || strings.Contains(match, keyword) {
+ cleanLink := strings.Trim(match, `" '`)
+ links = append(links, cleanLink)
+ }
+ }
+
+ return links
+}