Sicherheits-Best Practices mit go-mssqldb

Dieser Artikel behandelt Sicherheitspraktiken für Go-Anwendungen, die sich über den go-mssqldb Treiber mit SQL Server verbinden. Es behandelt SQL-Injektionsverhinderung, Credential-Management, Verschlüsselungskonfiguration und das Prinzip des minimalen Privilegs.

Verhindern der SQL-Einfügung

SQL-Injection ist die häufigste Sicherheitslücke in Datenbanken. Der Treiber go-mssqldb stellt parametrisierte Abfragen bereit, die SQL-Code von Datenwerten trennen. Verwenden Sie immer Parameter für benutzerdefinierte Eingaben.

Verwenden parametrisierter Abfragen

Parameter werden getrennt vom SQL-Text gesendet, sodass Benutzereingaben niemals als SQL-Code interpretiert werden können:

// CORRECT: Parameters are sent separately from the SQL text.
rows, err := db.QueryContext(ctx,
    "SELECT * FROM Sales.vSalesPerson WHERE FirstName = @name AND CountryRegionName = @loc",
    sql.Named("name", userName),
    sql.Named("loc", userLocation))

Niemals Benutzereingaben in SQL verknüpfen

String-Verkettung ermöglicht es Angreifern, beliebige SQL-Elemente einzuschleusen:

// WRONG: SQL injection vulnerability.
query := fmt.Sprintf("SELECT * FROM Sales.vSalesPerson WHERE FirstName = '%s'", userName)
rows, err := db.QueryContext(ctx, query) // If userName is "'; DROP TABLE HumanResources.Department;--" ...

Dynamische Tabellen- oder Spaltennamen

Du kannst keine parametrisierten Abfragen für Tabellennamen, Spaltennamen oder andere Identifikatoren verwenden. Wenn Ihre Anwendung dynamische Identifikatoren benötigt, überprüfen Sie diese mit einer Erlaubnisliste:

// Validate against known-safe values.
var validColumns = map[string]bool{
    "FirstName": true, "CountryRegionName": true, "TerritoryName": true,
}

func queryByColumn(ctx context.Context, db *sql.DB, column, value string) (*sql.Rows, error) {
    if !validColumns[column] {
        return nil, fmt.Errorf("invalid column name: %q", column)
    }
    // Safe to use column directly because it was validated against the allowlist.
    query := fmt.Sprintf("SELECT * FROM Sales.vSalesPerson WHERE [%s] = @p1", column)
    return db.QueryContext(ctx, query, sql.Named("p1", value))
}

Achtung

Platziere niemals vom Benutzer bereitgestellte Eingabe direkt in SQL-Identifikatoren, selbst wenn Klammern entweichen. Validiere immer gegen eine Erlaubnisliste der erlaubten Werte.

Gespeicherte Prozeduren und SQL-Injektion

Gespeicherte Prozeduren verhindern SQL-Injection nicht automatisch. Wenn das Verfahren intern zusammengekettete Strings verwendet EXECUTE oder sp_executesql , kann es dennoch verwundbar sein. Verwenden Sie parametrisierte Aufrufe:

// Parameters are passed safely to the procedure.
_, err := db.ExecContext(ctx, "dbo.SearchEmployees",
    sql.Named("searchTerm", userInput))

Verwaltung von Verbindungszeichenfolge-Geheimnissen

Verbindungszeichenketten können Passwörter, Client-Geheimnisse und Zertifikatspfade enthalten. Speichere sie niemals im Quellcode.

Verwenden von Umgebungsvariablen

Lesen Sie die vollständige Verbindungszeichenfolge aus einer Umgebungsvariable:

connString := os.Getenv("MSSQL_CONNECTION_STRING")
if connString == "" {
    log.Fatal("MSSQL_CONNECTION_STRING environment variable is required")
}
db, err := sql.Open("sqlserver", connString)

Baue Verbindungsstrings aus einzelnen Geheimnissen

Assembleren Sie den Verbindungszeichenfolge aus separaten Umweltvariablen für jede Komponente:

import (
    "net/url"
    "os"
)

query := url.Values{}
query.Add("database", os.Getenv("DB_NAME"))
query.Add("encrypt", "true")

password := os.Getenv("DB_PASSWORD")

u := &url.URL{
    Scheme:   "sqlserver",
    User:     url.UserPassword(os.Getenv("DB_USER"), password),
    Host:     os.Getenv("DB_HOST"),
    RawQuery: query.Encode(),
}
db, err := sql.Open("sqlserver", u.String())

Passwörter komplett eliminieren

Die sicherste Verbindungszeichenfolge hat überhaupt kein Passwort. Verwenden Sie Microsoft Entra ID-Authentifizierung mit verwalteter Identität:

import _ "github.com/microsoft/go-mssqldb/azuread"

// No password, no secret. The managed identity handles authentication.
db, err := sql.Open("azuresql",
    "sqlserver://<server>.database.windows.net?database=AdventureWorks2025&fedauth=ActiveDirectoryDefault&encrypt=true&TrustServerCertificate=false")

Geheime Speicheroptionen

Umgebung Empfohlener Speicher Hinweise
Lokale Entwicklung Umgebungsvariablen oder die User-Secrets-Datei Checken Sie .env-Dateien nicht in die Quellcodeverwaltung ein.
Azure App Service / Container Apps App-Einstellungen oder Key Vault-Referenzen Key Vault-Referenzen stellen Geheimwerte zur Laufzeit als Umgebungsvariablen bereit.
Kubernetes Kubernetes Secrets oder Azure Key Vault mit CSI-Treiber Geheimnisse werden als Dateien oder Umgebungsvariablen eingebunden.
CI/CD-Pipelines Pipeline-Geheimnisse / GitHub Actions-Geheimnisse Verwenden Sie ActiveDirectoryServicePrincipal oder ActiveDirectoryAzurePipelines für Azure SQL.

Von Bedeutung

Füge .env, *.pfx und *.pem zu deiner .gitignore Datei hinzu. Das versehentliche Übertragen von Geheimnissen in ein Repository ist eine der häufigsten Quellen für Zugangsdatenlecks.

Konfigurieren der Verschlüsselung

Verwenden Sie Verschlüsselung für alle entfernten Verbindungen

Legen Sie encrypt=true für alle Remoteverbindungen fest. Verschlüsselung, die nur serverseitig erforderlich ist, ist anfällig für Man-in-the-Middle-Angriffe. Der Client muss die Verschlüsselung durchsetzen. Azure SQL ermöglicht standardmäßig die Verschlüsselung:

sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true

Verwenden Sie TDS 8.0 für die stärkste Verschlüsselung

TDS 8.0 (Strict-Mode) führt den TLS-Handshake aus, bevor TDS-Protokolldaten ausgetauscht werden, und verhindert so Protokoll-Downgrade-Angriffe:

sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=strict

TDS 8.0 benötigt SQL Server 2022 oder Azure SQL.

Verwenden Sie TrustServerCertificate nicht in der Produktion

Die Einstellung TrustServerCertificate=true deaktiviert die Zertifikatsvalidierung, die Angriffe auf Gegner-in-der-Mitte ermöglicht:

// DEVELOPMENT ONLY. Never deploy to production.
connString := "sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&TrustServerCertificate=true"

Für die Produktion konfigurieren Sie die ordnungsgemäße Zertifikatsvalidierung. Siehe Verschlüsselung und Zertifikate.

Setze eine minimale TLS-Version

Setze TLS 1.2 oder später durch, um Protokoll-Downgrade-Angriffe zu verhindern.

sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true&tlsmin=1.2

Anwenden des Prinzips der geringsten Rechte

Verwenden Sie datenbankbezogene Benutzer

Erstellen Sie eigenständige Datenbankbenutzer, die auf eine einzelne Datenbank begrenzt sind, anstatt serverweiter Anmeldungen, die auf mehrere Datenbanken zugreifen.

-- Create a contained database user (no server-level login needed).
CREATE USER [app_user] WITH PASSWORD = '<password>';

-- Grant only the permissions the application needs.
ALTER ROLE db_datareader ADD MEMBER [app_user];
ALTER ROLE db_datawriter ADD MEMBER [app_user];
GRANT EXECUTE ON SCHEMA::dbo TO [app_user];

Getrennte Lese- und Schreibverbindungen

Wenn deine App unterschiedliche Lese- und Schreibpfade hat, erstelle separate Datenbankbenutzer mit entsprechenden Berechtigungen.

// Read-only queries use a restricted user.
readDB, _ := sql.Open("sqlserver",
    "sqlserver://app_reader:password@<server>?database=AdventureWorks2025&ApplicationIntent=ReadOnly&encrypt=true")

// Write operations use a user with write permissions.
writeDB, _ := sql.Open("sqlserver",
    "sqlserver://app_writer:password@<server>?database=AdventureWorks2025&encrypt=true")

Vermeiden Sie die Verwendung von SA oder DBO in Anwendungen

Die sa Anmeldung und der dbo Benutzer haben uneingeschränkten Zugriff. Wenn eine angeschlossene Anwendung eine sa SQL-Injektionslücke hat, erhält der Angreifer die volle Kontrolle über den Datenbankserver.

Always Encrypted-Schlüssel schützen

Wenn Sie Always Encrypted verwenden, schützt der Spalten-Hauptschlüssel (CMK) die Spaltenverschlüsselungsschlüssel (CEK). Sichern Sie das CMK entsprechend:

Wichtiger Anbieter Beste Praxis
Lokales Zertifikat (PFX) Speichere die PFX-Datei außerhalb des Anwendungsverzeichnisses. Setze einschränkende Dateiberechtigungen. Verwenden Sie eine Umgebungsvariable für das Passwort.
Windows-Zertifikatspeicher Für Servicekonten verwenden CurrentUser\My . Setze Zertifikatsberechtigungen mit certutil.
Azure Key Vault (ein Dienst zur sicheren Verwaltung kryptografischer Schlüssel) Nutze Managed Identity für den Zugriff. Legen Sie Key Vault-Zugriffsrichtlinien gemäß dem Prinzip der geringsten Rechte fest. Aktivieren Sie das vorläufige Löschen und den Schutz vor endgültigem Löschen.

Für die Konfiguration des Schlüsselanbieters siehe Always Encrypted.

Loggen Sie sicher

Protokolliere keine Verbindungszeichenketten oder Zugangsdaten

Verbindungszeichenketten können Passwörter enthalten, die in Logdateien landen:

// WRONG: Logs the password.
log.Printf("Connecting to %s", connString)

// CORRECT: Log only the server and database.
log.Printf("Connecting to server=%s database=%s", serverName, dbName)

Wenn Sie Verbindungsdetails für Diagnosezwecke protokollieren müssen, machen Sie zuerst die Verbindungszeichenfolge unkenntlich:

func redactSQLServerURL(raw string) string {
    u, err := url.Parse(raw)
    if err != nil {
        return "<invalid connection string>"
    }

    if u.User != nil {
        username := u.User.Username()
        if username != "" {
            u.User = url.UserPassword(username, "REDACTED")
        }
    }

    q := u.Query()
    for _, key := range []string{"password", "clientassertion", "systemtoken"} {
        if q.Has(key) {
            q.Set(key, "REDACTED")
        }
    }
    u.RawQuery = q.Encode()
    return u.String()
}

Protokolliere den geschwärzten Wert nur, wenn du ihn für kurzlebige Diagnosen brauchst. Man sollte den Servernamen, den Datenbanknamen und den Authentifizierungsmodus separat protokollieren.

Keine sensiblen Abfrageparameter protokollieren

Vermeiden Sie das Protokollieren von Parameterwerten, die persönliche oder sensible Daten enthalten:

// WRONG: Logs sensitive parameter values.
log.Printf("Query: SELECT * FROM HumanResources.Employee WHERE NationalIDNumber = %s", nationalIDNumber)

// CORRECT: Log the query structure without parameter values.
log.Printf("Querying HumanResources.Employee by NationalIDNumber")

Verwenden Sie Log-Flags in der Produktion sorgfältig

Der log Treiberparameter kann SQL-Anweisungen und Parameterwerte ausgeben:

Flag Risiko Produktion sicher?
1 (Fehler) Low Ja
2 (Nachrichten) Low Ja
4 (Zeilen) Medium, legt Daten frei No
8 (SQL) Medium, stellt Anfragen frei Bedingt
16 (params) Hoch, stellt Parameterwerte frei No
32 (Transaktionen) Low Ja
64 (Debuggen) Hoch, legt Protokolldetails frei No

In der Produktion verwenden Sie log=1 (nur Fehler) oder log=3 (Fehler + Meldungen).

Sicherheitscheckliste

Area Recommendation
Einschleusung von SQL-Befehlen Verwenden Sie parametrisierte Abfragen für alle Benutzereingaben. Überprüfen Sie dynamische Kennungen anhand einer Zulassungsliste.
Credentials Nutze Microsoft Entra Managed Identity, wenn möglich. Programmiere niemals die Passwörter fest im Quellcode.
Encryption Legen Sie für alle Produktionsverbindungen encrypt=true oder encrypt=strict fest. Legen Sie tlsmin=1.2 fest.
Zertifikate Verwenden Sie nicht TrustServerCertificate=true in der Produktion. Serverzertifikate validieren.
Prinzip der geringsten Rechte Erstellen Sie anwendungsspezifische Datenbankbenutzer mit nur den erforderlichen Berechtigungen.
Geheimnisse Speichere Verbindungszeichenfolgen in Umgebungsvariablen, in Key Vault oder in den Secret Stores der Plattform.
Protokollierung Protokolliere keine Verbindungszeichen, Passwörter oder sensible Parameterwerte.
Quellcodeverwaltung Füge .env, *.pfx und *.pem zu .gitignore hinzu.