├── .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 |
3 |
4 |
5 |
6 |
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 | 
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 | | [](https://asciinema.org/a/392827) [**Jenkins Gitlab XSS CVE-2020-2096**](https://asciinema.org/a/392827) | [](https://asciinema.org/a/392822) [**Grafana DoS Probing CVE-2020-13379**](https://asciinema.org/a/392822) |
68 | |:----------:|:-------------:|
69 | | [](https://asciinema.org/a/392824) [**SolarWindsOrion LFI CVE-2020-10148**](https://asciinema.org/a/392824) | [](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 | 
78 |
79 | ### Burp Integration
80 |
81 | 
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 |
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 | [](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 | [](https://paypal.me/j3ssiejjj)
167 |
168 | [](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 |
--------------------------------------------------------------------------------