├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── issue-report.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── release.yml └── workflows │ ├── build-test.yml │ ├── codeql-analysis.yml │ ├── dep-auto-merge.yml │ ├── dockerhub-push.yml │ ├── lint-test.yml │ ├── release-binary.yml │ └── release-test.yml ├── .gitignore ├── .goreleaser.yml ├── Dockerfile ├── LICENSE.MD ├── Makefile ├── README.md ├── THANKS.md ├── cmd ├── mitmrelay │ └── mitmrelay.go ├── proxify │ └── proxify.go ├── replay │ └── replay.go └── swaggergen │ ├── README.md │ └── swaggergen.go ├── go.mod ├── go.sum ├── internal └── runner │ ├── banner.go │ ├── doc.go │ ├── options.go │ └── runner.go ├── pkg ├── certs │ └── mitm.go ├── logger │ ├── elastic │ │ └── elasticsearch.go │ ├── file │ │ └── file.go │ ├── jsonl │ │ └── jsonl.go │ ├── kafka │ │ └── kafka.go │ ├── logger.go │ ├── options.go │ ├── writer.go │ └── yaml │ │ └── yaml.go ├── swaggergen │ ├── content.go │ ├── info.go │ ├── method.go │ ├── parameter.go │ ├── path.go │ ├── requestresponse.go │ ├── response.go │ ├── schema.go │ ├── servers.go │ └── spec.go ├── types │ ├── userdata.go │ └── verbosity.go └── util │ └── util.go ├── proxy.go ├── socket.go └── static ├── coding.png ├── index.html ├── pd-favicon.ico ├── pd-logo.png ├── proxify-logo-white.png ├── proxify-logo.png └── proxify-run.png /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Ask an question / advise on using proxify 5 | url: https://github.com/projectdiscovery/proxify/discussions/categories/q-a 6 | about: Ask a question or request support for using proxify 7 | 8 | - name: Share idea / feature to discuss for proxify 9 | url: https://github.com/projectdiscovery/proxify/discussions/categories/ideas 10 | about: Share idea / feature to discuss for proxify 11 | 12 | - name: Connect with PD Team (Discord) 13 | url: https://discord.gg/projectdiscovery 14 | about: Connect with PD Team for direct communication -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request feature to implement in this project 4 | labels: 'Type: Enhancement' 5 | --- 6 | 7 | 13 | 14 | ### Please describe your feature request: 15 | 16 | 17 | ### Describe the use case of this feature: 18 | 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue report 3 | about: Create a report to help us to improve the project 4 | labels: 'Type: Bug' 5 | 6 | --- 7 | 8 | 13 | 14 | 15 | 16 | ### Proxify version: 17 | 18 | 19 | 20 | 21 | ### Current Behavior: 22 | 23 | 24 | ### Expected Behavior: 25 | 26 | 27 | ### Steps To Reproduce: 28 | 33 | 34 | 35 | ### Anything else: 36 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed changes 2 | 3 | 6 | 7 | 8 | ## Checklist 9 | 10 | 11 | 12 | - [ ] Pull request is created against the [dev](https://github.com/projectdiscovery/proxify/tree/dev) branch 13 | - [ ] All checks passed (lint, unit/integration/regression tests etc.) with my changes 14 | - [ ] I have added tests that prove my fix is effective or that my feature works 15 | - [ ] I have added necessary documentation (if appropriate) -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | target-branch: "dev" 13 | commit-message: 14 | prefix: "chore" 15 | include: "scope" 16 | allow: 17 | - dependency-name: "github.com/projectdiscovery/*" 18 | groups: 19 | modules: 20 | patterns: ["github.com/projectdiscovery/*"] 21 | labels: 22 | - "Type: Maintenance" 23 | 24 | - package-ecosystem: "github-actions" 25 | directory: "/" 26 | schedule: 27 | interval: "weekly" 28 | target-branch: "dev" 29 | commit-message: 30 | prefix: "chore" 31 | include: "scope" 32 | groups: 33 | workflows: 34 | patterns: ["*"] 35 | exclude-patterns: ["projectdiscovery/actions/*"] 36 | labels: 37 | - "Type: Maintenance" 38 | 39 | # # Maintain dependencies for GitHub Actions 40 | # - package-ecosystem: "github-actions" 41 | # directory: "/" 42 | # schedule: 43 | # interval: "weekly" 44 | # target-branch: "dev" 45 | # commit-message: 46 | # prefix: "chore" 47 | # include: "scope" 48 | # labels: 49 | # - "Type: Maintenance" 50 | # 51 | # # Maintain dependencies for docker 52 | # - package-ecosystem: "docker" 53 | # directory: "/" 54 | # schedule: 55 | # interval: "weekly" 56 | # target-branch: "dev" 57 | # commit-message: 58 | # prefix: "chore" 59 | # include: "scope" 60 | # labels: 61 | # - "Type: Maintenance" 62 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | authors: 4 | - dependabot 5 | categories: 6 | - title: 🎉 Features 7 | labels: 8 | - "Type: Enhancement" 9 | - title: 🐞 Bugs 10 | labels: 11 | - "Type: Bug" 12 | - title: 🔨 Maintenance 13 | labels: 14 | - "Type: Maintenance" 15 | - title: Other Changes 16 | labels: 17 | - "*" -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: 🔨 Build Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | - '**.mod' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | name: Test Builds 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macOS-latest] 17 | steps: 18 | - name: Set up Go 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: 1.21.x 22 | 23 | - name: Check out code 24 | uses: actions/checkout@v3 25 | 26 | - name: Build 27 | run: go build . 28 | working-directory: cmd/proxify/ 29 | 30 | - name: Test 31 | run: go test ./... 32 | 33 | # Todo 34 | # - name: Integration Tests 35 | # env: 36 | # GH_ACTION: true 37 | # run: bash run.sh 38 | # working-directory: integration_tests/ 39 | 40 | - name: Race Condition Tests 41 | run: go build -race . 42 | working-directory: cmd/proxify/ 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: 🚨 CodeQL Analysis 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | paths: 7 | - '**.go' 8 | - '**.mod' 9 | branches: 10 | - dev 11 | 12 | jobs: 13 | analyze: 14 | name: Analyze 15 | runs-on: ubuntu-latest 16 | permissions: 17 | actions: read 18 | contents: read 19 | security-events: write 20 | 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | language: [ 'go' ] 25 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 26 | 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v3 30 | 31 | # Initializes the CodeQL tools for scanning. 32 | - name: Initialize CodeQL 33 | uses: github/codeql-action/init@v2 34 | with: 35 | languages: ${{ matrix.language }} 36 | 37 | - name: Autobuild 38 | uses: github/codeql-action/autobuild@v2 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v2 -------------------------------------------------------------------------------- /.github/workflows/dep-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: 🤖 dep auto merge 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - dev 7 | workflow_dispatch: 8 | 9 | permissions: 10 | pull-requests: write 11 | issues: write 12 | repository-projects: write 13 | 14 | jobs: 15 | automerge: 16 | runs-on: ubuntu-latest 17 | if: github.actor == 'dependabot[bot]' 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | token: ${{ secrets.DEPENDABOT_PAT }} 22 | 23 | - uses: ahmadnassri/action-dependabot-auto-merge@v2 24 | with: 25 | github-token: ${{ secrets.DEPENDABOT_PAT }} 26 | target: all -------------------------------------------------------------------------------- /.github/workflows/dockerhub-push.yml: -------------------------------------------------------------------------------- 1 | name: 🌥 Docker Push 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["🎉 Release Binary"] 6 | types: 7 | - completed 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest-16-cores 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Get Github tag 18 | id: meta 19 | run: | 20 | curl --silent "https://api.github.com/repos/projectdiscovery/proxify/releases/latest" | jq -r .tag_name | xargs -I {} echo TAG={} >> $GITHUB_OUTPUT 21 | 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v2 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v2 27 | 28 | - name: Login to DockerHub 29 | uses: docker/login-action@v2 30 | with: 31 | username: ${{ secrets.DOCKER_USERNAME }} 32 | password: ${{ secrets.DOCKER_TOKEN }} 33 | 34 | - name: Build and push 35 | uses: docker/build-push-action@v4 36 | with: 37 | context: . 38 | platforms: linux/amd64,linux/arm64,linux/arm 39 | push: true 40 | tags: projectdiscovery/proxify:latest,projectdiscovery/proxify:${{ steps.meta.outputs.TAG }} -------------------------------------------------------------------------------- /.github/workflows/lint-test.yml: -------------------------------------------------------------------------------- 1 | name: 🙏🏻 Lint Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | - '**.mod' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | lint: 12 | name: Lint Test 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Checkout code 17 | uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: 1.21.x 23 | 24 | - name: Run golangci-lint 25 | uses: golangci/golangci-lint-action@v3.6.0 26 | with: 27 | version: latest 28 | args: --timeout 5m 29 | working-directory: . -------------------------------------------------------------------------------- /.github/workflows/release-binary.yml: -------------------------------------------------------------------------------- 1 | name: 🎉 Release Binary 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | workflow_dispatch: 8 | 9 | jobs: 10 | release: 11 | runs-on: ubuntu-latest-16-cores 12 | steps: 13 | - name: "Check out code" 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: "Set up Go" 19 | uses: actions/setup-go@v4 20 | with: 21 | go-version: 1.21.x 22 | 23 | - name: "Create release on GitHub" 24 | uses: goreleaser/goreleaser-action@v4 25 | with: 26 | args: "release --rm-dist" 27 | version: latest 28 | workdir: . 29 | env: 30 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 31 | SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}" 32 | DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}" 33 | DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" 34 | -------------------------------------------------------------------------------- /.github/workflows/release-test.yml: -------------------------------------------------------------------------------- 1 | name: 🔨 Release Test 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '**.go' 7 | - '**.mod' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | release-test: 12 | runs-on: ubuntu-latest-16-cores 13 | steps: 14 | - name: "Check out code" 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: 1.21.x 23 | 24 | - name: release test 25 | uses: goreleaser/goreleaser-action@v4 26 | with: 27 | args: "release --clean --snapshot" 28 | version: latest 29 | workdir: . -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | cmd/proxify/proxify 3 | cmd/proxify/logs/* 4 | .DS_Store 5 | cmd/replay/replay 6 | cmd/mitmrelay/mitmrelay 7 | *.exe 8 | dist/* 9 | 10 | .vscode 11 | .devcontainer 12 | **/proxify_logs.jsonl 13 | **/proxify_logs.yaml 14 | /proxify 15 | **/proxify_logs -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - binary: proxify 7 | main: cmd/proxify/proxify.go 8 | goos: 9 | - linux 10 | - windows 11 | - darwin 12 | goarch: 13 | - amd64 14 | - 386 15 | - arm 16 | - arm64 17 | ignore: 18 | - goos: windows 19 | goarch: 'arm' 20 | - goos: windows 21 | goarch: 'arm64' 22 | id: proxify 23 | 24 | - binary: replay 25 | main: cmd/replay/replay.go 26 | goos: 27 | - linux 28 | - windows 29 | - darwin 30 | goarch: 31 | - amd64 32 | - 386 33 | - arm 34 | - arm64 35 | ignore: 36 | - goos: windows 37 | goarch: 'arm' 38 | - goos: windows 39 | goarch: 'arm64' 40 | id: replay 41 | 42 | - binary: mitmrelay 43 | main: cmd/mitmrelay/mitmrelay.go 44 | goos: 45 | - linux 46 | - windows 47 | - darwin 48 | goarch: 49 | - amd64 50 | - 386 51 | - arm 52 | - arm64 53 | ignore: 54 | - goos: windows 55 | goarch: 'arm' 56 | - goos: windows 57 | goarch: 'arm64' 58 | id: mitmrelay 59 | 60 | archives: 61 | - format: zip 62 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' 63 | 64 | 65 | checksum: 66 | algorithm: sha256 67 | 68 | announce: 69 | slack: 70 | enabled: true 71 | channel: '#release' 72 | username: GoReleaser 73 | message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}' 74 | 75 | discord: 76 | enabled: true 77 | message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}' -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Base 2 | FROM golang:1.21.4-alpine AS builder 3 | 4 | RUN apk add --no-cache git build-base 5 | WORKDIR /app 6 | COPY . /app 7 | RUN go mod download 8 | RUN go build ./cmd/proxify 9 | 10 | FROM alpine:3.18.2 11 | RUN apk -U upgrade --no-cache \ 12 | && apk add --no-cache bind-tools ca-certificates 13 | COPY --from=builder /app/proxify /usr/local/bin/ 14 | 15 | ENTRYPOINT ["proxify"] 16 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 ProjectDiscovery, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOMOD=$(GOCMD) mod 5 | GOTEST=$(GOCMD) test 6 | GOFLAGS := -v 7 | LDFLAGS := -s -w 8 | 9 | ifneq ($(shell go env GOOS),darwin) 10 | LDFLAGS := -extldflags "-static" 11 | endif 12 | 13 | all: build 14 | build: 15 | $(GOBUILD) $(GOFLAGS) -ldflags '$(LDFLAGS)' -o "proxify" cmd/proxify/proxify.go 16 | test: 17 | $(GOTEST) $(GOFLAGS) ./... 18 | tidy: 19 | $(GOMOD) tidy 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | proxify 3 |

4 | 5 |

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

14 | 15 |

16 | Features • 17 | Installation • 18 | Usage • 19 | Running Proxify • 20 | Installing SSL Certificate • 21 | Applications of Proxify • 22 | Join Discord 23 |

24 | 25 | Swiss Army Knife Proxy for rapid deployments. Supports multiple operations such as request/response dump, filtering and manipulation via DSL language, upstream HTTP/Socks5 proxy. 26 | Additionally, a replay utility allows to import the dumped traffic (request/responses with correct domain name) into BurpSuite or any other proxy by simply setting the upstream proxy to proxify. 27 | 28 | # Features 29 | 30 |

31 | proxify 32 |
33 |

34 | 35 | 36 | - Intercept / Manipulate **HTTP/HTTPS** & **NON-HTTP** traffic 37 | - **Invisible & Thick clients** traffic proxy support 38 | - TLS MITM support with client/server certificates 39 | - **HTTP** and **SOCKS5** support for upstream proxy 40 | - Traffic **Match/Filter and Replace** DSL support 41 | - Full traffic dump to file (request/responses) 42 | - Native embedded DNS server 43 | - Plugin Support to decode specific protocols (e.g XMPP/SMTP/FTP/SSH/) 44 | - Proxify Traffic replay in Burp 45 | 46 | # Installation 47 | 48 | Download the ready to run [binary](https://github.com/projectdiscovery/proxify/releases/) or install/build using GO 49 | 50 | ```shell 51 | go install -v github.com/projectdiscovery/proxify/cmd/proxify@latest 52 | ``` 53 | 54 | # Usage 55 | 56 | ```shell 57 | proxify -h 58 | ``` 59 | 60 | This will display help for the tool. Here are all the switches it supports. 61 | 62 | ```console 63 | Swiss Army Knife Proxy for rapid deployments. Supports multiple operations such as request/response dump,filtering and manipulation via DSL language, upstream HTTP/Socks5 proxy 64 | 65 | Usage: 66 | ./proxify [flags] 67 | 68 | Flags: 69 | OUTPUT: 70 | -sr, -store-resposne store raw http request / response to output directory (default proxify_logs) 71 | -o, -output output file to store proxify logs (default proxify_logs.jsonl) 72 | -of, -output-format string output format (jsonl/yaml) (default "jsonl") 73 | -dump-req Dump only HTTP requests to output file 74 | -dump-resp Dump only HTTP responses to output file 75 | -oca, -out-ca string Generate and Save CA File to filename 76 | 77 | UPDATE: 78 | -up, -update update proxify to latest version 79 | -duc, -disable-update-check disable automatic proxify update check 80 | 81 | FILTER: 82 | -req-fd, -request-dsl string[] Request Filter DSL 83 | -resp-fd, -response-dsl string[] Response Filter DSL 84 | -req-mrd, -request-match-replace-dsl string[] Request Match-Replace DSL 85 | -resp-mrd, -response-match-replace-dsl string[] Response Match-Replace DSL 86 | 87 | NETWORK: 88 | -ha, -http-addr string Listening HTTP IP and Port address (ip:port) (default "127.0.0.1:8888") 89 | -sa, -socks-addr Listening SOCKS IP and Port address (ip:port) (default 127.0.0.1:10080) 90 | -da, -dns-addr string Listening DNS IP and Port address (ip:port) 91 | -dm, -dns-mapping string Domain to IP DNS mapping (eg domain:ip,domain:ip,..) 92 | -r, -resolver string Custom DNS resolvers to use (ip:port) 93 | 94 | PROXY: 95 | -hp, -http-proxy string[] Upstream HTTP Proxies (eg http://proxy-ip:proxy-port) 96 | -sp, -socks5-proxy string[] Upstream SOCKS5 Proxies (eg socks5://proxy-ip:proxy-port) 97 | -c int Number of requests before switching to the next upstream proxy (default 1) 98 | 99 | EXPORT: 100 | -max-size int Max export data size (request/responses will be truncated) (default 9223372036854775807) 101 | 102 | CONFIGURATION: 103 | -config string path to the proxify configuration file 104 | -ec, -export-config string proxify export module configuration file (default "$CONFIG/export-config.yaml") 105 | -config-directory string override the default config path (default "$CONFIG/proxify") 106 | -cert-cache-size int Number of certificates to cache (default 256) 107 | -a, -allow string[] Allowed list of IP/CIDR's to be proxied 108 | -d, -deny string[] Denied list of IP/CIDR's to be proxied 109 | -pt, -passthrough string[] List of passthrough domains 110 | 111 | DEBUG: 112 | -nc, -no-color No Color 113 | -version Version 114 | -silent Silent 115 | -v, -verbose Verbose 116 | -vv, -very-verbose Very Verbose 117 | 118 | ``` 119 | 120 | ### Running Proxify 121 | 122 | Runs an HTTP proxy on port **8888**: 123 | ```shell 124 | proxify 125 | ``` 126 | 127 | Runs an HTTP proxy on custom port **1111**: 128 | ```shell 129 | proxify -http-addr ":1111" 130 | ``` 131 | 132 | ### TLS pass through 133 | 134 | The -pt flag can be used pass through (skip) encrypted traffic without attempting to terminate the TLS connection. 135 | 136 | 137 | ```bash 138 | proxify -pt '(.*\.)?google\.co.in.*' 139 | ``` 140 | 141 | ### Proxify with upstream proxy 142 | 143 | 144 | Runs an HTTP proxy on port 8888 and forward the traffic to burp on port **8080**: 145 | ```shell 146 | proxify -http-proxy http://127.0.0.1:8080 147 | ``` 148 | 149 | Runs an HTTP proxy on port 8888 and forward the traffic to the TOR network: 150 | ```shell 151 | proxify -socks5-proxy 127.0.0.1:9050 152 | ``` 153 | 154 | 155 | ### Dump all the HTTP/HTTPS traffic 156 | 157 | Proxify supports three output formats: **JSONL**, **YAML** and **Files**. 158 | 159 | **JSONL** (default): 160 | 161 | In Json Lines format each Http Request/Response pair is stored as json object in a single line. 162 | 163 | ```json 164 | {"timestamp":"2024-02-20T01:56:49+05:30","url":"https://scanme.sh:443","request":{"header":{"Connection":"close","User-Agent":"curl/8.1.2","host":"scanme.sh:443","method":"CONNECT","path":"","scheme":"https"},"raw":"CONNECT scanme.sh:443 HTTP/1.1\r\nHost: scanme.sh:443\r\nConnection: close\r\nUser-Agent: curl/8.1.2\r\n\r\n"},"response":{"header":{"Content-Length":"0"},"raw":"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"}} 165 | {"timestamp":"2024-02-20T01:56:49+05:30","url":"https://scanme.sh/","request":{"header":{"Accept":"*/*","Connection":"close","User-Agent":"curl/8.1.2","host":"scanme.sh","method":"GET","path":"/","scheme":"https"},"raw":"GET / HTTP/1.1\r\nHost: scanme.sh\r\nAccept: */*\r\nConnection: close\r\nUser-Agent: curl/8.1.2\r\n\r\n"},"response":{"header":{"Content-Type":"text/plain; charset=utf-8","Date":"Mon, 19 Feb 2024 20:26:49 GMT"},"body":"ok","raw":"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain; charset=utf-8\r\nDate: Mon, 19 Feb 2024 20:26:49 GMT\r\n\r\n"}} 166 | ``` 167 | 168 | **Yaml MultiDoc**: 169 | 170 | In the YAML MultiDoc format, each HTTP request and response pair is encapsulated as a separate document.All Documents in output yaml file are seperated by `---` to allow stream parsing and consumption. 171 | 172 | ```console 173 | proxify -output-format yaml 174 | ``` 175 | 176 | ```yaml 177 | timestamp: "2024-02-20T01:40:40+05:30" 178 | url: https://scanme.sh:443 179 | request: 180 | header: 181 | Connection: close 182 | User-Agent: curl/8.1.2 183 | host: scanme.sh:443 184 | method: CONNECT 185 | path: "" 186 | scheme: https 187 | body: "" 188 | raw: "CONNECT scanme.sh:443 HTTP/1.1\r\nHost: scanme.sh:443\r\nConnection: close\r\nUser-Agent: curl/8.1.2\r\n\r\n" 189 | response: 190 | header: 191 | Content-Length: "0" 192 | body: "" 193 | raw: "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" 194 | --- 195 | timestamp: "2024-02-20T01:40:40+05:30" 196 | ... 197 | ``` 198 | 199 | **Files**: 200 | 201 | In Files format, each HTTP request and response pair is stored in separate files with the request followed by the response. Filenames are in format of `{{Host}}-{{randstr}}.txt`. Additionally, **dump-req** or **dump-resp** flag can be used for saving specific part of the request to the file. 202 | 203 | ```console 204 | proxify -store-response 205 | ``` 206 | 207 | >Note: When using `-store-response` both jsonl and files are generated. 208 | 209 | ### Hostname mapping with Local DNS resolver 210 | 211 | Proxify supports embedding DNS resolver to map hostnames to specific addresses and define an upstream dns server for any other domain name 212 | 213 | Runs an HTTP proxy on port `8888` using an embedded dns server listening on port `53` and resolving `www.google.it` to `192.168.1.1` and all other `fqdn` are forwarded upstream to `1.1.1.1`: 214 | 215 | ```shell 216 | proxify -dns-addr ":53" -dns-mapping "www.google.it:192.168.1.1" -dns-resolver "1.1.1.1:53" 217 | ``` 218 | 219 | This feature is used for example by the `replay` utility to hijack the connections and simulate responses. It may be useful during internal assessments with private dns servers. Using `*` as domain name matches all dns requests. 220 | 221 | ### Match/Filter traffic with DSL 222 | 223 | If the request or response match the filters the dump is tagged with `.match.txt` suffix: 224 | 225 | ```shell 226 | proxify -request-dsl "contains(request,'firefox')" -response-dsl "contains(response, md5('test'))" 227 | ``` 228 | 229 | ### Match and Replace on the fly 230 | 231 | Proxify supports modifying Request and Responses on the fly with DSL language. 232 | 233 | Here is an example to replace `firefox` word from request to `chrome`: 234 | 235 | ```shell 236 | proxify -request-match-replace-dsl "replace(request,'firefox','chrome')" 237 | ``` 238 | 239 | Another example using **regex** based replacement of response: 240 | 241 | 242 | ```shell 243 | proxify -response-match-replace-dsl "replace_regex(response, '^authentication failed$', 'authentication ok')" 244 | ``` 245 | 246 | ### Replay all traffic into burp 247 | 248 | Replay all the dumped requests/responses into the destination URL (http://127.0.0.1:8080) if not specified. For this to work it's necessary to configure burp to use proxify as upstream proxy, as it will take care to hijack the dns resolutions and simulate the remote server with the dumped request. This allows to have in the burp history exactly all requests/responses as if they were originally sent through it, allowing for example to perform a remote interception on cloud, and merge all results locally within burp. 249 | 250 | ```shell 251 | replay -output "logs/" 252 | ``` 253 | 254 | ### Installing SSL Certificate 255 | 256 | A certificate authority is generated for proxify which is stored in the folder `~/.config/proxify/` as default, manually can be specified by `-config` flag. The generated certificate can be imported by visiting [http://proxify/cacert](http://proxify/cacert) in a browser connected to proxify. 257 | 258 | Installation steps for the Root Certificate is similar to other proxy tools which includes adding the cert to system trusted root store. 259 | 260 | ### Applications of Proxify 261 | 262 | Proxify can be used for multiple places, here are some common example where Proxify comes handy: 263 | 264 |
265 | 👉 Storing all the burp proxy history logs locally. 266 | 267 | Runs an HTTP proxy on port `8888` and forward the traffic to burp on port `8080`: 268 | 269 | ```shell 270 | proxify -http-proxy http://127.0.0.1:8080 271 | ``` 272 | 273 | From BurpSuite, set the Upstream Proxy to forward all the traffic back to `proxify`: 274 | 275 | ``` 276 | User Options > Upstream Proxy > Proxy & Port > 127.0.0.1 & 8888 277 | ``` 278 | Now all the request/response history will be stored in `logs` folder that can be used later for post-processing. 279 | 280 |
281 | 282 | 283 |
284 | 👉 Store all your browse history locally. 285 | 286 | 287 | While you browse the application, you can point the browser to `proxify` to store all the HTTP request / response to file. 288 | 289 | Start proxify on default or any port you wish, 290 | 291 | ```shell 292 | proxify -output chrome-logs 293 | ``` 294 | 295 | Start Chrome browser in macOS, 296 | ```shell 297 | /Applications/Chromium.app/Contents/MacOS/Chromium --ignore-certificate-errors --proxy-server=http://127.0.0.1:8888 & 298 | ``` 299 | 300 |
301 | 302 |
303 | 👉 Store all the response of while you fuzz as per you config at run time. 304 | 305 | 306 | Start proxify on default or any port you wish: 307 | 308 | ```shell 309 | proxify -output ffuf-logs 310 | ``` 311 | 312 | Run `FFuF` with proxy pointing to `proxify`: 313 | 314 | ```shell 315 | ffuf -x http://127.0.0.1:8888 FFUF_CMD_HERE 316 | ``` 317 | 318 |
319 | 320 | ------ 321 | 322 | `Proxify` is made with 🖤 by the [projectdiscovery](https://projectdiscovery.io) team. Community contributions have made the project what it is. See the **[Thanks.md](https://github.com/projectdiscovery/proxify/blob/master/THANKS.md)** file for more details. 323 | -------------------------------------------------------------------------------- /THANKS.md: -------------------------------------------------------------------------------- 1 | ### Thanks 2 | 3 | The project was inspired by the following tools, and we invite you to try them out as well: 4 | - [Burp Suite](https://portswigger.net/burp) 5 | - [Zaproxy](https://www.zaproxy.org/) 6 | - [Mitmproxy](https://mitmproxy.org/) 7 | - [Mitm_relay](https://github.com/jrmdev/mitm_relay) -------------------------------------------------------------------------------- /cmd/mitmrelay/mitmrelay.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "flag" 6 | "io" 7 | "net/http" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/projectdiscovery/gologger" 13 | "github.com/projectdiscovery/proxify" 14 | "github.com/projectdiscovery/tinydns" 15 | ) 16 | 17 | type Options struct { 18 | DNSListenerAddress string 19 | HTTPListenerAddress string 20 | HTTPProxy string 21 | OutputFolder string 22 | ServerTLS bool 23 | ServerCert string 24 | ServerKey string 25 | ClientTLS bool 26 | ClientCert string 27 | ClientKey string 28 | Protocol string 29 | Relays Relays 30 | DNSFallbackResolver string 31 | ListenDNSAddr string 32 | DNSMapping string 33 | Timeout int 34 | RequestMatchReplaceDSL string 35 | ResponseMatchReplaceDSL string 36 | } 37 | 38 | func httpserver(addr string) error { 39 | // echo server 40 | http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 41 | io.Copy(w, req.Body) //nolint 42 | }) 43 | 44 | return http.ListenAndServe(addr, nil) 45 | } 46 | 47 | func dnsserver(listenAddr, resolverAddr, dnsMap string) { 48 | domainsToAddresses := make(map[string]*tinydns.DnsRecord) 49 | for _, dnsitem := range strings.Split(dnsMap, ",") { 50 | tokens := strings.Split(dnsitem, ":") 51 | if len(tokens) != 2 { 52 | continue 53 | } 54 | domainsToAddresses[tokens[0]] = &tinydns.DnsRecord{A: []string{tokens[1]}} 55 | } 56 | tinydns, _ := tinydns.New(&tinydns.Options{ 57 | ListenAddress: listenAddr, 58 | UpstreamServers: []string{resolverAddr}, 59 | Net: "udp", 60 | DnsRecords: domainsToAddresses, 61 | }) 62 | go func() { 63 | if err := tinydns.Run(); err != nil { 64 | gologger.Fatal().Msgf("%s\n", err) 65 | } 66 | }() 67 | } 68 | 69 | func main() { 70 | options := &Options{} 71 | flag.StringVar(&options.OutputFolder, "output", "logs/", "Output Folder") 72 | flag.StringVar(&options.HTTPListenerAddress, "http-addr", "127.0.0.1:49999", "HTTP Server Listen Address") 73 | flag.StringVar(&options.HTTPProxy, "proxy-addr", "", "HTTP Proxy Address") 74 | flag.BoolVar(&options.ServerTLS, "tls-server", false, "Client => Relay should use tls") 75 | flag.StringVar(&options.ServerCert, "server-cert", "", "Client => Relay Cert File") 76 | flag.StringVar(&options.ServerKey, "server-key", "", "Client => Relay Key File") 77 | flag.BoolVar(&options.ClientTLS, "tls-client", false, "Relay => Server should use tls") 78 | flag.StringVar(&options.ClientCert, "client-cert", "", "Relay => Server Cert File") 79 | flag.StringVar(&options.ClientKey, "client-key", "", "Relay => Server Key File") 80 | flag.StringVar(&options.DNSFallbackResolver, "resolver-addr", "", "Listen DNS Ip and port (ip:port)") 81 | flag.StringVar(&options.ListenDNSAddr, "dns-addr", ":5353", "Listen DNS Ip and port (ip:port)") 82 | flag.StringVar(&options.DNSMapping, "dns-mapping", "", "DNS A mapping (eg domain:ip,domain:ip,..)") 83 | flag.IntVar(&options.Timeout, "timeout", 180, "Connection Timeout In Seconds") 84 | flag.StringVar(&options.RequestMatchReplaceDSL, "request-match-replace-dsl", "", "Request Match-Replace DSL") 85 | flag.StringVar(&options.ResponseMatchReplaceDSL, "response-match-replace-dsl", "", "Request Match-Replace DSL") 86 | // Single protocol for now 87 | flag.StringVar(&options.Protocol, "protocol", "tcp", "tcp or udp") 88 | flag.Var(&options.Relays, "relay", "listen_ip:listen_port => destination_ip:destination_port") 89 | flag.Parse() 90 | 91 | var proxyOpts proxify.SocketProxyOptions 92 | 93 | // TLS Relay => Server 94 | proxyOpts.TLSClient = options.ClientTLS 95 | if options.ClientCert != "" && options.ClientKey != "" { 96 | cert, err := tls.LoadX509KeyPair(options.ClientCert, options.ClientKey) 97 | if err != nil { 98 | gologger.Fatal().Msgf("%s\n", err) 99 | } 100 | config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} 101 | proxyOpts.TLSClientConfig = &config 102 | } 103 | 104 | // TLS Client => Relay 105 | proxyOpts.TLSServer = options.ServerTLS 106 | if options.ServerCert != "" && options.ServerKey != "" { 107 | cert, err := tls.LoadX509KeyPair(options.ServerCert, options.ServerKey) 108 | if err != nil { 109 | gologger.Fatal().Msgf("%s\n", err) 110 | } 111 | config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} 112 | proxyOpts.TLSServerConfig = &config 113 | } 114 | proxyOpts.Protocol = options.Protocol 115 | proxyOpts.HTTPProxy = options.HTTPProxy 116 | proxyOpts.RequestMatchReplaceDSL = []string{options.RequestMatchReplaceDSL} 117 | proxyOpts.ResponseMatchReplaceDSL = []string{options.ResponseMatchReplaceDSL} 118 | 119 | if options.Timeout >= 0 { 120 | proxyOpts.Timeout = time.Duration(options.Timeout) * time.Second 121 | } 122 | 123 | go httpserver(options.HTTPListenerAddress) //nolint 124 | go dnsserver(options.ListenDNSAddr, options.DNSFallbackResolver, options.DNSMapping) //nolint 125 | 126 | var wgproxies sync.WaitGroup 127 | 128 | for _, relay := range options.Relays { 129 | wgproxies.Add(1) 130 | go func(relay string) { 131 | defer wgproxies.Done() 132 | addresses := strings.Split(relay, "=>") 133 | if len(addresses) != 2 { 134 | gologger.Print().Msgf("[!] Skipping invalid relay %s", relay) 135 | return 136 | } 137 | ropts := proxyOpts.Clone() 138 | ropts.ListenAddress, ropts.RemoteAddress = strings.TrimSpace(addresses[0]), strings.TrimSpace(addresses[1]) 139 | sproxy := proxify.NewSocketProxy(&ropts) 140 | gologger.Print().Msgf("[+] Relay listening on %s -> %s", ropts.ListenAddress, ropts.RemoteAddress) 141 | gologger.Print().Msgf("%s\n", sproxy.Run()) 142 | }(relay) 143 | } 144 | 145 | wgproxies.Wait() 146 | } 147 | 148 | type Relays []string 149 | 150 | func (r *Relays) String() string { 151 | return "" 152 | } 153 | 154 | func (r *Relays) Set(value string) error { 155 | *r = append(*r, value) 156 | return nil 157 | } 158 | -------------------------------------------------------------------------------- /cmd/proxify/proxify.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "github.com/projectdiscovery/gologger" 10 | "github.com/projectdiscovery/proxify/internal/runner" 11 | ) 12 | 13 | func main() { 14 | 15 | options, err := runner.ParseOptions() 16 | if err != nil { 17 | gologger.Fatal().Msgf("Could not parse options: %s\n", err) 18 | } 19 | 20 | proxifyRunner, err := runner.NewRunner(options) 21 | if err != nil { 22 | gologger.Fatal().Msgf("Could not create runner: %s\n", err) 23 | } 24 | 25 | // Setup close handler 26 | go func() { 27 | c := make(chan os.Signal, 1) 28 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 29 | for range c { 30 | fmt.Println("\r- Ctrl+C pressed in Terminal") 31 | proxifyRunner.Close() 32 | os.Exit(0) 33 | } 34 | }() 35 | 36 | err = proxifyRunner.Run() 37 | if err != nil { 38 | gologger.Fatal().Msgf("Could not run proxify: %s\n", err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/replay/replay.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "crypto/tls" 6 | "flag" 7 | "io" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | 15 | "github.com/elazarl/goproxy" 16 | "github.com/projectdiscovery/fastdialer/fastdialer" 17 | "github.com/projectdiscovery/gologger" 18 | "github.com/projectdiscovery/tinydns" 19 | ) 20 | 21 | var ( 22 | httpclient *http.Client 23 | response *http.Response 24 | responses map[string]*http.Response 25 | ) 26 | 27 | type Options struct { 28 | DNSListenerAddress string 29 | HTTPListenerAddress string 30 | HTTPBurpAddress string 31 | HTTPProxyListenerAddress string 32 | OutputFolder string 33 | } 34 | 35 | func main() { 36 | options := &Options{} 37 | flag.StringVar(&options.OutputFolder, "output", "db/", "Output Folder") 38 | flag.StringVar(&options.HTTPListenerAddress, "http-addr", ":80", "HTTP Server Listen Address") 39 | flag.StringVar(&options.HTTPBurpAddress, "burp-addr", "http://127.0.0.1:8080", "Burp HTTP Address") 40 | flag.StringVar(&options.DNSListenerAddress, "dns-addr", ":10000", "DNS UDP Server Listen Address") 41 | flag.StringVar(&options.HTTPProxyListenerAddress, "proxy-addr", ":8081", "HTTP Proxy Server Listen Address") 42 | flag.Parse() 43 | 44 | dialerOpts := fastdialer.DefaultOptions 45 | dialerOpts.MaxRetries = 1 46 | dialerOpts.BaseResolvers = []string{"127.0.0.1" + options.DNSListenerAddress} 47 | dialer, err := fastdialer.NewDialer(dialerOpts) 48 | if err != nil { 49 | gologger.Fatal().Msgf("%s\n", err) 50 | } 51 | 52 | responses = make(map[string]*http.Response) 53 | httpproxy := goproxy.NewProxyHttpServer() 54 | httpproxy.Verbose = true 55 | httpproxy.Tr.DialContext = dialer.Dial 56 | httpproxy.OnRequest().HandleConnect(goproxy.AlwaysMitm) 57 | // httpproxy.OnRequest().DoFunc(OnRequest) 58 | go func() { 59 | if err := http.ListenAndServe(options.HTTPProxyListenerAddress, httpproxy); err != nil { 60 | gologger.Fatal().Msgf("Could not serve proxy: %s\n", err) 61 | } 62 | }() 63 | 64 | // dns server 65 | var domainsToAddresses map[string]*tinydns.DnsRecord = map[string]*tinydns.DnsRecord{ 66 | "*": {A: []string{"127.0.0.1"}}, 67 | } 68 | tinydns, _ := tinydns.New(&tinydns.Options{ 69 | ListenAddress: options.DNSListenerAddress, 70 | Net: "udp", 71 | DnsRecords: domainsToAddresses, 72 | }) 73 | go func() { 74 | if err := tinydns.Run(); err != nil { 75 | gologger.Fatal().Msgf("Could not serve dns: %s\n", err) 76 | } 77 | }() 78 | 79 | // http server 80 | http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { 81 | key := req.Header.Get("proxify") 82 | response := responses[key] 83 | delete(responses, key) 84 | 85 | for k, v := range response.Header { 86 | w.Header().Add(k, strings.Join(v, "; ")) 87 | } 88 | w.WriteHeader(response.StatusCode) 89 | io.Copy(w, response.Body) //nolint 90 | }) 91 | go func() { 92 | if err := http.ListenAndServe(options.HTTPListenerAddress, nil); err != nil { 93 | gologger.Fatal().Msgf("Could not listen and serve: %s\n", err) 94 | } 95 | }() 96 | 97 | // http client proxy 98 | proxyUrl, err := url.Parse(options.HTTPBurpAddress) 99 | if err != nil { 100 | log.Fatal(err) 101 | } 102 | transport := &http.Transport{ 103 | TLSClientConfig: &tls.Config{ 104 | InsecureSkipVerify: true, 105 | }, 106 | Proxy: http.ProxyURL(proxyUrl), 107 | } 108 | httpclient = &http.Client{Transport: transport} 109 | 110 | // process all requests 111 | root := options.OutputFolder 112 | err = filepath.Walk(root, visit()) 113 | if err != nil { 114 | log.Fatal(err) 115 | } 116 | } 117 | 118 | func visit() filepath.WalkFunc { 119 | return func(path string, info os.FileInfo, err error) error { 120 | if err != nil { 121 | return err 122 | } 123 | 124 | if info.IsDir() { 125 | return nil 126 | } 127 | 128 | if !strings.HasSuffix(path, ".txt") { 129 | return nil 130 | } 131 | 132 | filename := filepath.Base(path) 133 | filename = strings.TrimSuffix(filename, ".txt") 134 | filename = strings.TrimSuffix(filename, ".match") 135 | 136 | file, err := os.Open(path) 137 | if err != nil { 138 | return err 139 | } 140 | defer file.Close() 141 | bf := bufio.NewReader(file) 142 | 143 | tokens := strings.Split(filename, "-") 144 | host := strings.Join(tokens[:len(tokens)-1], "") 145 | id := strings.Split(tokens[len(tokens)-1], ".")[0] 146 | log.Println(host, id) 147 | 148 | request, err := http.ReadRequest(bf) 149 | if err != nil { 150 | return err 151 | } 152 | request.Header.Add("proxify", id) 153 | response, err = http.ReadResponse(bf, request) 154 | if err != nil { 155 | return err 156 | } 157 | 158 | // We can't have this set. And it only contains "/pkg/net/http/" anyway 159 | request.RequestURI = "" 160 | 161 | // Since the req.URL will not have all the information set, 162 | // such as protocol scheme and host, we create a new URL 163 | u, err := url.Parse("http://" + host) 164 | if err != nil { 165 | return err 166 | } 167 | request.URL = u 168 | 169 | responses[id] = response 170 | _, err = httpclient.Do(request) 171 | return err 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /cmd/swaggergen/README.md: -------------------------------------------------------------------------------- 1 | # SwaggerGen 2 | 3 | SwaggerGen can generate [Swagger/OpenAPI Specification](https://swagger.io/specification/) from `proxify`'s request and response logs. 4 | 5 | # Installation 6 | 7 | Download the latest, ready-to-run binary from under [releases](https://github.com/projectdiscovery/proxify/releases/) or install/build it using `Go`: 8 | 9 | ```shell 10 | go install -v github.com/projectdiscovery/proxify/cmd/swaggergen@latest 11 | ``` 12 | 13 | # Usage 14 | 15 | ```shell 16 | swaggergen -help 17 | 18 | Usage: 19 | ./swaggergen [flags] 20 | 21 | Flags: 22 | -log-dir string path to proxify's output log directory 23 | -api, -api-host string API host (example: api.example.com) 24 | -os, -output-spec string file to store Swagger/OpenAPI specification (example: OpenAPI.yaml) 25 | ``` 26 | 27 | ### Running SwaggerGen 28 | 29 | The following command generates and saves the Swagger/OpenAPI specification in `OpenAPI.yaml`, from the requests and responses captured for `localhost:8080` and stored in the `logs` directory: 30 | 31 | ```shell 32 | swaggergen -api localhost:8080 -log-dir ./logs -os OpenAPI.yaml 33 | ``` 34 | -------------------------------------------------------------------------------- /cmd/swaggergen/swaggergen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io/fs" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "path/filepath" 12 | "regexp" 13 | "strings" 14 | 15 | "gopkg.in/yaml.v3" 16 | 17 | "github.com/projectdiscovery/goflags" 18 | "github.com/projectdiscovery/gologger" 19 | "github.com/projectdiscovery/proxify/pkg/swaggergen" 20 | fileutil "github.com/projectdiscovery/utils/file" 21 | ) 22 | 23 | type Options struct { 24 | logDir string 25 | outputSpec string 26 | api string 27 | } 28 | 29 | func main() { 30 | options := &Options{} 31 | flagSet := goflags.NewFlagSet() 32 | 33 | flagSet.SetDescription(`SwaggerGen generates Swagger/OpenAPI specification using request and response files from a log directory`) 34 | 35 | flagSet.StringVar(&options.logDir, "log-dir", "", "path to proxify's output log directory") 36 | flagSet.StringVarP(&options.api, "api-host", "api", "", "API host (example: api.example.com)") 37 | flagSet.StringVarP(&options.outputSpec, "output-spec", "os", "", "file to store Swagger/OpenAPI specification (example: OpenAPI.yaml)") 38 | err := flagSet.Parse() 39 | if err != nil { 40 | gologger.Fatal().Msgf("Could not parse flags: %s", err) 41 | } 42 | if options.logDir == "" || options.outputSpec == "" || options.api == "" { 43 | gologger.Fatal().Msg("Please provide all required flags i.e, log-dir, output-spec, api-host") 44 | } 45 | _, err = url.Parse(options.api) 46 | if err != nil { 47 | gologger.Fatal().Msgf("Invalid API host: %s", err) 48 | } 49 | 50 | generator := NewGenerator(options) 51 | if err := generator.Generate(); err != nil { 52 | gologger.Fatal().Msg(err.Error()) 53 | } 54 | 55 | } 56 | 57 | // Generator is the swagger spec generator 58 | type Generator struct { 59 | RequestResponseList []swaggergen.RequestResponse 60 | Spec *swaggergen.Spec 61 | Options *Options 62 | } 63 | 64 | // NewGenerator creates a new generator instance 65 | func NewGenerator(options *Options) *Generator { 66 | return &Generator{ 67 | RequestResponseList: make([]swaggergen.RequestResponse, 0), 68 | Options: options, 69 | } 70 | } 71 | 72 | // Generate generates a swagger specification from a directory of request/response logs 73 | func (g *Generator) Generate() error { 74 | if err := g.ReadLog(); err != nil { 75 | return fmt.Errorf("error reading logs: %s", err) 76 | } 77 | if err := g.CreateSpec(); err != nil { 78 | return fmt.Errorf("error generating swagger specification: %s", err) 79 | } 80 | if err := g.WriteSpec(); err != nil { 81 | return fmt.Errorf("error writing data: %s", err) 82 | } 83 | return nil 84 | } 85 | 86 | // WriteSpec writes the spec to a yaml file 87 | func (g *Generator) WriteSpec() error { 88 | // create/open openapi specification yaml file 89 | f, err := os.OpenFile(g.Options.outputSpec, os.O_CREATE|os.O_WRONLY, 0644) 90 | if err != nil { 91 | return err 92 | } 93 | defer f.Close() 94 | // write the spec to the file 95 | return yaml.NewEncoder(f).Encode(g.Spec) 96 | } 97 | 98 | // CreateSpec crseate the swagger spec from the generator's RequestResponse 99 | func (g *Generator) CreateSpec() error { 100 | // check if spec file exists 101 | if fileutil.FileExists(g.Options.outputSpec) { 102 | // read the spec from the file 103 | f, err := os.Open(g.Options.outputSpec) 104 | if err != nil { 105 | return err 106 | } 107 | defer f.Close() 108 | g.Spec = &swaggergen.Spec{} 109 | if err := yaml.NewDecoder(f).Decode(g.Spec); err != nil { 110 | return err 111 | } 112 | g.Spec.UpdateSpec(g.Options.logDir, g.Options.api) 113 | } else { 114 | // create a new spec 115 | g.Spec = swaggergen.NewSpec(g.Options.logDir, g.Options.api) 116 | } 117 | 118 | for _, reqRes := range g.RequestResponseList { 119 | // filter out unrelated requests 120 | if reqRes.Request.Host == g.Options.api { 121 | g.Spec.AddPath(reqRes) 122 | } 123 | } 124 | return nil 125 | } 126 | 127 | // ReadLog reads the request/response list from logDir 128 | func (g *Generator) ReadLog() error { 129 | // check if log directory exists 130 | if !fileutil.FolderExists(g.Options.logDir) { 131 | return fmt.Errorf("log directory (%s) does not exist", g.Options.logDir) 132 | } 133 | // read all files in directory 134 | return filepath.Walk(g.Options.logDir, func(path string, info fs.FileInfo, _ error) error { 135 | if info.IsDir() { 136 | return nil 137 | } 138 | // open file 139 | f, err := os.Open(path) 140 | if err != nil { 141 | return err 142 | } 143 | defer f.Close() 144 | 145 | // read file 146 | buf := make([]byte, info.Size()) 147 | _, err = f.Read(buf) 148 | if err != nil { 149 | return err 150 | } 151 | 152 | requestResponseString := string(buf) 153 | 154 | // split requestResponseString into request and response parts 155 | responseRegex := regexp.MustCompile("\n(HTTP\\/1\\.[0-1] (.|\n)*)") 156 | result := responseRegex.FindString(requestResponseString) 157 | result = strings.TrimPrefix(result, "\n") 158 | var requestResponse swaggergen.RequestResponse 159 | var requestError, responseError error 160 | requestResponse.Request, requestError = http.ReadRequest(bufio.NewReader(bytes.NewReader(buf))) 161 | requestResponse.Response, responseError = http.ReadResponse(bufio.NewReader(bytes.NewReader([]byte(result))), nil) 162 | 163 | if requestError != nil && responseError != nil { 164 | return fmt.Errorf("error reading request: %s, response: %s", requestError, responseError) 165 | } 166 | 167 | g.RequestResponseList = append(g.RequestResponseList, requestResponse) 168 | return nil 169 | }) 170 | } 171 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/projectdiscovery/proxify 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/Knetic/govaluate v3.0.0+incompatible 7 | github.com/Shopify/sarama v1.38.1 8 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 9 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 10 | github.com/elastic/go-elasticsearch/v7 v7.17.10 11 | github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 12 | github.com/goccy/go-yaml v1.11.3 13 | github.com/haxii/fastproxy v0.5.37 14 | github.com/pkg/errors v0.9.1 15 | github.com/projectdiscovery/dsl v0.0.47 16 | github.com/projectdiscovery/fastdialer v0.0.62 17 | github.com/projectdiscovery/goflags v0.1.43 18 | github.com/projectdiscovery/gologger v1.1.12 19 | github.com/projectdiscovery/martian/v3 v3.0.0-20240219194442-fed3b744f477 20 | github.com/projectdiscovery/roundrobin v0.0.6 21 | github.com/projectdiscovery/tinydns v0.0.31 22 | github.com/projectdiscovery/utils v0.0.83 23 | golang.org/x/net v0.21.0 24 | gopkg.in/yaml.v3 v3.0.1 25 | ) 26 | 27 | require ( 28 | github.com/docker/go-units v0.5.0 // indirect 29 | github.com/go-ole/go-ole v1.2.6 // indirect 30 | github.com/klauspost/pgzip v1.2.6 // indirect 31 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 32 | github.com/mholt/archiver/v3 v3.5.1 // indirect 33 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 34 | github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 // indirect 35 | github.com/shirou/gopsutil/v3 v3.23.7 // indirect 36 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 37 | github.com/tklauser/go-sysconf v0.3.11 // indirect 38 | github.com/tklauser/numcpus v0.6.0 // indirect 39 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 40 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect 41 | ) 42 | 43 | require ( 44 | aead.dev/minisign v0.2.0 // indirect 45 | github.com/Masterminds/semver/v3 v3.2.1 // indirect 46 | github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect 47 | github.com/VividCortex/ewma v1.2.0 // indirect 48 | github.com/akrylysov/pogreb v0.10.1 // indirect 49 | github.com/alecthomas/chroma v0.10.0 // indirect 50 | github.com/andybalholm/brotli v1.1.0 // indirect 51 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 52 | github.com/aymerick/douceur v0.2.0 // indirect 53 | github.com/charmbracelet/glamour v0.6.0 // indirect 54 | github.com/cheggaaa/pb/v3 v3.1.4 // indirect 55 | github.com/cloudflare/circl v1.3.7 // indirect 56 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect 57 | github.com/davecgh/go-spew v1.1.1 // indirect 58 | github.com/dimchansky/utfbom v1.1.1 // indirect 59 | github.com/dlclark/regexp2 v1.8.1 // indirect 60 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect 61 | github.com/eapache/go-resiliency v1.3.0 // indirect 62 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 // indirect 63 | github.com/eapache/queue v1.1.0 // indirect 64 | github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e // indirect 65 | github.com/fatih/color v1.16.0 // indirect 66 | github.com/gaukas/godicttls v0.0.4 // indirect 67 | github.com/golang/protobuf v1.5.3 // indirect 68 | github.com/golang/snappy v0.0.4 // indirect 69 | github.com/google/go-github/v30 v30.1.0 // indirect 70 | github.com/google/go-querystring v1.1.0 // indirect 71 | github.com/gorilla/css v1.0.1 // indirect 72 | github.com/hashicorp/errwrap v1.1.0 // indirect 73 | github.com/hashicorp/go-multierror v1.1.1 // indirect 74 | github.com/hashicorp/go-uuid v1.0.3 // indirect 75 | github.com/hashicorp/go-version v1.6.0 // indirect 76 | github.com/hdm/jarm-go v0.0.7 // indirect 77 | github.com/jcmturner/aescts/v2 v2.0.0 // indirect 78 | github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect 79 | github.com/jcmturner/gofork v1.7.6 // indirect 80 | github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect 81 | github.com/jcmturner/rpc/v2 v2.0.3 // indirect 82 | github.com/json-iterator/go v1.1.12 // indirect 83 | github.com/kataras/jwt v0.1.8 // indirect 84 | github.com/klauspost/compress v1.17.6 // indirect 85 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 86 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 87 | github.com/mattn/go-colorable v0.1.13 // indirect 88 | github.com/mattn/go-isatty v0.0.20 // indirect 89 | github.com/mattn/go-runewidth v0.0.14 // indirect 90 | github.com/microcosm-cc/bluemonday v1.0.26 // indirect 91 | github.com/miekg/dns v1.1.56 // indirect 92 | github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect 93 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 94 | github.com/modern-go/reflect2 v1.0.2 // indirect 95 | github.com/muesli/reflow v0.3.0 // indirect 96 | github.com/muesli/termenv v0.15.1 // indirect 97 | github.com/nwaples/rardecode v1.1.3 // indirect 98 | github.com/olekukonko/tablewriter v0.0.5 // indirect 99 | github.com/pierrec/lz4/v4 v4.1.21 // indirect 100 | github.com/projectdiscovery/blackrock v0.0.1 // indirect 101 | github.com/projectdiscovery/gostruct v0.0.2 // indirect 102 | github.com/projectdiscovery/hmap v0.0.41 // indirect 103 | github.com/projectdiscovery/mapcidr v1.1.16 // indirect 104 | github.com/projectdiscovery/networkpolicy v0.0.8 // indirect 105 | github.com/projectdiscovery/retryabledns v1.0.58 // indirect 106 | github.com/quic-go/quic-go v0.37.7 // indirect 107 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect 108 | github.com/refraction-networking/utls v1.5.4 // indirect 109 | github.com/rivo/uniseg v0.4.4 // indirect 110 | github.com/rogpeppe/go-internal v1.11.0 // indirect 111 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect 112 | github.com/sashabaranov/go-openai v1.14.2 // indirect 113 | github.com/spaolacci/murmur3 v1.1.0 // indirect 114 | github.com/syndtr/goleveldb v1.0.0 // indirect 115 | github.com/tidwall/btree v1.6.0 // indirect 116 | github.com/tidwall/buntdb v1.3.0 // indirect 117 | github.com/tidwall/gjson v1.14.4 // indirect 118 | github.com/tidwall/grect v0.1.4 // indirect 119 | github.com/tidwall/match v1.1.1 // indirect 120 | github.com/tidwall/pretty v1.2.1 // indirect 121 | github.com/tidwall/rtred v0.1.2 // indirect 122 | github.com/tidwall/tinyqueue v0.1.1 // indirect 123 | github.com/ulikunitz/xz v0.5.11 // indirect 124 | github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db // indirect 125 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 126 | github.com/yl2chen/cidranger v1.0.2 // indirect 127 | github.com/yuin/goldmark v1.5.4 // indirect 128 | github.com/yuin/goldmark-emoji v1.0.1 // indirect 129 | github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect 130 | github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 // indirect 131 | go.etcd.io/bbolt v1.3.7 // indirect 132 | go.uber.org/multierr v1.11.0 // indirect 133 | golang.org/x/crypto v0.19.0 // indirect 134 | golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect 135 | golang.org/x/mod v0.12.0 // indirect 136 | golang.org/x/oauth2 v0.11.0 // indirect 137 | golang.org/x/sys v0.17.0 // indirect 138 | golang.org/x/text v0.14.0 // indirect 139 | golang.org/x/tools v0.13.0 // indirect 140 | google.golang.org/appengine v1.6.7 // indirect 141 | google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 // indirect 142 | google.golang.org/grpc v1.38.0 // indirect 143 | google.golang.org/protobuf v1.31.0 // indirect 144 | gopkg.in/djherbis/times.v1 v1.3.0 // indirect 145 | gopkg.in/yaml.v2 v2.4.0 146 | ) 147 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= 2 | aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= 3 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 4 | cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= 5 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 6 | github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= 7 | github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= 8 | github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= 9 | github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= 10 | github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 h1:KFac3SiGbId8ub47e7kd2PLZeACxc1LkiiNoDOFRClE= 11 | github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057/go.mod h1:iLB2pivrPICvLOuROKmlqURtFIEsoJZaMidQfCG1+D4= 12 | github.com/RumbleDiscovery/rumble-tools v0.0.0-20201105153123-f2adbb3244d2/go.mod h1:jD2+mU+E2SZUuAOHZvZj4xP4frlOo+N/YrXDvASFhkE= 13 | github.com/Shopify/sarama v1.38.1 h1:lqqPUPQZ7zPqYlWpTh+LQ9bhYNu2xJL6k1SJN4WVe2A= 14 | github.com/Shopify/sarama v1.38.1/go.mod h1:iwv9a67Ha8VNa+TifujYoWGxWnu2kNVAQdSdZ4X2o5g= 15 | github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc= 16 | github.com/Shopify/toxiproxy/v2 v2.5.0/go.mod h1:yhM2epWtAmel9CB8r2+L+PCmhH6yH2pITaPAo7jxJl0= 17 | github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= 18 | github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= 19 | github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w= 20 | github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= 21 | github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= 22 | github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= 23 | github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 24 | github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= 25 | github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 26 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 27 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 28 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= 29 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= 30 | github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= 31 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 32 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 33 | github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 34 | github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 35 | github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= 36 | github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= 37 | github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY= 38 | github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs= 39 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 40 | github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= 41 | github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= 42 | github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= 43 | github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= 44 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 45 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 46 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 47 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 48 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= 49 | github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= 50 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 51 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 52 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 53 | github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= 54 | github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= 55 | github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 56 | github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= 57 | github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 58 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 59 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 60 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= 61 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= 62 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 63 | github.com/eapache/go-resiliency v1.3.0 h1:RRL0nge+cWGlxXbUzJ7yMcq6w2XBEr19dCN6HECGaT0= 64 | github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= 65 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 h1:8yY/I9ndfrgrXUbOGObLHKBR4Fl3nZXwM2c7OYTT8hM= 66 | github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= 67 | github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= 68 | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= 69 | github.com/elastic/go-elasticsearch/v7 v7.17.10 h1:TCQ8i4PmIJuBunvBS6bwT2ybzVFxxUhhltAs3Gyu1yo= 70 | github.com/elastic/go-elasticsearch/v7 v7.17.10/go.mod h1:OJ4wdbtDNk5g503kvlHLyErCgQwwzmDtaFC4XyOxXA4= 71 | github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= 72 | github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 73 | github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 74 | github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e h1:CQn2/8fi3kmpT9BTiHEELgdxAOQNVZc9GoPA4qnQzrs= 75 | github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 76 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 77 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 78 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 79 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 80 | github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= 81 | github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= 82 | github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= 83 | github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= 84 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 85 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 86 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 87 | github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= 88 | github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= 89 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 90 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 91 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 92 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 93 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= 94 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 95 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= 96 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 97 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= 98 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 99 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 100 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 101 | github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= 102 | github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= 103 | github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 104 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 105 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 106 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 107 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 108 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 109 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 110 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 111 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 112 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 113 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 114 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 115 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 116 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 117 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 118 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 119 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 120 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 121 | github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 122 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 123 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 124 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 125 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 126 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 127 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 128 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 129 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 130 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 131 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 132 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 133 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 134 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 135 | github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= 136 | github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= 137 | github.com/google/go-github/v50 v50.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA= 138 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 139 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 140 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 141 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 142 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= 143 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 144 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 145 | github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 146 | github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= 147 | github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 148 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 149 | github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= 150 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 151 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 152 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 153 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 154 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 155 | github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 156 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 157 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 158 | github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= 159 | github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 160 | github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= 161 | github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 162 | github.com/haxii/fastproxy v0.5.37 h1:grfso8V9sNO8jZjI/vkizFXzz8fE/yrsgl+XxTb3fio= 163 | github.com/haxii/fastproxy v0.5.37/go.mod h1:VFy3M4EmTbeKu+IccQ6UiJ1W4sPeQ75GV+1JDa8h864= 164 | github.com/haxii/log v1.0.0/go.mod h1:y9MlOm+u2ny65yQxScWfSGZFOhRVLXz2vJlkiIx2jfI= 165 | github.com/hdm/jarm-go v0.0.7 h1:Eq0geenHrBSYuKrdVhrBdMMzOmA+CAMLzN2WrF3eL6A= 166 | github.com/hdm/jarm-go v0.0.7/go.mod h1:kinGoS0+Sdn1Rr54OtanET5E5n7AlD6T6CrJAKDjJSQ= 167 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 168 | github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= 169 | github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= 170 | github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= 171 | github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= 172 | github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= 173 | github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= 174 | github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= 175 | github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= 176 | github.com/jcmturner/gokrb5/v8 v8.4.3 h1:iTonLeSJOn7MVUtyMT+arAn5AKAPrkilzhGw8wE/Tq8= 177 | github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0= 178 | github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= 179 | github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= 180 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 181 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 182 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 183 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 184 | github.com/kataras/jwt v0.1.8 h1:u71baOsYD22HWeSOg32tCHbczPjdCk7V4MMeJqTtmGk= 185 | github.com/kataras/jwt v0.1.8/go.mod h1:Q5j2IkcIHnfwy+oNY3TVWuEBJNw0ADgCcXK9CaZwV4o= 186 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 187 | github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 188 | github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= 189 | github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 190 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 191 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 192 | github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= 193 | github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 194 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 195 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 196 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 197 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 198 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 199 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 200 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 201 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 202 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= 203 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 204 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 205 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 206 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 207 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 208 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 209 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 210 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 211 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 212 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 213 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 214 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 215 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 216 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 217 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 218 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 219 | github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= 220 | github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= 221 | github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= 222 | github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= 223 | github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= 224 | github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= 225 | github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= 226 | github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= 227 | github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc= 228 | github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= 229 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 230 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 231 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 232 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 233 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 234 | github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= 235 | github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= 236 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= 237 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= 238 | github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= 239 | github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= 240 | github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= 241 | github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 242 | github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= 243 | github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 244 | github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= 245 | github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= 246 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 247 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 248 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 249 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 250 | github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= 251 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 252 | github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= 253 | github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= 254 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 255 | github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= 256 | github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= 257 | github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= 258 | github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 259 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= 260 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 261 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 262 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 263 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 264 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 265 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 266 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 267 | github.com/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ= 268 | github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= 269 | github.com/projectdiscovery/dsl v0.0.47 h1:DdPfJvUWoOSQ/+H3tsfaURZRaCtsiGBkIPf92YM4Drs= 270 | github.com/projectdiscovery/dsl v0.0.47/go.mod h1:dU3Mm5lpxgBIFKXbOMVoLQHklXml0PUclrEHYh45LE0= 271 | github.com/projectdiscovery/fastdialer v0.0.62 h1:Wyba2hD6ZF3S04MgCn380mC+1RXJ+dq14Yq8u2yk7ps= 272 | github.com/projectdiscovery/fastdialer v0.0.62/go.mod h1:2baj2TRXTw+hHbKTW9IZR4dhpxCGJkq5AKL1ge5gis8= 273 | github.com/projectdiscovery/goflags v0.1.43 h1:02EyzlLUm4UAwQM9boBHeWPg1f/XdUa43N5e7YchiUM= 274 | github.com/projectdiscovery/goflags v0.1.43/go.mod h1:I8FvblBsdXWOooTlsv6J5jOrnTtksiCCbZ1wvEkKCao= 275 | github.com/projectdiscovery/gologger v1.1.12 h1:uX/QkQdip4PubJjjG0+uk5DtyAi1ANPJUvpmimXqv4A= 276 | github.com/projectdiscovery/gologger v1.1.12/go.mod h1:DI8nywPLERS5mo8QEA9E7gd5HZ3Je14SjJBH3F5/kLw= 277 | github.com/projectdiscovery/gostruct v0.0.2 h1:s8gP8ApugGM4go1pA+sVlPDXaWqNP5BBDDSv7VEdG1M= 278 | github.com/projectdiscovery/gostruct v0.0.2/go.mod h1:H86peL4HKwMXcQQtEa6lmC8FuD9XFt6gkNR0B/Mu5PE= 279 | github.com/projectdiscovery/hmap v0.0.41 h1:8IgTyDce3/2JzcfPVA4H+XpBRFfETULx8td3BMdSYVE= 280 | github.com/projectdiscovery/hmap v0.0.41/go.mod h1:bCrai6x5Eijqm2U+jtcH0wZX5ZcaZhcvzoMGTZgLAf0= 281 | github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983 h1:ZScLodGSezQVwsQDtBSMFp72WDq0nNN+KE/5DHKY5QE= 282 | github.com/projectdiscovery/machineid v0.0.0-20240226150047-2e2c51e35983/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI= 283 | github.com/projectdiscovery/mapcidr v1.1.16 h1:rjj1w5D6hbTsUQXYClLcGdfBEy9bryclgi70t0vBggo= 284 | github.com/projectdiscovery/mapcidr v1.1.16/go.mod h1:rGqpBhStdwOQ2uS62QM9qPsybwMwIhT7CTd2bxoHs8Q= 285 | github.com/projectdiscovery/martian/v3 v3.0.0-20240219194442-fed3b744f477 h1:VJaBELAC5Hw+kc+ylrFF5nSf7Wasnb9mZxMlF/kR3gg= 286 | github.com/projectdiscovery/martian/v3 v3.0.0-20240219194442-fed3b744f477/go.mod h1:wPvVUl2C/XOFacugXwsUp65GN0upmKfwKMyimA/AAaM= 287 | github.com/projectdiscovery/networkpolicy v0.0.8 h1:XvfBaBwSDNTesSfNQP9VLk3HX9I7x7gHm028TJ5XwI8= 288 | github.com/projectdiscovery/networkpolicy v0.0.8/go.mod h1:xnjNqhemxUPxU+UD5Jgsc3+K8IVmcqT1SJeo6UzMtkI= 289 | github.com/projectdiscovery/retryabledns v1.0.58 h1:ut1FSB9+GZ6zQIlKJFLqIz2RZs81EmkbsHTuIrWfYLE= 290 | github.com/projectdiscovery/retryabledns v1.0.58/go.mod h1:RobmKoNBgngAVE4H9REQtaLP1pa4TCyypHy1MWHT1mY= 291 | github.com/projectdiscovery/roundrobin v0.0.6 h1:zoJAFRgP9XK7B+iKSjR+djRAuDYxnc57+Fx+qpoPvds= 292 | github.com/projectdiscovery/roundrobin v0.0.6/go.mod h1:vTxcWqNLyMH6VE2Q/hsNNvDHFLiIzHozC1rLLT/vocQ= 293 | github.com/projectdiscovery/tinydns v0.0.31 h1:iQL1ze2/4PHWH+Nzsxg35kVIWkvEIatAZCDcP0c1/v4= 294 | github.com/projectdiscovery/tinydns v0.0.31/go.mod h1:6rj/dEzXZPn+nLl4O1wUMJdfkdUQ9eCV7NsDWPakYv8= 295 | github.com/projectdiscovery/utils v0.0.83 h1:r7OBAuEwe4lyEwTITbCEZytoxvjk/s0Xra2NT+K4fm4= 296 | github.com/projectdiscovery/utils v0.0.83/go.mod h1:2XFoaGD5NPUp6liTRHC2tGmMQnIhQSXscpP3zfAG7iE= 297 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 298 | github.com/quic-go/quic-go v0.37.7 h1:AgKsQLZ1+YCwZd2GYhBUsJDYZwEkA5gENtAjb+MxONU= 299 | github.com/quic-go/quic-go v0.37.7/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= 300 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= 301 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= 302 | github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o= 303 | github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= 304 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 305 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 306 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= 307 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 308 | github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= 309 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 310 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 311 | github.com/rs/zerolog v1.11.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 312 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= 313 | github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= 314 | github.com/sashabaranov/go-openai v1.14.2 h1:5DPTtR9JBjKPJS008/A409I5ntFhUPPGCmaAihcPRyo= 315 | github.com/sashabaranov/go-openai v1.14.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= 316 | github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= 317 | github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4= 318 | github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= 319 | github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= 320 | github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= 321 | github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= 322 | github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 323 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 324 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 325 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= 326 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 327 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 328 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 329 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 330 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 331 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 332 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 333 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 334 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 335 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 336 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 337 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 338 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 339 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 340 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 341 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= 342 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 343 | github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= 344 | github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8= 345 | github.com/tidwall/btree v1.6.0 h1:LDZfKfQIBHGHWSwckhXI0RPSXzlo+KYdjK7FWSqOzzg= 346 | github.com/tidwall/btree v1.6.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= 347 | github.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA= 348 | github.com/tidwall/buntdb v1.3.0/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU= 349 | github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 350 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 351 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 352 | github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= 353 | github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= 354 | github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= 355 | github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= 356 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 357 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 358 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 359 | github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= 360 | github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 361 | github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= 362 | github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= 363 | github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= 364 | github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= 365 | github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= 366 | github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= 367 | github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= 368 | github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= 369 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 370 | github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 371 | github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= 372 | github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 373 | github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= 374 | github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db h1:/WcxBne+5CbtbgWd/sV2wbravmr4sT7y52ifQaCgoLs= 375 | github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db/go.mod h1:aiQaH1XpzIfgrJq3S1iw7w+3EDbRP7mF5fmwUhWyRUs= 376 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 377 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 378 | github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= 379 | github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= 380 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 381 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 382 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 383 | github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 384 | github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= 385 | github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 386 | github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= 387 | github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= 388 | github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= 389 | github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 390 | github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= 391 | github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30= 392 | github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= 393 | github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= 394 | github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk= 395 | github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= 396 | github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= 397 | github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 h1:YOQ1vXEwE4Rnj+uQ/3oCuJk5wgVsvUyW+glsndwYuyA= 398 | github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968/go.mod h1:xIuOvYCZX21S5Z9bK1BMrertTGX/F8hgAPw7ERJRNS0= 399 | github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8= 400 | go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= 401 | go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= 402 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 403 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 404 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 405 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 406 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 407 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 408 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 409 | golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 410 | golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 411 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 412 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 413 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 414 | golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 415 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 416 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 417 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 418 | golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= 419 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 420 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 421 | golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo= 422 | golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 423 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 424 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 425 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 426 | golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 427 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 428 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 429 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 430 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 431 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 432 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 433 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 434 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 435 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 436 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 437 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 438 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 439 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 440 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 441 | golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 442 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 443 | golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 444 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 445 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 446 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 447 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 448 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 449 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 450 | golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= 451 | golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 452 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 453 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 454 | golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= 455 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 456 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 457 | golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= 458 | golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= 459 | golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= 460 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 461 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 462 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 463 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 464 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 465 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 466 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 467 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 468 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 469 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 470 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 471 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 472 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 473 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 474 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 475 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 476 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 477 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 478 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 479 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 480 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 481 | golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 482 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 483 | golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 484 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 485 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 486 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 487 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 488 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 489 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 490 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 491 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 492 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 493 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 494 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 495 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 496 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 497 | golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= 498 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 499 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 500 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 501 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 502 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 503 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 504 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 505 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 506 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 507 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 508 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 509 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 510 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 511 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 512 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 513 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 514 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 515 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 516 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 517 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 518 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 519 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 520 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 521 | golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 522 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 523 | golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 524 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 525 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 526 | golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= 527 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 528 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 529 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 530 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 531 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 532 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= 533 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= 534 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 535 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 536 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 537 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 538 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 539 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 540 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 541 | google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 h1:R1r5J0u6Cx+RNl/6mezTw6oA14cmKC96FeUwL6A9bd4= 542 | google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= 543 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 544 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 545 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 546 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 547 | google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0= 548 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= 549 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 550 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 551 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 552 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 553 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 554 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 555 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 556 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 557 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 558 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 559 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 560 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 561 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 562 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 563 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 564 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 565 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 566 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 567 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 568 | gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o= 569 | gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= 570 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 571 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 572 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 573 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 574 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 575 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 576 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 577 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 578 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 579 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 580 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 581 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 582 | -------------------------------------------------------------------------------- /internal/runner/banner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "github.com/projectdiscovery/gologger" 5 | updateutils "github.com/projectdiscovery/utils/update" 6 | ) 7 | 8 | const banner = ` 9 | _ ___ 10 | ___ _______ __ __ (_) _/_ __ 11 | / _ \/ __/ _ \\ \ // / _/ // / 12 | / .__/_/ \___/_\_\/_/_/ \_, / 13 | /_/ /___/ 14 | ` 15 | 16 | // Version is the current version 17 | const version = `v0.0.15` 18 | 19 | // showBanner is used to show the banner to the user 20 | func showBanner() { 21 | gologger.Print().Msgf("%s\n", banner) 22 | gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n") 23 | } 24 | 25 | // GetUpdateCallback returns a callback function that updates proxify 26 | func GetUpdateCallback() func() { 27 | return func() { 28 | showBanner() 29 | updateutils.GetUpdateToolCallback("proxify", version)() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/runner/doc.go: -------------------------------------------------------------------------------- 1 | // Package runner contains the internal logic 2 | package runner 3 | -------------------------------------------------------------------------------- /internal/runner/options.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "math" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/projectdiscovery/goflags" 10 | "github.com/projectdiscovery/gologger" 11 | "github.com/projectdiscovery/gologger/formatter" 12 | "github.com/projectdiscovery/gologger/levels" 13 | "github.com/projectdiscovery/proxify/pkg/logger" 14 | "github.com/projectdiscovery/proxify/pkg/logger/elastic" 15 | "github.com/projectdiscovery/proxify/pkg/logger/kafka" 16 | "github.com/projectdiscovery/proxify/pkg/types" 17 | errorutil "github.com/projectdiscovery/utils/errors" 18 | fileutil "github.com/projectdiscovery/utils/file" 19 | permissionutil "github.com/projectdiscovery/utils/permission" 20 | updateutils "github.com/projectdiscovery/utils/update" 21 | "gopkg.in/yaml.v2" 22 | ) 23 | 24 | var ( 25 | cfgFile string 26 | ) 27 | 28 | // Options of the runner 29 | type Options struct { 30 | OutputDirectory string 31 | OutputFile string // for storing the jsonl output 32 | OutputFormat string 33 | LoggerConfig string 34 | ConfigDir string 35 | CertCacheSize int 36 | Verbosity types.Verbosity 37 | Version bool 38 | ListenAddrHTTP string 39 | ListenAddrSocks5 string 40 | ListenDNSAddr string 41 | DNSMapping string // DNSMapping contains user provided hosts 42 | DNSFallbackResolver string // Listen DNS Ip and port (ip:port) 43 | NoColor bool // No Color 44 | RequestDSL goflags.StringSlice // Request Filter DSL 45 | RequestMatchReplaceDSL goflags.StringSlice // Request Match-Replace DSL 46 | ResponseDSL goflags.StringSlice // Response Filter DSL 47 | ResponseMatchReplaceDSL goflags.StringSlice // Request Match-Replace DSL 48 | UpstreamHTTPProxies goflags.StringSlice // Upstream HTTP comma separated Proxies (e.g. http://proxyip:proxyport) 49 | UpstreamSocks5Proxies goflags.StringSlice // Upstream SOCKS5 comma separated Proxies (e.g. socks5://proxyip:proxyport) 50 | UpstreamProxyRequestsNumber int // Number of requests before switching upstream proxy 51 | DumpRequest bool // Dump requests in separate files 52 | DumpResponse bool // Dump responses in separate files 53 | OutCAFile string 54 | Deny goflags.StringSlice // Deny ip/cidr 55 | Allow goflags.StringSlice // Allow ip/cidr 56 | Elastic elastic.Options 57 | Kafka kafka.Options 58 | PassThrough goflags.StringSlice // Passthrough items list 59 | MaxSize int 60 | DisableUpdateCheck bool // DisableUpdateCheck disables automatic update check 61 | OutputJsonl bool // OutputJsonl outputs data in JSONL format 62 | } 63 | 64 | func ParseOptions() (*Options, error) { 65 | homeDir, err := os.UserHomeDir() 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | options := &Options{} 71 | 72 | flagSet := goflags.NewFlagSet() 73 | flagSet.SetDescription(`Swiss Army Knife Proxy for rapid deployments. Supports multiple operations such as request/response dump,filtering and manipulation via DSL language, upstream HTTP/Socks5 proxy`) 74 | 75 | flagSet.CreateGroup("output", "Output", 76 | // Todo: flagSet.BoolVar(&options.Dump, "dump", true, "Dump HTTP requests/response to output file"), 77 | flagSet.DynamicVarP(&options.OutputDirectory, "store-response", "sr", "proxify_logs", "store raw http request / response to output directory"), 78 | flagSet.DynamicVarP(&options.OutputFile, "output", "o", "proxify_logs.jsonl", "output file to store proxify logs"), 79 | flagSet.StringVarP(&options.OutputFormat, "output-format", "of", "jsonl", "output format (jsonl/yaml)"), 80 | flagSet.BoolVar(&options.DumpRequest, "dump-req", false, "Dump only HTTP requests to output file"), 81 | flagSet.BoolVar(&options.DumpResponse, "dump-resp", false, "Dump only HTTP responses to output file"), 82 | flagSet.StringVarP(&options.OutCAFile, "out-ca", "oca", "", "Generate and Save CA File to filename"), 83 | ) 84 | 85 | flagSet.CreateGroup("update", "Update", 86 | flagSet.CallbackVarP(GetUpdateCallback(), "update", "up", "update proxify to latest version"), 87 | flagSet.BoolVarP(&options.DisableUpdateCheck, "disable-update-check", "duc", false, "disable automatic proxify update check"), 88 | ) 89 | 90 | flagSet.CreateGroup("filter", "Filter", 91 | flagSet.StringSliceVarP(&options.RequestDSL, "request-dsl", "req-fd", nil, "Request Filter DSL", goflags.StringSliceOptions), 92 | flagSet.StringSliceVarP(&options.ResponseDSL, "response-dsl", "resp-fd", nil, "Response Filter DSL", goflags.StringSliceOptions), 93 | flagSet.StringSliceVarP(&options.RequestMatchReplaceDSL, "request-match-replace-dsl", "req-mrd", nil, "Request Match-Replace DSL", goflags.StringSliceOptions), 94 | flagSet.StringSliceVarP(&options.ResponseMatchReplaceDSL, "response-match-replace-dsl", "resp-mrd", nil, "Response Match-Replace DSL", goflags.StringSliceOptions), 95 | ) 96 | 97 | flagSet.CreateGroup("network", "Network", 98 | flagSet.StringVarP(&options.ListenAddrHTTP, "http-addr", "ha", "127.0.0.1:8888", "Listening HTTP IP and Port address (ip:port)"), 99 | flagSet.DynamicVarP(&options.ListenAddrSocks5, "socks-addr", "sa", "127.0.0.1:10080", "Listening SOCKS IP and Port address (ip:port)"), 100 | flagSet.StringVarP(&options.ListenDNSAddr, "dns-addr", "da", "", "Listening DNS IP and Port address (ip:port)"), 101 | flagSet.StringVarP(&options.DNSMapping, "dns-mapping", "dm", "", "Domain to IP DNS mapping (eg domain:ip,domain:ip,..)"), 102 | flagSet.StringVarP(&options.DNSFallbackResolver, "resolver", "r", "", "Custom DNS resolvers to use (ip:port)"), 103 | ) 104 | 105 | flagSet.CreateGroup("proxy", "Proxy", 106 | flagSet.StringSliceVarP(&options.UpstreamHTTPProxies, "http-proxy", "hp", nil, "Upstream HTTP Proxies (eg http://proxy-ip:proxy-port)", goflags.NormalizedStringSliceOptions), 107 | flagSet.StringSliceVarP(&options.UpstreamSocks5Proxies, "socks5-proxy", "sp", nil, "Upstream SOCKS5 Proxies (eg socks5://proxy-ip:proxy-port)", goflags.NormalizedStringSliceOptions), 108 | flagSet.IntVar(&options.UpstreamProxyRequestsNumber, "c", 1, "Number of requests before switching to the next upstream proxy"), 109 | ) 110 | 111 | flagSet.CreateGroup("export", "Export", 112 | flagSet.IntVar(&options.MaxSize, "max-size", math.MaxInt, "Max export data size (request/responses will be truncated)"), 113 | ) 114 | 115 | flagSet.CreateGroup("configuration", "Configuration", 116 | flagSet.StringVar(&cfgFile, "config", "", "path to the proxify configuration file"), 117 | flagSet.StringVarP(&options.LoggerConfig, "export-config", "ec", filepath.Join(homeDir, ".config", "proxify", logger.LoggerConfigFilename), "proxify export module configuration file"), 118 | flagSet.StringVar(&options.ConfigDir, "config-directory", filepath.Join(homeDir, ".config", "proxify"), "override the default config path ($home/.config/proxify)"), 119 | flagSet.IntVar(&options.CertCacheSize, "cert-cache-size", 256, "Number of certificates to cache"), 120 | flagSet.StringSliceVarP(&options.Allow, "allow", "a", nil, "Allowed list of IP/CIDR's to be proxied", goflags.FileNormalizedStringSliceOptions), 121 | flagSet.StringSliceVarP(&options.Deny, "deny", "d", nil, "Denied list of IP/CIDR's to be proxied", goflags.FileNormalizedStringSliceOptions), 122 | flagSet.StringSliceVarP(&options.PassThrough, "passthrough", "pt", nil, "List of passthrough domains", goflags.FileNormalizedStringSliceOptions), 123 | ) 124 | 125 | silent, verbose, veryVerbose := false, false, false 126 | flagSet.CreateGroup("debug", "debug", 127 | flagSet.BoolVarP(&options.NoColor, "no-color", "nc", false, "No Color"), 128 | flagSet.BoolVar(&options.Version, "version", false, "Version"), 129 | flagSet.BoolVar(&silent, "silent", false, "Silent"), 130 | flagSet.BoolVarP(&verbose, "verbose", "v", false, "Verbose"), 131 | flagSet.BoolVarP(&veryVerbose, "very-verbose", "vv", false, "Very Verbose"), 132 | ) 133 | 134 | if err := flagSet.Parse(); err != nil { 135 | return nil, err 136 | } 137 | 138 | if options.ConfigDir != "" { 139 | _ = os.MkdirAll(options.ConfigDir, permissionutil.ConfigFolderPermission) 140 | readFlagsConfig(flagSet, options.ConfigDir) 141 | } 142 | 143 | if cfgFile != "" { 144 | if !fileutil.FileExists(cfgFile) { 145 | gologger.Fatal().Msgf("given config file '%s' does not exist", cfgFile) 146 | } 147 | // merge config file with flags 148 | if err := flagSet.MergeConfigFile(cfgFile); err != nil { 149 | gologger.Fatal().Msgf("Could not read config: %s\n", err) 150 | } 151 | } 152 | 153 | if err := options.createLoggerConfigIfNotExists(); err != nil { 154 | return nil, err 155 | } 156 | 157 | if err := options.parseLoggerConfig(); err != nil { 158 | return nil, err 159 | } 160 | 161 | // Read the inputs and configure the logging 162 | options.configureVerbosity(silent, verbose, veryVerbose) 163 | options.configureOutput() 164 | 165 | if options.Version { 166 | gologger.Info().Msgf("Current Version: %s\n", version) 167 | os.Exit(0) 168 | } 169 | 170 | // Show the user the banner 171 | showBanner() 172 | 173 | if !options.DisableUpdateCheck { 174 | latestVersion, err := updateutils.GetToolVersionCallback("proxify", version)() 175 | if err != nil { 176 | if verbose { 177 | gologger.Error().Msgf("proxify version check failed: %v", err.Error()) 178 | } 179 | } else { 180 | gologger.Info().Msgf("Current proxify version %v %v", version, updateutils.GetVersionDescription(version, latestVersion)) 181 | } 182 | } 183 | options.OutputJsonl = true 184 | // if OutputFile is not set, set it to default 185 | if options.OutputFile == "" { 186 | options.OutputFile = "proxify_logs.jsonl" 187 | } 188 | 189 | if options.OutputFormat == "yaml" { 190 | options.OutputFile = strings.ReplaceAll(options.OutputFile, "jsonl", "yaml") 191 | } 192 | 193 | return options, nil 194 | } 195 | 196 | // readFlagsConfig reads the config file from the default config dir and copies it to the current config dir. 197 | func readFlagsConfig(flagset *goflags.FlagSet, configDir string) { 198 | // check if config.yaml file exists 199 | defaultCfgFile, err := flagset.GetConfigFilePath() 200 | if err != nil { 201 | // something went wrong either dir is not readable or something else went wrong upstream in `goflags` 202 | // warn and exit in this case 203 | gologger.Warning().Msgf("Could not read config file: %s\n", err) 204 | return 205 | } 206 | cfgFile := filepath.Join(configDir, "config.yaml") 207 | if !fileutil.FileExists(cfgFile) { 208 | if !fileutil.FileExists(defaultCfgFile) { 209 | // if default config does not exist, warn and exit 210 | gologger.Warning().Msgf("missing default config file : %s", defaultCfgFile) 211 | return 212 | } 213 | // if does not exist copy it from the default config 214 | if err = fileutil.CopyFile(defaultCfgFile, cfgFile); err != nil { 215 | gologger.Warning().Msgf("Could not copy config file: %s\n", err) 216 | } 217 | return 218 | } 219 | // if config file exists, merge it with the default config 220 | if err = flagset.MergeConfigFile(cfgFile); err != nil { 221 | gologger.Warning().Msgf("failed to merge configfile with flags got: %s\n", err) 222 | } 223 | } 224 | 225 | func (options *Options) configureVerbosity(silent, verbose, veryVerbose bool) { 226 | if silent && (verbose || veryVerbose) { 227 | gologger.Error().Msgf("The -silent flag and -v/-vv flags cannot be set together\n") 228 | os.Exit(1) 229 | } 230 | 231 | if silent { 232 | options.Verbosity = types.VerbositySilent 233 | } else if veryVerbose { 234 | options.Verbosity = types.VerbosityVeryVerbose 235 | } else if verbose { 236 | options.Verbosity = types.VerbosityVerbose 237 | } else { 238 | options.Verbosity = types.VerbosityDefault 239 | } 240 | } 241 | 242 | func (options *Options) configureOutput() { 243 | if options.Verbosity <= types.VerbositySilent { 244 | gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) 245 | } else if options.Verbosity >= types.VerbosityVerbose { 246 | gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) 247 | } 248 | if options.NoColor { 249 | gologger.DefaultLogger.SetFormatter(formatter.NewCLI(true)) 250 | } 251 | } 252 | 253 | // createLoggerConfigIfNotExists creates export-config if it doesn't exists 254 | func (options *Options) createLoggerConfigIfNotExists() error { 255 | if fileutil.FileExists(options.LoggerConfig) { 256 | return nil 257 | } 258 | 259 | config := &logger.Config{ 260 | Elastic: elastic.Options{}, 261 | Kafka: kafka.Options{}, 262 | } 263 | loggerConfigFile, err := os.Create(options.LoggerConfig) 264 | if err != nil { 265 | return errorutil.NewWithErr(err).Msgf("could not create config file") 266 | } 267 | defer loggerConfigFile.Close() 268 | 269 | err = yaml.NewEncoder(loggerConfigFile).Encode(config) 270 | return err 271 | } 272 | 273 | // parseLoggerConfig parses the logger configuration file 274 | func (options *Options) parseLoggerConfig() error { 275 | var config logger.Config 276 | 277 | data, err := os.ReadFile(options.LoggerConfig) 278 | if err != nil { 279 | return err 280 | } 281 | 282 | expandedData := os.ExpandEnv(string(data)) 283 | err = yaml.Unmarshal([]byte(expandedData), &config) 284 | if err != nil { 285 | return err 286 | } 287 | 288 | options.Kafka = config.Kafka 289 | options.Elastic = config.Elastic 290 | 291 | return nil 292 | } 293 | -------------------------------------------------------------------------------- /internal/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/Knetic/govaluate" 8 | "github.com/projectdiscovery/dsl" 9 | "github.com/projectdiscovery/gologger" 10 | "github.com/projectdiscovery/proxify" 11 | "github.com/projectdiscovery/proxify/pkg/certs" 12 | ) 13 | 14 | // Runner contains the internal logic of the program 15 | type Runner struct { 16 | options *Options 17 | proxy *proxify.Proxy 18 | } 19 | 20 | // NewRunner instance 21 | func NewRunner(options *Options) (*Runner, error) { 22 | if err := certs.LoadCerts(options.ConfigDir); err != nil { 23 | gologger.Fatal().Msgf("%s\n", err) 24 | } 25 | 26 | if options.OutCAFile != "" { 27 | err := certs.SaveCAToFile(options.OutCAFile) 28 | if err != nil { 29 | return nil, err 30 | } 31 | gologger.Print().Msgf("Saved CA File at %v", options.OutCAFile) 32 | os.Exit(0) 33 | } 34 | 35 | proxy, err := proxify.NewProxy(&proxify.Options{ 36 | Directory: options.ConfigDir, 37 | CertCacheSize: options.CertCacheSize, 38 | Verbosity: options.Verbosity, 39 | ListenAddrHTTP: options.ListenAddrHTTP, 40 | ListenAddrSocks5: options.ListenAddrSocks5, 41 | OutputFile: options.OutputFile, 42 | OutputFormat: options.OutputFormat, 43 | OutputDirectory: options.OutputDirectory, 44 | RequestDSL: options.RequestDSL, 45 | ResponseDSL: options.ResponseDSL, 46 | UpstreamHTTPProxies: options.UpstreamHTTPProxies, 47 | UpstreamSock5Proxies: options.UpstreamSocks5Proxies, 48 | ListenDNSAddr: options.ListenDNSAddr, 49 | DNSMapping: options.DNSMapping, 50 | DNSFallbackResolver: options.DNSFallbackResolver, 51 | RequestMatchReplaceDSL: options.RequestMatchReplaceDSL, 52 | ResponseMatchReplaceDSL: options.ResponseMatchReplaceDSL, 53 | DumpRequest: options.DumpRequest, 54 | DumpResponse: options.DumpResponse, 55 | OutputJsonl: options.OutputJsonl, 56 | MaxSize: options.MaxSize, 57 | UpstreamProxyRequestsNumber: options.UpstreamProxyRequestsNumber, 58 | Elastic: &options.Elastic, 59 | Kafka: &options.Kafka, 60 | Allow: options.Allow, 61 | Deny: options.Deny, 62 | PassThrough: options.PassThrough, 63 | }) 64 | if err != nil { 65 | return nil, err 66 | } 67 | return &Runner{options: options, proxy: proxy}, nil 68 | } 69 | 70 | func (r *Runner) validateExpressions(expressionsGroups ...[]string) error { 71 | for _, expressionsGroup := range expressionsGroups { 72 | for _, expression := range expressionsGroup { 73 | if _, err := govaluate.NewEvaluableExpressionWithFunctions(expression, dsl.DefaultHelperFunctions); err != nil { 74 | printDslCompileError(err) 75 | return err 76 | } 77 | } 78 | } 79 | return nil 80 | } 81 | 82 | // Run polling and notification 83 | func (r *Runner) Run() error { 84 | 85 | if err := r.validateExpressions(r.options.RequestDSL, r.options.ResponseDSL, r.options.RequestMatchReplaceDSL, r.options.ResponseMatchReplaceDSL); err != nil { 86 | return err 87 | } 88 | 89 | // configuration summary 90 | if r.options.ListenAddrHTTP != "" { 91 | gologger.Info().Msgf("HTTP Proxy Listening on %s\n", r.options.ListenAddrHTTP) 92 | } 93 | if r.options.ListenAddrSocks5 != "" { 94 | gologger.Info().Msgf("Socks5 Proxy Listening on %s\n", r.options.ListenAddrSocks5) 95 | } 96 | 97 | if r.options.OutputFile != "" { 98 | gologger.Info().Msgf("Saving proxify logs to %s\n", r.options.OutputFile) 99 | } 100 | 101 | if r.options.OutputDirectory != "" { 102 | gologger.Info().Msgf("Saving proxify logs (raw) to %s\n", r.options.OutputDirectory) 103 | } 104 | 105 | if r.options.Kafka.Addr != "" { 106 | gologger.Info().Msgf("Sending traffic to Kafka at %s\n", r.options.Kafka.Addr) 107 | } 108 | if r.options.Elastic.Addr != "" { 109 | gologger.Info().Msgf("Sending traffic to Elasticsearch at %s\n", r.options.Elastic.Addr) 110 | } 111 | 112 | if len(r.options.UpstreamHTTPProxies) > 0 { 113 | gologger.Info().Msgf("Using upstream HTTP proxies: %s\n", r.options.UpstreamHTTPProxies) 114 | } else if len(r.options.UpstreamSocks5Proxies) > 0 { 115 | gologger.Info().Msgf("Using upstream SOCKS5 proxies: %s\n", r.options.UpstreamSocks5Proxies) 116 | } 117 | 118 | if r.options.DNSMapping != "" { 119 | for _, v := range strings.Split(r.options.DNSMapping, ",") { 120 | gologger.Info().Msgf("Domain => IP: %s\n", v) 121 | } 122 | 123 | if r.options.DNSFallbackResolver != "" { 124 | gologger.Info().Msgf("Fallback Resolver: %s\n", r.options.DNSFallbackResolver) 125 | } 126 | 127 | } 128 | 129 | return r.proxy.Run() 130 | } 131 | 132 | // Close the runner instance 133 | func (r *Runner) Close() { 134 | r.proxy.Stop() 135 | } 136 | 137 | // printDslCompileError prints the error message for a DSL compilation error 138 | func printDslCompileError(err error) { 139 | gologger.Error().Msgf("error compiling DSL: %s", err) 140 | gologger.Info().Msgf("The available custom DSL functions are:") 141 | gologger.Info().Label("").Msgf(dsl.GetPrintableDslFunctionSignatures(false)) 142 | } 143 | -------------------------------------------------------------------------------- /pkg/certs/mitm.go: -------------------------------------------------------------------------------- 1 | package certs 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/pem" 8 | "errors" 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | "time" 13 | 14 | "github.com/projectdiscovery/gologger" 15 | "github.com/projectdiscovery/martian/v3/mitm" 16 | fileutil "github.com/projectdiscovery/utils/file" 17 | ) 18 | 19 | var ( 20 | cert *x509.Certificate 21 | pkey *rsa.PrivateKey 22 | ) 23 | 24 | const ( 25 | caKeyName = "cakey.pem" 26 | caCertName = "cacert.pem" 27 | bits = 2048 28 | organization = "Proxify CA" 29 | country = "US" 30 | province = "CA" 31 | locality = "San Francisco" 32 | streetAddress = "548 Market St" 33 | postalCode = "94104" 34 | ) 35 | 36 | // GetMitMConfig returns mitm config for martian 37 | func GetMitMConfig() *mitm.Config { 38 | cfg, err := mitm.NewConfig(cert, pkey) 39 | if err != nil { 40 | gologger.Fatal().Msgf("failed to create mitm config") 41 | } 42 | return cfg 43 | } 44 | 45 | func SaveCAToFile(filename string) error { 46 | buffer, err := GetRawCA() 47 | if err != nil { 48 | return err 49 | } 50 | return os.WriteFile(filename, buffer.Bytes(), 0600) 51 | } 52 | 53 | func GetRawCA() (*bytes.Buffer, error) { 54 | buffer := &bytes.Buffer{} 55 | err := pem.Encode(buffer, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return buffer, nil 60 | } 61 | 62 | func SaveKeyToFile(filename string) error { 63 | buffer := &bytes.Buffer{} 64 | err := pem.Encode(buffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(pkey)}) 65 | if err != nil { 66 | return err 67 | } 68 | return os.WriteFile(filename, buffer.Bytes(), 0600) 69 | } 70 | 71 | // generateCertificate creates new certificate 72 | func generateCertificate(certFile, keyFile string) error { 73 | var err error 74 | cert, pkey, err = mitm.NewAuthority("Proxify CA", organization, time.Duration(24*365)*time.Hour) 75 | if err != nil { 76 | gologger.Fatal().Msgf("failed to generate CA Certificate") 77 | } 78 | if err = SaveCAToFile(certFile); err != nil { 79 | gologger.Fatal().Msgf("failed to save certFile to disk got %v", err) 80 | } 81 | if err := SaveKeyToFile(keyFile); err != nil { 82 | gologger.Fatal().Msgf("failed to write private key to file got %v", err) 83 | } 84 | return nil 85 | } 86 | 87 | func readCertNKeyFromDisk(certFile, keyFile string) error { 88 | block, err := readPemFromDisk(certFile) 89 | if err != nil { 90 | return err 91 | } 92 | cert, err = x509.ParseCertificate(block.Bytes) 93 | if err != nil { 94 | return err 95 | } 96 | if time.Now().After(cert.NotAfter) { 97 | return fmt.Errorf("expired certificate found") 98 | } 99 | block, err = readPemFromDisk(keyFile) 100 | if err != nil { 101 | return err 102 | } 103 | pkey, err = x509.ParsePKCS1PrivateKey(block.Bytes) 104 | if err != nil { 105 | return err 106 | } 107 | return nil 108 | } 109 | 110 | func readPemFromDisk(filename string) (*pem.Block, error) { 111 | Bin, err := os.ReadFile(filename) 112 | if err != nil { 113 | return nil, err 114 | } 115 | block, _ := pem.Decode(Bin) 116 | if block == nil { 117 | return nil, fmt.Errorf("failed to decode pem block got nil") 118 | } 119 | return block, nil 120 | } 121 | 122 | func LoadCerts(dir string) error { 123 | certFile := filepath.Join(dir, caCertName) 124 | keyFile := filepath.Join(dir, caKeyName) 125 | 126 | if !fileutil.FileExists(certFile) || !fileutil.FileExists(keyFile) { 127 | return generateCertificate(certFile, keyFile) 128 | } 129 | if err := readCertNKeyFromDisk(certFile, keyFile); err != nil { 130 | return fmt.Errorf("malformed/expired certificate found generating new ones\nNote: Certificates must be reinstalled") 131 | } 132 | if cert == nil || pkey == nil { 133 | return errors.New("something went wrong, cannot start proxify") 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /pkg/logger/elastic/elasticsearch.go: -------------------------------------------------------------------------------- 1 | package elastic 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "encoding/json" 8 | "io" 9 | "net/http" 10 | "time" 11 | 12 | elasticsearch "github.com/elastic/go-elasticsearch/v7" 13 | "github.com/elastic/go-elasticsearch/v7/esapi" 14 | "github.com/pkg/errors" 15 | "github.com/projectdiscovery/proxify/pkg/types" 16 | ) 17 | 18 | // Options contains necessary options required for elasticsearch communicaiton 19 | type Options struct { 20 | // Address for elasticsearch instance 21 | Addr string `yaml:"addr"` 22 | // SSL enables ssl for elasticsearch connection 23 | SSL bool `yaml:"ssl"` 24 | // SSLVerification disables SSL verification for elasticsearch 25 | SSLVerification bool `yaml:"ssl-verification"` 26 | // Username for the elasticsearch instance 27 | Username string `yaml:"username"` 28 | // Password is the password for elasticsearch instance 29 | Password string `yaml:"password"` 30 | // IndexName is the name of the elasticsearch index 31 | IndexName string `yaml:"index-name"` 32 | } 33 | 34 | // Client type for elasticsearch 35 | type Client struct { 36 | index string 37 | options *Options 38 | esClient *elasticsearch.Client 39 | } 40 | 41 | // New creates and returns a new client for elasticsearch 42 | func New(option *Options) (*Client, error) { 43 | scheme := "http://" 44 | if option.SSL { 45 | scheme = "https://" 46 | } 47 | 48 | elasticsearchClient, err := elasticsearch.NewClient(elasticsearch.Config{ 49 | Addresses: []string{scheme + option.Addr}, 50 | Username: option.Username, 51 | Password: option.Password, 52 | Transport: &http.Transport{ 53 | TLSClientConfig: &tls.Config{ 54 | InsecureSkipVerify: option.SSLVerification, 55 | }, 56 | }, 57 | }) 58 | if err != nil { 59 | return nil, errors.Wrap(err, "error creating elasticsearch client") 60 | } 61 | client := &Client{ 62 | esClient: elasticsearchClient, 63 | index: option.IndexName, 64 | options: option, 65 | } 66 | return client, nil 67 | 68 | } 69 | 70 | // Store saves a passed log event in elasticsearch 71 | func (c *Client) Save(data types.HTTPTransaction) error { 72 | var doc map[string]interface{} 73 | if data.Userdata.HasResponse { 74 | doc = map[string]interface{}{ 75 | "response": data.DataString, 76 | "timestamp": time.Now().Format(time.RFC3339), 77 | } 78 | } else { 79 | doc = map[string]interface{}{ 80 | "request": data.DataString, 81 | "timestamp": time.Now().Format(time.RFC3339), 82 | } 83 | } 84 | 85 | body, err := json.Marshal(&map[string]interface{}{ 86 | "doc": doc, 87 | "doc_as_upsert": true, 88 | }) 89 | if err != nil { 90 | return err 91 | } 92 | updateRequest := esapi.UpdateRequest{ 93 | Index: c.index, 94 | DocumentID: data.Name, 95 | Body: bytes.NewReader(body), 96 | } 97 | res, err := updateRequest.Do(context.Background(), c.esClient) 98 | if err != nil || res == nil { 99 | return errors.New("error thrown by elasticsearch: " + err.Error()) 100 | } 101 | if res.StatusCode >= 300 { 102 | return errors.New("elasticsearch responded with an error: " + string(res.String())) 103 | } 104 | // Drain response to reuse connection 105 | _, er := io.Copy(io.Discard, res.Body) 106 | res.Body.Close() 107 | return er 108 | } 109 | -------------------------------------------------------------------------------- /pkg/logger/file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/projectdiscovery/proxify/pkg/types" 9 | fileutil "github.com/projectdiscovery/utils/file" 10 | ) 11 | 12 | // Options required for file export 13 | type Options struct { 14 | // OutputFolder is the folder where the logs will be stored 15 | OutputFolder string `yaml:"output-folder"` 16 | // OutputFile is the file where the jsonl logs will be stored 17 | OutputFile string `yaml:"output-file"` 18 | OutputJsonl bool `yaml:"output-jsonl"` 19 | } 20 | 21 | // Client type for file based logging 22 | type Client struct { 23 | options *Options 24 | } 25 | 26 | // New creates and returns a new client for file based logging 27 | func New(option *Options) (*Client, error) { 28 | client := &Client{options: option} 29 | if option.OutputFolder != "" { 30 | if err := fileutil.CreateFolder(option.OutputFolder); err != nil { 31 | return client, err 32 | } 33 | } 34 | if option.OutputFile != "" { 35 | file, err := os.Create(option.OutputFile) 36 | if err != nil { 37 | return client, err 38 | } 39 | defer file.Close() 40 | } 41 | return client, nil 42 | } 43 | 44 | // Store writes the log to the file 45 | func (c *Client) Save(data types.HTTPTransaction) error { 46 | var err error 47 | logFile := fmt.Sprintf("%s.%s", data.Name, "txt") 48 | if c.options.OutputFolder != "" { 49 | err = c.writeToFile(filepath.Join(c.options.OutputFolder, logFile), string(data.RawData)) 50 | } 51 | if c.options.OutputFile != "" && len(data.Data) > 0 { 52 | err = c.writeToFile(c.options.OutputFile, data.DataString) 53 | } 54 | return err 55 | } 56 | 57 | func (c *Client) writeToFile(filepath, content string) error { 58 | f, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 59 | if err != nil { 60 | return err 61 | } 62 | // write to file 63 | fmt.Fprint(f, content) 64 | return f.Close() 65 | } 66 | -------------------------------------------------------------------------------- /pkg/logger/jsonl/jsonl.go: -------------------------------------------------------------------------------- 1 | package jsonl 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "strings" 7 | 8 | "github.com/projectdiscovery/proxify/pkg/types" 9 | ) 10 | 11 | // JsonLinesWriter is a writer for json lines 12 | type JsonLinesWriter struct { 13 | f *os.File 14 | } 15 | 16 | // NewJsonLinesWriter creates a new json lines writer 17 | func NewJsonLinesWriter(filePath string) (*JsonLinesWriter, error) { 18 | file, err := os.Create(filePath) 19 | if err != nil { 20 | return nil, err 21 | } 22 | return &JsonLinesWriter{f: file}, nil 23 | } 24 | 25 | // Write writes a http transaction to the file. 26 | func (j *JsonLinesWriter) Write(data *types.HTTPRequestResponseLog) error { 27 | binx, err := json.Marshal(data) 28 | if err != nil { 29 | return err 30 | } 31 | _, _ = j.f.WriteString(strings.ReplaceAll(string(binx), "\n", "\\n")) // escape new lines 32 | _, _ = j.f.WriteString("\n") 33 | return nil 34 | } 35 | 36 | // Close closes the file writer. 37 | func (j *JsonLinesWriter) Close() error { 38 | return j.f.Close() 39 | } 40 | -------------------------------------------------------------------------------- /pkg/logger/kafka/kafka.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "github.com/Shopify/sarama" 5 | "github.com/projectdiscovery/proxify/pkg/types" 6 | ) 7 | 8 | // Options required for kafka 9 | type Options struct { 10 | // Address for kafka instance 11 | Addr string `yaml:"addr"` 12 | // Topic to produce messages to 13 | Topic string `yaml:"topic"` 14 | } 15 | 16 | // Client for Kafka 17 | type Client struct { 18 | producer sarama.SyncProducer 19 | topic string 20 | } 21 | 22 | // New creates and returns a new client for kafka 23 | func New(option *Options) (*Client, error) { 24 | 25 | config := sarama.NewConfig() 26 | // Wait for all in-sync replicas to ack the message 27 | config.Producer.RequiredAcks = sarama.WaitForAll 28 | // Retry up to 10 times to produce the message 29 | config.Producer.Retry.Max = 10 30 | config.Producer.Return.Successes = true 31 | 32 | producer, err := sarama.NewSyncProducer([]string{option.Addr}, config) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return &Client{ 37 | producer: producer, 38 | topic: option.Topic, 39 | }, nil 40 | } 41 | 42 | // Store passes the message to kafka 43 | func (c *Client) Save(data types.HTTPTransaction) error { 44 | 45 | msg := &sarama.ProducerMessage{ 46 | Topic: c.topic, 47 | Value: sarama.StringEncoder(data.DataString), 48 | } 49 | 50 | _, _, err := c.producer.SendMessage(msg) 51 | if err != nil { 52 | return err 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/http/httputil" 8 | "strings" 9 | "time" 10 | 11 | "github.com/asaskevich/govalidator" 12 | "github.com/projectdiscovery/gologger" 13 | "github.com/projectdiscovery/proxify/pkg/logger/elastic" 14 | "github.com/projectdiscovery/proxify/pkg/logger/file" 15 | "github.com/projectdiscovery/proxify/pkg/logger/kafka" 16 | "github.com/projectdiscovery/utils/conversion" 17 | pdhttpUtils "github.com/projectdiscovery/utils/http" 18 | stringsutil "github.com/projectdiscovery/utils/strings" 19 | 20 | "github.com/projectdiscovery/proxify/pkg/types" 21 | ) 22 | 23 | const ( 24 | dataWithNewLine = "%s\n" 25 | dataWithoutNewLine = "%s" 26 | LoggerConfigFilename = "export-config.yaml" 27 | ) 28 | 29 | type OptionsLogger struct { 30 | Verbosity types.Verbosity 31 | OutputFolder string // when output is written to multiple files 32 | OutputFile string // when output is written to single file 33 | OutputFormat string // jsonl or yaml 34 | DumpRequest bool // dump request to file 35 | DumpResponse bool // dump response to file 36 | MaxSize int // max size of the output 37 | Elastic *elastic.Options 38 | Kafka *kafka.Options 39 | } 40 | 41 | type Store interface { 42 | Save(data types.HTTPTransaction) error 43 | } 44 | 45 | type Logger struct { 46 | options *OptionsLogger 47 | asyncqueue chan types.HTTPTransaction 48 | Store []Store 49 | sWriter OutputFileWriter // sWriter is the structured writer 50 | } 51 | 52 | // NewLogger instance 53 | func NewLogger(options *OptionsLogger) *Logger { 54 | logger := &Logger{ 55 | options: options, 56 | asyncqueue: make(chan types.HTTPTransaction, 1000), 57 | } 58 | if options.Elastic.Addr != "" { 59 | store, err := elastic.New(options.Elastic) 60 | if err != nil { 61 | gologger.Warning().Msgf("Error while creating elastic logger: %s", err) 62 | } else { 63 | logger.Store = append(logger.Store, store) 64 | } 65 | } 66 | if options.Kafka.Addr != "" { 67 | kfoptions := kafka.Options{ 68 | Addr: options.Kafka.Addr, 69 | Topic: options.Kafka.Topic, 70 | } 71 | store, err := kafka.New(&kfoptions) 72 | if err != nil { 73 | gologger.Warning().Msgf("Error while creating kafka logger: %s", err) 74 | } else { 75 | logger.Store = append(logger.Store, store) 76 | 77 | } 78 | } 79 | store, err := file.New(&file.Options{ 80 | OutputFolder: options.OutputFolder, 81 | }) 82 | if err != nil { 83 | gologger.Warning().Msgf("Error while creating file logger: %s", err) 84 | } else { 85 | logger.Store = append(logger.Store, store) 86 | } 87 | 88 | // setup structured writer 89 | if options.OutputFormat != "" { 90 | sWriter, err := NewOutputFileWriter(options.OutputFormat, options.OutputFile) 91 | if err != nil { 92 | gologger.Warning().Msgf("Error while creating structured writer: %s", err) 93 | } else { 94 | logger.sWriter = sWriter 95 | } 96 | } 97 | 98 | go logger.AsyncWrite() 99 | return logger 100 | } 101 | 102 | // LogRequest and user data 103 | func (l *Logger) LogRequest(req *http.Request, userdata types.UserData) error { 104 | // No-op for now , since proxify isn't intended to fail instead return 502 105 | // and request can be accessed via response.Request 106 | return nil 107 | } 108 | 109 | // LogResponse and user data 110 | func (l *Logger) LogResponse(resp *http.Response, userdata types.UserData) error { 111 | if resp == nil { 112 | return nil 113 | } 114 | // send to writer channel 115 | l.asyncqueue <- types.HTTPTransaction{ 116 | Userdata: userdata, 117 | Response: resp, 118 | Request: resp.Request, 119 | } 120 | return nil 121 | } 122 | 123 | // AsyncWrite data 124 | func (l *Logger) AsyncWrite() { 125 | for httpData := range l.asyncqueue { 126 | if httpData.Request == nil { 127 | // we can't do anything without request 128 | continue 129 | } 130 | // we have better options to handle this 131 | // i.e Buffer reuse and normalizing request/response body (removing encoding etc) 132 | reqDump, err := httputil.DumpRequest(httpData.Request, true) 133 | if err != nil { 134 | gologger.Warning().Msgf("Error while dumping request: %s", err) 135 | } 136 | 137 | // debug log request if true 138 | l.debugLogRequest(reqDump, httpData.Request) 139 | 140 | var respChain *pdhttpUtils.ResponseChain 141 | if httpData.Response != nil { 142 | respChainx := pdhttpUtils.NewResponseChain(httpData.Response, 4096) 143 | if err := respChainx.Fill(); err == nil { 144 | respChain = respChainx 145 | } else { 146 | gologger.Warning().Msgf("responseChain: Error while dumping response: %s", err) 147 | } 148 | } 149 | // debug log response if true 150 | if respChain != nil { 151 | if err := l.debugLogResponse(respChain); err != nil { 152 | gologger.Warning().Msgf("Error while logging response: %s", err) 153 | } 154 | } 155 | 156 | // first write to structured writer 157 | if l.sWriter != nil { 158 | func() { 159 | // if matchers were given only store those that match 160 | if httpData.Userdata.Match != nil { 161 | if !*httpData.Userdata.Match { 162 | return 163 | } 164 | } 165 | 166 | sData := &types.HTTPRequestResponseLog{ 167 | Timestamp: time.Now().Format(time.RFC3339), 168 | URL: httpData.Request.URL.String(), 169 | } 170 | defer func() { 171 | // write to structured writer with whatever data we have 172 | err := l.sWriter.Write(sData) 173 | if err != nil { 174 | gologger.Warning().Msgf("Error while logging: %s", err) 175 | } 176 | }() 177 | sRequest, err := types.NewHttpRequestData(httpData.Request) 178 | if err != nil { 179 | gologger.Warning().Msgf("Error while creating request: %s", err) 180 | return 181 | } 182 | sData.Request = sRequest 183 | if respChain != nil { 184 | sResponse, err := types.NewHttpResponseData(respChain) 185 | if err != nil { 186 | gologger.Warning().Msgf("Error while creating response: %s", err) 187 | } 188 | sData.Response = sResponse 189 | } 190 | }() 191 | } 192 | 193 | // write to other writers 194 | if len(l.Store) > 0 { 195 | // write request first 196 | outputData := httpData 197 | // outputData.Data = reqDump 198 | outputData.RawData = reqDump 199 | outputData.Userdata.HasResponse = false 200 | l.storeWriter(outputData) 201 | 202 | // write response if available 203 | if respChain != nil { 204 | // outputData.Data = respChain.FullResponse().Bytes() 205 | outputData.RawData = respChain.FullResponse().Bytes() 206 | outputData.Userdata.HasResponse = true 207 | l.storeWriter(outputData) 208 | } 209 | } 210 | } 211 | } 212 | 213 | // Close logger instance 214 | func (l *Logger) Close() { 215 | if l.sWriter != nil { 216 | l.sWriter.Close() 217 | } 218 | close(l.asyncqueue) 219 | } 220 | 221 | // debugLogRequest logs the request to the console if debugging is enabled 222 | func (l *Logger) debugLogRequest(reqdump []byte, req *http.Request) { 223 | if l.options.Verbosity >= types.VerbosityVeryVerbose { 224 | contentType := req.Header.Get("Content-Type") 225 | b, _ := io.ReadAll(req.Body) 226 | if isASCIICheckRequired(contentType) && !govalidator.IsPrintableASCII(string(b)) { 227 | reqdump, _ = httputil.DumpRequest(req, false) 228 | } 229 | gologger.Silent().Msgf("%s", string(reqdump)) 230 | } 231 | } 232 | 233 | // debugLogResponse logs the response to the console if debugging is enabled 234 | func (l *Logger) debugLogResponse(respChain *pdhttpUtils.ResponseChain) error { 235 | if l.options.Verbosity >= types.VerbosityVeryVerbose { 236 | contentType := respChain.Response().Header.Get("Content-Type") 237 | if isASCIICheckRequired(contentType) && !govalidator.IsPrintableASCII(conversion.String(respChain.Body().Bytes())) { 238 | gologger.Silent().Msgf("%s", respChain.Headers().String()) 239 | } else { 240 | gologger.Silent().Msgf("%s", respChain.FullResponse().String()) 241 | } 242 | } 243 | return nil 244 | } 245 | 246 | // storeWriter writes the data to the store (file, kafka, elastic) 247 | // this can be refactored to make it more readable and scalable 248 | // with improved interface and probably use of structure http data 249 | // instead of raw bytes 250 | func (l *Logger) storeWriter(outputdata types.HTTPTransaction) { 251 | if !l.options.DumpRequest && !l.options.DumpResponse { 252 | outputdata.PartSuffix = "" 253 | } else if l.options.DumpRequest && !outputdata.Userdata.HasResponse { 254 | outputdata.PartSuffix = ".request" 255 | } else if l.options.DumpResponse && outputdata.Userdata.HasResponse { 256 | outputdata.PartSuffix = ".response" 257 | } else { 258 | return 259 | } 260 | outputdata.Name = fmt.Sprintf("%s%s-%s", outputdata.Userdata.Host, outputdata.PartSuffix, outputdata.Userdata.ID) 261 | if outputdata.Userdata.HasResponse && !(l.options.DumpRequest || l.options.DumpResponse) { 262 | if outputdata.Userdata.Match != nil && *outputdata.Userdata.Match { 263 | outputdata.Name = outputdata.Name + ".match" 264 | } 265 | } 266 | outputdata.Format = dataWithoutNewLine 267 | if !strings.HasSuffix(string(outputdata.Data), "\n") { 268 | outputdata.Format = dataWithNewLine 269 | } 270 | 271 | outputdata.DataString = fmt.Sprintf(outputdata.Format, outputdata.Data) 272 | if outputdata.Userdata.HasResponse { 273 | outputdata.Format = "\n" + outputdata.Format 274 | } 275 | outputdata.RawData = []byte(fmt.Sprintf(outputdata.Format, outputdata.RawData)) 276 | 277 | if l.options.MaxSize > 0 { 278 | outputdata.DataString = stringsutil.Truncate(outputdata.DataString, l.options.MaxSize) 279 | outputdata.RawData = []byte(stringsutil.Truncate(string(outputdata.RawData), l.options.MaxSize)) 280 | } 281 | for _, store := range l.Store { 282 | err := store.Save(outputdata) 283 | if err != nil { 284 | gologger.Warning().Msgf("Error while logging: %s", err) 285 | } 286 | } 287 | } 288 | 289 | func isASCIICheckRequired(contentType string) bool { 290 | return stringsutil.ContainsAny(contentType, "application/octet-stream", "application/x-www-form-urlencoded") 291 | } 292 | -------------------------------------------------------------------------------- /pkg/logger/options.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "github.com/projectdiscovery/proxify/pkg/logger/elastic" 5 | "github.com/projectdiscovery/proxify/pkg/logger/kafka" 6 | ) 7 | 8 | // Config is a configuration file for proxify logger module 9 | type Config struct { 10 | Kafka kafka.Options `yaml:"kafka"` 11 | Elastic elastic.Options `yaml:"elastic"` 12 | } 13 | -------------------------------------------------------------------------------- /pkg/logger/writer.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/projectdiscovery/proxify/pkg/logger/jsonl" 7 | "github.com/projectdiscovery/proxify/pkg/logger/yaml" 8 | "github.com/projectdiscovery/proxify/pkg/types" 9 | ) 10 | 11 | var ( 12 | _ OutputFileWriter = &jsonl.JsonLinesWriter{} 13 | // multi doc yaml writer with --- separator 14 | _ OutputFileWriter = &yaml.YamlMultiDocWriter{} 15 | 16 | ErrorInvalidFormat = errors.New("invalid format: expected jsonl or yaml") 17 | ) 18 | 19 | // OutputFileWriter is an interface for writing structured 20 | // data to a file. 21 | type OutputFileWriter interface { 22 | // Write writes a http transaction to the file. 23 | Write(data *types.HTTPRequestResponseLog) error 24 | // Close closes the file writer. 25 | Close() error 26 | } 27 | 28 | // NewOutputFileWriter creates a new output file writer 29 | func NewOutputFileWriter(format, filePath string) (OutputFileWriter, error) { 30 | switch format { 31 | case "jsonl": 32 | return jsonl.NewJsonLinesWriter(filePath) 33 | case "yaml": 34 | return yaml.NewYamlMultiDocWriter(filePath) 35 | default: 36 | return nil, ErrorInvalidFormat 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/logger/yaml/yaml.go: -------------------------------------------------------------------------------- 1 | package yaml 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/goccy/go-yaml" 7 | "github.com/projectdiscovery/proxify/pkg/types" 8 | ) 9 | 10 | // YamlMultiDocWriter is a writer for yaml multi doc 11 | type YamlMultiDocWriter struct { 12 | f *os.File 13 | enc *yaml.Encoder 14 | } 15 | 16 | // NewYamlMultiDocWriter creates a new yaml multi doc writer 17 | func NewYamlMultiDocWriter(filePath string) (*YamlMultiDocWriter, error) { 18 | file, err := os.Create(filePath) 19 | if err != nil { 20 | return nil, err 21 | } 22 | enc := yaml.NewEncoder(file, yaml.UseLiteralStyleIfMultiline(true), yaml.UseSingleQuote(true)) 23 | return &YamlMultiDocWriter{f: file, enc: enc}, nil 24 | } 25 | 26 | // Write writes a http transaction to the file. 27 | func (y *YamlMultiDocWriter) Write(data *types.HTTPRequestResponseLog) error { 28 | if err := y.enc.Encode(data); err != nil { 29 | return err 30 | } 31 | return nil 32 | } 33 | 34 | // Close closes the file writer. 35 | func (y *YamlMultiDocWriter) Close() error { 36 | if y.enc != nil { 37 | y.enc.Close() 38 | } 39 | if y.f != nil { 40 | _ = y.f.Close() 41 | } 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /pkg/swaggergen/content.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | import "net/http" 4 | 5 | // Content represents a content in the spec 6 | type Content struct { 7 | Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"` 8 | } 9 | 10 | // NewContent creates a new content 11 | func NewContent(res *http.Response) *Content { 12 | var content *Content 13 | if res.Body != nil { 14 | content = &Content{ 15 | Schema: NewSchema(res.Body), 16 | } 17 | } 18 | return content 19 | } 20 | 21 | // UpdateContent updates a content 22 | func (c *Content) UpdateContent(res *http.Response) { 23 | if res.Body != nil { 24 | c.Schema = NewSchema(res.Body) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/swaggergen/info.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | type Info struct { 4 | Title string `json:"title,omitempty" yaml:"title,omitempty"` 5 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 6 | Version string `json:"version,omitempty" yaml:"version,omitempty"` 7 | } 8 | 9 | // NewInfo creates a new info 10 | func NewInfo(title string) *Info { 11 | return &Info{ 12 | Title: title, 13 | Version: "1.0.0", 14 | } 15 | } 16 | 17 | // UpdateInfo updates a info 18 | func (i *Info) UpdateInfo(title string) { 19 | i.Title = i.Title + "," + title 20 | } 21 | -------------------------------------------------------------------------------- /pkg/swaggergen/method.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | // Operation represents an operation in the spec 4 | type Method struct { 5 | Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` 6 | Responses map[int]*Response `json:"responses,omitempty" yaml:"responses,omitempty"` 7 | Parameters []*Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` 8 | } 9 | 10 | // NewMethod creates a new method 11 | func NewMethod(reqRes RequestResponse) *Method { 12 | method := &Method{ 13 | Responses: map[int]*Response{}, 14 | Parameters: NewParameters(reqRes.Request), 15 | } 16 | if reqRes.Response != nil { 17 | method.Responses = map[int]*Response{ 18 | reqRes.Response.StatusCode: NewResponse(reqRes.Response), 19 | } 20 | } 21 | return method 22 | } 23 | 24 | // UpdateMethod updates a method 25 | func (m *Method) UpdateMethod(reqRes RequestResponse) { 26 | if reqRes.Response != nil { 27 | if _, ok := m.Responses[reqRes.Response.StatusCode]; !ok { 28 | m.Responses[reqRes.Response.StatusCode] = NewResponse(reqRes.Response) 29 | } else { 30 | m.Responses[reqRes.Response.StatusCode].UpdateResponse(reqRes.Response) 31 | } 32 | } 33 | m.Parameters = NewParameters(reqRes.Request) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/swaggergen/parameter.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | import "net/http" 4 | 5 | // Parameter represents a parameter in the spec 6 | type Parameter struct { 7 | Name string `json:"name,omitempty" yaml:"name,omitempty"` 8 | In string `json:"in,omitempty" yaml:"in,omitempty"` 9 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 10 | Required bool `json:"required,omitempty" yaml:"required,omitempty"` 11 | Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"` 12 | } 13 | 14 | // NewParameters creates a new parameters 15 | func NewParameters(req *http.Request) []*Parameter { 16 | var params []*Parameter 17 | if req.Body != nil { 18 | // add body parameter 19 | Schema := NewSchema(req.Body) 20 | if Schema != nil { 21 | params = append(params, &Parameter{ 22 | Name: "body", 23 | In: "body", 24 | Required: true, 25 | Schema: Schema, 26 | }) 27 | } 28 | } 29 | // get request query parameters 30 | reqParams := req.URL.Query() 31 | // add query parameters 32 | for key, value := range reqParams { 33 | params = append(params, &Parameter{ 34 | Name: key, 35 | In: "query", 36 | Schema: &Schema{Type: "string"}, 37 | Description: value[0], 38 | Required: true, 39 | }) 40 | } 41 | return params 42 | } 43 | -------------------------------------------------------------------------------- /pkg/swaggergen/path.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Path represents a path in the spec 8 | type Path map[string]*Method 9 | 10 | // NewPath creates a new path 11 | func NewPath(reqRes RequestResponse) Path { 12 | return map[string]*Method{ 13 | strings.ToLower(reqRes.Request.Method): NewMethod(reqRes), 14 | } 15 | } 16 | 17 | // UpdatePath updates a path 18 | func (p Path) UpdatePath(reqRes RequestResponse) { 19 | if _, ok := p[strings.ToLower(reqRes.Request.Method)]; !ok { 20 | p[strings.ToLower(reqRes.Request.Method)] = NewMethod(reqRes) 21 | } else { 22 | p[strings.ToLower(reqRes.Request.Method)].UpdateMethod(reqRes) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/swaggergen/requestresponse.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | import "net/http" 4 | 5 | // RequestResponse represents a request and response 6 | type RequestResponse struct { 7 | Request *http.Request 8 | Response *http.Response 9 | } 10 | -------------------------------------------------------------------------------- /pkg/swaggergen/response.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | import "net/http" 4 | 5 | // Response represents a response in the spec 6 | type Response struct { 7 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 8 | Content map[string]*Content `json:"content,omitempty" yaml:"content,omitempty"` 9 | } 10 | 11 | // NewResponse creates a new response 12 | func NewResponse(res *http.Response) *Response { 13 | var response *Response 14 | if res.Header != nil { 15 | response = &Response{ 16 | Content: map[string]*Content{ 17 | res.Header.Get("Content-Type"): NewContent(res), 18 | }, 19 | } 20 | } 21 | return response 22 | } 23 | 24 | // UpdateResponse updates a response 25 | func (r *Response) UpdateResponse(res *http.Response) { 26 | if res.Header != nil { 27 | if _, ok := r.Content[res.Header.Get("Content-Type")]; !ok { 28 | r.Content[res.Header.Get("Content-Type")] = NewContent(res) 29 | } else { 30 | r.Content[res.Header.Get("Content-Type")].UpdateContent(res) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/swaggergen/schema.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | ) 7 | 8 | // Schema represents a schema in the spec 9 | type Schema struct { 10 | Type string `json:"type" yaml:"type"` 11 | Properties map[string]*Schema `json:"properties,omitempty" yaml:"properties,omitempty"` 12 | Description string `json:"description" yaml:"description,omitempty"` 13 | } 14 | 15 | func NewSchema(reader io.Reader) *Schema { 16 | body, err := io.ReadAll(reader) 17 | if err != nil { 18 | return nil 19 | } 20 | var data interface{} 21 | if err := json.Unmarshal(body, &data); err != nil { 22 | return nil 23 | } 24 | 25 | dataType := InterfaceToType(data) 26 | // generate shema from data map 27 | schema := &Schema{ 28 | Type: dataType, 29 | } 30 | if dataType == "object" { 31 | schema.Properties = make(map[string]*Schema) 32 | for key, value := range data.(map[string]interface{}) { 33 | schema.Properties[key] = &Schema{ 34 | Type: InterfaceToType(value), 35 | } 36 | } 37 | } 38 | return schema 39 | } 40 | 41 | func InterfaceToType(data interface{}) string { 42 | switch data.(type) { 43 | case map[string]interface{}: 44 | return "object" 45 | case []interface{}: 46 | return "array" 47 | case string: 48 | return "string" 49 | case float64: 50 | return "number" 51 | case bool: 52 | return "boolean" 53 | default: 54 | return "notfound" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /pkg/swaggergen/servers.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | type Server struct { 4 | URL string `json:"url,omitempty" yaml:"url,omitempty"` 5 | Description string `json:"description,omitempty" yaml:"description,omitempty"` 6 | } 7 | 8 | // NewServer creates a new server 9 | func NewServer(url, description string) *Server { 10 | return &Server{ 11 | URL: url, 12 | Description: description, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/swaggergen/spec.go: -------------------------------------------------------------------------------- 1 | package swaggergen 2 | 3 | const OpenApiVersion = "3.0.0" 4 | 5 | // Spec represents openapi 3 specification 6 | type Spec struct { 7 | OpenApi string `json:"openapi"` 8 | Info *Info `json:"info"` 9 | Servers []*Server `json:"servers"` 10 | Paths map[string]Path `json:"paths"` 11 | } 12 | 13 | // NewSpec creates a new spec 14 | func NewSpec(logDir, api string) *Spec { 15 | return &Spec{ 16 | OpenApi: OpenApiVersion, 17 | Info: NewInfo(logDir), 18 | Servers: []*Server{NewServer(api, "")}, 19 | Paths: map[string]Path{}, 20 | } 21 | } 22 | 23 | // UpdateSpec updates a spec 24 | func (s *Spec) UpdateSpec(logDir, api string) { 25 | s.Info.UpdateInfo(logDir) 26 | newServer := NewServer(api, "") 27 | for _, server := range s.Servers { 28 | if server.URL == newServer.URL { 29 | return 30 | } 31 | } 32 | s.Servers = append(s.Servers, newServer) 33 | } 34 | 35 | // AddPath adds a path to the spec 36 | func (s *Spec) AddPath(reqRes RequestResponse) { 37 | path := reqRes.Request.URL.Path 38 | if _, ok := s.Paths[path]; !ok { 39 | s.Paths[path] = NewPath(reqRes) 40 | } else { 41 | s.Paths[path].UpdatePath(reqRes) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/types/userdata.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/http/httputil" 7 | "strings" 8 | 9 | pdhttpUtils "github.com/projectdiscovery/utils/http" 10 | ) 11 | 12 | // UserData is context used to identify a http 13 | // transaction and its state like match, response etc. 14 | type UserData struct { 15 | ID string 16 | Match *bool 17 | HasResponse bool 18 | Host string 19 | } 20 | 21 | // HTTPTransaction is a struct for http transaction 22 | // it contains data of every request/response obtained 23 | // from proxy 24 | type HTTPTransaction struct { 25 | Userdata UserData 26 | RawData []byte 27 | Data []byte 28 | DataString string 29 | Name string 30 | PartSuffix string 31 | Format string 32 | 33 | Request *http.Request 34 | Response *http.Response 35 | } 36 | 37 | // HTTPRequestResponseLog is a struct for http request and response log 38 | // it is a processed version of http transaction for logging 39 | // in more structured format than just raw bytes 40 | type HTTPRequestResponseLog struct { 41 | Timestamp string `json:"timestamp,omitempty"` 42 | URL string `json:"url,omitempty"` 43 | Request *HTTPRequest `json:"request,omitempty"` 44 | Response *HTTPResponse `json:"response,omitempty"` 45 | } 46 | 47 | // HTTPRequest is a struct for http request 48 | type HTTPRequest struct { 49 | Header map[string]string `json:"header,omitempty"` 50 | Body string `json:"body,omitempty"` 51 | Raw string `json:"raw,omitempty"` 52 | } 53 | 54 | // NewHttpRequestData creates a new HttpRequest with data extracted from an http.Request 55 | func NewHttpRequestData(req *http.Request) (*HTTPRequest, error) { 56 | httpRequest := &HTTPRequest{ 57 | Header: make(map[string]string), 58 | } 59 | 60 | // Extract headers from the request 61 | httpRequest.Header["scheme"] = req.URL.Scheme 62 | httpRequest.Header["method"] = req.Method 63 | httpRequest.Header["path"] = req.URL.Path 64 | httpRequest.Header["host"] = req.URL.Host 65 | for key, values := range req.Header { 66 | httpRequest.Header[key] = strings.Join(values, ", ") 67 | } 68 | 69 | // Extract body from the request 70 | reqBody, err := io.ReadAll(req.Body) 71 | if err != nil { 72 | return nil, err 73 | } 74 | defer req.Body.Close() 75 | req.Body = io.NopCloser(strings.NewReader(string(reqBody))) 76 | httpRequest.Body = string(reqBody) 77 | 78 | // Extract raw request 79 | reqdumpNoBody, err := httputil.DumpRequest(req, false) 80 | if err != nil { 81 | return nil, err 82 | } 83 | httpRequest.Raw = string(reqdumpNoBody) 84 | 85 | return httpRequest, nil 86 | } 87 | 88 | // HTTPResponse is a struct for http response 89 | type HTTPResponse struct { 90 | Header map[string]string `json:"header,omitempty"` 91 | Body string `json:"body,omitempty"` 92 | Raw string `json:"raw,omitempty"` 93 | } 94 | 95 | // NewHttpResponseData creates a new HttpResponse with data extracted from an http.Response 96 | func NewHttpResponseData(resp *pdhttpUtils.ResponseChain) (*HTTPResponse, error) { 97 | httpResponse := &HTTPResponse{ 98 | Header: make(map[string]string), 99 | } 100 | // Extract headers from the response 101 | for key, values := range resp.Response().Header { 102 | httpResponse.Header[key] = strings.Join(values, ", ") 103 | } 104 | httpResponse.Body = resp.Body().String() 105 | httpResponse.Raw = resp.Headers().String() // doesn't include body 106 | 107 | return httpResponse, nil 108 | } 109 | -------------------------------------------------------------------------------- /pkg/types/verbosity.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type Verbosity int 4 | 5 | const ( 6 | VerbositySilent Verbosity = iota 7 | VerbosityDefault 8 | VerbosityVerbose 9 | VerbosityVeryVerbose 10 | ) 11 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/http/httputil" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | // HTTPRequestToMap Converts HTTP Request to Matcher Map 14 | func HTTPRequestToMap(req *http.Request) (map[string]interface{}, error) { 15 | m := make(map[string]interface{}) 16 | var headers string 17 | for k, v := range req.Header { 18 | k = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(k, "-", "_"))) 19 | vv := strings.Join(v, " ") 20 | m[k] = strings.Join(v, " ") 21 | headers += fmt.Sprintf("%s: %s", k, vv) 22 | } 23 | 24 | m["all_headers"] = headers 25 | 26 | body, err := io.ReadAll(req.Body) 27 | if err != nil { 28 | return nil, err 29 | } 30 | req.Body = io.NopCloser(bytes.NewBuffer(body)) 31 | m["body"] = string(body) 32 | 33 | reqdump, err := httputil.DumpRequest(req, true) 34 | if err != nil { 35 | return nil, err 36 | } 37 | reqdumpString := string(reqdump) 38 | m["raw"] = reqdumpString 39 | m["request"] = reqdumpString 40 | m["method"] = req.Method 41 | m["path"] = req.URL.Path 42 | m["host"] = req.URL.Host 43 | m["scheme"] = req.URL.Scheme 44 | m["url"] = req.URL.String() 45 | m["query"] = req.URL.Query().Encode() 46 | 47 | return m, nil 48 | } 49 | 50 | // HTTPResponseToMap Converts HTTP Response to Matcher Map 51 | func HTTPResponseToMap(resp *http.Response) (map[string]interface{}, error) { 52 | m := make(map[string]interface{}) 53 | 54 | m["content_length"] = resp.ContentLength 55 | m["status_code"] = resp.StatusCode 56 | var headers string 57 | for k, v := range resp.Header { 58 | k = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(k, "-", "_"))) 59 | vv := strings.Join(v, " ") 60 | m[k] = vv 61 | headers += fmt.Sprintf("%s: %s", k, vv) 62 | } 63 | m["all_headers"] = headers 64 | 65 | body, err := io.ReadAll(resp.Body) 66 | if err != nil { 67 | return nil, err 68 | } 69 | resp.Body.Close() 70 | resp.Body = io.NopCloser(bytes.NewBuffer(body)) 71 | m["body"] = string(body) 72 | 73 | if r, err := httputil.DumpResponse(resp, true); err == nil { 74 | responseString := string(r) 75 | m["raw"] = responseString 76 | m["response"] = responseString 77 | } 78 | 79 | return m, nil 80 | } 81 | 82 | // MatchAnyRegex checks if data matches any pattern 83 | func MatchAnyRegex(regexes []string, data string) bool { 84 | for _, regex := range regexes { 85 | if ok, err := regexp.MatchString(regex, data); err == nil && ok { 86 | return true 87 | } 88 | } 89 | return false 90 | } 91 | 92 | // EvalBoolSlice evaluates a slice of bools using a logical AND 93 | func EvalBoolSlice(slice []bool) bool { 94 | if len(slice) == 0 { 95 | return false 96 | } 97 | result := slice[0] 98 | for _, b := range slice { 99 | result = result && b 100 | } 101 | return result 102 | } 103 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package proxify 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "crypto/tls" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net" 11 | "net/http" 12 | "net/http/httptest" 13 | "net/http/httputil" 14 | "net/url" 15 | "os" 16 | "strconv" 17 | "strings" 18 | "sync" 19 | 20 | "github.com/armon/go-socks5" 21 | "github.com/haxii/fastproxy/bufiopool" 22 | "github.com/haxii/fastproxy/superproxy" 23 | "github.com/projectdiscovery/dsl" 24 | "github.com/projectdiscovery/fastdialer/fastdialer" 25 | "github.com/projectdiscovery/gologger" 26 | "github.com/projectdiscovery/martian/v3" 27 | martianlog "github.com/projectdiscovery/martian/v3/log" 28 | "github.com/projectdiscovery/proxify/pkg/certs" 29 | "github.com/projectdiscovery/proxify/pkg/logger" 30 | "github.com/projectdiscovery/proxify/pkg/logger/elastic" 31 | "github.com/projectdiscovery/proxify/pkg/logger/kafka" 32 | "github.com/projectdiscovery/proxify/pkg/types" 33 | "github.com/projectdiscovery/proxify/pkg/util" 34 | rbtransport "github.com/projectdiscovery/roundrobin/transport" 35 | "github.com/projectdiscovery/tinydns" 36 | errorutil "github.com/projectdiscovery/utils/errors" 37 | readerUtil "github.com/projectdiscovery/utils/reader" 38 | stringsutil "github.com/projectdiscovery/utils/strings" 39 | "golang.org/x/net/proxy" 40 | ) 41 | 42 | type OnRequestFunc func(req *http.Request, ctx *martian.Context) error 43 | type OnResponseFunc func(resp *http.Response, ctx *martian.Context) error 44 | 45 | type Options struct { 46 | DumpRequest bool 47 | DumpResponse bool 48 | OutputJsonl bool 49 | MaxSize int 50 | Verbosity types.Verbosity 51 | CertCacheSize int 52 | Directory string 53 | ListenAddrHTTP string 54 | ListenAddrSocks5 string 55 | OutputDirectory string 56 | OutputFile string 57 | OutputFormat string 58 | RequestDSL []string 59 | ResponseDSL []string 60 | UpstreamHTTPProxies []string 61 | UpstreamSock5Proxies []string 62 | ListenDNSAddr string 63 | DNSMapping string 64 | DNSFallbackResolver string 65 | RequestMatchReplaceDSL []string 66 | ResponseMatchReplaceDSL []string 67 | OnRequestCallback OnRequestFunc 68 | OnResponseCallback OnResponseFunc 69 | Deny []string 70 | Allow []string 71 | PassThrough []string 72 | UpstreamProxyRequestsNumber int 73 | Elastic *elastic.Options 74 | Kafka *kafka.Options 75 | } 76 | 77 | type Proxy struct { 78 | Dialer *fastdialer.Dialer 79 | options *Options 80 | logger *logger.Logger 81 | httpProxy *martian.Proxy 82 | socks5proxy *socks5.Server 83 | socks5tunnel *superproxy.SuperProxy 84 | bufioPool *bufiopool.Pool 85 | tinydns *tinydns.TinyDNS 86 | rbhttp *rbtransport.RoundTransport 87 | rbsocks5 *rbtransport.RoundTransport 88 | proxifyMux *http.ServeMux // serve banner page and static files 89 | listenAddr string 90 | } 91 | 92 | func NewProxy(options *Options) (*Proxy, error) { 93 | 94 | switch options.Verbosity { 95 | case types.VerbositySilent: 96 | martianlog.SetLevel(martianlog.Silent) 97 | case types.VerbosityVerbose: 98 | martianlog.SetLevel(martianlog.Info) 99 | case types.VerbosityVeryVerbose: 100 | martianlog.SetLevel(martianlog.Debug) 101 | default: 102 | martianlog.SetLevel(martianlog.Error) 103 | } 104 | 105 | logger := logger.NewLogger(&logger.OptionsLogger{ 106 | Verbosity: options.Verbosity, 107 | OutputFile: options.OutputFile, 108 | OutputFormat: options.OutputFormat, 109 | OutputFolder: options.OutputDirectory, 110 | DumpRequest: options.DumpRequest, 111 | DumpResponse: options.DumpResponse, 112 | MaxSize: options.MaxSize, 113 | Elastic: options.Elastic, 114 | Kafka: options.Kafka, 115 | }) 116 | 117 | var tdns *tinydns.TinyDNS 118 | 119 | fastdialerOptions := fastdialer.DefaultOptions 120 | fastdialerOptions.EnableFallback = true 121 | fastdialerOptions.Deny = options.Deny 122 | fastdialerOptions.Allow = options.Allow 123 | if options.ListenDNSAddr != "" { 124 | dnsmapping := make(map[string]*tinydns.DnsRecord) 125 | for _, record := range strings.Split(options.DNSMapping, ",") { 126 | data := strings.Split(record, ":") 127 | if len(data) != 2 { 128 | continue 129 | } 130 | dnsmapping[data[0]] = &tinydns.DnsRecord{A: []string{data[1]}} 131 | } 132 | var err error 133 | tdns, err = tinydns.New(&tinydns.Options{ 134 | ListenAddress: options.ListenDNSAddr, 135 | Net: "udp", 136 | UpstreamServers: []string{options.DNSFallbackResolver}, 137 | DnsRecords: dnsmapping, 138 | }) 139 | if err != nil { 140 | return nil, err 141 | } 142 | fastdialerOptions.BaseResolvers = []string{"127.0.0.1" + options.ListenDNSAddr} 143 | } 144 | dialer, err := fastdialer.NewDialer(fastdialerOptions) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | var rbhttp, rbsocks5 *rbtransport.RoundTransport 150 | if len(options.UpstreamHTTPProxies) > 0 { 151 | rbhttp, err = rbtransport.NewWithOptions(options.UpstreamProxyRequestsNumber, options.UpstreamHTTPProxies...) 152 | if err != nil { 153 | return nil, err 154 | } 155 | } 156 | if len(options.UpstreamSock5Proxies) > 0 { 157 | rbsocks5, err = rbtransport.NewWithOptions(options.UpstreamProxyRequestsNumber, options.UpstreamSock5Proxies...) 158 | if err != nil { 159 | return nil, err 160 | } 161 | } 162 | pmux, err := getProxifyServerMux() 163 | if err != nil { 164 | return nil, err 165 | } 166 | 167 | proxy := &Proxy{ 168 | logger: logger, 169 | options: options, 170 | Dialer: dialer, 171 | tinydns: tdns, 172 | rbhttp: rbhttp, 173 | rbsocks5: rbsocks5, 174 | proxifyMux: pmux, 175 | } 176 | 177 | if err := proxy.setupHTTPProxy(); err != nil { 178 | return nil, err 179 | } 180 | 181 | var socks5proxy *socks5.Server 182 | if options.ListenAddrSocks5 != "" { 183 | socks5Config := &socks5.Config{ 184 | Dial: proxy.httpTunnelDialer, 185 | } 186 | if options.Verbosity <= types.VerbositySilent { 187 | socks5Config.Logger = log.New(io.Discard, "", log.Ltime|log.Lshortfile) 188 | } 189 | socks5proxy, err = socks5.New(socks5Config) 190 | if err != nil { 191 | return nil, err 192 | } 193 | } 194 | 195 | proxy.socks5proxy = socks5proxy 196 | 197 | return proxy, nil 198 | } 199 | 200 | // ModifyRequest 201 | func (p *Proxy) ModifyRequest(req *http.Request) error { 202 | ctx := martian.NewContext(req) 203 | // disable upgrading http connections to https by default 204 | ctx.Session().MarkInsecure() 205 | // setup passthrought and hijack here 206 | userData := types.UserData{ 207 | ID: ctx.ID(), 208 | Host: req.Host, 209 | } 210 | 211 | if stringsutil.EqualFoldAny(req.Host, "proxify", "proxify:443", "proxify:80", p.listenAddr) { 212 | // hijack if this is true 213 | return p.hijackNServe(req, ctx) 214 | } 215 | 216 | // If callbacks are given use them (for library use cases) 217 | if p.options.OnRequestCallback != nil { 218 | return p.options.OnRequestCallback(req, ctx) 219 | } 220 | 221 | boolSlice := []bool{} 222 | for _, expr := range p.options.RequestDSL { 223 | m, _ := util.HTTPRequestToMap(req) 224 | v, err := dsl.EvalExpr(expr, m) 225 | if err != nil { 226 | gologger.Warning().Msgf("Could not evaluate request dsl: %s\n", err) 227 | } 228 | boolSlice = append(boolSlice, err == nil && v.(bool)) 229 | } 230 | // evaluate bool array to get match status 231 | if len(boolSlice) > 0 { 232 | tmp := util.EvalBoolSlice(boolSlice) 233 | userData.Match = &tmp 234 | } 235 | 236 | ctx.Set("user-data", userData) 237 | 238 | // perform match and replace 239 | if len(p.options.RequestMatchReplaceDSL) != 0 { 240 | _ = p.MatchReplaceRequest(req) 241 | } 242 | _ = p.logger.LogRequest(req, userData) 243 | return nil 244 | } 245 | 246 | // ModifyResponse 247 | func (p *Proxy) ModifyResponse(resp *http.Response) error { 248 | ctx := martian.NewContext(resp.Request) 249 | var userData *types.UserData 250 | if w, ok := ctx.Get("user-data"); ok { 251 | if data, ok2 := w.(types.UserData); ok2 { 252 | userData = &data 253 | } 254 | } 255 | if userData == nil { 256 | gologger.Warning().Msgf("something went wrong got response without userData") 257 | // pass empty struct to avoid panic 258 | userData = &types.UserData{} 259 | } 260 | userData.HasResponse = true 261 | 262 | // if content-length is zero and remove header 263 | if resp.ContentLength == 0 { 264 | resp.Header.Del("Content-Length") 265 | } 266 | 267 | // If callbacks are given use them (for library use cases) 268 | if p.options.OnResponseCallback != nil { 269 | return p.options.OnResponseCallback(resp, ctx) 270 | } 271 | 272 | boolSlice := []bool{} 273 | for _, expr := range p.options.ResponseDSL { 274 | m, _ := util.HTTPResponseToMap(resp) 275 | v, err := dsl.EvalExpr(expr, m) 276 | if err != nil { 277 | gologger.Warning().Msgf("Could not evaluate response dsl: %s\n", err) 278 | } 279 | boolSlice = append(boolSlice, err == nil && v.(bool)) 280 | } 281 | if len(boolSlice) > 0 { 282 | tmp := util.EvalBoolSlice(boolSlice) 283 | // finalize 284 | if userData.Match != nil { 285 | tmp = *userData.Match && tmp 286 | } 287 | userData.Match = &tmp 288 | } 289 | // perform match and replace 290 | if len(p.options.ResponseMatchReplaceDSL) != 0 { 291 | _ = p.MatchReplaceResponse(resp) 292 | } 293 | _ = p.logger.LogResponse(resp, *userData) 294 | if resp.StatusCode == 301 || resp.StatusCode == 302 { 295 | // set connection close header 296 | // close connection if redirected to different host 297 | if loc, err := resp.Location(); err == nil { 298 | if loc.Host == resp.Request.Host { 299 | // if same host redirect do not close connection 300 | return nil 301 | } 302 | } 303 | resp.Close = true 304 | } 305 | return nil 306 | } 307 | 308 | // MatchReplaceRequest strings or regex 309 | func (p *Proxy) MatchReplaceRequest(req *http.Request) error { 310 | // lazy mode - dump request 311 | reqdump, err := httputil.DumpRequest(req, true) 312 | if err != nil { 313 | return err 314 | } 315 | 316 | // lazy mode - ninja level - elaborate 317 | m := make(map[string]interface{}) 318 | m["request"] = string(reqdump) 319 | for _, expr := range p.options.RequestMatchReplaceDSL { 320 | v, err := dsl.EvalExpr(expr, m) 321 | if err != nil { 322 | return err 323 | } 324 | m["request"] = fmt.Sprint(v) 325 | } 326 | 327 | reqbuffer := fmt.Sprint(m["request"]) 328 | // lazy mode - epic level - rebuild 329 | bf := bufio.NewReader(strings.NewReader(reqbuffer)) 330 | requestNew, err := http.ReadRequest(bf) 331 | if err != nil { 332 | return err 333 | } 334 | // closes old body to allow memory reuse 335 | req.Body.Close() 336 | 337 | // override original properties 338 | req.Method = requestNew.Method 339 | req.Header = requestNew.Header 340 | req.Body = requestNew.Body 341 | req.URL = requestNew.URL 342 | return nil 343 | } 344 | 345 | // MatchReplaceRequest strings or regex 346 | func (p *Proxy) MatchReplaceResponse(resp *http.Response) error { 347 | // // Set Content-Length to zero to allow automatic calculation 348 | resp.ContentLength = -1 349 | 350 | // lazy mode - dump request 351 | respdump, err := httputil.DumpResponse(resp, true) 352 | if err != nil { 353 | return err 354 | } 355 | 356 | // lazy mode - ninja level - elaborate 357 | m := make(map[string]interface{}) 358 | m["response"] = string(respdump) 359 | for _, expr := range p.options.ResponseMatchReplaceDSL { 360 | v, err := dsl.EvalExpr(expr, m) 361 | 362 | if err != nil { 363 | return err 364 | } 365 | m["response"] = fmt.Sprint(v) 366 | } 367 | 368 | respbuffer := fmt.Sprint(m["response"]) 369 | // lazy mode - epic level - rebuild 370 | bf := bufio.NewReader(strings.NewReader(respbuffer)) 371 | responseNew, err := http.ReadResponse(bf, nil) 372 | if err != nil { 373 | return err 374 | } 375 | 376 | // closes old body to allow memory reuse 377 | resp.Body.Close() 378 | resp.Header = responseNew.Header 379 | resp.Body, err = readerUtil.NewReusableReadCloser(responseNew.Body) 380 | if err != nil { 381 | return err 382 | } 383 | if resp.ContentLength == 0 { 384 | resp.Header.Del("Content-Length") 385 | } 386 | // resp.ContentLength = responseNew.ContentLength 387 | return nil 388 | } 389 | 390 | func (p *Proxy) Run() error { 391 | var wg sync.WaitGroup 392 | 393 | if p.tinydns != nil { 394 | wg.Add(1) 395 | go func() { 396 | defer wg.Done() 397 | if err := p.tinydns.Run(); err != nil { 398 | gologger.Warning().Msgf("Could not start dns server: %s\n", err) 399 | } 400 | }() 401 | } 402 | 403 | // http proxy 404 | if p.httpProxy != nil { 405 | p.httpProxy.TLSPassthroughFunc = func(req *http.Request) bool { 406 | // Skip MITM for hosts that are in pass-through list 407 | return util.MatchAnyRegex(p.options.PassThrough, req.Host) 408 | } 409 | 410 | p.httpProxy.SetRequestModifier(p) 411 | p.httpProxy.SetResponseModifier(p) 412 | 413 | l, err := net.Listen("tcp", p.options.ListenAddrHTTP) 414 | if err != nil { 415 | gologger.Fatal().Msgf("failed to setup listener got %v", err) 416 | } 417 | p.listenAddr = l.Addr().String() 418 | wg.Add(1) 419 | go func() { 420 | defer wg.Done() 421 | gologger.Fatal().Msgf("%v", p.httpProxy.Serve(l)) 422 | }() 423 | } 424 | 425 | // socks5 proxy 426 | if p.socks5proxy != nil { 427 | if p.httpProxy != nil { 428 | httpProxyIP, httpProxyPort, err := net.SplitHostPort(p.options.ListenAddrHTTP) 429 | if err != nil { 430 | return err 431 | } 432 | httpProxyPortUint, err := strconv.ParseUint(httpProxyPort, 10, 16) 433 | if err != nil { 434 | return err 435 | } 436 | p.socks5tunnel, err = superproxy.NewSuperProxy(httpProxyIP, uint16(httpProxyPortUint), superproxy.ProxyTypeHTTP, "", "", "") 437 | if err != nil { 438 | return err 439 | } 440 | p.bufioPool = bufiopool.New(4096, 4096) 441 | } 442 | 443 | wg.Add(1) 444 | go func() { 445 | defer wg.Done() 446 | 447 | gologger.Fatal().Msgf("%v", p.socks5proxy.ListenAndServe("tcp", p.options.ListenAddrSocks5)) 448 | }() 449 | } 450 | 451 | wg.Wait() 452 | return nil 453 | } 454 | 455 | func (p *Proxy) Stop() {} 456 | 457 | // setupHTTPProxy configures proxy with settings 458 | func (p *Proxy) setupHTTPProxy() error { 459 | hp := martian.NewProxy() 460 | hp.Miscellaneous.SetH1ConnectionHeader = true 461 | hp.Miscellaneous.StripProxyHeaders = true 462 | hp.Miscellaneous.IgnoreWebSocketError = true 463 | rt, err := p.getRoundTripper() 464 | if err != nil { 465 | return errorutil.NewWithErr(err).Msgf("failed to setup transport") 466 | } 467 | hp.SetRoundTripper(rt) 468 | dialContextFunc := func(ctx context.Context, a, b string) (net.Conn, error) { 469 | return p.Dialer.Dial(ctx, a, b) 470 | } 471 | hp.SetDialContext(dialContextFunc) 472 | hp.SetMITM(certs.GetMitMConfig()) 473 | p.httpProxy = hp 474 | return nil 475 | } 476 | 477 | // getRoundTripper returns RoundTripper configured with options 478 | func (p *Proxy) getRoundTripper() (http.RoundTripper, error) { 479 | roundtrip := &http.Transport{ 480 | MaxIdleConnsPerHost: -1, 481 | MaxIdleConns: 0, 482 | MaxConnsPerHost: 0, 483 | TLSClientConfig: &tls.Config{ 484 | MinVersion: tls.VersionTLS10, 485 | InsecureSkipVerify: true, 486 | }, 487 | } 488 | 489 | if len(p.options.UpstreamHTTPProxies) > 0 { 490 | roundtrip = &http.Transport{Proxy: func(req *http.Request) (*url.URL, error) { 491 | return url.Parse(p.rbhttp.Next()) 492 | }, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} 493 | } else if len(p.options.UpstreamSock5Proxies) > 0 { 494 | // for each socks5 proxy create a dialer 495 | socks5Dialers := make(map[string]proxy.Dialer) 496 | for _, socks5proxy := range p.options.UpstreamSock5Proxies { 497 | dialer, err := proxy.SOCKS5("tcp", socks5proxy, nil, proxy.Direct) 498 | if err != nil { 499 | return nil, err 500 | } 501 | socks5Dialers[socks5proxy] = dialer 502 | } 503 | roundtrip = &http.Transport{Dial: func(network, addr string) (net.Conn, error) { 504 | // lookup next dialer 505 | socks5Proxy := p.rbsocks5.Next() 506 | socks5Dialer := socks5Dialers[socks5Proxy] 507 | // use it to perform the request 508 | return socks5Dialer.Dial(network, addr) 509 | }, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} 510 | } 511 | return roundtrip, nil 512 | } 513 | 514 | func (p *Proxy) httpTunnelDialer(ctx context.Context, network, addr string) (net.Conn, error) { 515 | return p.socks5tunnel.MakeTunnel(nil, nil, p.bufioPool, addr) 516 | } 517 | 518 | func (p *Proxy) hijackNServe(req *http.Request, ctx *martian.Context) error { 519 | conn, brw, err := ctx.Session().Hijack() 520 | if err != nil { 521 | return err 522 | } 523 | defer conn.Close() 524 | rec := httptest.NewRecorder() 525 | p.proxifyMux.ServeHTTP(rec, req) 526 | resp := rec.Result() 527 | resp.Close = true 528 | if err := resp.Write(brw); err != nil { 529 | gologger.Warning().Msgf("failed to write response: %v", err) 530 | } 531 | brw.Flush() 532 | return nil 533 | } 534 | 535 | func getProxifyServerMux() (*http.ServeMux, error) { 536 | cwd, err := os.Getwd() 537 | if err != nil { 538 | return nil, fmt.Errorf("failed to get current working directory: %v", err) 539 | } 540 | absStaticDirPath := strings.Join([]string{strings.Split(cwd, "cmd")[0], "static"}, "/") 541 | 542 | mux := http.NewServeMux() 543 | serveStatic := http.FileServer(http.Dir(absStaticDirPath)) 544 | mux.Handle("/", serveStatic) 545 | // download ca cert 546 | mux.HandleFunc("/cacert", func(w http.ResponseWriter, r *http.Request) { 547 | buffer, err := certs.GetRawCA() 548 | if err != nil { 549 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 550 | gologger.Warning().Msgf("failed to get raw CA: %v", err) 551 | return 552 | } 553 | w.Header().Set("Content-Type", "application/octet-stream") 554 | w.Header().Set("Content-Disposition", "attachment; filename=\"proxify.pem\"") 555 | if _, err := w.Write(buffer.Bytes()); err != nil { 556 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 557 | gologger.Warning().Msgf("failed to write raw CA: %v", err) 558 | return 559 | } 560 | }) 561 | return mux, nil 562 | } 563 | -------------------------------------------------------------------------------- /socket.go: -------------------------------------------------------------------------------- 1 | package proxify 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "io" 7 | "log" 8 | "net" 9 | "net/http" 10 | "net/url" 11 | "time" 12 | 13 | "github.com/projectdiscovery/dsl" 14 | "github.com/projectdiscovery/proxify/pkg/types" 15 | ) 16 | 17 | // SocketProxy - connect two sockets with TLS inspection 18 | type SocketProxy struct { 19 | Listener net.Listener 20 | options *SocketProxyOptions 21 | } 22 | 23 | // SocketConn represent the single full duplex pipe 24 | type SocketConn struct { 25 | // laddr, raddr net.Addr //nolint 26 | lconn, rconn net.Conn 27 | erred bool 28 | errsig chan bool 29 | httpclient *http.Client 30 | HTTPServer string 31 | sentBytes uint64 32 | receivedBytes uint64 33 | Verbosity types.Verbosity 34 | OutputHex bool 35 | Timeout time.Duration 36 | RequestMatchReplaceDSL []string 37 | ResponseMatchReplaceDSL []string 38 | OnRequest func([]byte) []byte 39 | OnResponse func([]byte) []byte 40 | } 41 | 42 | type SocketProxyOptions struct { 43 | Protocol string 44 | ListenAddress string 45 | RemoteAddress string 46 | HTTPProxy string 47 | HTTPServer string 48 | listenAddress net.TCPAddr 49 | remoteAddress net.TCPAddr 50 | TLSClientConfig *tls.Config 51 | TLSClient bool 52 | TLSServerConfig *tls.Config 53 | TLSServer bool 54 | Verbosity types.Verbosity 55 | OutputHex bool 56 | Timeout time.Duration 57 | RequestMatchReplaceDSL []string 58 | ResponseMatchReplaceDSL []string 59 | OnRequest func([]byte) []byte 60 | OnResponse func([]byte) []byte 61 | } 62 | 63 | func (so *SocketProxyOptions) Clone() SocketProxyOptions { 64 | return SocketProxyOptions{ 65 | Protocol: so.Protocol, 66 | ListenAddress: so.ListenAddress, 67 | RemoteAddress: so.RemoteAddress, 68 | HTTPProxy: so.HTTPProxy, 69 | HTTPServer: so.HTTPServer, 70 | listenAddress: so.listenAddress, 71 | remoteAddress: so.remoteAddress, 72 | TLSClientConfig: so.TLSClientConfig, 73 | TLSClient: so.TLSClient, 74 | TLSServerConfig: so.TLSServerConfig, 75 | TLSServer: so.TLSServer, 76 | OnRequest: so.OnRequest, 77 | OnResponse: so.OnResponse, 78 | RequestMatchReplaceDSL: so.RequestMatchReplaceDSL, 79 | ResponseMatchReplaceDSL: so.ResponseMatchReplaceDSL, 80 | } 81 | } 82 | 83 | func NewSocketProxy(options *SocketProxyOptions) *SocketProxy { 84 | return &SocketProxy{options: options} 85 | } 86 | 87 | func (p *SocketProxy) Run() error { 88 | var ( 89 | listener net.Listener 90 | err error 91 | ) 92 | if p.options.TLSServer { 93 | config := &tls.Config{InsecureSkipVerify: true} 94 | if p.options.TLSServerConfig != nil { 95 | config = p.options.TLSServerConfig 96 | } 97 | listener, err = tls.Listen(p.options.Protocol, p.options.ListenAddress, config) 98 | } else { 99 | listener, err = net.Listen(p.options.Protocol, p.options.ListenAddress) 100 | } 101 | if err != nil { 102 | return err 103 | } 104 | 105 | for { 106 | conn, err := listener.Accept() 107 | if err != nil { 108 | log.Println(err) 109 | return err 110 | } 111 | go p.Proxy(conn) //nolint 112 | } 113 | } 114 | 115 | func (p *SocketProxy) Proxy(conn net.Conn) error { 116 | var ( 117 | socketConn SocketConn 118 | err error 119 | ) 120 | 121 | socketConn.Timeout = p.options.Timeout 122 | socketConn.Verbosity = p.options.Verbosity 123 | socketConn.OutputHex = p.options.OutputHex 124 | 125 | socketConn.lconn = conn 126 | defer socketConn.lconn.Close() 127 | 128 | if p.options.TLSClient { 129 | config := &tls.Config{ 130 | InsecureSkipVerify: true, 131 | } 132 | if p.options.TLSClientConfig != nil { 133 | config = p.options.TLSClientConfig 134 | } 135 | socketConn.rconn, err = tls.Dial("tcp", p.options.RemoteAddress, config) 136 | } else { 137 | socketConn.rconn, err = net.Dial("tcp", p.options.RemoteAddress) 138 | } 139 | if err != nil { 140 | log.Println(err) 141 | return nil 142 | } 143 | 144 | defer socketConn.rconn.Close() 145 | 146 | if p.options.HTTPProxy != "" { 147 | proxyURL, err := url.Parse(p.options.HTTPProxy) 148 | if err != nil { 149 | return nil 150 | } 151 | socketConn.httpclient = &http.Client{ 152 | Transport: &http.Transport{ 153 | Proxy: http.ProxyURL(proxyURL), 154 | }, 155 | } 156 | socketConn.HTTPServer = p.options.HTTPServer 157 | } 158 | 159 | socketConn.OnRequest = p.options.OnRequest 160 | socketConn.OnResponse = p.options.OnResponse 161 | 162 | socketConn.RequestMatchReplaceDSL = p.options.RequestMatchReplaceDSL 163 | socketConn.ResponseMatchReplaceDSL = p.options.ResponseMatchReplaceDSL 164 | 165 | socketConn.errsig = make(chan bool) 166 | socketConn.fullduplex() 167 | 168 | return nil 169 | } 170 | 171 | func (p *SocketConn) err(s string, err error) { 172 | log.Println(err) 173 | if p.erred { 174 | return 175 | } 176 | if err != io.EOF { 177 | log.Printf(s, err) 178 | } 179 | p.errsig <- true 180 | p.erred = true 181 | } 182 | 183 | func (p *SocketConn) fullduplex() { 184 | //bidirectional copy 185 | log.Printf("Opened %s >>> %s", p.lconn.LocalAddr(), p.rconn.RemoteAddr()) 186 | go p.pipe(p.lconn, p.rconn) 187 | go p.pipe(p.rconn, p.lconn) 188 | if p.Timeout > 0 { 189 | p.lconn.SetDeadline(time.Now().Add(p.Timeout)) //nolint 190 | p.rconn.SetDeadline(time.Now().Add(p.Timeout)) //nolint 191 | } 192 | 193 | //wait for close... 194 | <-p.errsig 195 | log.Printf("Closed (%d bytes sent, %d bytes received)", p.sentBytes, p.receivedBytes) 196 | } 197 | 198 | func (p *SocketConn) pipe(src, dst io.ReadWriter) { 199 | islocal := src == p.lconn 200 | 201 | var dataDirection string 202 | if islocal { 203 | dataDirection = ">>> %d bytes sent%s" 204 | } else { 205 | dataDirection = "<<< %d bytes received%s" 206 | } 207 | 208 | byteFormat := "%s" 209 | if p.OutputHex { 210 | byteFormat = "%x" 211 | } 212 | //directional copy (64k buffer) 213 | buff := make([]byte, 0xffff) 214 | for { 215 | n, err := src.Read(buff) 216 | if err != nil { 217 | p.err("Read failed: %s\n", err) 218 | return 219 | } 220 | b := buff[:n] 221 | 222 | // Client => Proxy => Destination 223 | if islocal { 224 | // DSL 225 | for _, expr := range p.RequestMatchReplaceDSL { 226 | args := make(map[string]interface{}) 227 | args["data"] = b 228 | newB, err := dsl.EvalExpr(expr, args) 229 | // In case of error use the original value 230 | if err != nil { 231 | log.Printf("%s\n", err) 232 | } else { 233 | // otherwise replace it 234 | b = newB.([]byte) 235 | } 236 | } 237 | 238 | // Custom callback 239 | if p.OnRequest != nil { 240 | b = p.OnRequest(b) 241 | } 242 | } else { // Destination => Proxy => Client 243 | // DSL 244 | for _, expr := range p.ResponseMatchReplaceDSL { 245 | args := make(map[string]interface{}) 246 | args["data"] = b 247 | newB, err := dsl.EvalExpr(expr, args) 248 | // In case of error use the original value 249 | if err != nil { 250 | log.Printf("%s\n", err) 251 | } else { 252 | // otherwise replace it 253 | b = newB.([]byte) 254 | } 255 | } 256 | 257 | // Custom callback 258 | if p.OnResponse != nil { 259 | b = p.OnResponse(b) 260 | } 261 | } 262 | 263 | // show output 264 | log.Printf(dataDirection, n, "") 265 | log.Printf(byteFormat, b) 266 | 267 | // something custom 268 | if bytes.HasPrefix(b, []byte{0x16, 0x03}) { 269 | print("[!] SSL/TLS handshake detected, provide a server cert and key to enable interception.") 270 | } 271 | 272 | if p.httpclient != nil { 273 | resp, err := p.httpclient.Post(p.HTTPServer, "", bytes.NewReader(b)) 274 | if err != nil { 275 | log.Println(err) 276 | } else { 277 | b, _ = io.ReadAll(resp.Body) 278 | resp.Body.Close() 279 | } 280 | } 281 | 282 | // write out result 283 | n, err = dst.Write(b) 284 | if err != nil { 285 | p.err("Write failed: %s\n", err) 286 | return 287 | } 288 | 289 | if islocal { 290 | p.sentBytes += uint64(n) 291 | } else { 292 | p.receivedBytes += uint64(n) 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /static/coding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectdiscovery/proxify/b9c62cc293f99563f04e3a958d88af7b64dd4942/static/coding.png -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Proxify 6 | 149 | 150 | 151 |
152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | Download Certificate 161 | 162 |
163 | Swiss Army Knife Proxy for rapid deployments.
164 | Supports multiple operations such as request/response dump,
165 | filtering and manipulation via DSL language, upstream HTTP/Socks5 proxy.
166 | Additionally, a replay utility allows to import the dumped traffic
167 | (request/responses with correct domain name) into BurpSuite or
168 | any other proxy by simply setting the upstream proxy to proxify. 169 |
170 | 171 |
172 | 173 | Code Book 174 |
175 |
176 | 177 | 178 | -------------------------------------------------------------------------------- /static/pd-favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectdiscovery/proxify/b9c62cc293f99563f04e3a958d88af7b64dd4942/static/pd-favicon.ico -------------------------------------------------------------------------------- /static/pd-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectdiscovery/proxify/b9c62cc293f99563f04e3a958d88af7b64dd4942/static/pd-logo.png -------------------------------------------------------------------------------- /static/proxify-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectdiscovery/proxify/b9c62cc293f99563f04e3a958d88af7b64dd4942/static/proxify-logo-white.png -------------------------------------------------------------------------------- /static/proxify-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectdiscovery/proxify/b9c62cc293f99563f04e3a958d88af7b64dd4942/static/proxify-logo.png -------------------------------------------------------------------------------- /static/proxify-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/projectdiscovery/proxify/b9c62cc293f99563f04e3a958d88af7b64dd4942/static/proxify-run.png --------------------------------------------------------------------------------