diff --git a/db/create.go b/db/create.go index aac8595..abd95d3 100644 --- a/db/create.go +++ b/db/create.go @@ -35,7 +35,7 @@ var GlobalSQLHandle *sql.DB var GlobalHTTPRespCacheLimit int const ( - schemaVersion = "1.0.0" + schemaVersion = "1.1.0" metadataTable = `CREATE TABLE IF NOT EXISTS metadata (created INTEGER NOT NULL,schema_version TEXT NOT NULL);` initMetadataTable = `INSERT INTO metadata (created, schema_version) VALUES (?, ?)` @@ -58,6 +58,17 @@ const ( `rport INTEGER NOT NULL CHECK (rport >= 0 AND rport <= 65535),` + `uri TEXT NOT NULL,` + `data BLOB NOT NULL);` + + artifactsTable = `CREATE TABLE IF NOT EXIST artifacts(` + + `id INTEGER PRIMARY KEY AUTOINCREMENT,` + + `created INTEGER NOT NULL,` + + `rhost TEXT NOT NULL,` + + `rport INTEGER NOT NULL CHECK (rport >= 0 AND rport <= 65535),` + + `lhost TEXT NOT NULL,` + + `lport INTEGER NOT NULL CHECK (rport >= 0 AND rport <= 65535),` + + `path TEXT NOT NULL,` + + `meta TEXT NOT NULL,` + + `data BLOB NOT NULL);` ) func InitializeDB(name string) bool { @@ -80,7 +91,8 @@ func InitializeDB(name string) bool { } if !createVerifiedSoftwareTable(handle) || - !createHTTPCacheTable(handle) { + !createHTTPCacheTable(handle) || + !createArtifactsTable(handle) { return false } @@ -154,3 +166,14 @@ func createHTTPCacheTable(handle *sql.DB) bool { return true } + +func createArtifactsTable(handle *sql.DB) bool { + _, err := handle.Exec(artifactsTable) + if err != nil { + output.PrintFrameworkError(err.Error()) + + return false + } + + return true +} diff --git a/db/update.go b/db/update.go index a4c3f46..c638770 100644 --- a/db/update.go +++ b/db/update.go @@ -25,6 +25,9 @@ const ( `rport = excluded.rport,` + `uri = excluded.uri,` + `data = excluded.data;` + + artifactInsert = `INSERT INTO artifacts (created, rhost, rport, lhost, lport, path, meta, data)` + + `VALUES(?, ?, ?, ?, ?, ?, ?, ?);` ) func UpdateVerified(software string, installed bool, version string, rhost string, rport int) bool { @@ -60,3 +63,18 @@ func CacheHTTPResponse(rhost string, rport int, path string, httpResp []byte) { return } } + +// Store artifacts in the database with additional metadata. +func StoreArtifact(rhost string, rport int, lhost string, lport int, path string, meta string, data []byte) bool { + if GlobalSQLHandle == nil { + return true + } + _, err := GlobalSQLHandle.Exec(artifactInsert, time.Now().Unix(), rhost, rport, lhost, lport, path, meta, data) + if err != nil { + output.PrintFrameworkError(err.Error()) + + return false + } + + return true +} diff --git a/framework.go b/framework.go index 2f35fef..089ed09 100644 --- a/framework.go +++ b/framework.go @@ -378,6 +378,49 @@ func StoreVersion(conf *config.Config, version string) { db.UpdateVerified(conf.Product, true, version, conf.Rhost, conf.Rport) } +// Store artifact data. If the string flag `output` is set in your exploit, StoreArtifact will write +// the file to the defined file, which if is the `-` character it will print directly to the OS +// stdout. If the database is enabled, the artifact will be stored in the database artifacts table +// with surrounding metadata. If both are set, then both will be written to. +// +// If the "output" is not a defined flag and no database is set, an error will be emitted and false +// will be returned. +func StoreArtifact(conf *config.Config, path, meta string, data []byte) bool { + if conf.GetStringFlag("output") == "" && db.GlobalSQLHandle == nil { + output.PrintFrameworkError("Database not set and `-output` not defined, no place to output artifact") + + return false + } + if conf.GetStringFlag("output") != "" { + if len(meta) == 0 { + output.PrintFrameworkStatus(fmt.Sprintf("Storing `%s` (%d bytes) from %s:%d", path, len(data), conf.Rhost, conf.Rport)) + } else { + output.PrintFrameworkStatus(fmt.Sprintf("Storing `%s` (%d bytes) from %s:%d - metadata: %s", path, len(data), conf.Rhost, conf.Rport, meta)) + } + outfile := conf.GetStringFlag("output") + if conf.GetStringFlag("output") == "-" { + _, err := (os.Stdout).Write(data) + if err != nil { + output.PrintfFrameworkError("Could not write to output file: %s", err.Error()) + + return false + } + } else { + //nolint:godox + // TODO apply #306 output file name templates + err := os.WriteFile(outfile, data, 0o600) + if err != nil { + output.PrintfFrameworkError("Could not write to output file: %s", err.Error()) + + return false + } + output.PrintfFrameworkSuccess("File written to: %s", outfile) + } + } + + return db.StoreArtifact(conf.Rhost, conf.Rport, conf.Lhost, conf.Lport, path, meta, data) +} + // 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.