├── .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 |
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 |
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 |
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
--------------------------------------------------------------------------------