HashiCorp Vault’s Microsoft SQL Database Storage Backend Vulnerable to SQL Injection Via Configuration File in github.com/hashicorp/vault
Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.
Affected versions
0.8.0 → fixed in 1.11.91.12.0 → fixed in 1.12.51.13.0 → fixed in 1.13.1
Details
HashiCorp Vault’s Microsoft SQL Database Storage Backend Vulnerable to SQL Injection Via Configuration File in github.com/hashicorp/vault
The fix
vault-12244
physical/mssql/mssql.go+25 −5
@@ -7,6 +7,7 @@ import ("context""database/sql""fmt"+"regexp""sort""strconv""strings"@@ -21,6 +22,7 @@ import (// Verify MSSQLBackend satisfies the correct interfacesvar _ physical.Backend = (*MSSQLBackend)(nil)+var identifierRegex = regexp.MustCompile(`^[\p{L}_][\p{L}\p{Nd}@#$_]*$`)type MSSQLBackend struct {dbTable string@@ -30,6 +32,13 @@ type MSSQLBackend struct {permitPool *physical.PermitPool}+func isInvalidIdentifier(name string) bool {+if !identifierRegex.MatchString(name) {+return true+}+return false+}+func NewMSSQLBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {username, ok := conf["username"]if !ok {@@ -71,11 +80,19 @@ func NewMSSQLBackend(conf map[string]string, logger log.Logger) (physical.Backendatabase = "Vault"}+if isInvalidIdentifier(database) {+return nil, fmt.Errorf("invalid database name")+}+table, ok := conf["table"]if !ok {table = "Vault"}+if isInvalidIdentifier(table) {+return nil, fmt.Errorf("invalid table name")+}+appname, ok := conf["appname"]if !ok {appname = "Vault"@@ -96,6 +113,10 @@ func NewMSSQLBackend(conf map[string]string, logger log.Logger) (physical.Backenschema = "dbo"}+if isInvalidIdentifier(schema) {+return nil, fmt.Errorf("invalid schema name")+}+connectionString := fmt.Sprintf("server=%s;app name=%s;connection timeout=%s;log=%s", server, appname, connectionTimeout, logLevel)if username != "" {connectionString += ";user id=" + username@@ -116,18 +137,17 @@ func NewMSSQLBackend(conf map[string]string, logger log.Logger) (physical.Backendb.SetMaxOpenConns(maxParInt)-if _, err := db.Exec("IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = '" + database + "') CREATE DATABASE " + database); err != nil {+if _, err := db.Exec("IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = ?) CREATE DATABASE "+database, database); err != nil {return nil, fmt.Errorf("failed to create mssql database: %w", err)}dbTable := database + "." + schema + "." + table-createQuery := "IF NOT EXISTS(SELECT 1 FROM " + database + ".INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND TABLE_NAME='" + table + "' AND TABLE_SCHEMA='" + schema +-"') CREATE TABLE " + dbTable + " (Path VARCHAR(512) PRIMARY KEY, Value VARBINARY(MAX))"+createQuery := "IF NOT EXISTS(SELECT 1 FROM " + database + ".INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE' AND TABLE_NAME=? AND TABLE_SCHEMA=?) CREATE TABLE " + dbTable + " (Path VARCHAR(512) PRIMARY KEY, Value VARBINARY(MAX))"if schema != "dbo" {var num int-err = db.QueryRow("SELECT 1 FROM " + database + ".sys.schemas WHERE name = '" + schema + "'").Scan(&num)+err = db.QueryRow("SELECT 1 FROM "+database+".sys.schemas WHERE name = ?", schema).Scan(&num)switch {case err == sql.ErrNoRows:@@ -140,7 +160,7 @@ func NewMSSQLBackend(conf map[string]string, logger log.Logger) (physical.Backen}}-if _, err := db.Exec(createQuery); err != nil {+if _, err := db.Exec(createQuery, table, schema); err != nil {return nil, fmt.Errorf("failed to create mssql table: %w", err)}
physical/mssql/mssql_test.go+42 −2
@@ -7,13 +7,53 @@ import ("os""testing"+_ "github.com/denisenkom/go-mssqldb"log "github.com/hashicorp/go-hclog""github.com/hashicorp/vault/sdk/helper/logging""github.com/hashicorp/vault/sdk/physical"--_ "github.com/denisenkom/go-mssqldb")+// TestInvalidIdentifier checks validity of an identifier+func TestInvalidIdentifier(t *testing.T) {+testcases := map[string]bool{+"name": true,+"_name": true,+"Name": true,+"#name": false,+"?Name": false,+"9name": false,+"@name": false,+"$name": false,+" name": false,+"n ame": false,+"n4444444": true,+"_4321098765": true,+"_##$$@@__": true,+"_123name#@": true,+"name!": false,+"name%": false,+"name^": false,+"name&": false,+"name*": false,+"name(": false,+"name)": false,+"nåame": true,+"åname": true,+"name'": false,+"nam`e": false,+"пример": true,+"_#Āā@#$_ĂĄąćĈĉĊċ": true,+"ÛÜÝÞßàáâ": true,+"豈更滑a23$#@": true,+}++for i, expected := range testcases {+if !isInvalidIdentifier(i) != expected {+t.Fatalf("unexpected identifier %s: expected validity %v", i, expected)+}+}+}+func TestMSSQLBackend(t *testing.T) {server := os.Getenv("MSSQL_SERVER")if server == "" {changelog/19591.txt | 3 +++1 file changed, 3 insertions(+)create mode 100644 changelog/19591.txt
changelog/19591.txt+3 −0
@@ -0,0 +1,3 @@+```release-note:improvement+core: validate name identifiers in mssql physical storage backend prior use+```
References
- ADVISORYhttps://github.com/advisories/GHSA-v3hp-mcj5-pg39
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2023-0620
- FIXhttps://github.com/hashicorp/vault/pull/19591
- WEBhttps://discuss.hashicorp.com/t/hcsec-2023-12-vault-s-microsoft-sql-database-storage-backend-vulnerable-to-sql-injection-via-configuration-file/52080/1
- WEBhttps://github.com/hashicorp/vault/releases/tag/v1.11.9
- WEBhttps://github.com/hashicorp/vault/releases/tag/v1.12.5
- WEBhttps://github.com/hashicorp/vault/releases/tag/v1.13.1
- WEBhttps://security.netapp.com/advisory/ntap-20230526-0008