├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd └── log4shell-tools-server │ ├── dns.go │ ├── ldap.go │ ├── main.go │ ├── net.go │ ├── prometheus.go │ ├── storage │ ├── db.go │ ├── memory.go │ └── storage.go │ └── templates │ └── index.html ├── container.nix ├── example ├── pom.xml └── src │ └── main │ └── java │ └── me │ └── alexbakker │ └── cve_2021_44228 │ └── App.java ├── flake.lock ├── flake.nix ├── go.mod └── go.sum /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [pull_request, push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: DeterminateSystems/nix-installer-action@de22e16c4711fca50c816cc9081563429d1cf563 9 | with: 10 | diagnostic-endpoint: 11 | - name: Build server 12 | run: | 13 | nix build --print-build-logs 14 | # tiny smoke test 15 | ./result/bin/log4shell-tools-server -h 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tmp 2 | result 3 | example/target/ 4 | cmd/log4shell-tools-server/log4shell-tools-server 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Alexander Bakker 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # log4shell.tools [![build](https://github.com/alexbakker/log4shell-tools/actions/workflows/build.yml/badge.svg)](https://github.com/alexbakker/log4shell-tools/actions/workflows/build.yml) 2 | 3 | __log4shell.tools__ is a tool that allows you to run a test to check whether one 4 | of your applications is affected by the recent vulnerabilities in log4j: 5 | __CVE-2021-44228__ and __CVE-2021-45046__. 6 | 7 | This is the code that runs https://log4shell.alexbakker.me. If you'd like to inspect the 8 | code or run an instance in your own environment, you've come to the right 9 | place. 10 | 11 | ## How does this work? 12 | 13 | The tool generates a unique ID for you to test with. After you click start, 14 | we'll generate a piece of text for you that looks similar to this: 15 | __${jndi:ldap://\*.dns.log4shell.tools:12345/\*}__. Copy it and paste it anywhere 16 | you suspect it might end up getting passed through log4j. For example: search 17 | boxes, form fields or HTTP headers. 18 | 19 | Once an outdated version of log4j sees this string, it will perform a DNS lookup 20 | to get the IP address of __\*.dns.log4shell.tools__. If this happens, it is 21 | considered the first sign of vulnerability to information leakage. Next, it will 22 | attempt and LDAP search request to __log4shell.tools:12345__. The tool responds 23 | with a Java class description, along with a URL for where to obtain it. Log4j 24 | may even attempt to fetch the class file. The tool will return a 404 and 25 | conclude the test. 26 | 27 | ## Screenshot 28 | 29 | 30 | 31 | ## Installation 32 | 33 | The tool was tested with Go 1.16. Make sure it (or a more recent version of Go) is 34 | installed and run the following command: 35 | 36 | ``` 37 | go install github.com/alexbakker/log4shell-tools/cmd/log4shell-tools-server 38 | ``` 39 | 40 | The binary will be available in ``$GOPATH/bin`` 41 | 42 | ### Usage 43 | 44 | Since this tool compiles to a single binary, all you have to do is run it to 45 | start self hosting an instance of log4shell.tools. To make it accessible by 46 | other machines in your network, you'll want to pass a couple of flags to stop 47 | the tool from only listening on the loopback interface. If you're exposing this 48 | to the internet, you'll probably also want to put a reverse proxy in front of 49 | the HTTP server. Ignore the DNS options for now, they're not needed for simple 50 | internal deployments. 51 | 52 | For the full list of available flags, run `log4shell-tools-server -h`: 53 | 54 | ``` 55 | Usage of ./log4shell-tools-server: 56 | 57 | This tool only listens on 127.0.0.1 by default. Pass the flags below to customize for your environment. 58 | 59 | -dns-a string 60 | the IPv4 address to respond with to any A record queries for 'dns-zone' (default "127.0.0.1") 61 | -dns-aaaa string 62 | the IPv6 address to respond with to any AAAA record queries for 'dns-zone' (default "::1") 63 | -dns-addr string 64 | listening address for the DNS server (default "127.0.0.1:12346") 65 | -dns-enable 66 | enable the DNS server 67 | -dns-zone string 68 | DNS zone that is forwarded to the tool's DNS server (example: "dns.log4shell.tools") 69 | -http-addr string 70 | listening address for the HTTP server (default "127.0.0.1:8001") 71 | -http-addr-external string 72 | address where the HTTP server can be reached externally (default "127.0.0.1:8001") 73 | -ldap-addr string 74 | listening address for the LDAP server (default "127.0.0.1:12345") 75 | -ldap-addr-external string 76 | address where the LDAP server can be reached externally (default "127.0.0.1:12345") 77 | -ldap-http-proto string 78 | the HTTP protocol to use in the payload URL that the LDAP server responds with (default "http") 79 | -storage string 80 | storage connection URI (either memory:// or a postgres:// URI (default "memory://") 81 | -test-timeout int 82 | test timeout in minutes (default 30) 83 | ``` 84 | 85 | #### Storage 86 | 87 | The tool uses its in-memory storage backend by default. If you need test 88 | results to persist across restarts, you may want to use the Postgres backend instead. 89 | 90 | #### DNS 91 | 92 | The DNS server is disabled by default, because its configuration options are 93 | currently very specific to the setup over at https://log4shell.alexbakker.me. Let me 94 | know if you'd like to help make these more generic. 95 | -------------------------------------------------------------------------------- /cmd/log4shell-tools-server/dns.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/alexbakker/log4shell-tools/cmd/log4shell-tools-server/storage" 9 | "github.com/google/uuid" 10 | "github.com/miekg/dns" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type DNSServer struct { 15 | s *dns.Server 16 | store storage.Backend 17 | opts DNSServerOpts 18 | } 19 | 20 | type DNSServerOpts struct { 21 | Addr string 22 | Zone string 23 | A string 24 | AAAA string 25 | } 26 | 27 | func NewDNSServer(store storage.Backend, opts DNSServerOpts) *DNSServer { 28 | s := DNSServer{store: store, opts: opts} 29 | mux := dns.NewServeMux() 30 | mux.HandleFunc(fmt.Sprintf("%s.", opts.Zone), s.handleDNSQuery) 31 | server := &dns.Server{Addr: opts.Addr, Net: "udp", Handler: mux} 32 | s.s = server 33 | return &s 34 | } 35 | 36 | func (s *DNSServer) ListenAndServe() error { 37 | return s.s.ListenAndServe() 38 | } 39 | 40 | func (s *DNSServer) handleDNSQuery(w dns.ResponseWriter, r *dns.Msg) { 41 | m := new(dns.Msg) 42 | m.SetReply(r) 43 | m.Compress = false 44 | 45 | if r.Opcode == dns.OpcodeQuery { 46 | if len(m.Question) == 0 { 47 | w.WriteMsg(m) 48 | return 49 | } 50 | 51 | q := m.Question[0] 52 | if q.Qtype != dns.TypeA && q.Qtype != dns.TypeAAAA { 53 | m.SetRcode(r, dns.RcodeSuccess) 54 | w.WriteMsg(m) 55 | return 56 | } 57 | 58 | if strings.HasPrefix(strings.ToLower(q.Name), s.opts.Zone) { 59 | s.writeDNSRes(m, r, q.Name, q.Qtype) 60 | w.WriteMsg(m) 61 | return 62 | } 63 | 64 | ctxLog := log.WithFields(log.Fields{ 65 | "server": "dns", 66 | "addr": w.RemoteAddr().String(), 67 | "q": q.Name, 68 | "type": dns.TypeToString[q.Qtype], 69 | }) 70 | 71 | parts := strings.Split(q.Name, ".") 72 | id, err := uuid.Parse(parts[0]) 73 | if err != nil { 74 | ctxLog.WithError(err).Error("Unable to parse UUID") 75 | m.SetRcode(r, dns.RcodeNameError) 76 | w.WriteMsg(m) 77 | return 78 | } 79 | 80 | ctxLog = ctxLog.WithField("test", id) 81 | ctxLog.Info("Handling DNS query") 82 | counterDNSQueries.Inc() 83 | 84 | test, err := s.store.Test(context.Background(), id) 85 | if err != nil { 86 | ctxLog.WithError(err).Error("Unable to lookup test in storage") 87 | m.SetRcode(r, dns.RcodeNameError) 88 | w.WriteMsg(m) 89 | return 90 | } 91 | if test == nil { 92 | ctxLog.Warn("Test not found") 93 | m.SetRcode(r, dns.RcodeNameError) 94 | w.WriteMsg(m) 95 | return 96 | } 97 | if test.Done(testTimeout) { 98 | ctxLog.Warn("Test already done") 99 | m.SetRcode(r, dns.RcodeNameError) 100 | w.WriteMsg(m) 101 | return 102 | } 103 | 104 | addr, ptr := getAddrPtr(context.Background(), w.RemoteAddr().String()) 105 | if err = s.store.InsertTestResult(context.Background(), test, storage.TestResultDnsQuery, addr, ptr); err != nil { 106 | ctxLog.WithError(err).Error("Unable to insert test result") 107 | w.WriteMsg(m) 108 | return 109 | } 110 | 111 | s.writeDNSRes(m, r, q.Name, q.Qtype) 112 | } 113 | 114 | w.WriteMsg(m) 115 | } 116 | 117 | func (s *DNSServer) writeDNSRes(m *dns.Msg, r *dns.Msg, name string, recordType uint16) { 118 | var record string 119 | switch recordType { 120 | case dns.TypeA: 121 | record = s.opts.A 122 | case dns.TypeAAAA: 123 | record = s.opts.AAAA 124 | } 125 | 126 | if record != "" { 127 | rr, err := dns.NewRR(fmt.Sprintf("%s %s %s", name, dns.TypeToString[recordType], record)) 128 | if err == nil { 129 | m.Answer = append(m.Answer, rr) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /cmd/log4shell-tools-server/ldap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | ldap "github.com/alexbakker/ldapserver" 8 | "github.com/alexbakker/log4shell-tools/cmd/log4shell-tools-server/storage" 9 | "github.com/google/uuid" 10 | "github.com/lor00x/goldap/message" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type LDAPServer struct { 15 | s *ldap.Server 16 | store storage.Backend 17 | } 18 | 19 | func NewLDAPServer(store storage.Backend) *LDAPServer { 20 | s := LDAPServer{store: store} 21 | 22 | mux := ldap.NewRouteMux() 23 | mux.Bind(s.handleBind) 24 | mux.Search(s.handleSearch) 25 | 26 | s.s = ldap.NewServer() 27 | s.s.Handle(mux) 28 | return &s 29 | } 30 | 31 | func (s *LDAPServer) ListenAndServe(addr string) error { 32 | return s.s.ListenAndServe(addr) 33 | } 34 | 35 | func (s *LDAPServer) handleSearch(w ldap.ResponseWriter, m *ldap.Message) { 36 | req := m.GetSearchRequest() 37 | 38 | ctxLog := log.WithFields(log.Fields{ 39 | "server": "ldap", 40 | "addr": m.Client.Addr(), 41 | "req": "search", 42 | "object": req.BaseObject(), 43 | }) 44 | ctxLog.Info("Handling LDAP search request") 45 | counterSearchRequests.Inc() 46 | 47 | id, err := uuid.Parse(string(req.BaseObject())) 48 | if err != nil { 49 | ctxLog.WithError(err).Error("Unable to parse UUID") 50 | 51 | res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultNoSuchObject) 52 | w.Write(res) 53 | return 54 | } 55 | ctxLog = ctxLog.WithField("test", id) 56 | 57 | test, err := s.store.Test(context.Background(), id) 58 | if err != nil { 59 | res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultNoSuchObject) 60 | w.Write(res) 61 | return 62 | } 63 | if test == nil || test.Done(testTimeout) { 64 | ctxLog.Warn("Test not found or already done") 65 | res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultNoSuchObject) 66 | w.Write(res) 67 | return 68 | } 69 | 70 | addr, ptr := getAddrPtr(context.Background(), m.Client.Addr().String()) 71 | if err = s.store.InsertTestResult(context.Background(), test, storage.TestResultLdapSearch, addr, ptr); err != nil { 72 | ctxLog.WithError(err).Error("Unable to insert test result") 73 | res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOther) 74 | w.Write(res) 75 | return 76 | } 77 | 78 | codeBase := fmt.Sprintf("%s://%s/api/tests/%s/payload/", *flagLDAPHTTPProto, *flagHTTPAddrExternal, id) 79 | e := ldap.NewSearchResultEntry("") 80 | e.AddAttribute("javaClassName", message.AttributeValue(className)) 81 | e.AddAttribute("javaCodeBase", message.AttributeValue(codeBase)) 82 | e.AddAttribute("objectClass", "javaNamingReference") 83 | e.AddAttribute("javaFactory", message.AttributeValue(className)) 84 | w.Write(e) 85 | 86 | res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess) 87 | w.Write(res) 88 | } 89 | 90 | func (s *LDAPServer) handleBind(w ldap.ResponseWriter, m *ldap.Message) { 91 | ctxLog := log.WithFields(log.Fields{ 92 | "server": "ldap", 93 | "addr": m.Client.Addr(), 94 | "req": "bind", 95 | }) 96 | ctxLog.Info("Handling LDAP bind request") 97 | counterBindRequests.Inc() 98 | 99 | res := ldap.NewBindResponse(ldap.LDAPResultSuccess) 100 | w.Write(res) 101 | } 102 | -------------------------------------------------------------------------------- /cmd/log4shell-tools-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | _ "embed" 7 | "flag" 8 | "fmt" 9 | "html/template" 10 | "net/http" 11 | "os" 12 | "sync" 13 | "time" 14 | 15 | ldap "github.com/alexbakker/ldapserver" 16 | "github.com/alexbakker/log4shell-tools/cmd/log4shell-tools-server/storage" 17 | "github.com/google/uuid" 18 | "github.com/julienschmidt/httprouter" 19 | "github.com/prometheus/client_golang/prometheus/promhttp" 20 | log "github.com/sirupsen/logrus" 21 | ) 22 | 23 | var ( 24 | flagStorage = flag.String("storage", "memory://", "storage connection URI (either memory:// or a postgres:// URI") 25 | flagDNSEnable = flag.Bool("dns-enable", false, "enable the DNS server") 26 | flagDNSAddr = flag.String("dns-addr", "127.0.0.1:12346", "listening address for the DNS server") 27 | flagDNSZone = flag.String("dns-zone", "", "DNS zone that is forwarded to the tool's DNS server (example: \"dns.log4shell.tools\")") 28 | flagDNSA = flag.String("dns-a", "127.0.0.1", "the IPv4 address to respond with to any A record queries for 'dns-zone'") 29 | flagDNSAAAA = flag.String("dns-aaaa", "::1", "the IPv6 address to respond with to any AAAA record queries for 'dns-zone'") 30 | flagLDAPAddr = flag.String("ldap-addr", "127.0.0.1:12345", "listening address for the LDAP server") 31 | flagLDAPAddrExternal = flag.String("ldap-addr-external", "127.0.0.1:12345", "address where the LDAP server can be reached externally") 32 | flagLDAPHTTPProto = flag.String("ldap-http-proto", "http", "the HTTP protocol to use in the payload URL that the LDAP server responds with") 33 | flagHTTPAddr = flag.String("http-addr", "127.0.0.1:8001", "listening address for the HTTP server") 34 | flagHTTPAddrExternal = flag.String("http-addr-external", "127.0.0.1:8001", "address where the HTTP server can be reached externally") 35 | flagTestTimeout = flag.Int("test-timeout", 30, "test timeout in minutes") 36 | flagDiscontinued = flag.Bool("discontinued", false, "whether this service has been discontinued") 37 | testTimeout = time.Duration(*flagTestTimeout) 38 | 39 | className = "Log4Shell" 40 | 41 | //go:embed templates/index.html 42 | tmplIndexText string 43 | tmplIndex *template.Template 44 | 45 | store storage.Backend 46 | statsCache *StatsCache 47 | ) 48 | 49 | type IndexModel struct { 50 | New bool 51 | UUID uuid.UUID 52 | Test *storage.Test 53 | Context context.Context 54 | AddrLDAP string 55 | AddrLDAPExternal string 56 | DNSEnabled bool 57 | DNSZone string 58 | ActiveTests int64 59 | Error string 60 | Discontinued bool 61 | } 62 | 63 | type StatsCache struct { 64 | store storage.Backend 65 | l sync.Mutex 66 | activeTests int64 67 | activeTestsFetched time.Time 68 | } 69 | 70 | func init() { 71 | ldap.Logger = ldap.DiscardingLogger 72 | 73 | flag.Usage = func() { 74 | fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n\n", os.Args[0]) 75 | fmt.Fprintf(flag.CommandLine.Output(), "This tool only listens on 127.0.0.1 by default. Pass the flags below to customize for your environment.\n\n") 76 | flag.PrintDefaults() 77 | } 78 | 79 | tmplFuncs := template.FuncMap{ 80 | "IsTestDone": func(test *storage.Test) bool { 81 | return test.Done(testTimeout) 82 | }, 83 | "IsTestTimedOut": func(test *storage.Test) bool { 84 | return test.TimedOut(testTimeout) 85 | }, 86 | "GetTestResults": func(ctx context.Context, test *storage.Test) ([]*storage.TestResult, error) { 87 | return store.TestResults(ctx, test) 88 | }, 89 | } 90 | 91 | var err error 92 | tmplIndex, err = template.New("index").Funcs(tmplFuncs).Parse(tmplIndexText) 93 | if err != nil { 94 | log.WithError(err).Fatal("Unable to load template") 95 | } 96 | } 97 | 98 | func main() { 99 | flag.Parse() 100 | testTimeout = time.Minute * time.Duration(*flagTestTimeout) 101 | 102 | if !*flagDiscontinued { 103 | log.WithField("uri", *flagStorage).Info("Opening storage backend") 104 | var err error 105 | store, err = storage.NewBackend(*flagStorage) 106 | if err != nil { 107 | log.WithError(err).Fatal("Unable to open storage backend") 108 | } 109 | defer store.Close() 110 | statsCache = &StatsCache{store: store} 111 | 112 | go func() { 113 | for { 114 | <-time.After(1 * time.Minute) 115 | 116 | deleted, err := store.PruneTestResults(context.Background()) 117 | if err != nil { 118 | log.WithError(err).Error("Unable to delete old test results") 119 | } else { 120 | log.WithField("count", deleted).Info("Deleted old test results") 121 | } 122 | } 123 | }() 124 | 125 | ldapServer := NewLDAPServer(store) 126 | go func() { 127 | log.WithFields(log.Fields{ 128 | "addr": *flagLDAPAddr, 129 | "addr_ext": *flagLDAPAddrExternal, 130 | }).Info("Starting LDAP server") 131 | 132 | if err := ldapServer.ListenAndServe(*flagLDAPAddr); err != nil { 133 | log.WithError(err).Fatal("Unable to run LDAP server") 134 | } 135 | }() 136 | 137 | if *flagDNSEnable { 138 | dnsServer := NewDNSServer(store, DNSServerOpts{ 139 | Addr: *flagDNSAddr, 140 | Zone: *flagDNSZone, 141 | A: *flagDNSA, 142 | AAAA: *flagDNSAAAA, 143 | }) 144 | 145 | go func() { 146 | log.WithFields(log.Fields{ 147 | "addr": *flagDNSAddr, 148 | }).Info("Starting DNS server") 149 | 150 | if err := dnsServer.ListenAndServe(); err != nil { 151 | log.WithError(err).Fatal("Unable to run DNS server") 152 | } 153 | }() 154 | } 155 | } else { 156 | log.Warn("Running in discontinued mode") 157 | } 158 | 159 | router := httprouter.New() 160 | router.GET("/", handleIndex) 161 | if !*flagDiscontinued { 162 | promHandler := promhttp.Handler() 163 | router.GET("/metrics", func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 164 | promHandler.ServeHTTP(w, r) 165 | }) 166 | router.GET(fmt.Sprintf("/api/tests/:uuid/payload/%s.class", className), handleTestPayloadDownload) 167 | } 168 | 169 | log.WithFields(log.Fields{ 170 | "addr": *flagHTTPAddr, 171 | "addr_ext": *flagHTTPAddrExternal, 172 | }).Info("Starting HTTP server") 173 | 174 | if err := http.ListenAndServe(*flagHTTPAddr, router); err != nil { 175 | log.WithError(err).Fatal("Unable to start HTTP server") 176 | } 177 | } 178 | 179 | func handleIndex(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 180 | var activeTests int64 181 | if !*flagDiscontinued { 182 | activeTests = statsCache.getActiveTests(r.Context()) 183 | } 184 | model := IndexModel{ 185 | Context: r.Context(), 186 | ActiveTests: activeTests, 187 | AddrLDAP: *flagLDAPAddr, 188 | AddrLDAPExternal: *flagLDAPAddrExternal, 189 | DNSEnabled: *flagDNSEnable, 190 | DNSZone: *flagDNSZone, 191 | Discontinued: *flagDiscontinued, 192 | } 193 | ctxLog := log.WithFields(log.Fields{ 194 | "server": "http", 195 | "addr": getRemoteAddr(r), 196 | "req": r.URL.Path, 197 | }) 198 | 199 | if !*flagDiscontinued { 200 | idString := r.URL.Query().Get("uuid") 201 | if idString != "" { 202 | var err error 203 | if model.UUID, err = uuid.Parse(idString); err != nil { 204 | ctxLog.WithField("id", idString).WithError(err).Error("Unable to parse UUID") 205 | writeHttpError(w, http.StatusBadRequest) 206 | return 207 | } 208 | ctxLog = ctxLog.WithField("test", model.UUID) 209 | 210 | model.Test, err = store.Test(r.Context(), model.UUID) 211 | if err != nil { 212 | ctxLog.WithError(err).Error("Unable to lookup test in storage") 213 | writeHttpError(w, http.StatusInternalServerError) 214 | return 215 | } 216 | if model.Test == nil { 217 | if r.URL.Query().Get("terms") != "y" { 218 | model.Error = "You cannot continue before agreeing to only testing on machines that you have permission to test on." 219 | } else { 220 | ctxLog.Info("Inserting new test") 221 | 222 | if err := store.InsertTest(r.Context(), model.UUID); err != nil { 223 | ctxLog.WithError(err).Error("Unable to insert new test") 224 | writeHttpError(w, http.StatusInternalServerError) 225 | return 226 | } 227 | if model.Test, err = store.Test(r.Context(), model.UUID); err != nil { 228 | ctxLog.WithError(err).Error("Unable to lookup test in storage") 229 | writeHttpError(w, http.StatusInternalServerError) 230 | return 231 | } 232 | if model.Test == nil { 233 | ctxLog.Error("Unable to obtain test right after insert") 234 | writeHttpError(w, http.StatusInternalServerError) 235 | return 236 | } 237 | 238 | counterTestsCreated.Inc() 239 | } 240 | } 241 | } else { 242 | model.New = true 243 | model.UUID = uuid.New() 244 | } 245 | } else { 246 | model.New = true 247 | } 248 | 249 | var buf bytes.Buffer 250 | if err := tmplIndex.Execute(&buf, model); err != nil { 251 | ctxLog.WithError(err).Error("Unable to render template") 252 | writeHttpError(w, http.StatusInternalServerError) 253 | return 254 | } 255 | 256 | w.Header().Add("Content-Type", "text/html") 257 | w.Write(buf.Bytes()) 258 | } 259 | 260 | func handleTestPayloadDownload(w http.ResponseWriter, r *http.Request, p httprouter.Params) { 261 | ctxLog := log.WithFields(log.Fields{ 262 | "server": "http", 263 | "addr": getRemoteAddr(r), 264 | "req": r.URL.Path, 265 | }) 266 | 267 | idString := p.ByName("uuid") 268 | id, err := uuid.Parse(idString) 269 | if err != nil { 270 | ctxLog.WithField("id", idString).WithError(err).Error("Unable to parse UUID") 271 | writeHttpError(w, http.StatusBadRequest) 272 | return 273 | } 274 | 275 | ctxLog = ctxLog.WithField("test", id) 276 | ctxLog.Info("Handling payload download request") 277 | counterPayloadRequests.Inc() 278 | 279 | test, err := store.Test(r.Context(), id) 280 | if err != nil { 281 | ctxLog.WithError(err).Error("Unable to lookup test in storage") 282 | writeHttpError(w, http.StatusInternalServerError) 283 | return 284 | } 285 | if test == nil { 286 | ctxLog.Warn("Test not found") 287 | writeHttpError(w, http.StatusNotFound) 288 | return 289 | } 290 | if test.Done(testTimeout) { 291 | ctxLog.Warn("Test already done") 292 | writeHttpError(w, http.StatusGone) 293 | return 294 | } 295 | 296 | addr, ptr := getAddrPtr(r.Context(), getRemoteAddr(r)) 297 | if err = store.InsertTestResult(r.Context(), test, storage.TestResultHttpGet, addr, ptr); err != nil { 298 | ctxLog.WithError(err).Error("Unable to insert test result") 299 | writeHttpError(w, http.StatusInternalServerError) 300 | return 301 | } 302 | if err = store.FinishTest(r.Context(), test); err != nil { 303 | ctxLog.WithError(err).Error("Unable to mark test as finished") 304 | writeHttpError(w, http.StatusInternalServerError) 305 | return 306 | } 307 | counterTestsCompleted.Inc() 308 | 309 | writeHttpError(w, http.StatusNotFound) 310 | } 311 | 312 | func writeHttpError(w http.ResponseWriter, code int) { 313 | http.Error(w, fmt.Sprintf("%d - %s", code, http.StatusText(code)), code) 314 | } 315 | 316 | func (c *StatsCache) getActiveTests(ctx context.Context) int64 { 317 | c.l.Lock() 318 | defer c.l.Unlock() 319 | 320 | if time.Since(c.activeTestsFetched) > 1*time.Minute { 321 | var err error 322 | c.activeTests, err = c.store.ActiveTests(ctx, testTimeout) 323 | if err != nil { 324 | log.WithError(err).Error("Unable to fetch number of active tests") 325 | } 326 | c.activeTestsFetched = time.Now() 327 | } 328 | 329 | return c.activeTests 330 | } 331 | -------------------------------------------------------------------------------- /cmd/log4shell-tools-server/net.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "inet.af/netaddr" 11 | ) 12 | 13 | func getRemoteAddr(r *http.Request) string { 14 | for _, header := range []string{"X-Forwarded-For", "X-Real-Ip"} { 15 | ips := strings.Split(r.Header.Get(header), ",") 16 | for i := len(ips) - 1; i >= 0; i-- { 17 | ip := strings.TrimSpace(ips[i]) 18 | realIP, err := netaddr.ParseIP(ip) 19 | if err != nil || realIP.IsPrivate() { 20 | continue 21 | } 22 | return ip 23 | } 24 | } 25 | return r.RemoteAddr 26 | } 27 | 28 | func getAddrPtr(ctx context.Context, addr string) (string, *string) { 29 | resCtx, cancel := context.WithTimeout(ctx, 2*time.Second) 30 | defer cancel() 31 | 32 | host, _, err := net.SplitHostPort(addr) 33 | if err != nil { 34 | host = addr 35 | } 36 | 37 | var ptr *string 38 | var resolver net.Resolver 39 | if names, err := resolver.LookupAddr(resCtx, host); err == nil && len(names) != 0 { 40 | ptr = &names[0] 41 | } 42 | 43 | return host, ptr 44 | } 45 | -------------------------------------------------------------------------------- /cmd/log4shell-tools-server/prometheus.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | "github.com/prometheus/client_golang/prometheus/promauto" 6 | ) 7 | 8 | var ( 9 | counterTestsCreated = promauto.NewCounter(prometheus.CounterOpts{ 10 | Name: "log4shell_tests_created_total", 11 | Help: "The total number of new tests inserted into the database", 12 | }) 13 | counterTestsCompleted = promauto.NewCounter(prometheus.CounterOpts{ 14 | Name: "log4shell_tests_completed_total", 15 | Help: "The total number of new tests that were completed (no timeout)", 16 | }) 17 | counterBindRequests = promauto.NewCounter(prometheus.CounterOpts{ 18 | Name: "log4shell_ldap_bind_requests_total", 19 | Help: "The total number of LDAP bind requests", 20 | }) 21 | counterSearchRequests = promauto.NewCounter(prometheus.CounterOpts{ 22 | Name: "log4shell_ldap_search_requests_total", 23 | Help: "The total number of LDAP search requests", 24 | }) 25 | counterPayloadRequests = promauto.NewCounter(prometheus.CounterOpts{ 26 | Name: "log4shell_http_payload_requests_total", 27 | Help: "The total number of RCE payload requests", 28 | }) 29 | counterDNSQueries = promauto.NewCounter(prometheus.CounterOpts{ 30 | Name: "log4shell_dns_queries_total", 31 | Help: "The total number of DNS queries", 32 | }) 33 | ) 34 | -------------------------------------------------------------------------------- /cmd/log4shell-tools-server/storage/db.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | "github.com/jackc/pgx/v4" 10 | "github.com/jackc/pgx/v4/pgxpool" 11 | ) 12 | 13 | const ( 14 | schema = ` 15 | CREATE TABLE IF NOT EXISTS test ( 16 | id UUID NOT NULL, 17 | created timestamp NOT NULL DEFAULT timezone('utc'::text, CURRENT_TIMESTAMP), 18 | finished timestamp, 19 | PRIMARY KEY (id) 20 | ); 21 | 22 | CREATE TABLE IF NOT EXISTS test_result ( 23 | id BIGSERIAL NOT NULL, 24 | test_id UUID NOT NULL, 25 | created timestamp NOT NULL DEFAULT timezone('utc'::text, CURRENT_TIMESTAMP), 26 | type TEXT NOT NULL, 27 | addr TEXT, 28 | ptr TEXT, 29 | PRIMARY KEY (id), 30 | FOREIGN KEY (test_id) REFERENCES test (id) ON DELETE CASCADE 31 | ); 32 | 33 | CREATE INDEX IF NOT EXISTS test_result_test_id_idx ON test_result (test_id); 34 | ` 35 | ) 36 | 37 | type DB struct { 38 | p *pgxpool.Pool 39 | } 40 | 41 | func NewDB(connStr string) (*DB, error) { 42 | p, err := pgxpool.Connect(context.Background(), connStr) 43 | if err != nil { 44 | return nil, fmt.Errorf("db connect: %s", err) 45 | } 46 | 47 | if _, err = p.Exec(context.Background(), schema); err != nil { 48 | return nil, fmt.Errorf("schema init: %s", err) 49 | } 50 | 51 | return &DB{p: p}, nil 52 | } 53 | 54 | func (db *DB) Close() { 55 | db.p.Close() 56 | } 57 | 58 | func (db *DB) Test(ctx context.Context, id uuid.UUID) (*Test, error) { 59 | row := db.p.QueryRow(ctx, `SELECT id, created, finished FROM test WHERE id = $1`, id.String()) 60 | 61 | var test Test 62 | if err := row.Scan( 63 | &test.ID, 64 | &test.Created, 65 | &test.Finished); err != nil { 66 | if err == pgx.ErrNoRows { 67 | return nil, nil 68 | } 69 | 70 | return nil, err 71 | } 72 | 73 | return &test, nil 74 | } 75 | 76 | func (db *DB) InsertTest(ctx context.Context, id uuid.UUID) error { 77 | _, err := db.p.Exec(ctx, "INSERT INTO test (id) VALUES($1)", id) 78 | return err 79 | } 80 | 81 | func (db *DB) InsertTestResult(ctx context.Context, t *Test, resultType string, addr string, ptr *string) error { 82 | _, err := db.p.Exec(ctx, ` 83 | INSERT INTO test_result (test_id, type, addr, ptr) 84 | VALUES($1, $2, $3, $4) 85 | `, t.ID, resultType, addr, ptr) 86 | return err 87 | } 88 | 89 | func (db *DB) TestResults(ctx context.Context, t *Test) ([]*TestResult, error) { 90 | rows, err := db.p.Query(ctx, ` 91 | SELECT created, type, addr, ptr 92 | FROM test_result 93 | WHERE test_id = $1 94 | ORDER BY created ASC 95 | `, t.ID) 96 | if err != nil { 97 | return nil, err 98 | } 99 | defer rows.Close() 100 | 101 | var results []*TestResult 102 | for rows.Next() { 103 | var res TestResult 104 | if err = rows.Scan(&res.Created, &res.Type, &res.Addr, &res.Ptr); err != nil { 105 | return nil, err 106 | } 107 | 108 | results = append(results, &res) 109 | } 110 | 111 | return results, nil 112 | } 113 | 114 | func (db *DB) PruneTestResults(ctx context.Context) (int64, error) { 115 | res, err := db.p.Exec(ctx, ` 116 | DELETE FROM test 117 | WHERE created < timezone('utc'::text, CURRENT_TIMESTAMP) - '1 day'::interval 118 | `) 119 | if err != nil { 120 | return 0, err 121 | } 122 | 123 | return res.RowsAffected(), nil 124 | } 125 | 126 | func (db *DB) FinishTest(ctx context.Context, t *Test) error { 127 | _, err := db.p.Exec(ctx, ` 128 | UPDATE test 129 | SET finished = timezone('utc'::text, CURRENT_TIMESTAMP) 130 | WHERE id = $1 131 | `, t.ID) 132 | return err 133 | } 134 | 135 | func (db *DB) ActiveTests(ctx context.Context, timeout time.Duration) (int64, error) { 136 | var count int64 137 | row := db.p.QueryRow(ctx, ` 138 | SELECT count(*) 139 | FROM test 140 | WHERE finished IS NULL 141 | AND created > timezone('utc'::text, CURRENT_TIMESTAMP) - ('1 minute'::interval * $1); 142 | `, int64(timeout.Minutes())) 143 | 144 | err := row.Scan(&count) 145 | return count, err 146 | } 147 | -------------------------------------------------------------------------------- /cmd/log4shell-tools-server/storage/memory.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "time" 8 | 9 | "github.com/google/uuid" 10 | ) 11 | 12 | type testWrapper struct { 13 | Test *Test 14 | Results []*TestResult 15 | } 16 | 17 | type Memory struct { 18 | tests map[uuid.UUID]*testWrapper 19 | l sync.Mutex 20 | } 21 | 22 | func NewMemory() *Memory { 23 | return &Memory{tests: map[uuid.UUID]*testWrapper{}} 24 | } 25 | 26 | func (m *Memory) Close() { 27 | 28 | } 29 | 30 | func (m *Memory) Test(ctx context.Context, id uuid.UUID) (*Test, error) { 31 | m.l.Lock() 32 | defer m.l.Unlock() 33 | 34 | tw, ok := m.tests[id] 35 | if !ok { 36 | return nil, nil 37 | } 38 | 39 | copy := *tw.Test 40 | return ©, nil 41 | } 42 | 43 | func (m *Memory) InsertTest(ctx context.Context, id uuid.UUID) error { 44 | m.l.Lock() 45 | defer m.l.Unlock() 46 | 47 | _, ok := m.tests[id] 48 | if ok { 49 | return fmt.Errorf("not found: %s", id) 50 | } 51 | 52 | now := time.Now().UTC() 53 | m.tests[id] = &testWrapper{Test: &Test{ID: id, Created: &now}} 54 | return nil 55 | } 56 | 57 | func (m *Memory) InsertTestResult(ctx context.Context, t *Test, resultType string, addr string, ptr *string) error { 58 | m.l.Lock() 59 | defer m.l.Unlock() 60 | 61 | tw, ok := m.tests[t.ID] 62 | if !ok { 63 | return fmt.Errorf("not found: %s", t.ID) 64 | } 65 | 66 | now := time.Now().UTC() 67 | tw.Results = append(tw.Results, &TestResult{ 68 | TestID: tw.Test.ID, 69 | Created: &now, 70 | Type: resultType, 71 | Addr: &addr, 72 | Ptr: ptr, 73 | }) 74 | return nil 75 | } 76 | 77 | func (m *Memory) TestResults(ctx context.Context, t *Test) ([]*TestResult, error) { 78 | m.l.Lock() 79 | defer m.l.Unlock() 80 | 81 | tw, ok := m.tests[t.ID] 82 | if !ok { 83 | return nil, fmt.Errorf("not found: %s", t.ID) 84 | } 85 | 86 | var res []*TestResult 87 | for _, tres := range tw.Results { 88 | copy := *tres 89 | res = append(res, ©) 90 | } 91 | 92 | return res, nil 93 | } 94 | 95 | func (m *Memory) PruneTestResults(ctx context.Context) (int64, error) { 96 | m.l.Lock() 97 | defer m.l.Unlock() 98 | 99 | var count int64 100 | for id, tw := range m.tests { 101 | if time.Since(*tw.Test.Created) > time.Hour*24 { 102 | delete(m.tests, id) 103 | count++ 104 | } 105 | } 106 | 107 | return count, nil 108 | } 109 | 110 | func (m *Memory) FinishTest(ctx context.Context, t *Test) error { 111 | m.l.Lock() 112 | defer m.l.Unlock() 113 | 114 | tw, ok := m.tests[t.ID] 115 | if !ok { 116 | return fmt.Errorf("not found: %s", t.ID) 117 | } 118 | 119 | now := time.Now().UTC() 120 | tw.Test.Finished = &now 121 | return nil 122 | } 123 | 124 | func (m *Memory) ActiveTests(ctx context.Context, timeout time.Duration) (int64, error) { 125 | m.l.Lock() 126 | defer m.l.Unlock() 127 | 128 | var count int64 129 | for _, tw := range m.tests { 130 | if tw.Test.Finished == nil && time.Since(*tw.Test.Created) < timeout { 131 | count++ 132 | } 133 | } 134 | 135 | return count, nil 136 | } 137 | -------------------------------------------------------------------------------- /cmd/log4shell-tools-server/storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | ) 10 | 11 | const ( 12 | TestResultDnsQuery = "recv_dns_query" 13 | TestResultLdapBind = "recv_ldap_bind" 14 | TestResultLdapSearch = "recv_ldap_search" 15 | TestResultHttpGet = "recv_http_get" 16 | ) 17 | 18 | type Backend interface { 19 | Close() 20 | Test(ctx context.Context, id uuid.UUID) (*Test, error) 21 | InsertTest(ctx context.Context, id uuid.UUID) error 22 | InsertTestResult(ctx context.Context, t *Test, resultType string, addr string, ptr *string) error 23 | TestResults(ctx context.Context, t *Test) ([]*TestResult, error) 24 | PruneTestResults(ctx context.Context) (int64, error) 25 | FinishTest(ctx context.Context, t *Test) error 26 | ActiveTests(ctx context.Context, timeout time.Duration) (int64, error) 27 | } 28 | 29 | type Test struct { 30 | ID uuid.UUID `db:"id"` 31 | Created *time.Time `db:"created"` 32 | Finished *time.Time `db:"finished"` 33 | } 34 | 35 | type TestResult struct { 36 | TestID uuid.UUID `db:"test_id"` 37 | Created *time.Time `db:"created"` 38 | Type string `db:type` 39 | Addr *string `db:ip` 40 | Ptr *string `db:ptr` 41 | } 42 | 43 | func NewBackend(connStr string) (Backend, error) { 44 | if strings.HasPrefix(connStr, "memory://") { 45 | return NewMemory(), nil 46 | } 47 | 48 | return NewDB(connStr) 49 | } 50 | 51 | func (t *Test) Done(timeout time.Duration) bool { 52 | return t.Finished != nil || t.TimedOut(timeout) 53 | } 54 | 55 | func (t *Test) TimedOut(d time.Duration) bool { 56 | return time.Since(*t.Created) > d 57 | } 58 | 59 | func (r *TestResult) Color() string { 60 | if r.Type == TestResultHttpGet { 61 | return "danger" 62 | } 63 | 64 | return "warning" 65 | } 66 | -------------------------------------------------------------------------------- /cmd/log4shell-tools-server/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Log4Shell Vulnerability Test Tool 5 | 6 | 7 | 8 | {{ if .Test }} 9 | {{ if not (IsTestDone .Test) }} 10 | 11 | {{ end }} 12 | {{ end }} 13 | 14 | 15 | 34 | 35 | 36 |
37 |
38 | 46 |
47 |
48 |
49 |
50 | 59 | {{ if gt .ActiveTests 0 }} 60 | ({{ .ActiveTests }} tests currently active) 61 | {{ end }} 62 | {{ if .Discontinued }} 63 | 71 | {{ end }} 72 |

73 | This tool allows you to run a test to check whether one of your 74 | applications is affected by the recent vulnerabilities in log4j: 75 | CVE-2021-44228 77 | and CVE-2021-45046. 79 | When you hit 'Start', the tool will generate a unique JNDI URI for 80 | you to enter anywhere you suspect it might end up being processed 81 | by log4j. If log4j triggers so much as a DNS lookup, this tool 82 | will tell you about it. 83 |

84 |

85 | You may only use this tool on machines that you have permission 86 | to test on. 87 |

88 | {{ if .Error }} 89 | 92 | {{ end }} 93 |
94 |
95 | 96 | 97 |
98 |
99 | 100 | 103 |
104 | 105 |
106 |
107 |
108 | {{ if .Test }} 109 |
110 |
111 |

112 | {{ if IsTestDone .Test }} 113 | Finished 114 | {{ if IsTestTimedOut .Test }} 115 | (The test timed out) 116 | {{ end }} 117 | {{ else }} 118 |
119 | Waiting... 120 |
121 | Waiting 122 | {{ end }} 123 |

124 | {{ if not (IsTestDone .Test) }} 125 |

Copy the texts below and paste them anywhere you suspect it might end up being processed by log4j:

126 | 127 |
128 | {{ if .DNSEnabled }} 129 | 130 | {{ else }} 131 | 132 | {{ end }} 133 | 134 |
135 | {{ if .DNSEnabled }} 136 | 137 |
138 | 139 | 140 |
141 | {{ end }} 142 | {{ end }} 143 | 147 |
148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | {{ range $val := (GetTestResults .Context .Test) }} 159 | 160 | 161 | 162 | 163 | 172 | 173 | {{ end }} 174 | 175 |
TimeTypeSourceMessage
{{ $val.Created.Format "2006-01-02 15:04:05" }}{{ $val.Type }}{{ if $val.Ptr }}{{ $val.Ptr }}{{ else }}{{ $val.Addr }}{{ end }} 164 | {{ if eq $val.Type "recv_ldap_search" }} 165 | LDAP search query received. At the very least, your log4j deployment supports doing lookups. This can lead to information leakage. 166 | {{ else if eq $val.Type "recv_http_get" }} 167 | GET request for RCE payload payload received. 168 | {{ else if eq $val.Type "recv_dns_query" }} 169 | DNS query received. It is likely that your log4j deployment supports doing lookups. This can lead to information leakage. 170 | {{ end }} 171 |
176 |

Timestamps are in UTC. Test results are permanently deleted after 24 hours.

177 |
178 |
179 |
180 | {{ end }} 181 |
182 |
183 |

Changelog

184 |

When significant changes are made to the functionality of the tool, I'll post an update here.

185 |

2021-12-14

186 |

187 | The tool now supports detection through DNS. The 188 | jndi:ldap:// URI that is generated for your unique test 189 | ID now also contains a unique *.dns.log4shell.tools name. 190 | With that, the first signs of information leak vulnerability already appear 191 | when log4j performs a DNS lookup, before even connecting to the LDAP 192 | server. This should significantly reduce the number of false 193 | negatives when testing on machines that don't directly have access 194 | to the internet or can't connect to log4shell.tools for some other 195 | reason. 196 |

197 |

198 | Newly generated LDAP JNDI URI's now use this feature by default. There's a dedicated jndi:dns:// option as well. 199 |

200 |
201 |
202 |
203 |
204 |

FAQ

205 |

Here's a short list of frequently asked questions. If yours is not answered here, feel free to reach out.

206 |
What is CVE-2021-44228?
207 |

208 | CVE-2021-44228 209 | is a vulnerability in the popular log4j library by Apache. In the 210 | worst case, it allows bad actors to execute code on any server 211 | where they're able to get log4j to process a malicious log 212 | message. 213 |

214 |

215 | If you're using log4j or a product that depends on log4j, you should act on this immediately. 216 |

217 |
Can I use this tool to test for CVE-2021-45046 as well?
218 |

Yes.

219 |
What can I do to protect myself?
220 |

Please read the official advisory from Apache: https://logging.apache.org/log4j/2.x/security.html#Fixed_in_Log4j_2.15.0.

221 |
What does this tool do?
222 |

223 | The tool generates a unique ID for you to test with. After you 224 | click start, we'll generate a piece of text for you that looks 225 | similar to this: ${jndi:ldap://*.dns.log4shell.tools:12345/*}. 226 | Copy it and paste it anywhere you suspect it might end up getting 227 | passed through log4j. For example: search boxes, form fields or 228 | HTTP headers. 229 |

230 |

231 | Once an outdated version of log4j sees this string, it will 232 | perform a DNS lookup to get the IP address of 233 | *.dns.log4shell.tools. If this happens, it is considered 234 | the first sign of vulnerability to information leakage. Next, it 235 | will attempt and LDAP search request to 236 | log4shell.tools:12345. The tool responds with a Java class 237 | description, along with a URL for where to obtain it. Log4j may 238 | even attempt to fetch the class file. The tool will return a 404 239 | and conclude the test. 240 |

241 |
Am I safe if the tool doesn't report anything after the test?
242 |

243 | Not necessarily. If the machine you're testing on does not have 244 | access to the internet or can't reach log4shell.tools for 245 | some other reason, the results will not be accurate. The tool is 246 | only meant to give you a rough assessment of what someone with no 247 | special access to your environment would be able to do. 248 |

249 |

250 | The only way to make sure you're safe, is to start applying 251 | patches. 252 |

253 |
Is there any chance that the tool reports a false positive?
254 |

255 | The DNS lookup detection feature may result in a false positive in 256 | some cases. For example, this can happen if the environment you're 257 | testing has some other tooling that is examining the logs or the 258 | traffic on the network. If the tooling finds anything suspicious, 259 | it may perform a DNS lookup as part of its analysis. This will 260 | lead us to believe that we triggered a lookup in log4j, while that 261 | was actually not the case. 262 |

263 |
Isn't releasing such a tool to the public dangerous?
264 |

265 | I believe in arming the public with the same tools that the bad 266 | actors we're up against already have. Especially in this case 267 | where the vulnerability is so trivial to exploit. Anyone with some 268 | decent Google-fu will be able to find a full PoC (including RCE) 269 | within minutes. The goal is to contribute to leveling the playing 270 | field by allowing anyone to perform a rough assessment of how 271 | vulnerable they are to this log4j vulnerability. 272 |

273 |

274 | Especially if your product runs on a service where you don't have 275 | enough access to update log4j or change its options, a test result 276 | from a tool like this might be enough to get the attention of the 277 | right people. 278 |

279 |
Does this tool perform RCE (remote code execution)?
280 |

281 | No. It runs the exploit right up until your device requests the 282 | RCE payload, then it returns a 404 and concludes the test. 283 |

284 |
What is the privacy policy?
285 |

286 | The tool stores test results, IP addresses and PTR records of the servers 287 | that reach out to it. All test results and any information 288 | collected along with it is automatically permanently deleted 289 | after 24 hours. 290 |

291 |

292 | No information is shared with third parties. To ensure the privacy 293 | of your test results, do not share your unique test ID with anyone 294 | else. 295 |

296 |
Is this tool open source?
297 |

298 | Yes! The code is available on GitHub: 299 | https://github.com/alexbakker/log4shell-tools. 300 |

301 |
Is this tool affiliated with the Apache Software Foundation?
302 |

No.

303 |
I have another question / found an issue. How can I contact you?
304 |

305 | Feel free to send me an 306 | email. If you're reporting a security issue, please don't 307 | discuss it in public before I've had an opportunity to fix it. 308 |

309 |
310 |
311 |
312 |
313 | 318 | 345 | 346 | 347 | -------------------------------------------------------------------------------- /container.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, lib, ... }: 2 | 3 | # This container is only used during development, so we don't bother securely 4 | # configuring PostgreSQL here. 5 | 6 | { 7 | boot.isContainer = true; 8 | 9 | services.postgresql = { 10 | enable = true; 11 | enableTCPIP = true; 12 | settings = { 13 | log_statement = "all"; 14 | logging_collector = true; 15 | }; 16 | authentication = pkgs.lib.mkOverride 10 '' 17 | local all all trust 18 | hostnossl all all 0.0.0.0/0 trust 19 | ''; 20 | initialScript = pkgs.writeText "postgresql-init-script" '' 21 | CREATE ROLE log4shell WITH LOGIN PASSWORD 'log4shell' CREATEDB; 22 | CREATE DATABASE log4shell; 23 | GRANT ALL PRIVILEGES ON DATABASE log4shell TO log4shell; 24 | ''; 25 | }; 26 | 27 | networking = { 28 | useDHCP = false; 29 | firewall.allowedTCPPorts = [ 30 | config.services.postgresql.port 31 | ]; 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /example/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | me.alexbakker.cve_2021_44228 8 | cve_2021_44228 9 | 1.0-SNAPSHOT 10 | cve_2021_44228 11 | 12 | 13 | UTF-8 14 | 1.7 15 | 1.7 16 | 17 | 18 | 19 | 20 | org.apache.logging.log4j 21 | log4j-api 22 | 2.14.0 23 | 24 | 25 | org.apache.logging.log4j 26 | log4j-core 27 | 2.14.0 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | maven-assembly-plugin 36 | 2.5.5 37 | 38 | ${project.artifactId}-${project.version}-all 39 | false 40 | 41 | jar-with-dependencies 42 | 43 | 44 | 45 | me.alexbakker.cve_2021_44228.App 46 | 47 | 48 | 49 | 50 | 51 | make-assembly 52 | package 53 | 54 | single 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /example/src/main/java/me/alexbakker/cve_2021_44228/App.java: -------------------------------------------------------------------------------- 1 | package me.alexbakker.cve_2021_44228; 2 | 3 | import java.util.logging.Level; 4 | 5 | import org.apache.logging.log4j.LogManager; 6 | import org.apache.logging.log4j.Logger; 7 | 8 | public class App { 9 | private static Logger log = LogManager.getLogger(); 10 | 11 | public static void main(String[] args) { 12 | System.out.print("Enter a JNDI URI: "); 13 | String uri = System.console().readLine(); 14 | log.error(uri); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1710146030, 9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1712439257, 24 | "narHash": "sha256-aSpiNepFOMk9932HOax0XwNxbA38GOUVOiXfUVPOrck=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "ff0dbd94265ac470dda06a657d5fe49de93b4599", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "id": "nixpkgs", 32 | "ref": "nixos-unstable", 33 | "type": "indirect" 34 | } 35 | }, 36 | "root": { 37 | "inputs": { 38 | "flake-utils": "flake-utils", 39 | "nixpkgs": "nixpkgs" 40 | } 41 | }, 42 | "systems": { 43 | "locked": { 44 | "lastModified": 1681028828, 45 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 46 | "owner": "nix-systems", 47 | "repo": "default", 48 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "nix-systems", 53 | "repo": "default", 54 | "type": "github" 55 | } 56 | } 57 | }, 58 | "root": "root", 59 | "version": 7 60 | } 61 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Nix flake for log4shell-tools"; 3 | inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; 4 | inputs.flake-utils.url = "github:numtide/flake-utils"; 5 | 6 | outputs = { self, nixpkgs, flake-utils }: 7 | flake-utils.lib.eachDefaultSystem (system: 8 | let 9 | pkgs = import nixpkgs { inherit system; }; 10 | in { 11 | defaultPackage = 12 | with pkgs; buildGoModule { 13 | name = "log4shell-tools"; 14 | src = ./.; 15 | 16 | vendorHash = "sha256-TjtI04iYvpist+BH7Abrpru5cs0kmC0VJlX1HBT/tjw="; 17 | 18 | subPackages = [ "./cmd/log4shell-tools-server" ]; 19 | }; 20 | nixosConfigurations.devContainer = nixpkgs.lib.nixosSystem { 21 | inherit system; 22 | modules = [ 23 | ./container.nix 24 | ]; 25 | }; 26 | devShell = with pkgs; mkShell { 27 | buildInputs = [ 28 | go 29 | maven 30 | openjdk8 31 | ]; 32 | }; 33 | } 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/alexbakker/log4shell-tools 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/alexbakker/ldapserver v1.0.2-0.20211212143751-7c2ef6599ffa 7 | github.com/google/uuid v1.3.0 8 | github.com/jackc/pgx/v4 v4.18.1 9 | github.com/julienschmidt/httprouter v1.3.0 10 | github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 11 | github.com/miekg/dns v1.1.55 12 | github.com/prometheus/client_golang v1.16.0 13 | github.com/sirupsen/logrus v1.9.3 14 | inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a 15 | ) 16 | 17 | require ( 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 20 | github.com/golang/protobuf v1.5.3 // indirect 21 | github.com/jackc/chunkreader/v2 v2.0.1 // indirect 22 | github.com/jackc/pgconn v1.14.0 // indirect 23 | github.com/jackc/pgio v1.0.0 // indirect 24 | github.com/jackc/pgpassfile v1.0.0 // indirect 25 | github.com/jackc/pgproto3/v2 v2.3.2 // indirect 26 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 27 | github.com/jackc/pgtype v1.14.0 // indirect 28 | github.com/jackc/puddle v1.3.0 // indirect 29 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 30 | github.com/prometheus/client_model v0.4.0 // indirect 31 | github.com/prometheus/common v0.44.0 // indirect 32 | github.com/prometheus/procfs v0.11.0 // indirect 33 | go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect 34 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect 35 | golang.org/x/crypto v0.11.0 // indirect 36 | golang.org/x/mod v0.12.0 // indirect 37 | golang.org/x/net v0.12.0 // indirect 38 | golang.org/x/sys v0.10.0 // indirect 39 | golang.org/x/text v0.11.0 // indirect 40 | golang.org/x/tools v0.11.0 // indirect 41 | google.golang.org/protobuf v1.31.0 // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 3 | github.com/alexbakker/ldapserver v1.0.2-0.20211212143751-7c2ef6599ffa h1:Tsgs30wcAAx0Cj73NUeqFJXfieZFrVGUtenMr3jGfXU= 4 | github.com/alexbakker/ldapserver v1.0.2-0.20211212143751-7c2ef6599ffa/go.mod h1:3AQlUcdYBjOpq2QfJSifBsPcUoHxwAfDyDzYHRmw5+A= 5 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 6 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 7 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 8 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 10 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 11 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 12 | github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 13 | github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= 18 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 19 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 20 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 21 | github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= 22 | github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 23 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 24 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 25 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 26 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 27 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 28 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 29 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 30 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 31 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 32 | github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= 33 | github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 34 | github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= 35 | github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 36 | github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= 37 | github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= 38 | github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= 39 | github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= 40 | github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= 41 | github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= 42 | github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= 43 | github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= 44 | github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= 45 | github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= 46 | github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 47 | github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= 48 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= 49 | github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= 50 | github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 51 | github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 52 | github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= 53 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= 54 | github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= 55 | github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 56 | github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= 57 | github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 58 | github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 59 | github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= 60 | github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= 61 | github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= 62 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= 63 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 64 | github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= 65 | github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= 66 | github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= 67 | github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= 68 | github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= 69 | github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= 70 | github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= 71 | github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= 72 | github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= 73 | github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= 74 | github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= 75 | github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= 76 | github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 77 | github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 78 | github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 79 | github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= 80 | github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= 81 | github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= 82 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 83 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 84 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 85 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 86 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 87 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 88 | github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= 89 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 90 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 91 | github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 92 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 93 | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= 94 | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 95 | github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuMxhatuSzHaljStc4gjDeKycxy0A= 96 | github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0= 97 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= 98 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 99 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 100 | github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 101 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 102 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 103 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 104 | github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= 105 | github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= 106 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 107 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 108 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 109 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 110 | github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= 111 | github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= 112 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 113 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 114 | github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= 115 | github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 116 | github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk= 117 | github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= 118 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 119 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 120 | github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= 121 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 122 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 123 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 124 | github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= 125 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 126 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 127 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 128 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 129 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 130 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 131 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 132 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 133 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 134 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 135 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 136 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 137 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 138 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 139 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 140 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 141 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 142 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 143 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 144 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 145 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 146 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 147 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 148 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 149 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 150 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 151 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 152 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 153 | go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= 154 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 155 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 156 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 157 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 158 | go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= 159 | go4.org/intern v0.0.0-20230525184215-6c62f75575cb h1:ae7kzL5Cfdmcecbh22ll7lYP3iuUdnfnhiPcSaDgH/8= 160 | go4.org/intern v0.0.0-20230525184215-6c62f75575cb/go.mod h1:Ycrt6raEcnF5FTsLiLKkhBTO6DPX3RCUCUVnks3gFJU= 161 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= 162 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 h1:WJhcL4p+YeDxmZWg141nRm7XC8IDmhz7lk5GpadO1Sg= 163 | go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= 164 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 165 | golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 166 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 167 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 168 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 169 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 170 | golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 171 | golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 172 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 173 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 174 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 175 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= 176 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 177 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 178 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 179 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 180 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 181 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 182 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 183 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 184 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 185 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 186 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 187 | golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 188 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 189 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 190 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 191 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 192 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= 193 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 194 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 195 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 196 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 197 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 198 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 199 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 200 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 201 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 202 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 203 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 204 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 206 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 207 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 208 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 209 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 210 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 211 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 212 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 213 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 214 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 215 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 216 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 217 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 218 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 219 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 220 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 221 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 222 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 223 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 224 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 225 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 226 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 227 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 228 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 229 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 230 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= 231 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 232 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 233 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 234 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 235 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 236 | golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 237 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 238 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 239 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 240 | golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 241 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 242 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 243 | golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= 244 | golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= 245 | golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 246 | golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 247 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 248 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 249 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 250 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 251 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 252 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 253 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 254 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 255 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 256 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 257 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 258 | gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= 259 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 260 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 261 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 262 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 263 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 264 | inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a h1:1XCVEdxrvL6c0TGOhecLuB7U9zYNdxZEjvOqJreKZiM= 265 | inet.af/netaddr v0.0.0-20230525184311-b8eac61e914a/go.mod h1:e83i32mAQOW1LAqEIweALsuK2Uw4mhQadA5r7b0Wobo= 266 | --------------------------------------------------------------------------------