diff --git a/.github/workflows/CenFuzz.yml b/.github/workflows/CenFuzz.yml new file mode 100644 index 0000000..68e352f --- /dev/null +++ b/.github/workflows/CenFuzz.yml @@ -0,0 +1,22 @@ +--- +name: CenFuzz + +on: + push: + paths: + - '*' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: '1.16' + + - name: Build CenFuzz + run: | + go build -v ./... +... diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml new file mode 100644 index 0000000..a4c7af2 --- /dev/null +++ b/.github/workflows/artifacts.yml @@ -0,0 +1,13 @@ +name: 'nightly-cleanup' +on: + schedule: + - cron: '0 1 * * *' # every night at 1 am UTC + +jobs: + delete-artifacts: + runs-on: ubuntu-latest + steps: + - uses: kolpav/purge-artifacts-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + expire-in: 1days diff --git a/README.md b/README.md index b4abebf..c80e1d6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,131 @@ # CenFuzz -Tool for fuzzing HTTP and HTTPS requests to endpoints, and identify the rules and triggers of censorship devices. +[![Build Status](https://github.com/censoredplanet/CenFuzz/workflows/CenFuzz/badge.svg)](https://github.com/censoredplanet/CenFuzz/actions) + + +*Are you using `CenFuzz`? If so, let us know! Shoot us an email at censoredplanet@umich.edu.* + +`CenFuzz` is a deterministic censorship and middlebox fuzzing tool that performs application-layer fuzzing strategies on blocked connections such that the same strategies are performed across all tested devices. `CenFuzz` performs several modifications to the HTTP GET Request and the TLS Client Hello packets, based on the grammars of these protocols. See beloa and the `http_fuzzer/` and `https_fuzzer` directories for a set of deterministic fuzzers for each protocol. For more information, refer to [our paper](https://ramakrishnansr.org/publications). + +The following HTTP fuzzers are provided (for example domain `example.com`). + +| HTTP Fuzzer ID | HTTP Fuzzer | Fuzzer Operation | Example | Total No. of Permutations | +| -------------- | ---------------------------- | ----------------------------------------- | ---------------------- | ------------------------- | +| 1 | Hostname Padding | Padding `*` characters to hostname | **example.com* | 9 | +| 2 | Get Word Capitalize | Capitalize parts of the GET word | GeT | 8 | +| 3 | Get Word Remove | Remove parts of the GET word | Ge | 7 | +| 4 | Get Word Alternate | Use a different HTTP method | POST | 6 | +| 5 | Http Word Capitalize | Capitalize parts of the http word | HtTP/1.1 | 16 | +| 6 | Http Word Remove | Remove parts of the http word | HTP/1.1 | 167 | +| 7 | HttP Word Alternate | Use alternates in place of http word | XXXX/1.1 | 16 | +| 8 | Host Word Capitalize | Capitalize parts of the Host word | HoST: | 16 | +| 9 | Host Word Remove | Remove parts of the Host word | ost: | 63 | +| 10 | Host Word Alternate | Use alternates in place of Host word | HostHeader: | 7 | +| 11 | Http Delimiter Word Remove | Remove parts of the http delimiters | \r | 3 | +| 12 | Path Alternate | Use alternate path other than `/` | ? | 8 | +| 13 | Header Alternate | Add custom HTTP header to request | Connection: keep-alive | 59 | +| 14 | HostName Alternate | Repeat or modify domains in certain ways | "" | 5 | +| 15 | Hostname TLD Alternate | Provide different TLD to domain | example.net | 10 | +| 16 | Hostname Subdomain Alternate | Provide different subdomain | mail.example.net | 10 | + +The following HTTPS fuzzers are provided (for example domain `example.com`). + +| HTTPS Fuzzer ID | HTTPS Fuzzer | Fuzzer Operation | Example | Total No. of Permutations | +| --------------- | ---------------------------- | ----------------------------------------- | ---------------------- | ------------------------- | +| 1 | SNI Padding | Padding `*` characters to SNI | **example.com* | 9 | +| 2 | Min Version Alternate | Use a different minimum TLS version | TLS 1.1 | 4 | +| 3 | Max Version Alternate | Use a different maximum TLS version | TLS 1.1 | 4 | +| 4 | CipherSuite Alternate | Use a different ciphersuite | TLS_AES_128_GCM_SHA256 | 25 | +| 5 | Client Certificate Alternate | Use a different client certificate | CN=www.test.com | 3 | +| 6 | SNI Alternate | Repeat or modify domains in certain ways | " " | 4 | +| 7 | SNI TLD Alternate | Provide different TLD to domain | example.org | 10 | +| 8 | SNI Subdomain Alternate | Provide different subdomain | wiki.example.net | 10 | + + +## Installation +- Install go version 1.13 or newer see +- Run `go get github.com/censoredplanet/CenFuzz`. +- Run `go get` and `go build` once inside the directory. + +## Configuration +The following flags can be provided for running measurements: + +| Flag | Default | Function | Example | +| ---------------------- | ------------------------ | ------------------------------------------------------ | ------------------------------------------ | +| infile | Required | A csv file with `endpoint, domain` pairs to measure | `examples/input.csv` | +| fuzzer-infile | Required | A csv file with `Fuzzer ID, All permutations? boolean` | `examples/http-fuzz-input.csv` | +| outfile | stdout | File to write output in | `examples/example_direct_http_fuzz.json` | +| uncensored | example.com | Control keyword which is not blocked | | +| num-workers | 1 | Number of workers to run measurements parallely | | +| fuzz-delay | 5 | Number of seconds between unblocked measurements | | +| stateful-delay | 120 | Number of seconds between blocked measurements | | +| protocol | http | HTTP or HTTPS protocol | | +| All | false | If true, run all permutations | | +| iface | "" | Network interface to use to run measurements | | +| srcip | "" | Select source IP address to use (can be used to spoof) | | +| numprobes | 3 | No. of permutations per strategy in case All = false | | + +The following flags can be provided for analyzing measurements: +| Flag | Default | Function | Example | +| ---------------------- | ------------------------ | ------------------------------------------------------ | ----------------------------- | +| analyze-dir | Required | Directory with fuzzing measurement output (*_fuzz.json)| | +| infile | Required | [Blockpage signature patterns](https://assets.censoredplanet.org/blockpage_signatures.json) | | +| routeviews-file | Required | Data from Routeviews to get ASN information | | +| mmdb-file | Required | Maxmind mmdb file | | +| analyze-outfile | stdout | File to write analyzed output in | `examples/analyzed.json` | +| bigquery | false | If true, upload to table in Bigquery | | +| bigquery-project | - | Bigquery project ID | | +| bigquery-dataset | - | Bigquery dataset ID | | +| bigquery-table | - | Bigquery table ID | | + + +## Usage +The `CenFuzz` tool provides two functions: +1. Run fuzzing measurements across a list of endpoints: +``` +./CenFuzz --infile examples/input.csv --fuzzer-infile examples/http-fuzz-input.csv --outfile examples/example_direct_http_fuzz.json --num-workers 2 --fuzz-delay 10 --stateful-delay 120 --protocol http --iface enp1s0f0 +``` +2. Analyze data: + ``` + go run cmd/analyze/analyze.go --analyze-dir examples --routeviews-file routeviews_file --mmdb-file mmdb_file --analyze-outfile examples/analyzed.json --infile blockpage_signatures.json + ``` + +## Disclaimer +Russing `CenFuzz` from your machine may place you at risk if you use it within a highly censoring regime. `CenFuzz` takes actions that try to trigger censoring middleboxes multiple times, and try to interfere with the functioning of the middlebox. Therefore, please exercice caution while using the tool, and understand the risks of running `CenFuzz` before using it on your machine. Please refer to [our paper](https://ramakrishnansr.org/publications) for more information. + +## Data +The fuzzing measurement data from the study in [our paper](https://ramakrishnansr.org/publications) can be found [here](https://drive.google.com/file/d/1begpJRkNfI8Rg378A1S0BQKVYrWFfuSa/view?usp=sharing). + +## Citation +If you use the `CenFuzz` tool or data, please cite the following publication: +``` +@inproceedings{sundararaman2022network,
+title = {Network Measurement Methods for Locating and Examining Censorship Devices},
+author = {Sundara Raman, Ram and Wang, Mona and Dalek, Jakub and Mayer, Jonathan and Ensafi, Roya},
+booktitle={In ACM International Conference on emerging Networking EXperiments and Technologies (CoNEXT)},
+year={2022} +``` + +## Contributing +`CenFuzz` currently implements a small set of fuzzing strategies, and we need the help of the community to improve `CenFuzz` and keep it updated! We welcome any and all contributions. Please feel free to open an Issue, Pull Request, or send us an email. + +To simply add a new fuzzing strategy, the following changes would have to be made: + +1. Create an `Init()` and `Fuzz()` in a new Golang file inside the `http_fuzzer` or `https_fuzzer` directories. These two functions are required as they are interfaces, and the main logic of the strategy should be initiated inside the `Init()` function. See the strategies already implemented for examples. + +2. Make the appropriate changes inside `http_fuzzer/fuzzer.go` or `https_fuzzer/fuzzer.go` if your strategy requires runtime computations, as the requests are computed here. + +3. Add a new incrementing Fuzzer ID for your strategy and create the mapping inside `worker/http_worker.go` or `worker/https_Worker.go`. + +4. Run, Test, and Enjoy! + +## Licensing +This repository is released under the GNU General Public License (see [`LICENSE`](LICENSE)). + +## Contact +Email addresses: `censoredplanet@umich.edu`, `ramaks@umich.edu`, `monaw@princeton.edu`, `jakub@citizenlab.ca`, `jonathan.mayer@princeton.edu`, and `ensafi@umich.edu` + +## Contributors + +[Ram Sundara Raman](https://github.com/ramakrishnansr) + +[Mona Wang](https://github.com/m0namon) diff --git a/bigquery_upload/bigquery_upload.go b/bigquery_upload/bigquery_upload.go new file mode 100644 index 0000000..a667e1e --- /dev/null +++ b/bigquery_upload/bigquery_upload.go @@ -0,0 +1,46 @@ +package bigquery_upload + +import ( + "context" + "fmt" + "os" + + "cloud.google.com/go/bigquery" + "github.com/censoredplanet/CenFuzz/config" +) + +func JSONtoBigquery(filename string) error { + projectID := config.BigqueryProjectID + datasetID := config.BigqueryDatasetID + tableID := config.BigqueryTableID + ctx := context.Background() + client, err := bigquery.NewClient(ctx, projectID) + if err != nil { + return fmt.Errorf("bigquery.NewClient: %v", err) + } + defer client.Close() + + f, err := os.Open(filename) + if err != nil { + return err + } + source := bigquery.NewReaderSource(f) + source.AutoDetect = true + source.SourceFormat = bigquery.JSON + source.AllowQuotedNewlines = true + + loader := client.Dataset(datasetID).Table(tableID).LoaderFrom(source) + + job, err := loader.Run(ctx) + if err != nil { + return err + } + status, err := job.Wait(ctx) + if err != nil { + return err + } + if err := status.Err(); err != nil { + return err + } + return nil +} diff --git a/cmd/analyze/analyze.go b/cmd/analyze/analyze.go new file mode 100644 index 0000000..5f33bd8 --- /dev/null +++ b/cmd/analyze/analyze.go @@ -0,0 +1,357 @@ +package main + +import ( + "bufio" + "crypto/x509" + "encoding/json" + "log" + "math/rand" + "net" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + + "github.com/banviktor/asnlookup/pkg/database" + "github.com/censoredplanet/CenFuzz/bigquery_upload" + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/geolocate" + "github.com/censoredplanet/CenFuzz/https_fuzzer" + "github.com/censoredplanet/CenFuzz/util" +) + +type Output struct { + TestID string `json:"TestID"` + MeasurementType string `json:"MeasurementType"` + Country string `json:"Country"` + Protocol string `json:"Protocol"` + DstIP string `json:"DstIP"` + DstASN uint32 `json:"DstASN"` + Domain string `json:"Domain"` + TestName string `json:"TestName"` + IsNormal bool `json:"IsNormal"` + NormalResponseType string `json:"NormalResponseType"` + NormalCensored bool `json:"NormalCensored"` + MatchesNormal bool `json:"MatchesNormal"` + MatchesUncensored bool `json:"MatchesUncensored"` + NormalDifferences string `json:"NormalDifferences"` + UncensoredDifferences string `json:"UncensoredDifferences"` + Request string `json:"Request"` + RequestStrategy string `json:"RequestStrategy` + Response string `json:"Response"` + Error string `json:"Error"` + CensorResponseType string `json:"CensoredResponseType"` + UncensoredRequest string `json:"UncensoredRequest"` + UncensoredRequestStrategy string `json:"UncensoredRequestStrategy` + UncensoredResponse string `json:"UncensoredResponse"` + UncensoredError string `json:"UncensoredError"` + UncensoredResponseType string `json:"UncensoredResponsetype"` + StartTime string `json:"StartTime"` + EndTime string `json:"EndTime"` +} + +func split(fuzz_file string) (string, string, string) { + return strings.Split(fuzz_file, "_")[0], strings.Split(fuzz_file, "_")[1], strings.Split(fuzz_file, "_")[2] +} + +func readSignaturePatterns(patternfile string) map[string]*regexp.Regexp { + infile := util.OpenFileforRead(patternfile) + scanner := bufio.NewScanner(infile) + buf := make([]byte, 0, 64*1024) + scanner.Buffer(buf, 1024*1024) + signatureData := make(map[string]*regexp.Regexp) + for scanner.Scan() { + var signature map[string]string + line := scanner.Text() + if line == "\n" || line == "" || strings.HasPrefix(line, "//") { + continue + } + d := json.NewDecoder(strings.NewReader(line)) + d.UseNumber() + if err := d.Decode(&signature); err != nil { + log.Println(line) + log.Fatal("Could not unmarshal json: " + err.Error()) + } + pattern := regexp.QuoteMeta(signature["pattern"]) + pattern = strings.ReplaceAll(pattern, "%", ".*") + signatureData[signature["fingerprint"]] = regexp.MustCompile(pattern) + } + return signatureData +} + +func comparePatterns(str string, fingerprintOrder []string, fingerprints map[string]*regexp.Regexp) string { + for _, fingerprint := range fingerprintOrder { + pattern := fingerprints[fingerprint] + if pattern.MatchString(str) { + return fingerprint + } + } + return "" +} + +func responsetype(errString string, response string, fingerprintOrder []string, fingerprints map[string]*regexp.Regexp) string { + stage := "unknown" + method := "unknown" + if errString == "" && response == "" { + return "empty/empty" + } + if errString != "" { + if strings.Contains(errString, "Dial") { + stage = "dial" + } else if strings.Contains(errString, "read") { + stage = "read" + } else if strings.Contains(errString, "write") { + stage = "write" + } + + if strings.Contains(errString, "imeout") { + method = "timeout" + } else if strings.Contains(errString, "reset") { + method = "rst" + } else if strings.Contains(errString, "tls") { + method = "tls" + } + } + if response != "" { + fingerprint := comparePatterns(response, fingerprintOrder, fingerprints) + if fingerprint != "" { + stage = "content" + method = "blockpage:" + fingerprint + } else { + stage = "content" + method = "http" + } + } + + return stage + "/" + method +} + +func initializeASNDB() (database.Database, error) { + builder := database.NewBuilder() + routeviwsFile := util.OpenFileforRead(config.RouteviewsFile) + scanner := bufio.NewScanner(routeviwsFile) + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, "\t") + firstASN := strings.Split(parts[2], "_")[0] + secondASN := strings.Split(firstASN, ",")[0] + asn, err := strconv.Atoi(secondASN) + if err != nil { + log.Fatal(err) + } + _, prefix, _ := net.ParseCIDR(parts[0] + "/" + parts[1]) + err = builder.InsertMapping(prefix, uint32(asn)) + if err != nil { + log.Fatal(err) + } + } + return builder.Build() +} + +func Strategy(testname string, protocol string, domain string, request interface{}) (string, string) { + strategy := "" + if protocol == "http" { + httpRequest := request.(string) + if strings.Contains(testname, "Hostname") { + return strings.Replace(httpRequest, domain, "DOMAIN", -1), strings.Replace(strings.Replace(httpRequest, "GET / HTTP/1.1\r\nHost:", "", -1), "\r\n\r\n", "", -1) + } else if strings.Contains(testname, "Get Word") { + return strings.Replace(httpRequest, domain, "DOMAIN", -1), strings.Replace(strings.Replace(httpRequest, " / HTTP/1.1\r\nHost:"+domain, "", -1), "\r\n\r\n", "", -1) + } else if strings.Contains(testname, "Http Word") { + return strings.Replace(httpRequest, domain, "DOMAIN", -1), strings.Replace(strings.Replace(httpRequest, "GET / ", "", -1), "\r\nHost:"+domain+"\r\n\r\n", "", -1) + } else if strings.Contains(testname, "Host Word") { + return strings.Replace(httpRequest, domain, "DOMAIN", -1), strings.Replace(strings.Replace(httpRequest, "GET / HTTP/1.1\r\n", "", -1), " "+domain+"\r\n\r\n", "", -1) + } else if strings.Contains(testname, "Http Delimiter") { + return strings.Replace(httpRequest, domain, "DOMAIN", -1), strings.Replace(strings.Replace(httpRequest, "GET / HTTP/1.1", "", -1), "Host:"+domain+"\r\n\r\n", "", -1) + } else if strings.Contains(testname, "Path | Alternate") { + return strings.Replace(httpRequest, domain, "DOMAIN", -1), strings.Replace(strings.Replace(httpRequest, "GET ", "", -1), " HTTP/1.1\r\nHost:"+domain+"\r\n\r\n", "", -1) + } else if strings.Contains(testname, "Header | Alternate") { + return strings.Replace(httpRequest, domain, "DOMAIN", -1), strings.Replace(httpRequest, "GET / HTTP/1.1\r\nHost:"+domain+"\r\n", "", -1) + } + } else { + var httpsRequest https_fuzzer.RequestWord + requstByte, err := json.Marshal(request) + if err != nil { + log.Fatal(err) + } + json.Unmarshal(requstByte, &httpsRequest) + if strings.Contains(testname, "SNI") { + return strings.Replace(httpsRequest.Servername, domain, "DOMAIN", -1), httpsRequest.Servername + } else if strings.Contains(testname, "Min Version") { + return "Min version|" + strconv.Itoa(int(httpsRequest.MinVersion)) + "|" + strings.Replace(httpsRequest.Servername, domain, "DOMAIN", -1), strconv.Itoa(int(httpsRequest.MinVersion)) + } else if strings.Contains(testname, "Max Version") { + return "Max version|" + strconv.Itoa(int(httpsRequest.MaxVersion)) + "|" + strings.Replace(httpsRequest.Servername, domain, "DOMAIN", -1), strconv.Itoa(int(httpsRequest.MaxVersion)) + } else if strings.Contains(testname, "CipherSuite Alternate") { + return "CipherSuite Alternate|" + strconv.Itoa(int(httpsRequest.CipherSuites[0])) + "|" + strings.Replace(httpsRequest.Servername, domain, "DOMAIN", -1), strconv.Itoa(int(httpsRequest.CipherSuites[0])) + } else if strings.Contains(testname, "Client Certificate") { + cert := httpsRequest.Certificate[0] + c, _ := x509.ParseCertificate(cert.Certificate[0]) + return "Client Certificate|" + strings.Replace(c.Subject.CommonName, domain, "DOMAIN", -1) + "|" + strings.Replace(httpsRequest.Servername, domain, "DOMAIN", -1), strings.Replace(c.Subject.CommonName, domain, "DOMAIN", -1) + } + } + return "", strategy +} + +func FormatResponse(response interface{}, protocol string) string { + formattedResponse := "" + if response == nil { + return formattedResponse + } + if protocol == "http" { + return response.(string) + } else { + var httpsResponse util.TLSdata + requstByte, err := json.Marshal(response) + if err != nil { + log.Fatal(err) + } + json.Unmarshal(requstByte, &httpsResponse) + if httpsResponse.HandshakeComplete { + return "Handshake complete | " + httpsResponse.HTTPResponse.(string) + } else { + return "Handshake incomplete" + } + } + return formattedResponse +} + +type anyType struct{ f1 string } + +func analyze(fuzz_file string) []*Output { + infile := util.OpenFileforRead(fuzz_file) + scanner := bufio.NewScanner(infile) + buf := make([]byte, 0, 64*1024) + scanner.Buffer(buf, 1024*1024) + var outputs []*Output + db, err := initializeASNDB() + if err != nil { + log.Fatal(err) + } + geolocate.Initialize(config.MaxmindFile) + fingerprints := readSignaturePatterns(config.Infile) + fingerprintOrder := make([]string, len(fingerprints)) + i := 0 + for k := range fingerprints { + fingerprintOrder[i] = k + i++ + } + //Sort to map the blockpage strings in the right order + sort.Strings(fingerprintOrder) + alreadyDone := make(map[string]string) + normalCensored := make(map[string]string) + for scanner.Scan() { + var input *util.Result + line := scanner.Text() + d := json.NewDecoder(strings.NewReader(line)) + d.UseNumber() + if err := d.Decode(&input); err != nil { + log.Println(line) + log.Fatal("Could not unmarshal json: " + err.Error()) + } + + output := &Output{ + DstIP: input.IP, + Domain: input.Domain, + TestName: input.TestName, + IsNormal: input.IsNormal, + MatchesNormal: input.MatchesNormal, + MatchesUncensored: input.MatchesUncensored, + NormalDifferences: input.NormalDifferences, + UncensoredDifferences: input.UncensoredDifferences, + StartTime: input.StartTime.Format("2006-01-02T15:04:05"), + EndTime: input.EndTime.Format("2006-01-02T15:04:05"), + } + filename := strings.Split(fuzz_file, "/") + output.Country, output.MeasurementType, output.Protocol = split(filename[len(filename)-1]) + if output.Country == "blockpages" { + output.MeasurementType = "blockpage" + country, err := geolocate.Geolocate(input.IP) + if err != nil { + log.Printf("Cound not find country: " + err.Error()) + } + output.Country = strings.ToLower(country) + } + if input.Error == nil { + output.Error = "" + } else { + output.Error = input.Error.(string) + } + if input.UncensoredError == nil { + output.UncensoredError = "" + } else { + output.UncensoredError = input.UncensoredError.(string) + } + output.Response = FormatResponse(input.Response, output.Protocol) + output.UncensoredResponse = FormatResponse(input.UncensoredResponse, output.Protocol) + if input.IsNormal == true { + output.NormalResponseType = responsetype(output.Error, output.Response, fingerprintOrder, fingerprints) + if (strings.Contains(output.NormalResponseType, "read") || strings.Contains(output.NormalResponseType, "write") || strings.Contains(output.NormalResponseType, "content")) && (strings.Contains(output.NormalResponseType, "rst") || strings.Contains(output.NormalResponseType, "timeout") || strings.Contains(output.NormalResponseType, "blockpage")) { + normalCensored[output.Domain+"-"+output.Country+"-"+output.DstIP] = output.NormalResponseType + } + } + if normalCensored[output.Domain+"-"+output.Country+"-"+output.DstIP] != "" { + output.NormalCensored = true + } + + output.CensorResponseType = responsetype(output.Error, output.Response, fingerprintOrder, fingerprints) + output.UncensoredResponseType = responsetype(output.UncensoredError, output.Response, fingerprintOrder, fingerprints) + + //Fix matching bug in HTTPS + if output.NormalCensored && output.Protocol == "https" && output.CensorResponseType == normalCensored[output.Domain+"-"+output.Country+"-"+output.DstIP] { + output.MatchesNormal = true + } + + output.TestID = output.Domain + "-" + output.Country + "-" + output.DstIP + "-" + output.TestName + "-" + strconv.FormatBool(output.IsNormal) + "-" + strconv.Itoa(rand.Intn(1000)) + ip := net.ParseIP(output.DstIP) + as, _ := db.Lookup(ip) + output.DstASN = as.Number + output.Request, output.RequestStrategy = Strategy(input.TestName, output.Protocol, input.Domain, input.Request) + output.UncensoredRequest, output.UncensoredRequestStrategy = Strategy(input.TestName, output.Protocol, "www.example.com", input.UncensoredRequest) + if _, ok := alreadyDone[output.DstIP+output.Domain+output.TestName+output.Request]; !ok { + outputs = append(outputs, output) + } + alreadyDone[output.DstIP+output.Domain+output.TestName+output.Request] = "Done" + } + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + return outputs +} + +func main() { + log.Printf("Starting fuzz data analysis, looking into: " + config.Dir) + files, err := filepath.Glob(config.Dir + "/*_fuzz.json") + if err != nil { + log.Fatal("Could not get files") + } + if len(files) == 0 { + log.Fatal("No file found in directory") + } + outfile := util.CreateFile(config.AnalyzeOutfile) + for _, filename := range files { + log.Printf("File: " + filename) + outputs := analyze(filename) + for _, output := range outputs { + data, err := json.Marshal(output) + if err != nil { + log.Println(err.Error()) + continue + } + data = append(data, byte('\n')) + n, err := outfile.Write(data) + if err != nil || n != len(data) { + log.Println(err.Error()) + } + } + } + + log.Printf("Data written into JSON file") + if config.Bigquery { + log.Printf("Uploading JSON to bigquery") + err = bigquery_upload.JSONtoBigquery(config.AnalyzeOutfile) + if err != nil { + log.Fatal("Could not write to bigquery: " + err.Error()) + } + log.Printf("Data uploaded to bigquery") + } +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..f057ed7 --- /dev/null +++ b/config/config.go @@ -0,0 +1,66 @@ +package config + +import ( + "flag" + "time" + + "os" +) + +var StatefulDelay time.Duration +var FuzzDelay time.Duration +var Infile string +var FuzzerInFile string +var Outfile string +var UncensoredKeyword string +var Protocol string +var NumberOfProbesPerTest int +var All bool +var NumWorkers int +var Iface string +var Srcip string +var Dir string +var AnalyzeOutfile string +var RouteviewsFile string +var MaxmindFile string +var Bigquery bool +var BigqueryProjectID string +var BigqueryDatasetID string +var BigqueryTableID string + +func init() { + //Fuzz config + statefulDelay := 100 + fuzzDelay := 5 + flag.StringVar(&Infile, "infile", "", "File in which to read input from (required) - Must have lines in the form (vantage point ip, domain to send)") + flag.StringVar(&FuzzerInFile, "fuzzer-infile", "", "File which specifies the fuzzers to run (required). Each line must have fuzzer ID. Optionally, each line may also include (Comma-separated) a 'True' or 'False' value. A 'True' value indicates running all possible permutations for that fuzzer. A 'False' value indicates running random fuzzing values for that fuzzer.") + flag.StringVar(&Outfile, "outfile", "-", "File in which to write output (default stdout)") + flag.StringVar(&UncensoredKeyword, "uncensored", "example.com", "Uncensored Keyword") + flag.IntVar(&NumWorkers, "num-workers", 1, "Number of parallel workers to run for measurements") + flag.IntVar(&fuzzDelay, "fuzz-delay", 5, "number of seconds to wait between each request per strategy when there is no stateful blocking observed") + flag.IntVar(&statefulDelay, "stateful-delay", 100, "number of seconds to wait between each request when there is some stateful blocking observed") + flag.StringVar(&Protocol, "protocol", "http", "HTTPS or HTTP fuzzing. Default - HTTP") + flag.BoolVar(&All, "all", false, "If true, try all possible permutations for fuzzer values where applicable. If false, choose a set of random fuzzing values for all fuzzers. Default - False") + flag.StringVar(&Iface, "iface", "", "Interface to send measurements on") + flag.StringVar(&Srcip, "srcip", "", "Source IP to send measurements from") + flag.IntVar(&NumberOfProbesPerTest, "numprobes", 3, "Number of random requests to send per test (default 3)") + + //Analyze config + flag.StringVar(&Dir, "analyze-dir", "", "Directory with fuzz files (required)") + flag.StringVar(&RouteviewsFile, "routeviews-file", "-", "Routeviews File (required)") + flag.StringVar(&MaxmindFile, "mmdb-file", "-", "Maxmind MMDB File (required)") + flag.StringVar(&AnalyzeOutfile, "analyze-outfile", "-", "File to write analyzed fuzzing data") + flag.BoolVar(&Bigquery, "bigquery", false, "If true, upload data to table in bigquery") + flag.StringVar(&BigqueryProjectID, "bigquery-project", "-", "Bigquery project ID") + flag.StringVar(&BigqueryDatasetID, "bigquery-dataset", "-", "Bigquery dataset ID") + flag.StringVar(&BigqueryTableID, "bigquery-table", "-", "Bigquery table ID") + flag.Parse() + + // As of now, two input files are both required. + if (len(Infile) == 0 || len(FuzzerInFile) == 0) && len(Dir) == 0 { + flag.PrintDefaults() + os.Exit(1) + } + FuzzDelay = time.Duration(fuzzDelay) * time.Second + StatefulDelay = time.Duration(statefulDelay) * time.Second +} diff --git a/connection/connection.go b/connection/connection.go new file mode 100644 index 0000000..83c6def --- /dev/null +++ b/connection/connection.go @@ -0,0 +1,191 @@ +package connection + +import ( + "fmt" + "io" + "log" + "net" + "time" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" + reuseport "github.com/libp2p/go-reuseport" + utls "github.com/refraction-networking/utls" +) + +type Connection struct { + Host string + Raw *net.TCPConn + Err error +} + +func NewConnection(host string, port uint) *Connection { + conn := &Connection{ + Host: host, + } + + raw, err := Dial(host, port) + if err != nil { + conn.handleError(err) + //log.Println("Error in creating connection: ", err) + return nil + } + conn.Raw = raw + + return conn +} + +// SrcIP returns the IPv4 address for the input interface. +func SrcIP() string { + //See if source IP is specified + if config.Srcip != "" { + return config.Srcip + ":0" + } else if config.Iface == "" { + // Detect interface information + iface, err := net.InterfaceByName(config.Iface) + if err != nil { + log.Println("Connection.SrcIP: Warning: Picking default src IP, Could not open interface:", err.Error()) + } + ipaddress := "0.0.0.0:0" + if err != nil { + log.Println("Connection.SrcIP: Warning: Picking default src IP, Could not open interface:", err.Error()) + } + + // Get addresses for interface + addrs, err := iface.Addrs() + if err != nil { + log.Println("Connection.SrcIP: Warning: Picking default src IP, Could not get interface address", err.Error()) + return ipaddress + } + if len(addrs) == 0 { + log.Println("Connection.SrcIP: Warning: Picking default src IP, Could not find address for interface: ", config.Iface) + return ipaddress + } + + // Return first IPv4 address that works + for ip := range addrs { + if ipaddr := addrs[ip].(*net.IPNet).IP.To4(); ipaddr != nil { + return ipaddr.String() + ":0" + } + } + } + return "0.0.0.0:0" +} + +func Dial(host string, port uint) (*net.TCPConn, error) { + //log.Printf("Connecting to %s ...", host) + var conn net.Conn + var err error + srcIP := SrcIP() + conn, err = reuseport.Dial("tcp", srcIP, + fmt.Sprintf("%s:%d", host, port)) + if conn == nil { + return nil, err + } + connSetLinger := conn.(*net.TCPConn) + if err := connSetLinger.SetLinger(0); err != nil { + return nil, err + } + //log.Printf("Connected to %s ...", host) + return connSetLinger, err +} + +func SendHTTPRequest(conn *Connection, request string) interface{} { + defer conn.Raw.Close() + sent := []byte(request) + if _, err := conn.Raw.Write(sent); err != nil { + err = conn.handleError(err) + return nil + } + + if err := conn.Raw.CloseWrite(); err != nil { + err = conn.handleError(err) + return nil + + } + + maxResponseLength := 1 << 16 + response := make([]byte, maxResponseLength) + + responseLength := 0 + for { + // TODO(adrs): add to config + conn.Raw.SetReadDeadline(time.Now().Add(10 * time.Second)) + n, err := conn.Raw.Read(response[responseLength:maxResponseLength]) + if err == io.EOF { + break + } else if err != nil { + err = conn.handleError(err) + break + } + responseLength += n + } + + return string(response[:responseLength]) +} + +func SendHTTPSRequest(conn *Connection, config utls.Config) *util.TLSdata { + defer conn.Raw.Close() + conn.Raw.SetReadDeadline(time.Now().Add(2 * time.Second)) + tlsConn := utls.UClient(conn.Raw, &config, utls.HelloGolang) + defer tlsConn.Close() + //Use refraction networking's utls instead of using crypto/tls to have more flexibility + err := tlsConn.BuildHandshakeState() + if err != nil { + err = conn.handleError(err) + return nil + } + err = tlsConn.Handshake() + if err != nil { + err = conn.handleError(err) + return nil + } + + state := tlsConn.ConnectionState() + + tlsData := &util.TLSdata{ + Version: state.Version, + HandshakeComplete: state.HandshakeComplete, + CipherSuite: state.CipherSuite, + NegotiatedProtocol: state.NegotiatedProtocol, + NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual, + PeerCertificates: state.PeerCertificates[0].Raw, + ServerName: state.ServerName, + } + + getRequest := fmt.Sprintf("GET / HTTP/1.1\r\nHost:%s\r\nConnection: close\r\n\r\n", config.ServerName) + _, err = tlsConn.Write([]byte(getRequest)) + if err != nil { + err = conn.handleError(err) + return tlsData + } + maxResponseLength := 1 << 16 + httpResponse := make([]byte, maxResponseLength) + + responseLength := 0 + for { + // TODO(adrs): add to config + tlsConn.SetReadDeadline(time.Now().Add(10 * time.Second)) + n, err := tlsConn.Read(httpResponse[responseLength:maxResponseLength]) + if err == io.EOF { + break + } else if err != nil { + err = conn.handleError(err) + return tlsData + } + responseLength += n + } + tlsData.HTTPResponse = string(httpResponse[:responseLength]) + return tlsData +} + +func (conn *Connection) handleError(err error) error { + if err != nil { + //log.Println("Error in connection: ", err) + conn.Err = err + if conn.Raw != nil { + conn.Raw.Close() + } + } + return err +} diff --git a/geolocate/geolocate.go b/geolocate/geolocate.go new file mode 100644 index 0000000..4d2dbe8 --- /dev/null +++ b/geolocate/geolocate.go @@ -0,0 +1,319 @@ +package geolocate + +import ( + "errors" + "fmt" + "net" + "strings" + + geoip2 "github.com/oschwald/geoip2-golang" +) + +var geoIp2Reader *geoip2.Reader + +// Initializes the geolocation service with the MaxMind MMDB file +func Initialize(databasePath string) error { + reader, err := geoip2.Open(databasePath) + if err == nil { + geoIp2Reader = reader + } + return err +} + +type Country struct { + Code string + Name string +} + +const language = "en" + +var countryCodes = [...]Country{ + {"AD", "Andorra"}, + {"AE", "United Arab Emirates"}, + {"AF", "Afghanistan"}, + {"AG", "Antigua and Barbuda"}, + {"AI", "Anguilla"}, + {"AL", "Albania"}, + {"AM", "Armenia"}, + {"AN", "Netherlands Antilles"}, + {"AO", "Angola"}, + {"AQ", "Antarctica"}, + {"AR", "Argentina"}, + {"AS", "American Samoa"}, + {"AT", "Austria"}, + {"AU", "Australia"}, + {"AW", "Aruba"}, + {"AX", "Åland"}, + {"AZ", "Azerbaijan"}, + {"BA", "Bosnia and Herzegovina"}, + {"BB", "Barbados"}, + {"BD", "Bangladesh"}, + {"BE", "Belgium"}, + {"BF", "Burkina Faso"}, + {"BG", "Bulgaria"}, + {"BH", "Bahrain"}, + {"BI", "Burundi"}, + {"BJ", "Benin"}, + {"BL", "Saint Barthélemy"}, + {"BM", "Bermuda"}, + {"BN", "Brunei"}, + {"BO", "Bolivia"}, + {"BQ", "Bonaire, Sint Eustatius, and Saba"}, + {"BR", "Brazil"}, + {"BS", "Bahamas"}, + {"BT", "Bhutan"}, + {"BV", "Bouvet Island"}, + {"BW", "Botswana"}, + {"BY", "Belarus"}, + {"BZ", "Belize"}, + {"CA", "Canada"}, + {"CC", "Cocos [Keeling] Islands"}, + {"CD", "DR Congo"}, + {"CF", "Central African Republic"}, + {"CG", "Congo Republic"}, + {"CH", "Switzerland"}, + {"CI", "Ivory Coast"}, + {"CK", "Cook Islands"}, + {"CL", "Chile"}, + {"CM", "Cameroon"}, + {"CN", "China"}, + {"CO", "Colombia"}, + {"CR", "Costa Rica"}, + {"CS", "Serbia and Montenegro"}, + {"CU", "Cuba"}, + {"CV", "Cabo Verde"}, + {"CW", "Curaçao"}, + {"CX", "Christmas Island"}, + {"CY", "Cyprus"}, + {"CZ", "Czechia"}, + {"DE", "Germany"}, + {"DJ", "Djibouti"}, + {"DK", "Denmark"}, + {"DM", "Dominica"}, + {"DO", "Dominican Republic"}, + {"DZ", "Algeria"}, + {"EC", "Ecuador"}, + {"EE", "Estonia"}, + {"EG", "Egypt"}, + {"EH", "Western Sahara"}, + {"ER", "Eritrea"}, + {"ES", "Spain"}, + {"ET", "Ethiopia"}, + {"FI", "Finland"}, + {"FJ", "Fiji"}, + {"FK", "Falkland Islands"}, + {"FM", "Micronesia"}, + {"FO", "Faroe Islands"}, + {"FR", "France"}, + {"GA", "Gabon"}, + {"GB", "United Kingdom"}, + {"GD", "Grenada"}, + {"GE", "Georgia"}, + {"GF", "French Guiana"}, + {"GG", "Guernsey"}, + {"GH", "Ghana"}, + {"GI", "Gibraltar"}, + {"GL", "Greenland"}, + {"GM", "Gambia"}, + {"GN", "Guinea"}, + {"GP", "Guadeloupe"}, + {"GQ", "Equatorial Guinea"}, + {"GR", "Greece"}, + {"GS", "South Georgia and South Sandwich Islands"}, + {"GT", "Guatemala"}, + {"GU", "Guam"}, + {"GW", "Guinea-Bissau"}, + {"GY", "Guyana"}, + {"HK", "Hong Kong"}, + {"HM", "Heard Island and McDonald Islands"}, + {"HN", "Honduras"}, + {"HR", "Croatia"}, + {"HT", "Haiti"}, + {"HU", "Hungary"}, + {"ID", "Indonesia"}, + {"IE", "Ireland"}, + {"IL", "Israel"}, + {"IM", "Isle of Man"}, + {"IN", "India"}, + {"IO", "British Indian Ocean Territory"}, + {"IQ", "Iraq"}, + {"IR", "Iran"}, + {"IS", "Iceland"}, + {"IT", "Italy"}, + {"JE", "Jersey"}, + {"JM", "Jamaica"}, + {"JO", "Jordan"}, + {"JP", "Japan"}, + {"KE", "Kenya"}, + {"KG", "Kyrgyzstan"}, + {"KH", "Cambodia"}, + {"KI", "Kiribati"}, + {"KM", "Comoros"}, + {"KN", "St Kitts and Nevis"}, + {"KP", "North Korea"}, + {"KR", "South Korea"}, + {"KW", "Kuwait"}, + {"KY", "Cayman Islands"}, + {"KZ", "Kazakhstan"}, + {"LA", "Laos"}, + {"LB", "Lebanon"}, + {"LC", "Saint Lucia"}, + {"LI", "Liechtenstein"}, + {"LK", "Sri Lanka"}, + {"LR", "Liberia"}, + {"LS", "Lesotho"}, + {"LT", "Lithuania"}, + {"LU", "Luxembourg"}, + {"LV", "Latvia"}, + {"LY", "Libya"}, + {"MA", "Morocco"}, + {"MC", "Monaco"}, + {"MD", "Moldova"}, + {"ME", "Montenegro"}, + {"MF", "Saint Martin"}, + {"MG", "Madagascar"}, + {"MH", "Marshall Islands"}, + {"MK", "North Macedonia"}, + {"ML", "Mali"}, + {"MM", "Myanmar"}, + {"MN", "Mongolia"}, + {"MO", "Macao"}, + {"MP", "Northern Mariana Islands"}, + {"MQ", "Martinique"}, + {"MR", "Mauritania"}, + {"MS", "Montserrat"}, + {"MT", "Malta"}, + {"MU", "Mauritius"}, + {"MV", "Maldives"}, + {"MW", "Malawi"}, + {"MX", "Mexico"}, + {"MY", "Malaysia"}, + {"MZ", "Mozambique"}, + {"NA", "Namibia"}, + {"NC", "New Caledonia"}, + {"NE", "Niger"}, + {"NF", "Norfolk Island"}, + {"NG", "Nigeria"}, + {"NI", "Nicaragua"}, + {"NL", "Netherlands"}, + {"NO", "Norway"}, + {"NP", "Nepal"}, + {"NR", "Nauru"}, + {"NU", "Niue"}, + {"NZ", "New Zealand"}, + {"OM", "Oman"}, + {"PA", "Panama"}, + {"PE", "Peru"}, + {"PF", "French Polynesia"}, + {"PG", "Papua New Guinea"}, + {"PH", "Philippines"}, + {"PK", "Pakistan"}, + {"PL", "Poland"}, + {"PM", "Saint Pierre and Miquelon"}, + {"PN", "Pitcairn Islands"}, + {"PR", "Puerto Rico"}, + {"PS", "Palestine"}, + {"PT", "Portugal"}, + {"PW", "Palau"}, + {"PY", "Paraguay"}, + {"QA", "Qatar"}, + {"RE", "Réunion"}, + {"RO", "Romania"}, + {"RS", "Serbia"}, + {"RU", "Russia"}, + {"RW", "Rwanda"}, + {"SA", "Saudi Arabia"}, + {"SB", "Solomon Islands"}, + {"SC", "Seychelles"}, + {"SD", "Sudan"}, + {"SE", "Sweden"}, + {"SG", "Singapore"}, + {"SH", "Saint Helena"}, + {"SI", "Slovenia"}, + {"SJ", "Svalbard and Jan Mayen"}, + {"SK", "Slovakia"}, + {"SL", "Sierra Leone"}, + {"SM", "San Marino"}, + {"SN", "Senegal"}, + {"SO", "Somalia"}, + {"SR", "Suriname"}, + {"SS", "South Sudan"}, + {"ST", "São Tomé and Príncipe"}, + {"SV", "El Salvador"}, + {"SX", "Sint Maarten"}, + {"SY", "Syria"}, + {"SZ", "Eswatini"}, + {"TC", "Turks and Caicos Islands"}, + {"TD", "Chad"}, + {"TF", "French Southern Territories"}, + {"TG", "Togo"}, + {"TH", "Thailand"}, + {"TJ", "Tajikistan"}, + {"TK", "Tokelau"}, + {"TL", "Timor-Leste"}, + {"TM", "Turkmenistan"}, + {"TN", "Tunisia"}, + {"TO", "Tonga"}, + {"TR", "Turkey"}, + {"TT", "Trinidad and Tobago"}, + {"TV", "Tuvalu"}, + {"TW", "Taiwan"}, + {"TZ", "Tanzania"}, + {"UA", "Ukraine"}, + {"UG", "Uganda"}, + {"UM", "U.S. Minor Outlying Islands"}, + {"US", "United States"}, + {"UY", "Uruguay"}, + {"UZ", "Uzbekistan"}, + {"VA", "Vatican City"}, + {"VC", "St Vincent and Grenadines"}, + {"VE", "Venezuela"}, + {"VG", "British Virgin Islands"}, + {"VI", "U.S. Virgin Islands"}, + {"VN", "Vietnam"}, + {"VU", "Vanuatu"}, + {"WF", "Wallis and Futuna"}, + {"WS", "Samoa"}, + {"XK", "Kosovo"}, + {"YE", "Yemen"}, + {"YT", "Mayotte"}, + {"ZA", "South Africa"}, + {"ZM", "Zambia"}, + {"ZW", "Zimbabwe"}, +} + +var names map[string]string +var codes map[string]string + +func IsCountryCode(code string) bool { + _, ok := names[strings.ToUpper(code)] + return ok +} + +func init() { + names = make(map[string]string) + codes = make(map[string]string) + for _, cc := range countryCodes { + names[cc.Code] = cc.Name + codes[cc.Name] = cc.Code + } +} + +func Geolocate(ip string) (string, error) { + address := net.ParseIP(ip) + if address == nil { + return "", errors.New(fmt.Sprintf("invalid IP address \"%s\"", ip)) + } + if geoIp2Reader == nil { + return "", errors.New("GeoIp2 reader uninitialized") + } + data, err := geoIp2Reader.City(address) + if err != nil { + return "", err + } + countryCode := strings.ToUpper(data.Country.IsoCode) + if countryCode != "" && !IsCountryCode(countryCode) { + return "", errors.New(fmt.Sprintf("Error: unrecognized country code \"%s\"", countryCode)) + } + return countryCode, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6aaa764 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module CenFuzz + +go 1.16 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7499bea --- /dev/null +++ b/go.sum @@ -0,0 +1,653 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0 h1:DAq3r8y4mDgyB/ZPJ9v/5VJNqjgJAxTn6ZYLlUywOu8= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.32.0 h1:0OMQYCp03Ff9B5OeVY8GGUlOC99s93bjM+c5xS0H5gs= +cloud.google.com/go/bigquery v1.32.0/go.mod h1:hAfV1647X+/fGUqeVVdKW+HfYtT5UCjOZsuOydOSH4M= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/banviktor/asnlookup v0.1.0 h1:rErAtlKFoqkkYxySad3BNBmdOksfbMFTdQCp7WaY1Ok= +github.com/banviktor/asnlookup v0.1.0/go.mod h1:BtQkoiOTh7jgtnk4QpjEpowoVY9O4I4qGw5NKCcDpZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0 h1:nRJtk3y8Fm770D42QV6T90ZnvFZyk7agSo3Q+Z9p3WI= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jpillora/go-tld v1.1.1 h1:P1ZwtKDHBYYUl235R/D64cdBARfGYzEy1Hg2Ikir3FQ= +github.com/jpillora/go-tld v1.1.1/go.mod h1:kitBxOF//DR5FxYeIGw+etdiiTIq5S7bx0dwy1GUNAk= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kaorimatz/go-mrt v0.0.0-20210326003454-aa11f3646f93 h1:w3cdcsXZUIL9cAD3jlAPtsjIAOyGbgMRGfNcXTTgH5Q= +github.com/kaorimatz/go-mrt v0.0.0-20210326003454-aa11f3646f93/go.mod h1:KUXNgiu1+bxftJBB5MHhuDqBiL0gi6G4lTALnxp3qwE= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/libp2p/go-reuseport v0.1.0 h1:0ooKOx2iwyIkf339WCZ2HN3ujTDbkK0PjC7JVoP1AiM= +github.com/libp2p/go-reuseport v0.1.0/go.mod h1:bQVn9hmfcTaoo0c9v5pBhOarsU1eNOBZdaAd2hzXRKU= +github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560= +github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k= +github.com/mxschmitt/golang-combinations v1.1.0 h1:WlIZCnDm+Xlb2pRPf+R/qPKlGOU1w8lpN69/uy5z+Zg= +github.com/mxschmitt/golang-combinations v1.1.0/go.mod h1:RbMhWvfCelHR6WROvT2bVfxJvZHoEvBj71SKe+H0MYU= +github.com/oschwald/geoip2-golang v1.7.0 h1:JW1r5AKi+vv2ujSxjKthySK3jo8w8oKWPyXsw+Qs/S8= +github.com/oschwald/geoip2-golang v1.7.0/go.mod h1:mdI/C7iK7NVMcIDDtf4bCKMJ7r0o7UwGeCo9eiitCMQ= +github.com/oschwald/maxminddb-golang v1.9.0 h1:tIk4nv6VT9OiPyrnDAfJS1s1xKDQMZOsGojab6EjC1Y= +github.com/oschwald/maxminddb-golang v1.9.0/go.mod h1:TK+s/Z2oZq0rSl4PSeAEoP0bgm82Cp5HyvYbt8K3zLY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/refraction-networking/utls v1.0.0 h1:6XQHSjDmeBCF9sPq8p2zMVGq7Ud3rTD2q88Fw8Tz1tA= +github.com/refraction-networking/utls v1.0.0/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 h1:XDXtA5hveEEV8JB2l7nhMTp3t3cHp9ZpwcdjqyEWLlo= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9 h1:XGQ6tc+EnM35IAazg4y6AHmUg4oK8NXsXaILte1vRlk= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/http_fuzzer/10_hostword_alternate.go b/http_fuzzer/10_hostword_alternate.go new file mode 100644 index 0000000..2b09122 --- /dev/null +++ b/http_fuzzer/10_hostword_alternate.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HostWordAlternate struct{} + +func (h *HostWordAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + hostWordAlternate := util.GenerateHostAlternatives() + requestWord = &RequestWord{ + HostWord: hostWordAlternate, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HostWordAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + hostWordAllAlternate := util.GenerateAllHostAlternatives() + for _, hostWord := range hostWordAllAlternate { + requestWords = append(requestWords, &RequestWord{ + HostWord: hostWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (g *HostWordAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/11_httpdelimiterword_remove.go b/http_fuzzer/11_httpdelimiterword_remove.go new file mode 100644 index 0000000..7c1b1a8 --- /dev/null +++ b/http_fuzzer/11_httpdelimiterword_remove.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HttpDelimiterWordRemove struct{} + +func (h *HttpDelimiterWordRemove) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + httpDelimiterWord := util.GenerateRandomlyRemovedWord("\r\n") + requestWord = &RequestWord{ + HttpDelimiterWord: httpDelimiterWord, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HttpDelimiterWordRemove.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + httpDelimiterWordAllRemove := util.GenerateAllSubstringPermutations("\r\n") + for _, httpDelimiterWord := range httpDelimiterWordAllRemove { + requestWords = append(requestWords, &RequestWord{ + HttpDelimiterWord: httpDelimiterWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (g *HttpDelimiterWordRemove) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/12_path_alternate.go b/http_fuzzer/12_path_alternate.go new file mode 100644 index 0000000..4b2795a --- /dev/null +++ b/http_fuzzer/12_path_alternate.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type PathAlternate struct{} + +func (h *PathAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + pathAlternate := util.GeneratePathAlternatives() + requestWord = &RequestWord{ + Path: pathAlternate, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[pathAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + pathAllAlternate := util.GenerateAllPathAlternatives() + for _, path := range pathAllAlternate { + requestWords = append(requestWords, &RequestWord{ + Path: path, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (g *PathAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/13_header_alternate.go b/http_fuzzer/13_header_alternate.go new file mode 100644 index 0000000..3be531d --- /dev/null +++ b/http_fuzzer/13_header_alternate.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HeaderAlternate struct{} + +func (h *HeaderAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + headerAlternate := util.GenerateHeaderAlternatives() + requestWord = &RequestWord{ + Header: headerAlternate + "\r\n", + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HeaderAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + headerAllAlternate := util.GenerateAllHeaderAlternatives() + for _, header := range headerAllAlternate { + requestWords = append(requestWords, &RequestWord{ + Header: header + "\r\n", + Hostname: "%s", + }) + } + } + return requestWords +} + +func (g *HeaderAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/14_hostname_alternate.go b/http_fuzzer/14_hostname_alternate.go new file mode 100644 index 0000000..0681964 --- /dev/null +++ b/http_fuzzer/14_hostname_alternate.go @@ -0,0 +1,45 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HostNameAlternate struct{} + +func (h *HostNameAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + hostNameAlternate := util.GenerateHostNameAlternatives() + requestWord = &RequestWord{ + Hostname: hostNameAlternate, + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HostNameAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + hostnameAllAlternatives := util.GenerateAllHostNameAlternatives() + for _, hostname := range hostnameAllAlternatives { + requestWords = append(requestWords, &RequestWord{Hostname: hostname}) + } + } + return requestWords +} + +func (h *HostNameAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/15_hostname_tld_alternate.go b/http_fuzzer/15_hostname_tld_alternate.go new file mode 100644 index 0000000..caf16a1 --- /dev/null +++ b/http_fuzzer/15_hostname_tld_alternate.go @@ -0,0 +1,45 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HostnameTLDAlternate struct{} + +func (h *HostnameTLDAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + HostnameAlternate := util.GenerateTLDAlternatives() + requestWord = &RequestWord{ + Hostname: HostnameAlternate, + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HostnameTLDAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + HostnameAllAlternatives := util.GenerateAllTLDAlternatives() + for _, Hostname := range HostnameAllAlternatives { + requestWords = append(requestWords, &RequestWord{Hostname: Hostname}) + } + } + return requestWords +} + +func (h *HostnameTLDAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/16_hostname_subdomain_alternate.go b/http_fuzzer/16_hostname_subdomain_alternate.go new file mode 100644 index 0000000..c0e4662 --- /dev/null +++ b/http_fuzzer/16_hostname_subdomain_alternate.go @@ -0,0 +1,45 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HostnameSubdomainsAlternate struct{} + +func (h *HostnameSubdomainsAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + HostnameAlternate := util.GenerateSubdomainsAlternatives() + requestWord = &RequestWord{ + Hostname: HostnameAlternate, + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HostnameSubdomainsAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + HostnameAllAlternatives := util.GenerateAllSubdomainsAlternatives() + for _, Hostname := range HostnameAllAlternatives { + requestWords = append(requestWords, &RequestWord{Hostname: Hostname}) + } + } + return requestWords +} + +func (h *HostnameSubdomainsAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/1_hostname_padding.go b/http_fuzzer/1_hostname_padding.go new file mode 100644 index 0000000..71fdf56 --- /dev/null +++ b/http_fuzzer/1_hostname_padding.go @@ -0,0 +1,45 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HostnamePadding struct{} + +func (h *HostnamePadding) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + HostnameWithRandomPadding := util.GenerateHostNameRandomPadding() + requestWord = &RequestWord{ + Hostname: HostnameWithRandomPadding, + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HostnameWithRandomPadding.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + hostnameAllPadding := util.GenerateAllHostNamePaddings() + for _, hostname := range hostnameAllPadding { + requestWords = append(requestWords, &RequestWord{Hostname: hostname}) + } + } + return requestWords +} + +func (h *HostnamePadding) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/2_getword_capitalize.go b/http_fuzzer/2_getword_capitalize.go new file mode 100644 index 0000000..9044a89 --- /dev/null +++ b/http_fuzzer/2_getword_capitalize.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type GetWordCapitalize struct{} + +func (g *GetWordCapitalize) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + getWordCapitalize := util.GenerateRandomCapitalizedValues("GET") + requestWord = &RequestWord{ + GetWord: getWordCapitalize, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[GetWordCapitalize.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + getWordsAllCapitalize := util.GenerateAllCapitalizedPermutations("GET") + for _, getWord := range getWordsAllCapitalize { + requestWords = append(requestWords, &RequestWord{ + GetWord: getWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (g *GetWordCapitalize) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/3_getword_remove.go b/http_fuzzer/3_getword_remove.go new file mode 100644 index 0000000..a4e37eb --- /dev/null +++ b/http_fuzzer/3_getword_remove.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type GetWordRemove struct{} + +func (g *GetWordRemove) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + getWordRemove := util.GenerateRandomlyRemovedWord("GET") + requestWord = &RequestWord{ + GetWord: getWordRemove, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[GetWordRemove.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + getWordAllRemove := util.GenerateAllSubstringPermutations("GET") + for _, getWord := range getWordAllRemove { + requestWords = append(requestWords, &RequestWord{ + GetWord: getWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (g *GetWordRemove) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/4_getword_alternate.go b/http_fuzzer/4_getword_alternate.go new file mode 100644 index 0000000..9a66089 --- /dev/null +++ b/http_fuzzer/4_getword_alternate.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type GetWordAlternate struct{} + +func (g *GetWordAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + getWordAlternate := util.GenerateGetAlternatives() + requestWord = &RequestWord{ + GetWord: getWordAlternate, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[GetWordAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + getWordAllAlternate := util.GenerateAllGetAlternatives() + for _, getWord := range getWordAllAlternate { + requestWords = append(requestWords, &RequestWord{ + GetWord: getWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (g *GetWordAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/5_httpword_capitalize.go b/http_fuzzer/5_httpword_capitalize.go new file mode 100644 index 0000000..970452f --- /dev/null +++ b/http_fuzzer/5_httpword_capitalize.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HttpWordCapitalize struct{} + +func (h *HttpWordCapitalize) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + httpWordCapitalize := util.GenerateRandomCapitalizedValues("HTTP/1.1") + requestWord = &RequestWord{ + HttpWord: httpWordCapitalize, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HttpWordCapitalize.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + httpWordAllCapitalize := util.GenerateAllCapitalizedPermutations("HTTP/1.1") + for _, httpWord := range httpWordAllCapitalize { + requestWords = append(requestWords, &RequestWord{ + HttpWord: httpWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (h *HttpWordCapitalize) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/6_httpword_remove.go b/http_fuzzer/6_httpword_remove.go new file mode 100644 index 0000000..fa4f0bc --- /dev/null +++ b/http_fuzzer/6_httpword_remove.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HttpWordRemove struct{} + +func (h *HttpWordRemove) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + httpWordRemove := util.GenerateRandomlyRemovedWord("HTTP/1.1") + requestWord = &RequestWord{ + HttpWord: httpWordRemove, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HttpWordRemove.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + httpWordAllRemove := util.GenerateAllSubstringPermutations("HTTP/1.1") + for _, httpWord := range httpWordAllRemove { + requestWords = append(requestWords, &RequestWord{ + HttpWord: httpWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (h *HttpWordRemove) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/7_httpword_alternate.go b/http_fuzzer/7_httpword_alternate.go new file mode 100644 index 0000000..696ccdd --- /dev/null +++ b/http_fuzzer/7_httpword_alternate.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HttpWordAlternate struct{} + +func (h *HttpWordAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + httpWordAlternate := util.GenerateHttpAlternatives() + requestWord = &RequestWord{ + HttpWord: httpWordAlternate, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HttpWordAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + httpWordAllAlternate := util.GenerateAllHttpAlternatives() + for _, httpWord := range httpWordAllAlternate { + requestWords = append(requestWords, &RequestWord{ + HttpWord: httpWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (g *HttpWordAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/8_hostword_capitalize.go b/http_fuzzer/8_hostword_capitalize.go new file mode 100644 index 0000000..1e99633 --- /dev/null +++ b/http_fuzzer/8_hostword_capitalize.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HostWordCapitalize struct{} + +func (h *HostWordCapitalize) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + hostWordCapitalize := util.GenerateRandomCapitalizedValues("Host: ") + requestWord = &RequestWord{ + HostWord: hostWordCapitalize, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HostWordCapitalize.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + hostWordAllRemove := util.GenerateAllCapitalizedPermutations("Host: ") + for _, hostWord := range hostWordAllRemove { + requestWords = append(requestWords, &RequestWord{ + HostWord: hostWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (h *HostWordCapitalize) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/9_hostword_remove.go b/http_fuzzer/9_hostword_remove.go new file mode 100644 index 0000000..9f8a0bb --- /dev/null +++ b/http_fuzzer/9_hostword_remove.go @@ -0,0 +1,49 @@ +package http_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type HostWordRemove struct{} + +func (h *HostWordRemove) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + hostWordRemove := util.GenerateRandomlyRemovedWord("Host: ") + requestWord = &RequestWord{ + HostWord: hostWordRemove, + Hostname: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[HostWordRemove.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + hostWordAllRemove := util.GenerateAllSubstringPermutations("Host: ") + for _, hostWord := range hostWordAllRemove { + requestWords = append(requestWords, &RequestWord{ + HostWord: hostWord, + Hostname: "%s", + }) + } + } + return requestWords +} + +func (h *HostWordRemove) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/http_fuzzer/fuzzer.go b/http_fuzzer/fuzzer.go new file mode 100644 index 0000000..d6db278 --- /dev/null +++ b/http_fuzzer/fuzzer.go @@ -0,0 +1,120 @@ +package http_fuzzer + +import ( + "fmt" + "log" + "strconv" + "strings" + + "github.com/censoredplanet/CenFuzz/connection" + "github.com/censoredplanet/CenFuzz/util" + "github.com/google/go-cmp/cmp" + tld "github.com/jpillora/go-tld" +) + +type RequestWord struct { + Hostname string + GetWord string `default:"GET"` + HttpWord string `default:"HTTP/1.1"` + HostWord string `default:"Host:"` + HttpDelimiterWord string `default:"\r\n"` + Path string `default:"/"` + Header string `default:""` +} + +func containsRequestWord(s []*RequestWord, e *RequestWord) bool { + for _, a := range s { + if cmp.Equal(a, e) { + return true + } + } + return false +} + +// Returns of an HTTP request for URL. +func FormatHttpRequest(requestWord RequestWord) string { + getWord := "GET" + if requestWord.GetWord != "" { + getWord = requestWord.GetWord + } + httpWord := "HTTP/1.1" + if requestWord.HttpWord != "" { + httpWord = requestWord.HttpWord + } + hostWord := "Host:" + if requestWord.HostWord != "" { + hostWord = requestWord.HostWord + } + httpDelimiterWord := "\r\n" + if requestWord.HttpDelimiterWord != "" { + httpDelimiterWord = requestWord.HttpDelimiterWord + } + path := " / " + if requestWord.Path != "" { + path = requestWord.Path + } + header := "" + if requestWord.Header != "" { + header = requestWord.Header + } + + //Handle hostname changes - This has to be done at runtime, since the strategies would be selected first, but the hostname itself is only known at runtime + var host string + hostNameParts := strings.Split(requestWord.Hostname, "|") + if len(hostNameParts) > 1 { + //ServerNameParts[1] contains the strategy to be run at runtime + if hostNameParts[1] == "omit" { + format := "%s%s%s%s\r\n%s\r\n" + return fmt.Sprintf(format, getWord, path, httpWord, httpDelimiterWord, header) + } else if hostNameParts[1] == "empty" { + host = "" + } else if hostNameParts[1] == "repeat" { + //Now there should be a third part that says how many times to repeat + repeatTimes, err := strconv.Atoi(hostNameParts[2]) + if err != nil { + log.Println("[https_fuzzer.CreateTLSConfig] Error converting string into integer (repeat)") + log.Println(err) + log.Println("Reverting to default") + host = hostNameParts[0] + } else { + host = util.Repeat(hostNameParts[0], repeatTimes) + } + } else if hostNameParts[1] == "reverse" { + host = util.Reverse(hostNameParts[0]) + } else if hostNameParts[1] == "tld" { + domainParts, _ := tld.Parse("https://" + hostNameParts[0]) + if domainParts.Subdomain != "" { + host = domainParts.Subdomain + "." + domainParts.Domain + "." + hostNameParts[2] + } else { + host = domainParts.Domain + "." + hostNameParts[2] + } + } else if hostNameParts[1] == "subdomain" { + domainParts, _ := tld.Parse("https://" + hostNameParts[0]) + host = hostNameParts[2] + "." + domainParts.Domain + "." + domainParts.TLD + } + } else { + host = requestWord.Hostname + } + + format := "%s%s%s%s%s%s\r\n%s\r\n" + return fmt.Sprintf(format, getWord, path, httpWord, httpDelimiterWord, hostWord, host, header) +} + +func MakeConnection(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + formattedHostname := FormatHttpRequest(requestWord) + conn := connection.NewConnection(target, 80) + if conn == nil { + return formattedHostname, nil, "Dial" + } + + response := connection.SendHTTPRequest(conn, formattedHostname) + if conn.Err != nil { + return formattedHostname, nil, conn.Err.Error() + } + return formattedHostname, response, nil +} + +type Fuzzer interface { + Init(all bool) []*RequestWord + Fuzz(ip string, domain string, requestWord RequestWord) (interface{}, interface{}, interface{}) +} diff --git a/https_fuzzer/1_servername_padding.go b/https_fuzzer/1_servername_padding.go new file mode 100644 index 0000000..edaddd8 --- /dev/null +++ b/https_fuzzer/1_servername_padding.go @@ -0,0 +1,45 @@ +package https_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type ServernamePadding struct{} + +func (s *ServernamePadding) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + ServerNameWithRandomPadding := util.GenerateHostNameRandomPadding() + requestWord = &RequestWord{ + Servername: ServerNameWithRandomPadding, + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[ServerNameWithRandomPadding.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + servernameAllPadding := util.GenerateAllHostNamePaddings() + for _, servername := range servernameAllPadding { + requestWords = append(requestWords, &RequestWord{Servername: servername}) + } + } + return requestWords +} + +func (s *ServernamePadding) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/https_fuzzer/2_minversion_alternate.go b/https_fuzzer/2_minversion_alternate.go new file mode 100644 index 0000000..36ef387 --- /dev/null +++ b/https_fuzzer/2_minversion_alternate.go @@ -0,0 +1,59 @@ +package https_fuzzer + +import ( + "log" + "strconv" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type MinVersionAlternate struct{} + +func (m *MinVersionAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + minVersion := util.GenerateVersionAlternatives() + minVersionInt, err := strconv.ParseUint(minVersion, 10, 16) + if err != nil { + log.Println("[MinVersionAlternate.Init] Could not convert string to int") + log.Println(err) + continue + } + requestWord = &RequestWord{ + MinVersion: uint16(minVersionInt), + Servername: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[MinVersionAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + allMinVersions := util.GenerateAllVersionAlternatives() + for _, minVersion := range allMinVersions { + minVersionInt, err := strconv.ParseUint(minVersion, 10, 16) + if err != nil { + log.Println("[MinVersionAlternate.Init] Could not convert string to int") + log.Println(err) + continue + } + requestWords = append(requestWords, &RequestWord{MinVersion: uint16(minVersionInt), Servername: "%s"}) + } + } + return requestWords +} + +func (m *MinVersionAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/https_fuzzer/3_maxversion_alternate.go b/https_fuzzer/3_maxversion_alternate.go new file mode 100644 index 0000000..d64bab4 --- /dev/null +++ b/https_fuzzer/3_maxversion_alternate.go @@ -0,0 +1,59 @@ +package https_fuzzer + +import ( + "log" + "strconv" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type MaxversionAlternate struct{} + +func (m *MaxversionAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + maxversion := util.GenerateVersionAlternatives() + maxversionInt, err := strconv.ParseUint(maxversion, 10, 16) + if err != nil { + log.Println("[MaxversionAlternate.Init] Could not convert string to int") + log.Println(err) + continue + } + requestWord = &RequestWord{ + MaxVersion: uint16(maxversionInt), + Servername: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[MaxversionAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + allMaxversions := util.GenerateAllVersionAlternatives() + for _, maxversion := range allMaxversions { + maxversionInt, err := strconv.ParseUint(maxversion, 10, 16) + if err != nil { + log.Println("[MaxversionAlternate.Init] Could not convert string to int") + log.Println(err) + continue + } + requestWords = append(requestWords, &RequestWord{MaxVersion: uint16(maxversionInt), Servername: "%s"}) + } + } + return requestWords +} + +func (m *MaxversionAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/https_fuzzer/4_ciphersuite_alternate.go b/https_fuzzer/4_ciphersuite_alternate.go new file mode 100644 index 0000000..b9c524b --- /dev/null +++ b/https_fuzzer/4_ciphersuite_alternate.go @@ -0,0 +1,59 @@ +package https_fuzzer + +import ( + "log" + "strconv" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type CipherSuiteAlternate struct{} + +func (c *CipherSuiteAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + cipherSuite := util.GenerateCipherSuiteAlternatives() + cipherSuiteInt, err := strconv.ParseUint(cipherSuite, 10, 16) + if err != nil { + log.Println("[CipherSuiteAlternate.Init] Could not convert string to int") + log.Println(err) + continue + } + requestWord = &RequestWord{ + CipherSuites: []uint16{uint16(cipherSuiteInt)}, + Servername: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[CipherSuiteAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + allCipherSuites := util.GenerateAllCipherSuiteAlternatives() + for _, cipherSuite := range allCipherSuites { + cipherSuiteInt, err := strconv.ParseUint(cipherSuite, 10, 16) + if err != nil { + log.Println("[CipherSuiteAlternate.Init] Could not convert string to int") + log.Println(err) + continue + } + requestWords = append(requestWords, &RequestWord{CipherSuites: []uint16{uint16(cipherSuiteInt)}, Servername: "%s"}) + } + } + return requestWords +} + +func (c *CipherSuiteAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/https_fuzzer/5_clientcert_alternate.go b/https_fuzzer/5_clientcert_alternate.go new file mode 100644 index 0000000..5902adb --- /dev/null +++ b/https_fuzzer/5_clientcert_alternate.go @@ -0,0 +1,73 @@ +package https_fuzzer + +import ( + "log" + + utls "github.com/refraction-networking/utls" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type ClientCertAlternate struct{} + +func (c *ClientCertAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + certificateServername := util.GenerateCertificateAlternatives() + clientCertPEM, ClientCertKey, err := util.GenerateCertificate(certificateServername) + if err != nil { + log.Println("[ClientCertAlternate.Init] Could not generate client certificate") + log.Println(err) + continue + } + clientCert, err := utls.X509KeyPair(clientCertPEM, ClientCertKey) + if err != nil { + log.Println("[ClientCertAlternate.Init] Could not generate client certificate") + log.Println(err) + continue + } + requestWord = &RequestWord{ + Certificate: []utls.Certificate{clientCert}, + Servername: "%s", + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[ClientCertAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + allClientCerts := util.GenerateAllCertificateAlternatives() + for _, clientCert := range allClientCerts { + clientCertPEM, ClientCertKey, err := util.GenerateCertificate(clientCert) + if err != nil { + log.Println("[ClientCertAlternate.Init] Could not generate client certificate") + log.Println(err) + continue + } + clientCert, err := utls.X509KeyPair(clientCertPEM, ClientCertKey) + if err != nil { + log.Println("[ClientCertAlternate.Init] Could not generate client certificate") + log.Println(err) + continue + } + requestWords = append(requestWords, &RequestWord{Certificate: []utls.Certificate{clientCert}, Servername: "%s"}) + + } + } + return requestWords +} + +func (c *ClientCertAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/https_fuzzer/6_servername_alternate.go b/https_fuzzer/6_servername_alternate.go new file mode 100644 index 0000000..c395c0f --- /dev/null +++ b/https_fuzzer/6_servername_alternate.go @@ -0,0 +1,45 @@ +package https_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type ServernameAlternate struct{} + +func (s *ServernameAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + ServerNameAlternate := util.GenerateServerNameAlternatives() + requestWord = &RequestWord{ + Servername: ServerNameAlternate, + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[ServernameAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + servernameAllAlternatives := util.GenerateAllServerNameAlternatives() + for _, servername := range servernameAllAlternatives { + requestWords = append(requestWords, &RequestWord{Servername: servername}) + } + } + return requestWords +} + +func (s *ServernameAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/https_fuzzer/7_servername_tld_alternate.go b/https_fuzzer/7_servername_tld_alternate.go new file mode 100644 index 0000000..8a8a093 --- /dev/null +++ b/https_fuzzer/7_servername_tld_alternate.go @@ -0,0 +1,45 @@ +package https_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type ServernameTLDAlternate struct{} + +func (s *ServernameTLDAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + ServerNameAlternate := util.GenerateTLDAlternatives() + requestWord = &RequestWord{ + Servername: ServerNameAlternate, + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[ServernameTLDAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + servernameAllAlternatives := util.GenerateAllTLDAlternatives() + for _, servername := range servernameAllAlternatives { + requestWords = append(requestWords, &RequestWord{Servername: servername}) + } + } + return requestWords +} + +func (s *ServernameTLDAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/https_fuzzer/8_servername_subdomain_alternate.go b/https_fuzzer/8_servername_subdomain_alternate.go new file mode 100644 index 0000000..db65d3b --- /dev/null +++ b/https_fuzzer/8_servername_subdomain_alternate.go @@ -0,0 +1,45 @@ +package https_fuzzer + +import ( + "log" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" +) + +type ServernameSubdomainsAlternate struct{} + +func (s *ServernameSubdomainsAlternate) Init(all bool) []*RequestWord { + var requestWords []*RequestWord + var requestWord *RequestWord + retries := 0 + if !all { + for i := 0; i < config.NumberOfProbesPerTest; i++ { + ServerNameAlternate := util.GenerateSubdomainsAlternatives() + requestWord = &RequestWord{ + Servername: ServerNameAlternate, + } + if containsRequestWord(requestWords, requestWord) { + i-- + retries += 1 + if retries >= 10 { + log.Println("[ServernameSubdomainsAlternate.Init] Could not find a new random value after 10 retries. Breaking.") + break + } + } else { + requestWords = append(requestWords, requestWord) + retries = 0 + } + } + } else { + servernameAllAlternatives := util.GenerateAllSubdomainsAlternatives() + for _, servername := range servernameAllAlternatives { + requestWords = append(requestWords, &RequestWord{Servername: servername}) + } + } + return requestWords +} + +func (s *ServernameSubdomainsAlternate) Fuzz(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + return MakeConnection(target, hostname, requestWord) +} diff --git a/https_fuzzer/fuzzer.go b/https_fuzzer/fuzzer.go new file mode 100644 index 0000000..4919a7e --- /dev/null +++ b/https_fuzzer/fuzzer.go @@ -0,0 +1,200 @@ +package https_fuzzer + +import ( + "crypto/x509" + "log" + "strconv" + "strings" + + "github.com/censoredplanet/CenFuzz/connection" + "github.com/censoredplanet/CenFuzz/util" + "github.com/google/go-cmp/cmp" + tld "github.com/jpillora/go-tld" + utls "github.com/refraction-networking/utls" +) + +type RequestWord struct { + Servername string + CipherSuites []uint16 + MinVersion uint16 + MaxVersion uint16 + Certificate []utls.Certificate +} + +func containsRequestWord(s []*RequestWord, e *RequestWord) bool { + for _, a := range s { + if cmp.Equal(a, e) { + return true + } + } + return false +} + +// Returns of an HTTP request for URL. +func CreateTLSConfig(requestWord RequestWord) *utls.Config { + + //Set max version to TLS 1.2 if we want cipher suites to be configurable. TLS 1.3 cipher suites are not configurable + //https://pkg.go.dev/crypto/tls#Config + if len(requestWord.CipherSuites) > 0 { + if requestWord.MaxVersion == 772 || requestWord.MaxVersion == 0 { + requestWord.MaxVersion = 771 + } + } + + var cert utls.Certificate + if len(requestWord.Certificate) > 0 { + cert = requestWord.Certificate[0] + c, err := x509.ParseCertificate(cert.Certificate[0]) + if err != nil { + log.Println("[https_fuzzer.CreateTLSConfig] Error parsing certificate") + } + if c.Subject.CommonName == "" { + clientCertPEM, ClientCertKey, err := util.GenerateCertificate(requestWord.Servername) + if err != nil { + log.Println("[https_fuzzer.CreateTLSConfig] Could not generate client certificate") + log.Println(err) + return &utls.Config{ + ServerName: requestWord.Servername, + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + } + } + cert, err = utls.X509KeyPair(clientCertPEM, ClientCertKey) + if err != nil { + log.Println("[https_fuzzer.CreateTLSConfig] Could not generate client certificate") + log.Println(err) + return &utls.Config{ + ServerName: requestWord.Servername, + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + } + } + } + } + + //Handle servername changes - This has to be done at runtime, since the strategies would be selected first, but the servername itself is only known at runtime + serverNameParts := strings.Split(requestWord.Servername, "|") + if len(serverNameParts) > 1 { + //ServerNameParts[1] contains the strategy to be run at runtime + if serverNameParts[1] == "omit" { + return &utls.Config{ + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + Certificates: []utls.Certificate{cert}, + } + } else if serverNameParts[1] == "empty" { + return &utls.Config{ + ServerName: "", + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + Certificates: []utls.Certificate{cert}, + } + } else if serverNameParts[1] == "repeat" { + //Now there should be a third part that says how many times to repeat + repeatTimes, err := strconv.Atoi(serverNameParts[2]) + if err != nil { + log.Println("[https_fuzzer.CreateTLSConfig] Error converting string into integer (repeat)") + log.Println(err) + log.Println("Reverting to default") + return &utls.Config{ + ServerName: requestWord.Servername, + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + Certificates: []utls.Certificate{cert}, + } + } + serverName := util.Repeat(serverNameParts[0], repeatTimes) + return &utls.Config{ + ServerName: serverName, + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + Certificates: []utls.Certificate{cert}, + } + } else if serverNameParts[1] == "reverse" { + return &utls.Config{ + ServerName: util.Reverse(serverNameParts[0]), + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + Certificates: []utls.Certificate{cert}, + } + } else if serverNameParts[1] == "tld" { + domainParts, _ := tld.Parse("https://" + serverNameParts[0]) + var serverName string + if domainParts.Subdomain != "" { + serverName = domainParts.Subdomain + "." + domainParts.Domain + "." + serverNameParts[2] + } else { + serverName = domainParts.Domain + "." + serverNameParts[2] + } + return &utls.Config{ + ServerName: serverName, + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + Certificates: []utls.Certificate{cert}, + } + } else if serverNameParts[1] == "subdomain" { + domainParts, _ := tld.Parse("https://" + serverNameParts[0]) + serverName := serverNameParts[2] + "." + domainParts.Domain + "." + domainParts.TLD + return &utls.Config{ + ServerName: serverName, + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + Certificates: []utls.Certificate{cert}, + } + } + } + + return &utls.Config{ + ServerName: requestWord.Servername, + InsecureSkipVerify: true, + CipherSuites: requestWord.CipherSuites, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + Certificates: []utls.Certificate{cert}, + } + +} + +func MakeConnection(target string, hostname string, requestWord RequestWord) (interface{}, interface{}, interface{}) { + config := CreateTLSConfig(requestWord) + //Recrate updated requestword + request := &RequestWord{ + Servername: config.ServerName, + CipherSuites: config.CipherSuites, + MinVersion: config.MinVersion, + MaxVersion: config.MaxVersion, + Certificate: config.Certificates, + } + conn := connection.NewConnection(target, 443) + if conn == nil { + return request, nil, "Dial" + } + + response := connection.SendHTTPSRequest(conn, *config) + if conn.Err != nil { + return request, nil, conn.Err.Error() + } + return request, response, nil +} + +type Fuzzer interface { + Init(all bool) []*RequestWord + Fuzz(ip string, domain string, requestWord RequestWord) (interface{}, interface{}, interface{}) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..99018cd --- /dev/null +++ b/main.go @@ -0,0 +1,67 @@ +package main + +import ( + "log" + "sync" + + "github.com/censoredplanet/CenFuzz/config" + "github.com/censoredplanet/CenFuzz/util" + "github.com/censoredplanet/CenFuzz/worker" +) + +func WorkerType() worker.Worker { + + switch config.Protocol { + case "https": + return &worker.HTTPSWorker{} + case "http": + return &worker.HTTPWorker{} + default: + panic("unknown protocol") + } + +} + +func main() { + w := WorkerType() + log.Printf("Starting HTTP(S) fuzzers") + inputs := util.ParseInfile(config.Infile) + uncensoredKeyword := config.UncensoredKeyword + outfile := config.Outfile + + ResultsQueue := make(chan *util.Result) + workerdoneChan := make(chan bool) + resultsdoneChan := make(chan bool) + + workQueue := make(chan interface{}) + + fuzzerList := util.ParseFuzzerInfile(config.FuzzerInFile) + fuzzerObjects := w.FuzzerObjects(fuzzerList) + + var workWG sync.WaitGroup + for i := 0; i < config.NumWorkers; i++ { + go w.Worker(workQueue, ResultsQueue, uncensoredKeyword, &workWG, workerdoneChan) + } + log.Printf("Workers spawned") + + go util.SaveResults(ResultsQueue, outfile, resultsdoneChan) + log.Printf("Output routine started") + + for _, input := range inputs { + vp := input.VP + vp.Mu.Lock() + work := w.Work(input.VP.IP, input.Domain, fuzzerObjects) + workWG.Add(1) + workQueue <- work + log.Println("Assigned work to VP: ", input.VP.IP) + vp.Mu.Unlock() + } + close(workQueue) + log.Println("All work assigned. Waiting for workers to return") + workWG.Wait() + <-workerdoneChan + log.Println("Waiting for results to be written") + close(ResultsQueue) + <-resultsdoneChan + +} diff --git a/util/seed.go b/util/seed.go new file mode 100644 index 0000000..d4b93fe --- /dev/null +++ b/util/seed.go @@ -0,0 +1,374 @@ +package util + +import ( + "bytes" + rand2 "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "math/rand" + "net" + "strings" + "time" + + combinations "github.com/mxschmitt/golang-combinations" +) + +func init() { + rand.Seed(time.Now().UTC().UnixNano()) + +} + +func Reverse(s string) string { + runes := []rune(s) + for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 { + runes[i], runes[j] = runes[j], runes[i] + } + return string(runes) +} + +func Repeat(s string, n int) string { + returnString := "" + for i := 0; i < n; i++ { + returnString += s + } + return returnString +} + +func GenerateRandomCapitalizedValues(word string) string { + randomlyCapitalizedWord := "" + for _, char := range word { + if (char < 'a' || char > 'z') && (char < 'A' || char > 'Z') { + randomlyCapitalizedWord += string(char) + continue + } + rand.Seed(time.Now().UTC().UnixNano()) + choice := rand.Intn(2) + if choice == 0 { + randomlyCapitalizedWord += strings.ToLower(string(char)) + } else if choice == 1 { + randomlyCapitalizedWord += strings.ToUpper(string(char)) + } + } + return randomlyCapitalizedWord +} + +func unique(stringSlice []string) []string { + keys := make(map[string]bool) + list := []string{} + for _, entry := range stringSlice { + if _, value := keys[entry]; !value { + keys[entry] = true + list = append(list, entry) + } + } + return list +} + +func CapitalizedPermutations(ip string, op string) []string { + var s []string + if len(ip) == 0 { + return []string{op} + } + lowerChar := strings.ToLower(string(ip[0])) + upperChar := strings.ToUpper(string(ip[0])) + ip = ip[1:len(ip)] + s = append(s, CapitalizedPermutations(ip, op+lowerChar)...) + s = append(s, CapitalizedPermutations(ip, op+upperChar)...) + return unique(s) +} + +//TODO: This currently only works for ASCII characters +func GenerateAllCapitalizedPermutations(word string) []string { + return CapitalizedPermutations(word, "") +} + +func GenerateRandomlyRemovedWord(word string) string { + randomlyRemovedWord := "" + for _, char := range word { + rand.Seed(time.Now().UTC().UnixNano()) + choice := rand.Intn(2) + if choice == 1 { + randomlyRemovedWord += string(char) + } + } + return randomlyRemovedWord +} + +func GenerateAllSubstringPermutations(word string) []string { + splitWord := strings.Split(word, "") + combs := combinations.All(splitWord) + var permutations []string + for _, elem := range combs { + permutations = append(permutations, strings.Join(elem, "")) + } + return permutations +} + +func GenerateAlternatives(alternatives []string) string { + rand.Seed(time.Now().UTC().UnixNano()) + choice := rand.Intn(len(alternatives)) + return alternatives[choice] +} + +func GenerateAllAlternatives(alternatives []string) []string { + return alternatives +} + +func GenerateHostNameRandomPadding() string { + prefixPaddingLength := rand.Intn(5) + suffixPaddingLength := rand.Intn(5) + hostnameWithPadding := strings.Repeat("*", prefixPaddingLength) + hostnameWithPadding += "%s" + hostnameWithPadding += strings.Repeat("*", suffixPaddingLength) + return hostnameWithPadding +} +func GenerateAllHostNamePaddings() []string { + var hostnameWithAllPadding []string + for i := 0; i < 3; i++ { + for j := 0; j < 3; j++ { + hostnameWithPadding := strings.Repeat("*", i) + hostnameWithPadding += "%s" + hostnameWithPadding += strings.Repeat("*", j) + hostnameWithAllPadding = append(hostnameWithAllPadding, hostnameWithPadding) + } + + } + return hostnameWithAllPadding +} + +var GetAlternatives = []string{"POST", "PUT", "PATCH", "DELETE", "XXX", " "} + +func GenerateGetAlternatives() string { + return GenerateAlternatives(GetAlternatives) +} + +func GenerateAllGetAlternatives() []string { + return GenerateAllAlternatives(GetAlternatives) +} + +var HttpAlternatives = []string{"XXXX/1.1", "HTTP/11.1", "HTTP/1.12", "/11.1", "HTTP2", "HTTP3", "HTTP9", "HTTP/2", "HTTP/3", "HTTP/9", " ", "HTTPx/1.1", "HTTP /1.1", "HTTP/ 1.1", "HTTP/1.1x", "HTTP/x1.1"} + +func GenerateHttpAlternatives() string { + return GenerateAlternatives(HttpAlternatives) +} + +func GenerateAllHttpAlternatives() []string { + return GenerateAllAlternatives(HttpAlternatives) +} + +var HostAlternatives = []string{"XXXX: ", "XXXX:", "Host:\r\n", "Hostwww.", "Host:www.", "HostHeader:", " "} + +func GenerateHostAlternatives() string { + return GenerateAlternatives(HostAlternatives) +} + +func GenerateAllHostAlternatives() []string { + return GenerateAllAlternatives(HostAlternatives) +} + +var PathAlternatives = []string{"/ ", " z ", " ? ", " ", " /", "**", " /x", "x/ "} + +func GeneratePathAlternatives() string { + return GenerateAlternatives(PathAlternatives) +} + +func GenerateAllPathAlternatives() []string { + return GenerateAllAlternatives(PathAlternatives) +} + +var HTTPHeaders = []string{"Accept: text/html", "Accept: application/xml", "Accept: text/html,application/xhtml+xml", "Accept: application/json", "Accept: xxx", "Accept-Charset: utf-8", "Accept-Charset: xxx", "Accept-Datetime: Thu, 31 May 2007 20:35:00 GMT", "Accept-Datetime: xxx", "Accept-Encoding: gzip, deflate", "Accept-Encoding: xxx", "Accept-Language: en-US", "Accept-Language: xxx", "Access-Control-Request-Method: GET", "Access-Control-Request-Method: xxx", "Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", "Cache-Control: no-cache", "Cache-Control: xxx", "Connection: keep-alive", "Connection: xxx", "Content-Encoding: gzip", "Content-Encoding: xxx", "Content-Length: 1000", "Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ==", "Content-Type: application/x-www-form-urlencoded", "Content-Type: xxx", "Cookie: $Version=1; Skin=new;", "Cookie: xxx", "Date: Tue, 15 Nov 1994 08:12:31 GMT", "Expect: 100-continue", "Expect: xxx", "From: user@example.com", "If-Match: \"737060cd8c284d8af7ad3082f209582d\"", "If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT", "If-None-Match: \"737060cd8c284d8af7ad3082f209582d]\"", "If-Range: \"737060cd8c284d8af7ad3082f209582d\"", "If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT", "Max-Forwards: 10", "Max-Forwards: xxx", "Origin: http://www.example-xxx.com", "Pragma: no-cache", "Pragma: xxx", "Prefer: return=representation", "Prefer: xxx", "Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", "Range: bytes=500-999", "Referer: http://example-xxx.com", "TE: trailers, deflate", "Trailer: Max-Forwards", "Trailer: xxx", "Transfer-Encoding: chunked", "Transfer-Encoding: xxx", "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/12.0", "User-Agent: xxx", "Upgrade: h2c, HTTPS/1.3, IRC/6.9, RTA/x11, websocket", "Upgrade: xxx", "Via: 1.0 fred, 1.1 example-xxx.com (Apache/1.1)", "Warning: 199 Miscellaneous warning", "Warning: xxx"} + +func GenerateHeaderAlternatives() string { + return GenerateAlternatives(HTTPHeaders) +} + +func GenerateAllHeaderAlternatives() []string { + return GenerateAllAlternatives(HTTPHeaders) +} + +var versionAlternatives = []string{fmt.Sprint(tls.VersionTLS10), fmt.Sprint(tls.VersionTLS11), fmt.Sprint(tls.VersionTLS12), fmt.Sprint(tls.VersionTLS13)} + +func GenerateVersionAlternatives() string { + return GenerateAlternatives(versionAlternatives) +} + +func GenerateAllVersionAlternatives() []string { + return GenerateAllAlternatives(versionAlternatives) +} + +var cipherSuiteAlternatives = []string{fmt.Sprint(tls.TLS_RSA_WITH_RC4_128_SHA), + fmt.Sprint(tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA), + fmt.Sprint(tls.TLS_RSA_WITH_AES_128_CBC_SHA), + fmt.Sprint(tls.TLS_RSA_WITH_AES_256_CBC_SHA), + fmt.Sprint(tls.TLS_RSA_WITH_AES_128_CBC_SHA256), + fmt.Sprint(tls.TLS_RSA_WITH_AES_128_GCM_SHA256), + fmt.Sprint(tls.TLS_RSA_WITH_AES_256_GCM_SHA384), + fmt.Sprint(tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA), + fmt.Sprint(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA), + fmt.Sprint(tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA), + fmt.Sprint(tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA), + fmt.Sprint(tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA), + fmt.Sprint(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA), + fmt.Sprint(tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA), + fmt.Sprint(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256), + fmt.Sprint(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256), + fmt.Sprint(tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), + fmt.Sprint(tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), + fmt.Sprint(tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), + fmt.Sprint(tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), + fmt.Sprint(tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256), + fmt.Sprint(tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), + fmt.Sprint(tls.TLS_AES_128_GCM_SHA256), + fmt.Sprint(tls.TLS_AES_256_GCM_SHA384), + fmt.Sprint(tls.TLS_CHACHA20_POLY1305_SHA256)} + +func GenerateCipherSuiteAlternatives() string { + return GenerateAlternatives(cipherSuiteAlternatives) +} + +func GenerateAllCipherSuiteAlternatives() []string { + return GenerateAllAlternatives(cipherSuiteAlternatives) +} + +func GenerateCertificate(commonname string) ([]byte, []byte, error) { + + ca := &x509.Certificate{ + SerialNumber: big.NewInt(2022), + Subject: pkix.Name{ + Organization: []string{"Internet Measurement"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"Test"}, + StreetAddress: []string{"Test 123"}, + PostalCode: []string{"12345"}, + CommonName: commonname, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + caPrivKey, err := rsa.GenerateKey(rand2.Reader, 4096) + if err != nil { + return nil, nil, err + } + + caBytes, err := x509.CreateCertificate(rand2.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, nil, err + } + + caPEM := new(bytes.Buffer) + pem.Encode(caPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + }) + + caPrivKeyPEM := new(bytes.Buffer) + pem.Encode(caPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), + }) + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{ + Organization: []string{"Internet Measurement"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"Test"}, + StreetAddress: []string{"Test 123"}, + PostalCode: []string{"12345"}, + CommonName: commonname, + }, + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature, + } + + certPrivKey, err := rsa.GenerateKey(rand2.Reader, 4096) + if err != nil { + return nil, nil, err + } + + certBytes, err := x509.CreateCertificate(rand2.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) + if err != nil { + return nil, nil, err + } + + certPEM := new(bytes.Buffer) + pem.Encode(certPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + certPrivKeyPEM := new(bytes.Buffer) + pem.Encode(certPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + + return certPEM.Bytes(), certPrivKeyPEM.Bytes(), nil +} + +var commonnames = []string{"www.test.com", "XXXX", "www.pornhub.com"} + +func GenerateCertificateAlternatives() string { + return GenerateAlternatives(commonnames) +} + +func GenerateAllCertificateAlternatives() []string { + return GenerateAllAlternatives(commonnames) +} + +var servernames = []string{"%s|omit", "%s|empty", "%s|repeat|2", "%s|repeat|3", "%s|reverse"} + +func GenerateServerNameAlternatives() string { + return GenerateAlternatives(servernames) +} + +func GenerateAllServerNameAlternatives() []string { + return GenerateAllAlternatives(servernames) +} + +//https://azbigmedia.com/business/here-are-2021s-most-popular-tlds-and-domain-registration-trends/ +var TLDs = []string{"%s|tld|com", "%s|tld|xyz", "%s|tld|net", "%s|tld|club", "%s|tld|me", "%s|tld|org", "%s|tld|co", "%s|tld|shop", "%s|tld|info", "%s|tld|live"} + +func GenerateTLDAlternatives() string { + return GenerateAlternatives(TLDs) +} + +func GenerateAllTLDAlternatives() []string { + return GenerateAllAlternatives(TLDs) +} + +//https://securitytrails.com/blog/most-popular-subdomains-mx-records#:~:text=As%20you%20can%20see%2C%20the,forums%2C%20wiki%2C%20community). +var Subdomains = []string{"%s|subdomain|www", "%s|subdomain|mail", "%s|subdomain|forum", "%s|subdomain|m", "%s|subdomain|blog", "%s|subdomain|shop", "%s|subdomain|forums", "%s|subdomain|wiki", "%s|subdomain|community", "%s|subdomain|ww1"} + +func GenerateSubdomainsAlternatives() string { + return GenerateAlternatives(Subdomains) +} + +func GenerateAllSubdomainsAlternatives() []string { + return GenerateAllAlternatives(Subdomains) +} + +var hostnames = []string{"%s|omit", "%s|empty", "%s|repeat|2", "%s|repeat|3", "%s|reverse"} + +func GenerateHostNameAlternatives() string { + return GenerateAlternatives(servernames) +} + +func GenerateAllHostNameAlternatives() []string { + return GenerateAllAlternatives(servernames) +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..f46df85 --- /dev/null +++ b/util/util.go @@ -0,0 +1,152 @@ +package util + +import ( + "bufio" + "encoding/json" + "log" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/censoredplanet/CenFuzz/config" +) + +type VantagePoint struct { + IP string + Mu sync.Mutex +} + +type Input struct { + //Vantage Point IP + VP *VantagePoint + //Domain or Keyword to test for censorship + Domain string +} + +type Result struct { + IP string `json:"IP"` + Domain string `json:"Domain"` + TestName string `json:"TestName"` + IsNormal bool `json:"IsNormal"` + MatchesNormal bool `json:"MatchesNormal"` + MatchesUncensored bool `json:"MatchesUncensored"` + NormalDifferences string `json:"NormalDifferences"` + UncensoredDifferences string `json:"UncensoredDifferences"` + Request interface{} `json:"Request"` + Response interface{} `json:"Response"` + Error interface{} `json:"Error"` + UncensoredRequest interface{} `json:"UncensoredRequest"` + UncensoredResponse interface{} `json:"UncensoredResponse"` + UncensoredError interface{} `json:"UncensoredError"` + StartTime time.Time `json:"StartTime"` + EndTime time.Time `json:"EndTime"` +} + +type TLSdata struct { + Version uint16 + HandshakeComplete bool + CipherSuite uint16 + NegotiatedProtocol string + NegotiatedProtocolIsMutual bool + PeerCertificates []byte + ServerName string + HTTPResponse interface{} +} + +func CreateFile(path string) *os.File { + if path == "-" || path == "" { + return os.Stdout + } + file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0755) + if err != nil { + log.Fatal(err) + } + return file +} + +func OpenFileforRead(path string) *os.File { + infile, err := os.OpenFile(path, os.O_RDONLY, 0444) + if err != nil { + log.Fatal(err) + } + return infile +} + +func SaveResults(Results <-chan *Result, outfile string, done chan<- bool) { + resultsFile := CreateFile(outfile) + for result := range Results { + data, err := json.Marshal(result) + if err != nil { + log.Println(err.Error()) + continue + } + data = append(data, byte('\n')) + n, err := resultsFile.Write(data) + if err != nil || n != len(data) { + log.Println(err.Error()) + } + } + done <- true + +} + +func Sleep(err interface{}) time.Duration { + if err != nil { + //If there is an error, there is a possibility that this could be network interference or at least some temporary network loss. So we should wait for more time + return config.StatefulDelay + } + + return config.FuzzDelay +} + +func ParseInfile(path string) []*Input { + infile := OpenFileforRead(path) + scanner := bufio.NewScanner(infile) + var inputs []*Input + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, ",") + vp := &VantagePoint{IP: parts[0]} + inputs = append(inputs, &Input{ + VP: vp, + Domain: parts[1], + }) + } + return inputs +} + +type FuzzerInput struct { + FuzzerNumber int + All bool +} + +func ParseFuzzerInfile(path string) []*FuzzerInput { + infile := OpenFileforRead(path) + scanner := bufio.NewScanner(infile) + var fuzzers []*FuzzerInput + for scanner.Scan() { + line := scanner.Text() + parts := strings.Split(line, ",") + fuzzerNumber, err := strconv.Atoi(parts[0]) + if err != nil { + log.Fatal(err) + } + all := false + if len(parts) > 1 { + all, err = strconv.ParseBool(parts[1]) + if err != nil { + log.Fatal(err) + } + } else { + all = config.All + } + fuzzers = append(fuzzers, &FuzzerInput{ + FuzzerNumber: fuzzerNumber, + All: all, + }) + } + + return fuzzers +} diff --git a/worker/http_worker.go b/worker/http_worker.go new file mode 100644 index 0000000..84d6193 --- /dev/null +++ b/worker/http_worker.go @@ -0,0 +1,312 @@ +package worker + +import ( + "fmt" + "log" + "sync" + "time" + + "github.com/censoredplanet/CenFuzz/http_fuzzer" + "github.com/censoredplanet/CenFuzz/util" +) + +type HTTPWorker struct{} + +func (f FuzzerSpec) HTTPFuzzerInterface() http_fuzzer.Fuzzer { + switch f.Fuzzer() { + case 1: + return &http_fuzzer.HostnamePadding{} + case 2: + return &http_fuzzer.GetWordCapitalize{} + case 3: + return &http_fuzzer.GetWordRemove{} + case 4: + return &http_fuzzer.GetWordAlternate{} + case 5: + return &http_fuzzer.HttpWordCapitalize{} + case 6: + return &http_fuzzer.HttpWordRemove{} + case 7: + return &http_fuzzer.HttpWordAlternate{} + case 8: + return &http_fuzzer.HostWordCapitalize{} + case 9: + return &http_fuzzer.HostWordRemove{} + case 10: + return &http_fuzzer.HostWordAlternate{} + case 11: + return &http_fuzzer.HttpDelimiterWordRemove{} + case 12: + return &http_fuzzer.PathAlternate{} + case 13: + return &http_fuzzer.HeaderAlternate{} + case 14: + return &http_fuzzer.HostNameAlternate{} + case 15: + return &http_fuzzer.HostnameTLDAlternate{} + case 16: + return &http_fuzzer.HostnameSubdomainsAlternate{} + default: + panic("unknown fuzzer") + } +} + +func HTTPFuzzerMapping(fuzzer int) string { + switch fuzzer { + case 1: + return "Hostname Padding" + case 2: + return "Get Word | Capitalize" + case 3: + return "Get Word | Remove" + case 4: + return "Get Word | Alternate" + case 5: + return "Http Word | Capitalize" + case 6: + return "Http Word | Remove" + case 7: + return "Http Word | Alternate" + case 8: + return "Host Word | Capitalize" + case 9: + return "Host Word | Remove" + case 10: + return "Host Word | Alternate" + case 11: + return "Http Delimiter | Remove" + case 12: + return "Path | Alternate" + case 13: + return "Header | Alternate" + case 14: + return "Hostname Alternate" + case 15: + return "Hostname TLD Alternate" + case 16: + return "Hostname Subdomain Alternate" + default: + return "NA" + } +} + +type HTTPFuzzerObject struct { + TestName string + Spec FuzzerSpec + RequestWords []*http_fuzzer.RequestWord +} + +//Using a separate struct to assign work instead of just the input, +//since in the future we may want to assign different work for each vantage point +type HTTPWork struct { + IP string + Domain string + Fuzzers []*HTTPFuzzerObject +} + +func (h *HTTPWorker) Work(ip string, domain string, fuzzers interface{}) interface{} { + return &HTTPWork{ + IP: ip, + Domain: domain, + Fuzzers: fuzzers.([]*HTTPFuzzerObject), + } +} + +func (h *HTTPWorker) FuzzerObjects(fuzzerList []*util.FuzzerInput) interface{} { + var fuzzerObjects []*HTTPFuzzerObject + for _, fuzzerStruct := range fuzzerList { + + fuzzerspec := FuzzerSpec(fuzzerStruct.FuzzerNumber) + fuzzerName := HTTPFuzzerMapping(fuzzerStruct.FuzzerNumber) + if fuzzerName == "NA" { + log.Println("[HTTPWorker.FuzzerObjects] WARNING: Fuzzer not available: ", fuzzerStruct.FuzzerNumber) + continue + } + requestWords := fuzzerspec.HTTPFuzzerInterface().Init(fuzzerStruct.All) + + fuzzerObjects = append(fuzzerObjects, &HTTPFuzzerObject{ + TestName: fuzzerName, + Spec: fuzzerspec, + RequestWords: requestWords, + }) + } + return fuzzerObjects + +} + +func (h *HTTPWorker) GenerateTemplate(response interface{}, keyword string) interface{} { + if response == nil { + return nil + } + filterDomain := newDomainFilter(keyword) + filterBody := func(body string) string { + body = timestampRegex.ReplaceAllString(body, TimestampReplacmentMarker) + body = akamaiRegex.ReplaceAllString(body, AkamiIdReplacementMarker) + return filterDomain(body) + } + + return filterBody(response.(string)) +} + +//TODO: there are more efficient ways of doing this than going through the list twice, but this will do for now +func (h *HTTPWorker) MatchesControl(results []*util.Result) []*util.Result { + var normalResponse interface{} + var normalError interface{} + + for _, result := range results { + if result.IsNormal == true { + normalResponse = h.GenerateTemplate(result.Response, result.Domain) + normalError = result.Error + } + } + for _, result := range results { + normalDifferences := "" + uncensoredDifferences := "" + resultResponseTemplate := h.GenerateTemplate(result.Response, result.Domain) + uncensoredResponseTemplate := h.GenerateTemplate(result.UncensoredResponse, result.Domain) + if resultResponseTemplate == normalResponse && result.Error == normalError { + result.MatchesNormal = true + } else { + if resultResponseTemplate == nil && normalResponse != nil { + normalDifferences += "No expected response;" + } + if result.Error == nil && normalError != nil { + normalDifferences += "No expected error;" + } + if normalResponse != nil && (resultResponseTemplate != normalResponse) { + normalDifferences += "Different response;" + } + if normalError != nil && (result.Error != normalError) { + normalDifferences += "Different error;" + } + result.MatchesNormal = false + result.NormalDifferences = normalDifferences + } + + if resultResponseTemplate == uncensoredResponseTemplate && result.Error == result.UncensoredError { + result.MatchesUncensored = true + } else { + if resultResponseTemplate == nil && uncensoredResponseTemplate != nil { + uncensoredDifferences += "No expected response;" + } + if result.Error == nil && result.UncensoredError != nil { + uncensoredDifferences += "No expected error;" + } + if uncensoredResponseTemplate != nil && (resultResponseTemplate != uncensoredResponseTemplate) { + uncensoredDifferences += "Different response;" + } + if result.UncensoredError != nil && (result.Error != result.UncensoredError) { + uncensoredDifferences += "Different error;" + } + result.MatchesUncensored = false + result.UncensoredDifferences = uncensoredDifferences + } + + } + return results +} + +func (h *HTTPWorker) SendResults(results []*util.Result, ResultsQueue chan<- *util.Result) { + annotatedResults := h.MatchesControl(results) + for _, result := range annotatedResults { + ResultsQueue <- result + } + +} + +func (h *HTTPWorker) Worker(workQueue <-chan interface{}, resultQueue chan<- *util.Result, uncensoredDomain string, wg *sync.WaitGroup, done chan<- bool) { + for w := range workQueue { + work := w.(*HTTPWork) + var results []*util.Result + + //Uncensored Normal + startTime := time.Now() + uncensoredRequest, uncensoredResponse, uncensoredError := http_fuzzer.MakeConnection(work.IP, uncensoredDomain, http_fuzzer.RequestWord{Hostname: uncensoredDomain}) + time.Sleep(util.Sleep(uncensoredError)) + //Censored Normal + censoredRequest, censoredResponse, censoredError := http_fuzzer.MakeConnection(work.IP, work.Domain, http_fuzzer.RequestWord{Hostname: work.Domain}) + time.Sleep(util.Sleep(censoredError)) + //We're including the sleep time in endtime because that's the whole time taken for this one measurement. Could do it the other way also. + endTime := time.Now() + //Add normal results + results = append(results, &util.Result{ + IP: work.IP, + Domain: work.Domain, + TestName: "Normal", + IsNormal: true, + Request: censoredRequest, + Response: censoredResponse, + Error: censoredError, + UncensoredRequest: uncensoredRequest, + UncensoredResponse: uncensoredResponse, + UncensoredError: uncensoredError, + StartTime: startTime, + EndTime: endTime, + }) + + if Break(censoredError) && Break(uncensoredError) { + h.SendResults(results, resultQueue) + wg.Done() + continue + } + var breakFlag bool + for _, fuzzerObject := range work.Fuzzers { + breakFlag = false + for _, requestWord := range fuzzerObject.RequestWords { + //Uncensored Test + //Create copy + uncensoredRequestWord := requestWord.Hostname + censoredRequestWord := requestWord.Hostname + formattedUncensoredDomain := fmt.Sprintf(uncensoredRequestWord, uncensoredDomain) + startTime = time.Now() + uncensoredRequest, uncensoredResponse, uncensoredErr := fuzzerObject.Spec.HTTPFuzzerInterface().Fuzz(work.IP, work.Domain, http_fuzzer.RequestWord{ + Hostname: formattedUncensoredDomain, + GetWord: requestWord.GetWord, + HttpWord: requestWord.HttpWord, + HostWord: requestWord.HostWord, + HttpDelimiterWord: requestWord.HttpDelimiterWord, + Path: requestWord.Path, + Header: requestWord.Header, + }) + time.Sleep(util.Sleep(uncensoredErr)) + formattedCensoredDomain := fmt.Sprintf(censoredRequestWord, work.Domain) + censoredRequest, censoredResponse, censoredErr := fuzzerObject.Spec.HTTPFuzzerInterface().Fuzz(work.IP, work.Domain, http_fuzzer.RequestWord{ + Hostname: formattedCensoredDomain, + GetWord: requestWord.GetWord, + HttpWord: requestWord.HttpWord, + HostWord: requestWord.HostWord, + HttpDelimiterWord: requestWord.HttpDelimiterWord, + Path: requestWord.Path, + Header: requestWord.Header, + }) + time.Sleep(util.Sleep(censoredErr)) + endTime = time.Now() + results = append(results, &util.Result{ + IP: work.IP, + Domain: work.Domain, + TestName: fuzzerObject.TestName, + IsNormal: false, + Request: censoredRequest, + Response: censoredResponse, + Error: censoredErr, + UncensoredRequest: uncensoredRequest, + UncensoredResponse: uncensoredResponse, + UncensoredError: uncensoredErr, + StartTime: startTime, + EndTime: endTime, + }) + if Break(censoredError) && Break(uncensoredError) { + breakFlag = true + break + } + } + if breakFlag { + break + } + } + h.SendResults(results, resultQueue) + wg.Done() + } + done <- true + +} diff --git a/worker/https_worker.go b/worker/https_worker.go new file mode 100644 index 0000000..9d9404f --- /dev/null +++ b/worker/https_worker.go @@ -0,0 +1,321 @@ +package worker + +import ( + "fmt" + "log" + "sync" + "time" + + "github.com/censoredplanet/CenFuzz/https_fuzzer" + "github.com/censoredplanet/CenFuzz/util" + "github.com/google/go-cmp/cmp" +) + +func HTTPSFuzzerMapping(fuzzer int) string { + switch fuzzer { + case 1: + return "SNI Padding" + case 2: + return "Min Version Alternate" + case 3: + return "Max Version Alternate" + case 4: + return "CipherSuite Alternate" + case 5: + return "Client Certificate Alternate" + case 6: + return "SNI Alternate" + case 7: + return "SNI TLD Alternate" + case 8: + return "SNI Subdomain Alternate" + default: + return "NA" + } +} + +type HTTPSWorker struct{} + +func (f FuzzerSpec) HTTPSFuzzerInterface() https_fuzzer.Fuzzer { + switch f.Fuzzer() { + case 1: + return &https_fuzzer.ServernamePadding{} + case 2: + return &https_fuzzer.MinVersionAlternate{} + case 3: + return &https_fuzzer.MaxversionAlternate{} + case 4: + return &https_fuzzer.CipherSuiteAlternate{} + case 5: + return &https_fuzzer.ClientCertAlternate{} + case 6: + return &https_fuzzer.ServernameAlternate{} + case 7: + return &https_fuzzer.ServernameTLDAlternate{} + case 8: + return &https_fuzzer.ServernameSubdomainsAlternate{} + default: + panic("unknown fuzzer") + } +} + +type HTTPSFuzzerObject struct { + TestName string + Spec FuzzerSpec + RequestWords []*https_fuzzer.RequestWord +} + +type HTTPSWork struct { + IP string + Domain string + Fuzzers []*HTTPSFuzzerObject +} + +func (h *HTTPSWorker) Work(ip string, domain string, fuzzers interface{}) interface{} { + return &HTTPSWork{ + IP: ip, + Domain: domain, + Fuzzers: fuzzers.([]*HTTPSFuzzerObject), + } +} + +func (h *HTTPSWorker) FuzzerObjects(fuzzerList []*util.FuzzerInput) interface{} { + var fuzzerObjects []*HTTPSFuzzerObject + for _, fuzzerStruct := range fuzzerList { + + fuzzerspec := FuzzerSpec(fuzzerStruct.FuzzerNumber) + fuzzerName := HTTPSFuzzerMapping(fuzzerStruct.FuzzerNumber) + if fuzzerName == "NA" { + log.Println("[HTTPSWorker.FuzzerObjects] WARNING: Fuzzer not available: ", fuzzerStruct.FuzzerNumber) + continue + } + requestWords := fuzzerspec.HTTPSFuzzerInterface().Init(fuzzerStruct.All) + + fuzzerObjects = append(fuzzerObjects, &HTTPSFuzzerObject{ + TestName: fuzzerName, + Spec: fuzzerspec, + RequestWords: requestWords, + }) + } + return fuzzerObjects + +} + +func (h *HTTPSWorker) GenerateTemplate(response interface{}, keyword string) interface{} { + if response == nil { + return nil + } + tlsResponse := response.(*util.TLSdata) + filterDomain := newDomainFilter(keyword) + filterBody := func(body string) string { + body = timestampRegex.ReplaceAllString(body, TimestampReplacmentMarker) + body = akamaiRegex.ReplaceAllString(body, AkamiIdReplacementMarker) + return filterDomain(body) + } + + returnResponse := tlsResponse + returnResponse.HTTPResponse = filterBody(tlsResponse.HTTPResponse.(string)) + return returnResponse +} + +//TODO: there are more efficient ways of doing this than going through the list twice, but this will do for now +func (h *HTTPSWorker) MatchesControl(results []*util.Result) []*util.Result { + var normalResponse interface{} + var normalError interface{} + + for _, result := range results { + if result.IsNormal == true { + normalResponse = h.GenerateTemplate(result.Response, result.Domain) + normalError = result.Error + } + } + for _, result := range results { + normalDifferences := "" + uncensoredDifferences := "" + resultResponseTemplate := h.GenerateTemplate(result.Response, result.Domain) + uncensoredResponseTemplate := h.GenerateTemplate(result.UncensoredResponse, result.Domain) + + var normalResponseObject *util.TLSdata + var resultResponseTemplateObject *util.TLSdata + var uncensoredResponseTemplateObject *util.TLSdata + if normalResponse != nil { + normalResponseObject = normalResponse.(*util.TLSdata) + } + if resultResponseTemplate != nil { + resultResponseTemplateObject = resultResponseTemplate.(*util.TLSdata) + } + if uncensoredResponseTemplate != nil { + uncensoredResponseTemplateObject = uncensoredResponseTemplate.(*util.TLSdata) + } + + if (resultResponseTemplateObject != nil && normalResponseObject != nil) && cmp.Equal(resultResponseTemplateObject, normalResponseObject) && result.Error == normalError { + result.MatchesNormal = true + } else { + if resultResponseTemplateObject == nil { + normalDifferences += "Empty censored response;" + } + if normalResponseObject == nil { + normalDifferences += "Empty normal response;" //Should never happen, since we error out earlier if we get a dial error in normal query. Keeping this here just for error handling in case of exception + } + if resultResponseTemplateObject != nil && normalResponseObject != nil { + //TODO: Will first if statement here ever happen? + if resultResponseTemplateObject.Version == 0 && normalResponseObject.Version != 0 { + normalDifferences += "No expected response;" + } + if result.Error == nil && normalError != nil { + normalDifferences += "No expected error;" + } + if normalResponseObject.Version != 0 && (resultResponseTemplateObject.Version != normalResponseObject.Version) { + normalDifferences += "Different version;" + } + if normalResponseObject.CipherSuite != 0 && (resultResponseTemplateObject.CipherSuite != normalResponseObject.CipherSuite) { + normalDifferences += "Different ciphersuite;" + } + if normalResponseObject.PeerCertificates != nil && (string(resultResponseTemplateObject.PeerCertificates) != string(normalResponseObject.PeerCertificates)) { + normalDifferences += "Different certificate;" + } + if normalError != nil && (result.Error != normalError) { + normalDifferences += "Different error;" + } + } + result.MatchesNormal = false + } + result.NormalDifferences = normalDifferences + + if (resultResponseTemplate != nil && resultResponseTemplateObject != nil) && cmp.Equal(resultResponseTemplateObject, uncensoredResponseTemplateObject) && result.Error == result.UncensoredError { + result.MatchesUncensored = true + } else { + if resultResponseTemplateObject == nil { + uncensoredDifferences += "Empty censored response;" + result.MatchesUncensored = false + } + if uncensoredResponseTemplateObject == nil { + uncensoredDifferences += "Empty uncensored response;" + result.MatchesUncensored = false + } + if resultResponseTemplateObject != nil && uncensoredResponseTemplateObject != nil { + if resultResponseTemplateObject.Version == 0 && uncensoredResponseTemplateObject.Version != 0 { + uncensoredDifferences += "No expected response;" + } + if result.Error == nil && result.UncensoredError != nil { + uncensoredDifferences += "No expected error;" + } + if uncensoredResponseTemplateObject.Version != 0 && (resultResponseTemplateObject.Version != uncensoredResponseTemplateObject.Version) { + uncensoredDifferences += "Different version;" + } + if uncensoredResponseTemplateObject.CipherSuite != 0 && (resultResponseTemplateObject.CipherSuite != uncensoredResponseTemplateObject.CipherSuite) { + uncensoredDifferences += "Different ciphersuite;" + } + if uncensoredResponseTemplateObject.PeerCertificates != nil && (string(resultResponseTemplateObject.PeerCertificates) != string(uncensoredResponseTemplateObject.PeerCertificates)) { + uncensoredDifferences += "Different certificate;" + } + if result.UncensoredError != nil && (result.Error != result.UncensoredError) { + uncensoredDifferences += "Different error;" + } + } + //NOTE: Taking a call here to mark this as false even if censored and uncensored are both empty + result.MatchesUncensored = false + } + result.UncensoredDifferences = uncensoredDifferences + } + return results +} + +func (h *HTTPSWorker) SendResults(results []*util.Result, ResultsQueue chan<- *util.Result) { + annotatedResults := h.MatchesControl(results) + for _, result := range annotatedResults { + ResultsQueue <- result + } + +} + +func (h *HTTPSWorker) Worker(workQueue <-chan interface{}, resultQueue chan<- *util.Result, uncensoredDomain string, wg *sync.WaitGroup, done chan<- bool) { + for w := range workQueue { + work := w.(*HTTPSWork) + var results []*util.Result + + //Uncensored Normal + startTime := time.Now() + uncensoredRequest, uncensoredResponse, uncensoredError := https_fuzzer.MakeConnection(work.IP, uncensoredDomain, https_fuzzer.RequestWord{Servername: uncensoredDomain}) + time.Sleep(util.Sleep(uncensoredError)) + //Censored Normal + censoredRequest, censoredResponse, censoredError := https_fuzzer.MakeConnection(work.IP, work.Domain, https_fuzzer.RequestWord{Servername: work.Domain}) + time.Sleep(util.Sleep(censoredError)) + endTime := time.Now() + //Add normal results + results = append(results, &util.Result{ + IP: work.IP, + Domain: work.Domain, + TestName: "Normal", + IsNormal: true, + Request: censoredRequest, + Response: censoredResponse, + Error: censoredError, + UncensoredRequest: uncensoredRequest, + UncensoredResponse: uncensoredResponse, + UncensoredError: uncensoredError, + StartTime: startTime, + EndTime: endTime, + }) + if Break(censoredError) && Break(uncensoredError) { + h.SendResults(results, resultQueue) + wg.Done() + continue + } + var breakFlag bool + for _, fuzzerObject := range work.Fuzzers { + breakFlag = false + for _, requestWord := range fuzzerObject.RequestWords { + //Uncensored Test + //Create copy + uncensoredRequestWord := requestWord.Servername + censoredRequestWord := requestWord.Servername + formattedUncensoredDomain := fmt.Sprintf(uncensoredRequestWord, uncensoredDomain) + startTime = time.Now() + uncensoredRequest, uncensoredResponse, uncensoredErr := fuzzerObject.Spec.HTTPSFuzzerInterface().Fuzz(work.IP, work.Domain, https_fuzzer.RequestWord{ + Servername: formattedUncensoredDomain, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + CipherSuites: requestWord.CipherSuites, + Certificate: requestWord.Certificate, + }) + time.Sleep(util.Sleep(uncensoredErr)) + formattedCensoredDomain := fmt.Sprintf(censoredRequestWord, work.Domain) + censoredRequest, censoredResponse, censoredErr := fuzzerObject.Spec.HTTPSFuzzerInterface().Fuzz(work.IP, work.Domain, https_fuzzer.RequestWord{ + Servername: formattedCensoredDomain, + MinVersion: requestWord.MinVersion, + MaxVersion: requestWord.MaxVersion, + CipherSuites: requestWord.CipherSuites, + Certificate: requestWord.Certificate, + }) + time.Sleep(util.Sleep(censoredErr)) + endTime = time.Now() + results = append(results, &util.Result{ + IP: work.IP, + Domain: work.Domain, + TestName: fuzzerObject.TestName, + IsNormal: false, + Request: censoredRequest, + Response: censoredResponse, + Error: censoredErr, + UncensoredRequest: uncensoredRequest, + UncensoredResponse: uncensoredResponse, + UncensoredError: uncensoredErr, + StartTime: startTime, + EndTime: endTime, + }) + if Break(censoredError) && Break(uncensoredError) { + breakFlag = true + break + } + } + if breakFlag { + break + } + } + h.SendResults(results, resultQueue) + wg.Done() + } + done <- true + +} diff --git a/worker/worker.go b/worker/worker.go new file mode 100644 index 0000000..1134ab8 --- /dev/null +++ b/worker/worker.go @@ -0,0 +1,85 @@ +package worker + +import ( + "regexp" + "strings" + "sync" + + "github.com/censoredplanet/CenFuzz/util" +) + +type FuzzerSpec int64 + +func (f FuzzerSpec) Fuzzer() int { + return int(f) +} +func Break(err interface{}) bool { + if err == "Dial" { + return true + } + return false +} + +// Returns version of domain starting with label "www" +func WwwDomainVersion(domain string) string { + if strings.HasPrefix(domain, "www.") { + return domain + } else { + return "www." + domain + } +} + +// Returns version of domain with the starting label "www" +func NonWwwDomainVersion(domain string) string { + if strings.HasPrefix(domain, "www.") { + return domain[len("www."):] + } else { + return domain + } +} + +// Markers for replacing occurences of the request domain, timestamps, and +// ids in responses when generating templates. The motivation for replacing +// predictable variable response elements with a marker instead of deleting them +// is to allow the frequency of occurances to be counted. +// +// Because the marker contains a random string it is unlikely actual response +// content will be confused with it. +const ReplacementMarkerSuffix = "-FUZZER-6B6LwyGe4cHLccMAfNYVbQ]" +const DomainReplacmentMarker = "[DOMAIN" + ReplacementMarkerSuffix +const TimestampReplacmentMarker = "[TIMESTAMP" + ReplacementMarkerSuffix +const AkamiIdReplacementMarker = "[AKAMAI_ID" + ReplacementMarkerSuffix + +const timestampFormat = "[0-2][0-9]:[0-9][0-9]:[0-9][0-9]" + +var timestampRegex = regexp.MustCompile(timestampFormat) + +// I could not find a published list of Akami's IP address ranges. To identify +// vantage points which belong to Akami we can search for the AkamaiGHost +// Reference id. +var akamaiRegex = regexp.MustCompile("Reference #[^\n]*\n") + +// Returns a function which replaces www and non-www forms a domain with a +// marker. +func newDomainFilter(domain string) func(string) string { + wwwDomainFmt := "(?i)" + regexp.QuoteMeta(WwwDomainVersion(domain)) + domainFmt := "(?i)" + regexp.QuoteMeta(NonWwwDomainVersion(domain)) + wwwDomainRegex := regexp.MustCompile(wwwDomainFmt) + domainRegex := regexp.MustCompile(domainFmt) + return func(text string) string { + // Because the non-www version of a domain is a suffix of the www + // version, the www version must be replaced first. Otherwise the "www." + // would remain. + r := wwwDomainRegex.ReplaceAllString(text, DomainReplacmentMarker) + return domainRegex.ReplaceAllString(r, DomainReplacmentMarker) + } +} + +type Worker interface { + FuzzerObjects(fuzzerList []*util.FuzzerInput) interface{} + Work(ip string, domain string, requestWord interface{}) interface{} + GenerateTemplate(response interface{}, keyword string) interface{} + MatchesControl(results []*util.Result) []*util.Result + SendResults(results []*util.Result, ResultsQueue chan<- *util.Result) + Worker(workQueue <-chan interface{}, resultQueue chan<- *util.Result, uncensoredDomain string, wg *sync.WaitGroup, done chan<- bool) +}