├── .github └── FUNDING.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── config.go ├── report.go ├── root.go ├── scan.go ├── server.go └── server_test.go ├── core ├── analyze.go ├── background.go ├── conclusions.go ├── config.go ├── detector.go ├── dns.go ├── filter.go ├── generator.go ├── generator_test.go ├── middleware.go ├── middleware_test.go ├── output.go ├── parser.go ├── parser_test.go ├── passive.go ├── passive_test.go ├── report.go ├── report_test.go ├── routine.go ├── runner.go ├── runner_test.go ├── sender_test.go ├── sending.go ├── signature.go ├── template.go ├── template_test.go ├── update.go ├── variables.go └── variables_test.go ├── database ├── collaborator.go ├── config.go ├── connect.go ├── dnsbin.go ├── models │ ├── collab.go │ ├── configuration.go │ ├── dummy.go │ ├── model.go │ ├── oob.go │ ├── record.go │ ├── scan.go │ ├── signature.go │ └── user.go ├── record.go ├── scan.go ├── sign.go └── user.go ├── dns ├── query.go └── query_test.go ├── go.mod ├── go.sum ├── libs ├── banner.go ├── dns.go ├── http.go ├── options.go ├── passive.go ├── signature.go └── version.go ├── main.go ├── sender ├── checksum.go ├── chrome.go └── sender.go ├── server ├── api.go ├── controllers.go └── routers.go ├── test-signatures ├── common-error-header.yaml ├── common-error.yaml ├── local-analyze.yaml ├── query-fuzz.yaml ├── routine-simple.yaml ├── simple-dns.yaml ├── with-check-request.yaml ├── with-origin.yaml ├── with-passive-in-dection.yaml ├── with-passive.yaml └── with-prefix.yaml └── utils ├── helper.go └── log.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: j3ssie 5 | open_collective: jaeles-project 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: [ 'https://paypal.me/j3ssiejjj', 'https://www.buymeacoffee.com/j3ssie' ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | .goreleaser.yml 3 | .idea 4 | .vscode 5 | dist 6 | out 7 | passive-* 8 | old-out 9 | http-out 10 | test-scripts -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20-buster as builder 2 | RUN go install github.com/jaeles-project/jaeles@latest 3 | RUN apt update -qq \ 4 | && apt install -y chromium && apt clean 5 | WORKDIR /root/ 6 | EXPOSE 5000 7 | RUN jaeles config init -y 8 | ENTRYPOINT ["/go/bin/jaeles"] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 j3ssie 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 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET ?= jaeles 2 | GO ?= go 3 | GOFLAGS ?= 4 | VERSION := $(shell cat libs/version.go | grep 'VERSION =' | cut -d '"' -f 2 | sed 's/ /\-/g') 5 | 6 | build: 7 | go install 8 | go build -ldflags="-s -w" -tags netgo -trimpath -buildmode=pie -o dist/$(TARGET) 9 | 10 | release: 11 | go install 12 | @echo "==> Clean up old builds" 13 | rm -rf ./dist/* 14 | @echo "==> building binaries for for mac intel" 15 | GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w" -tags netgo -trimpath -buildmode=pie -o dist/$(TARGET) 16 | zip -9 -j dist/$(TARGET)-macos-amd64.zip dist/$(TARGET) && rm -rf ./dist/$(TARGET) 17 | @echo "==> building binaries for for mac M1 chip" 18 | CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w" -tags netgo -trimpath -buildmode=pie -o dist/$(TARGET) 19 | zip -9 -j dist/$(TARGET)-macos-arm64.zip dist/$(TARGET)&& rm -rf ./dist/$(TARGET) 20 | @echo "==> building binaries for linux intel build on mac" 21 | GOOS=linux GOARCH=amd64 CC="/usr/local/bin/x86_64-linux-musl-gcc" CGO_ENABLED=1 go build -ldflags="-s -w" -tags netgo -trimpath -buildmode=pie -o dist/$(TARGET) 22 | zip -9 -j dist/$(TARGET)-linux.zip dist/$(TARGET)&& rm -rf ./dist/$(TARGET) 23 | mv dist/$(TARGET)-macos-amd64.zip dist/$(TARGET)-$(VERSION)-macos-amd64.zip 24 | mv dist/$(TARGET)-macos-arm64.zip dist/$(TARGET)-$(VERSION)-macos-arm64.zip 25 | mv dist/$(TARGET)-linux.zip dist/$(TARGET)-$(VERSION)-linux.zip 26 | run: 27 | $(GO) $(GOFLAGS) run *.go 28 | 29 | fmt: 30 | $(GO) $(GOFLAGS) fmt ./...; \ 31 | echo "Done." 32 | 33 | test: 34 | $(GO) $(GOFLAGS) test ./... -v% -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Jaeles 3 |

4 | Release 5 | Documentation 6 | Software License 7 |

8 |

9 | 10 | **Jaeles** is a powerful, flexible and easily extensible framework written in Go for building your own Web Application 11 | Scanner. 12 | 13 | ![Architecture](https://github.com/jaeles-project/jaeles-plugins/blob/master/imgs/jaeles-architecture.png?raw=true) 14 | 15 | 16 | ## Installation 17 | 18 | Download [precompiled version here](https://github.com/jaeles-project/jaeles/releases). 19 | 20 | If you have a Go environment, make sure you have **Go >= 1.17** with Go Modules enable and run the following command. 21 | 22 | ```shell 23 | go install github.com/jaeles-project/jaeles@latest 24 | ``` 25 | 26 | Please visit the [Official Documention](https://jaeles-project.github.io/) for more details. 27 | 28 | ### **Note**: Checkout [Signatures Repo](https://github.com/jaeles-project/jaeles-signatures) for install signature. 29 | 30 | ## Usage 31 | 32 | ```shell 33 | # Scan Usage example: 34 | jaeles scan -s -u 35 | jaeles scan -c 50 -s -U -L 36 | jaeles scan -c 50 -s -U 37 | jaeles scan -c 50 -s -U -p 'dest=xxx.burpcollaborator.net' 38 | jaeles scan -c 50 -s -U -f 'noti_slack "{{.vulnInfo}}"' 39 | jaeles scan -v -c 50 -s -U list_target.txt -o /tmp/output 40 | jaeles scan -s -s -u http://example.com 41 | jaeles scan -G -s -s -x -u http://example.com 42 | cat list_target.txt | jaeles scan -c 100 -s 43 | 44 | 45 | # Examples: 46 | jaeles scan -s 'jira' -s 'ruby' -u target.com 47 | jaeles scan -c 50 -s 'java' -x 'tomcat' -U list_of_urls.txt 48 | jaeles scan -G -c 50 -s '/tmp/custom-signature/.*' -U list_of_urls.txt 49 | jaeles scan -v -s '~/my-signatures/products/wordpress/.*' -u 'https://wp.example.com' -p 'root=[[.URL]]' 50 | cat urls.txt | grep 'interesting' | jaeles scan -L 5 -c 50 -s 'fuzz/.*' -U list_of_urls.txt --proxy http://127.0.0.1:8080 51 | jaeles server -s '/tmp/custom-signature/sensitive/.*' -L 2 --fi 52 | 53 | ``` 54 | 55 | More usage can be found [here](https://jaeles-project.github.io/usage/) 56 | 57 | ## Run with Docker 58 | 59 | ```shell 60 | docker pull j3ssie/jaeles 61 | docker run j3ssie/jaeles scan -s '' -u http://example.com 62 | ``` 63 | 64 | 65 | ## Showcases 66 | 67 | | [![asciicast](https://asciinema.org/a/392827.svg)](https://asciinema.org/a/392827) [**Jenkins Gitlab XSS CVE-2020-2096**](https://asciinema.org/a/392827) | [![asciicast](https://asciinema.org/a/392822.svg)](https://asciinema.org/a/392822) [**Grafana DoS Probing CVE-2020-13379**](https://asciinema.org/a/392822) | 68 | |:----------:|:-------------:| 69 | | [![asciicast](https://asciinema.org/a/392824.svg)](https://asciinema.org/a/392824) [**SolarWindsOrion LFI CVE-2020-10148**](https://asciinema.org/a/392824) | [![asciicast](https://asciinema.org/a/392821.svg)](https://asciinema.org/a/392821) [**Nginx Vhost XSS**](https://asciinema.org/a/392821) | 70 | 71 |

More showcase can be found here

72 | 73 | *** 74 | 75 | ### HTML Report summary 76 | 77 | ![HTML Report](https://github.com/jaeles-project/jaeles-plugins/blob/master/assets/jaeles-report.png?raw=true) 78 | 79 | ### Burp Integration 80 | 81 | ![Burp Integration](https://github.com/jaeles-project/jaeles-plugins/blob/master/imgs/Burp-Integration.gif?raw=true) 82 | 83 | Plugin can be found [here](https://github.com/jaeles-project/jaeles-plugins/blob/master/jaeles-burp.py) and Video 84 | Guide [here](https://youtu.be/1lxsYhfTq3M) 85 | 86 | ## Mentions 87 | 88 | [My introduction slide about Jaeles](https://speakerdeck.com/j3ssie/jaeles-the-swiss-army-knife-for-automated-web-application-testing) 89 | 90 | ### Planned Features 91 | 92 | * Adding more signatures. 93 | * Adding more input sources. 94 | * Adding more APIs to get access to more properties of the request. 95 | * Adding proxy plugins to directly receive input from browser of http client. 96 | * Adding more action on Web UI. 97 | * Integrate with many other tools. 98 | 99 | ## Painless integrate Jaeles into your recon workflow? 100 | 101 |

102 | OsmedeusEngine 103 |

104 | This project was part of Osmedeus Engine. Check out how it was integrated at @OsmedeusEngine 105 |

106 |

107 | 108 | 109 | ## Contribute 110 | 111 | If you have some new idea about this project, issue, feedback or found some valuable tool feel free to open an issue for 112 | just DM me via @j3ssiejjj. Feel free to submit new signature to 113 | this [repo](https://github.com/jaeles-project/jaeles-signatures). 114 | 115 | ### Credits 116 | 117 | * Special thanks to [chaitin](https://github.com/chaitin/xray) team for sharing ideas to me for build the architecture. 118 | 119 | * React components is powered by [Carbon](https://www.carbondesignsystem.com/) 120 | and [carbon-tutorial](https://github.com/carbon-design-system/carbon-tutorial). 121 | 122 | * Awesomes artworks are powered by [Freepik](http://freepik.com) at [flaticon.com](http://flaticon.com). 123 | 124 | ## In distributions 125 | 126 | [![Packaging status](https://repology.org/badge/vertical-allrepos/jaeles.svg)](https://repology.org/project/jaeles/versions) 127 | 128 | ## Contributors 129 | 130 | ### Code Contributors 131 | 132 | This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. 133 | 134 | 135 | ### Financial Contributors 136 | 137 | Become a financial contributor and help us sustain our 138 | community. [[Contribute](https://opencollective.com/jaeles-project/contribute)] 139 | 140 | #### Individuals 141 | 142 | 143 | 144 | #### Organizations 145 | 146 | Support this project with your organization. Your logo will show up here with a link to your 147 | website. [[Contribute](https://opencollective.com/jaeles-project/contribute)] 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | ## License 161 | 162 | `Jaeles` is made with ♥ by [@j3ssiejjj](https://twitter.com/j3ssiejjj) and it is released under the MIT license. 163 | 164 | ## Donation 165 | 166 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/j3ssiejjj) 167 | 168 | [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/j3ssie) 169 | -------------------------------------------------------------------------------- /cmd/report.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/jaeles-project/jaeles/core" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "github.com/jaeles-project/jaeles/sender" 7 | "github.com/jaeles-project/jaeles/utils" 8 | "github.com/spf13/cobra" 9 | "os" 10 | "path" 11 | ) 12 | 13 | func init() { 14 | var reportCmd = &cobra.Command{ 15 | Use: "report", 16 | Short: "Generate HTML report based on scanned output", 17 | Long: libs.Banner(), 18 | RunE: runReport, 19 | } 20 | reportCmd.Flags().String("template", "~/.jaeles/plugins/report/index.html", "Report Template File") 21 | reportCmd.SetHelpFunc(ReportHelp) 22 | RootCmd.AddCommand(reportCmd) 23 | } 24 | 25 | func runReport(cmd *cobra.Command, _ []string) error { 26 | templateFile, _ := cmd.Flags().GetString("template") 27 | options.Report.TemplateFile = templateFile 28 | DoGenReport(options) 29 | return nil 30 | } 31 | 32 | // DoGenReport generate report from scanned result 33 | func DoGenReport(options libs.Options) error { 34 | if options.Report.TemplateFile == "" { 35 | options.Report.TemplateFile = "~/.jaeles/plugins/report/index.html" 36 | } 37 | if options.VerboseSummary { 38 | options.Report.TemplateFile = "~/.jaeles/plugins/report/verbose.html" 39 | } 40 | 41 | if options.Report.ReportName == "" { 42 | options.Report.ReportName = "jaeles-report.html" 43 | } 44 | 45 | // get template file 46 | options.Report.TemplateFile = utils.NormalizePath(options.Report.TemplateFile) 47 | if !utils.FileExists(options.Report.TemplateFile) { 48 | // get content of remote URL via GET request 49 | req := libs.Request{ 50 | URL: libs.REPORT, 51 | } 52 | if options.VerboseSummary { 53 | req.URL = libs.VREPORT 54 | } 55 | utils.DebugF("Download template from: %v", req.URL) 56 | 57 | res, err := sender.JustSend(options, req) 58 | if err != nil || len(res.Body) <= 0 { 59 | utils.ErrorF("Error GET templateFile: %v", err) 60 | return nil 61 | } 62 | 63 | os.MkdirAll(path.Dir(options.Report.TemplateFile), 0750) 64 | _, err = utils.WriteToFile(options.Report.TemplateFile, res.Body) 65 | if err != nil { 66 | utils.ErrorF("Error write templateFile: %v", err) 67 | return nil 68 | } 69 | utils.InforF("Write report template to: %v", options.Report.TemplateFile) 70 | } 71 | 72 | err := core.GenActiveReport(options) 73 | if err != nil { 74 | utils.ErrorF("Error gen active report: %v", err) 75 | } 76 | err = core.GenPassiveReport(options) 77 | if err != nil { 78 | utils.ErrorF("Error gen passive report: %v", err) 79 | } 80 | 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /cmd/scan.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/jaeles-project/jaeles/core" 7 | "github.com/jaeles-project/jaeles/libs" 8 | "github.com/jaeles-project/jaeles/utils" 9 | "github.com/panjf2000/ants" 10 | "github.com/spf13/cobra" 11 | "os" 12 | "os/exec" 13 | "path" 14 | "strings" 15 | "sync" 16 | ) 17 | 18 | func init() { 19 | var scanCmd = &cobra.Command{ 20 | Use: "scan", 21 | Short: "Scan list of URLs based on selected signatures", 22 | Long: libs.Banner(), 23 | RunE: runScan, 24 | } 25 | 26 | scanCmd.Flags().StringP("url", "u", "", "URL of target") 27 | scanCmd.Flags().StringP("urls", "U", "", "URLs file of target") 28 | scanCmd.Flags().StringVarP(&options.Scan.RawRequest, "raw", "r", "", "Raw request from Burp for origin") 29 | scanCmd.Flags().BoolVar(&options.Scan.EnableGenReport, "html", false, "Generate HTML report after the scan done") 30 | scanCmd.SetHelpFunc(ScanHelp) 31 | RootCmd.AddCommand(scanCmd) 32 | } 33 | 34 | func runScan(cmd *cobra.Command, _ []string) error { 35 | // fmt.Println(os.Args) 36 | SelectSign() 37 | var urls []string 38 | // parse URL input here 39 | urlFile, _ := cmd.Flags().GetString("urls") 40 | urlInput, _ := cmd.Flags().GetString("url") 41 | if urlInput != "" { 42 | urls = append(urls, urlInput) 43 | } 44 | // input as a file 45 | if urlFile != "" { 46 | URLs := utils.ReadingLines(urlFile) 47 | for _, url := range URLs { 48 | urls = append(urls, url) 49 | } 50 | } 51 | 52 | // input as stdin 53 | if len(urls) == 0 { 54 | stat, _ := os.Stdin.Stat() 55 | // detect if anything came from std 56 | if (stat.Mode() & os.ModeCharDevice) == 0 { 57 | sc := bufio.NewScanner(os.Stdin) 58 | for sc.Scan() { 59 | url := strings.TrimSpace(sc.Text()) 60 | if err := sc.Err(); err == nil && url != "" { 61 | urls = append(urls, url) 62 | } 63 | } 64 | // store stdin as a temp file 65 | if len(urls) > options.ChunkLimit && options.ChunkRun { 66 | urlFile = path.Join(options.ChunkDir, fmt.Sprintf("raw-%v", core.RandomString(8))) 67 | utils.InforF("Write stdin data to: %v", urlFile) 68 | utils.WriteToFile(urlFile, strings.Join(urls, "\n")) 69 | } 70 | } 71 | } 72 | 73 | if len(urls) == 0 { 74 | fmt.Fprintf(os.Stderr, "[Error] No input loaded\n") 75 | fmt.Fprintf(os.Stderr, "Use 'jaeles -h' for more information about a command.\n") 76 | os.Exit(1) 77 | } 78 | 79 | if len(urls) > options.ChunkLimit && !options.ChunkRun { 80 | utils.WarningF("Your inputs look very big.") 81 | utils.WarningF("Consider using --chunk options") 82 | } 83 | if len(urls) > options.ChunkLimit && options.ChunkRun { 84 | utils.InforF("Running Jaeles in Chunk mode") 85 | rawCommand := strings.Join(os.Args, " ") 86 | 87 | if strings.Contains(rawCommand, "-U ") { 88 | rawCommand = strings.ReplaceAll(rawCommand, fmt.Sprintf("-U %v", urlFile), "-U {}") 89 | } else { 90 | rawCommand += " -U {}" 91 | } 92 | urlFiles := genChunkFiles(urlFile, options) 93 | runChunk(rawCommand, urlFiles, options.ChunkThreads) 94 | for _, chunkFile := range urlFiles { 95 | os.RemoveAll(chunkFile) 96 | } 97 | os.Exit(0) 98 | } 99 | utils.InforF("Input Loaded: %v", len(urls)) 100 | 101 | /* ---- Really start do something ---- */ 102 | 103 | // run background detector 104 | if !options.NoBackGround { 105 | go func() { 106 | for { 107 | core.Background(options) 108 | } 109 | }() 110 | } 111 | 112 | var wg sync.WaitGroup 113 | p, _ := ants.NewPoolWithFunc(options.Concurrency, func(i interface{}) { 114 | CreateRunner(i) 115 | wg.Done() 116 | }, ants.WithPreAlloc(true)) 117 | defer p.Release() 118 | 119 | for _, url := range urls { 120 | // calculate filtering result first if enabled from cli 121 | baseJob := libs.Job{URL: url} 122 | if options.EnableFiltering { 123 | core.BaseCalculateFiltering(&baseJob, options) 124 | } 125 | 126 | for _, sign := range options.ParsedSelectedSigns { 127 | // filter signature by level 128 | if sign.Level > options.Level { 129 | continue 130 | } 131 | sign.Checksums = baseJob.Checksums 132 | 133 | wg.Add(1) 134 | // Submit tasks one by one. 135 | job := libs.Job{URL: url, Sign: sign} 136 | _ = p.Invoke(job) 137 | } 138 | } 139 | 140 | wg.Wait() 141 | CleanOutput() 142 | 143 | if options.Scan.EnableGenReport && utils.FolderExists(options.Output) { 144 | DoGenReport(options) 145 | } 146 | return nil 147 | } 148 | 149 | func CreateRunner(j interface{}) { 150 | var jobs []libs.Job 151 | rawJob := j.(libs.Job) 152 | 153 | if rawJob.Sign.Type == "dns" { 154 | CreateDnsRunner(rawJob) 155 | return 156 | } 157 | 158 | // enable local analyze 159 | if options.LocalAnalyze { 160 | core.LocalFileToResponse(&rawJob) 161 | } 162 | 163 | // auto prepend http and https prefix if not present 164 | if !options.LocalAnalyze && !strings.HasPrefix(rawJob.URL, "http://") && !strings.HasPrefix(rawJob.URL, "https://") { 165 | withPrefixJob := rawJob 166 | withPrefixJob.URL = "http://" + rawJob.URL 167 | jobs = append(jobs, withPrefixJob) 168 | 169 | withPrefixJob = rawJob 170 | withPrefixJob.URL = "https://" + rawJob.URL 171 | jobs = append(jobs, withPrefixJob) 172 | } else { 173 | jobs = append(jobs, rawJob) 174 | } 175 | 176 | if (rawJob.Sign.Replicate.Ports != "" || rawJob.Sign.Replicate.Prefixes != "") && !options.Mics.DisableReplicate { 177 | if options.Mics.BaseRoot { 178 | rawJob.Sign.BasePath = true 179 | } 180 | moreJobs, err := core.ReplicationJob(rawJob.URL, rawJob.Sign) 181 | if err == nil { 182 | jobs = append(jobs, moreJobs...) 183 | } 184 | } 185 | 186 | for _, job := range jobs { 187 | // custom calculate filtering if enabled inside signature 188 | if job.Sign.Filter || len(job.Sign.FilteringPaths) > 0 { 189 | core.CalculateFiltering(&job, options) 190 | } 191 | utils.DebugF("Raw Checksum: %v", job.Sign.Checksums) 192 | 193 | if job.Sign.Type == "routine" { 194 | routine, err := core.InitRoutine(job.URL, job.Sign, options) 195 | if err != nil { 196 | utils.ErrorF("Error create new routine: %v", err) 197 | } 198 | routine.Start() 199 | continue 200 | } 201 | runner, err := core.InitRunner(job.URL, job.Sign, options) 202 | if err != nil { 203 | utils.ErrorF("Error create new runner: %v", err) 204 | } 205 | runner.Sending() 206 | } 207 | } 208 | 209 | // CreateDnsRunner create runner for dns 210 | func CreateDnsRunner(job libs.Job) { 211 | runner, err := core.InitDNSRunner(job.URL, job.Sign, options) 212 | if err != nil { 213 | utils.ErrorF("Error create new dns runner: %v", err) 214 | } 215 | runner.Resolving() 216 | } 217 | 218 | /////////////////////// Chunk options (very experimental) 219 | 220 | func genChunkFiles(urlFile string, options libs.Options) []string { 221 | utils.DebugF("Store tmp chunk data at: %v", options.ChunkDir) 222 | var divided [][]string 223 | var chunkFiles []string 224 | divided = utils.ChunkFileBySize(urlFile, options.ChunkSize) 225 | for index, chunk := range divided { 226 | outName := path.Join(options.ChunkDir, fmt.Sprintf("%v-%v", core.RandomString(6), index)) 227 | utils.WriteToFile(outName, strings.Join(chunk, "\n")) 228 | chunkFiles = append(chunkFiles, outName) 229 | } 230 | return chunkFiles 231 | } 232 | 233 | func runChunk(command string, urlFiles []string, threads int) { 234 | utils.DebugF("Run chunk command with template: %v", command) 235 | 236 | var commands []string 237 | for index, urlFile := range urlFiles { 238 | cmd := command 239 | cmd = strings.Replace(cmd, "{}", urlFile, -1) 240 | cmd = strings.Replace(cmd, "{#}", fmt.Sprintf("%d", index), -1) 241 | commands = append(commands, cmd) 242 | } 243 | 244 | var wg sync.WaitGroup 245 | p, _ := ants.NewPoolWithFunc(threads, func(i interface{}) { 246 | cmd := i.(string) 247 | ExecutionWithStd(cmd) 248 | wg.Done() 249 | }, ants.WithPreAlloc(true)) 250 | defer p.Release() 251 | for _, cmd := range commands { 252 | wg.Add(1) 253 | _ = p.Invoke(cmd) 254 | } 255 | wg.Wait() 256 | } 257 | 258 | // ExecutionWithStd Run a command 259 | func ExecutionWithStd(cmd string) (string, error) { 260 | command := []string{ 261 | "bash", 262 | "-c", 263 | cmd, 264 | } 265 | var output string 266 | realCmd := exec.Command(command[0], command[1:]...) 267 | // output command output to std too 268 | cmdReader, _ := realCmd.StdoutPipe() 269 | scanner := bufio.NewScanner(cmdReader) 270 | var out string 271 | go func() { 272 | for scanner.Scan() { 273 | out += scanner.Text() 274 | //fmt.Fprintf(os.Stderr, scanner.Text()+"\n") 275 | fmt.Println(scanner.Text()) 276 | } 277 | }() 278 | if err := realCmd.Start(); err != nil { 279 | return "", err 280 | } 281 | if err := realCmd.Wait(); err != nil { 282 | return "", err 283 | } 284 | return output, nil 285 | } 286 | -------------------------------------------------------------------------------- /cmd/server.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/panjf2000/ants" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "sync" 10 | 11 | "github.com/jaeles-project/jaeles/core" 12 | "github.com/jaeles-project/jaeles/database" 13 | "github.com/jaeles-project/jaeles/libs" 14 | "github.com/jaeles-project/jaeles/server" 15 | "github.com/jaeles-project/jaeles/utils" 16 | 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | func init() { 21 | var serverCmd = &cobra.Command{ 22 | Use: "server", 23 | Short: "Start API server", 24 | Long: libs.Banner(), RunE: runServer, 25 | } 26 | serverCmd.Flags().String("host", "127.0.0.1", "IP address to bind the server") 27 | serverCmd.Flags().String("port", "5000", "Port") 28 | serverCmd.Flags().BoolP("no-auth", "A", false, "Turn off authenticated on API server") 29 | serverCmd.SetHelpFunc(ServerHelp) 30 | RootCmd.AddCommand(serverCmd) 31 | } 32 | 33 | func runServer(cmd *cobra.Command, _ []string) error { 34 | if options.NoDB { 35 | fmt.Fprintf(os.Stderr, "Can't run Jaeles Server without DB\n") 36 | os.Exit(-1) 37 | } 38 | SelectSign() 39 | // prepare DB stuff 40 | if options.Server.Username != "" { 41 | database.CreateUser(options.Server.Username, options.Server.Password) 42 | } 43 | // reload signature 44 | SignFolder, _ := filepath.Abs(path.Join(options.RootFolder, "base-signatures")) 45 | allSigns := utils.GetFileNames(SignFolder, ".yaml") 46 | if allSigns != nil { 47 | for _, signFile := range allSigns { 48 | database.ImportSign(signFile) 49 | } 50 | } 51 | database.InitConfigSign() 52 | 53 | var wg sync.WaitGroup 54 | p, _ := ants.NewPoolWithFunc(options.Concurrency, func(i interface{}) { 55 | CreateRunner(i) 56 | wg.Done() 57 | }, ants.WithPreAlloc(true)) 58 | defer p.Release() 59 | 60 | result := make(chan libs.Record) 61 | go func() { 62 | for { 63 | record := <-result 64 | utils.InforF("[Receive] %v %v \n", record.OriginReq.Method, record.OriginReq.URL) 65 | for _, signFile := range options.SelectedSigns { 66 | sign, err := core.ParseSign(signFile) 67 | if err != nil { 68 | utils.ErrorF("Error loading sign: %v\n", signFile) 69 | continue 70 | } 71 | // filter signature by level 72 | if sign.Level > options.Level { 73 | continue 74 | } 75 | 76 | // parse sign as list or single 77 | var url string 78 | if sign.Type != "fuzz" { 79 | url = record.OriginReq.URL 80 | } else { 81 | fuzzSign := sign 82 | fuzzSign.Requests = []libs.Request{} 83 | for _, req := range sign.Requests { 84 | core.ParseRequestFromServer(&record, req, sign) 85 | // override the original if these field defined in signature 86 | if req.Method == "" { 87 | req.Method = record.OriginReq.Method 88 | } 89 | if req.URL == "" { 90 | req.URL = record.OriginReq.URL 91 | } 92 | if len(req.Headers) == 0 { 93 | req.Headers = record.OriginReq.Headers 94 | } 95 | if req.Body == "" { 96 | req.Body = record.OriginReq.Body 97 | } 98 | fuzzSign.Requests = append(fuzzSign.Requests, req) 99 | } 100 | url = record.OriginReq.URL 101 | sign = fuzzSign 102 | } 103 | 104 | // single routine 105 | wg.Add(1) 106 | job := libs.Job{URL: url, Sign: sign} 107 | _ = p.Invoke(job) 108 | } 109 | } 110 | }() 111 | 112 | host, _ := cmd.Flags().GetString("host") 113 | port, _ := cmd.Flags().GetString("port") 114 | options.Server.NoAuth, _ = cmd.Flags().GetBool("no-auth") 115 | bind := fmt.Sprintf("%v:%v", host, port) 116 | options.Server.Bind = bind 117 | utils.GoodF("Start API server at %v", fmt.Sprintf("http://%v/", bind)) 118 | server.InitRouter(options, result) 119 | wg.Wait() 120 | if utils.DirLength(options.Output) == 0 { 121 | os.RemoveAll(options.Output) 122 | } 123 | return nil 124 | } 125 | -------------------------------------------------------------------------------- /cmd/server_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/jaeles-project/jaeles/core" 8 | "github.com/jaeles-project/jaeles/libs" 9 | ) 10 | 11 | func TestServerWithSign(t *testing.T) { 12 | raw := `GET /rest/sample/redirect?to=localhoost&example=123 HTTP/1.1 13 | Host: juice-shop.herokuapp.com 14 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3968.0 Safari/537.36 15 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 16 | Accept-Encoding: gzip, deflate 17 | Accept-Language: en-US,en;q=0.9 18 | Connection: close 19 | Cookie: language=en 20 | Upgrade-Insecure-Requests: 1 21 | ` 22 | var record libs.Record 23 | record.OriginReq = core.ParseBurpRequest(raw) 24 | signFile := "../test-sign/open-redirect.yaml" 25 | sign, err := core.ParseSign(signFile) 26 | if err != nil { 27 | t.Errorf("Error parsing signature") 28 | } 29 | for _, req := range sign.Requests { 30 | core.ParseRequestFromServer(&record, req, sign) 31 | // send origin request 32 | Reqs := core.ParseFuzzRequest(record, sign) 33 | 34 | if len(Reqs) == 0 { 35 | t.Errorf("Error generate Path") 36 | } 37 | for _, req := range Reqs { 38 | fmt.Println(req.Method, req.URL) 39 | // fmt.Println(req.URL) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /core/analyze.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/utils" 6 | ) 7 | 8 | func (r *Record) Analyze() { 9 | // print some log 10 | if r.Opt.Verbose && r.Request.Method != "" { 11 | if r.Response.StatusCode != 0 { 12 | fmt.Printf("[Sent] %v %v %v %v %v \n", r.Request.Method, r.Request.URL, r.Response.Status, r.Response.ResponseTime, len(r.Response.Beautify)) 13 | } 14 | // middleware part 15 | if r.Request.MiddlewareOutput != "" { 16 | utils.DebugF(r.Request.MiddlewareOutput) 17 | } 18 | } 19 | 20 | if len(r.Sign.Origins) > 0 { 21 | r.Origins = r.Sign.Origins 22 | } 23 | 24 | r.Detector() 25 | if r.Opt.Mics.AlwaysTrue { 26 | r.IsVulnerable = true 27 | r.Output() 28 | } 29 | 30 | // set new values for next request here 31 | if len(r.Request.Conclusions) > 0 { 32 | r.Conclude() 33 | } 34 | 35 | // do passive analyze 36 | if r.Opt.EnablePassive || r.Sign.Passive || r.DoPassive { 37 | r.Passives() 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/background.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/jaeles-project/jaeles/utils" 5 | "time" 6 | 7 | "github.com/jaeles-project/jaeles/libs" 8 | ) 9 | 10 | // Background main function to call other background task 11 | func Background(options libs.Options) { 12 | utils.DebugF("Checking backround task") 13 | time.Sleep(time.Duration(options.Refresh) * time.Second) 14 | 15 | // @NOTE: disable for now 16 | //PollingLog() 17 | //PickupLog(options) 18 | // @TODO: Add passive signature for analyzer each request 19 | } 20 | 21 | // 22 | //// PollingLog polling all request with their 23 | //func PollingLog() { 24 | // objs := database.GetUnPollReq() 25 | // for _, obj := range objs { 26 | // // sending part 27 | // secret := url.QueryEscape(database.GetSecretbyCollab(obj.Secret)) 28 | // URL := fmt.Sprintf("http://polling.burpcollaborator.net/burpresults?biid=%v", secret) 29 | // resp, err := resty.New().R().Get(URL) 30 | // if err != nil { 31 | // continue 32 | // } 33 | // response := string(resp.Body()) 34 | // 35 | // jsonParsed, _ := gabs.ParseJSON([]byte(response)) 36 | // exists := jsonParsed.Exists("responses") 37 | // if exists == false { 38 | // continue 39 | // } else { 40 | // for _, element := range jsonParsed.Path("responses").Children() { 41 | // // import this to DB so we don't miss in other detect 42 | // database.ImportOutOfBand(fmt.Sprintf("%v", element)) 43 | // } 44 | // } 45 | // } 46 | //} 47 | // 48 | //// PickupLog pickup request that's have log coming back 49 | //func PickupLog(options libs.Options) { 50 | // objs := database.GetUnPollReq() 51 | // for _, obj := range objs { 52 | // interactString := obj.InteractionString 53 | // data := database.GetOOB(interactString) 54 | // if data != "" { 55 | // var rec libs.Record 56 | // rec.Request.Beautify = obj.Req 57 | // rec.Response.Beautify = obj.Res 58 | // rec.ExtraOutput = data 59 | // 60 | // if options.NoOutput == false { 61 | // outputName := StoreOutput(rec, options) 62 | // rec.RawOutput = outputName 63 | // database.ImportRecord(rec) 64 | // } 65 | // 66 | // } 67 | // } 68 | //} 69 | -------------------------------------------------------------------------------- /core/conclusions.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/jaeles-project/jaeles/utils" 5 | "github.com/robertkrimen/otto" 6 | "os/exec" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | // check conditions before sending request 12 | func (r *Record) Condition() bool { 13 | check := r.RequestScripts("condition", r.Request.Conditions) 14 | return check 15 | } 16 | 17 | // Conclude is main function for detections 18 | func (r *Record) Conclude() { 19 | record := *r 20 | vm := otto.New() 21 | 22 | // ExecCmd execute command command 23 | vm.Set("ExecCmd", func(call otto.FunctionCall) otto.Value { 24 | result, _ := vm.ToValue(Execution(call.Argument(0).String())) 25 | return result 26 | }) 27 | 28 | // write something to a file 29 | vm.Set("WriteTo", func(call otto.FunctionCall) otto.Value { 30 | dest := utils.NormalizePath(call.Argument(0).String()) 31 | value := call.Argument(1).String() 32 | utils.WriteToFile(dest, value) 33 | return otto.Value{} 34 | }) 35 | 36 | vm.Set("StringSearch", func(call otto.FunctionCall) otto.Value { 37 | componentName := call.Argument(0).String() 38 | analyzeString := call.Argument(1).String() 39 | component := GetComponent(record, componentName) 40 | validate := StringSearch(component, analyzeString) 41 | result, _ := vm.ToValue(validate) 42 | return result 43 | }) 44 | 45 | vm.Set("StringCount", func(call otto.FunctionCall) otto.Value { 46 | componentName := call.Argument(0).String() 47 | analyzeString := call.Argument(1).String() 48 | component := GetComponent(record, componentName) 49 | validate := StringCount(component, analyzeString) 50 | result, _ := vm.ToValue(validate) 51 | return result 52 | }) 53 | 54 | vm.Set("RegexSearch", func(call otto.FunctionCall) otto.Value { 55 | componentName := call.Argument(0).String() 56 | analyzeString := call.Argument(1).String() 57 | component := GetComponent(record, componentName) 58 | _, validate := RegexSearch(component, analyzeString) 59 | result, _ := vm.ToValue(validate) 60 | return result 61 | }) 62 | 63 | vm.Set("RegexCount", func(call otto.FunctionCall) otto.Value { 64 | componentName := call.Argument(0).String() 65 | analyzeString := call.Argument(1).String() 66 | component := GetComponent(record, componentName) 67 | validate := RegexCount(component, analyzeString) 68 | result, _ := vm.ToValue(validate) 69 | return result 70 | }) 71 | 72 | vm.Set("StatusCode", func(call otto.FunctionCall) otto.Value { 73 | statusCode := record.Response.StatusCode 74 | result, _ := vm.ToValue(statusCode) 75 | return result 76 | }) 77 | vm.Set("ResponseTime", func(call otto.FunctionCall) otto.Value { 78 | responseTime := record.Response.ResponseTime 79 | result, _ := vm.ToValue(responseTime) 80 | return result 81 | }) 82 | vm.Set("ContentLength", func(call otto.FunctionCall) otto.Value { 83 | ContentLength := record.Response.Length 84 | result, _ := vm.ToValue(ContentLength) 85 | return result 86 | }) 87 | 88 | // StringSelect select a string from component 89 | // e.g: StringSelect("component", "res1", "right", "left") 90 | vm.Set("StringSelect", func(call otto.FunctionCall) otto.Value { 91 | componentName := call.Argument(0).String() 92 | valueName := call.Argument(1).String() 93 | left := call.Argument(2).String() 94 | right := call.Argument(3).String() 95 | component := GetComponent(record, componentName) 96 | value := Between(component, left, right) 97 | r.Request.Target[valueName] = value 98 | utils.DebugF("StringSelect: %v --> %v", valueName, value) 99 | return otto.Value{} 100 | }) 101 | 102 | // - RegexSelect("component", "regex") 103 | // - RegexSelect("component", "regex") 104 | vm.Set("RegexSelect", func(call otto.FunctionCall) otto.Value { 105 | result := RegexSelect(record, call.ArgumentList) 106 | if len(result) > 0 { 107 | for k, value := range result { 108 | utils.DebugF("New variales: %v -- %v", k, value) 109 | r.Request.Target[k] = value 110 | } 111 | } 112 | return otto.Value{} 113 | }) 114 | 115 | // SetValue("var_name", StatusCode()) 116 | // SetValue("status", StringCount('middleware', '11')) 117 | vm.Set("SetValue", func(call otto.FunctionCall) otto.Value { 118 | valueName := call.Argument(0).String() 119 | value := call.Argument(1).String() 120 | utils.DebugF("SetValue: %v -- %v", valueName, value) 121 | r.Request.Target[valueName] = value 122 | return otto.Value{} 123 | }) 124 | 125 | for _, concludeScript := range record.Request.Conclusions { 126 | utils.DebugF("[Conclude]: %v", concludeScript) 127 | vm.Run(concludeScript) 128 | } 129 | } 130 | 131 | // Between get string between left and right 132 | func Between(value string, left string, right string) string { 133 | // Get substring between two strings. 134 | posFirst := strings.Index(value, left) 135 | if posFirst == -1 { 136 | return "" 137 | } 138 | posLast := strings.Index(value, right) 139 | if posLast == -1 { 140 | return "" 141 | } 142 | posFirstAdjusted := posFirst + len(left) 143 | if posFirstAdjusted >= posLast { 144 | return "" 145 | } 146 | return value[posFirstAdjusted:posLast] 147 | } 148 | 149 | // RegexSelect get regex string from component 150 | func RegexSelect(realRec Record, arguments []otto.Value) map[string]string { 151 | result := make(map[string]string) 152 | // - RegexSelect("component", "var_name", "regex") 153 | utils.DebugF("arguments -- %v", arguments) 154 | if len(arguments) < 2 { 155 | utils.DebugF("Invalid Conclude") 156 | return result 157 | } 158 | componentName := arguments[0].String() 159 | component := GetComponent(realRec, componentName) 160 | regexString := arguments[1].String() 161 | 162 | // map all selected 163 | var myExp, err = regexp.Compile(regexString) 164 | if err != nil { 165 | utils.ErrorF("Regex error: %v %v", regexString, err) 166 | return result 167 | } 168 | match := myExp.FindStringSubmatch(component) 169 | if len(match) == 0 { 170 | utils.DebugF("No match found: %v", regexString) 171 | } 172 | for i, name := range myExp.SubexpNames() { 173 | if i != 0 && name != "" && len(match) > i { 174 | result[name] = match[i] 175 | } 176 | } 177 | return result 178 | } 179 | 180 | // Execution Run a command 181 | func Execution(cmd string) string { 182 | command := []string{ 183 | "bash", 184 | "-c", 185 | cmd, 186 | } 187 | var output string 188 | utils.DebugF("[Exec] %v", command) 189 | realCmd := exec.Command(command[0], command[1:]...) 190 | out, _ := realCmd.CombinedOutput() 191 | output = string(out) 192 | return output 193 | } 194 | -------------------------------------------------------------------------------- /core/config.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/Jeffail/gabs/v2" 7 | "github.com/jaeles-project/jaeles/libs" 8 | "github.com/jaeles-project/jaeles/utils" 9 | "github.com/spf13/viper" 10 | "io/ioutil" 11 | "os" 12 | "path" 13 | "path/filepath" 14 | ) 15 | 16 | // InitConfig init config 17 | func InitConfig(options *libs.Options) { 18 | options.RootFolder = utils.NormalizePath(options.RootFolder) 19 | options.Server.DBPath = path.Join(options.RootFolder, "sqlite3.db") 20 | // init new root folder 21 | if !utils.FolderExists(options.RootFolder) { 22 | utils.InforF("Init new config at %v", options.RootFolder) 23 | os.MkdirAll(options.RootFolder, 0750) 24 | // cloning default repo 25 | UpdatePlugins(*options) 26 | UpdateSignature(*options) 27 | } 28 | 29 | configPath := path.Join(options.RootFolder, "config.yaml") 30 | v := viper.New() 31 | v.AddConfigPath(options.RootFolder) 32 | v.SetConfigName("config") 33 | v.SetConfigType("yaml") 34 | if !utils.FileExists(configPath) { 35 | utils.InforF("Write new config to: %v", configPath) 36 | // save default config if not exist 37 | bind := "http://127.0.0.1:5000" 38 | v.SetDefault("defaultSign", "*") 39 | v.SetDefault("cors", "*") 40 | // default credential 41 | v.SetDefault("username", "jaeles") 42 | v.SetDefault("password", utils.GenHash(utils.GetTS())[:10]) 43 | v.SetDefault("secret", utils.GenHash(utils.GetTS())) 44 | v.SetDefault("bind", bind) 45 | v.WriteConfigAs(configPath) 46 | 47 | } else { 48 | if options.Debug { 49 | utils.InforF("Load config from: %v", configPath) 50 | } 51 | b, _ := ioutil.ReadFile(configPath) 52 | v.ReadConfig(bytes.NewBuffer(b)) 53 | } 54 | 55 | // WARNING: change me if you really want to deploy on remote server 56 | // allow all origin 57 | options.Server.Cors = v.GetString("cors") 58 | options.Server.JWTSecret = v.GetString("secret") 59 | options.Server.Username = v.GetString("username") 60 | options.Server.Password = v.GetString("password") 61 | 62 | // store default credentials for Burp plugin 63 | burpConfigPath := path.Join(options.RootFolder, "burp.json") 64 | if !utils.FileExists(burpConfigPath) { 65 | jsonObj := gabs.New() 66 | jsonObj.Set("", "JWT") 67 | jsonObj.Set(v.GetString("username"), "username") 68 | jsonObj.Set(v.GetString("password"), "password") 69 | bind := v.GetString("bind") 70 | if bind == "" { 71 | bind = "http://127.0.0.1:5000" 72 | } 73 | jsonObj.Set(fmt.Sprintf("http://%v/api/parse", bind), "endpoint") 74 | utils.WriteToFile(burpConfigPath, jsonObj.String()) 75 | if options.Verbose { 76 | utils.InforF("Store default credentials for client at: %v", burpConfigPath) 77 | } 78 | } 79 | 80 | // set some default config 81 | options.PassiveFolder = path.Join(utils.NormalizePath(options.RootFolder), "passives") 82 | options.ResourcesFolder = path.Join(utils.NormalizePath(options.RootFolder), "resources") 83 | options.ThirdPartyFolder = path.Join(utils.NormalizePath(options.RootFolder), "thirdparty") 84 | 85 | // create output folder 86 | var err error 87 | err = os.MkdirAll(options.Output, 0750) 88 | if err != nil && options.NoOutput == false { 89 | fmt.Fprintf(os.Stderr, "Failed to create output directory: %s -- %s\n", err, options.Output) 90 | os.Exit(1) 91 | } 92 | if options.SummaryOutput == "" { 93 | options.SummaryOutput = path.Join(options.Output, "jaeles-summary.txt") 94 | } 95 | if options.SummaryVuln == "" { 96 | options.SummaryVuln = path.Join(options.Output, "vuln-summary.txt") 97 | } 98 | 99 | if options.PassiveOutput == "" { 100 | passiveOut := "passive-" + path.Base(options.Output) 101 | options.PassiveOutput = path.Join(filepath.Dir(path.Clean(options.Output)), passiveOut) 102 | } 103 | if options.PassiveSummary == "" { 104 | options.PassiveSummary = path.Join(options.PassiveOutput, "jaeles-passive-summary.txt") 105 | } 106 | 107 | dbSize := utils.GetFileSize(options.Server.DBPath) 108 | if dbSize > 5.0 { 109 | utils.WarningF("Your Database size look very big: %vGB", fmt.Sprintf("%.2f", dbSize)) 110 | utils.WarningF("Consider clean your db with this command: 'jaeles config -a clear' or just remove your '~/.jaeles/'") 111 | } 112 | utils.InforF("Summary output: %v", options.SummaryOutput) 113 | 114 | if options.ChunkRun { 115 | if options.ChunkDir == "" { 116 | options.ChunkDir = path.Join(os.TempDir(), "jaeles-chunk-data") 117 | } 118 | os.MkdirAll(options.ChunkDir, 0755) 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /core/dns.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/dns" 6 | "github.com/jaeles-project/jaeles/libs" 7 | "github.com/jaeles-project/jaeles/utils" 8 | "github.com/robertkrimen/otto" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | // InitDNSRunner init task 14 | func InitDNSRunner(url string, sign libs.Signature, opt libs.Options) (Runner, error) { 15 | var runner Runner 16 | runner.Input = url 17 | runner.Opt = opt 18 | runner.Sign = sign 19 | runner.RunnerType = "dns" 20 | runner.PrepareTarget() 21 | 22 | // @NOTE: add some variables due to the escape issue 23 | runner.Target["RexDomain"] = regexp.QuoteMeta(runner.Target["Domain"]) 24 | if strings.Contains(runner.Target["RexDomain"], `\.`) { 25 | runner.Target["RexDomain"] = strings.ReplaceAll(runner.Target["RexDomain"], `\.`, `\\.`) 26 | } 27 | 28 | return runner, nil 29 | } 30 | 31 | // Resolving get dns ready to resolve 32 | func (r *Runner) Resolving() { 33 | if len(r.Sign.Dns) == 0 { 34 | return 35 | } 36 | for _, dnsRecord := range r.Sign.Dns { 37 | dnsRecord.Domain = ResolveVariable(dnsRecord.Domain, r.Target) 38 | dnsRecord.RecordType = ResolveVariable(dnsRecord.RecordType, r.Target) 39 | dnsRecord.Detections = ResolveDetection(dnsRecord.Detections, r.Target) 40 | dnsRecord.PostRun = ResolveDetection(dnsRecord.PostRun, r.Target) 41 | 42 | dns.QueryDNS(&dnsRecord, r.Opt) 43 | if len(dnsRecord.Results) == 0 { 44 | return 45 | } 46 | 47 | var rec Record 48 | // set somethings in record 49 | rec.Dns = dnsRecord 50 | rec.Sign = r.Sign 51 | rec.Opt = r.Opt 52 | r.Records = append(r.Records, rec) 53 | } 54 | 55 | r.DnsDetection() 56 | } 57 | 58 | // DnsDetection get requests ready to send 59 | func (r *Runner) DnsDetection() { 60 | for _, rec := range r.Records { 61 | rec.DnsDetector() 62 | } 63 | } 64 | 65 | func (r *Record) DnsDetector() bool { 66 | record := *r 67 | var extra string 68 | vm := otto.New() 69 | 70 | // Only for dns detection 71 | vm.Set("DnsString", func(call otto.FunctionCall) otto.Value { 72 | args := call.ArgumentList 73 | recordName := "ANY" 74 | searchString := args[0].String() 75 | if len(args) > 1 { 76 | searchString = args[1].String() 77 | recordName = args[0].String() 78 | } 79 | content := GetDnsComponent(record, recordName) 80 | record.Response.Beautify = content 81 | result, _ := vm.ToValue(StringSearch(content, searchString)) 82 | return result 83 | }) 84 | 85 | vm.Set("DnsRegex", func(call otto.FunctionCall) otto.Value { 86 | args := call.ArgumentList 87 | recordName := "ANY" 88 | searchString := args[0].String() 89 | if len(args) > 1 { 90 | searchString = args[1].String() 91 | recordName = args[0].String() 92 | } 93 | content := GetDnsComponent(record, recordName) 94 | record.Response.Beautify = content 95 | 96 | matches, validate := RegexSearch(content, searchString) 97 | result, err := vm.ToValue(validate) 98 | if err != nil { 99 | utils.ErrorF("Error Regex: %v", searchString) 100 | result, _ = vm.ToValue(false) 101 | } 102 | if matches != "" { 103 | extra = matches 104 | } 105 | return result 106 | }) 107 | 108 | // really run detection here 109 | for _, analyze := range record.Dns.Detections { 110 | // pass detection here 111 | result, _ := vm.Run(analyze) 112 | analyzeResult, err := result.Export() 113 | // in case vm panic 114 | if err != nil || analyzeResult == nil { 115 | r.DetectString = analyze 116 | r.IsVulnerable = false 117 | r.DetectResult = "" 118 | r.ExtraOutput = "" 119 | continue 120 | } 121 | r.DetectString = analyze 122 | r.IsVulnerable = analyzeResult.(bool) 123 | r.DetectResult = extra 124 | r.ExtraOutput = extra 125 | 126 | // add extra things for standard output 127 | r.Request.URL = r.Dns.Domain 128 | r.Request.Beautify = fmt.Sprintf("dig %s %s @%s", r.Dns.RecordType, r.Dns.Domain, r.Dns.Resolver) 129 | r.Response.Beautify = record.Response.Beautify 130 | 131 | utils.DebugF("[Detection] %v -- %v", analyze, r.IsVulnerable) 132 | // deal with vulnerable one here 133 | next := r.Output() 134 | if next == "stop" { 135 | return true 136 | } 137 | } 138 | 139 | return false 140 | } 141 | 142 | func GetDnsComponent(record Record, componentName string) string { 143 | var any string 144 | for _, dnsResult := range record.Dns.Results { 145 | if dnsResult.RecordType == strings.TrimSpace(componentName) { 146 | return dnsResult.Data 147 | } 148 | any += dnsResult.Data + "\n" 149 | } 150 | return any 151 | } 152 | -------------------------------------------------------------------------------- /core/filter.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "github.com/jaeles-project/jaeles/sender" 7 | "github.com/jaeles-project/jaeles/utils" 8 | "github.com/thoas/go-funk" 9 | ) 10 | 11 | var baseFiltering = []string{ 12 | "hopetoget404" + RandomString(6), 13 | fmt.Sprintf("%s", RandomString(16)+"/"+RandomString(5)), 14 | fmt.Sprintf("%s.html", RandomString(16)), 15 | fmt.Sprintf("%%00%s", RandomString(16)), 16 | fmt.Sprintf("%s.json", RandomString(16)), 17 | } 18 | 19 | // BaseCalculateFiltering send couple of requests first to do filtering later 20 | func BaseCalculateFiltering(job *libs.Job, options libs.Options) { 21 | utils.DebugF("Start Calculate Basic Filtering: %s", job.URL) 22 | // generated base calculate inputs first 23 | var baseFilteringURLs []string 24 | for _, filterPath := range baseFiltering { 25 | baseFilteringURLs = append(baseFilteringURLs, utils.JoinURL(job.URL, filterPath)) 26 | } 27 | baseFilteringURLs = funk.UniqString(baseFilteringURLs) 28 | 29 | for _, filteringURL := range baseFilteringURLs { 30 | var req libs.Request 31 | req.Method = "GET" 32 | req.EnableChecksum = true 33 | req.URL = filteringURL 34 | 35 | res, err := sender.JustSend(options, req) 36 | // in case of timeout or anything, just ignore it 37 | if err != nil { 38 | return 39 | } 40 | 41 | // store the base result for local analyze if input is not a file 42 | if (req.URL == job.URL) || (req.URL == fmt.Sprintf("%s/", job.URL)) { 43 | job.Sign.Response = res 44 | } 45 | 46 | if res.Checksum != "" { 47 | utils.DebugF("[Checksum] %s - %s", req.URL, res.Checksum) 48 | job.Checksums = append(job.Checksums, res.Checksum) 49 | } 50 | } 51 | job.Checksums = funk.UniqString(job.Checksums) 52 | } 53 | 54 | func CalculateFiltering(job *libs.Job, options libs.Options) { 55 | var filteringPaths []string 56 | 57 | // ignore the base result if enabled from signature 58 | if job.Sign.OverrideFilerPaths { 59 | job.Sign.Checksums = []string{} 60 | } else { 61 | // mean doesn't have --fi in cli 62 | if len(job.Sign.Checksums) == 0 { 63 | filteringPaths = append(filteringPaths, baseFiltering...) 64 | } 65 | } 66 | if len(job.Sign.FilteringPaths) > 0 { 67 | filteringPaths = append(filteringPaths, job.Sign.FilteringPaths...) 68 | } 69 | 70 | if len(filteringPaths) == 0 { 71 | return 72 | } 73 | utils.DebugF("Start Calculate Custom Filtering: %s", job.URL) 74 | var FilteringURLs []string 75 | for _, filterPath := range filteringPaths { 76 | FilteringURLs = append(FilteringURLs, utils.JoinURL(job.URL, filterPath)) 77 | } 78 | FilteringURLs = funk.UniqString(FilteringURLs) 79 | 80 | for _, filteringURL := range FilteringURLs { 81 | var req libs.Request 82 | req.Method = "GET" 83 | req.EnableChecksum = true 84 | req.URL = filteringURL 85 | 86 | res, err := sender.JustSend(options, req) 87 | // in case of timeout or anything 88 | if err != nil { 89 | return 90 | } 91 | 92 | // store the base result for local analyze if input is not a file 93 | if (req.URL == job.URL) || (req.URL == fmt.Sprintf("%s/", job.URL)) { 94 | job.Sign.Response = res 95 | } 96 | 97 | if res.Checksum != "" { 98 | utils.DebugF("[Checksum] %s - %s", req.URL, res.Checksum) 99 | job.Sign.Checksums = append(job.Sign.Checksums, res.Checksum) 100 | } 101 | } 102 | 103 | job.Sign.Checksums = funk.UniqString(job.Sign.Checksums) 104 | } 105 | 106 | func LocalFileToResponse(job *libs.Job) { 107 | if !utils.FileExists(job.URL) { 108 | return 109 | } 110 | utils.DebugF("Parsing %s to response", job.URL) 111 | 112 | // @TODO: add burp format here too 113 | content := utils.GetFileContent(job.URL) 114 | var res libs.Response 115 | 116 | res.Body = content 117 | res.Beautify = content 118 | 119 | job.Sign.Response = res 120 | job.Sign.Local = true 121 | } 122 | -------------------------------------------------------------------------------- /core/generator_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/jaeles-project/jaeles/libs" 7 | ) 8 | 9 | // func TestGeneratorPath(t *testing.T) { 10 | // var req libs.Request 11 | 12 | // req.URL = "http://example.com/rest/products/6/reviews" 13 | // reqs := RunGenerator(req, ".json", `Path("{{.payload}}", "*")`) 14 | // fmt.Println(reqs) 15 | // // for _, r := range reqs { 16 | // // if !strings.Contains(r.URL, ".json") { 17 | // // t.Errorf("Error generate Path") 18 | // // } 19 | // // } 20 | // } 21 | 22 | func TestGeneratorMethod(t *testing.T) { 23 | var req libs.Request 24 | req.Method = "GET" 25 | reqs := RunGenerator(req, `Method("PUT")`) 26 | for _, r := range reqs { 27 | if r.Method != "PUT" { 28 | t.Errorf("Error generate Path") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/middleware.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/url" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/jaeles-project/jaeles/utils" 14 | "github.com/thoas/go-funk" 15 | 16 | "github.com/robertkrimen/otto" 17 | ) 18 | 19 | // @NOTE: Middleware allow execute command on your machine 20 | // So make sure you read the signature before you run it 21 | 22 | // Conclude is main function for detections 23 | func (r *Record) MiddleWare() { 24 | //record := *r 25 | vm := otto.New() 26 | var middlewareOutput string 27 | 28 | vm.Set("Host2IP", func(call otto.FunctionCall) otto.Value { 29 | var realHeaders []map[string]string 30 | for _, head := range r.Request.Headers { 31 | containHost := funk.Contains(head, "Host") 32 | if containHost == false { 33 | realHeaders = append(realHeaders, head) 34 | } 35 | } 36 | HostHeader := Host2IP(r.Request.URL) 37 | if !funk.IsEmpty(HostHeader) { 38 | realHeaders = append(realHeaders, HostHeader) 39 | } 40 | r.Request.Headers = realHeaders 41 | return otto.Value{} 42 | }) 43 | 44 | vm.Set("InvokeCmd", func(call otto.FunctionCall) otto.Value { 45 | rawCmd := call.Argument(0).String() 46 | result := InvokeCmd(r, rawCmd) 47 | middlewareOutput += result 48 | utils.DebugF(result) 49 | return otto.Value{} 50 | }) 51 | 52 | vm.Set("TurboIntruder", func(call otto.FunctionCall) otto.Value { 53 | if r.Request.Raw != "" { 54 | result := TurboIntruder(r) 55 | utils.DebugF(result) 56 | } 57 | return otto.Value{} 58 | }) 59 | 60 | for _, middleScript := range r.Request.Middlewares { 61 | utils.DebugF("[MiddleWare]: %s", middleScript) 62 | vm.Run(middleScript) 63 | } 64 | r.Request.MiddlewareOutput = middlewareOutput 65 | } 66 | 67 | // 68 | //// MiddleWare is main function for middleware 69 | //func MiddleWare(rec *libs.Record, options libs.Options) { 70 | // // func MiddleWare(req *libs.Request) { 71 | // vm := otto.New() 72 | // var middlewareOutput string 73 | // 74 | // vm.Set("Host2IP", func(call otto.FunctionCall) otto.Value { 75 | // var realHeaders []map[string]string 76 | // for _, head := range rec.Request.Headers { 77 | // containHost := funk.Contains(head, "Host") 78 | // if containHost == false { 79 | // realHeaders = append(realHeaders, head) 80 | // } 81 | // } 82 | // HostHeader := Host2IP(rec.Request.URL) 83 | // if !funk.IsEmpty(HostHeader) { 84 | // realHeaders = append(realHeaders, HostHeader) 85 | // } 86 | // rec.Request.Headers = realHeaders 87 | // return otto.Value{} 88 | // }) 89 | // 90 | // vm.Set("InvokeCmd", func(call otto.FunctionCall) otto.Value { 91 | // rawCmd := call.Argument(0).String() 92 | // result := InvokeCmd(rec, rawCmd) 93 | // middlewareOutput += result 94 | // utils.DebugF(result) 95 | // return otto.Value{} 96 | // }) 97 | // 98 | // vm.Set("TurboIntruder", func(call otto.FunctionCall) otto.Value { 99 | // if rec.Request.Raw != "" { 100 | // result := TurboIntruder(rec) 101 | // utils.DebugF(result) 102 | // } 103 | // return otto.Value{} 104 | // }) 105 | // 106 | // for _, middleString := range rec.Request.Middlewares { 107 | // utils.DebugF(middleString) 108 | // vm.Run(middleString) 109 | // } 110 | // 111 | // if middlewareOutput != "" { 112 | // rec.Request.MiddlewareOutput = middlewareOutput 113 | // } 114 | //} 115 | 116 | // Host2IP replace Host header with IP address 117 | func Host2IP(rawURL string) map[string]string { 118 | // HostHeader = 119 | u, err := url.Parse(rawURL) 120 | if err != nil { 121 | return map[string]string{} 122 | } 123 | hostname := u.Hostname() 124 | resolved, err := net.LookupHost(hostname) 125 | if err != nil { 126 | return map[string]string{} 127 | } 128 | 129 | ip := resolved[0] 130 | if u.Port() == "443" || u.Port() == "80" || u.Port() == "" { 131 | hostname = ip 132 | } else { 133 | hostname = ip + ":" + u.Port() 134 | } 135 | 136 | return map[string]string{"Host": hostname} 137 | } 138 | 139 | // InvokeCmd execute external command 140 | func InvokeCmd(rec *Record, rawCmd string) string { 141 | target := ParseTarget(rec.Request.URL) 142 | realCommand := Encoder(rec.Request.Encoding, ResolveVariable(rawCmd, target)) 143 | utils.DebugF("Execute Command: %v", realCommand) 144 | command := []string{ 145 | "bash", 146 | "-c", 147 | realCommand, 148 | } 149 | out, _ := exec.Command(command[0], command[1:]...).CombinedOutput() 150 | rec.Request.MiddlewareOutput = string(out) 151 | return string(out) 152 | } 153 | 154 | // TurboIntruder execute Turbo Intruder CLI 155 | func TurboIntruder(rec *Record) string { 156 | req := rec.Request 157 | turboPath := ResolveVariable("{{.homePath}}/plugins/turbo-intruder/turbo-intruder-all.jar", req.Target) 158 | scriptPath := ResolveVariable("{{.homePath}}/plugins/turbo-intruder/basic.py", req.Target) 159 | 160 | // create a folder in case it didn't exist 161 | logReqPath := ResolveVariable("{{.homePath}}/log/req/", req.Target) 162 | url := ResolveVariable("{{.URL}}", req.Target) 163 | rec.Request.URL = url 164 | if _, err := os.Stat(logReqPath); os.IsNotExist(err) { 165 | os.MkdirAll(logReqPath, 0750) 166 | } 167 | // write request to a file 168 | rawReq := ResolveVariable(req.Raw, req.Target) 169 | reqPath := path.Join(logReqPath, utils.GenHash(rawReq)) 170 | utils.WriteToFile(reqPath, rawReq) 171 | 172 | // call the command and parse some info 173 | turboCmd := fmt.Sprintf(`java -jar %v %v %v %v foo`, turboPath, scriptPath, reqPath, url) 174 | 175 | command := []string{ 176 | "bash", 177 | "-c", 178 | turboCmd, 179 | } 180 | out, _ := exec.Command(command[0], command[1:]...).CombinedOutput() 181 | 182 | // parse output 183 | rawOutput := string(out) 184 | if strings.Contains(rawOutput, "=-+-================") { 185 | // split the prefix 186 | resp := strings.Split(rawOutput, "=-+-================")[1] 187 | result := strings.Split(resp, "------------------+=") 188 | 189 | // [Info] 403 11585 0.272 190 | info := result[0] 191 | statusCode, _ := strconv.Atoi(strings.Split(info, " ")[1]) 192 | rec.Response.StatusCode = statusCode 193 | 194 | length, _ := strconv.Atoi(strings.Split(info, " ")[2]) 195 | rec.Response.Length = length 196 | 197 | resTime, _ := strconv.ParseFloat(strings.TrimSpace(strings.Split(info, " ")[2]), 64) 198 | rec.Response.ResponseTime = resTime 199 | 200 | rec.Request.Beautify = result[1] 201 | rec.Response.Beautify = result[2] 202 | verbose := fmt.Sprintf("[TurboIntruder] %v %v %v %v", rec.Request.URL, reqPath, rec.Response.StatusCode, rec.Response.ResponseTime) 203 | return verbose 204 | } 205 | 206 | verbose := fmt.Sprintf("[TurboIntruder] Error sending request from: %v", reqPath) 207 | return verbose 208 | } 209 | -------------------------------------------------------------------------------- /core/middleware_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davecgh/go-spew/spew" 6 | "github.com/jaeles-project/jaeles/libs" 7 | "testing" 8 | ) 9 | 10 | func TestMiddleWare(t *testing.T) { 11 | opt := libs.Options{ 12 | Concurrency: 3, 13 | Threads: 5, 14 | Verbose: true, 15 | NoDB: true, 16 | NoOutput: true, 17 | } 18 | URL := "http://httpbin.org:80" 19 | 20 | signContent := ` 21 | id: nginx-smuggling-01 22 | info: 23 | name: Nginx Smuggling 24 | risk: High 25 | 26 | variables: 27 | - random: RandomString("6") 28 | 29 | requests: 30 | - middlewares: 31 | - >- 32 | InvokeCmd('echo {{.BaseURL}}/sam; ls /tmp/') 33 | - >- 34 | InvokeCmd('touch /tmp/ssssssss') 35 | detections: 36 | - >- 37 | StringSearch("middleware", "dlm_message_server_in") 38 | ` 39 | sign, err := ParseSignFromContent(signContent) 40 | if err != nil { 41 | t.Errorf("Error parsing signature") 42 | } 43 | runner, err := InitRunner(URL, sign, opt) 44 | if err != nil { 45 | t.Errorf("Error parsing signature") 46 | } 47 | fmt.Println("New Requests generated: ", len(runner.Records)) 48 | 49 | spew.Dump(runner.Records[0].Request.Middlewares) 50 | runner.Sending() 51 | } 52 | -------------------------------------------------------------------------------- /core/output.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "github.com/jaeles-project/jaeles/libs" 7 | "github.com/jaeles-project/jaeles/utils" 8 | jsoniter "github.com/json-iterator/go" 9 | "github.com/logrusorgru/aurora/v3" 10 | "github.com/spf13/cast" 11 | "net/url" 12 | "os" 13 | "path" 14 | "strings" 15 | ) 16 | 17 | func (r *Record) Output() string { 18 | if !r.IsVulnerable { 19 | return "" 20 | } 21 | utils.InforF("[Found] %v", color.MagentaString(r.DetectString)) 22 | 23 | // do passive analyze if got called from detector 24 | if strings.Contains(strings.ToLower(r.DetectString), "dopassive") { 25 | r.Passives() 26 | } 27 | 28 | // @NOTE: Refactor 29 | //var outputName string 30 | outputName := "" 31 | if r.Opt.NoOutput == false && r.Sign.Noutput == false { 32 | r.StoreOutput() 33 | outputName = r.RawOutput 34 | 35 | // @NOTE: Disable viewing from UI from v0.14 36 | //if !r.Opt.NoDB { 37 | // database.ImportRecord(*r) 38 | //} 39 | } 40 | 41 | vulnInfo := fmt.Sprintf("[%v][%v] %v", r.Sign.ID, r.Sign.Info.Risk, r.Request.URL) 42 | if r.Opt.Quiet { 43 | lTarget := make(map[string]string) 44 | lTarget["VulnURL"] = r.Request.URL 45 | lTarget["Payload"] = r.Request.Payload 46 | lTarget["payload"] = r.Request.Payload 47 | lTarget["Status"] = fmt.Sprintf("%v", r.Response.StatusCode) 48 | lTarget["Length"] = fmt.Sprintf("%v", r.Response.Length) 49 | lTarget["Words"] = fmt.Sprintf("%v", int64(len(strings.Split(r.Response.Beautify, " ")))) 50 | lTarget["Time"] = fmt.Sprintf("%v", r.Response.ResponseTime) 51 | fmt.Printf("%v\n", ResolveVariable(r.Opt.QuietFormat, lTarget)) 52 | } else { 53 | // use this libs because we still want to see color when use chunked mode 54 | au := aurora.NewAurora(true) 55 | colorSignID := fmt.Sprintf("%s", au.Cyan(r.Sign.ID)) 56 | colorRisk := fmt.Sprintf("%s", au.BrightCyan(r.Sign.Info.Risk)) 57 | risk := strings.ToLower(r.Sign.Info.Risk) 58 | switch risk { 59 | case "critical": 60 | colorRisk = fmt.Sprintf("%s", au.Red(r.Sign.Info.Risk)) 61 | case "high": 62 | colorRisk = fmt.Sprintf("%s", au.BrightRed(r.Sign.Info.Risk)) 63 | case "medium": 64 | colorRisk = fmt.Sprintf("%s", au.Yellow(r.Sign.Info.Risk)) 65 | case "low": 66 | colorRisk = fmt.Sprintf("%s", au.BrightMagenta(r.Sign.Info.Risk)) 67 | case "info": 68 | colorRisk = fmt.Sprintf("%s", au.Blue(r.Sign.Info.Risk)) 69 | case "potential": 70 | colorRisk = fmt.Sprintf("%s", au.Magenta(r.Sign.Info.Risk)) 71 | } 72 | info := fmt.Sprintf("[%s][%s][%s] %s %s", au.Green("Vulnerable"), colorSignID, colorRisk, au.Green(r.Request.URL), au.Green(outputName)) 73 | fmt.Println(info) 74 | } 75 | if r.Opt.FoundCmd != "" { 76 | // add some more variables for notification 77 | r.Request.Target["vulnInfo"] = vulnInfo 78 | r.Request.Target["vulnOut"] = outputName 79 | r.Request.Target["notiText"] = vulnInfo 80 | r.Opt.FoundCmd = ResolveVariable(r.Opt.FoundCmd, r.Request.Target) 81 | Execution(r.Opt.FoundCmd) 82 | } 83 | 84 | if len(r.Request.PostRun) > 0 { 85 | r.Request.PostRun = ResolveDetection(r.Request.PostRun, r.Request.Target) 86 | for _, postrun := range r.Request.PostRun { 87 | Execution(postrun) 88 | } 89 | } 90 | 91 | //// do passive analyze if got called from detector 92 | //if strings.Contains(strings.ToLower(r.DetectString), "invokesign") { 93 | // r.InvokeSign() 94 | // options.SignFolder/sign-name.yaml 95 | //} 96 | 97 | if r.Sign.Donce { 98 | return "stop" 99 | } 100 | return "continue" 101 | } 102 | 103 | // StoreOutput store vulnerable request to a file 104 | func (r *Record) StoreOutput() { 105 | // disable out 106 | if r.NoOutput { 107 | return 108 | } 109 | // store output to a file 110 | if r.Request.URL == "" { 111 | r.Request.URL = r.Request.Target["URL"] 112 | } 113 | 114 | head := fmt.Sprintf("[%v][%v-%v] - %v\n", r.Sign.ID, r.Sign.Info.Confidence, r.Sign.Info.Risk, r.Request.URL) 115 | if r.Opt.VerboseSummary { 116 | // status-length-words-time 117 | moreInfo := fmt.Sprintf("%v-%v-%v-%v", r.Response.StatusCode, r.Response.Length, len(strings.Split(r.Response.Beautify, " ")), r.Response.ResponseTime) 118 | head = fmt.Sprintf("[%v][%v-%v][%v] - %v\n", r.Sign.ID, r.Sign.Info.Confidence, r.Sign.Info.Risk, moreInfo, r.Request.URL) 119 | } 120 | 121 | sInfo := fmt.Sprintf("[Sign-Info][%v-%v] - %v - %v\n", r.Sign.Info.Confidence, r.Sign.Info.Risk, r.Sign.RawPath, r.Sign.Info.Name) 122 | content := "[Vuln-Info]" + head + sInfo + fmt.Sprintf("[Detect-String] - %v\n\n", r.DetectString) 123 | if r.Request.MiddlewareOutput != "" { 124 | content += strings.Join(r.Request.Middlewares, "\n") 125 | content += fmt.Sprintf("\n<<%v>>\n", strings.Repeat("-", 50)) 126 | content += r.Request.MiddlewareOutput 127 | } 128 | 129 | if r.ExtraOutput != "" { 130 | content += fmt.Sprintf("%v\n", strings.Repeat("-", 50)) 131 | content += fmt.Sprintf("[Matches String]\n") 132 | content += strings.TrimSpace(r.ExtraOutput) 133 | content += fmt.Sprintf("\n") 134 | } 135 | 136 | content += fmt.Sprintf(">>>>%v\n", strings.Repeat("-", 50)) 137 | if r.Request.MiddlewareOutput == "" { 138 | content += r.Request.Beautify 139 | content += fmt.Sprintf("\n%v<<<<\n", strings.Repeat("-", 50)) 140 | content += r.Response.Beautify 141 | } 142 | 143 | // hash the content 144 | checksum := utils.GenHash(r.Response.Body) 145 | if r.Response.Body == "" { 146 | checksum = utils.GenHash(r.Response.Beautify) 147 | } 148 | 149 | parts := []string{r.Opt.Output} 150 | if r.Request.URL == "" { 151 | parts = append(parts, r.Request.Target["Domain"]) 152 | } else { 153 | host := utils.StripName(r.Request.Host) 154 | u, err := url.Parse(r.Request.URL) 155 | if err == nil { 156 | host = u.Hostname() 157 | } 158 | if host == "" { 159 | host = URLEncode(r.Request.URL) 160 | } 161 | parts = append(parts, host) 162 | } 163 | parts = append(parts, fmt.Sprintf("%v-%s", r.Sign.ID, checksum)) 164 | 165 | p := path.Join(parts...) 166 | if _, err := os.Stat(path.Dir(p)); os.IsNotExist(err) { 167 | err = os.MkdirAll(path.Dir(p), 0750) 168 | if err != nil { 169 | utils.ErrorF("Error Write content to: %v", p) 170 | } 171 | } 172 | // store output as JSON 173 | if r.Opt.JsonOutput { 174 | vulnData := libs.VulnData{ 175 | SignID: r.Sign.ID, 176 | SignName: r.Sign.Info.Name, 177 | Risk: r.Sign.Info.Risk, 178 | Confidence: r.Sign.Info.Confidence, 179 | DetectionString: r.DetectString, 180 | DetectResult: r.DetectResult, 181 | URL: r.Request.URL, 182 | Req: Base64Encode(r.Request.Beautify), 183 | Res: Base64Encode(r.Response.Beautify), 184 | } 185 | if data, err := jsoniter.MarshalToString(vulnData); err == nil { 186 | content = data 187 | } 188 | } 189 | 190 | // detail normal output 191 | utils.WriteToFile(p, content) 192 | 193 | // summary file 194 | sum := fmt.Sprintf("%v - %v", strings.TrimSpace(head), p) 195 | if r.Opt.JsonOutput { 196 | vulnData := libs.VulnData{ 197 | SignID: r.Sign.ID, 198 | SignName: r.Sign.Info.Name, 199 | Risk: r.Sign.Info.Risk, 200 | Confidence: r.Sign.Info.Confidence, 201 | DetectionString: r.DetectString, 202 | DetectResult: r.DetectResult, 203 | URL: r.Request.URL, 204 | StatusCode: cast.ToString(r.Response.StatusCode), 205 | ContentLength: cast.ToString(r.Response.Length), 206 | SignatureFile: r.Sign.RawPath, 207 | OutputFile: p, 208 | } 209 | if data, err := jsoniter.MarshalToString(vulnData); err == nil { 210 | sum = data 211 | } 212 | } 213 | utils.AppendToContent(r.Opt.SummaryOutput, sum) 214 | 215 | // file to parse single vulnerable 216 | vulnSum := fmt.Sprintf("[%v][%v] - %v", r.Sign.ID, r.Sign.Info.Risk, r.Request.Target["Raw"]) 217 | utils.AppendToContent(r.Opt.SummaryVuln, vulnSum) 218 | r.RawOutput = p 219 | } 220 | -------------------------------------------------------------------------------- /core/passive_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | //func TestPassiveCheck(t *testing.T) { 4 | // var record libs.Record 5 | // //record.Response.Beautify = "SQLite3::SQLException foo" 6 | // record.Response.Beautify = "Warning: file_exists(a" 7 | // var passive libs.Passive 8 | // passive = defaultPassive() 9 | // 10 | // for _, rule := range passive.Rules { 11 | // for _, detectionString := range rule.Detections { 12 | // fmt.Println(detectionString) 13 | // _, result := RunDetector(record, detectionString) 14 | // fmt.Println(result) 15 | // if !result { 16 | // t.Errorf("Error resolve variable") 17 | // } 18 | // } 19 | // } 20 | //} 21 | // 22 | //func TestRegexSearch(t *testing.T) { 23 | // raw := "SQLite3::SQLException foo" 24 | // regex := "(Exception (condition )?\\d+\\. Transaction rollback|com\\.frontbase\\.jdbc|org\\.h2\\.jdbc|Unexpected end of command in statement \\[\"|Unexpected token.*?in statement \\[|org\\.hsqldb\\.jdbc|CLI Driver.*?DB2|DB2 SQL error|\\bdb2_\\w+\\(|SQLSTATE.+SQLCODE|com\\.ibm\\.db2\\.jcc|Zend_Db_(Adapter|Statement)_Db2_Exception|Pdo[./_\\\\]Ibm|DB2Exception|Warning.*?\\Wifx_|Exception.*?Informix|Informix ODBC Driver|ODBC Informix driver|com\\.informix\\.jdbc|weblogic\\.jdbc\\.informix|Pdo[./_\\\\]Informix|IfxException|Warning.*?\\Wingres_|Ingres SQLSTATE|Ingres\\W.*?Driver|com\\.ingres\\.gcf\\.jdbc|Dynamic SQL Error|Warning.*?\\Wibase_|org\\.firebirdsql\\.jdbc|Pdo[./_\\\\]Firebird|Microsoft Access (\\d+ )?Driver|JET Database Engine|Access Database Engine|ODBC Microsoft Access|Syntax error \\(missing operator\\) in query expression|Driver.*? SQL[\\-\\_\\ ]*Server|OLE DB.*? SQL Server|\\bSQL Server[^<"]+Driver|Warning.*?\\W(mssql|sqlsrv)_|\\bSQL Server[^<"]+[0-9a-fA-F]{8}|System\\.Data\\.SqlClient\\.SqlException|(?s)Exception.*?\\bRoadhouse\\.Cms\\.|Microsoft SQL Native Client error '[0-9a-fA-F]{8}|\\[SQL Server\\]|ODBC SQL Server Driver|ODBC Driver \\d+ for SQL Server|SQLServer JDBC Driver|com\\.jnetdirect\\.jsql|macromedia\\.jdbc\\.sqlserver|Zend_Db_(Adapter|Statement)_Sqlsrv_Exception|com\\.microsoft\\.sqlserver\\.jdbc|Pdo[./_\\\\](Mssql|SqlSrv)|SQL(Srv|Server)Exception|SQL syntax.*?MySQL|Warning.*?\\Wmysqli?_|MySQLSyntaxErrorException|valid MySQL result|check the manual that corresponds to your (MySQL|MariaDB) server version|Unknown column '[^ ]+' in 'field list'|MySqlClient\\.|com\\.mysql\\.jdbc|Zend_Db_(Adapter|Statement)_Mysqli_Exception|Pdo[./_\\\\]Mysql|MySqlException|\\bORA-\\d{5}|Oracle error|Oracle.*?Driver|Warning.*?\\W(oci|ora)_|quoted string not properly terminated|SQL command not properly ended|macromedia\\.jdbc\\.oracle|oracle\\.jdbc|Zend_Db_(Adapter|Statement)_Oracle_Exception|Pdo[./_\\\\](Oracle|OCI)|OracleException|PostgreSQL.*?ERROR|Warning.*?\\Wpg_|valid PostgreSQL result|Npgsql\\.|PG::SyntaxError:|org\\.postgresql\\.util\\.PSQLException|ERROR:\\s\\ssyntax error at or near|ERROR: parser: parse error at or near|PostgreSQL query failed|org\\.postgresql\\.jdbc|Pdo[./_\\\\]Pgsql|PSQLException|SQL error.*?POS([0-9]+)|Warning.*?\\Wmaxdb_|DriverSapDB|com\\.sap\\.dbtech\\.jdbc|SQLite/JDBCDriver|SQLite\\.Exception|(Microsoft|System)\\.Data\\.SQLite\\.SQLiteException|Warning.*?\\W(sqlite_|SQLite3::)|\\[SQLITE_ERROR\\]|SQLite error \\d+:|sqlite3.OperationalError:|SQLite3::SQLException|org\\.sqlite\\.JDBC|Pdo[./_\\\\]Sqlite|SQLiteException|Warning.*?\\Wsybase_|Sybase message|Sybase.*?Server message|SybSQLException|Sybase\\.Data\\.AseClient|com\\.sybase\\.jdbc)" 25 | // 26 | // _, result := RegexSearch(raw, regex) 27 | // if !result { 28 | // t.Errorf("Error resolve variable") 29 | // } 30 | //} 31 | -------------------------------------------------------------------------------- /core/report.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "github.com/jaeles-project/jaeles/libs" 8 | "github.com/jaeles-project/jaeles/utils" 9 | "html/template" 10 | "path" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | type Vulnerability struct { 16 | SignID string 17 | SignPath string 18 | URL string 19 | Risk string 20 | Confidence string 21 | ReportPath string 22 | ReportFile string 23 | Status string 24 | Length string 25 | Words string 26 | Time string 27 | } 28 | 29 | type ReportData struct { 30 | Vulnerabilities []Vulnerability 31 | } 32 | 33 | // GenActiveReport generate report file 34 | func GenActiveReport(options libs.Options) error { 35 | title := "Jaeles Active Report" 36 | if options.Report.Title != "" { 37 | title = options.Report.Title 38 | } 39 | // parse vulns from out/jaeles-summary.txt 40 | vulns := ParseVuln(options) 41 | if len(vulns) == 0 { 42 | return errors.New(fmt.Sprintf("no Vulnerability found from %v", options.Output)) 43 | } 44 | data := struct { 45 | Vulnerabilities []Vulnerability 46 | CurrentDay string 47 | Version string 48 | Title string 49 | }{ 50 | Title: title, 51 | Vulnerabilities: vulns, 52 | CurrentDay: utils.GetCurrentDay(), 53 | Version: libs.VERSION, 54 | } 55 | 56 | // read template file 57 | tmpl := utils.GetFileContent(options.Report.TemplateFile) 58 | if tmpl == "" { 59 | return errors.New("blank template file") 60 | } 61 | 62 | t := template.Must(template.New("").Parse(tmpl)) 63 | buf := &bytes.Buffer{} 64 | err := t.Execute(buf, data) 65 | if err != nil { 66 | return err 67 | } 68 | result := buf.String() 69 | 70 | if !strings.Contains(options.Report.ReportName, "/") { 71 | options.Report.ReportName = path.Join(path.Dir(options.SummaryOutput), options.Report.ReportName) 72 | } 73 | utils.DebugF("Writing HTML report to: %v", options.Report.ReportName) 74 | _, err = utils.WriteToFile(options.Report.ReportName, result) 75 | 76 | // print result 77 | if err == nil { 78 | report, _ := filepath.Abs(options.Report.ReportName) 79 | utils.GoodF("Genereted Active HTML report: %v", report) 80 | } 81 | return err 82 | } 83 | 84 | // ParseVuln parse vulnerbility based on 85 | func ParseVuln(options libs.Options) []Vulnerability { 86 | var vulns []Vulnerability 87 | utils.DebugF("Parsing summary file: %v", options.SummaryOutput) 88 | content := utils.ReadingLines(options.SummaryOutput) 89 | if len(content) == 0 { 90 | return vulns 91 | } 92 | 93 | for _, line := range content { 94 | data := strings.Split(line, " - ") 95 | if len(data) <= 0 { 96 | continue 97 | } 98 | if !strings.Contains(data[0], "][") { 99 | continue 100 | } 101 | if len(strings.Split(data[0], "][")) < 2 { 102 | continue 103 | } 104 | 105 | signID := strings.Split(data[0], "][")[0][1:] 106 | info := strings.Split(data[0], "][")[1][:len(strings.Split(data[0], "][")[1])-1] 107 | if options.VerboseSummary { 108 | info = strings.Split(data[0], "][")[1] 109 | } 110 | confidence := strings.Split(info, "-")[0] 111 | risk := strings.Split(info, "-")[1] 112 | 113 | raw := data[2] 114 | // host/sign-hash 115 | reportPath := path.Join(path.Base(path.Dir(raw)), filepath.Base(raw)) 116 | 117 | vuln := Vulnerability{ 118 | SignID: signID, 119 | SignPath: "SignPath", 120 | URL: data[1], 121 | Risk: risk, 122 | Confidence: confidence, 123 | ReportPath: reportPath, 124 | ReportFile: filepath.Base(raw), 125 | } 126 | 127 | // verbose info 128 | if options.VerboseSummary { 129 | if len(strings.Split(data[0], "][")) < 3 { 130 | utils.ErrorF("Summary doesn't have verbose format") 131 | return vulns 132 | } 133 | // status-length-words-time 134 | verbose := strings.Split(strings.Split(data[0], "][")[2], "-") 135 | if len(verbose) < 4 { 136 | utils.ErrorF("Summary doesn't have verbose format") 137 | return vulns 138 | } 139 | vuln.Status = verbose[0] 140 | vuln.Length = verbose[1] 141 | vuln.Words = verbose[2] 142 | vuln.Time = strings.Trim(verbose[3], "]") 143 | } 144 | vulns = append(vulns, vuln) 145 | } 146 | return vulns 147 | } 148 | 149 | /// 150 | /* Start passive part */ 151 | /// 152 | 153 | // GenPassiveReport generate report file 154 | func GenPassiveReport(options libs.Options) error { 155 | title := "Jaeles Passive Report" 156 | if options.Report.Title != "" { 157 | title = options.Report.Title 158 | } 159 | // parse vulns from passive-out/jaeles-passive-summary.txt 160 | vulns := ParsePassiveVuln(options) 161 | if len(vulns) == 0 { 162 | return errors.New(fmt.Sprintf("no Passive found from %v", options.PassiveOutput)) 163 | } 164 | data := struct { 165 | Vulnerabilities []Vulnerability 166 | CurrentDay string 167 | Version string 168 | Title string 169 | }{ 170 | Title: title, 171 | Vulnerabilities: vulns, 172 | CurrentDay: utils.GetCurrentDay(), 173 | Version: libs.VERSION, 174 | } 175 | 176 | // read template file 177 | tmpl := utils.GetFileContent(options.Report.TemplateFile) 178 | if tmpl == "" { 179 | return errors.New("blank template file") 180 | } 181 | 182 | t := template.Must(template.New("").Parse(tmpl)) 183 | buf := &bytes.Buffer{} 184 | err := t.Execute(buf, data) 185 | if err != nil { 186 | return err 187 | } 188 | result := buf.String() 189 | 190 | if !strings.Contains(options.Report.ReportName, "/") { 191 | options.Report.ReportName = path.Join(path.Dir(options.PassiveSummary), options.Report.ReportName) 192 | } 193 | utils.DebugF("Writing HTML report to: %v", options.Report.ReportName) 194 | _, err = utils.WriteToFile(options.Report.ReportName, result) 195 | 196 | // print result 197 | if err == nil { 198 | report, _ := filepath.Abs(options.Report.ReportName) 199 | utils.GoodF("Genereted Passive HTML report: %v", report) 200 | } 201 | return err 202 | } 203 | 204 | // ParsePassiveVuln parse vulnerbility based on 205 | func ParsePassiveVuln(options libs.Options) []Vulnerability { 206 | var vulns []Vulnerability 207 | utils.DebugF("Parsing passive summary file: %v", options.PassiveSummary) 208 | content := utils.ReadingLines(options.PassiveSummary) 209 | if len(content) == 0 { 210 | return vulns 211 | } 212 | 213 | for _, line := range content { 214 | data := strings.Split(line, " - ") 215 | if len(data) <= 0 { 216 | continue 217 | } 218 | if !strings.Contains(data[0], "][") { 219 | continue 220 | } 221 | if len(strings.Split(data[0], "][")) < 3 { 222 | continue 223 | } 224 | 225 | signID := strings.Split(data[0], "][")[1] 226 | info := strings.TrimRight(strings.Split(data[0], "][")[2], "]") 227 | confidence := strings.Split(info, "-")[0] 228 | risk := strings.Split(info, "-")[1] 229 | 230 | raw := data[2] 231 | // host/sign-hash 232 | reportPath := path.Join(path.Base(path.Dir(raw)), filepath.Base(raw)) 233 | 234 | vuln := Vulnerability{ 235 | SignID: signID, 236 | SignPath: "SignPath", 237 | URL: data[1], 238 | Risk: risk, 239 | Confidence: confidence, 240 | ReportPath: reportPath, 241 | ReportFile: filepath.Base(raw), 242 | } 243 | vulns = append(vulns, vuln) 244 | } 245 | return vulns 246 | } 247 | -------------------------------------------------------------------------------- /core/report_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "testing" 7 | ) 8 | 9 | // 10 | //func TestReportTemplate(t *testing.T) { 11 | // var opt libs.Options 12 | // result := GenVulnData(opt) 13 | // fmt.Println(result) 14 | // if result == "" { 15 | // t.Errorf("Error resolve variable") 16 | // } 17 | //} 18 | 19 | func TestParseVuln(t *testing.T) { 20 | var opt libs.Options 21 | opt.SummaryOutput = "/tmp/rr/out/jaeles-summary.txt" 22 | vulns := ParseVuln(opt) 23 | fmt.Println(vulns) 24 | if len(vulns) == 0 { 25 | t.Errorf("Error read jaeles-summary") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/routine.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/jaeles-project/jaeles/libs" 5 | "github.com/jaeles-project/jaeles/utils" 6 | "github.com/robertkrimen/otto" 7 | ) 8 | 9 | // RoutineRunner runner struct 10 | type RoutineRunner struct { 11 | Input string 12 | SendingType string 13 | Opt libs.Options 14 | Sign libs.Signature 15 | Routines []libs.Routine 16 | Results map[string]bool 17 | Target map[string]string 18 | } 19 | 20 | // InitRoutine init routine task 21 | func InitRoutine(url string, sign libs.Signature, opt libs.Options) (RoutineRunner, error) { 22 | var routine RoutineRunner 23 | routine.Input = url 24 | routine.Opt = opt 25 | routine.Sign = sign 26 | 27 | routine.Results = make(map[string]bool) 28 | routine.Target = MoreVariables(ParseTarget(routine.Input), routine.Sign, routine.Opt) 29 | routine.ParseRoutines(&sign) 30 | 31 | return routine, nil 32 | } 33 | 34 | // ParseRoutines parse routine 35 | func (r *RoutineRunner) ParseRoutines(sign *libs.Signature) { 36 | var routines []libs.Routine 37 | 38 | for _, rawRoutine := range sign.Routines { 39 | var routine libs.Routine 40 | routine.Signs = ResolveHeader(rawRoutine.Signs, r.Target) 41 | for _, logic := range rawRoutine.Logics { 42 | logic.Expression = ResolveVariable(logic.Expression, r.Target) 43 | logic.Invokes = ResolveDetection(logic.Invokes, r.Target) 44 | routine.Logics = append(routine.Logics, logic) 45 | } 46 | routines = append(routines, routine) 47 | } 48 | 49 | r.Routines = routines 50 | } 51 | 52 | // Start start the routine 53 | func (r *RoutineRunner) Start() { 54 | for _, routine := range r.Routines { 55 | r.StartRunner(routine) 56 | if len(r.Results) == 0 { 57 | continue 58 | } 59 | 60 | for _, logic := range routine.Logics { 61 | IsPassed := r.DoExpression(logic.Expression) 62 | utils.DebugF("Expression: %s -- %v", logic.Expression, IsPassed) 63 | 64 | if IsPassed { 65 | // set new level 66 | r.Opt.Level = logic.Level 67 | r.DoInvokes(logic.Invokes) 68 | } 69 | } 70 | } 71 | } 72 | 73 | // Start start the routine 74 | func (r *RoutineRunner) StartRunner(routine libs.Routine) { 75 | 76 | for _, Signs := range routine.Signs { 77 | for key, signFile := range Signs { 78 | utils.DebugF("Start runner for: %s", key) 79 | sign, err := ParseSign(signFile) 80 | if err != nil { 81 | utils.ErrorF("Error parsing YAML sign: %v", signFile) 82 | continue 83 | } 84 | 85 | // Forced to send sign as serial 86 | //sign.Single = true 87 | 88 | job := libs.Job{ 89 | URL: r.Input, 90 | Sign: sign, 91 | } 92 | 93 | runner, err := InitRunner(job.URL, job.Sign, r.Opt) 94 | if err != nil { 95 | utils.ErrorF("Error create new runner: %v", err) 96 | } 97 | runner.InRoutine = true 98 | runner.Sending() 99 | utils.DebugF("Done runner for: %s", key) 100 | 101 | // set result here 102 | for _, rec := range runner.Records { 103 | if rec.IsVulnerable { 104 | _, exist := r.Results[key] 105 | if exist { 106 | continue 107 | } 108 | r.Results[key] = true 109 | } 110 | } 111 | } 112 | } 113 | 114 | } 115 | 116 | // DoExpression start the routine 117 | func (r *RoutineRunner) DoExpression(expression string) bool { 118 | vm := otto.New() 119 | // export value 120 | for k, v := range r.Results { 121 | vm.Set(k, func(call otto.FunctionCall) otto.Value { 122 | result, _ := vm.ToValue(v) 123 | return result 124 | }) 125 | } 126 | 127 | result, _ := vm.Run(expression) 128 | analyzeResult, err := result.Export() 129 | 130 | if err != nil || analyzeResult == nil { 131 | return false 132 | } 133 | return analyzeResult.(bool) 134 | } 135 | 136 | // DoExpression start the routine 137 | func (r *RoutineRunner) DoInvokes(invokes []string) { 138 | for _, signFile := range invokes { 139 | sign, err := ParseSign(signFile) 140 | if err != nil { 141 | utils.ErrorF("Error parsing YAML sign: %v", signFile) 142 | continue 143 | } 144 | job := libs.Job{ 145 | URL: r.Input, 146 | Sign: sign, 147 | } 148 | 149 | runner, err := InitRunner(job.URL, job.Sign, r.Opt) 150 | if err != nil { 151 | utils.ErrorF("Error create new runner: %v", err) 152 | } 153 | runner.Sending() 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /core/runner.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "github.com/jaeles-project/jaeles/sender" 7 | "github.com/jaeles-project/jaeles/utils" 8 | "strings" 9 | ) 10 | 11 | // Runner runner struct 12 | type Runner struct { 13 | Input string 14 | SendingType string 15 | RunnerType string 16 | Opt libs.Options 17 | Sign libs.Signature 18 | Origin Record 19 | 20 | CRecords []Record 21 | CMatched bool 22 | InRoutine bool 23 | 24 | Target map[string]string 25 | Records []Record 26 | } 27 | 28 | // Record all information about request 29 | type Record struct { 30 | // main part 31 | Request libs.Request 32 | Response libs.Response 33 | Sign libs.Signature 34 | 35 | // for dns part 36 | Dns libs.Dns 37 | 38 | // passive check 39 | NoOutput bool 40 | DoPassive bool 41 | SelectPassive string 42 | IsVulnerablePassive bool 43 | PassiveString string 44 | PassiveMatch string 45 | PassiveRules map[string]libs.Rule 46 | 47 | OriginReq libs.Request 48 | OriginRes libs.Response 49 | Origins []libs.Origin 50 | // for output 51 | Opt libs.Options 52 | RawOutput string 53 | ExtraOutput string 54 | // for detection 55 | PassCondition bool 56 | IsVulnerable bool 57 | DetectString string 58 | DetectResult string 59 | ScanID string 60 | } 61 | 62 | // InitRunner init task 63 | func InitRunner(url string, sign libs.Signature, opt libs.Options) (Runner, error) { 64 | var runner Runner 65 | runner.Input = url 66 | runner.Opt = opt 67 | runner.Sign = sign 68 | runner.SendingType = "parallels" 69 | runner.PrepareTarget() 70 | 71 | if runner.Sign.Single || runner.Sign.Serial { 72 | runner.SendingType = "serial" 73 | } 74 | 75 | if runner.Sign.Local == true { 76 | runner.SendingType = "local" 77 | } 78 | 79 | // sending origin if we have it here 80 | if runner.Sign.Origin.Method != "" || runner.Sign.Origin.Res != "" { 81 | runner.PrePareOrigin() 82 | } 83 | 84 | if len(runner.Sign.CRequests) > 0 { 85 | runner.GenCRequests() 86 | } 87 | 88 | // generate requests 89 | runner.GetRequests() 90 | return runner, nil 91 | } 92 | 93 | func (r *Runner) PrepareTarget() { 94 | // clean up the '//' on hostname in case we use --ba option 95 | if r.Opt.Mics.BaseRoot || r.Sign.CleanSlash { 96 | r.Input = strings.TrimRight(r.Input, "/") 97 | } 98 | 99 | Target := make(map[string]string) 100 | // parse Input from JSON format 101 | if r.Opt.EnableFormatInput { 102 | Target = ParseInputFormat(r.Input) 103 | } else { 104 | Target = ParseTarget(r.Input) 105 | } 106 | 107 | // auto turn on baseRoot when we have prefix 108 | if r.Opt.Mics.BaseRoot || r.Sign.Replicate.Prefixes != "" { 109 | Target["BaseURL"] = Target["Raw"] 110 | } 111 | 112 | r.Sign.Target = Target 113 | r.Target = Target 114 | } 115 | 116 | // GetRequests get requests ready to send 117 | func (r *Runner) GetRequests() { 118 | reqs := r.GenRequests() 119 | if len(reqs) > 0 { 120 | for _, req := range reqs { 121 | var rec Record 122 | // set somethings in record 123 | rec.Request = req 124 | rec.Request.Target = r.Target 125 | rec.Sign = r.Sign 126 | rec.Opt = r.Opt 127 | // assign origins here 128 | rec.OriginReq = r.Origin.Request 129 | rec.OriginRes = r.Origin.Response 130 | 131 | r.Records = append(r.Records, rec) 132 | } 133 | } 134 | } 135 | 136 | // GenRequests generate request for sending 137 | func (r *Runner) GenRequests() []libs.Request { 138 | // quick param for calling resource 139 | r.Sign.Target = MoreVariables(r.Sign.Target, r.Sign, r.Opt) 140 | 141 | var realReqs []libs.Request 142 | globalVariables := ParseVariable(r.Sign) 143 | if len(globalVariables) > 0 { 144 | for _, globalVariable := range globalVariables { 145 | r.Sign.Target = r.Target 146 | for k, v := range globalVariable { 147 | r.Sign.Target[k] = v 148 | } 149 | // start to send stuff 150 | for _, req := range r.Sign.Requests { 151 | // receive request from "-r req.txt" 152 | if r.Sign.RawRequest != "" { 153 | req.Raw = r.Sign.RawRequest 154 | } 155 | // gen bunch of request to send 156 | realReqs = append(realReqs, ParseRequest(req, r.Sign, r.Opt)...) 157 | } 158 | } 159 | } else { 160 | r.Sign.Target = r.Target 161 | // start to send stuff 162 | for _, req := range r.Sign.Requests { 163 | // receive request from "-r req.txt" 164 | if r.Sign.RawRequest != "" { 165 | req.Raw = r.Sign.RawRequest 166 | } 167 | // gen bunch of request to send 168 | realReqs = append(realReqs, ParseRequest(req, r.Sign, r.Opt)...) 169 | } 170 | } 171 | return realReqs 172 | } 173 | 174 | // PrePareOrigin parsing origin request 175 | func (r *Runner) PrePareOrigin() { 176 | var originRec libs.Record 177 | var origin libs.Origin 178 | // prepare initial signature and variables 179 | Target := make(map[string]string) 180 | Target = MoreVariables(r.Target, r.Sign, r.Opt) 181 | // base origin 182 | if r.Sign.Origin.Method != "" || r.Sign.Origin.Res != "" { 183 | origin, Target = r.SendOrigin(r.Sign.Origin) 184 | originRec.Request = origin.ORequest 185 | originRec.Response = origin.OResponse 186 | } 187 | 188 | // in case we have many origin 189 | if len(r.Sign.Origins) > 0 { 190 | var origins []libs.Origin 191 | for index, origin := range r.Sign.Origins { 192 | origin, Target = r.SendOrigin(origin.ORequest) 193 | if origin.Label == "" { 194 | origin.Label = fmt.Sprintf("%v", index) 195 | } 196 | origins = append(origins, origin) 197 | } 198 | r.Sign.Origins = origins 199 | } 200 | 201 | r.Target = Target 202 | } 203 | 204 | // SendOrigin sending origin request 205 | func (r *Runner) SendOrigin(originReq libs.Request) (libs.Origin, map[string]string) { 206 | var origin libs.Origin 207 | var err error 208 | var originRes libs.Response 209 | originReq.EnableChecksum = true 210 | 211 | originSign := r.Sign 212 | if r.Opt.Scan.RawRequest != "" { 213 | RawRequest := utils.GetFileContent(r.Opt.Scan.RawRequest) 214 | originReq = ParseBurpRequest(RawRequest) 215 | } 216 | 217 | if originReq.Raw == "" { 218 | originSign.Target = r.Target 219 | originReq = ParseOrigin(originReq, originSign, r.Opt) 220 | } 221 | 222 | // parse response directly without sending 223 | if originReq.Res != "" { 224 | originRes = ParseBurpResponse("", originReq.Res) 225 | } else { 226 | originRes, err = sender.JustSend(r.Opt, originReq) 227 | if err == nil { 228 | if r.Opt.Verbose && (originReq.Method != "") { 229 | fmt.Printf("[Sent-Origin] %v %v %v %v %v\n", originReq.Method, originReq.URL, originRes.Status, originRes.ResponseTime, len(originRes.Beautify)) 230 | } 231 | } 232 | } 233 | 234 | originRec := Record{Request: originReq, Response: originRes} 235 | // set some more variables 236 | originRec.Conclude() 237 | 238 | for k, v := range originSign.Target { 239 | if r.Target[k] == "" { 240 | r.Target[k] = v 241 | } 242 | } 243 | 244 | origin.ORequest = originReq 245 | origin.OResponse = originRes 246 | r.Origin = originRec 247 | 248 | if originRes.Checksum != "" { 249 | utils.DebugF("[Checksum Origin] %s - %s", originReq.URL, originRes.Checksum) 250 | r.Sign.Checksums = append(r.Sign.Checksums, originRes.Checksum) 251 | } 252 | return origin, r.Target 253 | } 254 | 255 | // GenCRequests generate condition requests 256 | func (r *Runner) GenCRequests() { 257 | // quick param for calling resource 258 | r.Sign.Target = MoreVariables(r.Sign.Target, r.Sign, r.Opt) 259 | 260 | var realReqs []libs.Request 261 | globalVariables := ParseVariable(r.Sign) 262 | if len(globalVariables) > 0 { 263 | for _, globalVariable := range globalVariables { 264 | r.Sign.Target = r.Target 265 | for k, v := range globalVariable { 266 | r.Sign.Target[k] = v 267 | } 268 | // start to send stuff 269 | for _, req := range r.Sign.CRequests { 270 | // receive request from "-r req.txt" 271 | if r.Sign.RawRequest != "" { 272 | req.Raw = r.Sign.RawRequest 273 | } 274 | // gen bunch of request to send 275 | realReqs = append(realReqs, ParseRequest(req, r.Sign, r.Opt)...) 276 | } 277 | } 278 | } else { 279 | r.Sign.Target = r.Target 280 | // start to send stuff 281 | for _, req := range r.Sign.CRequests { 282 | // receive request from "-r req.txt" 283 | if r.Sign.RawRequest != "" { 284 | req.Raw = r.Sign.RawRequest 285 | } 286 | // gen bunch of request to send 287 | realReqs = append(realReqs, ParseRequest(req, r.Sign, r.Opt)...) 288 | } 289 | } 290 | 291 | if len(realReqs) > 0 { 292 | for _, req := range realReqs { 293 | var rec Record 294 | 295 | rec.NoOutput = true 296 | if r.Sign.COutput { 297 | rec.NoOutput = false 298 | } 299 | 300 | // set somethings in record 301 | rec.Request = req 302 | rec.Request.Target = r.Target 303 | rec.Sign = r.Sign 304 | rec.Opt = r.Opt 305 | // assign origins here 306 | rec.OriginReq = r.Origin.Request 307 | rec.OriginRes = r.Origin.Response 308 | 309 | r.CRecords = append(r.CRecords, rec) 310 | } 311 | } 312 | 313 | } 314 | -------------------------------------------------------------------------------- /core/runner_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davecgh/go-spew/spew" 6 | "github.com/jaeles-project/jaeles/libs" 7 | "testing" 8 | ) 9 | 10 | func TestInitRunner(t *testing.T) { 11 | opt := libs.Options{ 12 | Concurrency: 3, 13 | Threads: 5, 14 | Verbose: true, 15 | NoDB: true, 16 | NoOutput: true, 17 | } 18 | URL := "http://httpbin.org" 19 | signContent := ` 20 | # info to search signature 21 | id: cred-01-01 22 | noutput: true 23 | info: 24 | name: Default Credentials 25 | risk: High 26 | 27 | origin: 28 | method: GET 29 | redirect: false 30 | headers: 31 | - User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55 32 | url: >- 33 | {{.BaseURL}}/anything?q=1122 34 | concllousions: 35 | - SetValue("code", StatusCode()) 36 | 37 | variables: 38 | - tomcat: | 39 | /manager/ 40 | /manager/html/ 41 | /server-status/ 42 | /html/ 43 | / 44 | requests: 45 | - method: GET 46 | redirect: false 47 | url: >- 48 | {{.BaseURL}}/anything?aaaa={{.tomcat}} 49 | headers: 50 | - User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55 51 | detections: 52 | - >- 53 | StatusCode() == 200 && (1 == 1) 54 | - >- 55 | StatusCode() == 200 56 | 57 | ` 58 | //signFile := "/Users/j3ssie/go/src/github.com/jaeles-project/jaeles/test-sign/default-cred.yaml" 59 | sign, err := ParseSignFromContent(signContent) 60 | if err != nil { 61 | t.Errorf("Error parsing signature") 62 | 63 | } 64 | runner, err := InitRunner(URL, sign, opt) 65 | if err != nil { 66 | t.Errorf("Error parsing signature") 67 | } 68 | spew.Dump(runner.Target) 69 | fmt.Println("New Requests generated: ", len(runner.Records)) 70 | 71 | runner.Sending() 72 | } 73 | 74 | func TestInitRunnerSerial(t *testing.T) { 75 | opt := libs.Options{ 76 | Concurrency: 3, 77 | Threads: 5, 78 | Verbose: true, 79 | NoDB: true, 80 | NoOutput: true, 81 | } 82 | URL := "http://httpbin.org" 83 | 84 | signContent := ` 85 | id: dom-xss-01 86 | single: true 87 | info: 88 | name: DOM XSS test 89 | risk: High 90 | 91 | 92 | variables: 93 | - xss: RandomString(4) 94 | 95 | requests: 96 | - method: GET 97 | url: >- 98 | {{.BaseURL}}/tests/sinks.html?name=[[.custom]]{{.xss}} 99 | conclusions: 100 | - StringSelect("component", "res1", "right", "left") 101 | - SetValue("sam", "regex") 102 | - RegexSelect("component", "var_name", "regex") 103 | detections: 104 | - StatusCode() == 200 && StringSearch("response", "{{.xss}}") 105 | 106 | - conditions: 107 | - ValueOf('sam') == 'regex' 108 | method: GET 109 | url: >- 110 | {{.BaseURL}}/tests/sinks.html?name=111{{.xss}}22[[.custom]] 111 | detections: 112 | - >- 113 | StatusCode() == 200 && StringSearch("response", "{{.xss}}") 114 | 115 | ` 116 | sign, err := ParseSignFromContent(signContent) 117 | if err != nil { 118 | t.Errorf("Error parsing signature") 119 | 120 | } 121 | runner, err := InitRunner(URL, sign, opt) 122 | if err != nil { 123 | t.Errorf("Error parsing signature") 124 | } 125 | spew.Dump(runner.Target) 126 | fmt.Println("New Requests generated: ", len(runner.Records)) 127 | 128 | runner.Sending() 129 | } 130 | -------------------------------------------------------------------------------- /core/sender_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/jaeles-project/jaeles/sender" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/jaeles-project/jaeles/libs" 9 | ) 10 | 11 | func TestReallySending(t *testing.T) { 12 | var headers []map[string]string 13 | var req libs.Request 14 | headers = append(headers, map[string]string{ 15 | "Content-Type": "application/json", 16 | }) 17 | 18 | req.Method = "POST" 19 | req.URL = "https://httpbin.org/post" 20 | req.Headers = headers 21 | 22 | var opt libs.Options 23 | // opt.Proxy = "http://127.0.0.1:8080" 24 | res, err := sender.JustSend(opt, req) 25 | if err != nil { 26 | t.Errorf("Error sending request") 27 | } 28 | 29 | status := res.StatusCode 30 | if status != 200 { 31 | t.Errorf("Error parsing result") 32 | } 33 | // sending with POST data 34 | req.Body = "example1=23" 35 | res, err = sender.JustSend(opt, req) 36 | if err != nil { 37 | t.Errorf("Error sending request") 38 | } 39 | 40 | if !strings.Contains(res.Body, "example1") { 41 | t.Errorf("Error parsing result") 42 | } 43 | 44 | req.Body = `{"example1": "3333"}` 45 | res, err = sender.JustSend(opt, req) 46 | if err != nil { 47 | t.Errorf("Error sending request") 48 | } 49 | 50 | if !strings.Contains(res.Body, "example1") { 51 | t.Errorf("Error parsing result") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /core/sending.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "github.com/jaeles-project/jaeles/sender" 7 | "github.com/jaeles-project/jaeles/utils" 8 | "github.com/panjf2000/ants" 9 | "github.com/thoas/go-funk" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | func (r *Runner) Sending() { 15 | if len(r.CRecords) > 0 { 16 | if r.Sign.Match == "" { 17 | r.Sign.Match = "all" 18 | } 19 | r.SendCRequests() 20 | if !r.CMatched { 21 | utils.DebugF("Check request not matched") 22 | return 23 | } 24 | utils.DebugF("Passed check request") 25 | } 26 | 27 | switch r.SendingType { 28 | case "local": 29 | r.LocalSending() 30 | break 31 | case "serial": 32 | r.SendingSerial() 33 | break 34 | case "parallels": 35 | r.SendingParallels() 36 | break 37 | default: 38 | r.SendingParallels() 39 | } 40 | } 41 | 42 | func (r *Runner) LocalSending() { 43 | utils.DebugF("Start local analyze for: %s", r.Input) 44 | if !strings.HasPrefix(r.Input, "file://") { 45 | // just make it up for beautiful output 46 | r.Input = fmt.Sprintf("http://jaeles.local/?file=%s", r.Input) 47 | } 48 | 49 | record := Record{ 50 | Opt: r.Opt, 51 | Sign: r.Sign, 52 | Request: libs.Request{ 53 | URL: r.Input, 54 | }, 55 | Response: r.Sign.Response, 56 | } 57 | var localScripts []string 58 | 59 | for _, rule := range r.Sign.Rules { 60 | localScripts = append(localScripts, rule.Detections...) 61 | if rule.Regex != "" { 62 | analyze := rule.Regex 63 | utils.DebugF("LocalRegex: %s", analyze) 64 | extra, validate := RegexSearch(r.Sign.Response.Beautify, rule.Regex) 65 | record.DetectString = analyze 66 | record.IsVulnerable = validate 67 | record.DetectResult = extra 68 | record.ExtraOutput = extra 69 | 70 | utils.DebugF("[Detection] %v -- %v", analyze, record.IsVulnerable) 71 | // deal with vulnerable one here 72 | record.Output() 73 | } 74 | } 75 | record.RequestScripts("detections", localScripts) 76 | } 77 | 78 | func (r *Runner) SendingSerial() { 79 | var recordsSent []Record 80 | // Submit tasks one by one. 81 | for _, record := range r.Records { 82 | record.DoSending() 83 | if r.InRoutine { 84 | recordsSent = append(recordsSent, record) 85 | } 86 | } 87 | if r.InRoutine { 88 | r.Records = recordsSent 89 | } 90 | } 91 | 92 | func (r *Runner) SendingParallels() { 93 | var recordsSent []Record 94 | threads := r.Opt.Threads 95 | if r.Sign.Threads != 0 { 96 | threads = r.Sign.Threads 97 | } 98 | if r.Sign.Single { 99 | threads = 1 100 | } 101 | 102 | var wg sync.WaitGroup 103 | p, _ := ants.NewPoolWithFunc(threads, func(j interface{}) { 104 | rec := j.(Record) 105 | rec.DoSending() 106 | if r.InRoutine { 107 | recordsSent = append(recordsSent, rec) 108 | } 109 | wg.Done() 110 | }, ants.WithPreAlloc(true)) 111 | defer p.Release() 112 | 113 | // Submit tasks one by one. 114 | for _, record := range r.Records { 115 | wg.Add(1) 116 | _ = p.Invoke(record) 117 | } 118 | wg.Wait() 119 | if r.InRoutine { 120 | r.Records = recordsSent 121 | } 122 | } 123 | 124 | // DoSending really sending the request 125 | func (r *Record) DoSending() { 126 | // replace things second time here with new values section 127 | AltResolveRequest(&r.Request) 128 | // check conditions 129 | if len(r.Request.Conditions) > 0 { 130 | validate := r.Condition() 131 | if !validate { 132 | return 133 | } 134 | } 135 | 136 | // run middleware here 137 | if !funk.IsEmpty(r.Request.Middlewares) { 138 | r.MiddleWare() 139 | } 140 | 141 | req := r.Request 142 | if r.Opt.EnableFiltering || r.Sign.Filter { 143 | req.EnableChecksum = true 144 | } 145 | 146 | // if middleware return the response skip sending it 147 | var res libs.Response 148 | if r.Response.StatusCode == 0 && r.Request.Method != "" && r.Request.MiddlewareOutput == "" && req.Res == "" { 149 | // sending with real browser 150 | if req.Engine == "chrome" { 151 | res, _ = sender.SendWithChrome(r.Opt, req) 152 | } else { 153 | res, _ = sender.JustSend(r.Opt, req) 154 | } 155 | } 156 | // parse response directly without sending 157 | if req.Res != "" { 158 | res = ParseBurpResponse("", req.Res) 159 | } 160 | r.Request = req 161 | r.Response = res 162 | 163 | if r.Response.Checksum != "" { 164 | utils.DebugF("[Checksum] %s - %s", req.URL, res.Checksum) 165 | } 166 | r.Analyze() 167 | } 168 | 169 | // SendCRequests sending condition requests 170 | func (r *Runner) SendCRequests() { 171 | var matchCount int 172 | for _, rec := range r.CRecords { 173 | 174 | // sending func for parallel mode 175 | // replace things second time here with new values section 176 | AltResolveRequest(&rec.Request) 177 | // check conditions 178 | if len(rec.Request.Conditions) > 0 { 179 | validate := rec.Condition() 180 | if !validate { 181 | return 182 | } 183 | } 184 | 185 | // run middleware here 186 | if !funk.IsEmpty(rec.Request.Middlewares) { 187 | rec.MiddleWare() 188 | } 189 | 190 | req := rec.Request 191 | // if middleware return the response skip sending it 192 | var res libs.Response 193 | if rec.Response.StatusCode == 0 && rec.Request.Method != "" && rec.Request.MiddlewareOutput == "" && req.Res == "" { 194 | // sending with real browser 195 | if req.Engine == "chrome" { 196 | res, _ = sender.SendWithChrome(rec.Opt, req) 197 | } else { 198 | res, _ = sender.JustSend(rec.Opt, req) 199 | } 200 | } 201 | // parse response directly without sending 202 | if req.Res != "" { 203 | res = ParseBurpResponse("", req.Res) 204 | } 205 | rec.Request = req 206 | rec.Response = res 207 | 208 | rec.Analyze() 209 | if rec.IsVulnerable { 210 | matchCount += 1 211 | } 212 | 213 | } 214 | 215 | switch r.Sign.Match { 216 | case "all": 217 | if matchCount == len(r.CRecords) { 218 | r.CMatched = true 219 | } 220 | break 221 | case "any": 222 | if matchCount > 0 { 223 | r.CMatched = true 224 | } 225 | break 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /core/signature.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "github.com/jaeles-project/jaeles/database" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "github.com/jaeles-project/jaeles/utils" 7 | "github.com/thoas/go-funk" 8 | "path" 9 | "path/filepath" 10 | "regexp" 11 | "strings" 12 | ) 13 | 14 | // @NOTE: Signatures allow execute command on your machine 15 | // So make sure you read the signature before you run it 16 | 17 | // SelectSign select signature by multiple selector 18 | func SelectSign(signName string) []string { 19 | var Signs []string 20 | // return default sign if doesn't set anything 21 | if signName == "**" { 22 | Signs = database.SelectSign("") 23 | return Signs 24 | } 25 | signs := SingleSign(strings.TrimSpace(signName)) 26 | if len(signs) > 0 { 27 | Signs = append(Signs, signs...) 28 | } 29 | Signs = funk.UniqString(Signs) 30 | return Signs 31 | } 32 | 33 | // SingleSign select signature by single selector 34 | func SingleSign(signName string) []string { 35 | signName = utils.NormalizePath(signName) 36 | 37 | var Signs []string 38 | // in case selector is file 39 | if strings.HasSuffix(signName, ".yaml") && !strings.Contains(signName, "*") { 40 | if utils.FileExists(signName) { 41 | Signs = append(Signs, signName) 42 | } 43 | return Signs 44 | } 45 | 46 | // in case selector is a folder 47 | if utils.FolderExists(signName) { 48 | signName = path.Join(path.Clean(signName), ".*") 49 | } 50 | 51 | // get more signature 52 | if strings.Contains(signName, "*") && strings.Contains(signName, "/") { 53 | asbPath, _ := filepath.Abs(signName) 54 | baseSelect := filepath.Base(signName) 55 | rawSigns := utils.GetFileNames(filepath.Dir(asbPath), "yaml") 56 | for _, signFile := range rawSigns { 57 | baseSign := filepath.Base(signFile) 58 | if len(baseSign) == 1 && baseSign == "*" { 59 | Signs = append(Signs, signFile) 60 | continue 61 | } 62 | r, err := regexp.Compile(baseSelect) 63 | if err != nil { 64 | if strings.Contains(signFile, baseSelect) { 65 | Signs = append(Signs, signFile) 66 | } 67 | } 68 | if r.MatchString(baseSign) { 69 | Signs = append(Signs, signFile) 70 | } 71 | } 72 | } 73 | return Signs 74 | } 75 | 76 | // AltResolveRequest resolve all request again but look for [[ ]] delimiter 77 | func AltResolveRequest(req *libs.Request) { 78 | target := req.Target 79 | if len(req.Values) > 0 { 80 | for _, value := range req.Values { 81 | for k, v := range value { 82 | if strings.Contains(v, "{{.") && strings.Contains(v, "}}") { 83 | v = ResolveVariable(v, target) 84 | } 85 | // variable as a script 86 | if strings.Contains(v, "(") && strings.Contains(v, ")") { 87 | 88 | newValue := RunVariables(v) 89 | if len(newValue) > 0 { 90 | target[k] = newValue[0] 91 | } 92 | } else { 93 | target[k] = v 94 | } 95 | } 96 | } 97 | } 98 | // resolve all part again but with secondary template 99 | req.URL = AltResolveVariable(req.URL, target) 100 | req.Body = AltResolveVariable(req.Body, target) 101 | req.Headers = AltResolveHeader(req.Headers, target) 102 | req.Detections = AltResolveDetection(req.Detections, target) 103 | req.Generators = AltResolveDetection(req.Generators, target) 104 | req.Middlewares = AltResolveDetection(req.Middlewares, target) 105 | } 106 | 107 | // ResolveDetection resolve detection part in YAML signature file 108 | func ResolveDetection(detections []string, target map[string]string) []string { 109 | var realDetections []string 110 | for _, detect := range detections { 111 | realDetections = append(realDetections, ResolveVariable(detect, target)) 112 | } 113 | return realDetections 114 | } 115 | 116 | // AltResolveDetection resolve detection part in YAML signature file 117 | func AltResolveDetection(detections []string, target map[string]string) []string { 118 | var realDetections []string 119 | for _, detect := range detections { 120 | realDetections = append(realDetections, AltResolveVariable(detect, target)) 121 | } 122 | return realDetections 123 | } 124 | 125 | // ResolveHeader resolve headers part in YAML signature file 126 | func ResolveHeader(headers []map[string]string, target map[string]string) []map[string]string { 127 | // realHeaders := headers 128 | var realHeaders []map[string]string 129 | 130 | for _, head := range headers { 131 | realHeader := make(map[string]string) 132 | for key, value := range head { 133 | realKey := ResolveVariable(key, target) 134 | realVal := ResolveVariable(value, target) 135 | realHeader[realKey] = realVal 136 | } 137 | realHeaders = append(realHeaders, realHeader) 138 | } 139 | 140 | return realHeaders 141 | } 142 | 143 | // AltResolveHeader resolve headers part in YAML signature file 144 | func AltResolveHeader(headers []map[string]string, target map[string]string) []map[string]string { 145 | var realHeaders []map[string]string 146 | 147 | for _, head := range headers { 148 | realHeader := make(map[string]string) 149 | for key, value := range head { 150 | realKey := AltResolveVariable(key, target) 151 | realVal := AltResolveVariable(value, target) 152 | realHeader[realKey] = realVal 153 | } 154 | realHeaders = append(realHeaders, realHeader) 155 | } 156 | 157 | return realHeaders 158 | } 159 | -------------------------------------------------------------------------------- /core/template.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "github.com/Masterminds/sprig/v3" 6 | "github.com/jaeles-project/jaeles/utils" 7 | "regexp" 8 | "strings" 9 | "text/template" 10 | ) 11 | 12 | // ResolveVariable resolve template from signature file 13 | func ResolveVariable(format string, data map[string]string) string { 14 | if strings.TrimSpace(format) == "" { 15 | return format 16 | } 17 | _, exist := data["original"] 18 | if !exist { 19 | data["original"] = "" 20 | } 21 | realFormat, err := template.New("").Funcs(sprig.TxtFuncMap()).Parse(format) 22 | // when template contain {{ 23 | if err != nil { 24 | r, rerr := regexp.Compile(`\{\{[^.]`) 25 | if rerr != nil { 26 | return format 27 | } 28 | matches := r.FindStringSubmatch(format) 29 | if len(matches) > 0 { 30 | for _, m := range matches { 31 | new := strings.Replace(m, `{{`, `{{"{{"}}`, -1) 32 | format = strings.Replace(format, m, new, -1) 33 | } 34 | } 35 | // parse it again 36 | realFormat, err = template.New("").Funcs(sprig.TxtFuncMap()).Parse(format) 37 | if err != nil { 38 | utils.ErrorF("improper template format %v", format) 39 | return format 40 | } 41 | } 42 | t := template.Must(realFormat, err) 43 | 44 | buf := &bytes.Buffer{} 45 | err = t.Execute(buf, data) 46 | if err != nil { 47 | return format 48 | } 49 | return buf.String() 50 | } 51 | 52 | // AltResolveVariable just like ResolveVariable but looking for [[.var]] 53 | func AltResolveVariable(format string, data map[string]string) string { 54 | if strings.TrimSpace(format) == "" { 55 | return format 56 | } 57 | realFormat, err := template.New("").Delims("[[", "]]").Funcs(sprig.TxtFuncMap()).Parse(format) 58 | _, exist := data["original"] 59 | if !exist { 60 | data["original"] = "" 61 | } 62 | 63 | // when template contain [[ 64 | if err != nil { 65 | r, rerr := regexp.Compile(`\[\[[^.]`) 66 | if rerr != nil { 67 | return format 68 | } 69 | matches := r.FindStringSubmatch(format) 70 | if len(matches) > 0 { 71 | for _, m := range matches { 72 | new := strings.Replace(m, `[[`, `[["[["]]`, -1) 73 | format = strings.Replace(format, m, new, -1) 74 | } 75 | } 76 | // parse it again 77 | realFormat, err = template.New("").Funcs(sprig.TxtFuncMap()).Parse(format) 78 | if err != nil { 79 | utils.ErrorF("improper template format %v", format) 80 | return format 81 | } 82 | } 83 | t := template.Must(realFormat, err) 84 | 85 | buf := &bytes.Buffer{} 86 | err = t.Execute(buf, data) 87 | if err != nil { 88 | return format 89 | } 90 | return buf.String() 91 | } 92 | -------------------------------------------------------------------------------- /core/template_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | // See doc here: http://masterminds.github.io/sprig/ 9 | func TestResolveVariableWithFunc(t *testing.T) { 10 | data := make(map[string]string) 11 | data["var"] = "foo" 12 | data["sam"] = "foo bar" 13 | data["uu"] = "http://example.com/a?q=2" 14 | format := "{{.var}}" 15 | result := ResolveVariable(format, data) 16 | fmt.Println(result) 17 | if result == "" { 18 | t.Errorf("Error TestResolveVariable") 19 | } 20 | 21 | format = "{{.var | b64enc }}" 22 | result = ResolveVariable(format, data) 23 | fmt.Println(result) 24 | if result == "" { 25 | t.Errorf("Error TestResolveVariable") 26 | } 27 | 28 | format = `[[ .uu | sha1sum ]]` 29 | result = AltResolveVariable(format, data) 30 | fmt.Println(result) 31 | if result == "" { 32 | t.Errorf("Error TestResolveVariable") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/update.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "github.com/jaeles-project/jaeles/utils" 7 | "gopkg.in/src-d/go-git.v4" 8 | "gopkg.in/src-d/go-git.v4/plumbing/transport/http" 9 | "os" 10 | "path" 11 | ) 12 | 13 | // UpdatePlugins update latest UI and Plugins from default repo 14 | func UpdatePlugins(options libs.Options) { 15 | pluginPath := path.Join(options.RootFolder, "plugins") 16 | url := libs.UIREPO 17 | utils.GoodF("Cloning Plugins from: %v", url) 18 | if utils.FolderExists(pluginPath) { 19 | utils.InforF("Remove: %v", pluginPath) 20 | os.RemoveAll(pluginPath) 21 | } 22 | _, err := git.PlainClone(pluginPath, false, &git.CloneOptions{ 23 | URL: url, 24 | RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, 25 | Depth: 1, 26 | }) 27 | 28 | if err != nil { 29 | utils.ErrorF("Error to clone Plugins repo: %v - %v", url, err) 30 | return 31 | } 32 | } 33 | 34 | // UpdateSignature update latest UI from UI repo 35 | func UpdateSignature(options libs.Options) { 36 | signPath := path.Join(options.RootFolder, "base-signatures") 37 | url := libs.SIGNREPO 38 | // in case we want to in private repo 39 | if options.Config.Repo != "" { 40 | url = options.Config.Repo 41 | } 42 | 43 | utils.GoodF("Cloning Signature from: %v", url) 44 | if utils.FolderExists(signPath) { 45 | utils.InforF("Remove: %v", signPath) 46 | os.RemoveAll(signPath) 47 | os.RemoveAll(options.PassiveFolder) 48 | os.RemoveAll(options.ResourcesFolder) 49 | os.RemoveAll(options.ThirdPartyFolder) 50 | } 51 | if options.Config.PrivateKey != "" { 52 | cmd := fmt.Sprintf("GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no -i %v' git clone --depth=1 %v %v", options.Config.PrivateKey, url, signPath) 53 | Execution(cmd) 54 | } else { 55 | var err error 56 | if options.Server.Username != "" && options.Server.Password != "" { 57 | _, err = git.PlainClone(signPath, false, &git.CloneOptions{ 58 | Auth: &http.BasicAuth{ 59 | Username: options.Config.Username, 60 | Password: options.Config.Password, 61 | }, 62 | URL: url, 63 | RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, 64 | Depth: 1, 65 | Progress: os.Stdout, 66 | }) 67 | } else { 68 | _, err = git.PlainClone(signPath, false, &git.CloneOptions{ 69 | URL: url, 70 | RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, 71 | Depth: 1, 72 | Progress: os.Stdout, 73 | }) 74 | } 75 | 76 | if err != nil { 77 | utils.ErrorF("Error to clone Signature repo: %v - %v", url, err) 78 | return 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /core/variables_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/jaeles-project/jaeles/libs" 8 | ) 9 | 10 | func TestVariables(t *testing.T) { 11 | varString := `RandomString("6")` 12 | data := RunVariables(varString) 13 | if len(data) <= 0 { 14 | t.Errorf("Error RandomString") 15 | } 16 | varString = `RandomString(3)` 17 | data = RunVariables(varString) 18 | 19 | if len(data) <= 0 { 20 | t.Errorf("Error RandomString") 21 | } 22 | varString = `Range(0,5)` 23 | data = RunVariables(varString) 24 | fmt.Println(varString, ":", data) 25 | if len(data) <= 0 { 26 | t.Errorf("Error RandomString") 27 | } 28 | varString = `File("~/suites/contents/quick.txt")` 29 | data = RunVariables(varString) 30 | fmt.Println(varString, ":", len(data)) 31 | if len(data) <= 0 { 32 | t.Errorf("Error RandomString") 33 | } 34 | varString = `InputCmd("echo 123")` 35 | data = RunVariables(varString) 36 | fmt.Println(varString, ":", data) 37 | if len(data) <= 0 { 38 | t.Errorf("Error RandomString") 39 | } 40 | } 41 | 42 | func TestMultipleVariables(t *testing.T) { 43 | var sign libs.Signature 44 | var vars []map[string]string 45 | 46 | varElement := make(map[string]string) 47 | varElement["param"] = `[1,2,3,4]` 48 | vars = append(vars, varElement) 49 | 50 | varElement2 := make(map[string]string) 51 | varElement2["dest"] = `[a,b,c]` 52 | vars = append(vars, varElement2) 53 | 54 | sign.Variables = vars 55 | 56 | realVaris := ParseVariable(sign) 57 | fmt.Println(realVaris) 58 | if len(realVaris) <= 0 { 59 | t.Errorf("Error RandomString") 60 | } 61 | } 62 | 63 | func TestEncoding(t *testing.T) { 64 | varString := `URLEncode(" das da")` 65 | data := RunVariables(varString) 66 | fmt.Println(data) 67 | if len(data) <= 0 { 68 | t.Errorf("Error RandomString") 69 | } 70 | varString = `Base64Encode("das da c")` 71 | data = RunVariables(varString) 72 | fmt.Println(data) 73 | if len(data) <= 0 { 74 | t.Errorf("Error RandomString") 75 | } 76 | 77 | varString = `Base64EncodeByLines('das\nda\nc')` 78 | data = RunVariables(varString) 79 | fmt.Println(data) 80 | if len(data) <= 0 { 81 | t.Errorf("Error RandomString") 82 | } 83 | } 84 | 85 | func TestReplicationJob(t *testing.T) { 86 | opt := libs.Options{ 87 | Concurrency: 3, 88 | Threads: 5, 89 | Verbose: true, 90 | NoDB: true, 91 | NoOutput: true, 92 | } 93 | URL := "http://httpbin.org:80" 94 | 95 | signContent := ` 96 | # info to search signature 97 | id: cred-01-01 98 | noutput: true 99 | info: 100 | name: Default Credentials 101 | risk: High 102 | 103 | ports: '8080,9000' 104 | postfixes: 'foo,bar' 105 | 106 | requests: 107 | - method: GET 108 | redirect: false 109 | url: >- 110 | {{.BaseURL}}/anything?aaaa=sample 111 | headers: 112 | - User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55 113 | detections: 114 | - >- 115 | StatusCode() == 200 116 | 117 | ` 118 | sign, err := ParseSignFromContent(signContent) 119 | if err != nil { 120 | t.Errorf("Error parsing signature") 121 | 122 | } 123 | job := libs.Job{ 124 | URL: URL, 125 | Sign: sign, 126 | } 127 | 128 | jobs := []libs.Job{job} 129 | 130 | if job.Sign.Ports != "" || job.Sign.Prefixes != "" { 131 | moreJobs, err := ReplicationJob(job.URL, job.Sign) 132 | if err == nil { 133 | jobs = append(jobs, moreJobs...) 134 | } 135 | } 136 | 137 | for _, job := range jobs { 138 | runner, err := InitRunner(job.URL, job.Sign, opt) 139 | if err != nil { 140 | t.Errorf("Error replicate") 141 | } 142 | if len(runner.Records) == 0 { 143 | t.Errorf("Error replicate") 144 | } 145 | fmt.Println("New Requests generated: ", runner.Records[0].Request.URL) 146 | 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /database/collaborator.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/jaeles-project/jaeles/libs" 9 | 10 | "github.com/Jeffail/gabs/v2" 11 | "github.com/jaeles-project/jaeles/database/models" 12 | ) 13 | 14 | // GetCollab get random collab to test 15 | func GetCollab() string { 16 | var collabs []models.Collab 17 | DB.Find(&collabs) 18 | if len(collabs) == 0 { 19 | // auto gen a new one using request bin 20 | // dnsbin := NewDNSBin() 21 | // if dnsbin != "" { 22 | // return dnsbin 23 | // } 24 | return "" 25 | } 26 | rand.Seed(time.Now().Unix()) 27 | n := rand.Int() % len(collabs) 28 | return collabs[n].InteractionString 29 | } 30 | 31 | // GetSecretbyCollab get secret by interactString 32 | func GetSecretbyCollab(InteractionString string) string { 33 | var collabs models.Collab 34 | // DB.Find(&collabs) 35 | DB.Where("interaction_string = ?", InteractionString).First(&collabs) 36 | return collabs.Secret 37 | } 38 | 39 | // CleanCollab clean all collab 40 | func CleanCollab() { 41 | var rec []models.Collab 42 | DB.Find(&rec) 43 | DB.Unscoped().Delete(&rec) 44 | } 45 | 46 | // ImportCollab import burp collab with it's secret 47 | func ImportCollab(secret string, InteractionString string) { 48 | recObj := models.Collab{ 49 | Secret: secret, 50 | InteractionString: InteractionString, 51 | } 52 | DB.Create(&recObj) 53 | } 54 | 55 | // GetOOB check oob log with interactString 56 | func GetOOB(InteractionString string) string { 57 | var oob models.OutOfBand 58 | DB.Where("interaction_string = ?", InteractionString).First(&oob) 59 | return oob.Data 60 | } 61 | 62 | // ImportOutOfBand import polling result to DB 63 | func ImportOutOfBand(data string) { 64 | jsonParsed, _ := gabs.ParseJSON([]byte(data)) 65 | clientIP := jsonParsed.Path("client").Data().(string) 66 | protocol := jsonParsed.Path("protocol").Data().(string) 67 | ts := jsonParsed.Path("time").Data().(string) 68 | rawData := fmt.Sprintf("%v", jsonParsed.Path("data")) 69 | 70 | interactionString := jsonParsed.Path("interactionString").Data().(string) 71 | secret := GetSecretbyCollab(interactionString) 72 | 73 | // interactionString 74 | recObj := models.OutOfBand{ 75 | InteractionString: interactionString, 76 | ClientIP: clientIP, 77 | Time: ts, 78 | Protocol: protocol, 79 | Data: rawData, 80 | Secret: secret, 81 | } 82 | DB.Create(&recObj) 83 | } 84 | 85 | // GetUnPollReq get request that unpoll 86 | func GetUnPollReq() []models.ReqLog { 87 | var reqLogs []models.ReqLog 88 | DB.Where("data = ?", "").Find(&reqLogs) 89 | return reqLogs 90 | } 91 | 92 | // ImportReqLog import polling result to DB 93 | func ImportReqLog(rec libs.Record, analyzeString string) { 94 | secret := GetSecretbyCollab(analyzeString) 95 | recObj := models.ReqLog{ 96 | Req: rec.Request.Beautify, 97 | Res: rec.Response.Beautify, 98 | InteractionString: analyzeString, 99 | ScanID: rec.ScanID, 100 | Secret: secret, 101 | } 102 | DB.Create(&recObj) 103 | } 104 | -------------------------------------------------------------------------------- /database/config.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-resty/resty/v2" 6 | "github.com/jaeles-project/jaeles/database/models" 7 | "strings" 8 | ) 9 | 10 | // ImportBurpCollab used to init some default config 11 | func ImportBurpCollab(burpcollab string) string { 12 | var conObj models.Configuration 13 | DB.Where(models.Configuration{Name: "BurpCollab"}).Assign(models.Configuration{Value: burpcollab}).FirstOrCreate(&conObj) 14 | ImportBurpCollabResponse(burpcollab, "") 15 | return burpcollab 16 | } 17 | 18 | // GetDefaultBurpCollab update default sign 19 | func GetDefaultBurpCollab() string { 20 | var conObj models.Configuration 21 | DB.Where("name = ?", "BurpCollab").First(&conObj) 22 | return conObj.Value 23 | } 24 | 25 | // ImportBurpCollabResponse used to init some default config 26 | func ImportBurpCollabResponse(burpcollab string, data string) string { 27 | burpcollabres := data 28 | if burpcollabres == "" { 29 | url := fmt.Sprintf("http://%v?original=true", burpcollab) 30 | resp, err := resty.New().R().Get(url) 31 | if err != nil { 32 | return "" 33 | } 34 | burpcollabres = string(resp.Body()) 35 | burpcollabres = strings.Replace(burpcollabres, "", "", -1) 36 | burpcollabres = strings.Replace(burpcollabres, "", "", -1) 37 | } 38 | 39 | var conObj models.Configuration 40 | DB.Where(models.Configuration{Name: "BurpCollabResponse"}).Assign(models.Configuration{Value: burpcollabres}).FirstOrCreate(&conObj) 41 | return burpcollabres 42 | } 43 | 44 | // GetDefaultBurpRes update default sign 45 | func GetDefaultBurpRes() string { 46 | var conObj models.Configuration 47 | DB.Where("name = ?", "BurpCollabResponse").First(&conObj) 48 | return conObj.Value 49 | } 50 | 51 | // InitConfigSign used to init some default config 52 | func InitConfigSign() { 53 | conObj := models.Configuration{ 54 | Name: "DefaultSign", 55 | Value: "*", 56 | } 57 | DB.Create(&conObj) 58 | } 59 | 60 | // GetDefaultSign update default sign 61 | func GetDefaultSign() string { 62 | var conObj models.Configuration 63 | DB.Where("name = ?", "DefaultSign").First(&conObj) 64 | return conObj.Value 65 | } 66 | 67 | // UpdateDefaultSign update default sign 68 | func UpdateDefaultSign(sign string) { 69 | var conObj models.Configuration 70 | DB.Where("name = ?", "DefaultSign").First(&conObj) 71 | conObj.Value = sign 72 | DB.Save(&conObj) 73 | } 74 | 75 | // UpdateDefaultBurpCollab update default burp collab 76 | func UpdateDefaultBurpCollab(collab string) { 77 | var conObj models.Configuration 78 | DB.Where("name = ?", "BurpCollab").First(&conObj) 79 | conObj.Value = collab 80 | DB.Save(&conObj) 81 | } 82 | -------------------------------------------------------------------------------- /database/connect.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "github.com/jaeles-project/jaeles/database/models" 5 | "github.com/jinzhu/gorm" 6 | 7 | // load driver 8 | _ "github.com/jinzhu/gorm/dialects/sqlite" 9 | ) 10 | 11 | // DB global DB variable 12 | var DB *gorm.DB 13 | 14 | // InitDB init DB connection 15 | func InitDB(DBPath string) (*gorm.DB, error) { 16 | db, err := gorm.Open("sqlite3", DBPath) 17 | // turn this on when we go live 18 | // Disable Logger, don't show any log even errors 19 | db.LogMode(false) 20 | 21 | if err == nil { 22 | DB = db 23 | db.AutoMigrate(&models.Scans{}) 24 | db.AutoMigrate(&models.Record{}) 25 | db.AutoMigrate(&models.Signature{}) 26 | db.AutoMigrate(&models.User{}) 27 | db.AutoMigrate(&models.Configuration{}) 28 | db.AutoMigrate(&models.Dummy{}) 29 | // table for Out of band stuff 30 | db.AutoMigrate(&models.Collab{}) 31 | db.AutoMigrate(&models.OutOfBand{}) 32 | db.AutoMigrate(&models.ReqLog{}) 33 | return db, err 34 | } 35 | return nil, err 36 | } 37 | -------------------------------------------------------------------------------- /database/dnsbin.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | // Use to gen bunch of DNS on dns.requestbin.net 4 | 5 | import ( 6 | "fmt" 7 | "net/url" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | "github.com/Jeffail/gabs/v2" 13 | "github.com/gorilla/websocket" 14 | ) 15 | 16 | // NewDNSBin create new dnsbin 17 | func NewDNSBin() string { 18 | var dnsbin string 19 | // var .fbbbf336914aa6bd9b58.d.requestbin.net 20 | addr := "dns.requestbin.net:8080" 21 | u := url.URL{Scheme: "ws", Host: addr, Path: "/dns"} 22 | 23 | // init a connection 24 | c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) 25 | if err != nil { 26 | return "" 27 | } 28 | defer c.Close() 29 | done := make(chan struct{}) 30 | go func() { 31 | defer close(done) 32 | for { 33 | _, message, err := c.ReadMessage() 34 | if err != nil { 35 | return 36 | } 37 | jsonParsed, err := gabs.ParseJSON([]byte(message)) 38 | if err != nil { 39 | return 40 | } 41 | // jsonParsed.Path("master") 42 | prefix := strconv.FormatInt(time.Now().Unix(), 10) 43 | token := strings.Trim(fmt.Sprintf("%v", jsonParsed.Path("master")), `"`) 44 | dnsbin = fmt.Sprintf("%v.%v.d.requestbin.net", prefix, token) 45 | return 46 | } 47 | }() 48 | 49 | err = c.WriteMessage(websocket.TextMessage, []byte(``)) 50 | if err != nil { 51 | return dnsbin 52 | } 53 | time.Sleep(time.Second) 54 | c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 55 | return dnsbin 56 | } 57 | 58 | // GetTS get current timestamp and return a string 59 | func GetTS() string { 60 | return strconv.FormatInt(time.Now().Unix(), 10) 61 | } 62 | -------------------------------------------------------------------------------- /database/models/collab.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Collab store all collab server 4 | type Collab struct { 5 | Model 6 | Secret string `gorm:"type:varchar(255);"` 7 | InteractionString string `gorm:"type:varchar(255);"` 8 | Type string `gorm:"type:varchar(255);default:'burp'"` 9 | } 10 | -------------------------------------------------------------------------------- /database/models/configuration.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Configuration used to store some config for entire tools 4 | type Configuration struct { 5 | Model 6 | Name string `gorm:"type:varchar(255);"` 7 | Value string `gorm:"type:varchar(255);"` 8 | } 9 | -------------------------------------------------------------------------------- /database/models/dummy.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Dummy just testing table 4 | type Dummy struct { 5 | Model 6 | 7 | Name string `gorm:"type:varchar(30);"` 8 | Desc string `gorm:"type:varchar(30);"` 9 | } 10 | -------------------------------------------------------------------------------- /database/models/model.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | // Model is base table 6 | type Model struct { 7 | ID uint `gorm:"primary_key" json:"id"` 8 | CreatedAt time.Time `json:"created_at,omitempty"` 9 | UpdatedAt time.Time `json:"updated_at,omitempty"` 10 | DeletedAt *time.Time `sql:"index" json:"deleted_at,omitempty"` 11 | } 12 | -------------------------------------------------------------------------------- /database/models/oob.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // OutOfBand table to store all OOB data 4 | type OutOfBand struct { 5 | Model 6 | Secret string `gorm:"type:varchar(255);"` 7 | InteractionString string `gorm:"type:varchar(255);"` 8 | Protocol string `gorm:"type:varchar(255);"` 9 | ClientIP string `gorm:"type:varchar(255);"` 10 | Time string `gorm:"type:varchar(255);"` 11 | Data string `gorm:"type:longtext;"` 12 | Type string `gorm:"type:varchar(255);default:'burp'"` 13 | } 14 | 15 | // ReqLog table to store request have OOB payload 16 | type ReqLog struct { 17 | Model 18 | Req string `gorm:"type:longtext;"` 19 | Res string `gorm:"type:longtext;"` 20 | ScanID string `gorm:"type:longtext;"` 21 | InteractionString string `gorm:"type:varchar(255);"` 22 | Secret string `gorm:"type:varchar(255);"` 23 | Data string `gorm:"type:longtext;"` 24 | Count int `gorm:"type:int;"` 25 | } 26 | -------------------------------------------------------------------------------- /database/models/record.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Record define record table in db 4 | type Record struct { 5 | Model 6 | ReqURL string `gorm:"type:longtext;"` 7 | ReqMethod string `gorm:"type:varchar(30);"` 8 | ReqBody string `gorm:"type:longtext;"` 9 | ReqRaw string `gorm:"type:longtext;"` 10 | 11 | StatusCode int `gorm:"type:int;"` 12 | ResBody string `gorm:"type:longtext;"` 13 | ResTime float64 `gorm:"type:float64;"` 14 | ResLength int `gorm:"type:int;"` 15 | ResRaw string `gorm:"type:longtext;"` 16 | Issues string `gorm:"type:varchar(100);"` 17 | Risk string `gorm:"type:varchar(100);"` 18 | ExtraOutput string `gorm:"type:longtext;"` 19 | ScanID string `gorm:"type:longtext;"` 20 | // Issues []string `gorm:"type:varchar(100);"` 21 | 22 | RawFile string `gorm:"type:longtext"` 23 | // ChechSum string `gorm:"type:varchar(30);unique_index"` 24 | } 25 | -------------------------------------------------------------------------------- /database/models/scan.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Scans store scan log 4 | type Scans struct { 5 | Model 6 | ScanID string `gorm:"type:varchar(255);"` 7 | ScanName string `gorm:"type:varchar(255);"` 8 | SignatureID string `gorm:"type:varchar(255);"` 9 | Input string `gorm:"type:longtext;default:''"` 10 | OutputDir string `gorm:"type:longtext;"` 11 | Mode string `gorm:"type:varchar(255);default:'scan'"` 12 | Level int `gorm:"type:int;default:'1'"` 13 | Source string `gorm:"type:longtext;default:''"` 14 | } 15 | -------------------------------------------------------------------------------- /database/models/signature.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Signature mapping signature to a db 4 | type Signature struct { 5 | Model 6 | SignID string `gorm:"type:varchar(100);unique_index"` 7 | Name string `gorm:"type:varchar(100);default:'single'"` 8 | Category string `gorm:"type:varchar(100);default:'general'"` 9 | Risk string `gorm:"type:varchar(100);default:'Info'"` 10 | Tech string `gorm:"type:varchar(100);default:'general'"` 11 | OS string `gorm:"type:varchar(100);default:'general'"` 12 | AsbPath string `gorm:"type:longtext;default:''"` 13 | 14 | Type string `gorm:"type:varchar(30);not null;default:'single'"` 15 | } 16 | -------------------------------------------------------------------------------- /database/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // User define user table in db 4 | type User struct { 5 | Model 6 | Username string `gorm:"type:varchar(255);"` 7 | Password string `gorm:"type:varchar(255);"` 8 | Email string `gorm:"type:varchar(255);"` 9 | Secret string `gorm:"type:varchar(255);"` 10 | Token string `gorm:"type:varchar(255);"` 11 | } 12 | -------------------------------------------------------------------------------- /database/record.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "encoding/base64" 5 | "path/filepath" 6 | 7 | "github.com/jaeles-project/jaeles/database/models" 8 | "github.com/jaeles-project/jaeles/libs" 9 | ) 10 | 11 | // CleanRecords clean all record 12 | func CleanRecords() { 13 | var rec []models.Record 14 | DB.Find(&rec) 15 | DB.Unscoped().Delete(&rec) 16 | } 17 | 18 | // ImportRecord import record to db 19 | func ImportRecord(rec libs.Record) { 20 | rawOutput, _ := filepath.Abs(rec.RawOutput) 21 | ReqRaw := base64.StdEncoding.EncodeToString([]byte(rec.Request.Beautify)) 22 | ResRaw := base64.StdEncoding.EncodeToString([]byte(rec.Response.Beautify)) 23 | extraOutput := base64.StdEncoding.EncodeToString([]byte(rec.ExtraOutput)) 24 | 25 | if rec.Sign.Info.Name == "" { 26 | rec.Sign.Info.Name = rec.Sign.ID 27 | } 28 | if rec.Sign.Info.Risk == "" { 29 | rec.Sign.Info.Risk = "Potential" 30 | } 31 | 32 | recObj := models.Record{ 33 | ReqMethod: rec.Request.Method, 34 | ReqURL: rec.Request.URL, 35 | ReqRaw: ReqRaw, 36 | ReqBody: rec.Request.Body, 37 | ResLength: rec.Response.Length, 38 | StatusCode: rec.Response.StatusCode, 39 | ResTime: rec.Response.ResponseTime, 40 | ResRaw: ResRaw, 41 | RawFile: rawOutput, 42 | ExtraOutput: extraOutput, 43 | Issues: rec.Sign.ID, 44 | Risk: rec.Sign.Info.Risk, 45 | ScanID: rec.ScanID, 46 | } 47 | DB.Create(&recObj) 48 | 49 | } 50 | -------------------------------------------------------------------------------- /database/scan.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/google/uuid" 9 | "github.com/jaeles-project/jaeles/database/models" 10 | "github.com/jaeles-project/jaeles/libs" 11 | ) 12 | 13 | // CleanScans clean all scan 14 | func CleanScans() { 15 | var scans []models.Scans 16 | DB.Find(&scans) 17 | DB.Unscoped().Delete(&scans) 18 | } 19 | 20 | // NewScan select signature to gen request 21 | func NewScan(options libs.Options, mode string, signs []string) string { 22 | if options.NoDB { 23 | return "0" 24 | } 25 | id, _ := uuid.NewUUID() 26 | rawScanID, _ := id.MarshalText() 27 | 28 | var shortSigns []string 29 | for _, signName := range signs { 30 | shortSigns = append(shortSigns, filepath.Base(signName)) 31 | } 32 | signatures := strings.Join(shortSigns[:], ",") 33 | 34 | signObj := models.Scans{ 35 | ScanID: fmt.Sprintf("%x", rawScanID), 36 | SignatureID: signatures, 37 | OutputDir: options.Output, 38 | Mode: mode, 39 | } 40 | DB.Create(&signObj) 41 | return fmt.Sprintf("%v", signObj.ScanID) 42 | } 43 | -------------------------------------------------------------------------------- /database/sign.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/utils" 6 | "gopkg.in/yaml.v2" 7 | "io/ioutil" 8 | "os" 9 | "regexp" 10 | "strings" 11 | 12 | "github.com/jaeles-project/jaeles/database/models" 13 | "github.com/jaeles-project/jaeles/libs" 14 | ) 15 | 16 | // CleanSigns clean all signature 17 | func CleanSigns() { 18 | var signs []models.Signature 19 | DB.Find(&signs) 20 | DB.Unscoped().Delete(&signs) 21 | } 22 | 23 | // SelectSign select signature to gen request 24 | func SelectSign(signName string) []string { 25 | var signs []models.Signature 26 | DB.Find(&signs) 27 | 28 | if signName == "*" || signName == "" { 29 | fmt.Fprintf(os.Stderr, "[Warning] You literally just select ALL signatures. I hope you know what are you doing.\n") 30 | } 31 | // DB.Find(&signs) 32 | //} else { 33 | // DB.Where("sign_id LIKE ? OR name LIKE ?", fmt.Sprintf("%%%v%%", signName), fmt.Sprintf("%%%v%%", signName)).Find(&signs) 34 | //} 35 | 36 | var selectedSigns []string 37 | for _, sign := range signs { 38 | if signName == "*" || signName == "" { 39 | selectedSigns = append(selectedSigns, sign.AsbPath) 40 | continue 41 | } 42 | // grep info 43 | info := fmt.Sprintf("%v|%v|%v|tech:%v", sign.SignID, strings.ToLower(sign.Name), sign.AsbPath, sign.Tech) 44 | if strings.Contains(strings.ToLower(info), strings.ToLower(signName)) { 45 | selectedSigns = append(selectedSigns, sign.AsbPath) 46 | continue 47 | } 48 | r, err := regexp.Compile(signName) 49 | if err == nil { 50 | if r.MatchString(info) { 51 | selectedSigns = append(selectedSigns, sign.AsbPath) 52 | } 53 | } 54 | } 55 | return selectedSigns 56 | } 57 | 58 | // ImportSign import signature to DB 59 | func ImportSign(signPath string) error { 60 | sign, err := ParseSignature(signPath) 61 | if err != nil { 62 | return fmt.Errorf("error parse sign: %v", err) 63 | } 64 | 65 | if sign.Info.Category == "" { 66 | if strings.Contains(sign.ID, "-") { 67 | sign.Info.Category = strings.Split(sign.ID, "-")[0] 68 | } else { 69 | sign.Info.Category = sign.ID 70 | } 71 | } 72 | if sign.Info.Name == "" { 73 | sign.Info.Name = sign.ID 74 | } 75 | 76 | signObj := models.Signature{ 77 | Name: sign.Info.Name, 78 | Category: sign.Info.Category, 79 | Risk: sign.Info.Risk, 80 | Tech: sign.Info.Tech, 81 | OS: sign.Info.OS, 82 | SignID: sign.ID, 83 | AsbPath: signPath, 84 | Type: sign.Type, 85 | } 86 | DB.Create(&signObj) 87 | return nil 88 | } 89 | 90 | // ParseSign parsing YAML signature file 91 | func ParseSignature(signFile string) (sign libs.Signature, err error) { 92 | yamlFile, err := ioutil.ReadFile(signFile) 93 | if err != nil { 94 | utils.ErrorF("yamlFile.Get err #%v - %v", err, signFile) 95 | } 96 | err = yaml.Unmarshal(yamlFile, &sign) 97 | if err != nil { 98 | utils.ErrorF("Error: %v - %v", err, signFile) 99 | } 100 | // set some default value 101 | if sign.Info.Category == "" { 102 | if strings.Contains(sign.ID, "-") { 103 | sign.Info.Category = strings.Split(sign.ID, "-")[0] 104 | } else { 105 | sign.Info.Category = sign.ID 106 | } 107 | } 108 | if sign.Info.Name == "" { 109 | sign.Info.Name = sign.ID 110 | } 111 | if sign.Info.Risk == "" { 112 | sign.Info.Risk = "Potential" 113 | } 114 | return sign, err 115 | } 116 | -------------------------------------------------------------------------------- /database/user.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "crypto/sha1" 5 | "fmt" 6 | "github.com/jaeles-project/jaeles/utils" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/jaeles-project/jaeles/database/models" 11 | ) 12 | 13 | // SelectUser get password of one user to compare 14 | func SelectUser(username string) string { 15 | var user models.User 16 | DB.Where("username = ?", username).First(&user) 17 | if user.Username == username { 18 | return user.Password 19 | } 20 | return "" 21 | } 22 | 23 | // CreateUser used to create new user 24 | func CreateUser(username string, password string) { 25 | oldpass := SelectUser(username) 26 | if oldpass != "" { 27 | UpdateUser(username, password) 28 | } else { 29 | rawToken := fmt.Sprintf("%v-%v", username, strconv.FormatInt(time.Now().Unix(), 10)) 30 | rawSecret := fmt.Sprintf("%v-%v-%v", username, password, strconv.FormatInt(time.Now().Unix(), 10)) 31 | 32 | userObj := models.User{ 33 | Username: username, 34 | Email: username, 35 | Password: GenHash(password), 36 | Secret: GenHash(rawSecret), 37 | Token: GenHash(rawToken), 38 | } 39 | utils.GoodF("Create new credentials %v:%v", username, password) 40 | 41 | DB.Create(&userObj) 42 | } 43 | } 44 | 45 | // UpdateUser update default sign 46 | func UpdateUser(username string, password string) { 47 | var userObj models.User 48 | DB.Where("username = ?", username).First(&userObj) 49 | userObj.Password = GenHash(password) 50 | DB.Save(&userObj) 51 | } 52 | 53 | // GenHash generate SHA1 hash 54 | func GenHash(text string) string { 55 | h := sha1.New() 56 | h.Write([]byte(text)) 57 | hashed := h.Sum(nil) 58 | return fmt.Sprintf("%x", hashed) 59 | } 60 | -------------------------------------------------------------------------------- /dns/query.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "github.com/jaeles-project/jaeles/libs" 5 | "github.com/jaeles-project/jaeles/utils" 6 | "github.com/lixiangzhong/dnsutil" 7 | "github.com/thoas/go-funk" 8 | ) 9 | 10 | var recordMap = map[string]uint16{ 11 | "A": 1, 12 | "AAAA": 28, 13 | "NS": 2, 14 | "CNAME": 5, 15 | "SOA": 6, 16 | "PTR": 12, 17 | "MX": 15, 18 | "TXT": 16, 19 | } 20 | 21 | var CommonResolvers = []string{ 22 | "1.1.1.1", // Cloudflare 23 | "8.8.8.8", // Google 24 | "8.8.4.4", // Google 25 | } 26 | 27 | func QueryDNS(dnsRecord *libs.Dns, options libs.Options) { 28 | resolver := options.Resolver 29 | if resolver == "" { 30 | index := funk.RandomInt(0, len(CommonResolvers)) 31 | resolver = CommonResolvers[index] 32 | } 33 | domain := dnsRecord.Domain 34 | queryType := dnsRecord.RecordType 35 | dnsRecord.Resolver = resolver 36 | 37 | var dig dnsutil.Dig 38 | dig.Retry = options.Retry 39 | dig.SetDNS(dnsRecord.Resolver) 40 | utils.InforF("[resolved] %v -- %v", domain, queryType) 41 | 42 | if queryType == "ANY" || queryType == "" { 43 | for k, v := range recordMap { 44 | var dnsResult libs.DnsResult 45 | msg, err := dig.GetMsg(v, domain) 46 | if err != nil { 47 | utils.DebugF("err to resolved: %v -- %v", domain, err) 48 | return 49 | } 50 | dnsResult.Data = msg.String() 51 | //utils.DebugF(dnsResult.Data) 52 | dnsResult.RecordType = k 53 | dnsRecord.Results = append(dnsRecord.Results, dnsResult) 54 | } 55 | } else { 56 | var dnsResult libs.DnsResult 57 | msg, err := dig.GetMsg(recordMap[queryType], domain) 58 | if err != nil { 59 | utils.DebugF("err to resolved: %v -- %v", domain, err) 60 | return 61 | } 62 | dnsResult.Data = msg.String() 63 | //utils.DebugF(dnsResult.Data) 64 | dnsResult.RecordType = queryType 65 | dnsRecord.Results = append(dnsRecord.Results, dnsResult) 66 | } 67 | 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /dns/query_test.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "testing" 7 | ) 8 | 9 | func TestQueryDNS(t *testing.T) { 10 | opt := libs.Options{ 11 | Concurrency: 3, 12 | Threads: 5, 13 | Verbose: true, 14 | NoDB: true, 15 | NoOutput: true, 16 | } 17 | 18 | dnsRcord := libs.Dns{ 19 | Domain: "github.com", 20 | } 21 | QueryDNS(&dnsRcord, opt) 22 | fmt.Println(dnsRcord) 23 | } 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jaeles-project/jaeles 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/Jeffail/gabs/v2 v2.7.0 7 | github.com/Masterminds/sprig/v3 v3.2.3 8 | github.com/PuerkitoBio/goquery v1.8.1 9 | github.com/appleboy/gin-jwt/v2 v2.9.1 10 | github.com/chromedp/cdproto v0.0.0-20230816033919-17ee49f3eb4f 11 | github.com/chromedp/chromedp v0.9.2 12 | github.com/davecgh/go-spew v1.1.1 13 | github.com/fatih/color v1.15.0 14 | github.com/gin-contrib/cors v1.4.0 15 | github.com/gin-contrib/static v0.0.1 16 | github.com/gin-gonic/gin v1.9.1 17 | github.com/go-resty/resty/v2 v2.7.0 18 | github.com/google/uuid v1.3.0 19 | github.com/gorilla/websocket v1.5.0 20 | github.com/jinzhu/copier v0.4.0 21 | github.com/jinzhu/gorm v1.9.16 22 | github.com/json-iterator/go v1.1.12 23 | github.com/lixiangzhong/dnsutil v1.4.0 24 | github.com/logrusorgru/aurora/v3 v3.0.0 25 | github.com/mitchellh/go-homedir v1.1.0 26 | github.com/panjf2000/ants v1.3.0 27 | github.com/robertkrimen/otto v0.2.1 28 | github.com/sirupsen/logrus v1.9.3 29 | github.com/spf13/cast v1.5.1 30 | github.com/spf13/cobra v1.7.0 31 | github.com/spf13/viper v1.16.0 32 | github.com/thoas/go-funk v0.9.3 33 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 34 | gopkg.in/src-d/go-git.v4 v4.13.1 35 | gopkg.in/yaml.v2 v2.4.0 36 | ) 37 | 38 | require ( 39 | github.com/Masterminds/goutils v1.1.1 // indirect 40 | github.com/Masterminds/semver/v3 v3.2.0 // indirect 41 | github.com/andybalholm/cascadia v1.3.1 // indirect 42 | github.com/bytedance/sonic v1.9.1 // indirect 43 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 44 | github.com/chromedp/sysutil v1.0.0 // indirect 45 | github.com/emirpasic/gods v1.12.0 // indirect 46 | github.com/fsnotify/fsnotify v1.6.0 // indirect 47 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 48 | github.com/gin-contrib/sse v0.1.0 // indirect 49 | github.com/go-playground/locales v0.14.1 // indirect 50 | github.com/go-playground/universal-translator v0.18.1 // indirect 51 | github.com/go-playground/validator/v10 v10.14.0 // indirect 52 | github.com/gobwas/httphead v0.1.0 // indirect 53 | github.com/gobwas/pool v0.2.1 // indirect 54 | github.com/gobwas/ws v1.2.1 // indirect 55 | github.com/goccy/go-json v0.10.2 // indirect 56 | github.com/golang-jwt/jwt/v4 v4.4.3 // indirect 57 | github.com/hashicorp/hcl v1.0.0 // indirect 58 | github.com/huandu/xstrings v1.3.3 // indirect 59 | github.com/imdario/mergo v0.3.11 // indirect 60 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 61 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 62 | github.com/jinzhu/inflection v1.0.0 // indirect 63 | github.com/josharian/intern v1.0.0 // indirect 64 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect 65 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 66 | github.com/leodido/go-urn v1.2.4 // indirect 67 | github.com/magiconair/properties v1.8.7 // indirect 68 | github.com/mailru/easyjson v0.7.7 // indirect 69 | github.com/mattn/go-colorable v0.1.13 // indirect 70 | github.com/mattn/go-isatty v0.0.19 // indirect 71 | github.com/mattn/go-sqlite3 v1.14.0 // indirect 72 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 73 | github.com/miekg/dns v1.1.40 // indirect 74 | github.com/mitchellh/copystructure v1.0.0 // indirect 75 | github.com/mitchellh/mapstructure v1.5.0 // indirect 76 | github.com/mitchellh/reflectwalk v1.0.0 // indirect 77 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 78 | github.com/modern-go/reflect2 v1.0.2 // indirect 79 | github.com/onsi/ginkgo v1.16.5 // indirect 80 | github.com/onsi/gomega v1.27.10 // indirect 81 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 82 | github.com/sergi/go-diff v1.0.0 // indirect 83 | github.com/shopspring/decimal v1.2.0 // indirect 84 | github.com/spf13/afero v1.9.5 // indirect 85 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 86 | github.com/spf13/pflag v1.0.5 // indirect 87 | github.com/src-d/gcfg v1.4.0 // indirect 88 | github.com/subosito/gotenv v1.4.2 // indirect 89 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 90 | github.com/ugorji/go/codec v1.2.11 // indirect 91 | github.com/xanzy/ssh-agent v0.2.1 // indirect 92 | golang.org/x/arch v0.3.0 // indirect 93 | golang.org/x/crypto v0.11.0 // indirect 94 | golang.org/x/net v0.12.0 // indirect 95 | golang.org/x/sys v0.10.0 // indirect 96 | golang.org/x/term v0.10.0 // indirect 97 | golang.org/x/text v0.11.0 // indirect 98 | google.golang.org/protobuf v1.30.0 // indirect 99 | gopkg.in/ini.v1 v1.67.0 // indirect 100 | gopkg.in/sourcemap.v1 v1.0.5 // indirect 101 | gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect 102 | gopkg.in/warnings.v0 v0.1.2 // indirect 103 | gopkg.in/yaml.v3 v3.0.1 // indirect 104 | ) 105 | -------------------------------------------------------------------------------- /libs/banner.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | ) 6 | 7 | // Banner print ascii banner 8 | func Banner() string { 9 | version := color.HiWhiteString(VERSION) 10 | author := color.MagentaString(AUTHOR) 11 | b := color.GreenString(``) 12 | 13 | b += "\n" + color.HiGreenString(``) 14 | b += "\n" + color.GreenString(` ,+izzir, `) 15 | b += "\n" + color.GreenString(` '*K@@Q8&Q@@8t' `) 16 | b += "\n" + color.GreenString(` !Q@N;''`) + color.HiWhiteString(`,~~;`) + color.GreenString(`\D@@t' `) 17 | b += "\n" + color.GreenString(` ,Q@q. `) + color.HiWhiteString(`'~~~~~~;`) + color.GreenString(`5@@L `) 18 | b += "\n" + color.GreenString(` L@@+ `) + color.HiWhiteString(`'~~~~~~~`) + color.GreenString(`^Q@X `) 19 | b += "\n" + color.GreenString(` ^@@z `) + color.HiWhiteString(`'~~~~~~~`) + color.GreenString(`|Q@y `) 20 | b += "\n" + color.GreenString(` 'Z@@7 `) + color.HiWhiteString(`'~~~~;`) + color.GreenString(`TQ@N, `) 21 | b += "\n" + color.GreenString(` ^%@QhJ7fmDQ@Q7' ~}DQ@@@Qqv, `) 22 | b += "\n" + color.GreenString(` ~jdQ@@Qdjr' ,U@@qv=|tm#@QY `) 23 | b += "\n" + color.GreenString(` *@@= D@&;`) + color.HiWhiteString(` ,~~~`) + color.GreenString(`;f@@^ `) 24 | b += "\n" + color.GreenString(` <@@+ .@@L`) + color.HiWhiteString(` '~~~~~~`) + color.GreenString(`K@P `) 25 | b += "\n" + color.GreenString(` ,~' `) 48 | 49 | b += "\n\n" + color.GreenString(` `) 50 | 51 | b += "\n" + color.CyanString(` 🚀 Jaeles %v`, version) + color.CyanString(` by %v 🚀`, author) 52 | b += "\n\n" + color.HiWhiteString(` The Swiss Army knife for automated Web Application Testing `) 53 | b += "\n\n" + color.HiGreenString(` ¯\_(ツ)_/¯`) + "\n\n" 54 | color.Unset() 55 | return b 56 | } 57 | -------------------------------------------------------------------------------- /libs/dns.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | // Dns result for DNS 4 | type Dns struct { 5 | Results []DnsResult 6 | Resolver string 7 | Domain string 8 | 9 | // for DNS part 10 | RecordType string `yaml:"record"` // ANY, A, CNAME 11 | 12 | Conditions []string 13 | Middlewares []string 14 | Conclusions []string 15 | Detections []string 16 | 17 | // run when detection is true 18 | PostRun []string 19 | } 20 | 21 | type DnsResult struct { 22 | RecordType string 23 | Data string 24 | } 25 | -------------------------------------------------------------------------------- /libs/http.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | // Record all information about request 4 | type Record struct { 5 | Opt Options 6 | DonePassive bool 7 | SelectPassive string 8 | OriginReq Request 9 | OriginRes Response 10 | Origins []Origin 11 | Request Request 12 | Response Response 13 | Sign Signature 14 | RawOutput string 15 | ExtraOutput string 16 | // for detection 17 | IsVulnerable bool 18 | DetectString string 19 | DetectResult string 20 | ScanID string 21 | } 22 | 23 | // Origin contain map of origins 24 | type Origin struct { 25 | Label string 26 | ORequest Request `yaml:"origin_req"` 27 | OResponse Response `yaml:"origin_res"` 28 | } 29 | 30 | // Request all information about request 31 | type Request struct { 32 | RawInput string 33 | Engine string 34 | Timeout int 35 | Repeat int 36 | Scheme string 37 | Host string 38 | Port string 39 | Path string 40 | URL string 41 | Proto string 42 | Proxy string 43 | Method string 44 | Payload string 45 | Redirect bool 46 | UseTemplateHeader bool 47 | EnableChecksum bool 48 | Headers []map[string]string 49 | Values []map[string]string 50 | Body string 51 | Beautify string 52 | MiddlewareOutput string 53 | Raw string 54 | Res string 55 | Conditions []string 56 | Middlewares []string 57 | Conclusions []string 58 | Detections []string 59 | 60 | // run when detection is true 61 | PostRun []string 62 | 63 | // for fuzzing 64 | Generators []string 65 | Encoding string 66 | Target map[string]string 67 | } 68 | 69 | // Response all information about response 70 | type Response struct { 71 | HasPopUp bool 72 | StatusCode int 73 | Status string 74 | Checksum string 75 | 76 | Headers []map[string]string 77 | Body string 78 | ResponseTime float64 79 | Length int 80 | Beautify string 81 | } 82 | -------------------------------------------------------------------------------- /libs/options.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | // Options global options 4 | type Options struct { 5 | RootFolder string 6 | SignFolder string 7 | PassiveFolder string 8 | ResourcesFolder string 9 | ThirdPartyFolder string 10 | ScanID string 11 | ConfigFile string 12 | FoundCmd string 13 | QuietFormat string 14 | PassiveOutput string 15 | PassiveSummary string 16 | Output string 17 | SummaryOutput string 18 | SummaryVuln string 19 | LogFile string 20 | Proxy string 21 | Selectors string 22 | InlineDetection string 23 | Params []string 24 | Headers []string 25 | Signs []string 26 | Excludes []string 27 | SelectedSigns []string 28 | ParsedSelectedSigns []Signature 29 | ParallelSigns []string 30 | SelectedPassive string 31 | GlobalVar map[string]string 32 | 33 | Level int 34 | Concurrency int 35 | Threads int 36 | Delay int 37 | Timeout int 38 | Refresh int 39 | Retry int 40 | SaveRaw bool 41 | LocalAnalyze bool 42 | JsonOutput bool 43 | VerboseSummary bool 44 | Quiet bool 45 | FullHelp bool 46 | Verbose bool 47 | Version bool 48 | Debug bool 49 | NoDB bool 50 | NoBackGround bool 51 | NoOutput bool 52 | EnableFormatInput bool 53 | EnablePassive bool 54 | DisableParallel bool 55 | 56 | // only enable when doing sensitive mode 57 | EnableFiltering bool 58 | // for DNS 59 | Resolver string 60 | 61 | // Chunk Options 62 | ChunkDir string 63 | ChunkRun bool 64 | ChunkThreads int 65 | ChunkSize int 66 | ChunkLimit int 67 | 68 | Mics Mics 69 | Scan Scan 70 | Server Server 71 | Report Report 72 | Config Config 73 | } 74 | 75 | // Scan options for api server 76 | type Scan struct { 77 | RawRequest string 78 | EnableGenReport bool 79 | } 80 | 81 | // Mics some shortcut options 82 | type Mics struct { 83 | FullHelp bool 84 | AlwaysTrue bool 85 | BaseRoot bool 86 | BurpProxy bool 87 | DisableReplicate bool 88 | } 89 | 90 | // Report options for api server 91 | type Report struct { 92 | VerboseReport bool 93 | ReportName string 94 | TemplateFile string 95 | VTemplateFile string 96 | OutputPath string 97 | Title string 98 | } 99 | 100 | // Server options for api server 101 | type Server struct { 102 | NoAuth bool 103 | DBPath string 104 | Bind string 105 | JWTSecret string 106 | Cors string 107 | DefaultSign string 108 | SecretCollab string 109 | Username string 110 | Password string 111 | Key string 112 | } 113 | 114 | // Config options for api server 115 | type Config struct { 116 | Forced bool 117 | SkipMics bool 118 | Username string 119 | Password string 120 | Repo string 121 | PrivateKey string 122 | } 123 | 124 | // Job define job for running routine 125 | type Job struct { 126 | URL string 127 | Checksums []string 128 | Sign Signature 129 | // the base response 130 | Response Response 131 | } 132 | 133 | // VulnData vulnerable Data 134 | type VulnData struct { 135 | ScanID string 136 | SignID string 137 | SignName string 138 | URL string 139 | Risk string 140 | DetectionString string 141 | DetectResult string 142 | Confidence string 143 | Req string 144 | Res string 145 | // little information 146 | StatusCode string 147 | ContentLength string 148 | OutputFile string 149 | SignatureFile string 150 | } 151 | -------------------------------------------------------------------------------- /libs/passive.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | // Passive struct for passive detection 4 | type Passive struct { 5 | Name string 6 | Desc string 7 | Risk string 8 | Confidence string 9 | Level int 10 | Rules []Rule 11 | } 12 | 13 | // Rule rule for run detections 14 | type Rule struct { 15 | ID string 16 | Risk string 17 | Confidence string 18 | Reason string 19 | // raw regex to avoid the pain of escape char 20 | Regex string 21 | Detections []string 22 | } 23 | -------------------------------------------------------------------------------- /libs/signature.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | // Signature base signature struct 4 | type Signature struct { 5 | ID string 6 | RawPath string 7 | Type string 8 | Level int 9 | 10 | // Enable filtering mode 11 | Filter bool 12 | OverrideFilerPaths bool 13 | FilteringPaths []string `yaml:"fpaths"` 14 | Checksums []string 15 | // local analyze 16 | Local bool 17 | Response Response 18 | 19 | // some mics options 20 | Threads int 21 | Passive bool 22 | Parallel bool 23 | Single bool 24 | Serial bool 25 | BasePath bool 26 | CleanSlash bool 27 | // Detect once 28 | Noutput bool 29 | Donce bool 30 | StopOnSucces bool 31 | 32 | // Default variables for gen more inputs 33 | Replicate struct { 34 | Ports string 35 | Prefixes string 36 | } 37 | 38 | // conditions to check before sending the whole requests 39 | CRequests []Request 40 | COutput bool `yaml:"coutput"` // store output for check request too 41 | Match string // any, all 42 | 43 | Info struct { 44 | Name string 45 | Author string 46 | Risk string 47 | Confidence string 48 | Category string 49 | Tech string 50 | OS string 51 | } 52 | 53 | Origin Request 54 | Origins []Origin 55 | Requests []Request 56 | RawRequest string 57 | Payloads []string 58 | Params []map[string]string 59 | Variables []map[string]string 60 | Target map[string]string 61 | 62 | // for dns part only 63 | Dns []Dns 64 | 65 | // similar to passive but only applied in local check 66 | Rules []Rule 67 | 68 | // routines 69 | Routines []Routine 70 | } 71 | 72 | // Routine struct 73 | type Routine struct { 74 | Signs []map[string]string 75 | Names []string 76 | Passed bool 77 | 78 | Logics []struct { 79 | Level int 80 | Expression string `yaml:"expr"` 81 | Invokes []string `yaml:"invokes"` 82 | } `yaml:"logics"` 83 | } 84 | -------------------------------------------------------------------------------- /libs/version.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | const ( 4 | // VERSION current Jaeles version 5 | VERSION = "beta v0.17.1" 6 | // AUTHOR author of this 7 | AUTHOR = "@j3ssiejjj" 8 | // DOCS link to official documentation 9 | DOCS = "https://jaeles-project.github.io/" 10 | // SIGNREPO default repo to get signature 11 | SIGNREPO = "https://github.com/jaeles-project/jaeles-signatures" 12 | // UIREPO default repo to get UI 13 | UIREPO = "https://github.com/jaeles-project/jaeles-plugins" 14 | // REPORT default report template file 15 | REPORT = "https://raw.githubusercontent.com/jaeles-project/jaeles-plugins/master/report/index.html" 16 | // VREPORT verbose report template file 17 | VREPORT = "https://raw.githubusercontent.com/jaeles-project/jaeles-plugins/master/report/verbose.html" 18 | ) 19 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/jaeles-project/jaeles/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /sender/checksum.go: -------------------------------------------------------------------------------- 1 | package sender 2 | 3 | import ( 4 | "github.com/PuerkitoBio/goquery" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "github.com/jaeles-project/jaeles/utils" 7 | "github.com/spf13/cast" 8 | "regexp" 9 | "sort" 10 | "strings" 11 | ) 12 | 13 | func GenCheckSum(res *libs.Response) string { 14 | var checksum, scriptStructure string 15 | var domStructure, cssStructure, headerKeys, cookies []string 16 | 17 | contentLines := len(strings.Split(res.Body, "\n")) 18 | contentWords := len(strings.Split(res.Body, " ")) 19 | 20 | // Verify the content type 21 | contentType := "text/html" 22 | title := "Blank-Title" 23 | 24 | // Header keys 25 | for _, header := range res.Headers { 26 | for k, v := range header { 27 | key := strings.ToLower(k) 28 | headerKeys = append(headerKeys, key) 29 | if strings.Contains(key, "content-type") { 30 | contentType = v 31 | } 32 | 33 | if strings.Contains(key, "set-cookie") { 34 | cookies = append(cookies, v) 35 | } 36 | 37 | } 38 | } 39 | sort.Strings(headerKeys) 40 | // Set-Cookie keys 41 | var cookieKeys []string 42 | if len(cookies) > 0 { 43 | for _, cookie := range cookies { 44 | if strings.Contains(cookie, "=") { 45 | cookieKey := strings.Split(cookie, "=")[0] 46 | cookieKeys = append(cookieKeys, cookieKey) 47 | } 48 | } 49 | } 50 | sort.Strings(cookieKeys) 51 | 52 | if strings.Contains(contentType, "html") { 53 | doc, _ := goquery.NewDocumentFromReader(strings.NewReader(res.Body)) 54 | domStructure, cssStructure = GetDomCssList(doc) 55 | scriptStructure = GetScriptSrc(doc) 56 | title = GetTitle(doc) 57 | } 58 | 59 | format := []string{ 60 | title, 61 | res.Status, 62 | contentType, 63 | cast.ToString(contentLines), 64 | cast.ToString(contentWords), 65 | strings.Join(domStructure, ","), 66 | strings.Join(cssStructure, ","), 67 | scriptStructure, 68 | strings.Join(headerKeys, ","), 69 | strings.Join(cookieKeys, ","), 70 | } 71 | 72 | checksum = utils.GenHash(strings.Join(format, ";;")) 73 | res.Checksum = checksum 74 | return checksum 75 | } 76 | 77 | // GetTitle get title of response 78 | func GetTitle(doc *goquery.Document) string { 79 | var title string 80 | doc.Find("title").Each(func(i int, s *goquery.Selection) { 81 | title = strings.TrimSpace(s.Text()) 82 | }) 83 | if title == "" { 84 | title = "Blank Title" 85 | } 86 | 87 | // clean title if if have new line here 88 | if strings.Contains(title, "\n") { 89 | title = regexp.MustCompile(`[\t\r\n]+`).ReplaceAllString(strings.TrimSpace(title), "\n") 90 | } 91 | return title 92 | } 93 | 94 | // GetScriptSrc calculate Hash based on src in scripts 95 | func GetScriptSrc(doc *goquery.Document) string { 96 | var result []string 97 | doc.Find("*").Each(func(i int, s *goquery.Selection) { 98 | tag := goquery.NodeName(s) 99 | result = append(result, tag) 100 | if tag == "script" { 101 | src, _ := s.Attr("src") 102 | if src != "" { 103 | result = append(result, src) 104 | } 105 | } 106 | }) 107 | sort.Strings(result) 108 | return strings.Join(result, "-") 109 | } 110 | 111 | func GetDomCssList(doc *goquery.Document) ([]string, []string) { 112 | var queue []*goquery.Selection 113 | var domRes []string 114 | var cssRes []string 115 | queue = append(queue, doc.Selection) 116 | for len(queue) > 0 { 117 | curSel := queue[0] 118 | queue = queue[1:] 119 | if len(curSel.Nodes) == 0 { 120 | continue 121 | } 122 | 123 | for _, node := range curSel.Nodes { 124 | for _, item := range node.Attr { 125 | key := strings.ToLower(item.Key) 126 | if key == "class" || key == "style" { 127 | cssRes = append(cssRes, item.Val) 128 | } 129 | } 130 | } 131 | 132 | curSel.Contents().Each(func(i int, s *goquery.Selection) { 133 | nName := goquery.NodeName(s) 134 | if nName == "#text" { 135 | return 136 | } 137 | domRes = append(domRes, nName) 138 | }) 139 | queue = append(queue, curSel.Children()) 140 | } 141 | return domRes[1:], cssRes 142 | } 143 | -------------------------------------------------------------------------------- /sender/chrome.go: -------------------------------------------------------------------------------- 1 | package sender 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/jaeles-project/jaeles/utils" 7 | "log" 8 | "time" 9 | 10 | "github.com/chromedp/cdproto/dom" 11 | "github.com/chromedp/cdproto/network" 12 | "github.com/chromedp/cdproto/page" 13 | "github.com/chromedp/chromedp" 14 | "github.com/jaeles-project/jaeles/libs" 15 | ) 16 | 17 | // SendWithChrome send request with real browser 18 | func SendWithChrome(options libs.Options, req libs.Request) (libs.Response, error) { 19 | // parsing some stuff 20 | url := req.URL 21 | // @TODO: parse more request component later 22 | // method := req.Method 23 | // body := req.Body 24 | // headers := GetHeaders(req) 25 | if options.Verbose { 26 | fmt.Printf("[Sent][Chrome] %v \n", url) 27 | } 28 | var res libs.Response 29 | 30 | isHeadless := true 31 | if options.Debug { 32 | isHeadless = false 33 | } 34 | // prepare the chrome options 35 | opts := append(chromedp.DefaultExecAllocatorOptions[:], 36 | chromedp.Flag("headless", isHeadless), 37 | chromedp.Flag("ignore-certificate-errors", true), 38 | chromedp.Flag("disable-gpu", true), 39 | chromedp.Flag("enable-automation", true), 40 | chromedp.Flag("disable-extensions", false), 41 | chromedp.Flag("disable-setuid-sandbox", true), 42 | chromedp.Flag("no-first-run", true), 43 | chromedp.Flag("no-default-browser-check", true), 44 | chromedp.Flag("single-process", true), 45 | chromedp.Flag("no-zygote", true), 46 | chromedp.Flag("no-sandbox", true), 47 | ) 48 | 49 | // proxy chrome headless 50 | if options.Proxy != "" { 51 | opts = append(opts, chromedp.ProxyServer(options.Proxy)) 52 | } 53 | 54 | allocCtx, bcancel := chromedp.NewExecAllocator(context.Background(), opts...) 55 | allocCtx, bcancel = context.WithTimeout(allocCtx, time.Duration(options.Timeout*2)*time.Second) 56 | defer bcancel() 57 | chromeContext, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf)) 58 | defer cancel() 59 | 60 | // catch the pop up 61 | chromedp.ListenTarget(chromeContext, func(event interface{}) { 62 | if _, ok := event.(*page.EventJavascriptDialogOpening); ok { 63 | // fmt.Println("closing alert:", ev.Message) 64 | utils.DebugF("Detecting Pop-up: %v", url) 65 | res.HasPopUp = true 66 | go func() { 67 | if err := chromedp.Run(chromeContext, 68 | page.HandleJavaScriptDialog(true), 69 | ); err != nil { 70 | res.HasPopUp = false 71 | } 72 | }() 73 | } 74 | }) 75 | timeStart := time.Now() 76 | // waiting time for the page to load 77 | waiting := time.Duration(1) 78 | if req.Timeout != 0 { 79 | waiting = time.Duration(req.Timeout) 80 | } 81 | // start Chrome and run given tasks 82 | err := chromedp.Run( 83 | chromeContext, 84 | chromeTask(chromeContext, url, 85 | // @TODO: add header here 86 | map[string]interface{}{}, 87 | &res), 88 | // wait for the page to load 89 | chromedp.Sleep(waiting*time.Second), 90 | chromedp.ActionFunc(func(ctx context.Context) error { 91 | node, err := dom.GetDocument().Do(ctx) 92 | if err != nil { 93 | return err 94 | } 95 | res.Body, err = dom.GetOuterHTML().WithNodeID(node.NodeID).Do(ctx) 96 | return err 97 | }), 98 | ) 99 | res.ResponseTime = time.Since(timeStart).Seconds() 100 | if err != nil { 101 | utils.ErrorF("%v", err) 102 | return res, err 103 | } 104 | 105 | res.Beautify = fmt.Sprintf("%v\n%v\n", res.StatusCode, res.Body) 106 | return res, err 107 | } 108 | 109 | // chrome debug protocol tasks to run 110 | func chromeTask(chromeContext context.Context, url string, requestHeaders map[string]interface{}, res *libs.Response) chromedp.Tasks { 111 | // setup a listener for events 112 | chromedp.ListenTarget(chromeContext, func(event interface{}) { 113 | // get which type of event it is 114 | switch msg := event.(type) { 115 | // just before request sent 116 | case *network.EventRequestWillBeSent: 117 | request := msg.Request 118 | // see if we have been redirected 119 | // if so, change the URL that we are tracking 120 | if msg.RedirectResponse != nil { 121 | url = request.URL 122 | } 123 | 124 | // once we have the full response 125 | case *network.EventResponseReceived: 126 | response := msg.Response 127 | // is the request we want the status/headers on? 128 | if response.URL == url { 129 | res.StatusCode = int(response.Status) 130 | // fmt.Printf(" url: %s\n", response.URL) 131 | // fmt.Printf(" status code: %d\n", res.StatusCode) 132 | for k, v := range response.Headers { 133 | header := make(map[string]string) 134 | // fmt.Println(k, v) 135 | header[k] = v.(string) 136 | res.Headers = append(res.Headers, header) 137 | } 138 | } 139 | } 140 | 141 | }) 142 | 143 | return chromedp.Tasks{ 144 | network.Enable(), 145 | network.SetExtraHTTPHeaders(network.Headers(requestHeaders)), 146 | chromedp.Navigate(url), 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /sender/sender.go: -------------------------------------------------------------------------------- 1 | package sender 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "github.com/jaeles-project/jaeles/utils" 8 | "io/ioutil" 9 | "math/rand" 10 | "net/http" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | "github.com/go-resty/resty/v2" 16 | "github.com/jaeles-project/jaeles/libs" 17 | "github.com/sirupsen/logrus" 18 | ) 19 | 20 | // JustSend just sending request 21 | func JustSend(options libs.Options, req libs.Request) (res libs.Response, err error) { 22 | if req.Method == "" { 23 | req.Method = "GET" 24 | } 25 | method := req.Method 26 | url := req.URL 27 | body := req.Body 28 | headers := GetHeaders(req) 29 | proxy := options.Proxy 30 | 31 | // override proxy 32 | if req.Proxy != "" && req.Proxy != "blank" { 33 | proxy = req.Proxy 34 | } 35 | 36 | timeout := options.Timeout 37 | if req.Timeout > 0 { 38 | timeout = req.Timeout 39 | } 40 | 41 | disableCompress := false 42 | if len(headers) > 0 && strings.Contains(headers["Accept-Encoding"], "gzip") { 43 | disableCompress = true 44 | } 45 | 46 | // update it again 47 | var newHeader []map[string]string 48 | for k, v := range headers { 49 | element := make(map[string]string) 50 | element[k] = v 51 | newHeader = append(newHeader, element) 52 | } 53 | req.Headers = newHeader 54 | 55 | // disable log when retry 56 | logger := logrus.New() 57 | if !options.Debug { 58 | logger.Out = ioutil.Discard 59 | } 60 | 61 | client := resty.New() 62 | client.SetLogger(logger) 63 | tlsCfg := &tls.Config{ 64 | Renegotiation: tls.RenegotiateOnceAsClient, 65 | PreferServerCipherSuites: true, 66 | InsecureSkipVerify: true, 67 | } 68 | 69 | if proxy != "" { 70 | // some times burp reject default cipher 71 | tlsCfg = &tls.Config{ 72 | CipherSuites: []uint16{ 73 | tls.TLS_RSA_WITH_RC4_128_SHA, 74 | tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 75 | tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 76 | }, 77 | Renegotiation: tls.RenegotiateOnceAsClient, 78 | PreferServerCipherSuites: true, 79 | InsecureSkipVerify: true, 80 | } 81 | } 82 | 83 | client.SetTransport(&http.Transport{ 84 | MaxIdleConns: 100, 85 | MaxConnsPerHost: 1000, 86 | IdleConnTimeout: time.Duration(timeout) * time.Second, 87 | ExpectContinueTimeout: time.Duration(timeout) * time.Second, 88 | ResponseHeaderTimeout: time.Duration(timeout) * time.Second, 89 | TLSHandshakeTimeout: time.Duration(timeout) * time.Second, 90 | DisableCompression: disableCompress, 91 | DisableKeepAlives: true, 92 | TLSClientConfig: tlsCfg, 93 | }) 94 | 95 | if proxy != "" { 96 | client.SetProxy(proxy) 97 | } 98 | client.SetHeaders(headers) 99 | client.SetCloseConnection(true) 100 | 101 | if options.Retry > 0 { 102 | client.SetRetryCount(options.Retry) 103 | } 104 | client.SetTimeout(time.Duration(timeout) * time.Second) 105 | client.SetRetryWaitTime(time.Duration(timeout/2) * time.Second) 106 | client.SetRetryMaxWaitTime(time.Duration(timeout) * time.Second) 107 | timeStart := time.Now() 108 | // redirect policy 109 | if req.Redirect == false { 110 | client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { 111 | // keep the header the same 112 | // client.SetHeaders(headers) 113 | 114 | res.StatusCode = req.Response.StatusCode 115 | res.Status = req.Response.Status 116 | resp := req.Response 117 | bodyBytes, err := ioutil.ReadAll(resp.Body) 118 | if err != nil { 119 | utils.ErrorF("%v", err) 120 | } 121 | bodyString := string(bodyBytes) 122 | resLength := len(bodyString) 123 | // format the headers 124 | var resHeaders []map[string]string 125 | for k, v := range resp.Header { 126 | element := make(map[string]string) 127 | //fmt.Printf("%v: %v\n", k, v) 128 | element[k] = strings.Join(v[:], "") 129 | resLength += len(fmt.Sprintf("%s: %s\n", k, strings.Join(v[:], ""))) 130 | resHeaders = append(resHeaders, element) 131 | } 132 | 133 | // response time in second 134 | resTime := time.Since(timeStart).Seconds() 135 | resHeaders = append(resHeaders, 136 | map[string]string{"Total Length": strconv.Itoa(resLength)}, 137 | map[string]string{"Response Time": fmt.Sprintf("%f", resTime)}, 138 | ) 139 | 140 | // set some variable 141 | res.Headers = resHeaders 142 | res.StatusCode = resp.StatusCode 143 | res.Status = fmt.Sprintf("%v %v", resp.Status, resp.Proto) 144 | res.Body = bodyString 145 | res.ResponseTime = resTime 146 | res.Length = resLength 147 | 148 | // beautify 149 | res.Beautify = BeautifyResponse(res) 150 | return errors.New("auto redirect is disabled") 151 | })) 152 | 153 | client.AddRetryCondition( 154 | func(r *resty.Response, err error) bool { 155 | return false 156 | }, 157 | ) 158 | } else { 159 | client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { 160 | // keep the header the same 161 | client.SetHeaders(headers) 162 | return nil 163 | })) 164 | } 165 | 166 | var resp *resty.Response 167 | // really sending things here 168 | method = strings.ToLower(strings.TrimSpace(method)) 169 | switch method { 170 | case "get": 171 | resp, err = client.R(). 172 | SetBody([]byte(body)). 173 | Get(url) 174 | break 175 | case "post": 176 | resp, err = client.R().EnableTrace(). 177 | SetBody([]byte(body)). 178 | Post(url) 179 | break 180 | case "head": 181 | resp, err = client.R(). 182 | SetBody([]byte(body)). 183 | Head(url) 184 | break 185 | case "options": 186 | resp, err = client.R(). 187 | SetBody([]byte(body)). 188 | Options(url) 189 | break 190 | case "patch": 191 | resp, err = client.R(). 192 | SetBody([]byte(body)). 193 | Patch(url) 194 | break 195 | case "put": 196 | resp, err = client.R(). 197 | SetBody([]byte(body)). 198 | Put(url) 199 | break 200 | case "delete": 201 | resp, err = client.R(). 202 | SetBody([]byte(body)). 203 | Delete(url) 204 | break 205 | } 206 | 207 | // in case we want to get redirect stuff 208 | if res.StatusCode != 0 { 209 | if req.EnableChecksum { 210 | GenCheckSum(&res) 211 | } 212 | return res, nil 213 | } 214 | 215 | if err != nil { 216 | utils.ErrorF("%v %v", url, err) 217 | if strings.Contains(err.Error(), "EOF") && resp.StatusCode() != 0 { 218 | return ParseResponse(*resp), nil 219 | } 220 | return libs.Response{}, err 221 | } 222 | 223 | res = ParseResponse(*resp) 224 | if req.EnableChecksum { 225 | GenCheckSum(&res) 226 | } 227 | return res, nil 228 | } 229 | 230 | // ParseResponse field to Response 231 | func ParseResponse(resp resty.Response) (res libs.Response) { 232 | // var res libs.Response 233 | resLength := len(string(resp.Body())) 234 | // format the headers 235 | var resHeaders []map[string]string 236 | for k, v := range resp.RawResponse.Header { 237 | element := make(map[string]string) 238 | element[k] = strings.Join(v[:], "") 239 | resLength += len(fmt.Sprintf("%s: %s\n", k, strings.Join(v[:], ""))) 240 | resHeaders = append(resHeaders, element) 241 | } 242 | // response time in second 243 | resTime := float64(resp.Time()) / float64(time.Second) 244 | resHeaders = append(resHeaders, 245 | map[string]string{"Total Length": strconv.Itoa(resLength)}, 246 | map[string]string{"Response Time": fmt.Sprintf("%f", resTime)}, 247 | ) 248 | 249 | // set some variable 250 | res.Headers = resHeaders 251 | res.StatusCode = resp.StatusCode() 252 | res.Status = fmt.Sprintf("%v %v", resp.Status(), resp.RawResponse.Proto) 253 | res.Body = string(resp.Body()) 254 | res.ResponseTime = resTime 255 | res.Length = resLength 256 | 257 | // beautify 258 | res.Beautify = BeautifyResponse(res) 259 | return res 260 | } 261 | 262 | // GetHeaders generate headers if not provide 263 | func GetHeaders(req libs.Request) map[string]string { 264 | // random user agent 265 | UserAgens := []string{ 266 | "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", 267 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3941.0 Safari/537.36", 268 | "Mozilla/5.0 (X11; U; Windows NT 6; en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.587.0 Safari/534.12", 269 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36", 270 | } 271 | 272 | headers := make(map[string]string) 273 | if len(req.Headers) == 0 { 274 | rand.Seed(time.Now().Unix()) 275 | headers["User-Agent"] = UserAgens[rand.Intn(len(UserAgens))] 276 | return headers 277 | } 278 | 279 | for _, header := range req.Headers { 280 | for key, value := range header { 281 | headers[key] = value 282 | } 283 | } 284 | 285 | rand.Seed(time.Now().Unix()) 286 | // append user agent in case you didn't set user-agent 287 | if headers["User-Agent"] == "" { 288 | rand.Seed(time.Now().Unix()) 289 | headers["User-Agent"] = UserAgens[rand.Intn(len(UserAgens))] 290 | } 291 | return headers 292 | } 293 | 294 | // BeautifyRequest beautify request 295 | func BeautifyRequest(req libs.Request) string { 296 | var beautifyReq string 297 | // hardcoded HTTP/1.1 for now 298 | beautifyReq += fmt.Sprintf("%v %v HTTP/1.1\n", req.Method, req.URL) 299 | 300 | for _, header := range req.Headers { 301 | for key, value := range header { 302 | if key != "" && value != "" { 303 | beautifyReq += fmt.Sprintf("%v: %v\n", key, value) 304 | } 305 | } 306 | } 307 | if req.Body != "" { 308 | beautifyReq += fmt.Sprintf("\n%v\n", req.Body) 309 | } 310 | return beautifyReq 311 | } 312 | 313 | // BeautifyResponse beautify response 314 | func BeautifyResponse(res libs.Response) string { 315 | var beautifyRes string 316 | beautifyRes += fmt.Sprintf("%v \n", res.Status) 317 | 318 | for _, header := range res.Headers { 319 | for key, value := range header { 320 | beautifyRes += fmt.Sprintf("%v: %v\n", key, value) 321 | } 322 | } 323 | 324 | beautifyRes += fmt.Sprintf("\n%v\n", res.Body) 325 | return beautifyRes 326 | } 327 | -------------------------------------------------------------------------------- /server/api.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/jaeles-project/jaeles/database" 8 | "github.com/jaeles-project/jaeles/database/models" 9 | ) 10 | 11 | // Ping testing authenticated connection 12 | func Ping(c *gin.Context) { 13 | c.JSON(200, gin.H{ 14 | "status": "200", 15 | "message": "pong", 16 | }) 17 | } 18 | 19 | // GetStats return stat data 20 | func GetStats(c *gin.Context) { 21 | var info []models.Record 22 | database.DB.Where("risk = ?", "Info").Find(&info) 23 | var potential []models.Record 24 | database.DB.Where("risk = ?", "Potential").Find(&potential) 25 | var low []models.Record 26 | database.DB.Where("risk = ?", "Low").Find(&low) 27 | var medium []models.Record 28 | database.DB.Where("risk = ?", "Medium").Find(&medium) 29 | var high []models.Record 30 | database.DB.Where("risk = ?", "High").Find(&high) 31 | var critical []models.Record 32 | database.DB.Where("risk = ?", "Critical").Find(&critical) 33 | 34 | stats := []int{ 35 | len(info), 36 | len(potential), 37 | len(low), 38 | len(medium), 39 | len(high), 40 | len(critical), 41 | } 42 | 43 | c.JSON(200, gin.H{ 44 | "status": "200", 45 | "message": "Success", 46 | "stats": stats, 47 | }) 48 | } 49 | 50 | // GetSignSummary return signature stat 51 | func GetSignSummary(c *gin.Context) { 52 | var signs []models.Signature 53 | var categories []string 54 | var data []int 55 | database.DB.Find(&signs).Pluck("DISTINCT category", &categories) 56 | // stats := make(map[string]int) 57 | for _, category := range categories { 58 | var signatures []models.Signature 59 | database.DB.Where("category = ?", category).Find(&signatures) 60 | data = append(data, len(signatures)) 61 | } 62 | 63 | c.JSON(200, gin.H{ 64 | "status": "200", 65 | "message": "Success", 66 | "categories": categories, 67 | "data": data, 68 | }) 69 | } 70 | 71 | // GetSigns return signature record 72 | func GetSigns(c *gin.Context) { 73 | var signs []models.Signature 74 | database.DB.Find(&signs) 75 | 76 | c.JSON(200, gin.H{ 77 | "status": "200", 78 | "message": "Success", 79 | "signatures": signs, 80 | }) 81 | } 82 | 83 | // GetAllScan return all scans 84 | func GetAllScan(c *gin.Context) { 85 | var scans []models.Scans 86 | database.DB.Find(&scans) 87 | 88 | // remove empty scan 89 | var realScans []models.Scans 90 | for _, scan := range scans { 91 | var rec models.Record 92 | database.DB.First(&rec, "scan_id = ?", scan.ScanID) 93 | if rec.ScanID != "" { 94 | realScans = append(realScans, scan) 95 | } 96 | } 97 | 98 | c.JSON(200, gin.H{ 99 | "status": "200", 100 | "message": "Success", 101 | "scans": realScans, 102 | }) 103 | } 104 | 105 | // GetRecords get record by scan ID 106 | func GetRecords(c *gin.Context) { 107 | sid := c.Param("sid") 108 | var records []models.Record 109 | database.DB.Where("scan_id = ?", sid).Find(&records) 110 | 111 | c.JSON(200, gin.H{ 112 | "status": "200", 113 | "message": "Success", 114 | "records": records, 115 | }) 116 | } 117 | 118 | // GetRecord get record detail by record ID 119 | func GetRecord(c *gin.Context) { 120 | rid := c.Param("rid") 121 | var record models.Record 122 | database.DB.Where("id = ?", rid).First(&record) 123 | 124 | c.JSON(200, gin.H{ 125 | "status": "200", 126 | "message": "Success", 127 | "record": record, 128 | }) 129 | } 130 | 131 | // SignConfig config 132 | type SignConfig struct { 133 | Value string `json:"sign"` 134 | } 135 | 136 | // UpdateDefaultSign geet record by scan 137 | func UpdateDefaultSign(c *gin.Context) { 138 | var signConfig SignConfig 139 | err := c.ShouldBindJSON(&signConfig) 140 | if err != nil { 141 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 142 | } 143 | 144 | database.UpdateDefaultSign(signConfig.Value) 145 | c.JSON(200, gin.H{ 146 | "status": "200", 147 | "message": "Update Defeult sign success", 148 | }) 149 | } 150 | -------------------------------------------------------------------------------- /server/controllers.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "github.com/jaeles-project/jaeles/utils" 7 | "net/http" 8 | 9 | "github.com/jaeles-project/jaeles/libs" 10 | 11 | "github.com/fatih/color" 12 | "github.com/gin-gonic/gin" 13 | "github.com/jaeles-project/jaeles/core" 14 | ) 15 | 16 | // RequestData struct for recive request from burp 17 | type RequestData struct { 18 | RawReq string `json:"req"` 19 | RawRes string `json:"res"` 20 | URL string `json:"url"` 21 | } 22 | 23 | // SetBurpCollab setup Burp 24 | func SetBurpCollab(c *gin.Context) { 25 | c.JSON(200, gin.H{ 26 | "status": "200", 27 | "message": "Got it", 28 | }) 29 | } 30 | 31 | // ParseRaw Get Raw Burp Request in base64 encode 32 | func ParseRaw(c *gin.Context) { 33 | // result <- record 34 | // core data 35 | var reqData RequestData 36 | // c.BindJSON(&reqData) 37 | err := c.ShouldBindJSON(&reqData) 38 | if err != nil { 39 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 40 | } 41 | 42 | go func() { 43 | var record libs.Record 44 | // core data 45 | rawReq := reqData.RawReq 46 | rawRes := reqData.RawRes 47 | 48 | req, err := base64.StdEncoding.DecodeString(rawReq) 49 | if err != nil { 50 | c.JSON(500, gin.H{ 51 | "message": "error decode request", 52 | "status": "Error", 53 | }) 54 | } 55 | record.OriginReq = core.ParseBurpRequest(string(req)) 56 | /* Response part */ 57 | if rawRes != "" { 58 | // response stuff 59 | res, err := base64.StdEncoding.DecodeString(rawRes) 60 | if err != nil { 61 | c.JSON(500, gin.H{ 62 | "message": "error decode response", 63 | "status": "Error", 64 | }) 65 | } 66 | record.OriginRes = core.ParseBurpResponse(string(req), string(res)) 67 | } 68 | 69 | color.Green("-- got from gin") 70 | fmt.Println(record.OriginReq.URL) 71 | color.Green("-- done from gin") 72 | // result <- record 73 | }() 74 | 75 | c.JSON(200, gin.H{ 76 | "status": "200", 77 | "message": "Got it", 78 | }) 79 | } 80 | 81 | // ReceiveRequest is handler to got request from Burp 82 | func ReceiveRequest(result chan libs.Record) gin.HandlerFunc { 83 | return func(c *gin.Context) { 84 | cCp := c.Copy() 85 | var reqData RequestData 86 | err := cCp.ShouldBindJSON(&reqData) 87 | if err != nil { 88 | c.JSON(200, gin.H{ 89 | "status": "500", 90 | "message": "Error parsing JSON data", 91 | }) 92 | return 93 | } 94 | var record libs.Record 95 | // core data 96 | rawReq := reqData.RawReq 97 | rawRes := reqData.RawRes 98 | URL := reqData.URL 99 | 100 | // var record libs.Record 101 | req, err := base64.StdEncoding.DecodeString(rawReq) 102 | if err != nil { 103 | c.JSON(200, gin.H{ 104 | "status": "500", 105 | "message": "Error parsing request", 106 | }) 107 | return 108 | } 109 | 110 | utils.DebugF("Raw req: %v", string(req)) 111 | 112 | record.OriginReq = core.ParseBurpRequest(string(req)) 113 | utils.DebugF("Origin Body: %v", record.OriginReq.Body) 114 | if URL != "" { 115 | record.OriginReq.URL = URL 116 | } 117 | utils.InforF("[Recive] %v %v \n", record.OriginReq.Method, record.OriginReq.URL) 118 | 119 | /* Response part */ 120 | if rawRes != "" { 121 | // response stuff 122 | res, err := base64.StdEncoding.DecodeString(rawRes) 123 | if err != nil { 124 | c.JSON(200, gin.H{ 125 | "status": "500", 126 | "message": "Error parsing response", 127 | }) 128 | } 129 | record.OriginRes = core.ParseBurpResponse(string(req), string(res)) 130 | } 131 | result <- record 132 | 133 | c.JSON(200, gin.H{ 134 | "status": "200", 135 | "message": "Got it", 136 | }) 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /server/routers.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "github.com/fatih/color" 6 | "log" 7 | "net/http" 8 | "os" 9 | "path" 10 | "time" 11 | 12 | jwt "github.com/appleboy/gin-jwt/v2" 13 | "github.com/jaeles-project/jaeles/database" 14 | "github.com/jaeles-project/jaeles/libs" 15 | "github.com/jaeles-project/jaeles/utils" 16 | 17 | "github.com/gin-contrib/cors" 18 | "github.com/gin-contrib/static" 19 | "github.com/gin-gonic/gin" 20 | ) 21 | 22 | type login struct { 23 | Username string `form:"username" json:"username" binding:"required"` 24 | Password string `form:"password" json:"password" binding:"required"` 25 | } 26 | 27 | var identityKey = "id" 28 | 29 | // User struct 30 | type User struct { 31 | UserName string 32 | Role string 33 | Email string 34 | IsAdmin bool 35 | } 36 | 37 | // InitRouter start point of api server 38 | func InitRouter(options libs.Options, result chan libs.Record) { 39 | // turn off Gin debug mode 40 | if !options.Debug { 41 | gin.SetMode(gin.ReleaseMode) 42 | } 43 | r := gin.New() 44 | r.Use(gin.Logger()) 45 | r.Use(gin.Recovery()) 46 | 47 | if options.Server.NoAuth { 48 | fmt.Fprintf(os.Stderr, "[Warning] You're running server with %v\n", color.RedString("NO AUTHENTICATION")) 49 | } 50 | 51 | // default is ~/.jaeles/ui/ 52 | uiPath := path.Join(options.RootFolder, "/plugins/ui") 53 | r.Use(static.Serve("/", static.LocalFile(uiPath, true))) 54 | 55 | allowOrigin := "*" 56 | secret := "something you have to change" 57 | if options.Server.JWTSecret != "" { 58 | secret = options.Server.JWTSecret 59 | } 60 | if options.Server.Cors != "" { 61 | allowOrigin = options.Server.Cors 62 | } 63 | 64 | r.Use(cors.New(cors.Config{ 65 | AllowOrigins: []string{allowOrigin}, 66 | AllowMethods: []string{"POST", "GET", "OPTIONS"}, 67 | AllowHeaders: []string{"Authorization"}, 68 | AllowCredentials: true, 69 | MaxAge: 24 * time.Hour, 70 | })) 71 | 72 | // the jwt middleware 73 | authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{ 74 | Realm: "jaeles server", 75 | Key: []byte(secret), 76 | Timeout: time.Hour * 360, 77 | MaxRefresh: time.Hour * 720, 78 | IdentityKey: identityKey, 79 | PayloadFunc: func(data interface{}) jwt.MapClaims { 80 | if v, ok := data.(*User); ok { 81 | return jwt.MapClaims{ 82 | identityKey: v.Role, 83 | } 84 | } 85 | return jwt.MapClaims{} 86 | }, 87 | IdentityHandler: func(c *gin.Context) interface{} { 88 | claims := jwt.ExtractClaims(c) 89 | return &User{ 90 | Role: claims[identityKey].(string), 91 | } 92 | }, 93 | Authenticator: func(c *gin.Context) (interface{}, error) { 94 | var loginVals login 95 | err := c.ShouldBindJSON(&loginVals) 96 | if err != nil { 97 | return "", jwt.ErrMissingLoginValues 98 | } 99 | username := loginVals.Username 100 | password := loginVals.Password 101 | 102 | // compare hashed password 103 | realPassword := database.SelectUser(username) 104 | if utils.GenHash(password) == realPassword { 105 | return &User{ 106 | UserName: username, 107 | // only have one role for now 108 | Role: "admin", 109 | Email: username, 110 | IsAdmin: true, 111 | }, nil 112 | } 113 | 114 | return nil, jwt.ErrFailedAuthentication 115 | }, 116 | Authorizator: func(data interface{}, c *gin.Context) bool { 117 | // @TODO: Disable authorization for now 118 | if v, ok := data.(*User); ok && v.Role == "admin" { 119 | return true 120 | } 121 | return false 122 | // return true 123 | }, 124 | Unauthorized: func(c *gin.Context, code int, message string) { 125 | c.JSON(code, gin.H{ 126 | "code": code, 127 | "message": message, 128 | }) 129 | }, 130 | TokenLookup: "header: Authorization, query: token, cookie: jwt", 131 | TokenHeadName: "Jaeles", 132 | TimeFunc: time.Now, 133 | }) 134 | 135 | if err != nil { 136 | log.Fatal("JWT Error:" + err.Error()) 137 | } 138 | 139 | r.POST("/auth/login", authMiddleware.LoginHandler) 140 | r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) { 141 | claims := jwt.ExtractClaims(c) 142 | utils.InforF("NoRoute claims: %#v\n", claims) 143 | c.JSON(404, gin.H{"code": "404", "message": "Page not found"}) 144 | }) 145 | auth := r.Group("/api") 146 | 147 | // Refresh time can be longer than token timeout 148 | auth.GET("/refresh_token", authMiddleware.RefreshHandler) 149 | if !options.Server.NoAuth { 150 | auth.Use(authMiddleware.MiddlewareFunc()) 151 | } 152 | { 153 | auth.GET("/ping", Ping) 154 | auth.POST("/parse", ReceiveRequest(result)) 155 | auth.POST("/config/sign", UpdateDefaultSign) 156 | auth.GET("/stats/vuln", GetStats) 157 | auth.GET("/stats/sign", GetSignSummary) 158 | auth.GET("/signatures", GetSigns) 159 | auth.GET("/scans", GetAllScan) 160 | auth.GET("/scan/:sid/", GetRecords) 161 | auth.GET("/record/:rid/", GetRecord) 162 | } 163 | 164 | if err := http.ListenAndServe(options.Server.Bind, r); err != nil { 165 | log.Fatal(err) 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /test-signatures/common-error-header.yaml: -------------------------------------------------------------------------------- 1 | id: common-error-header 2 | type: fuzz 3 | info: 4 | name: Common case to trigger the error 02 5 | risk: Info 6 | confidence: Tentative 7 | 8 | variables: 9 | - head: | 10 | X-Forwarded-By 11 | X-Original-Url 12 | X-Rewrite-URL 13 | X-Forwarded-For 14 | - prefix: | 15 | %00 16 | %20 17 | %09 18 | %0A 19 | %0D 20 | %ff 21 | 22 | payloads: 23 | - '{{.prefix}}' 24 | 25 | requests: 26 | - generators: 27 | - Header("{{.payload}}", "{{.head}}") 28 | detections: 29 | - >- 30 | StatusCode() < 300 && StatusCode() >= 500 && !RegexSearch("response", "(?i)(Oops!|Whoops!|not\sfound|Request\sRejected|Access\sDenied|a\sbad\sURL|has\sbeen\slocked)") && (Math.abs(ContentLength('body') - OriginContentLength('body')) > 100) && CommonError() 31 | -------------------------------------------------------------------------------- /test-signatures/common-error.yaml: -------------------------------------------------------------------------------- 1 | id: common-error-01 2 | type: fuzz 3 | info: 4 | name: Common case to trigger the error 5 | risk: Info 6 | 7 | 8 | 9 | variables: 10 | - prefix: | 11 | %20 12 | %09 13 | 14 | 15 | payloads: 16 | - "{{.prefix}}" 17 | 18 | requests: 19 | - generators: 20 | - Path("[[.original]]{{.payload}}", "*") 21 | detections: 22 | - >- 23 | StatusCode() < 300 && StatusCode() >= 500 && StatusCode() != OriginStatusCode() && !StringSearch("response", "Not found") && !StringSearch("response", "Request Rejected") && CommonError() 24 | -------------------------------------------------------------------------------- /test-signatures/local-analyze.yaml: -------------------------------------------------------------------------------- 1 | id: test-sign-local-analyze 2 | info: 3 | name: Test Routine 4 | risk: Potential 5 | 6 | # jaeles scan -s test-signatures/local-analyze.yaml -u /tmp/jtt/req.txt -v --debug --local 7 | rules: 8 | - detections: 9 | - >- 10 | StringSearch("response", "nginx") 11 | - >- 12 | RegexSearch("response", 'application\\/json;') 13 | - detections: 14 | - >- 15 | RegexSearch("response", '(?m)nginx\/1\.14.*') 16 | -------------------------------------------------------------------------------- /test-signatures/query-fuzz.yaml: -------------------------------------------------------------------------------- 1 | id: common-error-01 2 | type: fuzz 3 | info: 4 | name: Common case to trigger the error 5 | risk: Info 6 | 7 | variables: 8 | - prefix: | 9 | %20 10 | %09 11 | 12 | 13 | payloads: 14 | - "{{.prefix}}" 15 | 16 | requests: 17 | - generators: 18 | - Query("[[.original]]{{.payload}}", "*") 19 | detections: 20 | - >- 21 | StatusCode() < 300 && StatusCode() >= 500 && StatusCode() != OriginStatusCode() && !StringSearch("response", "Not found") && !StringSearch("response", "Request Rejected") && CommonError() 22 | -------------------------------------------------------------------------------- /test-signatures/routine-simple.yaml: -------------------------------------------------------------------------------- 1 | id: test-sign 2 | type: routine 3 | info: 4 | name: Test Routine 5 | risk: Potential 6 | 7 | params: 8 | - root: '{{.BaseURL}}' 9 | 10 | routines: 11 | - signs: 12 | # - sign1: '{{.BaseSign}}/products/dependencies/gemfile-exposed.yaml' 13 | - sign2: 'test-signatures/with-origin.yaml' 14 | # - sign3: '{{.SignPwd}}/with-prefix.yaml' 15 | logics: 16 | - expr: 'sign2()' 17 | invokes: 18 | - '~/pro-signatures/products/dependencies/gemfile-exposed.yaml' 19 | 20 | -------------------------------------------------------------------------------- /test-signatures/simple-dns.yaml: -------------------------------------------------------------------------------- 1 | id: simple-dns 2 | type: 'dns' 3 | info: 4 | name: Spring Boot Common Paths 5 | risk: Potential 6 | 7 | dns: 8 | - domain: '{{.Domain}}' 9 | record: 'NS' 10 | detections: 11 | - >- 12 | DnsString('NS', 'nsone.') 13 | - >- 14 | DnsRegex('NS', '*.nsone.*') 15 | -------------------------------------------------------------------------------- /test-signatures/with-check-request.yaml: -------------------------------------------------------------------------------- 1 | id: test-sign-04 2 | info: 3 | name: Test sign 4 | risk: Potential 5 | 6 | params: 7 | - root: '{{.BaseURL}}' 8 | 9 | # checking request 10 | crequests: 11 | - method: GET 12 | redirect: false 13 | url: >- 14 | {{.root}}/Gemfile 15 | headers: 16 | - User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55 17 | detections: 18 | - >- 19 | StatusCode() == 200 && StringSearch("body", 'source') && StringSearch("body", 'end') 20 | 21 | requests: 22 | - method: GET 23 | redirect: false 24 | url: >- 25 | {{.root}}/Gemfile.lock 26 | headers: 27 | - User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55 28 | detections: 29 | - >- 30 | StatusCode() == 200 && StringSearch("body", 'GEM') && StringSearch("body", 'specs') 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test-signatures/with-origin.yaml: -------------------------------------------------------------------------------- 1 | id: sensitive-secret-01 2 | donce: true 3 | info: 4 | name: Common Secret Files 5 | risk: Potential 6 | confidence: Tentative 7 | 8 | params: 9 | - root: "{{.BaseURL}}" 10 | 11 | origin: 12 | method: GET 13 | redirect: false 14 | headers: 15 | - User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55 16 | url: >- 17 | {{.BaseURL}}/hopefully404.lock 18 | 19 | variables: 20 | - secret: | 21 | Gemfile.lock 22 | Gemfile 23 | 24 | requests: 25 | - method: GET 26 | redirect: false 27 | headers: 28 | - User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3984.0 Safari/537.36 29 | url: >- 30 | {{.root}}/{{.secret}} 31 | detections: 32 | - >- 33 | StatusCode() == 200 && !RegexSearch("response", "(?i)(Oops!|Whoops!|AutodiscoverService|not\sfound|Request\sRejected|Access\sDenied|a\sbad\sURL|has\sbeen\slocked)") && (RegexSearch("resHeaders", "Accept-Ranges.*bytes") || RegexSearch("resHeaders", ".*Content-Type:.*octet-stream") || RegexSearch("resHeaders", "text/plain")) && !RegexSearch("resHeaders", "text/html") && (Math.abs(ContentLength() - OriginContentLength()) > 20) && ContentLength('body') > 100 && !StringSearch("oresponse", "text/plain") 34 | -------------------------------------------------------------------------------- /test-signatures/with-passive-in-dection.yaml: -------------------------------------------------------------------------------- 1 | id: testing-passive 2 | info: 3 | name: testing-passive 4 | risk: Potential 5 | 6 | 7 | requests: 8 | - method: GET 9 | redirect: false 10 | url: >- 11 | {{.Raw}} 12 | headers: 13 | - User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55 14 | detections: 15 | - >- 16 | StatusCode() == 200 && DoPassive() 17 | -------------------------------------------------------------------------------- /test-signatures/with-passive.yaml: -------------------------------------------------------------------------------- 1 | id: testing-passive 2 | passive: true 3 | info: 4 | name: testing-passive 5 | risk: Potential 6 | 7 | 8 | requests: 9 | - method: GET 10 | redirect: false 11 | url: >- 12 | {{.Raw}} 13 | headers: 14 | - User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55 15 | 16 | 17 | -------------------------------------------------------------------------------- /test-signatures/with-prefix.yaml: -------------------------------------------------------------------------------- 1 | id: spring-probe-02 2 | donce: true 3 | info: 4 | name: Spring Boot Common Paths 5 | risk: Potential 6 | 7 | params: 8 | - root: '{{.BaseURL}}' 9 | 10 | replicate: 11 | ports: '8080' 12 | prefixes: 'actuator, admin' 13 | 14 | variables: 15 | - infos: | 16 | actuator 17 | admin 18 | auditevents 19 | caches 20 | 21 | requests: 22 | - method: GET 23 | redirect: false 24 | url: >- 25 | {{.root}}/{{.infos}} 26 | headers: 27 | - User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55 28 | detections: 29 | - >- 30 | StatusCode() == 200 && StringSearch("response", '{"_links":{"self"') && !StringSearch("response", 'Autodiscover') 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /utils/helper.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bufio" 5 | "crypto/sha1" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "log" 11 | "net/url" 12 | "os" 13 | "path" 14 | "path/filepath" 15 | "regexp" 16 | "strconv" 17 | "strings" 18 | "time" 19 | 20 | "github.com/mitchellh/go-homedir" 21 | ) 22 | 23 | // StrToInt string to int 24 | func StrToInt(data string) int { 25 | i, err := strconv.Atoi(data) 26 | if err != nil { 27 | return 0 28 | } 29 | return i 30 | } 31 | 32 | // GetOSEnv get enviroment variable 33 | func GetOSEnv(name string) string { 34 | varibale, ok := os.LookupEnv(name) 35 | if !ok { 36 | return name 37 | } 38 | return varibale 39 | } 40 | 41 | // MakeDir just make a folder 42 | func MakeDir(folder string) { 43 | os.MkdirAll(folder, 0750) 44 | } 45 | 46 | // GetCurrentDay get current day 47 | func GetCurrentDay() string { 48 | currentTime := time.Now() 49 | return fmt.Sprintf("%v", currentTime.Format("2006-01-02_3:4:5")) 50 | } 51 | 52 | // NormalizePath the path 53 | func NormalizePath(path string) string { 54 | if strings.HasPrefix(path, "~") { 55 | path, _ = homedir.Expand(path) 56 | } 57 | return path 58 | } 59 | 60 | // FileLength count len of file 61 | func FileLength(filename string) int { 62 | filename = NormalizePath(filename) 63 | return len(ReadingLines(filename)) 64 | } 65 | 66 | // DirLength count len of file 67 | func DirLength(dir string) int { 68 | dir = NormalizePath(dir) 69 | files, err := ioutil.ReadDir(dir) 70 | if err != nil { 71 | return 0 72 | } 73 | return len(files) 74 | } 75 | 76 | // GetFileContent Reading file and return content of it 77 | func GetFileContent(filename string) string { 78 | var result string 79 | if strings.Contains(filename, "~") { 80 | filename, _ = homedir.Expand(filename) 81 | } 82 | file, err := os.Open(filename) 83 | if err != nil { 84 | return result 85 | } 86 | defer file.Close() 87 | b, err := ioutil.ReadAll(file) 88 | if err != nil { 89 | return result 90 | } 91 | return string(b) 92 | } 93 | 94 | // ReadingLines Reading file and return content as []string 95 | func ReadingLines(filename string) []string { 96 | var result []string 97 | if strings.HasPrefix(filename, "~") { 98 | filename, _ = homedir.Expand(filename) 99 | } 100 | file, err := os.Open(filename) 101 | defer file.Close() 102 | if err != nil { 103 | return result 104 | } 105 | 106 | scanner := bufio.NewScanner(file) 107 | for scanner.Scan() { 108 | val := scanner.Text() 109 | if val == "" { 110 | continue 111 | } 112 | result = append(result, val) 113 | } 114 | 115 | if err := scanner.Err(); err != nil { 116 | return result 117 | } 118 | return result 119 | } 120 | 121 | // ReadingFileUnique Reading file and return content as []string 122 | func ReadingFileUnique(filename string) []string { 123 | var result []string 124 | if strings.Contains(filename, "~") { 125 | filename, _ = homedir.Expand(filename) 126 | } 127 | file, err := os.Open(filename) 128 | defer file.Close() 129 | if err != nil { 130 | return result 131 | } 132 | 133 | unique := true 134 | seen := make(map[string]bool) 135 | 136 | scanner := bufio.NewScanner(file) 137 | for scanner.Scan() { 138 | val := scanner.Text() 139 | // unique stuff 140 | if val == "" { 141 | continue 142 | } 143 | if seen[val] && unique { 144 | continue 145 | } 146 | 147 | if unique { 148 | seen[val] = true 149 | result = append(result, val) 150 | } 151 | } 152 | 153 | if err := scanner.Err(); err != nil { 154 | return result 155 | } 156 | return result 157 | } 158 | 159 | // WriteToFile write string to a file 160 | func WriteToFile(filename string, data string) (string, error) { 161 | file, err := os.Create(filename) 162 | if err != nil { 163 | return "", err 164 | } 165 | defer file.Close() 166 | 167 | _, err = io.WriteString(file, data+"\n") 168 | if err != nil { 169 | return "", err 170 | } 171 | return filename, file.Sync() 172 | } 173 | 174 | // AppendToContent append string to a file 175 | func AppendToContent(filename string, data string) (string, error) { 176 | // If the file doesn't exist, create it, or append to the file 177 | f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 178 | if err != nil { 179 | return "", err 180 | } 181 | if _, err := f.Write([]byte(data + "\n")); err != nil { 182 | return "", err 183 | } 184 | if err := f.Close(); err != nil { 185 | return "", err 186 | } 187 | return filename, nil 188 | } 189 | 190 | // FileExists check if file is exist or not 191 | func FileExists(filename string) bool { 192 | _, err := os.Stat(filename) 193 | if os.IsNotExist(err) { 194 | return false 195 | } 196 | return true 197 | } 198 | 199 | // FolderExists check if file is exist or not 200 | func FolderExists(foldername string) bool { 201 | foldername = NormalizePath(foldername) 202 | if _, err := os.Stat(foldername); os.IsNotExist(err) { 203 | return false 204 | } 205 | return true 206 | } 207 | 208 | // GetFileNames get all file name with extension 209 | func GetFileNames(dir string, ext string) []string { 210 | if _, err := os.Stat(dir); os.IsNotExist(err) { 211 | return nil 212 | } 213 | 214 | var files []string 215 | filepath.Walk(dir, func(path string, f os.FileInfo, _ error) error { 216 | if !f.IsDir() { 217 | if strings.HasSuffix(f.Name(), ext) { 218 | filename, _ := filepath.Abs(path) 219 | files = append(files, filename) 220 | } 221 | } 222 | return nil 223 | }) 224 | return files 225 | } 226 | 227 | // IsJSON check if string is JSON or not 228 | func IsJSON(str string) bool { 229 | var js json.RawMessage 230 | return json.Unmarshal([]byte(str), &js) == nil 231 | } 232 | 233 | // GetTS get current timestamp and return a string 234 | func GetTS() string { 235 | return strconv.FormatInt(time.Now().Unix(), 10) 236 | } 237 | 238 | // GenHash gen SHA1 hash from string 239 | func GenHash(text string) string { 240 | h := sha1.New() 241 | h.Write([]byte(text)) 242 | hashed := h.Sum(nil) 243 | return fmt.Sprintf("%x", hashed) 244 | } 245 | 246 | // CopyDir copy directory to dest 247 | func CopyDir(src string, dst string) error { 248 | var err error 249 | var fds []os.FileInfo 250 | var srcInfo os.FileInfo 251 | 252 | if srcInfo, err = os.Stat(src); err != nil { 253 | return err 254 | } 255 | 256 | if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil { 257 | return err 258 | } 259 | 260 | if fds, err = ioutil.ReadDir(src); err != nil { 261 | return err 262 | } 263 | for _, fd := range fds { 264 | srcfp := path.Join(src, fd.Name()) 265 | dstfp := path.Join(dst, fd.Name()) 266 | 267 | if fd.IsDir() { 268 | if err = CopyDir(srcfp, dstfp); err != nil { 269 | fmt.Println(err) 270 | } 271 | } else { 272 | if err = CopyFile(srcfp, dstfp); err != nil { 273 | fmt.Println(err) 274 | } 275 | } 276 | } 277 | return nil 278 | } 279 | 280 | // CopyFile copies a single file from src to dst 281 | func CopyFile(src, dst string) error { 282 | var err error 283 | var srcfd *os.File 284 | var dstfd *os.File 285 | var srcinfo os.FileInfo 286 | 287 | if srcfd, err = os.Open(src); err != nil { 288 | return err 289 | } 290 | defer srcfd.Close() 291 | 292 | if dstfd, err = os.Create(dst); err != nil { 293 | return err 294 | } 295 | defer dstfd.Close() 296 | 297 | if _, err = io.Copy(dstfd, srcfd); err != nil { 298 | return err 299 | } 300 | if srcinfo, err = os.Stat(src); err != nil { 301 | return err 302 | } 303 | return os.Chmod(dst, srcinfo.Mode()) 304 | } 305 | 306 | // ExpandLength make slice to length 307 | func ExpandLength(list []string, length int) []string { 308 | c := []string{} 309 | for i := 1; i <= length; i++ { 310 | c = append(c, list[i%len(list)]) 311 | } 312 | return c 313 | } 314 | 315 | // StartWithNum check if string start with number 316 | func StartWithNum(raw string) bool { 317 | r, err := regexp.Compile("^[0-9].*") 318 | if err != nil { 319 | return false 320 | } 321 | return r.MatchString(raw) 322 | } 323 | 324 | // StripName strip a file name 325 | func StripName(raw string) string { 326 | return strings.Replace(raw, "/", "_", -1) 327 | } 328 | 329 | // MoveFolder move folder 330 | func MoveFolder(src string, dest string) { 331 | os.Rename(NormalizePath(src), NormalizePath(dest)) 332 | } 333 | 334 | // GetFileSize get file size of a file in GB 335 | func GetFileSize(src string) float64 { 336 | var sizeGB float64 337 | fi, err := os.Stat(NormalizePath(src)) 338 | if err != nil { 339 | return sizeGB 340 | } 341 | // get the size 342 | size := fi.Size() 343 | sizeGB = float64(size) / (1024 * 1024 * 1024) 344 | return sizeGB 345 | } 346 | 347 | // ChunkFileByPart chunk file to multiple part 348 | func ChunkFileByPart(source string, chunk int) [][]string { 349 | var divided [][]string 350 | data := ReadingLines(source) 351 | if len(data) <= 0 || chunk > len(data) { 352 | if len(data) > 0 { 353 | divided = append(divided, data) 354 | } 355 | return divided 356 | } 357 | 358 | chunkSize := (len(data) + chunk - 1) / chunk 359 | for i := 0; i < len(data); i += chunkSize { 360 | end := i + chunkSize 361 | if end > len(data) { 362 | end = len(data) 363 | } 364 | 365 | divided = append(divided, data[i:end]) 366 | } 367 | return divided 368 | } 369 | 370 | // ChunkFileBySize chunk file to multiple part 371 | func ChunkFileBySize(source string, chunk int) [][]string { 372 | var divided [][]string 373 | data := ReadingLines(source) 374 | if len(data) <= 0 || chunk > len(data) { 375 | if len(data) > 0 { 376 | divided = append(divided, data) 377 | } 378 | return divided 379 | } 380 | 381 | chunkSize := chunk 382 | for i := 0; i < len(data); i += chunkSize { 383 | end := i + chunkSize 384 | if end > len(data) { 385 | end = len(data) 386 | } 387 | 388 | divided = append(divided, data[i:end]) 389 | } 390 | return divided 391 | } 392 | 393 | func PromptConfirm(s string) bool { 394 | reader := bufio.NewReader(os.Stdin) 395 | for { 396 | fmt.Printf("%s [y/n]: ", s) 397 | 398 | response, err := reader.ReadString('\n') 399 | if err != nil { 400 | log.Fatal(err) 401 | } 402 | 403 | response = strings.ToLower(strings.TrimSpace(response)) 404 | 405 | if response == "y" || response == "yes" { 406 | return true 407 | } else if response == "n" || response == "no" { 408 | return false 409 | } 410 | } 411 | } 412 | 413 | func JoinURL(raw string, suffix string) string { 414 | u, err := url.Parse(raw) 415 | if err != nil { 416 | if strings.HasSuffix(raw, "/") { 417 | return fmt.Sprintf("%s%s", raw, suffix) 418 | } else { 419 | return fmt.Sprintf("%s/%s", raw, suffix) 420 | } 421 | } 422 | 423 | u.Path = path.Join(u.Path, suffix) 424 | s := u.String() 425 | return s 426 | } 427 | -------------------------------------------------------------------------------- /utils/log.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/fatih/color" 13 | "github.com/jaeles-project/jaeles/libs" 14 | "github.com/sirupsen/logrus" 15 | prefixed "github.com/x-cray/logrus-prefixed-formatter" 16 | ) 17 | 18 | var logger = logrus.New() 19 | 20 | // InitLog init log 21 | func InitLog(options *libs.Options) { 22 | logger = &logrus.Logger{ 23 | Out: os.Stdout, 24 | Level: logrus.InfoLevel, 25 | Formatter: &prefixed.TextFormatter{ 26 | ForceColors: true, 27 | ForceFormatting: true, 28 | }, 29 | } 30 | 31 | if options.LogFile != "" { 32 | options.LogFile = NormalizePath(options.LogFile) 33 | dir := path.Dir(options.LogFile) 34 | tmpFile, _ := ioutil.TempFile(dir, "jaeles-*.log") 35 | options.LogFile = tmpFile.Name() 36 | dir = filepath.Dir(options.LogFile) 37 | if !FolderExists(dir) { 38 | os.MkdirAll(dir, 0755) 39 | } 40 | f, err := os.OpenFile(options.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 41 | if err != nil { 42 | logger.Errorf("error opening file: %v", err) 43 | } 44 | 45 | mwr := io.MultiWriter(os.Stdout, f) 46 | logger = &logrus.Logger{ 47 | Out: mwr, 48 | Level: logrus.InfoLevel, 49 | Formatter: &prefixed.TextFormatter{ 50 | ForceColors: true, 51 | ForceFormatting: true, 52 | }, 53 | } 54 | } 55 | 56 | if options.Debug == true { 57 | logger.SetLevel(logrus.DebugLevel) 58 | } else if options.Verbose == true { 59 | logger.SetOutput(os.Stdout) 60 | logger.SetLevel(logrus.InfoLevel) 61 | } else { 62 | logger.SetLevel(logrus.PanicLevel) 63 | logger.SetOutput(ioutil.Discard) 64 | } 65 | if options.LogFile != "" { 66 | logger.Info(fmt.Sprintf("Store log file to: %v", options.LogFile)) 67 | } 68 | } 69 | 70 | // PrintLine print seperate line 71 | func PrintLine() { 72 | dash := color.HiWhiteString("-") 73 | fmt.Println(strings.Repeat(dash, 40)) 74 | } 75 | 76 | // GoodF print good message 77 | func GoodF(format string, args ...interface{}) { 78 | good := color.HiGreenString("[+]") 79 | fmt.Fprintf(os.Stderr, "%s %s\n", good, fmt.Sprintf(format, args...)) 80 | } 81 | 82 | // BannerF print info message 83 | func BannerF(format string, data string) { 84 | banner := fmt.Sprintf("%v%v%v ", color.WhiteString("["), color.BlueString(format), color.WhiteString("]")) 85 | fmt.Printf("%v%v\n", banner, color.HiGreenString(data)) 86 | } 87 | 88 | // BlockF print info message 89 | func BlockF(name string, data string) { 90 | banner := fmt.Sprintf("%v%v%v ", color.WhiteString("["), color.GreenString(name), color.WhiteString("]")) 91 | fmt.Printf(fmt.Sprintf("%v%v\n", banner, data)) 92 | } 93 | 94 | // InforF print info message 95 | func InforF(format string, args ...interface{}) { 96 | logger.Info(fmt.Sprintf(format, args...)) 97 | } 98 | 99 | // ErrorF print good message 100 | func ErrorF(format string, args ...interface{}) { 101 | logger.Error(fmt.Sprintf(format, args...)) 102 | } 103 | 104 | // WarningF print good message 105 | func WarningF(format string, args ...interface{}) { 106 | good := color.YellowString("[!]") 107 | fmt.Fprintf(os.Stderr, "%s %s\n", good, fmt.Sprintf(format, args...)) 108 | } 109 | 110 | // DebugF print debug message 111 | func DebugF(format string, args ...interface{}) { 112 | logger.Debug(fmt.Sprintf(format, args...)) 113 | } 114 | --------------------------------------------------------------------------------