Security context
Medium· 6.7GHSA-v3hp-mcj5-pg39 CVE-2023-0620CWE-89Published Mar 30, 2023

HashiCorp Vault’s Microsoft SQL Database Storage Backend Vulnerable to SQL Injection Via Configuration File

Research this vulnerability

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 and Vault Enterprise versions 0.8.0 until 1.13.1 are vulnerable to an SQL injection attack when using the Microsoft SQL (MSSQL) Database Storage Backend. When configuring the MSSQL plugin, certain parameters are required to establish a connection (schema, database, and table) are not sanitized when passed to the user-provided MSSQL database. A privileged attacker with the ability to write arbitrary data to Vault's configuration may modify these parameters to execute a malicious SQL command when the Vault configuration is applied. This issue is fixed in versions 1.13.1, 1.12.5, and 1.11.9.

The fix

vault-12244

hamid ghaf· Mar 16, 2023, 07:56 PM+70746ac425745
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 interfaces
var _ 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.Backen
database = "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.Backen
schema = "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.Backen
db.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