├── 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 ├── user.go ├── config.go ├── dnsbin.go ├── sign.go └── collaborator.go ├── libs ├── passive.go ├── version.go ├── signature.go ├── options.go ├── http.go └── banner.go ├── Dockerfile ├── .github └── FUNDING.yml ├── core ├── generator_test.go ├── sender_test.go ├── background.go ├── variables_test.go ├── update.go ├── analyze.go ├── config.go ├── passive_test.go ├── middleware.go ├── conclusions.go ├── signature.go ├── variables.go ├── detecter.go ├── parser_test.go ├── passive.go ├── parser.go └── generator.go ├── LICENSE ├── go.mod ├── cmd ├── server_test.go ├── server.go ├── root.go ├── config.go └── scan.go ├── utils ├── log.go └── helper.go ├── server ├── controllers.go ├── api.go └── routers.go ├── sender ├── chrome.go └── sender.go └── 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 | Makefile 6 | dist 7 | out 8 | passive-* 9 | old-out 10 | http-out 11 | test-sign 12 | 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /libs/passive.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | // Passive struct for passive detection 4 | type Passive struct { 5 | Name string 6 | Desc string 7 | Rules []Rule 8 | } 9 | 10 | // Rule rule for run detections 11 | type Rule struct { 12 | ID string 13 | Reason string 14 | Detections []string 15 | } 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu 2 | 3 | RUN apt-get update -y; apt-get install golang git -y; go get -u github.com/jaeles-project/jaeles; /root/go/bin/jaeles config -a init 4 | 5 | WORKDIR /root/go/bin/ 6 | 7 | VOLUME /root/go/bin/out 8 | VOLUME /root/.jaeles/ 9 | 10 | EXPOSE 5000 11 | 12 | CMD [ "/root/go/bin/jaeles", "server", "--host", "0.0.0.0" ] 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /libs/version.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | const ( 4 | // VERSION current Jaeles version 5 | VERSION = "beta v0.4.4" 6 | // AUTHOR author of this 7 | AUTHOR = "@j3ssiejjj" 8 | // SIGNREPO default repo to get signature 9 | SIGNREPO = "https://github.com/jaeles-project/jaeles-signatures" 10 | // UIREPO default repo to get UI 11 | UIREPO = "https://github.com/jaeles-project/jaeles-plugins" 12 | ) 13 | -------------------------------------------------------------------------------- /libs/signature.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | // Signature base signature struct 4 | type Signature struct { 5 | ID string 6 | Type string 7 | Info struct { 8 | Name string 9 | Category string 10 | Risk string 11 | Tech string 12 | OS string 13 | } 14 | 15 | Origin Request 16 | Requests []Request 17 | RawRequest string 18 | Payloads []string 19 | Params []map[string]string 20 | Variables []map[string]string 21 | Target map[string]string 22 | } 23 | -------------------------------------------------------------------------------- /database/models/scan.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Scans store scan log 4 | type Scans struct { 5 | Model 6 | ScanID string `gorm:"type:varchar(255);"` 7 | ScanName string `gorm:"type:varchar(255);"` 8 | SignatureID string `gorm:"type:varchar(255);"` 9 | Input string `gorm:"type:longtext;default:''"` 10 | OutputDir string `gorm:"type:longtext;"` 11 | Mode string `gorm:"type:varchar(255);default:'scan'"` 12 | Level int `gorm:"type:int;default:'1'"` 13 | Source string `gorm:"type:longtext;default:''"` 14 | } 15 | -------------------------------------------------------------------------------- /database/models/signature.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Signature mapping signature to a db 4 | type Signature struct { 5 | Model 6 | SignID string `gorm:"type:varchar(100);unique_index"` 7 | Name string `gorm:"type:varchar(100);default:'single'"` 8 | Category string `gorm:"type:varchar(100);default:'general'"` 9 | Risk string `gorm:"type:varchar(100);default:'Info'"` 10 | Tech string `gorm:"type:varchar(100);default:'general'"` 11 | OS string `gorm:"type:varchar(100);default:'general'"` 12 | AsbPath string `gorm:"type:longtext;default:''"` 13 | 14 | Type string `gorm:"type:varchar(30);not null;default:'single'"` 15 | } 16 | -------------------------------------------------------------------------------- /.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: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/models/record.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // Record define record table in db 4 | type Record struct { 5 | Model 6 | ReqURL string `gorm:"type:longtext;"` 7 | ReqMethod string `gorm:"type:varchar(30);"` 8 | ReqBody string `gorm:"type:longtext;"` 9 | ReqRaw string `gorm:"type:longtext;"` 10 | 11 | StatusCode int `gorm:"type:int;"` 12 | ResBody string `gorm:"type:longtext;"` 13 | ResTime float64 `gorm:"type:float64;"` 14 | ResLength int `gorm:"type:int;"` 15 | ResRaw string `gorm:"type:longtext;"` 16 | Issues string `gorm:"type:varchar(100);"` 17 | Risk string `gorm:"type:varchar(100);"` 18 | ExtraOutput string `gorm:"type:longtext;"` 19 | ScanID string `gorm:"type:longtext;"` 20 | // Issues []string `gorm:"type:varchar(100);"` 21 | 22 | RawFile string `gorm:"type:longtext"` 23 | // ChechSum string `gorm:"type:varchar(30);unique_index"` 24 | } 25 | -------------------------------------------------------------------------------- /database/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 | id, _ := uuid.NewUUID() 23 | rawScanID, _ := id.MarshalText() 24 | 25 | var shortSigns []string 26 | for _, signName := range signs { 27 | shortSigns = append(shortSigns, filepath.Base(signName)) 28 | } 29 | signatures := strings.Join(shortSigns[:], ",") 30 | 31 | signObj := models.Scans{ 32 | ScanID: fmt.Sprintf("%x", rawScanID), 33 | SignatureID: signatures, 34 | OutputDir: options.Output, 35 | Mode: mode, 36 | } 37 | DB.Create(&signObj) 38 | return fmt.Sprintf("%v", signObj.ScanID) 39 | } 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jaeles-project/jaeles 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Jeffail/gabs/v2 v2.4.0 7 | github.com/Shopify/yaml v2.1.0+incompatible 8 | github.com/appleboy/gin-jwt/v2 v2.6.3 9 | github.com/chromedp/cdproto v0.0.0-20200209033844-7e00b02ea7d2 10 | github.com/chromedp/chromedp v0.5.3 11 | github.com/fatih/color v1.9.0 12 | github.com/gin-contrib/cors v1.3.0 13 | github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2 14 | github.com/gin-gonic/gin v1.5.0 15 | github.com/go-resty/resty/v2 v2.1.0 16 | github.com/google/uuid v1.1.1 17 | github.com/gorilla/websocket v1.4.1 18 | github.com/jinzhu/gorm v1.9.12 19 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect 20 | github.com/mitchellh/go-homedir v1.1.0 21 | github.com/parnurzeal/gorequest v0.2.16 22 | github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff 23 | github.com/sirupsen/logrus v1.4.2 24 | github.com/spf13/cobra v0.0.5 25 | github.com/spf13/viper v1.6.2 26 | github.com/thoas/go-funk v0.5.0 27 | github.com/x-cray/logrus-prefixed-formatter v0.5.2 28 | gopkg.in/sourcemap.v1 v1.0.5 // indirect 29 | gopkg.in/src-d/go-git.v4 v4.13.1 30 | gopkg.in/yaml.v2 v2.2.8 31 | moul.io/http2curl v1.0.0 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /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 | ScanID string 10 | ConfigFile string 11 | SummaryOutput string 12 | Output string 13 | PassiveOutput string 14 | LogFile string 15 | Proxy string 16 | Params []string 17 | Signs []string 18 | Excludes []string 19 | SelectedSigns []string 20 | GlobalVar map[string]string 21 | 22 | Concurrency int 23 | Threads int 24 | Delay int 25 | SaveRaw bool 26 | Timeout int 27 | Refresh int 28 | Retry int 29 | Verbose bool 30 | Debug bool 31 | NoBackGround bool 32 | NoOutput bool 33 | EnablePassive bool 34 | Server Server 35 | } 36 | 37 | // Server options for api server 38 | type Server struct { 39 | DBPath string 40 | Bind string 41 | JWTSecret string 42 | Cors string 43 | DefaultSign string 44 | SecretCollab string 45 | Username string 46 | Password string 47 | } 48 | 49 | // Job define job for running routine 50 | type Job struct { 51 | URL string 52 | Sign Signature 53 | } 54 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /libs/http.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | // Record all information about request 4 | type Record struct { 5 | OriginReq Request 6 | OriginRes Response 7 | Request Request 8 | Response Response 9 | Sign Signature 10 | RawOutput string 11 | ExtraOutput string 12 | ScanID string 13 | } 14 | 15 | // Request all information about request 16 | type Request struct { 17 | Engine string 18 | Timeout int 19 | Repeat int 20 | Scheme string 21 | Host string 22 | Port string 23 | Path string 24 | URL string 25 | Proxy string 26 | Method string 27 | Redirect bool 28 | UseTemplateHeader bool 29 | Headers []map[string]string 30 | Values []map[string]string 31 | Body string 32 | Beautify string 33 | MiddlewareOutput string 34 | Raw string 35 | Conditions []string 36 | Middlewares []string 37 | Conclusions []string 38 | Detections []string 39 | Generators []string 40 | Encoding string 41 | Target map[string]string 42 | } 43 | 44 | // Response all information about response 45 | type Response struct { 46 | HasPopUp bool 47 | StatusCode int 48 | Status string 49 | Headers []map[string]string 50 | Body string 51 | ResponseTime float64 52 | Length int 53 | Beautify string 54 | } 55 | -------------------------------------------------------------------------------- /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 | // if dbObj := DB.Create(&recObj); dbObj.Error != nil != true { 50 | // fmt.Printf("[Error] something woringcreate record\n") 51 | // } 52 | } 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /core/background.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/utils" 6 | "net/url" 7 | "time" 8 | 9 | "github.com/Jeffail/gabs/v2" 10 | "github.com/jaeles-project/jaeles/database" 11 | "github.com/jaeles-project/jaeles/libs" 12 | "github.com/parnurzeal/gorequest" 13 | ) 14 | 15 | // Background main function to call other background task 16 | func Background(options libs.Options) { 17 | utils.DebugF("Checking backround task") 18 | time.Sleep(time.Duration(options.Refresh) * time.Second) 19 | PollingLog() 20 | PickupLog(options) 21 | // @TODO: Add passive signature for analyzer each request 22 | } 23 | 24 | // PollingLog polling all request with their 25 | func PollingLog() { 26 | objs := database.GetUnPollReq() 27 | for _, obj := range objs { 28 | // sending part 29 | secret := url.QueryEscape(database.GetSecretbyCollab(obj.Secret)) 30 | url := fmt.Sprintf("http://polling.burpcollaborator.net/burpresults?biid=%v", secret) 31 | request := gorequest.New() 32 | _, response, errs := request.Get(url).End() 33 | if len(errs) > 0 { 34 | continue 35 | } 36 | jsonParsed, _ := gabs.ParseJSON([]byte(response)) 37 | exists := jsonParsed.Exists("responses") 38 | if exists == false { 39 | continue 40 | } else { 41 | for _, element := range jsonParsed.Path("responses").Children() { 42 | // import this to DB so we don't miss in other detect 43 | database.ImportOutOfBand(fmt.Sprintf("%v", element)) 44 | } 45 | } 46 | } 47 | } 48 | 49 | // PickupLog pickup request that's have log coming back 50 | func PickupLog(options libs.Options) { 51 | objs := database.GetUnPollReq() 52 | for _, obj := range objs { 53 | interactString := obj.InteractionString 54 | data := database.GetOOB(interactString) 55 | if data != "" { 56 | var rec libs.Record 57 | rec.Request.Beautify = obj.Req 58 | rec.Response.Beautify = obj.Res 59 | rec.ExtraOutput = data 60 | 61 | if options.NoOutput == false { 62 | outputName := StoreOutput(rec, options) 63 | rec.RawOutput = outputName 64 | database.ImportRecord(rec) 65 | } 66 | 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/config.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/jaeles-project/jaeles/database/models" 8 | "github.com/parnurzeal/gorequest" 9 | ) 10 | 11 | // ImportBurpCollab used to init some default config 12 | func ImportBurpCollab(burpcollab string) string { 13 | var conObj models.Configuration 14 | DB.Where(models.Configuration{Name: "BurpCollab"}).Assign(models.Configuration{Value: burpcollab}).FirstOrCreate(&conObj) 15 | ImportBurpCollabResponse(burpcollab, "") 16 | return burpcollab 17 | } 18 | 19 | // GetDefaultBurpCollab update default sign 20 | func GetDefaultBurpCollab() string { 21 | var conObj models.Configuration 22 | DB.Where("name = ?", "BurpCollab").First(&conObj) 23 | return conObj.Value 24 | } 25 | 26 | // ImportBurpCollabResponse used to init some default config 27 | func ImportBurpCollabResponse(burpcollab string, data string) string { 28 | burpcollabres := data 29 | if burpcollabres == "" { 30 | url := fmt.Sprintf("http://%v?original=true", burpcollab) 31 | _, burpcollabres, _ := gorequest.New().Get(url).End() 32 | burpcollabres = strings.Replace(burpcollabres, "", "", -1) 33 | burpcollabres = strings.Replace(burpcollabres, "", "", -1) 34 | } 35 | 36 | var conObj models.Configuration 37 | DB.Where(models.Configuration{Name: "BurpCollabResponse"}).Assign(models.Configuration{Value: burpcollabres}).FirstOrCreate(&conObj) 38 | return burpcollabres 39 | } 40 | 41 | // GetDefaultBurpRes update default sign 42 | func GetDefaultBurpRes() string { 43 | var conObj models.Configuration 44 | DB.Where("name = ?", "BurpCollabResponse").First(&conObj) 45 | return conObj.Value 46 | } 47 | 48 | // InitConfigSign used to init some default config 49 | func InitConfigSign() { 50 | conObj := models.Configuration{ 51 | Name: "DefaultSign", 52 | Value: "*", 53 | } 54 | DB.Create(&conObj) 55 | } 56 | 57 | // GetDefaultSign update default sign 58 | func GetDefaultSign() string { 59 | var conObj models.Configuration 60 | DB.Where("name = ?", "DefaultSign").First(&conObj) 61 | return conObj.Value 62 | } 63 | 64 | // UpdateDefaultSign update default sign 65 | func UpdateDefaultSign(sign string) { 66 | var conObj models.Configuration 67 | DB.Where("name = ?", "DefaultSign").First(&conObj) 68 | conObj.Value = sign 69 | DB.Save(&conObj) 70 | } 71 | 72 | // UpdateDefaultBurpCollab update default burp collab 73 | func UpdateDefaultBurpCollab(collab string) { 74 | var conObj models.Configuration 75 | DB.Where("name = ?", "BurpCollab").First(&conObj) 76 | conObj.Value = collab 77 | DB.Save(&conObj) 78 | } 79 | -------------------------------------------------------------------------------- /core/update.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | 8 | "github.com/jaeles-project/jaeles/libs" 9 | "github.com/jaeles-project/jaeles/utils" 10 | "gopkg.in/src-d/go-git.v4" 11 | "gopkg.in/src-d/go-git.v4/plumbing/transport/http" 12 | ) 13 | 14 | // UpdatePlugins update latest UI and Plugins from default repo 15 | func UpdatePlugins(options libs.Options) { 16 | pluginPath := path.Join(options.RootFolder, "plugins") 17 | url := libs.UIREPO 18 | utils.GoodF("Cloning Plugins from: %v", url) 19 | if utils.FolderExists(pluginPath) { 20 | utils.InforF("Remove: %v", pluginPath) 21 | os.RemoveAll(pluginPath) 22 | } 23 | r, err := git.PlainClone(pluginPath, false, &git.CloneOptions{ 24 | URL: url, 25 | RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, 26 | Depth: 1, 27 | }) 28 | if err != nil { 29 | fmt.Println("Error to clone Plugins repo") 30 | } else { 31 | _, err = r.Head() 32 | if err != nil { 33 | fmt.Println("Error to clone Plugins repo") 34 | } 35 | } 36 | } 37 | 38 | // UpdateSignature update latest UI from UI repo 39 | func UpdateSignature(options libs.Options, customRepo string) { 40 | signPath := path.Join(options.RootFolder, "base-signatures") 41 | passivePath := path.Join(signPath, "passives") 42 | resourcesPath := path.Join(signPath, "resources") 43 | 44 | url := libs.SIGNREPO 45 | if customRepo != "" { 46 | url = customRepo 47 | } 48 | 49 | utils.GoodF("Cloning Signature from: %v", url) 50 | if utils.FolderExists(signPath) { 51 | utils.InforF("Remove: %v", signPath) 52 | os.RemoveAll(signPath) 53 | os.RemoveAll(options.PassiveFolder) 54 | os.RemoveAll(options.PassiveFolder) 55 | } 56 | _, err := git.PlainClone(signPath, false, &git.CloneOptions{ 57 | Auth: &http.BasicAuth{ 58 | Username: options.Server.Username, 59 | Password: options.Server.Password, 60 | }, 61 | URL: url, 62 | RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, 63 | Depth: 1, 64 | Progress: os.Stdout, 65 | }) 66 | 67 | if err != nil { 68 | utils.ErrorF("Error to clone Signature repo: %v", url) 69 | return 70 | } 71 | 72 | // move passive signatures to default passive 73 | if utils.FolderExists(passivePath) { 74 | utils.MoveFolder(passivePath, options.PassiveFolder) 75 | } 76 | if utils.FolderExists(resourcesPath) { 77 | utils.MoveFolder(resourcesPath, options.ResourcesFolder) 78 | } 79 | 80 | } 81 | 82 | // // UpdateOutOfBand renew things in Out of band check 83 | // func UpdateOutOfBand(options libs.Options) { 84 | // // http 85 | // // dns 86 | // } 87 | -------------------------------------------------------------------------------- /database/dnsbin.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | // Use to gen bunch of DNS on dns.requestbin.net 4 | 5 | import ( 6 | "crypto/tls" 7 | "fmt" 8 | "net/url" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | "github.com/Jeffail/gabs/v2" 14 | "github.com/gorilla/websocket" 15 | "github.com/parnurzeal/gorequest" 16 | ) 17 | 18 | // NewDNSBin create new dnsbin 19 | func NewDNSBin() string { 20 | var dnsbin string 21 | // var .fbbbf336914aa6bd9b58.d.requestbin.net 22 | addr := "dns.requestbin.net:8080" 23 | u := url.URL{Scheme: "ws", Host: addr, Path: "/dns"} 24 | 25 | // init a connection 26 | c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) 27 | if err != nil { 28 | return "" 29 | } 30 | defer c.Close() 31 | done := make(chan struct{}) 32 | go func() { 33 | defer close(done) 34 | for { 35 | _, message, err := c.ReadMessage() 36 | if err != nil { 37 | return 38 | } 39 | jsonParsed, err := gabs.ParseJSON([]byte(message)) 40 | if err != nil { 41 | return 42 | } 43 | // jsonParsed.Path("master") 44 | prefix := strconv.FormatInt(time.Now().Unix(), 10) 45 | token := strings.Trim(fmt.Sprintf("%v", jsonParsed.Path("master")), `"`) 46 | dnsbin = fmt.Sprintf("%v.%v.d.requestbin.net", prefix, token) 47 | return 48 | } 49 | }() 50 | 51 | err = c.WriteMessage(websocket.TextMessage, []byte(``)) 52 | if err != nil { 53 | return dnsbin 54 | } 55 | time.Sleep(time.Second) 56 | c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) 57 | return dnsbin 58 | } 59 | 60 | // NewReqBin gen new http bin 61 | func NewReqBin() string { 62 | var reqbin string 63 | url := fmt.Sprintf("https://bin-api.pipedream.com/api/v2/http_endpoints") 64 | prefix := strconv.FormatInt(time.Now().Unix(), 10) 65 | // client := resty.New() 66 | client := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}) 67 | client.Post(url) 68 | body := fmt.Sprintf(`{"name":"%v","pvt":false}`, prefix) 69 | client.Send(body) 70 | _, resBody, _ := client.End() 71 | 72 | message := resBody 73 | // {"status":0,"message":"success","data":{"api_key":"enw9yvvawe47","name":"Untitled","pvt":false,"created_at":"2019-11-20T10:56:29.962Z"}} 74 | jsonParsed, err := gabs.ParseJSON([]byte(message)) 75 | if err != nil { 76 | return "" 77 | } 78 | // jsonParsed.Path("master") 79 | // prefix := strconv.FormatInt(time.Now().Unix(), 10) 80 | token := strings.Trim(fmt.Sprintf("%v", jsonParsed.Path("data.api_key")), `"`) 81 | reqbin = fmt.Sprintf("https://%v.x.pipedream.net/", token) 82 | if err != nil { 83 | return "" 84 | } 85 | 86 | return reqbin 87 | } 88 | 89 | // GetTS get current timestamp and return a string 90 | func GetTS() string { 91 | return strconv.FormatInt(time.Now().Unix(), 10) 92 | } 93 | -------------------------------------------------------------------------------- /database/sign.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/utils" 6 | "io/ioutil" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/Shopify/yaml" 11 | "github.com/jaeles-project/jaeles/database/models" 12 | "github.com/jaeles-project/jaeles/libs" 13 | ) 14 | 15 | // CleanSigns clean all signature 16 | func CleanSigns() { 17 | var signs []models.Signature 18 | DB.Find(&signs) 19 | DB.Unscoped().Delete(&signs) 20 | } 21 | 22 | // SelectSign select signature to gen request 23 | func SelectSign(signName string) []string { 24 | var signs []models.Signature 25 | DB.Find(&signs) 26 | 27 | //if signName == "*" || signName == "" { 28 | // DB.Find(&signs) 29 | //} else { 30 | // DB.Where("sign_id LIKE ? OR name LIKE ?", fmt.Sprintf("%%%v%%", signName), fmt.Sprintf("%%%v%%", signName)).Find(&signs) 31 | //} 32 | 33 | var selectedSigns []string 34 | for _, sign := range signs { 35 | if signName == "*" || signName == "" { 36 | selectedSigns = append(selectedSigns, sign.AsbPath) 37 | continue 38 | } 39 | // grep info 40 | info := fmt.Sprintf("%v|%v|%v|tech:%v", sign.SignID, sign.Name, sign.AsbPath, sign.Tech) 41 | if strings.Contains(info, signName) { 42 | selectedSigns = append(selectedSigns, sign.AsbPath) 43 | continue 44 | } 45 | r, err := regexp.Compile(signName) 46 | if err == nil { 47 | if r.MatchString(info) { 48 | selectedSigns = append(selectedSigns, sign.AsbPath) 49 | } 50 | } 51 | } 52 | return selectedSigns 53 | } 54 | 55 | // ImportSign import signature to DB 56 | func ImportSign(signPath string) { 57 | sign, err := ParseSignature(signPath) 58 | if err != nil { 59 | return 60 | } 61 | 62 | if sign.Info.Category == "" { 63 | if strings.Contains(sign.ID, "-") { 64 | sign.Info.Category = strings.Split(sign.ID, "-")[0] 65 | } else { 66 | sign.Info.Category = sign.ID 67 | } 68 | } 69 | if sign.Info.Name == "" { 70 | sign.Info.Name = sign.ID 71 | } 72 | 73 | signObj := models.Signature{ 74 | Name: sign.Info.Name, 75 | Category: sign.Info.Category, 76 | Risk: sign.Info.Risk, 77 | Tech: sign.Info.Tech, 78 | OS: sign.Info.OS, 79 | SignID: sign.ID, 80 | AsbPath: signPath, 81 | Type: sign.Type, 82 | } 83 | DB.Create(&signObj) 84 | } 85 | 86 | // ParseSign parsing YAML signature file 87 | func ParseSignature(signFile string) (sign libs.Signature, err error) { 88 | yamlFile, err := ioutil.ReadFile(signFile) 89 | if err != nil { 90 | utils.ErrorF("yamlFile.Get err #%v - %v", err, signFile) 91 | } 92 | err = yaml.Unmarshal(yamlFile, &sign) 93 | if err != nil { 94 | utils.ErrorF("Error: %v - %v", err, signFile) 95 | } 96 | // set some default value 97 | if sign.Info.Category == "" { 98 | if strings.Contains(sign.ID, "-") { 99 | sign.Info.Category = strings.Split(sign.ID, "-")[0] 100 | } else { 101 | sign.Info.Category = sign.ID 102 | } 103 | } 104 | if sign.Info.Name == "" { 105 | sign.Info.Name = sign.ID 106 | } 107 | if sign.Info.Risk == "" { 108 | sign.Info.Risk = "Potential" 109 | } 110 | return sign, err 111 | } 112 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /core/analyze.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/sha1" 5 | "fmt" 6 | "github.com/jaeles-project/jaeles/sender" 7 | "net/url" 8 | "os" 9 | "path" 10 | "strings" 11 | 12 | "github.com/fatih/color" 13 | "github.com/jaeles-project/jaeles/database" 14 | "github.com/jaeles-project/jaeles/libs" 15 | "github.com/jaeles-project/jaeles/utils" 16 | ) 17 | 18 | // Analyze run analyzer with each detections 19 | func Analyze(options libs.Options, record *libs.Record) { 20 | /* Analyze part */ 21 | if record.Request.Beautify == "" { 22 | record.Request.Beautify = sender.BeautifyRequest(record.Request) 23 | } 24 | if len(record.Request.Detections) <= 0 { 25 | return 26 | } 27 | 28 | for _, analyze := range record.Request.Detections { 29 | utils.DebugF("[Detection] %v", analyze) 30 | extra, result := RunDetector(*record, analyze) 31 | if extra != "" { 32 | record.ExtraOutput = extra 33 | } 34 | if result == true { 35 | if options.Verbose { 36 | color.Magenta("[Found] %v", analyze) 37 | } 38 | var outputName string 39 | if options.NoOutput == false { 40 | outputName = StoreOutput(*record, options) 41 | record.RawOutput = outputName 42 | database.ImportRecord(*record) 43 | } 44 | color.Green("[Vulnerable][%v] %v %v", record.Sign.Info.Risk, record.Request.URL, outputName) 45 | } 46 | } 47 | } 48 | 49 | // StoreOutput store vulnerable request to a file 50 | func StoreOutput(rec libs.Record, options libs.Options) string { 51 | // store output to a file 52 | if rec.Request.URL == "" { 53 | rec.Request.URL = rec.Request.Target["URL"] 54 | } 55 | head := fmt.Sprintf("[%v] - %v\n\n", rec.Sign.ID, rec.Request.URL) 56 | content := head 57 | if rec.Request.MiddlewareOutput != "" { 58 | content += strings.Join(rec.Request.Middlewares, "\n") 59 | content += fmt.Sprintf("\n%v\n", strings.Repeat("-", 50)) 60 | content += rec.Request.MiddlewareOutput 61 | } 62 | if rec.ExtraOutput != "" { 63 | content += strings.Join(rec.Request.Middlewares, "\n") 64 | content += fmt.Sprintf("\n%v\n", strings.Repeat("-", 50)) 65 | content += rec.ExtraOutput 66 | } 67 | if rec.ExtraOutput == "" && rec.Request.MiddlewareOutput == "" { 68 | content += rec.Request.Beautify 69 | content += fmt.Sprintf("\n%v\n", strings.Repeat("-", 50)) 70 | content += rec.Response.Beautify 71 | } 72 | 73 | // hash the content 74 | h := sha1.New() 75 | h.Write([]byte(content)) 76 | checksum := h.Sum(nil) 77 | 78 | parts := []string{options.Output} 79 | if rec.Request.URL == "" { 80 | parts = append(parts, rec.Request.Target["Domain"]) 81 | } else { 82 | u, _ := url.Parse(rec.Request.URL) 83 | parts = append(parts, u.Hostname()) 84 | } 85 | parts = append(parts, fmt.Sprintf("%v-%x", rec.Sign.ID, checksum)) 86 | 87 | p := path.Join(parts...) 88 | if _, err := os.Stat(path.Dir(p)); os.IsNotExist(err) { 89 | err = os.MkdirAll(path.Dir(p), 0750) 90 | if err != nil { 91 | utils.ErrorF("Error Write content to: %v", p) 92 | } 93 | } 94 | utils.WriteToFile(p, content) 95 | sum := fmt.Sprintf("%v - %v", strings.TrimSpace(head), p) 96 | utils.AppendToContent(options.SummaryOutput, sum) 97 | 98 | return p 99 | } 100 | -------------------------------------------------------------------------------- /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 | ) 14 | 15 | // InitConfig init config 16 | func InitConfig(options *libs.Options) { 17 | options.RootFolder = utils.NormalizePath(options.RootFolder) 18 | options.Server.DBPath = path.Join(options.RootFolder, "sqlite3.db") 19 | // init new root folder 20 | if !utils.FolderExists(options.RootFolder) { 21 | utils.InforF("Init new config at %v", options.RootFolder) 22 | os.MkdirAll(options.RootFolder, 0750) 23 | // cloning default repo 24 | UpdatePlugins(*options) 25 | UpdateSignature(*options, "") 26 | } 27 | 28 | configPath := path.Join(options.RootFolder, "config.yaml") 29 | v := viper.New() 30 | v.AddConfigPath(options.RootFolder) 31 | v.SetConfigName("config") 32 | v.SetConfigType("yaml") 33 | if !utils.FileExists(configPath) { 34 | utils.InforF("Write new config to: %v", configPath) 35 | // save default config if not exist 36 | bind := "http://127.0.0.1:5000" 37 | v.SetDefault("defaultSign", "*") 38 | v.SetDefault("cors", "*") 39 | // default credential 40 | v.SetDefault("username", "jaeles") 41 | v.SetDefault("password", utils.GenHash(utils.GetTS())[:10]) 42 | v.SetDefault("secret", utils.GenHash(utils.GetTS())) 43 | v.SetDefault("bind", bind) 44 | v.WriteConfigAs(configPath) 45 | 46 | } else { 47 | if options.Debug { 48 | utils.InforF("Load config from: %v", configPath) 49 | } 50 | b, _ := ioutil.ReadFile(configPath) 51 | v.ReadConfig(bytes.NewBuffer(b)) 52 | } 53 | // config.defaultSign = fmt.Sprintf("%v", v.Get("defaultSign")) 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 | 84 | // create output folder 85 | var err error 86 | err = os.MkdirAll(options.Output, 0750) 87 | if err != nil && options.NoOutput == false { 88 | fmt.Fprintf(os.Stderr, "failed to create output directory: %s\n", err) 89 | os.Exit(1) 90 | } 91 | if options.SummaryOutput == "" { 92 | options.SummaryOutput = path.Join(options.Output, "jaeles-summary.txt") 93 | } 94 | utils.InforF("Summary output: %v", options.SummaryOutput) 95 | } 96 | -------------------------------------------------------------------------------- /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.Printf("%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.Printf("%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 | -------------------------------------------------------------------------------- /cmd/server.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "path/filepath" 7 | "sync" 8 | 9 | "github.com/jaeles-project/jaeles/core" 10 | "github.com/jaeles-project/jaeles/database" 11 | "github.com/jaeles-project/jaeles/libs" 12 | "github.com/jaeles-project/jaeles/server" 13 | "github.com/jaeles-project/jaeles/utils" 14 | 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var serverCmd *cobra.Command 19 | 20 | func init() { 21 | // byeCmd represents the bye command 22 | var serverCmd = &cobra.Command{ 23 | Use: "server", 24 | Short: "Start API server", 25 | Long: libs.Banner(), RunE: runServer, 26 | } 27 | //serverCmd.Flags().StringSliceP("sign", "s", []string{}, "Signature selector (Multiple -s flags are accepted)") 28 | serverCmd.Flags().String("host", "127.0.0.1", "IP address to bind the server") 29 | serverCmd.Flags().String("port", "5000", "Port") 30 | RootCmd.AddCommand(serverCmd) 31 | 32 | } 33 | 34 | func runServer(cmd *cobra.Command, args []string) error { 35 | SelectSign() 36 | // prepare DB stuff 37 | if options.Server.Username != "" { 38 | database.CreateUser(options.Server.Username, options.Server.Password) 39 | } 40 | // reload signature 41 | SignFolder, _ := filepath.Abs(path.Join(options.RootFolder, "base-signatures")) 42 | allSigns := utils.GetFileNames(SignFolder, ".yaml") 43 | if allSigns != nil { 44 | for _, signFile := range allSigns { 45 | database.ImportSign(signFile) 46 | } 47 | } 48 | database.InitConfigSign() 49 | 50 | result := make(chan libs.Record) 51 | jobs := make(chan libs.Job) 52 | 53 | go func() { 54 | for { 55 | record := <-result 56 | utils.InforF("[Receive] %v %v \n", record.OriginReq.Method, record.OriginReq.URL) 57 | for _, signFile := range options.SelectedSigns { 58 | sign, err := core.ParseSign(signFile) 59 | if err != nil { 60 | utils.ErrorF("Error loading sign: %v\n", signFile) 61 | continue 62 | } 63 | // parse sign as list or single 64 | if sign.Type != "fuzz" { 65 | url := record.OriginReq.URL 66 | jobs <- libs.Job{URL: url, Sign: sign} 67 | 68 | } else { 69 | 70 | fuzzSign := sign 71 | fuzzSign.Requests = []libs.Request{} 72 | for _, req := range sign.Requests { 73 | core.ParseRequestFromServer(&record, req, sign) 74 | // append all requests in sign with request from api 75 | req.Method = record.Request.Method 76 | req.URL = record.Request.URL 77 | req.Headers = record.Request.Headers 78 | req.Body = record.Request.Body 79 | fuzzSign.Requests = append(fuzzSign.Requests, req) 80 | 81 | } 82 | url := record.OriginReq.URL 83 | jobs <- libs.Job{URL: url, Sign: fuzzSign} 84 | 85 | } 86 | } 87 | 88 | } 89 | }() 90 | 91 | /* Start sending request here */ 92 | var wg sync.WaitGroup 93 | for i := 0; i < options.Concurrency; i++ { 94 | wg.Add(1) 95 | go func() { 96 | defer wg.Done() 97 | for job := range jobs { 98 | sign := job.Sign 99 | url := job.URL 100 | RunJob(url, sign, options) 101 | } 102 | }() 103 | } 104 | 105 | host, _ := cmd.Flags().GetString("host") 106 | port, _ := cmd.Flags().GetString("port") 107 | bind := fmt.Sprintf("%v:%v", host, port) 108 | options.Server.Bind = bind 109 | utils.InforF("Start API server at %v", fmt.Sprintf("http://%v/#/", bind)) 110 | 111 | server.InitRouter(options, result) 112 | // wg.Wait() 113 | return nil 114 | } 115 | -------------------------------------------------------------------------------- /core/passive_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/libs" 6 | "testing" 7 | ) 8 | 9 | func TestPassiveCheck(t *testing.T) { 10 | var record libs.Record 11 | //record.Response.Beautify = "SQLite3::SQLException foo" 12 | record.Response.Beautify = "Warning: file_exists(a" 13 | var passive libs.Passive 14 | passive = defaultPassive() 15 | 16 | for _, rule := range passive.Rules { 17 | for _, detectionString := range rule.Detections { 18 | fmt.Println(detectionString) 19 | _, result := RunDetector(record, detectionString) 20 | fmt.Println(result) 21 | if !result { 22 | t.Errorf("Error resolve variable") 23 | } 24 | } 25 | } 26 | } 27 | 28 | func TestRegexSearch(t *testing.T) { 29 | raw := "SQLite3::SQLException foo" 30 | 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)" 31 | 32 | result := RegexSearch(raw, regex) 33 | if !result { 34 | t.Errorf("Error resolve variable") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/controllers.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/jaeles-project/jaeles/libs" 9 | 10 | "github.com/fatih/color" 11 | "github.com/gin-gonic/gin" 12 | "github.com/jaeles-project/jaeles/core" 13 | ) 14 | 15 | // RequestData struct for recive request from burp 16 | type RequestData struct { 17 | RawReq string `json:"req"` 18 | RawRes string `json:"res"` 19 | URL string `json:"url"` 20 | } 21 | 22 | // SetBurpCollab setup Burp 23 | func SetBurpCollab(c *gin.Context) { 24 | c.JSON(200, gin.H{ 25 | "status": "200", 26 | "message": "Got it", 27 | }) 28 | } 29 | 30 | // ParseRaw Get Raw Burp Request in base64 encode 31 | func ParseRaw(c *gin.Context) { 32 | // result <- record 33 | // core data 34 | var reqData RequestData 35 | // c.BindJSON(&reqData) 36 | err := c.ShouldBindJSON(&reqData) 37 | if err != nil { 38 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 39 | } 40 | 41 | go func() { 42 | var record libs.Record 43 | // core data 44 | rawReq := reqData.RawReq 45 | rawRes := reqData.RawRes 46 | 47 | req, err := base64.StdEncoding.DecodeString(rawReq) 48 | if err != nil { 49 | c.JSON(500, gin.H{ 50 | "message": "error decode request", 51 | "status": "Error", 52 | }) 53 | } 54 | record.OriginReq = core.ParseBurpRequest(string(req)) 55 | /* Response part */ 56 | if rawRes != "" { 57 | // response stuff 58 | res, err := base64.StdEncoding.DecodeString(rawRes) 59 | if err != nil { 60 | c.JSON(500, gin.H{ 61 | "message": "error decode response", 62 | "status": "Error", 63 | }) 64 | } 65 | record.OriginRes = core.ParseBurpResponse(string(req), string(res)) 66 | } 67 | 68 | color.Green("-- got from gin") 69 | fmt.Println(record.OriginReq.URL) 70 | color.Green("-- done from gin") 71 | // result <- record 72 | }() 73 | 74 | c.JSON(200, gin.H{ 75 | "status": "200", 76 | "message": "Got it", 77 | }) 78 | } 79 | 80 | // ReceiveRequest is handler to got request from Burp 81 | func ReceiveRequest(result chan libs.Record) gin.HandlerFunc { 82 | return func(c *gin.Context) { 83 | cCp := c.Copy() 84 | var reqData RequestData 85 | err := cCp.ShouldBindJSON(&reqData) 86 | if err != nil { 87 | c.JSON(200, gin.H{ 88 | "status": "500", 89 | "message": "Error parsing JSON data", 90 | }) 91 | return 92 | } 93 | var record libs.Record 94 | // core data 95 | rawReq := reqData.RawReq 96 | rawRes := reqData.RawRes 97 | URL := reqData.URL 98 | 99 | // var record libs.Record 100 | req, err := base64.StdEncoding.DecodeString(rawReq) 101 | if err != nil { 102 | c.JSON(200, gin.H{ 103 | "status": "500", 104 | "message": "Error parsing request", 105 | }) 106 | return 107 | } 108 | record.OriginReq = core.ParseBurpRequest(string(req)) 109 | if URL != "" { 110 | record.OriginReq.URL = URL 111 | } 112 | //fmt.Printf("[Recive] %v %v \n", record.OriginReq.Method, record.OriginReq.URL) 113 | 114 | /* Response part */ 115 | if rawRes != "" { 116 | // response stuff 117 | res, err := base64.StdEncoding.DecodeString(rawRes) 118 | if err != nil { 119 | c.JSON(200, gin.H{ 120 | "status": "500", 121 | "message": "Error parsing response", 122 | }) 123 | } 124 | record.OriginRes = core.ParseBurpResponse(string(req), string(res)) 125 | } 126 | result <- record 127 | 128 | c.JSON(200, gin.H{ 129 | "status": "200", 130 | "message": "Got it", 131 | }) 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /server/api.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/jaeles-project/jaeles/database" 8 | "github.com/jaeles-project/jaeles/database/models" 9 | ) 10 | 11 | // Ping testing authenticated connection 12 | func Ping(c *gin.Context) { 13 | c.JSON(200, gin.H{ 14 | "status": "200", 15 | "message": "pong", 16 | }) 17 | } 18 | 19 | // GetStats return stat data 20 | func GetStats(c *gin.Context) { 21 | var info []models.Record 22 | database.DB.Where("risk = ?", "Info").Find(&info) 23 | var potential []models.Record 24 | database.DB.Where("risk = ?", "Potential").Find(&potential) 25 | var low []models.Record 26 | database.DB.Where("risk = ?", "Low").Find(&low) 27 | var medium []models.Record 28 | database.DB.Where("risk = ?", "Medium").Find(&medium) 29 | var high []models.Record 30 | database.DB.Where("risk = ?", "High").Find(&high) 31 | var critical []models.Record 32 | database.DB.Where("risk = ?", "Critical").Find(&critical) 33 | 34 | stats := []int{ 35 | len(info), 36 | len(potential), 37 | len(low), 38 | len(medium), 39 | len(high), 40 | len(critical), 41 | } 42 | 43 | c.JSON(200, gin.H{ 44 | "status": "200", 45 | "message": "Success", 46 | "stats": stats, 47 | }) 48 | } 49 | 50 | // GetSignSummary return signature stat 51 | func GetSignSummary(c *gin.Context) { 52 | var signs []models.Signature 53 | var categories []string 54 | var data []int 55 | database.DB.Find(&signs).Pluck("DISTINCT category", &categories) 56 | // stats := make(map[string]int) 57 | for _, category := range categories { 58 | var signatures []models.Signature 59 | database.DB.Where("category = ?", category).Find(&signatures) 60 | data = append(data, len(signatures)) 61 | } 62 | 63 | c.JSON(200, gin.H{ 64 | "status": "200", 65 | "message": "Success", 66 | "categories": categories, 67 | "data": data, 68 | }) 69 | } 70 | 71 | // GetSigns return signature record 72 | func GetSigns(c *gin.Context) { 73 | var signs []models.Signature 74 | database.DB.Find(&signs) 75 | 76 | c.JSON(200, gin.H{ 77 | "status": "200", 78 | "message": "Success", 79 | "signatures": signs, 80 | }) 81 | } 82 | 83 | // GetAllScan return all scans 84 | func GetAllScan(c *gin.Context) { 85 | var scans []models.Scans 86 | database.DB.Find(&scans) 87 | 88 | // remove empty scan 89 | var realScans []models.Scans 90 | for _, scan := range scans { 91 | var rec models.Record 92 | database.DB.First(&rec, "scan_id = ?", scan.ScanID) 93 | if rec.ScanID != "" { 94 | realScans = append(realScans, scan) 95 | } 96 | } 97 | 98 | c.JSON(200, gin.H{ 99 | "status": "200", 100 | "message": "Success", 101 | "scans": realScans, 102 | }) 103 | } 104 | 105 | // GetRecords get record by scan ID 106 | func GetRecords(c *gin.Context) { 107 | sid := c.Param("sid") 108 | var records []models.Record 109 | database.DB.Where("scan_id = ?", sid).Find(&records) 110 | 111 | c.JSON(200, gin.H{ 112 | "status": "200", 113 | "message": "Success", 114 | "records": records, 115 | }) 116 | } 117 | 118 | // GetRecord get record detail by record ID 119 | func GetRecord(c *gin.Context) { 120 | rid := c.Param("rid") 121 | var record models.Record 122 | database.DB.Where("id = ?", rid).First(&record) 123 | 124 | c.JSON(200, gin.H{ 125 | "status": "200", 126 | "message": "Success", 127 | "record": record, 128 | }) 129 | } 130 | 131 | // SignConfig config 132 | type SignConfig struct { 133 | Value string `json:"sign"` 134 | } 135 | 136 | // UpdateDefaultSign geet record by scan 137 | func UpdateDefaultSign(c *gin.Context) { 138 | var signConfig SignConfig 139 | err := c.ShouldBindJSON(&signConfig) 140 | if err != nil { 141 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 142 | } 143 | 144 | database.UpdateDefaultSign(signConfig.Value) 145 | c.JSON(200, gin.H{ 146 | "status": "200", 147 | "message": "Update Defeult sign success", 148 | }) 149 | } 150 | -------------------------------------------------------------------------------- /server/routers.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "path" 7 | "time" 8 | 9 | jwt "github.com/appleboy/gin-jwt/v2" 10 | "github.com/jaeles-project/jaeles/database" 11 | "github.com/jaeles-project/jaeles/libs" 12 | "github.com/jaeles-project/jaeles/utils" 13 | 14 | "github.com/gin-contrib/cors" 15 | "github.com/gin-contrib/static" 16 | "github.com/gin-gonic/gin" 17 | ) 18 | 19 | type login struct { 20 | Username string `form:"username" json:"username" binding:"required"` 21 | Password string `form:"password" json:"password" binding:"required"` 22 | } 23 | 24 | var identityKey = "id" 25 | 26 | // User struct 27 | type User struct { 28 | UserName string 29 | Role string 30 | Email string 31 | IsAdmin bool 32 | } 33 | 34 | // InitRouter start point of api server 35 | func InitRouter(options libs.Options, result chan libs.Record) { 36 | // turn off Gin debug mode 37 | if !options.Debug { 38 | gin.SetMode(gin.ReleaseMode) 39 | } 40 | r := gin.New() 41 | r.Use(gin.Logger()) 42 | r.Use(gin.Recovery()) 43 | 44 | // default is ~/.jaeles/ui/ 45 | uiPath := path.Join(options.RootFolder, "/plugins/ui") 46 | r.Use(static.Serve("/", static.LocalFile(uiPath, true))) 47 | 48 | allowOrigin := "*" 49 | secret := "something you have to change" 50 | if options.Server.JWTSecret != "" { 51 | secret = options.Server.JWTSecret 52 | } 53 | if options.Server.Cors != "" { 54 | allowOrigin = options.Server.Cors 55 | } 56 | 57 | r.Use(cors.New(cors.Config{ 58 | AllowOrigins: []string{allowOrigin}, 59 | AllowMethods: []string{"POST", "GET", "OPTIONS"}, 60 | AllowHeaders: []string{"Authorization"}, 61 | AllowCredentials: true, 62 | MaxAge: 24 * time.Hour, 63 | })) 64 | 65 | // the jwt middleware 66 | authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{ 67 | Realm: "jaeles server", 68 | Key: []byte(secret), 69 | Timeout: time.Hour * 360, 70 | MaxRefresh: time.Hour * 720, 71 | IdentityKey: identityKey, 72 | PayloadFunc: func(data interface{}) jwt.MapClaims { 73 | if v, ok := data.(*User); ok { 74 | return jwt.MapClaims{ 75 | identityKey: v.Role, 76 | } 77 | } 78 | return jwt.MapClaims{} 79 | }, 80 | IdentityHandler: func(c *gin.Context) interface{} { 81 | claims := jwt.ExtractClaims(c) 82 | return &User{ 83 | Role: claims[identityKey].(string), 84 | } 85 | }, 86 | Authenticator: func(c *gin.Context) (interface{}, error) { 87 | var loginVals login 88 | err := c.ShouldBindJSON(&loginVals) 89 | if err != nil { 90 | return "", jwt.ErrMissingLoginValues 91 | } 92 | username := loginVals.Username 93 | password := loginVals.Password 94 | 95 | // compare hashed password 96 | realPassword := database.SelectUser(username) 97 | if utils.GenHash(password) == realPassword { 98 | return &User{ 99 | UserName: username, 100 | // only have one role for now 101 | Role: "admin", 102 | Email: username, 103 | IsAdmin: true, 104 | }, nil 105 | } 106 | 107 | return nil, jwt.ErrFailedAuthentication 108 | }, 109 | Authorizator: func(data interface{}, c *gin.Context) bool { 110 | // @TODO: Disable authorization for now 111 | if v, ok := data.(*User); ok && v.Role == "admin" { 112 | return true 113 | } 114 | return false 115 | // return true 116 | }, 117 | Unauthorized: func(c *gin.Context, code int, message string) { 118 | c.JSON(code, gin.H{ 119 | "code": code, 120 | "message": message, 121 | }) 122 | }, 123 | TokenLookup: "header: Authorization, query: token, cookie: jwt", 124 | TokenHeadName: "Jaeles", 125 | TimeFunc: time.Now, 126 | }) 127 | 128 | if err != nil { 129 | log.Fatal("JWT Error:" + err.Error()) 130 | } 131 | 132 | r.POST("/auth/login", authMiddleware.LoginHandler) 133 | 134 | r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) { 135 | claims := jwt.ExtractClaims(c) 136 | log.Printf("NoRoute claims: %#v\n", claims) 137 | c.JSON(404, gin.H{"code": "404", "message": "Page not found"}) 138 | }) 139 | 140 | auth := r.Group("/api") 141 | // Refresh time can be longer than token timeout 142 | auth.GET("/refresh_token", authMiddleware.RefreshHandler) 143 | auth.Use(authMiddleware.MiddlewareFunc()) 144 | { 145 | auth.GET("/ping", Ping) 146 | auth.POST("/parse", ReceiveRequest(result)) 147 | auth.POST("/config/sign", UpdateDefaultSign) 148 | auth.GET("/stats/vuln", GetStats) 149 | auth.GET("/stats/sign", GetSignSummary) 150 | auth.GET("/signatures", GetSigns) 151 | auth.GET("/scans", GetAllScan) 152 | auth.GET("/scan/:sid/", GetRecords) 153 | auth.GET("/record/:rid/", GetRecord) 154 | } 155 | 156 | if err := http.ListenAndServe(options.Server.Bind, r); err != nil { 157 | log.Fatal(err) 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /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 | fmt.Printf("[Sent][Chrome] %v \n", url) 26 | var res libs.Response 27 | 28 | // prepare the chrome options 29 | opts := append(chromedp.DefaultExecAllocatorOptions[:], 30 | chromedp.Flag("headless", true), 31 | chromedp.Flag("ignore-certificate-errors", true), 32 | chromedp.Flag("disable-gpu", true), 33 | chromedp.Flag("enable-automation", true), 34 | chromedp.Flag("disable-extensions", false), 35 | chromedp.Flag("disable-setuid-sandbox", true), 36 | ) 37 | 38 | if options.Debug { 39 | // show the chrome page in debug mode 40 | opts = append(chromedp.DefaultExecAllocatorOptions[:], 41 | chromedp.Flag("headless", false), 42 | chromedp.Flag("ignore-certificate-errors", true), 43 | chromedp.Flag("disable-gpu", true), 44 | chromedp.Flag("enable-automation", true), 45 | chromedp.Flag("disable-extensions", false), 46 | chromedp.Flag("disable-setuid-sandbox", true), 47 | ) 48 | } 49 | 50 | allocCtx, bcancel := chromedp.NewExecAllocator(context.Background(), opts...) 51 | allocCtx, bcancel = context.WithTimeout(allocCtx, time.Duration(options.Timeout)*time.Second) 52 | defer bcancel() 53 | chromeContext, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(log.Printf)) 54 | defer cancel() 55 | 56 | // catch the pop up 57 | chromedp.ListenTarget(chromeContext, func(event interface{}) { 58 | if _, ok := event.(*page.EventJavascriptDialogOpening); ok { 59 | // fmt.Println("closing alert:", ev.Message) 60 | utils.DebugF("Detecting Pop-up: %v", url) 61 | res.HasPopUp = true 62 | go func() { 63 | if err := chromedp.Run(chromeContext, 64 | page.HandleJavaScriptDialog(true), 65 | ); err != nil { 66 | res.HasPopUp = false 67 | } 68 | }() 69 | } 70 | }) 71 | timeStart := time.Now() 72 | // waiting time for the page to load 73 | waiting := time.Duration(1) 74 | if req.Timeout != 0 { 75 | waiting = time.Duration(req.Timeout) 76 | } 77 | // start Chrome and run given tasks 78 | err := chromedp.Run( 79 | chromeContext, 80 | chromeTask(chromeContext, url, 81 | // @TODO: add header here 82 | map[string]interface{}{}, 83 | &res), 84 | // wait for the page to load 85 | chromedp.Sleep(waiting*time.Second), 86 | chromedp.ActionFunc(func(ctx context.Context) error { 87 | node, err := dom.GetDocument().Do(ctx) 88 | if err != nil { 89 | return err 90 | } 91 | res.Body, err = dom.GetOuterHTML().WithNodeID(node.NodeID).Do(ctx) 92 | return err 93 | }), 94 | ) 95 | res.ResponseTime = time.Since(timeStart).Seconds() 96 | if err != nil { 97 | utils.ErrorF("%v", err) 98 | return res, err 99 | } 100 | 101 | res.Beautify = fmt.Sprintf("%v\n%v\n", res.StatusCode, res.Body) 102 | return res, err 103 | } 104 | 105 | // chrome debug protocol tasks to run 106 | func chromeTask(chromeContext context.Context, url string, requestHeaders map[string]interface{}, res *libs.Response) chromedp.Tasks { 107 | // setup a listener for events 108 | chromedp.ListenTarget(chromeContext, func(event interface{}) { 109 | // get which type of event it is 110 | switch msg := event.(type) { 111 | // just before request sent 112 | case *network.EventRequestWillBeSent: 113 | request := msg.Request 114 | // see if we have been redirected 115 | // if so, change the URL that we are tracking 116 | if msg.RedirectResponse != nil { 117 | url = request.URL 118 | } 119 | 120 | // once we have the full response 121 | case *network.EventResponseReceived: 122 | response := msg.Response 123 | // is the request we want the status/headers on? 124 | if response.URL == url { 125 | res.StatusCode = int(response.Status) 126 | // fmt.Printf(" url: %s\n", response.URL) 127 | // fmt.Printf(" status code: %d\n", res.StatusCode) 128 | for k, v := range response.Headers { 129 | header := make(map[string]string) 130 | // fmt.Println(k, v) 131 | header[k] = v.(string) 132 | res.Headers = append(res.Headers, header) 133 | } 134 | } 135 | } 136 | 137 | }) 138 | 139 | return chromedp.Tasks{ 140 | network.Enable(), 141 | network.SetExtraHTTPHeaders(network.Headers(requestHeaders)), 142 | chromedp.Navigate(url), 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /core/middleware.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/url" 7 | "os" 8 | "os/exec" 9 | "path" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/jaeles-project/jaeles/libs" 14 | "github.com/jaeles-project/jaeles/utils" 15 | "github.com/thoas/go-funk" 16 | 17 | "github.com/robertkrimen/otto" 18 | ) 19 | 20 | // @NOTE: Middleware allow execute command on your machine 21 | // So make sure you read the signature before you run it 22 | 23 | // MiddleWare is main function for middleware 24 | func MiddleWare(rec *libs.Record, options libs.Options) { 25 | // func MiddleWare(req *libs.Request) { 26 | vm := otto.New() 27 | var middlewareOutput string 28 | 29 | vm.Set("Host2IP", func(call otto.FunctionCall) otto.Value { 30 | var realHeaders []map[string]string 31 | for _, head := range rec.Request.Headers { 32 | containHost := funk.Contains(head, "Host") 33 | if containHost == false { 34 | realHeaders = append(realHeaders, head) 35 | } 36 | } 37 | HostHeader := Host2IP(rec.Request.URL) 38 | if !funk.IsEmpty(HostHeader) { 39 | realHeaders = append(realHeaders, HostHeader) 40 | } 41 | rec.Request.Headers = realHeaders 42 | return otto.Value{} 43 | }) 44 | 45 | vm.Set("InvokeCmd", func(call otto.FunctionCall) otto.Value { 46 | rawCmd := call.Argument(0).String() 47 | result := InvokeCmd(rec, rawCmd) 48 | middlewareOutput += result 49 | utils.DebugF(result) 50 | return otto.Value{} 51 | }) 52 | 53 | vm.Set("TurboIntruder", func(call otto.FunctionCall) otto.Value { 54 | if rec.Request.Raw != "" { 55 | result := TurboIntruder(rec) 56 | utils.DebugF(result) 57 | } 58 | return otto.Value{} 59 | }) 60 | 61 | for _, middleString := range rec.Request.Middlewares { 62 | utils.DebugF(middleString) 63 | vm.Run(middleString) 64 | } 65 | 66 | if middlewareOutput != "" { 67 | rec.Request.MiddlewareOutput = middlewareOutput 68 | } 69 | } 70 | 71 | // Host2IP replace Host header with IP address 72 | func Host2IP(rawURL string) map[string]string { 73 | // HostHeader = 74 | u, err := url.Parse(rawURL) 75 | if err != nil { 76 | return map[string]string{} 77 | } 78 | hostname := u.Hostname() 79 | resolved, err := net.LookupHost(hostname) 80 | if err != nil { 81 | return map[string]string{} 82 | } 83 | 84 | ip := resolved[0] 85 | if u.Port() == "443" || u.Port() == "80" || u.Port() == "" { 86 | hostname = ip 87 | } else { 88 | hostname = ip + ":" + u.Port() 89 | } 90 | 91 | return map[string]string{"Host": hostname} 92 | } 93 | 94 | // InvokeCmd execute external command 95 | func InvokeCmd(rec *libs.Record, rawCmd string) string { 96 | target := ParseTarget(rec.Request.URL) 97 | realCommand := Encoder(rec.Request.Encoding, ResolveVariable(rawCmd, target)) 98 | utils.DebugF("Execute Command: %v", realCommand) 99 | command := []string{ 100 | "bash", 101 | "-c", 102 | realCommand, 103 | } 104 | out, _ := exec.Command(command[0], command[1:]...).CombinedOutput() 105 | rec.Request.MiddlewareOutput = string(out) 106 | return string(out) 107 | } 108 | 109 | // TurboIntruder execute Turbo Intruder CLI 110 | func TurboIntruder(rec *libs.Record) string { 111 | req := rec.Request 112 | turboPath := ResolveVariable("{{.homePath}}/plugins/turbo-intruder/turbo-intruder-all.jar", req.Target) 113 | scriptPath := ResolveVariable("{{.homePath}}/plugins/turbo-intruder/basic.py", req.Target) 114 | 115 | // create a folder in case it didn't exist 116 | logReqPath := ResolveVariable("{{.homePath}}/log/req/", req.Target) 117 | url := ResolveVariable("{{.URL}}", req.Target) 118 | rec.Request.URL = url 119 | if _, err := os.Stat(logReqPath); os.IsNotExist(err) { 120 | os.MkdirAll(logReqPath, 0750) 121 | } 122 | // write request to a file 123 | rawReq := ResolveVariable(req.Raw, req.Target) 124 | reqPath := path.Join(logReqPath, utils.GenHash(rawReq)) 125 | utils.WriteToFile(reqPath, rawReq) 126 | 127 | // call the command and parse some info 128 | turboCmd := fmt.Sprintf(`java -jar %v %v %v %v foo`, turboPath, scriptPath, reqPath, url) 129 | 130 | command := []string{ 131 | "bash", 132 | "-c", 133 | turboCmd, 134 | } 135 | out, _ := exec.Command(command[0], command[1:]...).CombinedOutput() 136 | 137 | // parse output 138 | rawOutput := string(out) 139 | if strings.Contains(rawOutput, "=-+-================") { 140 | // split the prefix 141 | resp := strings.Split(rawOutput, "=-+-================")[1] 142 | result := strings.Split(resp, "------------------+=") 143 | 144 | // [Info] 403 11585 0.272 145 | info := result[0] 146 | statusCode, _ := strconv.Atoi(strings.Split(info, " ")[1]) 147 | rec.Response.StatusCode = statusCode 148 | 149 | length, _ := strconv.Atoi(strings.Split(info, " ")[2]) 150 | rec.Response.Length = length 151 | 152 | resTime, _ := strconv.ParseFloat(strings.TrimSpace(strings.Split(info, " ")[2]), 64) 153 | rec.Response.ResponseTime = resTime 154 | 155 | rec.Request.Beautify = result[1] 156 | rec.Response.Beautify = result[2] 157 | verbose := fmt.Sprintf("[TurboIntruder] %v %v %v %v", rec.Request.URL, reqPath, rec.Response.StatusCode, rec.Response.ResponseTime) 158 | return verbose 159 | } 160 | 161 | verbose := fmt.Sprintf("[TurboIntruder] Error sending request from: %v", reqPath) 162 | return verbose 163 | } 164 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/core" 6 | "github.com/jaeles-project/jaeles/database" 7 | "github.com/jaeles-project/jaeles/libs" 8 | "github.com/jaeles-project/jaeles/utils" 9 | "github.com/jinzhu/gorm" 10 | "github.com/spf13/cobra" 11 | "os" 12 | "path/filepath" 13 | "regexp" 14 | "strings" 15 | ) 16 | 17 | var options = libs.Options{} 18 | 19 | // DB database variables 20 | var DB *gorm.DB 21 | 22 | // RootCmd represents the base command when called without any subcommands 23 | var RootCmd = &cobra.Command{ 24 | Use: "jaeles", 25 | Short: "Jaeles Scanner", 26 | Long: libs.Banner(), 27 | } 28 | 29 | // Execute main function 30 | func Execute() { 31 | if err := RootCmd.Execute(); err != nil { 32 | fmt.Println(err) 33 | os.Exit(1) 34 | } 35 | } 36 | 37 | func init() { 38 | cobra.OnInitialize(initConfig) 39 | RootCmd.PersistentFlags().StringVar(&options.ConfigFile, "config", "", "config file (default is $HOME/.jaeles/config.yaml)") 40 | RootCmd.PersistentFlags().StringVar(&options.RootFolder, "rootDir", "~/.jaeles/", "root Project") 41 | RootCmd.PersistentFlags().StringVar(&options.SignFolder, "signDir", "~/.jaeles/signatures-base/", "Folder contain default signatures") 42 | RootCmd.PersistentFlags().StringVar(&options.ScanID, "scanID", "", "Scan ID") 43 | 44 | RootCmd.PersistentFlags().StringVar(&options.Proxy, "proxy", "", "proxy") 45 | RootCmd.PersistentFlags().IntVar(&options.Timeout, "timeout", 20, "HTTP timeout") 46 | RootCmd.PersistentFlags().IntVar(&options.Delay, "delay", 100, "Milliseconds delay for polling new job") 47 | RootCmd.PersistentFlags().IntVar(&options.Retry, "retry", 0, "retry") 48 | 49 | RootCmd.PersistentFlags().BoolVar(&options.SaveRaw, "save-raw", false, "save raw request") 50 | RootCmd.PersistentFlags().BoolVar(&options.NoOutput, "no-output", false, "Do not store raw output") 51 | RootCmd.PersistentFlags().BoolVar(&options.EnablePassive, "passive", false, "Do not run passive detections") 52 | RootCmd.PersistentFlags().BoolVar(&options.NoBackGround, "no-background", false, "Do not run background task") 53 | RootCmd.PersistentFlags().BoolVarP(&options.Verbose, "verbose", "v", false, "Verbose") 54 | RootCmd.PersistentFlags().BoolVar(&options.Debug, "debug", false, "Debug") 55 | RootCmd.PersistentFlags().IntVar(&options.Refresh, "refresh", 10, "Refresh") 56 | 57 | RootCmd.PersistentFlags().IntVarP(&options.Concurrency, "concurrency", "c", 20, "concurrency") 58 | RootCmd.PersistentFlags().IntVarP(&options.Threads, "threads", "t", 1, "Enable parallel in single signature") 59 | RootCmd.PersistentFlags().StringVarP(&options.Output, "output", "o", "out", "output folder name") 60 | RootCmd.PersistentFlags().StringVarP(&options.SummaryOutput, "summaryOutput", "O", "", "Summary output file") 61 | RootCmd.PersistentFlags().StringVarP(&options.LogFile, "log", "l", "", "log file") 62 | // custom params from cli 63 | RootCmd.PersistentFlags().StringSliceVarP(&options.Signs, "signs", "s", []string{}, "Signature selector (Multiple -s flags are accepted)") 64 | RootCmd.PersistentFlags().StringSliceVarP(&options.Excludes, "exclude", "x", []string{}, "Exclude Signature selector (Multiple -x flags are accepted)") 65 | RootCmd.PersistentFlags().StringSliceVarP(&options.Params, "params", "p", []string{}, "Custom params --params='foo=bar'") 66 | } 67 | 68 | // initConfig reads in config file and ENV variables if set. 69 | func initConfig() { 70 | fmt.Printf("Jaeles %v by %v\n", libs.VERSION, libs.AUTHOR) 71 | if options.Debug { 72 | options.Verbose = true 73 | } 74 | utils.InitLog(&options) 75 | core.InitConfig(&options) 76 | 77 | // Init DB 78 | var err error 79 | DB, err = database.InitDB(utils.NormalizePath(options.Server.DBPath)) 80 | if err != nil { 81 | fmt.Printf("Can't connect to DB at %v\n", options.Server.DBPath) 82 | os.Exit(-1) 83 | } 84 | } 85 | 86 | // SelectSign select signature 87 | func SelectSign() { 88 | //options.SelectedSigns 89 | var selectedSigns []string 90 | 91 | // default is all signature 92 | if len(options.Signs) == 0 { 93 | selectedSigns = core.SelectSign("**") 94 | } 95 | 96 | // search signature through Signatures table 97 | for _, signName := range options.Signs { 98 | selectedSigns = append(selectedSigns, core.SelectSign(signName)...) 99 | Signs := database.SelectSign(signName) 100 | selectedSigns = append(selectedSigns, Signs...) 101 | } 102 | 103 | // exclude some signature 104 | if len(options.Excludes) > 0 { 105 | for _, exclude := range options.Excludes { 106 | for index, sign := range selectedSigns { 107 | if strings.Contains(sign, exclude) { 108 | selectedSigns = append(selectedSigns[:index], selectedSigns[index+1:]...) 109 | } 110 | r, err := regexp.Compile(exclude) 111 | if err != nil { 112 | continue 113 | } 114 | if r.MatchString(sign) { 115 | selectedSigns = append(selectedSigns[:index], selectedSigns[index+1:]...) 116 | } 117 | 118 | } 119 | } 120 | } 121 | options.SelectedSigns = selectedSigns 122 | 123 | if len(selectedSigns) == 0 { 124 | fmt.Println("[Error] No signature loaded") 125 | os.Exit(1) 126 | } 127 | utils.InforF("Signatures Loaded: %v", len(selectedSigns)) 128 | signInfo := fmt.Sprintf("Signature Loaded: ") 129 | for _, signName := range selectedSigns { 130 | signInfo += fmt.Sprintf("%v ", filepath.Base(signName)) 131 | } 132 | utils.InforF(signInfo) 133 | 134 | // create new scan or group with old one 135 | var scanID string 136 | if options.ScanID == "" { 137 | scanID = database.NewScan(options, "scan", selectedSigns) 138 | } else { 139 | scanID = options.ScanID 140 | } 141 | utils.InforF("Start Scan with ID: %v", scanID) 142 | options.ScanID = scanID 143 | } 144 | -------------------------------------------------------------------------------- /libs/banner.go: -------------------------------------------------------------------------------- 1 | package libs 2 | 3 | import ( 4 | "github.com/fatih/color" 5 | ) 6 | 7 | // Banner print ascii banner 8 | func Banner() string { 9 | version := color.HiWhiteString(VERSION) 10 | author := color.MagentaString(AUTHOR) 11 | b := color.GreenString(``) 12 | 13 | b += "\n" + color.HiGreenString(``) 14 | b += "\n" + color.GreenString(` ,+izzir, `) 15 | b += "\n" + color.GreenString(` '*K@@Q8&Q@@8t' `) 16 | b += "\n" + color.GreenString(` !Q@N;''`) + color.HiWhiteString(`,~~;`) + color.GreenString(`\D@@t' `) 17 | b += "\n" + color.GreenString(` ,Q@q. `) + color.HiWhiteString(`'~~~~~~;`) + color.GreenString(`5@@L `) 18 | b += "\n" + color.GreenString(` L@@+ `) + color.HiWhiteString(`'~~~~~~~`) + color.GreenString(`^Q@X `) 19 | b += "\n" + color.GreenString(` ^@@z `) + color.HiWhiteString(`'~~~~~~~`) + color.GreenString(`|Q@y `) 20 | b += "\n" + color.GreenString(` 'Z@@7 `) + color.HiWhiteString(`'~~~~;`) + color.GreenString(`TQ@N, `) 21 | b += "\n" + color.GreenString(` ^%@QhJ7fmDQ@Q7' ~}DQ@@@Qqv, `) 22 | b += "\n" + color.GreenString(` ~jdQ@@Qdjr' ,U@@qv=|tm#@QY `) 23 | b += "\n" + color.GreenString(` *@@= D@&;`) + color.HiWhiteString(` ,~~~`) + color.GreenString(`;f@@^ `) 24 | b += "\n" + color.GreenString(` <@@+ .@@L`) + color.HiWhiteString(` '~~~~~~`) + color.GreenString(`K@P `) 25 | b += "\n" + color.GreenString(` ,~' `) 48 | 49 | b += "\n\n" + color.GreenString(` `) 50 | 51 | b += "\n" + color.CyanString(` 🚀 Jaeles %v`, version) + color.CyanString(` by %v 🚀`, author) 52 | b += "\n\n" + color.HiWhiteString(` The Swiss Army knife for automated Web Application Testing `) 53 | b += "\n\n" + color.HiGreenString(` ¯\_(ツ)_/¯`) + "\n\n" 54 | color.Unset() 55 | return b 56 | } 57 | -------------------------------------------------------------------------------- /cmd/config.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/core" 6 | "github.com/jaeles-project/jaeles/database" 7 | "github.com/jaeles-project/jaeles/libs" 8 | "github.com/jaeles-project/jaeles/utils" 9 | "github.com/spf13/cobra" 10 | "os" 11 | "path" 12 | "path/filepath" 13 | ) 14 | 15 | var configCmd *cobra.Command 16 | 17 | func init() { 18 | // byeCmd represents the bye command 19 | var configCmd = &cobra.Command{ 20 | Use: "config", 21 | Short: "Configuration CLI", 22 | Long: libs.Banner(), 23 | RunE: runConfig, 24 | } 25 | configCmd.Flags().Bool("clean", false, "Force continued operation when wildcard found") 26 | //configCmd.Flags().Int16P("level", "l", 1, "Provide custom header seperate by ';'") 27 | configCmd.Flags().StringP("action", "a", "", "Action") 28 | // load signature 29 | configCmd.Flags().StringP("signFolder", "F", "", "Signature Folder") 30 | // used for cred action 31 | configCmd.Flags().String("user", "", "Username") 32 | configCmd.Flags().String("pass", "", "Password") 33 | configCmd.Flags().Bool("hh", false, "More helper") 34 | configCmd.Flags().Bool("poll", false, "Polling all record in OOB config") 35 | // used for cred action 36 | configCmd.Flags().String("secret", "", "Secret of Burp Collab") 37 | configCmd.Flags().String("collab", "", "List of Burp Collab File") 38 | configCmd.Flags().String("repo", "", "Signature Repo") 39 | configCmd.SetHelpFunc(configHelp) 40 | RootCmd.AddCommand(configCmd) 41 | 42 | } 43 | 44 | func runConfig(cmd *cobra.Command, args []string) error { 45 | // print more help 46 | helps, _ := cmd.Flags().GetBool("hh") 47 | if helps == true { 48 | HelpMessage() 49 | os.Exit(1) 50 | } 51 | // turn on verbose by default 52 | options.Verbose = true 53 | 54 | polling, _ := cmd.Flags().GetBool("poll") 55 | // polling all oob 56 | if polling == true { 57 | secret, _ := cmd.Flags().GetString("secret") 58 | collabFile, _ := cmd.Flags().GetString("collab") 59 | collabs := utils.ReadingLines(collabFile) 60 | for _, collab := range collabs { 61 | database.ImportCollab(secret, collab) 62 | } 63 | } 64 | 65 | action, _ := cmd.Flags().GetString("action") 66 | switch action { 67 | case "update": 68 | // in case we want to in private repo 69 | username, _ := cmd.Flags().GetString("user") 70 | password, _ := cmd.Flags().GetString("pass") 71 | if username != "" { 72 | options.Server.Username = username 73 | options.Server.Password = password 74 | } 75 | 76 | core.UpdatePlugins(options) 77 | repo, _ := cmd.Flags().GetString("repo") 78 | core.UpdateSignature(options, repo) 79 | reloadSignature(path.Join(options.RootFolder, "base-signatures")) 80 | break 81 | case "clear": 82 | database.CleanScans() 83 | database.CleanSigns() 84 | database.CleanRecords() 85 | break 86 | case "clean": 87 | os.RemoveAll(path.Join(options.RootFolder, "sqlite.db")) 88 | os.RemoveAll(path.Join(options.RootFolder, "config.yaml")) 89 | os.RemoveAll(path.Join(options.RootFolder, "burp.json")) 90 | break 91 | case "cred": 92 | username, _ := cmd.Flags().GetString("user") 93 | password, _ := cmd.Flags().GetString("pass") 94 | database.CreateUser(username, password) 95 | utils.GoodF("Create new credentials %v:%v \n", username, password) 96 | break 97 | case "oob": 98 | secret, _ := cmd.Flags().GetString("secret") 99 | collabFile, _ := cmd.Flags().GetString("collab") 100 | collabs := utils.ReadingLines(collabFile) 101 | for _, collab := range collabs { 102 | database.ImportCollab(secret, collab) 103 | } 104 | break 105 | case "reload": 106 | signFolder, _ := cmd.Flags().GetString("signFolder") 107 | reloadSignature(signFolder) 108 | break 109 | default: 110 | HelpMessage() 111 | } 112 | return nil 113 | } 114 | 115 | // reloadSignature signature 116 | func reloadSignature(signFolder string) { 117 | if !utils.FolderExists(signFolder) { 118 | utils.ErrorF("Signature folder not found: %v", signFolder) 119 | return 120 | } 121 | utils.GoodF("Reload signature in: %v", signFolder) 122 | 123 | database.CleanSigns() 124 | SignFolder, _ := filepath.Abs(path.Join(options.RootFolder, "base-signatures")) 125 | if signFolder != "" && utils.FolderExists(signFolder) { 126 | SignFolder = signFolder 127 | } 128 | allSigns := utils.GetFileNames(SignFolder, ".yaml") 129 | if allSigns != nil { 130 | utils.InforF("Load Signature from: %v", SignFolder) 131 | for _, signFile := range allSigns { 132 | database.ImportSign(signFile) 133 | } 134 | } 135 | } 136 | 137 | func configHelp(cmd *cobra.Command, args []string) { 138 | HelpMessage() 139 | } 140 | 141 | // HelpMessage print help message 142 | func HelpMessage() { 143 | fmt.Println(libs.Banner()) 144 | h := "\nConfig Command example:\n\n" 145 | h += " jaeles config -a update\n\n" 146 | h += " jaeles config -a update --repo http://github.com/jaeles-project/another-signatures --user admin --pass admin\n" 147 | h += " jaeles config -a clean\n\n" 148 | h += " jaeles config -a reload\n\n" 149 | h += " jaeles config -a reload -F /tmp/custom-signatures/\n\n" 150 | h += " jaeles config -a cred --user sample --pass not123456\n\n" 151 | h += " jaeles config -a oob --secret SomethingSecret --collab list_of_collabs.txt\n\n" 152 | fmt.Printf(h) 153 | } 154 | 155 | func ScanHelp(cmd *cobra.Command, args []string) { 156 | fmt.Println(libs.Banner()) 157 | h := "\nScan Usage example:\n" 158 | h += " jaeles scan -s -u http://example.com\n" 159 | h += " jaeles scan -c 50 -s -U list_target.txt\n" 160 | h += " jaeles scan -v -c 50 -s -U list_target.txt -o /tmp/output\n" 161 | h += " jaeles scan -s -s -u http://example.com\n" 162 | h += " cat list_target.txt | jaeles scan -c 100 -t 5 -s \n" 163 | 164 | h += "\n\nExamples:\n" 165 | h += " jaeles scan -s 'jira' -s 'ruby' -u target.com\n" 166 | h += " jaeles scan -c 50 -t 3 -s 'java' -x 'tomcat' -U list_of_urls.txt\n" 167 | h += " jaeles scan -c 50 -t 3 -s '/tmp/custom-signature/.*' -U list_of_urls.txt\n" 168 | h += " cat urls.txt | grep 'interesting' | jaeles scan -c 50 -t 3 -s 'fuzz/.*' -U list_of_urls.txt --proxy http://127.0.0.1:8080\n" 169 | h += "\n" 170 | fmt.Printf(h) 171 | } 172 | -------------------------------------------------------------------------------- /core/conclusions.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 | "os/exec" 8 | "regexp" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // RunConclusions set new value for next request 14 | func RunConclusions(record libs.Record, sign *libs.Signature) { 15 | if len(record.Request.Conclusions) == 0 { 16 | return 17 | } 18 | for _, concludeScript := range record.Request.Conclusions { 19 | utils.DebugF("[Conclude]: %v", concludeScript) 20 | RunConclude(concludeScript, record, sign) 21 | } 22 | } 23 | 24 | // RunConclude run conclusion script 25 | func RunConclude(concludeScript string, record libs.Record, sign *libs.Signature) { 26 | vm := otto.New() 27 | 28 | // ExecCmd execute command command 29 | vm.Set("ExecCmd", func(call otto.FunctionCall) otto.Value { 30 | result, _ := vm.ToValue(Execution(call.Argument(0).String())) 31 | return result 32 | }) 33 | 34 | // write something to a file 35 | vm.Set("WriteTo", func(call otto.FunctionCall) otto.Value { 36 | dest := utils.NormalizePath(call.Argument(0).String()) 37 | value := call.Argument(1).String() 38 | utils.WriteToFile(dest, value) 39 | return otto.Value{} 40 | }) 41 | 42 | vm.Set("StringSearch", func(call otto.FunctionCall) otto.Value { 43 | componentName := call.Argument(0).String() 44 | analyzeString := call.Argument(1).String() 45 | component := GetComponent(record, componentName) 46 | validate := StringSearch(component, analyzeString) 47 | result, _ := vm.ToValue(validate) 48 | return result 49 | }) 50 | 51 | vm.Set("StringCount", func(call otto.FunctionCall) otto.Value { 52 | componentName := call.Argument(0).String() 53 | analyzeString := call.Argument(1).String() 54 | component := GetComponent(record, componentName) 55 | validate := StringCount(component, analyzeString) 56 | result, _ := vm.ToValue(validate) 57 | return result 58 | }) 59 | 60 | vm.Set("RegexSearch", func(call otto.FunctionCall) otto.Value { 61 | componentName := call.Argument(0).String() 62 | analyzeString := call.Argument(1).String() 63 | component := GetComponent(record, componentName) 64 | validate := RegexSearch(component, analyzeString) 65 | result, _ := vm.ToValue(validate) 66 | return result 67 | }) 68 | 69 | vm.Set("RegexCount", func(call otto.FunctionCall) otto.Value { 70 | componentName := call.Argument(0).String() 71 | analyzeString := call.Argument(1).String() 72 | component := GetComponent(record, componentName) 73 | validate := RegexCount(component, analyzeString) 74 | result, _ := vm.ToValue(validate) 75 | return result 76 | }) 77 | 78 | vm.Set("StatusCode", func(call otto.FunctionCall) otto.Value { 79 | statusCode := record.Response.StatusCode 80 | result, _ := vm.ToValue(statusCode) 81 | return result 82 | }) 83 | vm.Set("ResponseTime", func(call otto.FunctionCall) otto.Value { 84 | responseTime := record.Response.ResponseTime 85 | result, _ := vm.ToValue(responseTime) 86 | return result 87 | }) 88 | vm.Set("ContentLength", func(call otto.FunctionCall) otto.Value { 89 | ContentLength := record.Response.Length 90 | result, _ := vm.ToValue(ContentLength) 91 | return result 92 | }) 93 | 94 | // StringSelect select a string from component 95 | // e.g: StringSelect("component", "res1", "right", "left") 96 | vm.Set("StringSelect", func(call otto.FunctionCall) otto.Value { 97 | componentName := call.Argument(0).String() 98 | valueName := call.Argument(1).String() 99 | left := call.Argument(2).String() 100 | right := call.Argument(3).String() 101 | component := GetComponent(record, componentName) 102 | value := Between(component, left, right) 103 | sign.Target[valueName] = value 104 | return otto.Value{} 105 | }) 106 | 107 | // - RegexSelect("component", "var_name", "regex") 108 | // - RegexSelect("component", "var_name", "regex", "position") 109 | vm.Set("RegexSelect", func(call otto.FunctionCall) otto.Value { 110 | valueName, value := RegexSelect(record, call.ArgumentList) 111 | sign.Target[valueName] = value 112 | return otto.Value{} 113 | }) 114 | 115 | // SetValue("var_name", StatusCode()) 116 | // SetValue("status", StringCount('middleware', '11')) 117 | vm.Set("SetValue", func(call otto.FunctionCall) otto.Value { 118 | valueName := call.Argument(0).String() 119 | value := call.Argument(1).String() 120 | sign.Target[valueName] = value 121 | return otto.Value{} 122 | }) 123 | 124 | vm.Run(concludeScript) 125 | } 126 | 127 | // Between get string between left and right 128 | func Between(value string, left string, right string) string { 129 | // Get substring between two strings. 130 | posFirst := strings.Index(value, left) 131 | if posFirst == -1 { 132 | return "" 133 | } 134 | posLast := strings.Index(value, right) 135 | if posLast == -1 { 136 | return "" 137 | } 138 | posFirstAdjusted := posFirst + len(left) 139 | if posFirstAdjusted >= posLast { 140 | return "" 141 | } 142 | return value[posFirstAdjusted:posLast] 143 | } 144 | 145 | // RegexSelect get regex string from component 146 | func RegexSelect(realRec libs.Record, arguments []otto.Value) (string, string) { 147 | // - RegexSelect("component", "var_name", "regex") 148 | // - RegexSelect("component", "var_name", "regex", "position") 149 | componentName := arguments[0].String() 150 | valueName := arguments[1].String() 151 | component := GetComponent(realRec, componentName) 152 | 153 | regexString := arguments[2].String() 154 | var position int 155 | var err error 156 | if len(arguments) > 3 { 157 | position, err = strconv.Atoi(arguments[3].String()) 158 | if err != nil { 159 | position = 0 160 | } 161 | } 162 | 163 | var value string 164 | r, rerr := regexp.Compile(regexString) 165 | if rerr != nil { 166 | return valueName, "" 167 | } 168 | matches := r.FindStringSubmatch(component) 169 | if len(matches) > 0 { 170 | if position <= len(matches) { 171 | value = matches[position] 172 | } else { 173 | value = matches[0] 174 | } 175 | } 176 | return valueName, value 177 | } 178 | 179 | // Execution Run a command 180 | func Execution(cmd string) string { 181 | command := []string{ 182 | "bash", 183 | "-c", 184 | cmd, 185 | } 186 | var output string 187 | utils.DebugF("[Exec] %v", command) 188 | realCmd := exec.Command(command[0], command[1:]...) 189 | out, _ := realCmd.CombinedOutput() 190 | output = string(out) 191 | return output 192 | } 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Jaeles 4 |

5 | Software License 6 | Release 7 | Rawsec's CyberSecurity Inventory 8 |

9 |

10 | 11 | **Jaeles** is a powerful, flexible and easily extensible framework written in Go for building your own Web Application Scanner. 12 | 13 | ![Architecture](https://github.com/jaeles-project/jaeles-plugins/blob/master/imgs/jaeles-architecture.png?raw=true) 14 | 15 | ## Installation 16 | Download [precompiled version here](https://github.com/jaeles-project/jaeles/releases). 17 | 18 | If you have a Go environment, make sure you have **Go >= 1.13** with Go Modules enable and run the following command. 19 | 20 | ```shell 21 | GO111MODULE=on go get -u github.com/jaeles-project/jaeles 22 | ``` 23 | 24 | Please visit the [Official Documention](https://jaeles-project.github.io/) for more details. 25 | 26 | Checkout [Signature Repo](https://github.com/jaeles-project/jaeles-signatures) for base signature and passive signature. 27 | 28 | ## Usage 29 | 30 | More usage [here](https://jaeles-project.github.io/usage/) 31 | 32 | Example commands. 33 | 34 | ```shell 35 | jaeles scan -s 'jira' -s 'ruby' -u target.com 36 | 37 | jaeles scan -c 50 -s 'java' -x 'tomcat' -U list_of_urls.txt 38 | 39 | jaeles scan -c 50 -t 3 -s '/tmp/custom-signature/.*' -U list_of_urls.txt 40 | 41 | cat urls.txt | grep 'interesting' | jaeles scan -c 50 -t 3 -s 'fuzz/.*' -U list_of_urls.txt --proxy http://127.0.0.1:8080 42 | 43 | jaeles server --verbose -s sqli 44 | ``` 45 | 46 | ## Showcases 47 | 48 | More showcase [here](https://jaeles-project.github.io/showcases/) 49 | 50 | [![asciicast](https://asciinema.org/a/281205.svg)](https://asciinema.org/a/281205) 51 |

52 | Detect Jira SSRF CVE-2019-8451 53 |

54 | 55 | ### Burp Integration 56 | 57 | ![Burp Integration](https://github.com/jaeles-project/jaeles-plugins/blob/master/imgs/Burp-Integration.gif?raw=true) 58 | 59 | Plugin can be found [here](https://github.com/jaeles-project/jaeles-plugins/blob/master/jaeles-burp.py) and Video Guide [here](https://youtu.be/1lxsYhfTq3M) 60 | 61 | ## Mentions 62 | 63 | [My introduction slide about Jaeles](https://speakerdeck.com/j3ssie/jaeles-the-swiss-army-knife-for-automated-web-application-testing) 64 | 65 | 66 | ### Planned Features 67 | 68 | * Adding more signatures. 69 | * Adding more input sources. 70 | * Adding more APIs to get access to more properties of the request. 71 | * Adding proxy plugins to directly receive input from browser of http client. 72 | * ~~Adding passive signature for passive checking each request.~~ 73 | * Adding more action on Web UI. 74 | * Integrate with many other tools. 75 | 76 | ## Contribute 77 | 78 | If you have some new idea about this project, issue, feedback or found some valuable tool feel free to open an issue for just DM me via @j3ssiejjj. 79 | Feel free to submit new signature to this [repo](https://github.com/jaeles-project/jaeles-signatures). 80 | 81 | ### Credits 82 | 83 | * Special thanks to [chaitin](https://github.com/chaitin/xray) team for sharing ideas to me for build the architecture. 84 | 85 | * React components is powered by [Carbon](https://www.carbondesignsystem.com/) and [carbon-tutorial](https://github.com/carbon-design-system/carbon-tutorial). 86 | 87 | * Awesomes artworks are powered by [Freepik](http://freepik.com) at [flaticon.com](http://flaticon.com). 88 | 89 | ## In distributions 90 | 91 | [![Packaging status](https://repology.org/badge/vertical-allrepos/jaeles.svg)](https://repology.org/project/jaeles/versions) 92 | 93 | ## Contributors 94 | 95 | ### Code Contributors 96 | 97 | This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. 98 | 99 | 100 | ### Financial Contributors 101 | 102 | Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/jaeles-project/contribute)] 103 | 104 | #### Individuals 105 | 106 | 107 | 108 | #### Organizations 109 | 110 | Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/jaeles-project/contribute)] 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | ## License 124 | 125 | `Jaeles` is made with ♥ by [@j3ssiejjj](https://twitter.com/j3ssiejjj) and it is released under the MIT license. 126 | -------------------------------------------------------------------------------- /core/signature.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "github.com/thoas/go-funk" 6 | "path/filepath" 7 | "regexp" 8 | "strings" 9 | "text/template" 10 | 11 | "github.com/jaeles-project/jaeles/database" 12 | "github.com/jaeles-project/jaeles/libs" 13 | "github.com/jaeles-project/jaeles/utils" 14 | ) 15 | 16 | // @NOTE: Signatures allow execute command on your machine 17 | // So make sure you read the signature before you run it 18 | 19 | // SelectSign select signature by multiple selector 20 | func SelectSign(signName string) []string { 21 | var Signs []string 22 | // return default sign if doesn't set anything 23 | if signName == "**" { 24 | Signs = database.SelectSign("") 25 | return Signs 26 | } 27 | signs := SingleSign(strings.TrimSpace(signName)) 28 | if len(signs) > 0 { 29 | Signs = append(Signs, signs...) 30 | } 31 | Signs = funk.UniqString(Signs) 32 | return Signs 33 | } 34 | 35 | // SingleSign select signature by single selector 36 | func SingleSign(signName string) []string { 37 | signName = utils.NormalizePath(signName) 38 | 39 | var Signs []string 40 | if strings.HasSuffix(signName, ".yaml") { 41 | if utils.FileExists(signName) { 42 | Signs = append(Signs, signName) 43 | } 44 | } 45 | // get more signature 46 | if strings.Contains(signName, "*") && strings.Contains(signName, "/") { 47 | asbPath, _ := filepath.Abs(signName) 48 | baseSelect := filepath.Base(signName) 49 | rawSigns := utils.GetFileNames(filepath.Dir(asbPath), "yaml") 50 | for _, signFile := range rawSigns { 51 | baseSign := filepath.Base(signFile) 52 | if len(baseSign) == 1 && baseSign == "*" { 53 | Signs = append(Signs, signFile) 54 | continue 55 | } 56 | r, err := regexp.Compile(baseSelect) 57 | if err != nil { 58 | if strings.Contains(signFile, baseSelect) { 59 | Signs = append(Signs, signFile) 60 | } 61 | } 62 | if r.MatchString(baseSign) { 63 | Signs = append(Signs, signFile) 64 | } 65 | } 66 | } 67 | return Signs 68 | } 69 | 70 | // AltResolveRequest resolve all request but look for [[ ]] delimiter 71 | func AltResolveRequest(req *libs.Request) { 72 | target := req.Target 73 | 74 | if len(req.Values) > 0 { 75 | for _, value := range req.Values { 76 | for k, v := range value { 77 | // variable as a script 78 | if strings.Contains(v, "(") && strings.Contains(v, ")") { 79 | newValue := RunVariables(v) 80 | if len(newValue) > 0 { 81 | target[k] = newValue[0] 82 | } 83 | } else { 84 | target[k] = v 85 | } 86 | } 87 | } 88 | } 89 | // resolve all part again but with secondary template 90 | req.URL = AltResolveVariable(req.URL, target) 91 | req.Body = AltResolveVariable(req.Body, target) 92 | req.Headers = AltResolveHeader(req.Headers, target) 93 | req.Detections = AltResolveDetection(req.Detections, target) 94 | req.Generators = AltResolveDetection(req.Generators, target) 95 | req.Middlewares = AltResolveDetection(req.Middlewares, target) 96 | } 97 | 98 | // ResolveDetection resolve detection part in YAML signature file 99 | func ResolveDetection(detections []string, target map[string]string) []string { 100 | var realDetections []string 101 | for _, detect := range detections { 102 | realDetections = append(realDetections, ResolveVariable(detect, target)) 103 | } 104 | return realDetections 105 | } 106 | 107 | // AltResolveDetection resolve detection part in YAML signature file 108 | func AltResolveDetection(detections []string, target map[string]string) []string { 109 | var realDetections []string 110 | for _, detect := range detections { 111 | realDetections = append(realDetections, AltResolveVariable(detect, target)) 112 | } 113 | return realDetections 114 | } 115 | 116 | // ResolveHeader resolve headers part in YAML signature file 117 | func ResolveHeader(headers []map[string]string, target map[string]string) []map[string]string { 118 | // realHeaders := headers 119 | var realHeaders []map[string]string 120 | 121 | for _, head := range headers { 122 | realHeader := make(map[string]string) 123 | for key, value := range head { 124 | realKey := ResolveVariable(key, target) 125 | realVal := ResolveVariable(value, target) 126 | realHeader[realKey] = realVal 127 | } 128 | realHeaders = append(realHeaders, realHeader) 129 | } 130 | 131 | return realHeaders 132 | } 133 | 134 | // AltResolveHeader resolve headers part in YAML signature file 135 | func AltResolveHeader(headers []map[string]string, target map[string]string) []map[string]string { 136 | // realHeaders := headers 137 | var realHeaders []map[string]string 138 | 139 | for _, head := range headers { 140 | realHeader := make(map[string]string) 141 | for key, value := range head { 142 | realKey := AltResolveVariable(key, target) 143 | realVal := AltResolveVariable(value, target) 144 | realHeader[realKey] = realVal 145 | } 146 | realHeaders = append(realHeaders, realHeader) 147 | } 148 | 149 | return realHeaders 150 | } 151 | 152 | // ResolveVariable resolve template from signature file 153 | func ResolveVariable(format string, data map[string]string) string { 154 | if strings.TrimSpace(format) == "" { 155 | return format 156 | } 157 | realFormat, err := template.New("").Parse(format) 158 | // when template contain {{ 159 | if err != nil { 160 | r, rerr := regexp.Compile(`\{\{[^.]`) 161 | if rerr != nil { 162 | return format 163 | } 164 | matches := r.FindStringSubmatch(format) 165 | if len(matches) > 0 { 166 | for _, m := range matches { 167 | new := strings.Replace(m, `{{`, `{{"{{"}}`, -1) 168 | format = strings.Replace(format, m, new, -1) 169 | } 170 | } 171 | // parse it again 172 | realFormat, err = template.New("").Parse(format) 173 | if err != nil { 174 | utils.ErrorF("improper template format %v", format) 175 | return format 176 | } 177 | } 178 | t := template.Must(realFormat, err) 179 | 180 | buf := &bytes.Buffer{} 181 | err = t.Execute(buf, data) 182 | if err != nil { 183 | return format 184 | } 185 | return buf.String() 186 | } 187 | 188 | // AltResolveVariable just like ResolveVariable but looking for [[.var]] 189 | func AltResolveVariable(format string, data map[string]string) string { 190 | if strings.TrimSpace(format) == "" { 191 | return format 192 | } 193 | realFormat, err := template.New("").Delims("[[", "]]").Parse(format) 194 | // when template contain [[ 195 | if err != nil { 196 | r, rerr := regexp.Compile(`\[\[[^.]`) 197 | if rerr != nil { 198 | return format 199 | } 200 | matches := r.FindStringSubmatch(format) 201 | if len(matches) > 0 { 202 | for _, m := range matches { 203 | new := strings.Replace(m, `[[`, `[["[["]]`, -1) 204 | format = strings.Replace(format, m, new, -1) 205 | } 206 | } 207 | // parse it again 208 | realFormat, err = template.New("").Parse(format) 209 | if err != nil { 210 | utils.ErrorF("improper template format %v", format) 211 | return format 212 | } 213 | } 214 | t := template.Must(realFormat, err) 215 | 216 | buf := &bytes.Buffer{} 217 | err = t.Execute(buf, data) 218 | if err != nil { 219 | return format 220 | } 221 | return buf.String() 222 | } 223 | -------------------------------------------------------------------------------- /utils/helper.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "archive/zip" 5 | "bufio" 6 | "crypto/sha1" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "regexp" 14 | "strconv" 15 | "strings" 16 | "time" 17 | 18 | "github.com/mitchellh/go-homedir" 19 | ) 20 | 21 | // StrToInt string to int 22 | func StrToInt(data string) int { 23 | i, err := strconv.Atoi(data) 24 | if err != nil { 25 | return 0 26 | } 27 | return i 28 | } 29 | 30 | // GetOSEnv get enviroment variable 31 | func GetOSEnv(name string) string { 32 | varibale, ok := os.LookupEnv(name) 33 | if !ok { 34 | return name 35 | } 36 | return varibale 37 | } 38 | 39 | // MakeDir just make a folder 40 | func MakeDir(folder string) { 41 | os.MkdirAll(folder, 0750) 42 | } 43 | 44 | // GetCurrentDay get current day 45 | func GetCurrentDay() string { 46 | currentTime := time.Now() 47 | return fmt.Sprintf("%v", currentTime.Format("2006-01-02_3:4:5")) 48 | } 49 | 50 | // NormalizePath the path 51 | func NormalizePath(path string) string { 52 | if strings.HasPrefix(path, "~") { 53 | path, _ = homedir.Expand(path) 54 | } 55 | return path 56 | } 57 | 58 | // GetFileContent Reading file and return content of it 59 | func GetFileContent(filename string) string { 60 | var result string 61 | if strings.Contains(filename, "~") { 62 | filename, _ = homedir.Expand(filename) 63 | } 64 | file, err := os.Open(filename) 65 | if err != nil { 66 | return result 67 | } 68 | defer file.Close() 69 | b, err := ioutil.ReadAll(file) 70 | if err != nil { 71 | return result 72 | } 73 | return string(b) 74 | } 75 | 76 | // ReadingLines Reading file and return content as []string 77 | func ReadingLines(filename string) []string { 78 | var result []string 79 | if strings.HasPrefix(filename, "~") { 80 | filename, _ = homedir.Expand(filename) 81 | } 82 | file, err := os.Open(filename) 83 | defer file.Close() 84 | if err != nil { 85 | return result 86 | } 87 | 88 | scanner := bufio.NewScanner(file) 89 | for scanner.Scan() { 90 | val := scanner.Text() 91 | result = append(result, val) 92 | } 93 | 94 | if err := scanner.Err(); err != nil { 95 | return result 96 | } 97 | return result 98 | } 99 | 100 | // ReadingFileUnique Reading file and return content as []string 101 | func ReadingFileUnique(filename string) []string { 102 | var result []string 103 | if strings.Contains(filename, "~") { 104 | filename, _ = homedir.Expand(filename) 105 | } 106 | file, err := os.Open(filename) 107 | defer file.Close() 108 | if err != nil { 109 | return result 110 | } 111 | 112 | unique := true 113 | seen := make(map[string]bool) 114 | 115 | scanner := bufio.NewScanner(file) 116 | for scanner.Scan() { 117 | val := scanner.Text() 118 | // unique stuff 119 | if val == "" { 120 | continue 121 | } 122 | if seen[val] && unique { 123 | continue 124 | } 125 | 126 | if unique { 127 | seen[val] = true 128 | result = append(result, val) 129 | } 130 | } 131 | 132 | if err := scanner.Err(); err != nil { 133 | return result 134 | } 135 | return result 136 | } 137 | 138 | // WriteToFile write string to a file 139 | func WriteToFile(filename string, data string) (string, error) { 140 | file, err := os.Create(filename) 141 | if err != nil { 142 | return "", err 143 | } 144 | defer file.Close() 145 | 146 | _, err = io.WriteString(file, data+"\n") 147 | if err != nil { 148 | return "", err 149 | } 150 | return filename, file.Sync() 151 | } 152 | 153 | // AppendToContent append string to a file 154 | func AppendToContent(filename string, data string) (string, error) { 155 | // If the file doesn't exist, create it, or append to the file 156 | f, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 157 | if err != nil { 158 | return "", err 159 | } 160 | if _, err := f.Write([]byte(data + "\n")); err != nil { 161 | return "", err 162 | } 163 | if err := f.Close(); err != nil { 164 | return "", err 165 | } 166 | return filename, nil 167 | } 168 | 169 | // FileExists check if file is exist or not 170 | func FileExists(filename string) bool { 171 | info, err := os.Stat(filename) 172 | if os.IsNotExist(err) { 173 | return false 174 | } 175 | return !info.IsDir() 176 | } 177 | 178 | // FolderExists check if file is exist or not 179 | func FolderExists(foldername string) bool { 180 | foldername = NormalizePath(foldername) 181 | if _, err := os.Stat(foldername); os.IsNotExist(err) { 182 | return false 183 | } 184 | return true 185 | } 186 | 187 | // GetFileNames get all file name with extension 188 | func GetFileNames(dir string, ext string) []string { 189 | if _, err := os.Stat(dir); os.IsNotExist(err) { 190 | return nil 191 | } 192 | 193 | var files []string 194 | filepath.Walk(dir, func(path string, f os.FileInfo, _ error) error { 195 | if !f.IsDir() { 196 | if strings.HasSuffix(f.Name(), ext) { 197 | filename, _ := filepath.Abs(path) 198 | files = append(files, filename) 199 | } 200 | } 201 | return nil 202 | }) 203 | return files 204 | } 205 | 206 | // IsJSON check if string is JSON or not 207 | func IsJSON(str string) bool { 208 | var js json.RawMessage 209 | return json.Unmarshal([]byte(str), &js) == nil 210 | } 211 | 212 | // GetTS get current timestamp and return a string 213 | func GetTS() string { 214 | return strconv.FormatInt(time.Now().Unix(), 10) 215 | } 216 | 217 | // GenHash gen SHA1 hash from string 218 | func GenHash(text string) string { 219 | h := sha1.New() 220 | h.Write([]byte(text)) 221 | hashed := h.Sum(nil) 222 | return fmt.Sprintf("%x", hashed) 223 | } 224 | 225 | // Unzip will decompress a zip archive, moving all files and folders 226 | // within the zip file (parameter 1) to an output directory (parameter 2). 227 | func Unzip(src string, dest string) ([]string, error) { 228 | 229 | var filenames []string 230 | 231 | r, err := zip.OpenReader(src) 232 | if err != nil { 233 | return filenames, err 234 | } 235 | defer r.Close() 236 | 237 | for _, f := range r.File { 238 | 239 | // Store filename/path for returning and using later on 240 | fpath := filepath.Join(dest, f.Name) 241 | 242 | if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { 243 | return filenames, fmt.Errorf("%s: illegal file path", fpath) 244 | } 245 | 246 | filenames = append(filenames, fpath) 247 | 248 | if f.FileInfo().IsDir() { 249 | // Make Folder 250 | os.MkdirAll(fpath, os.ModePerm) 251 | continue 252 | } 253 | 254 | // Make File 255 | if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { 256 | return filenames, err 257 | } 258 | 259 | outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) 260 | if err != nil { 261 | return filenames, err 262 | } 263 | 264 | rc, err := f.Open() 265 | if err != nil { 266 | return filenames, err 267 | } 268 | 269 | _, err = io.Copy(outFile, rc) 270 | 271 | // Close the file without defer to close before next iteration of loop 272 | outFile.Close() 273 | rc.Close() 274 | 275 | if err != nil { 276 | return filenames, err 277 | } 278 | } 279 | return filenames, nil 280 | } 281 | 282 | // ExpandLength make slice to length 283 | func ExpandLength(list []string, length int) []string { 284 | c := []string{} 285 | for i := 1; i <= length; i++ { 286 | c = append(c, list[i%len(list)]) 287 | } 288 | return c 289 | } 290 | 291 | // StartWithNum check if string start with number 292 | func StartWithNum(raw string) bool { 293 | r, err := regexp.Compile("^[0-9].*") 294 | if err != nil { 295 | return false 296 | } 297 | return r.MatchString(raw) 298 | } 299 | 300 | // StripName strip a file name 301 | func StripName(raw string) string { 302 | return strings.Replace(raw, "/", "_", -1) 303 | } 304 | 305 | // MoveFolder move folder 306 | func MoveFolder(src string, dest string) { 307 | os.Rename(NormalizePath(src), NormalizePath(dest)) 308 | } 309 | -------------------------------------------------------------------------------- /sender/sender.go: -------------------------------------------------------------------------------- 1 | package sender 2 | 3 | import ( 4 | "crypto/tls" 5 | "errors" 6 | "fmt" 7 | "github.com/jaeles-project/jaeles/utils" 8 | "io/ioutil" 9 | "math/rand" 10 | "net/http" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | "github.com/go-resty/resty/v2" 16 | "github.com/jaeles-project/jaeles/libs" 17 | "github.com/sirupsen/logrus" 18 | ) 19 | 20 | // JustSend just sending request 21 | func JustSend(options libs.Options, req libs.Request) (res libs.Response, err error) { 22 | method := req.Method 23 | url := req.URL 24 | body := req.Body 25 | headers := GetHeaders(req) 26 | 27 | // update it again 28 | var newHeader []map[string]string 29 | for k, v := range headers { 30 | element := make(map[string]string) 31 | element[k] = v 32 | newHeader = append(newHeader, element) 33 | } 34 | req.Headers = newHeader 35 | 36 | // disable log when retry 37 | logger := logrus.New() 38 | if !options.Debug { 39 | logger.Out = ioutil.Discard 40 | } 41 | client := resty.New().SetLogger(logger) 42 | client.SetLogger(logger) 43 | client.SetCloseConnection(true) 44 | 45 | // setting for client 46 | client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) 47 | client.SetDisableWarn(true) 48 | client.SetHeaders(headers) 49 | 50 | // redirect policy 51 | if req.Redirect == false { 52 | // client.SetRedirectPolicy(resty.NoRedirectPolicy()) 53 | client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { 54 | // keep the header the same 55 | // client.SetHeaders(headers) 56 | 57 | res.StatusCode = req.Response.StatusCode 58 | res.Status = req.Response.Status 59 | resp := req.Response 60 | bodyBytes, err := ioutil.ReadAll(resp.Body) 61 | if err != nil { 62 | utils.ErrorF("%v", err) 63 | } 64 | bodyString := string(bodyBytes) 65 | resLength := len(bodyString) 66 | // format the headers 67 | var resHeaders []map[string]string 68 | for k, v := range resp.Header { 69 | element := make(map[string]string) 70 | element[k] = strings.Join(v[:], "") 71 | resLength += len(fmt.Sprintf("%s: %s\n", k, strings.Join(v[:], ""))) 72 | resHeaders = append(resHeaders, element) 73 | } 74 | 75 | // respones time in second 76 | resTime := float64(0.0) 77 | resHeaders = append(resHeaders, 78 | map[string]string{"Total Length": strconv.Itoa(resLength)}, 79 | map[string]string{"Response Time": fmt.Sprintf("%f", resTime)}, 80 | ) 81 | 82 | // set some variable 83 | res.Headers = resHeaders 84 | res.StatusCode = resp.StatusCode 85 | res.Status = fmt.Sprintf("%v %v", resp.Status, resp.Proto) 86 | res.Body = bodyString 87 | res.ResponseTime = resTime 88 | res.Length = resLength 89 | // beautify 90 | res.Beautify = BeautifyResponse(res) 91 | return errors.New("auto redirect is disabled") 92 | })) 93 | 94 | client.AddRetryCondition( 95 | func(r *resty.Response, err error) bool { 96 | return false 97 | }, 98 | ) 99 | 100 | } else { 101 | client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { 102 | // keep the header the same 103 | client.SetHeaders(headers) 104 | return nil 105 | })) 106 | } 107 | 108 | if options.Retry > 0 { 109 | client.SetRetryCount(options.Retry) 110 | } 111 | client.SetTimeout(time.Duration(options.Timeout) * time.Second) 112 | if req.Timeout > 0 { 113 | client.SetTimeout(time.Duration(req.Timeout) * time.Second) 114 | } 115 | 116 | client.SetRetryWaitTime(time.Duration(options.Timeout/2) * time.Second) 117 | client.SetRetryMaxWaitTime(time.Duration(options.Timeout) * time.Second) 118 | 119 | if options.Proxy != "" { 120 | client.SetProxy(options.Proxy) 121 | } 122 | // override proxy 123 | if req.Proxy != "" && req.Proxy != "blank" { 124 | client.SetProxy(req.Proxy) 125 | } 126 | 127 | var resp *resty.Response 128 | // really sending things here 129 | switch method { 130 | case "GET": 131 | resp, err = client.R(). 132 | SetBody([]byte(body)). 133 | Get(url) 134 | break 135 | case "POST": 136 | resp, err = client.R(). 137 | SetBody([]byte(body)). 138 | Post(url) 139 | break 140 | case "HEAD": 141 | resp, err = client.R(). 142 | SetBody([]byte(body)). 143 | Head(url) 144 | break 145 | case "OPTIONS": 146 | resp, err = client.R(). 147 | SetBody([]byte(body)). 148 | Options(url) 149 | break 150 | case "PATCH": 151 | resp, err = client.R(). 152 | SetBody([]byte(body)). 153 | Patch(url) 154 | break 155 | case "PUT": 156 | resp, err = client.R(). 157 | SetBody([]byte(body)). 158 | Put(url) 159 | break 160 | case "DELETE": 161 | resp, err = client.R(). 162 | SetBody([]byte(body)). 163 | Delete(url) 164 | break 165 | } 166 | 167 | // in case we want to get redirect stuff 168 | if res.StatusCode != 0 { 169 | return res, nil 170 | } 171 | 172 | if err != nil || resp == nil { 173 | utils.ErrorF("%v %v", url, err) 174 | return libs.Response{}, err 175 | } 176 | 177 | return ParseResponse(*resp), nil 178 | } 179 | 180 | // ParseResponse field to Response 181 | func ParseResponse(resp resty.Response) (res libs.Response) { 182 | // var res libs.Response 183 | resLength := len(string(resp.Body())) 184 | // format the headers 185 | var resHeaders []map[string]string 186 | for k, v := range resp.RawResponse.Header { 187 | element := make(map[string]string) 188 | element[k] = strings.Join(v[:], "") 189 | resLength += len(fmt.Sprintf("%s: %s\n", k, strings.Join(v[:], ""))) 190 | resHeaders = append(resHeaders, element) 191 | } 192 | // respones time in second 193 | resTime := float64(resp.Time()) / float64(time.Second) 194 | resHeaders = append(resHeaders, 195 | map[string]string{"Total Length": strconv.Itoa(resLength)}, 196 | map[string]string{"Response Time": fmt.Sprintf("%f", resTime)}, 197 | ) 198 | 199 | // set some variable 200 | res.Headers = resHeaders 201 | res.StatusCode = resp.StatusCode() 202 | res.Status = fmt.Sprintf("%v %v", resp.Status(), resp.RawResponse.Proto) 203 | res.Body = string(resp.Body()) 204 | res.ResponseTime = resTime 205 | res.Length = resLength 206 | // beautify 207 | res.Beautify = BeautifyResponse(res) 208 | return res 209 | } 210 | 211 | // GetHeaders generate headers if not provide 212 | func GetHeaders(req libs.Request) map[string]string { 213 | // random user agent 214 | UserAgens := []string{ 215 | "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", 216 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3941.0 Safari/537.36", 217 | "Mozilla/5.0 (X11; U; Windows NT 6; en-US) AppleWebKit/534.12 (KHTML, like Gecko) Chrome/9.0.587.0 Safari/534.12", 218 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36", 219 | } 220 | 221 | headers := make(map[string]string) 222 | if len(req.Headers) == 0 { 223 | rand.Seed(time.Now().Unix()) 224 | headers["User-Agent"] = UserAgens[rand.Intn(len(UserAgens))] 225 | return headers 226 | } 227 | 228 | for _, header := range req.Headers { 229 | for key, value := range header { 230 | headers[key] = value 231 | } 232 | } 233 | 234 | rand.Seed(time.Now().Unix()) 235 | // append user agent in case you didn't set user-agent 236 | if headers["User-Agent"] == "" { 237 | rand.Seed(time.Now().Unix()) 238 | headers["User-Agent"] = UserAgens[rand.Intn(len(UserAgens))] 239 | } 240 | return headers 241 | } 242 | 243 | // BeautifyRequest beautify request 244 | func BeautifyRequest(req libs.Request) string { 245 | var beautifyReq string 246 | // hardcord HTTP/1.1 for now 247 | beautifyReq += fmt.Sprintf("%v %v HTTP/1.1\n", req.Method, req.URL) 248 | 249 | for _, header := range req.Headers { 250 | for key, value := range header { 251 | if key != "" && value != "" { 252 | beautifyReq += fmt.Sprintf("%v: %v\n", key, value) 253 | } 254 | } 255 | } 256 | if req.Body != "" { 257 | beautifyReq += fmt.Sprintf("\n%v\n", req.Body) 258 | } 259 | return beautifyReq 260 | } 261 | 262 | // BeautifyResponse beautify response 263 | func BeautifyResponse(res libs.Response) string { 264 | var beautifyRes string 265 | beautifyRes += fmt.Sprintf("%v \n", res.Status) 266 | 267 | for _, header := range res.Headers { 268 | for key, value := range header { 269 | beautifyRes += fmt.Sprintf("%v: %v\n", key, value) 270 | } 271 | } 272 | 273 | beautifyRes += fmt.Sprintf("\n%v\n", res.Body) 274 | return beautifyRes 275 | } 276 | -------------------------------------------------------------------------------- /cmd/scan.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/jaeles-project/jaeles/libs" 7 | "github.com/jaeles-project/jaeles/sender" 8 | "github.com/jaeles-project/jaeles/utils" 9 | "github.com/thoas/go-funk" 10 | "os" 11 | "strings" 12 | "sync" 13 | 14 | "github.com/jaeles-project/jaeles/core" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var scanCmd *cobra.Command 19 | 20 | func init() { 21 | // byeCmd represents the bye command 22 | var scanCmd = &cobra.Command{ 23 | Use: "scan", 24 | Short: "Scan list of URLs based on signatures", 25 | Long: libs.Banner(), 26 | RunE: runScan, 27 | } 28 | 29 | scanCmd.Flags().StringP("url", "u", "", "URL of target") 30 | scanCmd.Flags().StringP("urls", "U", "", "URLs file of target") 31 | scanCmd.Flags().StringP("raw", "r", "", "Raw request from Burp for origin") 32 | scanCmd.SetHelpFunc(ScanHelp) 33 | RootCmd.AddCommand(scanCmd) 34 | 35 | } 36 | 37 | func runScan(cmd *cobra.Command, args []string) error { 38 | SelectSign() 39 | var urls []string 40 | // parse URL input here 41 | urlFile, _ := cmd.Flags().GetString("urls") 42 | urlInput, _ := cmd.Flags().GetString("url") 43 | if urlInput != "" { 44 | urls = append(urls, urlInput) 45 | } 46 | // input as a file 47 | if urlFile != "" { 48 | URLs := utils.ReadingLines(urlFile) 49 | for _, url := range URLs { 50 | urls = append(urls, url) 51 | } 52 | } 53 | 54 | // input as stdin 55 | if len(urls) == 0 { 56 | sc := bufio.NewScanner(os.Stdin) 57 | for sc.Scan() { 58 | url := strings.TrimSpace(sc.Text()) 59 | if err := sc.Err(); err == nil && url != "" { 60 | urls = append(urls, url) 61 | } 62 | } 63 | } 64 | 65 | if len(urls) == 0 { 66 | utils.ErrorF("No Input found") 67 | os.Exit(1) 68 | } 69 | utils.InforF("Input Loaded: %v", len(urls)) 70 | 71 | // get origin request from a file 72 | raw, _ := cmd.Flags().GetString("raw") 73 | var OriginRaw libs.Request 74 | var RawRequest string 75 | if raw != "" { 76 | RawRequest = utils.GetFileContent(raw) 77 | OriginRaw = core.ParseBurpRequest(RawRequest) 78 | } 79 | 80 | // Really start do something 81 | 82 | // run background detector 83 | if !options.NoBackGround { 84 | go func() { 85 | for { 86 | core.Background(options) 87 | } 88 | }() 89 | } 90 | 91 | jobs := make(chan libs.Job) 92 | 93 | var wg sync.WaitGroup 94 | for i := 0; i < options.Concurrency; i++ { 95 | wg.Add(1) 96 | go func() { 97 | for job := range jobs { 98 | sign := job.Sign 99 | url := job.URL 100 | 101 | // get origin from -r req.txt options 102 | if OriginRaw.Raw != "" { 103 | sign.Origin = OriginRaw 104 | } 105 | if RawRequest != "" { 106 | sign.RawRequest = RawRequest 107 | } 108 | // really run the job 109 | RunJob(url, sign, options) 110 | } 111 | wg.Done() 112 | }() 113 | } 114 | 115 | // jobs to send request 116 | for _, signFile := range options.SelectedSigns { 117 | sign, err := core.ParseSign(signFile) 118 | if err != nil { 119 | utils.ErrorF("Error parsing YAML sign %v", signFile) 120 | continue 121 | } 122 | for _, url := range urls { 123 | jobs <- libs.Job{url, sign} 124 | } 125 | } 126 | 127 | close(jobs) 128 | wg.Wait() 129 | return nil 130 | } 131 | 132 | // RunJob really run the job 133 | func RunJob(url string, sign libs.Signature, options libs.Options) { 134 | // var signatures []libs.Signature 135 | var originRec libs.Record 136 | var err error 137 | 138 | // prepare initial signature and variables 139 | Target := core.ParseTarget(url) 140 | Target = core.MoreVariables(Target, sign, options) 141 | 142 | // sending original 143 | if sign.Origin.Method != "" { 144 | var originReq libs.Request 145 | var originRes libs.Response 146 | 147 | originSign := sign 148 | if sign.Origin.Raw == "" { 149 | originSign.Target = Target 150 | originReq = core.ParseOrigin(originSign.Origin, originSign, options) 151 | } else { 152 | originReq = sign.Origin 153 | } 154 | 155 | originRes, err = sender.JustSend(options, originReq) 156 | if err == nil { 157 | if options.Verbose && (originReq.Method != "") { 158 | fmt.Printf("[Sent-Origin] %v %v \n", originReq.Method, originReq.URL) 159 | } 160 | } 161 | originRec.Request = originReq 162 | originRec.Response = originRes 163 | // set some more variables 164 | core.RunConclusions(originRec, &originSign) 165 | for k, v := range originSign.Target { 166 | if Target[k] == "" { 167 | Target[k] = v 168 | } 169 | } 170 | } 171 | singleJob(originRec, sign, Target) 172 | } 173 | 174 | func singleJob(originRec libs.Record, sign libs.Signature, target map[string]string) { 175 | 176 | globalVariables := core.ParseVariable(sign) 177 | if len(globalVariables) > 0 { 178 | // if Parallel not enable, override the threads 179 | var rg sync.WaitGroup 180 | count := 0 181 | for _, globalVariable := range globalVariables { 182 | sign.Target = target 183 | for k, v := range globalVariable { 184 | sign.Target[k] = v 185 | } 186 | 187 | // start to send stuff 188 | for _, req := range sign.Requests { 189 | rg.Add(1) 190 | // receive request from "-r req.txt" 191 | if sign.RawRequest != "" { 192 | req.Raw = sign.RawRequest 193 | } 194 | // gen bunch of request to send 195 | realReqs := core.ParseRequest(req, sign, options) 196 | 197 | // sending things 198 | go func() { 199 | defer rg.Done() 200 | SendRequest(realReqs, sign, originRec) 201 | }() 202 | 203 | count++ 204 | if count == options.Threads { 205 | rg.Wait() 206 | count = 0 207 | } 208 | } 209 | 210 | } 211 | rg.Wait() 212 | } else { 213 | sign.Target = target 214 | //singleJob(originRec, sign) 215 | // start to send stuff 216 | for _, req := range sign.Requests { 217 | // receive request from "-r req.txt" 218 | if sign.RawRequest != "" { 219 | req.Raw = sign.RawRequest 220 | } 221 | // gen bunch of request to send 222 | realReqs := core.ParseRequest(req, sign, options) 223 | // sending things 224 | SendRequest(realReqs, sign, originRec) 225 | //go func () { 226 | //}() 227 | } 228 | } 229 | } 230 | 231 | // SendRequest sending request generated 232 | func SendRequest(realReqs []libs.Request, sign libs.Signature, originRec libs.Record) { 233 | for _, realReq := range realReqs { 234 | var realRec libs.Record 235 | // set some stuff 236 | realRec.OriginReq = originRec.Request 237 | realRec.OriginRes = originRec.Response 238 | realRec.Request = realReq 239 | realRec.Request.Target = sign.Target 240 | realRec.Sign = sign 241 | realRec.ScanID = options.ScanID 242 | 243 | // replace things second time here with values section 244 | core.AltResolveRequest(&realRec.Request) 245 | 246 | // check conditions 247 | if len(realRec.Request.Conditions) > 0 { 248 | validate := checkConditions(realRec) 249 | if !validate { 250 | return 251 | } 252 | } 253 | 254 | // run middleware here 255 | if !funk.IsEmpty(realRec.Request.Middlewares) { 256 | core.MiddleWare(&realRec, options) 257 | } 258 | 259 | req := realRec.Request 260 | // if middleware return the response skip sending it 261 | if realRec.Response.StatusCode == 0 && realRec.Request.Method != "" && realRec.Request.MiddlewareOutput == "" { 262 | var res libs.Response 263 | // sending with real browser 264 | if req.Engine == "chrome" { 265 | res, _ = sender.SendWithChrome(options, req) 266 | } else { 267 | res, _ = sender.JustSend(options, req) 268 | } 269 | realRec.Request = req 270 | realRec.Response = res 271 | } 272 | 273 | DoAnalyze(realRec, &sign) 274 | } 275 | } 276 | 277 | func DoAnalyze(realRec libs.Record, sign *libs.Signature) { 278 | // print some log 279 | if options.Verbose && realRec.Request.Method != "" { 280 | if realRec.Response.StatusCode != 0 { 281 | fmt.Printf("[Sent] %v %v %v %v\n", realRec.Request.Method, realRec.Request.URL, realRec.Response.Status, realRec.Response.ResponseTime) 282 | } 283 | // middleware part 284 | if realRec.Request.MiddlewareOutput != "" { 285 | utils.DebugF(realRec.Request.MiddlewareOutput) 286 | } 287 | } 288 | 289 | // set new values for next request here 290 | core.RunConclusions(realRec, sign) 291 | // really do analyzer 292 | core.Analyze(options, &realRec) 293 | // do passive scan 294 | 295 | if options.EnablePassive { 296 | core.PassiveAnalyze(options, realRec) 297 | // go func() { 298 | // utils.DebugF("Passive Analyze") 299 | // core.PassiveAnalyze(options, realRec) 300 | // }() 301 | } 302 | } 303 | 304 | // check conditions before sending request 305 | func checkConditions(record libs.Record) bool { 306 | for _, conditionString := range record.Request.Conditions { 307 | utils.DebugF("[conditionString] %v", conditionString) 308 | _, check := core.RunDetector(record, conditionString) 309 | if !check { 310 | return false 311 | } 312 | } 313 | return true 314 | } 315 | -------------------------------------------------------------------------------- /core/variables.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "math/rand" 7 | "net/url" 8 | "os/exec" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/jaeles-project/jaeles/libs" 13 | "github.com/jaeles-project/jaeles/utils" 14 | "github.com/robertkrimen/otto" 15 | ) 16 | 17 | // ParseVariable parse variable in YAML signature file 18 | func ParseVariable(sign libs.Signature) []map[string]string { 19 | var realVariables []map[string]string 20 | rawVariables := make(map[string][]string) 21 | // reading variable 22 | for _, variable := range sign.Variables { 23 | for key, value := range variable { 24 | // strip out blank line 25 | if strings.Trim(value, " ") == "" { 26 | continue 27 | } 28 | 29 | // variable as a script 30 | if strings.Contains(value, "(") && strings.Contains(value, ")") { 31 | rawVariables[key] = RunVariables(value) 32 | } 33 | /* 34 | - variable: [google.com,example.com] 35 | */ 36 | // variable as a list 37 | if strings.HasPrefix(value, "[") && strings.Contains(value, ",") { 38 | rawVar := strings.Trim(value[1:len(value)-1], " ") 39 | rawVariables[key] = strings.Split(rawVar, ",") 40 | continue 41 | } 42 | /* 43 | - variable: | 44 | google.com 45 | example.com 46 | */ 47 | if strings.Contains(value, "\n") { 48 | value = strings.Trim(value, "\n\n") 49 | rawVariables[key] = strings.Split(value, "\n") 50 | continue 51 | } 52 | } 53 | } 54 | 55 | if len(rawVariables) == 1 { 56 | for k, v := range rawVariables { 57 | for _, value := range v { 58 | variable := make(map[string]string) 59 | variable[k] = value 60 | realVariables = append(realVariables, variable) 61 | } 62 | } 63 | return realVariables 64 | } 65 | 66 | // select max number of list 67 | var maxLength int 68 | for _, v := range rawVariables { 69 | if maxLength < len(v) { 70 | maxLength = len(v) 71 | } 72 | } 73 | 74 | // @TODO: Need to improve this 75 | if len(rawVariables) == 2 { 76 | if maxLength == 1 { 77 | variable := make(map[string]string) 78 | for k, Variables := range rawVariables { 79 | variable[k] = Variables[0] 80 | } 81 | realVariables = append(realVariables, variable) 82 | return realVariables 83 | } 84 | 85 | tmpVar := make(map[string][]string) 86 | secondVar := make(map[string][]string) 87 | var maxKey string 88 | for k, Variables := range rawVariables { 89 | if len(Variables) == maxLength { 90 | maxKey = k 91 | tmpVar[k] = Variables 92 | } else { 93 | secondVar[k] = Variables 94 | } 95 | } 96 | 97 | for index := 0; index < maxLength; index++ { 98 | for k, v := range secondVar { 99 | for _, value := range v { 100 | variable := make(map[string]string) 101 | variable[maxKey] = tmpVar[maxKey][index] 102 | variable[k] = value 103 | realVariables = append(realVariables, variable) 104 | } 105 | } 106 | } 107 | return realVariables 108 | } 109 | 110 | // make all variable to same length 111 | Variables := make(map[string][]string) 112 | for k, v := range rawVariables { 113 | Variables[k] = utils.ExpandLength(v, maxLength) 114 | } 115 | 116 | // join all together to make list of map variable 117 | for i := 0; i < maxLength; i++ { 118 | for j := 0; j < maxLength; j++ { 119 | variable := make(map[string]string) 120 | for k, v := range Variables { 121 | variable[k] = v[j] 122 | } 123 | realVariables = append(realVariables, variable) 124 | } 125 | } 126 | 127 | seen := make(map[string]bool) 128 | // just unique the variables 129 | var uniqVariables []map[string]string 130 | for index := 0; index < len(realVariables); index++ { 131 | for k, v := range realVariables[index] { 132 | val := fmt.Sprintf("%v%v", k, v) 133 | if _, ok := seen[val]; !ok { 134 | // fmt.Println(k, v) 135 | seen[val] = true 136 | uniqVariables = append(uniqVariables, realVariables[index]) 137 | } 138 | } 139 | } 140 | 141 | return uniqVariables 142 | } 143 | 144 | // RunVariables is main function for detections 145 | func RunVariables(variableString string) []string { 146 | var extra []string 147 | if !strings.Contains(variableString, "(") { 148 | return extra 149 | } 150 | 151 | vm := otto.New() 152 | 153 | vm.Set("ExecJS", func(call otto.FunctionCall) otto.Value { 154 | jscode := call.Argument(0).String() 155 | value, err := vm.Run(jscode) 156 | if err == nil { 157 | data := value.String() 158 | extra = append(extra, data) 159 | } 160 | return otto.Value{} 161 | }) 162 | 163 | vm.Set("File", func(call otto.FunctionCall) otto.Value { 164 | filename := call.Argument(0).String() 165 | data := utils.ReadingLines(filename) 166 | if len(data) > 0 { 167 | extra = append(extra, data...) 168 | } 169 | return otto.Value{} 170 | }) 171 | 172 | vm.Set("InputCmd", func(call otto.FunctionCall) otto.Value { 173 | cmd := call.Argument(0).String() 174 | data := InputCmd(cmd) 175 | if len(data) <= 0 { 176 | return otto.Value{} 177 | } 178 | if !strings.Contains(data, "\n") { 179 | extra = append(extra, data) 180 | return otto.Value{} 181 | } 182 | extra = append(extra, strings.Split(data, "\n")...) 183 | return otto.Value{} 184 | }) 185 | 186 | vm.Set("RandomString", func(call otto.FunctionCall) otto.Value { 187 | length, err := strconv.Atoi(call.Argument(0).String()) 188 | if err != nil { 189 | return otto.Value{} 190 | } 191 | extra = append(extra, RandomString(length)) 192 | return otto.Value{} 193 | }) 194 | 195 | vm.Set("RandomNumber", func(call otto.FunctionCall) otto.Value { 196 | length, err := strconv.Atoi(call.Argument(0).String()) 197 | if err != nil { 198 | return otto.Value{} 199 | } 200 | extra = append(extra, RandomNumber(length)) 201 | return otto.Value{} 202 | }) 203 | 204 | vm.Set("Range", func(call otto.FunctionCall) otto.Value { 205 | min, err := strconv.Atoi(call.Argument(0).String()) 206 | max, err := strconv.Atoi(call.Argument(1).String()) 207 | if err != nil { 208 | return otto.Value{} 209 | } 210 | for i := min; i < max; i++ { 211 | extra = append(extra, fmt.Sprintf("%v", i)) 212 | } 213 | return otto.Value{} 214 | }) 215 | 216 | vm.Set("SplitLines", func(call otto.FunctionCall) otto.Value { 217 | data := call.Argument(0).String() 218 | extra = append(extra, SplitLines(data)...) 219 | return otto.Value{} 220 | }) 221 | 222 | vm.Set("Base64Encode", func(call otto.FunctionCall) otto.Value { 223 | data := call.Argument(0).String() 224 | extra = append(extra, Base64Encode(data)) 225 | return otto.Value{} 226 | }) 227 | 228 | vm.Set("Base64EncodeByLines", func(call otto.FunctionCall) otto.Value { 229 | data := SplitLines(call.Argument(0).String()) 230 | if len(data) == 0 { 231 | return otto.Value{} 232 | } 233 | for _, line := range data { 234 | extra = append(extra, Base64Encode(line)) 235 | } 236 | return otto.Value{} 237 | }) 238 | 239 | vm.Set("URLEncode", func(call otto.FunctionCall) otto.Value { 240 | data := call.Argument(0).String() 241 | extra = append(extra, URLEncode(data)) 242 | return otto.Value{} 243 | }) 244 | 245 | vm.Set("URLEncodeByLines", func(call otto.FunctionCall) otto.Value { 246 | data := SplitLines(call.Argument(0).String()) 247 | if len(data) == 0 { 248 | return otto.Value{} 249 | } 250 | for _, line := range data { 251 | extra = append(extra, URLEncode(line)) 252 | } 253 | return otto.Value{} 254 | }) 255 | 256 | vm.Run(variableString) 257 | return extra 258 | } 259 | 260 | // RandomString return a random string with length 261 | func RandomString(n int) string { 262 | var letter = []rune("abcdefghijklmnopqrstuvwxyz") 263 | b := make([]rune, n) 264 | for i := range b { 265 | b[i] = letter[rand.Intn(len(letter))] 266 | } 267 | return string(b) 268 | } 269 | 270 | // RandomNumber return a random number with length 271 | func RandomNumber(n int) string { 272 | var letter = []rune("0123456789") 273 | b := make([]rune, n) 274 | for i := range b { 275 | b[i] = letter[rand.Intn(len(letter))] 276 | } 277 | result := string(b) 278 | if !strings.HasPrefix(result, "0") || len(result) == 1 { 279 | return result 280 | } 281 | return result[1:] 282 | } 283 | 284 | // InputCmd take input as os command 285 | // @NOTE: this is a feature not an RCE :P 286 | func InputCmd(Cmd string) string { 287 | command := []string{ 288 | "bash", 289 | "-c", 290 | Cmd, 291 | } 292 | out, _ := exec.Command(command[0], command[1:]...).CombinedOutput() 293 | return strings.TrimSpace(string(out)) 294 | } 295 | 296 | // SplitLines just split new Line 297 | func SplitLines(raw string) []string { 298 | var result []string 299 | if strings.Contains(raw, "\n") { 300 | result = strings.Split(raw, "\n") 301 | } 302 | return result 303 | } 304 | 305 | // Base64Encode just Base64 Encode 306 | func Base64Encode(raw string) string { 307 | return base64.StdEncoding.EncodeToString([]byte(raw)) 308 | } 309 | 310 | // URLEncode just URL Encode 311 | func URLEncode(raw string) string { 312 | return url.QueryEscape(raw) 313 | } 314 | -------------------------------------------------------------------------------- /core/detecter.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jaeles-project/jaeles/utils" 6 | "net/url" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/Jeffail/gabs/v2" 12 | "github.com/jaeles-project/jaeles/database" 13 | "github.com/jaeles-project/jaeles/libs" 14 | "github.com/parnurzeal/gorequest" 15 | 16 | "github.com/robertkrimen/otto" 17 | ) 18 | 19 | // RunDetector is main function for detections 20 | func RunDetector(record libs.Record, detectionString string) (string, bool) { 21 | var extra string 22 | vm := otto.New() 23 | 24 | // ExecCmd execute command command 25 | vm.Set("ExecCmd", func(call otto.FunctionCall) otto.Value { 26 | result, _ := vm.ToValue(Execution(call.Argument(0).String())) 27 | return result 28 | }) 29 | 30 | vm.Set("StringGrepCmd", func(call otto.FunctionCall) otto.Value { 31 | command := call.Argument(0).String() 32 | searchString := call.Argument(0).String() 33 | result, _ := vm.ToValue(StringSearch(Execution(command), searchString)) 34 | return result 35 | }) 36 | 37 | vm.Set("RegexGrepCmd", func(call otto.FunctionCall) otto.Value { 38 | command := call.Argument(0).String() 39 | searchString := call.Argument(0).String() 40 | result, _ := vm.ToValue(RegexSearch(Execution(command), searchString)) 41 | return result 42 | }) 43 | 44 | vm.Set("StringSearch", func(call otto.FunctionCall) otto.Value { 45 | componentName := call.Argument(0).String() 46 | analyzeString := call.Argument(1).String() 47 | component := GetComponent(record, componentName) 48 | validate := StringSearch(component, analyzeString) 49 | result, _ := vm.ToValue(validate) 50 | return result 51 | }) 52 | 53 | vm.Set("StringCount", func(call otto.FunctionCall) otto.Value { 54 | componentName := call.Argument(0).String() 55 | analyzeString := call.Argument(1).String() 56 | component := GetComponent(record, componentName) 57 | validate := StringCount(component, analyzeString) 58 | result, _ := vm.ToValue(validate) 59 | return result 60 | }) 61 | 62 | vm.Set("RegexSearch", func(call otto.FunctionCall) otto.Value { 63 | componentName := call.Argument(0).String() 64 | analyzeString := call.Argument(1).String() 65 | component := GetComponent(record, componentName) 66 | validate := RegexSearch(component, analyzeString) 67 | result, err := vm.ToValue(validate) 68 | if err != nil { 69 | utils.ErrorF("Error Regex: %v", analyzeString) 70 | result, _ = vm.ToValue(false) 71 | } 72 | return result 73 | }) 74 | 75 | vm.Set("RegexCount", func(call otto.FunctionCall) otto.Value { 76 | componentName := call.Argument(0).String() 77 | analyzeString := call.Argument(1).String() 78 | component := GetComponent(record, componentName) 79 | validate := RegexCount(component, analyzeString) 80 | result, _ := vm.ToValue(validate) 81 | return result 82 | }) 83 | 84 | vm.Set("StatusCode", func(call otto.FunctionCall) otto.Value { 85 | statusCode := record.Response.StatusCode 86 | result, _ := vm.ToValue(statusCode) 87 | return result 88 | }) 89 | vm.Set("ResponseTime", func(call otto.FunctionCall) otto.Value { 90 | responseTime := record.Response.ResponseTime 91 | result, _ := vm.ToValue(responseTime) 92 | return result 93 | }) 94 | vm.Set("ContentLength", func(call otto.FunctionCall) otto.Value { 95 | ContentLength := record.Response.Length 96 | result, _ := vm.ToValue(ContentLength) 97 | return result 98 | }) 99 | 100 | vm.Set("HasPopUp", func(call otto.FunctionCall) otto.Value { 101 | result, _ := vm.ToValue(record.Response.HasPopUp) 102 | return result 103 | }) 104 | 105 | // Origin field 106 | vm.Set("OriginStatusCode", func(call otto.FunctionCall) otto.Value { 107 | statusCode := record.OriginRes.StatusCode 108 | result, _ := vm.ToValue(statusCode) 109 | return result 110 | }) 111 | vm.Set("OriginResponseTime", func(call otto.FunctionCall) otto.Value { 112 | responseTime := record.OriginRes.ResponseTime 113 | result, _ := vm.ToValue(responseTime) 114 | return result 115 | }) 116 | vm.Set("OriginContentLength", func(call otto.FunctionCall) otto.Value { 117 | ContentLength := record.OriginRes.Length 118 | result, _ := vm.ToValue(ContentLength) 119 | return result 120 | }) 121 | vm.Set("Collab", func(call otto.FunctionCall) otto.Value { 122 | analyzeString := call.Argument(0).String() 123 | res, validate := PollCollab(record, analyzeString) 124 | extra = res 125 | result, _ := vm.ToValue(validate) 126 | return result 127 | }) 128 | 129 | // StringGrep select a string from component 130 | // e.g: StringGrep("component", "right", "left") 131 | vm.Set("StringSelect", func(call otto.FunctionCall) otto.Value { 132 | componentName := call.Argument(0).String() 133 | left := call.Argument(2).String() 134 | right := call.Argument(3).String() 135 | component := GetComponent(record, componentName) 136 | value := Between(component, left, right) 137 | result, _ := vm.ToValue(value) 138 | return result 139 | }) 140 | 141 | // - RegexGrep("component", "regex") 142 | // - RegexGrep("component", "regex", "position") 143 | vm.Set("RegexGrep", func(call otto.FunctionCall) otto.Value { 144 | value := RegexGrep(record, call.ArgumentList) 145 | result, _ := vm.ToValue(value) 146 | return result 147 | }) 148 | 149 | vm.Set("ValueOf", func(call otto.FunctionCall) otto.Value { 150 | valueName := call.Argument(0).String() 151 | if record.Request.Target[valueName] != "" { 152 | value := record.Request.Target[valueName] 153 | result, _ := vm.ToValue(value) 154 | return result 155 | } 156 | result, _ := vm.ToValue(false) 157 | return result 158 | }) 159 | 160 | // check if folder, file exist or not 161 | vm.Set("Exist", func(call otto.FunctionCall) otto.Value { 162 | input := utils.NormalizePath(call.Argument(0).String()) 163 | var exist bool 164 | if utils.FileExists(input) { 165 | exist = true 166 | } 167 | if utils.FolderExists(input) { 168 | exist = true 169 | } 170 | result, _ := vm.ToValue(exist) 171 | return result 172 | }) 173 | 174 | result, _ := vm.Run(detectionString) 175 | analyzeResult, err := result.Export() 176 | if err != nil || analyzeResult == nil { 177 | return "", false 178 | } 179 | return extra, analyzeResult.(bool) 180 | } 181 | 182 | // GetComponent get component to run detection 183 | func GetComponent(record libs.Record, component string) string { 184 | switch strings.ToLower(component) { 185 | case "orequest": 186 | return record.OriginReq.Beautify 187 | case "oresponse": 188 | return record.OriginRes.Beautify 189 | case "request": 190 | return record.Request.Beautify 191 | case "response": 192 | if record.Response.Beautify == "" { 193 | return record.Response.Body 194 | } 195 | return record.Response.Beautify 196 | case "resheaders": 197 | beautifyHeader := fmt.Sprintf("%v \n", record.Response.Status) 198 | for _, header := range record.Response.Headers { 199 | for key, value := range header { 200 | beautifyHeader += fmt.Sprintf("%v: %v\n", key, value) 201 | } 202 | } 203 | return beautifyHeader 204 | case "resbody": 205 | return record.Response.Body 206 | case "middleware": 207 | return record.Request.MiddlewareOutput 208 | default: 209 | return record.Response.Beautify 210 | } 211 | } 212 | 213 | // StringSearch search string literal in component 214 | func StringSearch(component string, analyzeString string) bool { 215 | if strings.Contains(component, analyzeString) { 216 | return true 217 | } 218 | return false 219 | } 220 | 221 | // StringCount count string literal in component 222 | func StringCount(component string, analyzeString string) int { 223 | return strings.Count(component, analyzeString) 224 | } 225 | 226 | // RegexSearch search regex string in component 227 | func RegexSearch(component string, analyzeString string) bool { 228 | r, err := regexp.Compile(analyzeString) 229 | if err != nil { 230 | return false 231 | } 232 | return r.MatchString(component) 233 | } 234 | 235 | // RegexCount count regex string in component 236 | func RegexCount(component string, analyzeString string) int { 237 | r, err := regexp.Compile(analyzeString) 238 | if err != nil { 239 | return 0 240 | } 241 | matches := r.FindAllStringIndex(component, -1) 242 | return len(matches) 243 | } 244 | 245 | // RegexGrep grep regex string from component 246 | func RegexGrep(realRec libs.Record, arguments []otto.Value) string { 247 | componentName := arguments[0].String() 248 | component := GetComponent(realRec, componentName) 249 | 250 | regexString := arguments[1].String() 251 | var position int 252 | var err error 253 | if len(arguments) > 2 { 254 | position, err = strconv.Atoi(arguments[2].String()) 255 | if err != nil { 256 | position = 0 257 | } 258 | } 259 | 260 | var value string 261 | r, rerr := regexp.Compile(regexString) 262 | if rerr != nil { 263 | return "" 264 | } 265 | matches := r.FindStringSubmatch(component) 266 | if len(matches) > 0 { 267 | if position <= len(matches) { 268 | value = matches[position] 269 | } else { 270 | value = matches[0] 271 | } 272 | } 273 | return value 274 | } 275 | 276 | // PollCollab polling burp collab with secret from DB 277 | func PollCollab(record libs.Record, analyzeString string) (string, bool) { 278 | // only checking response return in external OOB 279 | ssrf := database.GetDefaultBurpCollab() 280 | if ssrf != "" { 281 | res := database.GetDefaultBurpRes() 282 | result := StringSearch(record.Response.Beautify, res) 283 | return res, result 284 | } 285 | 286 | // storing raw here so we can poll later 287 | database.ImportReqLog(record, analyzeString) 288 | secretCollab := url.QueryEscape(database.GetSecretbyCollab(analyzeString)) 289 | 290 | // poll directly 291 | url := fmt.Sprintf("http://polling.burpcollaborator.net/burpresults?biid=%v", secretCollab) 292 | _, response, _ := gorequest.New().Get(url).End() 293 | jsonParsed, _ := gabs.ParseJSON([]byte(response)) 294 | exists := jsonParsed.Exists("responses") 295 | if exists == false { 296 | data := database.GetOOB(analyzeString) 297 | if data != "" { 298 | return data, strings.Contains(data, analyzeString) 299 | } 300 | return "", false 301 | } 302 | 303 | // jsonParsed.Path("responses").Children() 304 | for _, element := range jsonParsed.Path("responses").Children() { 305 | protocol := element.Path("protocol").Data().(string) 306 | // import this to DB so we don't miss in other detect 307 | database.ImportOutOfBand(fmt.Sprintf("%v", element)) 308 | if protocol == "http" { 309 | interactionString := element.Path("interactionString").Data().(string) 310 | return element.String(), strings.Contains(analyzeString, interactionString) 311 | } else if protocol == "dns" { 312 | interactionString := element.Path("interactionString").Data().(string) 313 | return element.String(), strings.Contains(analyzeString, interactionString) 314 | } 315 | } 316 | 317 | return "", false 318 | } 319 | -------------------------------------------------------------------------------- /core/parser_test.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestAltResolveVariable(t *testing.T) { 10 | format := `https://brutelogic.com.br/tests/sinks.html?name=[[.custom]]xvlb` 11 | target := make(map[string]string) 12 | target["custom"] = "zzz" 13 | result := AltResolveVariable(format, target) 14 | fmt.Println(result) 15 | if result != "2sam1" { 16 | t.Errorf("Error resolve variable") 17 | } 18 | } 19 | 20 | func TestResolveVariable(t *testing.T) { 21 | // format := `2{{constructor.constructor('alert(1)')()}}{{.sam}}` 22 | format := `2 {{constructor('alert(1)')()}} {{.var}}` 23 | target := make(map[string]string) 24 | target["var"] = "sam" 25 | result := ResolveVariable(format, target) 26 | fmt.Println(result) 27 | 28 | format = `2{{constructor.constructor('alert(1)')()}}{{.sam}}` 29 | format = `2 {{"{{"}}constructor('alert(1)')()}} {{.var}}` 30 | result = ResolveVariable(format, target) 31 | 32 | fmt.Println(result) 33 | if !strings.HasSuffix(result, "sam") { 34 | t.Errorf("Error resolve variable") 35 | } 36 | } 37 | 38 | func TestParseBurpRequest(t *testing.T) { 39 | raw := `GET /test HTTP/1.1 40 | Host: cors-test.appspot.com 41 | Connection: close 42 | Accept: */* 43 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3983.0 Safari/537.36 44 | Sec-Fetch-Site: cross-site 45 | Sec-Fetch-Mode: cors 46 | Referer: https://a.appspot.com/ 47 | Accept-Encoding: gzip, deflate 48 | Accept-Language: en-US,en;q=0.9 49 | 50 | ` 51 | 52 | req := ParseBurpRequest(raw) 53 | fmt.Println(req.URL) 54 | fmt.Println(req.Host) 55 | fmt.Println(req.Path) 56 | if req.Method == "" { 57 | t.Errorf("Error parsing Burp") 58 | } 59 | } 60 | 61 | func TestParseBurpResponse(t *testing.T) { 62 | rawReq := `GET /listproducts.php?cat=1 HTTP/1.1 63 | Host: testphp.vulnweb.com 64 | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:71.0) Gecko/20100101 Firefox/71.0 65 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 66 | Accept-Language: en-US,en;q=0.5 67 | Accept-Encoding: gzip, deflate 68 | Connection: close 69 | Upgrade-Insecure-Requests: 1 70 | 71 | ` 72 | 73 | rawRes := `HTTP/1.1 200 OK 74 | Server: nginx/1.4.1 75 | Date: Thu, 08 Jan 1970 04:26:25 GMT 76 | Content-Type: text/html 77 | Connection: close 78 | X-Powered-By: PHP/5.3.10-1~lucid+2uwsgi2 79 | Content-Length: 7880 80 | 81 | 83 | 84 | 85 | 86 | 87 | 88 | pictures 89 | 90 | 91 | 92 | 93 | 103 | 104 | 105 | 106 |
107 |
108 |

Acunetix website security

109 |
TEST and Demonstration site for Acunetix Web Vulnerability Scanner
110 | 122 |
123 | 124 | 125 | 126 | 127 |
128 |

Posters

The shore

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec molestie. 129 | Sed aliquam sem ut arcu.

painted by: r4w8173

comment on this picture

Mistery

Donec molestie. 130 | Sed aliquam sem ut arcu.

painted by: r4w8173

comment on this picture

The universe

Lorem ipsum dolor sit amet. Donec molestie. 131 | Sed aliquam sem ut arcu.

painted by: r4w8173

comment on this picture

Walking

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec molestie. 132 | Sed aliquam sem ut arcu. Phasellus sollicitudin. 133 |

painted by: r4w8173

comment on this picture

Mean

Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

painted by: r4w8173

comment on this picture

Trees

bla bla bla

painted by: Blad3

comment on this picture

134 | 135 | 136 | 137 | 176 | 177 | 178 |
About Us | Privacy Policy | Contact Us | ©2019 179 | Acunetix Ltd 180 |
181 |
182 |
183 |

Warning: This is not a real shop. This is an example PHP application, which is intentionally vulnerable to web attacks. It is intended to help you test Acunetix. It also helps you understand how developer errors and bad configuration may let someone break into your website. You can use it to test other tools and your manual hacking skills as well. Tip: Look for potential SQL Injections, Cross-site Scripting (XSS), and Cross-site Request Forgery (CSRF), and more.

184 |
185 |
186 | 187 | 188 | ` 189 | res := ParseBurpResponse(rawReq, rawRes) 190 | fmt.Println(res.Status) 191 | fmt.Println(res.Headers) 192 | if res.StatusCode != 200 { 193 | t.Errorf("Error parsing Burp Response") 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /core/passive.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/sha1" 5 | "fmt" 6 | "github.com/fatih/color" 7 | "github.com/jaeles-project/jaeles/libs" 8 | "github.com/jaeles-project/jaeles/utils" 9 | "net/url" 10 | "os" 11 | "path" 12 | "strings" 13 | ) 14 | 15 | // PassiveAnalyze do passive analyzer based on default passive signature 16 | func PassiveAnalyze(options libs.Options, record libs.Record) { 17 | if !utils.FolderExists(options.PassiveFolder) { 18 | return 19 | } 20 | passives := GetPassives(options) 21 | //spew.Dump(passives) 22 | if len(passives) <= 0 { 23 | return 24 | } 25 | options.PassiveOutput = getPassiveOutput(options) 26 | for _, passive := range passives { 27 | for _, rule := range passive.Rules { 28 | if len(rule.Detections) <= 0 { 29 | continue 30 | } 31 | runPassive(options, record, rule) 32 | } 33 | } 34 | } 35 | 36 | func runPassive(options libs.Options, record libs.Record, rule libs.Rule) { 37 | for _, detectionString := range rule.Detections { 38 | utils.DebugF("[Passive] %v", rule.Reason) 39 | extra, result := RunDetector(record, detectionString) 40 | if extra != "" { 41 | record.ExtraOutput = extra 42 | } 43 | if result == true { 44 | var outputName string 45 | if options.NoOutput == false { 46 | outputName = StorePassiveOutput(record, rule, detectionString, options) 47 | record.RawOutput = outputName 48 | } 49 | color.Yellow("[Passive][%v] %v %v", record.Sign.Info.Risk, record.Request.URL, outputName) 50 | } 51 | } 52 | } 53 | 54 | func getPassiveOutput(options libs.Options) string { 55 | passiveOut := "passive-" + path.Base(options.Output) 56 | passiveOut = path.Join(path.Dir(options.Output), passiveOut) 57 | return passiveOut 58 | } 59 | 60 | // GetPassives get all passives rule 61 | func GetPassives(options libs.Options) []libs.Passive { 62 | var passives []libs.Passive 63 | passives = append(passives, defaultPassive()) 64 | passiveFiles := utils.GetFileNames(utils.NormalizePath(options.PassiveFolder), "yaml") 65 | for _, passiveFile := range passiveFiles { 66 | passive, err := ParsePassive(passiveFile) 67 | if err == nil { 68 | passives = append(passives, passive) 69 | } 70 | } 71 | return passives 72 | } 73 | 74 | // StorePassiveOutput store passive output found 75 | func StorePassiveOutput(record libs.Record, rule libs.Rule, detectionString string, options libs.Options) string { 76 | content := fmt.Sprintf("[%v] - %v\n\n", rule.ID, record.Request.URL) 77 | content += fmt.Sprintf("[%v] - %v\n\n", rule.Reason, detectionString) 78 | 79 | if record.Request.MiddlewareOutput != "" { 80 | content += strings.Join(record.Request.Middlewares, "\n") 81 | content += fmt.Sprintf("\n%v\n", strings.Repeat("-", 50)) 82 | content += record.Request.MiddlewareOutput 83 | } 84 | if record.ExtraOutput != "" { 85 | content += strings.Join(record.Request.Middlewares, "\n") 86 | content += fmt.Sprintf("\n%v\n", strings.Repeat("-", 50)) 87 | content += record.ExtraOutput 88 | } 89 | if record.ExtraOutput == "" && record.Request.MiddlewareOutput == "" { 90 | content += record.Request.Beautify 91 | content += fmt.Sprintf("\n%v\n", strings.Repeat("-", 50)) 92 | content += record.Response.Beautify 93 | } 94 | 95 | // hash the content 96 | h := sha1.New() 97 | h.Write([]byte(content)) 98 | checksum := h.Sum(nil) 99 | parts := []string{options.PassiveOutput} 100 | if record.Request.URL == "" { 101 | parts = append(parts, record.Request.Target["Domain"]) 102 | } else { 103 | u, _ := url.Parse(record.Request.URL) 104 | parts = append(parts, u.Hostname()) 105 | } 106 | parts = append(parts, fmt.Sprintf("%v-%x", utils.StripName(rule.ID), checksum)) 107 | p := path.Join(parts...) 108 | if _, err := os.Stat(path.Dir(p)); os.IsNotExist(err) { 109 | err = os.MkdirAll(path.Dir(p), 0750) 110 | if err != nil { 111 | utils.ErrorF("Error Write content to: %v", p) 112 | } 113 | } 114 | utils.WriteToFile(p, content) 115 | return p 116 | } 117 | 118 | // default rule 119 | func defaultPassive() libs.Passive { 120 | rules := []libs.Rule{ 121 | libs.Rule{ 122 | ID: "default-error-01", 123 | Reason: "SQL Error", 124 | Detections: []string{ 125 | `RegexSearch("response", "(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)")`, 126 | }}, 127 | libs.Rule{ 128 | ID: "default-error-02", 129 | Reason: "General Error", 130 | Detections: []string{ 131 | `RegexSearch("response", "injectx|stack smashing detected|Backtrace|Memory map|500 Internal Server Error|Set-Cookie:\\scrlf=injection|java\\.io\\.FileNotFoundException|java\\.lang\\.Exception|java\\.lang\\.IllegalArgumentException|java\\.net\\.MalformedURLException|Warning: include\\(|Warning: unlink\\(|for inclusion \\(include_path=|fread\\(|Failed opening required|Warning: file_get_contents\\(|Fatal error: require_once\\(|Warning: file_exists\\(|root:|(uid|gid|groups)=\\d+|bytes from \\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b|Configuration File \\(php\\.ini\\) Path |vulnerable 10|Trying \\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b|\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b\\s+localhost|BROADCAST,MULTICAST|drwxr-xr|Active Internet connections|Syntax error|sh:|Average Speed Time|dir: cannot access||drwxrwxr|GNU/Linux|(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)|System\\.Xml\\.XPath\\.XPathException|MS\\.Internal\\.Xml|Unknown error in XPath|org\\.apache\\.xpath\\.XPath|A closing bracket expected in|An operand in Union Expression does not produce a node-set|Cannot convert expression to a number|Document Axis does not allow any context Location Steps|Empty Path Expression|DOMXPath|Empty Relative Location Path|Empty Union Expression|Expected \\'\\)\\' in|Expected node test or name specification after axis operator|Incompatible XPath key|Incorrect Variable Binding|libxml2 library function failed|libxml2|Invalid predicate|Invalid expression|xmlsec library function|xmlsec|error \\'80004005\\'|A document must contain exactly one root element|Expression must evaluate to a node-set|Expected token ']'|

msxml4\\.dll<\\/font>|

msxml3\\.dll<\\/font>|4005 Notes error: Query is not understandable|SimpleXMLElement::xpath|xmlXPathEval:|simplexml_load_string|parser error :|An error occured!|xmlParseEntityDecl|simplexml_load_string|xmlParseInternalSubset|DOCTYPE improperly terminated|Start tag expected|No declaration for attribute|No declaration for element|failed to load external entity|Start tag expected|Invalid URI: file:\\/\\/\\/|Malformed declaration expecting version|Unicode strings with encoding|must be well-formed|Content is not allowed in prolog|org.xml.sax|SAXParseException|com.sun.org.apache.xerces|ParseError|nokogiri|REXML|XML syntax error on line|Error unmarshaling XML|conflicts with field|illegal character code|XML Parsing Error|SyntaxError|no root element|not well-formed\n")`}, 132 | }, 133 | ////// 134 | libs.Rule{ 135 | ID: "default-error-03", 136 | Reason: "PHP Error", 137 | Detections: []string{ 138 | `RegexSearch("response", "Warning: include\(|Warning: unlink\(|for inclusion \(include_path=|fread\(|Failed opening required|Warning: file_get_contents\(|Fatal error: require_once\(|Warning: file_exists\(")`}, 139 | }, 140 | 141 | libs.Rule{ 142 | ID: "default-error-04", 143 | Reason: "Java Error", 144 | Detections: []string{ 145 | `RegexSearch("response", "java\.io\.FileNotFoundException|java\.lang\.Exception|java\.lang\.IllegalArgumentException|java\.net\.MalformedURLException"`}, 146 | }, 147 | 148 | libs.Rule{ 149 | ID: "default-error-05", 150 | Reason: "XML Error", 151 | Detections: []string{ 152 | `RegexSearch("response", "simplexml_load_string|parser error :|An error occured!|xmlParseEntityDecl|simplexml_load_string|xmlParseInternalSubset|DOCTYPE improperly terminated|Start tag expected|No declaration for attribute|No declaration for element|failed to load external entity|Start tag expected|Invalid URI: file:\/\/\/|Malformed declaration expecting version|Unicode strings with encoding|must be well-formed|Content is not allowed in prolog|org.xml.sax|SAXParseException|com.sun.org.apache.xerces|ParseError|nokogiri|REXML|XML syntax error on line|Error unmarshaling XML|conflicts with field|illegal character code|XML Parsing Error|SyntaxError|no root element|not well-formed"`}, 153 | }, 154 | 155 | libs.Rule{ 156 | ID: "default-error-06", 157 | Reason: "RCE Error", 158 | Detections: []string{ 159 | `RegexSearch("response", "root:|(uid|gid|groups)=\d+|bytes from \b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b|Configuration File \(php\.ini\) Path |vulnerable 10|Trying \b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b|\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b\s+localhost|BROADCAST,MULTICAST|drwxr-xr|Active Internet connections|Syntax error|sh:|Average Speed Time|dir: cannot access||drwxrwxr|GNU/Linux"`}, 160 | }, 161 | 162 | //libs.Rule{ 163 | // ID: "default-error-02", 164 | // Reason: "General Error", 165 | // Detections: []string{ 166 | // `RegexSearch("response", "regexhere"`}, 167 | //}, 168 | 169 | } 170 | return libs.Passive{ 171 | Name: "Default", 172 | Desc: "Default Rule for catching common Error", 173 | Rules: rules, 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /core/parser.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/jaeles-project/jaeles/utils" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/jaeles-project/jaeles/database" 14 | "github.com/jaeles-project/jaeles/libs" 15 | "github.com/thoas/go-funk" 16 | "gopkg.in/yaml.v2" 17 | ) 18 | 19 | // ParseSign parsing YAML signature file 20 | func ParseSign(signFile string) (sign libs.Signature, err error) { 21 | yamlFile, err := ioutil.ReadFile(signFile) 22 | if err != nil { 23 | utils.ErrorF("yamlFile.Get err #%v - %v", err, signFile) 24 | } 25 | err = yaml.Unmarshal(yamlFile, &sign) 26 | if err != nil { 27 | utils.ErrorF("Error: %v - %v", err, signFile) 28 | } 29 | // set some default value 30 | if sign.Info.Category == "" { 31 | if strings.Contains(sign.ID, "-") { 32 | sign.Info.Category = strings.Split(sign.ID, "-")[0] 33 | } else { 34 | sign.Info.Category = sign.ID 35 | } 36 | } 37 | if sign.Info.Name == "" { 38 | sign.Info.Name = sign.ID 39 | } 40 | if sign.Info.Risk == "" { 41 | sign.Info.Risk = "Potential" 42 | } 43 | return sign, err 44 | } 45 | 46 | // ParsePassive parsing YAML passive file 47 | func ParsePassive(passiveFile string) (passive libs.Passive, err error) { 48 | yamlFile, err := ioutil.ReadFile(passiveFile) 49 | if err != nil { 50 | utils.ErrorF("yamlFile.Get err #%v - %v", err, passiveFile) 51 | } 52 | err = yaml.Unmarshal(yamlFile, &passive) 53 | if err != nil { 54 | utils.ErrorF("Error: %v - %v", err, passiveFile) 55 | } 56 | return passive, err 57 | } 58 | 59 | // ParseTarget parsing target and some variable for template 60 | func ParseTarget(raw string) map[string]string { 61 | target := make(map[string]string) 62 | target["Raw"] = raw 63 | if raw == "" { 64 | return target 65 | } 66 | u, err := url.Parse(raw) 67 | 68 | // something wrong so parsing it again 69 | if err != nil || u.Scheme == "" || strings.Contains(u.Scheme, ".") { 70 | raw = fmt.Sprintf("https://%v", raw) 71 | u, err = url.Parse(raw) 72 | if err != nil { 73 | return target 74 | } 75 | // fmt.Println("parse again") 76 | } 77 | var hostname string 78 | var query string 79 | port := u.Port() 80 | // var domain string 81 | domain := u.Hostname() 82 | 83 | query = u.RawQuery 84 | if u.Port() == "" { 85 | if strings.Contains(u.Scheme, "https") { 86 | port = "443" 87 | } else { 88 | port = "80" 89 | } 90 | 91 | hostname = u.Hostname() 92 | } else { 93 | // ignore common port in Host 94 | if u.Port() == "443" || u.Port() == "80" { 95 | hostname = u.Hostname() 96 | } else { 97 | hostname = u.Hostname() + ":" + u.Port() 98 | } 99 | } 100 | 101 | target["Scheme"] = u.Scheme 102 | target["Path"] = u.Path 103 | target["Domain"] = domain 104 | target["Host"] = hostname 105 | target["Port"] = port 106 | target["RawQuery"] = query 107 | 108 | if (target["RawQuery"] != "") && (port == "80" || port == "443") { 109 | target["URL"] = fmt.Sprintf("%v://%v%v?%v", target["Scheme"], target["Host"], target["Path"], target["RawQuery"]) 110 | } else if port != "80" && port != "443" { 111 | target["URL"] = fmt.Sprintf("%v://%v:%v%v?%v", target["Scheme"], target["Domain"], target["Port"], target["Path"], target["RawQuery"]) 112 | } else { 113 | target["URL"] = fmt.Sprintf("%v://%v%v", target["Scheme"], target["Host"], target["Path"]) 114 | } 115 | 116 | uu, _ := url.Parse(raw) 117 | target["BaseURL"] = fmt.Sprintf("%v://%v", uu.Scheme, uu.Host) 118 | target["Extension"] = filepath.Ext(target["BaseURL"]) 119 | return target 120 | } 121 | 122 | // MoreVariables get more options to render in sign template 123 | func MoreVariables(target map[string]string, sign libs.Signature, options libs.Options) map[string]string { 124 | realTarget := target 125 | 126 | ssrf := database.GetDefaultBurpCollab() 127 | if ssrf != "" { 128 | target["oob"] = ssrf 129 | } else { 130 | target["oob"] = database.GetCollab() 131 | } 132 | 133 | // more options 134 | realTarget["rootPath"] = options.RootFolder 135 | realTarget["resourcePath"] = options.ResourcesFolder 136 | realTarget["proxy"] = options.Proxy 137 | realTarget["output"] = options.Output 138 | 139 | // default params in signature 140 | signParams := sign.Params 141 | if len(signParams) > 0 { 142 | for _, param := range signParams { 143 | for k, v := range param { 144 | // variable as a script 145 | if strings.Contains(v, "(") && strings.Contains(v, ")") { 146 | newValue := RunVariables(v) 147 | if len(newValue) > 0 { 148 | realTarget[k] = newValue[0] 149 | } 150 | } else { 151 | realTarget[k] = v 152 | } 153 | } 154 | } 155 | } 156 | 157 | // more params 158 | if len(options.Params) > 0 { 159 | params := ParseParams(options.Params) 160 | if len(params) > 0 { 161 | for k, v := range params { 162 | realTarget[k] = v 163 | } 164 | } 165 | } 166 | return realTarget 167 | } 168 | 169 | // ParseParams parse more params from cli 170 | func ParseParams(rawParams []string) map[string]string { 171 | params := make(map[string]string) 172 | 173 | for _, item := range rawParams { 174 | if strings.Contains(item, "=") { 175 | data := strings.Split(item, "=") 176 | params[data[0]] = strings.Replace(item, data[0]+"=", "", -1) 177 | } 178 | } 179 | return params 180 | } 181 | 182 | // ParseOrigin parse origin request 183 | func ParseOrigin(req libs.Request, sign libs.Signature, options libs.Options) libs.Request { 184 | target := sign.Target 185 | // resolve some parts with global variables first 186 | req.Target = target 187 | req.URL = ResolveVariable(req.URL, target) 188 | // @NOTE: backward compatible 189 | if req.URL == "" && req.Path != "" { 190 | req.URL = ResolveVariable(req.Path, target) 191 | } 192 | req.Body = ResolveVariable(req.Body, target) 193 | req.Headers = ResolveHeader(req.Headers, target) 194 | req.Middlewares = ResolveDetection(req.Middlewares, target) 195 | req.Conclusions = ResolveDetection(req.Conclusions, target) 196 | 197 | // parse raw request 198 | if req.Raw != "" { 199 | rawReq := ResolveVariable(req.Raw, target) 200 | burpReq := ParseBurpRequest(rawReq) 201 | burpReq.Detections = ResolveDetection(req.Detections, target) 202 | burpReq.Middlewares = ResolveDetection(req.Middlewares, target) 203 | burpReq.Conclusions = ResolveDetection(req.Conclusions, target) 204 | return burpReq 205 | } 206 | return req 207 | } 208 | 209 | // ParseRequest parse request part in YAML signature file 210 | func ParseRequest(req libs.Request, sign libs.Signature, options libs.Options) []libs.Request { 211 | var Reqs []libs.Request 212 | target := sign.Target 213 | 214 | // resolve some parts with global variables first 215 | req.Target = target 216 | req.URL = ResolveVariable(req.URL, target) 217 | // @NOTE: backward compatible 218 | if req.URL == "" && req.Path != "" { 219 | req.URL = ResolveVariable(req.Path, target) 220 | } 221 | req.Body = ResolveVariable(req.Body, target) 222 | req.Headers = ResolveHeader(req.Headers, target) 223 | req.Middlewares = ResolveDetection(req.Middlewares, target) 224 | req.Conditions = ResolveDetection(req.Conditions, target) 225 | 226 | if sign.Type != "fuzz" { 227 | // in case we only want to run a middleware alone 228 | if req.Raw != "" { 229 | rawReq := ResolveVariable(req.Raw, target) 230 | burpReq := ParseBurpRequest(rawReq) 231 | burpReq.Detections = ResolveDetection(req.Detections, target) 232 | burpReq.Middlewares = ResolveDetection(req.Middlewares, target) 233 | Reqs = append(Reqs, burpReq) 234 | } 235 | 236 | // if req.path is blank 237 | if req.URL == "" && funk.IsEmpty(req.Middlewares) { 238 | return Reqs 239 | } else if !funk.IsEmpty(req.Middlewares) { 240 | Req := req 241 | Req.Middlewares = ResolveDetection(req.Middlewares, target) 242 | Reqs = append(Reqs, Req) 243 | return Reqs 244 | } 245 | req.Detections = ResolveDetection(req.Detections, target) 246 | // normal requests here 247 | Req := req 248 | Req.Redirect = req.Redirect 249 | if Req.URL != "" { 250 | Reqs = append(Reqs, Req) 251 | } 252 | return Reqs 253 | } 254 | 255 | // start parse fuzz req 256 | // only take URL as a input from cli 257 | var record libs.Record 258 | 259 | // parse raw request in case we have -r options as a origin request 260 | if req.Raw != "" { 261 | rawReq := ResolveVariable(req.Raw, target) 262 | burpReq := ParseBurpRequest(rawReq) 263 | // resolve again with custom delimiter generator 264 | burpReq.Generators = req.Generators 265 | burpReq.Detections = req.Detections 266 | burpReq.Middlewares = req.Middlewares 267 | record.OriginReq = burpReq 268 | } else { 269 | record.OriginReq.URL = target["URL"] 270 | } 271 | 272 | record.Request = req 273 | reqs := ParseFuzzRequest(record, sign) 274 | if len(reqs) > 0 { 275 | Reqs = append(Reqs, reqs...) 276 | } 277 | utils.DebugF("[New Parsed Reuqest] %v", len(Reqs)) 278 | 279 | // repeat section 280 | if req.Repeat == 0 { 281 | return Reqs 282 | } 283 | realReqsWithRepeat := Reqs 284 | for i := 0; i < req.Repeat-1; i++ { 285 | realReqsWithRepeat = append(realReqsWithRepeat, Reqs...) 286 | } 287 | return realReqsWithRepeat 288 | } 289 | 290 | // ParseFuzzRequest parse request recive in API server 291 | func ParseFuzzRequest(record libs.Record, sign libs.Signature) []libs.Request { 292 | req := record.Request 293 | 294 | var Reqs []libs.Request 295 | // color.Green("-- Start do Injecting") 296 | if req.URL == "" { 297 | req.URL = record.OriginReq.URL 298 | } 299 | Reqs = Generators(req, sign) 300 | return Reqs 301 | } 302 | 303 | // ParsePayloads parse payload to replace 304 | func ParsePayloads(sign libs.Signature) []string { 305 | rawPayloads := []string{} 306 | rawPayloads = append(rawPayloads, sign.Payloads...) 307 | // strip out blank line 308 | for index, value := range rawPayloads { 309 | if strings.Trim(value, " ") == "" { 310 | rawPayloads = append(rawPayloads[:index], rawPayloads[index+1:]...) 311 | } 312 | } 313 | 314 | var realPayloads []string 315 | // check if use variables or not 316 | if len(sign.Variables) > 0 { 317 | realVariables := ParseVariable(sign) 318 | // replace payload with variables 319 | if len(realVariables) > 0 { 320 | for _, variable := range realVariables { 321 | for _, payload := range rawPayloads { 322 | target := make(map[string]string) 323 | // replace here 324 | for k, v := range variable { 325 | target[k] = v 326 | } 327 | payload := ResolveVariable(payload, target) 328 | realPayloads = append(realPayloads, payload) 329 | } 330 | } 331 | } 332 | } else { 333 | // just append the payload 334 | for _, payload := range rawPayloads { 335 | realPayloads = append(realPayloads, ResolveVariable(payload, sign.Target)) 336 | } 337 | } 338 | return realPayloads 339 | } 340 | 341 | // ParseBurpRequest parse burp style request 342 | func ParseBurpRequest(raw string) (req libs.Request) { 343 | var realReq libs.Request 344 | realReq.Raw = raw 345 | reader := bufio.NewReader(strings.NewReader(raw)) 346 | parsedReq, err := http.ReadRequest(reader) 347 | if err != nil { 348 | return realReq 349 | } 350 | realReq.Method = parsedReq.Method 351 | // URL part 352 | if parsedReq.URL.Host == "" { 353 | realReq.Host = parsedReq.Host 354 | parsedReq.URL.Host = parsedReq.Host 355 | } 356 | if parsedReq.URL.Scheme == "" { 357 | if parsedReq.Referer() == "" { 358 | realReq.Scheme = "https" 359 | parsedReq.URL.Scheme = "https" 360 | } else { 361 | u, err := url.Parse(parsedReq.Referer()) 362 | if err == nil { 363 | realReq.Scheme = u.Scheme 364 | parsedReq.URL.Scheme = u.Scheme 365 | } 366 | } 367 | } 368 | // fmt.Println(parsedReq.URL) 369 | // parsedReq.URL.RequestURI = parsedReq.RequestURI 370 | realReq.URL = parsedReq.URL.String() 371 | realReq.Path = parsedReq.RequestURI 372 | realReq.Headers = ParseHeaders(parsedReq.Header) 373 | 374 | body, _ := ioutil.ReadAll(parsedReq.Body) 375 | realReq.Body = string(body) 376 | 377 | return realReq 378 | } 379 | 380 | // ParseHeaders parse header for sending method 381 | func ParseHeaders(rawHeaders map[string][]string) []map[string]string { 382 | var headers []map[string]string 383 | for name, value := range rawHeaders { 384 | header := map[string]string{ 385 | name: strings.Join(value[:], ""), 386 | } 387 | headers = append(headers, header) 388 | } 389 | return headers 390 | } 391 | 392 | // ParseBurpResponse parse burp style response 393 | func ParseBurpResponse(rawReq string, rawRes string) (res libs.Response) { 394 | // var res libs.Response 395 | readerr := bufio.NewReader(strings.NewReader(rawReq)) 396 | parsedReq, _ := http.ReadRequest(readerr) 397 | 398 | reader := bufio.NewReader(strings.NewReader(rawRes)) 399 | parsedRes, err := http.ReadResponse(reader, parsedReq) 400 | if err != nil { 401 | return res 402 | } 403 | 404 | res.Status = fmt.Sprintf("%v %v", parsedRes.Status, parsedRes.Proto) 405 | res.StatusCode = parsedRes.StatusCode 406 | 407 | var headers []map[string]string 408 | for name, value := range parsedReq.Header { 409 | header := map[string]string{ 410 | name: strings.Join(value[:], ""), 411 | } 412 | headers = append(headers, header) 413 | } 414 | res.Headers = headers 415 | 416 | body, _ := ioutil.ReadAll(parsedRes.Body) 417 | res.Body = string(body) 418 | 419 | return res 420 | } 421 | 422 | // ParseRequestFromServer parse request receive from API server 423 | func ParseRequestFromServer(record *libs.Record, req libs.Request, sign libs.Signature) { 424 | if req.Raw != "" { 425 | parsedReq := ParseBurpRequest(req.Raw) 426 | // check if parse request ok 427 | if parsedReq.Method != "ParseRequest" { 428 | record.Request = parsedReq 429 | } else { 430 | record.Request = record.OriginReq 431 | } 432 | } else { 433 | record.Request = record.OriginReq 434 | } 435 | 436 | // get some component from sign 437 | if req.Method != "" { 438 | record.Request.Method = req.Method 439 | } 440 | if req.Path != "" { 441 | record.Request.Path = req.Path 442 | } else { 443 | record.Request.Path = record.Request.URL 444 | } 445 | if req.Body != "" { 446 | record.Request.Body = req.Body 447 | } 448 | 449 | // header stuff 450 | if len(req.Headers) > 0 { 451 | realHeaders := req.Headers 452 | keys := []string{} 453 | for _, realHeader := range req.Headers { 454 | for key := range realHeader { 455 | keys = append(keys, key) 456 | } 457 | } 458 | for _, rawHeader := range record.Request.Headers { 459 | for key := range rawHeader { 460 | // only add header we didn't define 461 | if !funk.Contains(keys, key) { 462 | realHeaders = append(realHeaders, rawHeader) 463 | } 464 | } 465 | } 466 | record.Request.Headers = realHeaders 467 | } 468 | record.Request.Generators = req.Generators 469 | record.Request.Encoding = req.Encoding 470 | record.Request.Middlewares = req.Middlewares 471 | record.Request.Redirect = req.Redirect 472 | 473 | // resolve template 474 | target := ParseTarget(record.Request.URL) 475 | record.Request.URL = ResolveVariable(record.Request.Path, target) 476 | record.Request.Body = ResolveVariable(record.Request.Body, target) 477 | record.Request.Headers = ResolveHeader(record.Request.Headers, target) 478 | record.Request.Detections = ResolveDetection(req.Detections, target) 479 | } 480 | -------------------------------------------------------------------------------- /core/generator.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "path/filepath" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/Jeffail/gabs/v2" 11 | //"github.com/davecgh/go-spew/spew" 12 | "github.com/jaeles-project/jaeles/libs" 13 | "github.com/jaeles-project/jaeles/utils" 14 | "github.com/robertkrimen/otto" 15 | "github.com/thoas/go-funk" 16 | ) 17 | 18 | // Generators run multiple generator 19 | func Generators(req libs.Request, sign libs.Signature) []libs.Request { 20 | var reqs []libs.Request 21 | realPayloads := funk.UniqString(ParsePayloads(sign)) 22 | for _, payload := range realPayloads { 23 | fuzzReq := req 24 | // prepare something so we can access variable in generator string too 25 | fuzzReq.Target["payload"] = payload 26 | // set original to blank first 27 | fuzzReq.Target["original"] = "" 28 | fuzzReq.Detections = ResolveDetection(fuzzReq.Detections, fuzzReq.Target) 29 | fuzzReq.Generators = funk.UniqString(ResolveDetection(fuzzReq.Generators, fuzzReq.Target)) 30 | 31 | for _, genString := range fuzzReq.Generators { 32 | // just copy exactly request again 33 | if genString == "Null()" { 34 | reqs = append(reqs, fuzzReq) 35 | continue 36 | } 37 | if fuzzReq.Method == "" { 38 | fuzzReq.Method = "GET" 39 | } 40 | 41 | utils.DebugF("[Generator] %v", genString) 42 | injectedReqs := RunGenerator(fuzzReq, genString) 43 | if len(injectedReqs) <= 0 { 44 | utils.DebugF("No request generated by: %v", genString) 45 | continue 46 | } 47 | 48 | for _, injectedReq := range injectedReqs { 49 | // resolve detection this time because we may need parse something in the variable and original 50 | injectedReq.Detections = AltResolveDetection(fuzzReq.Detections, injectedReq.Target) 51 | injectedReq.Conclusions = AltResolveDetection(fuzzReq.Conclusions, injectedReq.Target) 52 | reqs = append(reqs, injectedReq) 53 | } 54 | } 55 | } 56 | 57 | return reqs 58 | } 59 | 60 | // RunGenerator is main function for generator 61 | func RunGenerator(req libs.Request, genString string) []libs.Request { 62 | var reqs []libs.Request 63 | vm := otto.New() 64 | 65 | vm.Set("Query", func(call otto.FunctionCall) otto.Value { 66 | var injectedReq []libs.Request 67 | if len(reqs) > 0 { 68 | for _, req := range reqs { 69 | injectedReq = Query(req, call.ArgumentList) 70 | } 71 | } else { 72 | injectedReq = Query(req, call.ArgumentList) 73 | } 74 | 75 | if len(injectedReq) > 0 { 76 | reqs = append(reqs, injectedReq...) 77 | } 78 | return otto.Value{} 79 | }) 80 | 81 | vm.Set("Body", func(call otto.FunctionCall) otto.Value { 82 | var injectedReq []libs.Request 83 | if len(reqs) > 0 { 84 | for _, req := range reqs { 85 | injectedReq = Body(req, call.ArgumentList) 86 | } 87 | } else { 88 | injectedReq = Body(req, call.ArgumentList) 89 | } 90 | 91 | if len(injectedReq) > 0 { 92 | reqs = append(reqs, injectedReq...) 93 | } 94 | return otto.Value{} 95 | }) 96 | 97 | vm.Set("Path", func(call otto.FunctionCall) otto.Value { 98 | var injectedReq []libs.Request 99 | if len(reqs) > 0 { 100 | for _, req := range reqs { 101 | injectedReq = Path(req, call.ArgumentList) 102 | } 103 | } else { 104 | injectedReq = Path(req, call.ArgumentList) 105 | } 106 | 107 | if len(injectedReq) > 0 { 108 | reqs = append(reqs, injectedReq...) 109 | } 110 | return otto.Value{} 111 | }) 112 | 113 | vm.Set("Header", func(call otto.FunctionCall) otto.Value { 114 | var injectedReq []libs.Request 115 | if len(reqs) > 0 { 116 | for _, req := range reqs { 117 | injectedReq = Header(req, call.ArgumentList) 118 | } 119 | } else { 120 | injectedReq = Header(req, call.ArgumentList) 121 | } 122 | 123 | if len(injectedReq) > 0 { 124 | reqs = append(reqs, injectedReq...) 125 | } 126 | return otto.Value{} 127 | }) 128 | 129 | vm.Set("Cookie", func(call otto.FunctionCall) otto.Value { 130 | var injectedReq []libs.Request 131 | if len(reqs) > 0 { 132 | for _, req := range reqs { 133 | injectedReq = Cookie(req, call.ArgumentList) 134 | reqs = append(reqs, injectedReq...) 135 | } 136 | } else { 137 | injectedReq = Cookie(req, call.ArgumentList) 138 | reqs = append(reqs, injectedReq...) 139 | } 140 | if len(injectedReq) > 0 { 141 | reqs = append(reqs, injectedReq...) 142 | } 143 | return otto.Value{} 144 | }) 145 | 146 | vm.Set("Method", func(call otto.FunctionCall) otto.Value { 147 | if len(reqs) > 0 { 148 | for _, req := range reqs { 149 | injectedReq := Method(req, call.ArgumentList) 150 | reqs = append(reqs, injectedReq...) 151 | } 152 | } else { 153 | injectedReq := Method(req, call.ArgumentList) 154 | reqs = append(reqs, injectedReq...) 155 | } 156 | return otto.Value{} 157 | }) 158 | 159 | vm.Run(genString) 160 | return reqs 161 | } 162 | 163 | // Encoder encoding part after resolve 164 | func Encoder(encodeString string, data string) string { 165 | if encodeString == "" { 166 | return data 167 | } 168 | var result string 169 | vm := otto.New() 170 | 171 | // Encode part 172 | vm.Set("URL", func(call otto.FunctionCall) otto.Value { 173 | result = url.QueryEscape(data) 174 | return otto.Value{} 175 | }) 176 | 177 | vm.Run(encodeString) 178 | return result 179 | } 180 | 181 | // Method gen request with multiple method 182 | func Method(req libs.Request, arguments []otto.Value) []libs.Request { 183 | methods := []string{"GET", "POST", "PUT", "HEAD", "PATCH"} 184 | if len(arguments) > 0 { 185 | methods = []string{strings.ToUpper(arguments[0].String())} 186 | } 187 | var reqs []libs.Request 188 | for _, method := range methods { 189 | injectedReq := req 190 | injectedReq.Method = method 191 | injectedReq.Target["original"] = req.Method 192 | reqs = append(reqs, injectedReq) 193 | } 194 | 195 | return reqs 196 | } 197 | 198 | // Query gen request with query string 199 | func Query(req libs.Request, arguments []otto.Value) []libs.Request { 200 | injectedString := arguments[0].String() 201 | paramName := "undefined" 202 | if len(arguments) > 1 { 203 | paramName = arguments[1].String() 204 | } 205 | 206 | var reqs []libs.Request 207 | rawURL := req.URL 208 | target := req.Target 209 | u, _ := url.Parse(rawURL) 210 | 211 | // replace one or create a new one if they're not exist 212 | if paramName != "undefined" { 213 | injectedReq := req 214 | uu, _ := url.Parse(injectedReq.URL) 215 | target["original"] = uu.Query().Get(paramName) 216 | // only replace value for now 217 | newValue := AltResolveVariable(injectedString, target) 218 | query := uu.Query() 219 | query.Set(paramName, newValue) 220 | uu.RawQuery = query.Encode() 221 | 222 | injectedReq.URL = uu.String() 223 | injectedReq.Target = target 224 | reqs = append(reqs, injectedReq) 225 | return reqs 226 | } 227 | 228 | for key, value := range u.Query() { 229 | injectedReq := req 230 | uu, _ := url.Parse(injectedReq.URL) 231 | if len(value) == 1 { 232 | target["original"] = strings.Join(value[:], "") 233 | } 234 | // only replace value for now 235 | newValue := AltResolveVariable(injectedString, target) 236 | 237 | query := uu.Query() 238 | query.Set(key, newValue) 239 | uu.RawQuery = query.Encode() 240 | 241 | injectedReq.URL = uu.String() 242 | injectedReq.Target = target 243 | reqs = append(reqs, injectedReq) 244 | } 245 | // return rawURL 246 | return reqs 247 | } 248 | 249 | // Body gen request with body 250 | func Body(req libs.Request, arguments []otto.Value) []libs.Request { 251 | injectedString := arguments[0].String() 252 | paramName := "undefined" 253 | if len(arguments) > 1 { 254 | paramName = arguments[1].String() 255 | } 256 | 257 | var reqs []libs.Request 258 | target := req.Target 259 | 260 | rawBody := req.Body 261 | // @TODO: deal with XML body later 262 | // @TODO: deal with multipart form later 263 | if paramName == "undefined" { 264 | // var newBody []string 265 | if rawBody != "" { 266 | // @TODO: inject for all child node, only 3 depth for now 267 | if utils.IsJSON(rawBody) { 268 | jsonParsed, _ := gabs.ParseJSON([]byte(rawBody)) 269 | for key, child := range jsonParsed.ChildrenMap() { 270 | injectedReq := req 271 | if len(child.Children()) == 0 { 272 | str := fmt.Sprint(child) 273 | target["original"] = str 274 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 275 | jsonBody, _ := gabs.ParseJSON([]byte(rawBody)) 276 | jsonBody.Set(newValue, key) 277 | injectedReq.Body = jsonBody.String() 278 | injectedReq.Target = target 279 | reqs = append(reqs, injectedReq) 280 | 281 | } else { 282 | // depth 2 283 | for _, ch := range child.Children() { 284 | if len(ch.Children()) == 0 { 285 | str := fmt.Sprint(child) 286 | target["original"] = str 287 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 288 | jsonBody, _ := gabs.ParseJSON([]byte(rawBody)) 289 | jsonBody.Set(newValue, key) 290 | injectedReq.Body = jsonBody.String() 291 | injectedReq.Target = target 292 | reqs = append(reqs, injectedReq) 293 | } else { 294 | // depth 3 295 | for _, ch := range child.Children() { 296 | if len(ch.Children()) == 0 { 297 | str := fmt.Sprint(child) 298 | target["original"] = str 299 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 300 | jsonBody, _ := gabs.ParseJSON([]byte(rawBody)) 301 | jsonBody.Set(newValue, key) 302 | injectedReq.Body = jsonBody.String() 303 | injectedReq.Target = target 304 | reqs = append(reqs, injectedReq) 305 | } 306 | } 307 | } 308 | } 309 | } 310 | // dd, ok := nn[1].Data().(int) 311 | } 312 | 313 | } else { 314 | // normal form body 315 | params := strings.SplitN(rawBody, "&", -1) 316 | for index, param := range params { 317 | newParams := strings.SplitN(rawBody, "&", -1) 318 | injectedReq := req 319 | k := strings.SplitN(param, "=", -1) 320 | if len(k) > 1 { 321 | target["original"] = k[1] 322 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 323 | newParams[index] = fmt.Sprintf("%v=%v", k[0], newValue) 324 | injectedReq.Body = strings.Join(newParams[:], "&") 325 | injectedReq.Target = target 326 | reqs = append(reqs, injectedReq) 327 | } else if len(k) == 1 { 328 | target["original"] = k[0] 329 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 330 | newParams[index] = fmt.Sprintf("%v=%v", k[0], newValue) 331 | injectedReq.Body = strings.Join(newParams[:], "&") 332 | injectedReq.Target = target 333 | reqs = append(reqs, injectedReq) 334 | } 335 | } 336 | 337 | } 338 | 339 | } 340 | } 341 | // return rawURL 342 | return reqs 343 | } 344 | 345 | // Path gen request with path 346 | func Path(req libs.Request, arguments []otto.Value) []libs.Request { 347 | injectedString := arguments[0].String() 348 | paramName := "last" 349 | if len(arguments) > 1 { 350 | paramName = arguments[1].String() 351 | } 352 | 353 | var reqs []libs.Request 354 | target := req.Target 355 | 356 | u, _ := url.Parse(req.URL) 357 | rawPath := u.Path 358 | rawQuery := u.RawQuery 359 | Paths := strings.Split(rawPath, "/") 360 | ext := filepath.Ext(Paths[len(Paths)-1]) 361 | 362 | // only replace extension file 363 | if paramName == "ext" && ext != "" { 364 | injectedReq := req 365 | target["original"] = filepath.Ext(Paths[len(Paths)-1]) 366 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 367 | newPaths := Paths 368 | newPaths[len(newPaths)-1] = strings.Replace(Paths[len(Paths)-1], target["original"], newValue, -1) 369 | injectedReq.URL = target["BaseURL"] + strings.Join(newPaths[:], "/") 370 | injectedReq.Target = target 371 | reqs = append(reqs, injectedReq) 372 | // only replace the last path 373 | } else if paramName == "last" || (paramName == "-1" && ext == "") { 374 | injectedReq := req 375 | target["original"] = Paths[len(Paths)-1] 376 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 377 | 378 | newPaths := Paths 379 | // if the path have query before append with it 380 | newPaths[len(newPaths)-1] = newValue 381 | if rawQuery != "" { 382 | injectedReq.URL = target["BaseURL"] + strings.Join(newPaths[:], "/") 383 | if strings.Contains(injectedReq.URL, "?") { 384 | injectedReq.URL = target["BaseURL"] + strings.Join(newPaths[:], "/") + "&" + rawQuery 385 | } else { 386 | injectedReq.URL = target["BaseURL"] + strings.Join(newPaths[:], "/") + "?" + rawQuery 387 | } 388 | 389 | // newPaths[len(newPaths)-1] = newValue + "&" + rawQuery 390 | } else { 391 | injectedReq.URL = target["BaseURL"] + strings.Join(newPaths[:], "/") 392 | } 393 | injectedReq.Target = target 394 | reqs = append(reqs, injectedReq) 395 | // specific position 396 | } else if paramName != "*" && len(paramName) == 1 { 397 | position, err := strconv.ParseInt(paramName, 10, 64) 398 | // select reverse 399 | if strings.HasPrefix(paramName, "-") { 400 | v := int(position) * -1 401 | if len(Paths) > v { 402 | position = int64(len(Paths) - v) 403 | } 404 | } 405 | 406 | if err == nil { 407 | 408 | injectedReq := req 409 | target["original"] = Paths[position] 410 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 411 | 412 | newPaths := Paths 413 | newPaths[position] = newValue 414 | 415 | injectedReq.URL = target["BaseURL"] + strings.Join(newPaths[:], "/") 416 | injectedReq.Target = target 417 | reqs = append(reqs, injectedReq) 418 | } 419 | } else if paramName == "*" || strings.Contains(paramName, ",") { 420 | // select path 421 | var injectPositions []int 422 | if strings.Contains(paramName, ",") { 423 | positions := strings.Split(paramName, ",") 424 | for _, pos := range positions { 425 | index, err := strconv.Atoi(strings.TrimSpace(pos)) 426 | if err == nil { 427 | injectPositions = append(injectPositions, index) 428 | } 429 | } 430 | } else { 431 | // all paths 432 | for index := range Paths { 433 | injectPositions = append(injectPositions, index) 434 | } 435 | } 436 | 437 | for _, injectPos := range injectPositions { 438 | Paths := strings.Split(rawPath, "/") 439 | 440 | injectedReq := req 441 | target["original"] = Paths[injectPos] 442 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 443 | 444 | newPaths := Paths 445 | newPaths[injectPos] = newValue 446 | reallyNewPaths := strings.Join(newPaths[:], "/") 447 | //// in case we miss the first / 448 | //if !strings.HasPrefix(reallyNewPaths, "/") { 449 | // reallyNewPaths = "/" + reallyNewPaths 450 | //} 451 | injectedReq.URL = target["BaseURL"] + reallyNewPaths 452 | injectedReq.Target = target 453 | reqs = append(reqs, injectedReq) 454 | } 455 | 456 | } 457 | return reqs 458 | } 459 | 460 | // Cookie gen request with Cookie 461 | func Cookie(req libs.Request, arguments []otto.Value) []libs.Request { 462 | var reqs []libs.Request 463 | injectedString := arguments[0].String() 464 | cookieName := "undefined" 465 | if len(arguments) > 1 { 466 | cookieName = arguments[1].String() 467 | } 468 | 469 | target := req.Target 470 | 471 | var haveCookie bool 472 | var cookieExist bool 473 | var originalCookies string 474 | originCookies := make(map[string]string) 475 | // check if request have cookie or not 476 | for _, header := range req.Headers { 477 | haveCookie = funk.Contains(header, "Cookie") 478 | if haveCookie == true { 479 | // got a cookie 480 | for _, v := range header { 481 | originalCookies = v 482 | rawCookies := strings.Split(v, ";") 483 | for _, rawCookie := range rawCookies { 484 | 485 | name := strings.Split(strings.TrimSpace(rawCookie), "=")[0] 486 | // just in case some weird part after '=' 487 | value := strings.Join(strings.Split(strings.TrimSpace(rawCookie), "=")[1:], "") 488 | originCookies[name] = value 489 | } 490 | } 491 | break 492 | } else { 493 | haveCookie = false 494 | } 495 | 496 | } 497 | if haveCookie == true && funk.Contains(originCookies, cookieName) { 498 | cookieExist = true 499 | } 500 | 501 | // start gen request 502 | if haveCookie == true { 503 | // replace entire old cookie if you don't define cookie name 504 | if cookieName == "undefined" { 505 | newHeaders := req.Headers 506 | target["original"] = originalCookies 507 | newCookie := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 508 | 509 | for _, header := range req.Headers { 510 | for k := range header { 511 | if k == "Cookie" { 512 | head := map[string]string{ 513 | "Cookie": newCookie, 514 | } 515 | newHeaders = append(newHeaders, head) 516 | } else { 517 | newHeaders = append(newHeaders, header) 518 | } 519 | 520 | } 521 | } 522 | injectedReq := req 523 | injectedReq.Headers = newHeaders 524 | injectedReq.Target = target 525 | reqs = append(reqs, injectedReq) 526 | return reqs 527 | } 528 | 529 | var newHeaders []map[string]string 530 | // replace old header 531 | for _, header := range req.Headers { 532 | for k := range header { 533 | // do things with Cookie header 534 | if k == "Cookie" { 535 | if cookieExist == true { 536 | target["original"] = originCookies[cookieName] 537 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 538 | originCookies[cookieName] = newValue 539 | 540 | } else { 541 | target["original"] = "" 542 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 543 | originCookies[cookieName] = newValue 544 | } 545 | 546 | // join it again to append to the rest of header 547 | var realCookies string 548 | for name, value := range originCookies { 549 | realCookies += fmt.Sprintf("%v=%v; ", name, value) 550 | } 551 | newHead := map[string]string{ 552 | "Cookie": realCookies, 553 | } 554 | 555 | // replace cookie 556 | newHeaders = append(newHeaders, newHead) 557 | } else { 558 | newHeaders = append(newHeaders, header) 559 | } 560 | } 561 | } 562 | injectedReq := req 563 | injectedReq.Headers = newHeaders 564 | injectedReq.Target = target 565 | reqs = append(reqs, injectedReq) 566 | 567 | } else { 568 | target["original"] = "" 569 | var realCookies string 570 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 571 | if cookieName == "undefined" { 572 | realCookies = fmt.Sprintf("%v; ", newValue) 573 | 574 | } else { 575 | realCookies = fmt.Sprintf("%v=%v; ", cookieName, newValue) 576 | } 577 | head := map[string]string{ 578 | "Cookie": realCookies, 579 | } 580 | injectedReq := req 581 | newHeaders := req.Headers 582 | newHeaders = append(newHeaders, head) 583 | injectedReq.Headers = newHeaders 584 | injectedReq.Target = target 585 | reqs = append(reqs, injectedReq) 586 | } 587 | 588 | return reqs 589 | } 590 | 591 | // Header gen request with header 592 | func Header(req libs.Request, arguments []otto.Value) []libs.Request { 593 | var reqs []libs.Request 594 | injectedString := arguments[0].String() 595 | headerName := arguments[1].String() 596 | 597 | target := req.Target 598 | 599 | injectedReq := req 600 | var isExistHeader bool 601 | // check if inject header is new or not 602 | for _, header := range req.Headers { 603 | isExistHeader = funk.Contains(header, headerName) 604 | if isExistHeader == true { 605 | break 606 | } else { 607 | isExistHeader = false 608 | } 609 | } 610 | if isExistHeader == false { 611 | newHeaders := req.Headers 612 | target["original"] = "" 613 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 614 | head := map[string]string{ 615 | headerName: newValue, 616 | } 617 | newHeaders = append(newHeaders, head) 618 | injectedReq.Headers = newHeaders 619 | injectedReq.Target = target 620 | reqs = append(reqs, injectedReq) 621 | } else { 622 | var newHeaders []map[string]string 623 | // replace old header 624 | for _, header := range req.Headers { 625 | for k, v := range header { 626 | if k == headerName { 627 | target["original"] = v 628 | newValue := Encoder(req.Encoding, AltResolveVariable(injectedString, target)) 629 | newHead := map[string]string{ 630 | headerName: newValue, 631 | } 632 | newHeaders = append(newHeaders, newHead) 633 | } else { 634 | newHeaders = append(newHeaders, header) 635 | } 636 | } 637 | } 638 | injectedReq.Target = target 639 | injectedReq.Headers = newHeaders 640 | reqs = append(reqs, injectedReq) 641 | } 642 | 643 | return reqs 644 | } 645 | --------------------------------------------------------------------------------