diff --git a/framework.go b/framework.go index ea02fc3..51f83f3 100644 --- a/framework.go +++ b/framework.go @@ -71,6 +71,7 @@ import ( "sync/atomic" "time" + "github.com/Masterminds/semver" "github.com/vulncheck-oss/go-exploit/c2" "github.com/vulncheck-oss/go-exploit/c2/channel" "github.com/vulncheck-oss/go-exploit/cli" @@ -428,6 +429,27 @@ func StoreVersion(conf *config.Config, version string) { db.UpdateVerified(conf.Product, true, version, conf.Rhost, conf.Rport) } +// Compare a version to a semantic version constraint using the [Masterminds semver constraints](https://github.com/Masterminds/semver?tab=readme-ov-file#checking-version-constraints). +// Provide a version string and a constraint and if the semver is within the constraint a boolean +// response of whether the version is constrained or not will occur. Any errors from the constraint +// or version will propagate through the framework errors and the value will be false. +func CheckSemVer(version string, constraint string) bool { + c, err := semver.NewConstraint(constraint) + if err != nil { + output.PrintfFrameworkError("Invalid constraint: %s", err.Error()) + + return false + } + v, err := semver.NewVersion(version) + if err != nil { + output.PrintfFrameworkError("Invalid version: %s", err.Error()) + + return false + } + + return c.Check(v) +} + // modify godebug to re-enable old cipher suites that were removed in 1.22. This does have implications for our // client fingerprint, and we should consider how to improve/fix that in the future. We also should be respectful // of other disabling this feature, so we will check for it before re-enabling it. diff --git a/framework_test.go b/framework_test.go new file mode 100644 index 0000000..0c79c48 --- /dev/null +++ b/framework_test.go @@ -0,0 +1,34 @@ +package exploit_test + +import ( + "testing" + + "github.com/vulncheck-oss/go-exploit" +) + +func TestCheckSemVer_Full(t *testing.T) { + if !exploit.CheckSemVer("1.0.0", "<= 1.0.0") { + t.Error("Constraint should have passed") + } + if exploit.CheckSemVer("1.0.0", "> 1.0.0") { + t.Error("Constraint should not have passed") + } +} + +func TestCheckSemVer_BadVersion(t *testing.T) { + if exploit.CheckSemVer("uwu", "<= 1.0.0") { + t.Error("Version was invalid, should not have passed") + } + if exploit.CheckSemVer("1.0.0 ", "<= 1.0.0") { + t.Error("Version was invalid, should not have passed") + } +} + +func TestCheckSemVer_BadConstraint(t *testing.T) { + if exploit.CheckSemVer("1.0.0", "<== 1.0.0") { + t.Error("Constraint was invalid, should not have passed") + } + if exploit.CheckSemVer("1.0.0", "xp") { + t.Error("Constraint was invalid, should not have passed") + } +} diff --git a/go.mod b/go.mod index 023522b..16b33de 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( ) require ( + github.com/Masterminds/semver v1.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 28329c7..c3320b9 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= diff --git a/wrappers/wrappers.go b/wrappers/wrappers.go new file mode 100644 index 0000000..6750629 --- /dev/null +++ b/wrappers/wrappers.go @@ -0,0 +1,68 @@ +package wrappers + +import( + "regexp" + "net/http" + + "github.com/vulncheck-oss/go-exploit" + "github.com/vulncheck-oss/go-exploit/config" + "github.com/vulncheck-oss/go-exploit/output" + "github.com/vulncheck-oss/go-exploit/protocol" + "github.com/vulncheck-oss/go-exploit/c2/channel" + "github.com/vulncheck-oss/go-exploit/c2/httpservefile" +) + +// A collection of wrappers/helper functions that have otherwise no place to go and may need to be in their own packages +// to avoid certain import cycle errors. + +// Helper function for use within exploits to reduce the overall amount of boilerplate when setting up a file server to host a dynamically generated file. +func HTTPServeFileInitAndRunWithFile(conf *config.Config, fileName string, routeName string, data[]byte) bool { + httpServer := httpservefile.GetInstance() + + if httpServer.HTTPAddr == "" { + httpServer.HTTPAddr = conf.Lhost + } + + if !httpServer.Init(&channel.Channel{HTTPAddr: httpServer.HTTPAddr, HTTPPort: httpServer.HTTPPort}) { + output.PrintFrameworkError("Could not start http server") + + return false + } + + httpServer.AddFile(fileName, routeName, data) + + go httpServer.Run(conf.C2Timeout) + + return true +} + +// This removes generic version checking boiler plate that works in a great deal of cases. +// Of course it will not work in every case but that is not the point. +func SimpleVersionCheck(conf *config.Config, rePattern string, versionConstraint string, endpoint string) exploit.VersionCheckType { + url := protocol.GenerateURL(conf.Rhost, conf.Rport, conf.SSL, endpoint) + resp, body, ok := protocol.HTTPGetCache(url) + if !ok || resp == nil { + return exploit.Unknown + } + + if resp.StatusCode != http.StatusOK { + output.PrintfFrameworkError("Version check failed: Unexpected response code during initial GET request: %d", resp.StatusCode) + + return exploit.Unknown + } + + matches := regexp.MustCompile(rePattern).FindStringSubmatch(body) + if len(matches) < 2 { + output.PrintFrameworkError("Version check failed: No matches found for the provided pattern") + + return exploit.Unknown + } + + exploit.StoreVersion(conf, matches[1]) + + if exploit.CheckSemVer(matches[1], versionConstraint) { + return exploit.Vulnerable + } + + return exploit.NotVulnerable +}