├── main.go ├── .gitignore ├── database ├── models │ ├── dummy.go │ ├── configuration.go │ ├── collab.go │ ├── model.go │ ├── user.go │ ├── scan.go │ ├── signature.go │ ├── record.go │ └── oob.go ├── connect.go ├── scan.go ├── record.go ├── dnsbin.go ├── user.go ├── config.go ├── collaborator.go └── sign.go ├── Dockerfile ├── test-signatures ├── simple-dns.yaml ├── with-passive.yaml ├── with-passive-in-dection.yaml ├── local-analyze.yaml ├── routine-simple.yaml ├── query-fuzz.yaml ├── common-error.yaml ├── with-prefix.yaml ├── common-error-header.yaml ├── with-check-request.yaml └── with-origin.yaml ├── dns ├── query_test.go └── query.go ├── libs ├── dns.go ├── passive.go ├── version.go ├── signature.go ├── http.go ├── options.go └── banner.go ├── core ├── report_test.go ├── generator_test.go ├── template_test.go ├── analyze.go ├── middleware_test.go ├── sender_test.go ├── background.go ├── template.go ├── update.go ├── runner_test.go ├── passive_test.go ├── variables_test.go ├── filter.go ├── routine.go ├── config.go ├── dns.go ├── signature.go ├── sending.go ├── conclusions.go ├── middleware.go ├── report.go ├── output.go └── runner.go ├── .github └── FUNDING.yml ├── LICENSE ├── cmd ├── server_test.go ├── report.go ├── server.go └── scan.go ├── Makefile ├── utils ├── log.go └── helper.go ├── server ├── controllers.go ├── api.go └── routers.go ├── sender ├── checksum.go ├── chrome.go └── sender.go ├── go.mod └── README.md /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/jaeles-project/jaeles/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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-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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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' ] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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% -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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(` ,
2 |
3 |
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 |