Best practices voor beveiliging met go-mssqldb

Dit artikel behandelt beveiligingspraktijken voor Go-applicaties die verbinding maken met SQL Server via de go-mssqldb driver. Het behandelt SQL-injectiepreventie, credentialbeheer, encryptieconfiguratie en het principe van het minste privilege.

SQL-injectie voorkomen

SQL-injectie is de meest voorkomende beveiligingskwetsbaarheid in databases. De go-mssqldb driver biedt geparametriseerde queries die SQL-code scheiden van datawaarden. Gebruik altijd parameters voor door de gebruiker ingevoerde invoer.

Geparameteriseerde query's gebruiken

Parameters worden apart van de SQL-tekst verzonden, zodat gebruikersinvoer nooit als SQL-code kan worden geïnterpreteerd:

// 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))

Verbind gebruikersinvoer nooit met SQL

Stringconcatenatie stelt aanvallers in staat om willekeurige SQL te injecteren:

// 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 tabel- of kolomnamen

Je kunt geen geparametriseerde queries gebruiken voor tabelnamen, kolomnamen of andere identificaties. Als je applicatie dynamische identificaties vereist, valideer deze dan met een toestemmingslijst:

// 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))
}

Let op

Plaats nooit door de gebruiker ingevoerde invoer direct in SQL-identificaties, zelfs niet met haakjesontsnapping. Valideer altijd tegen een toestaanlijst van toegestane waarden.

Opgeslagen procedures en SQL-injectie

Stored procedures voorkomen SQL-injectie niet automatisch. Als de procedure intern gebruikmaakt van EXECUTE of van sp_executesql met samengevoegde tekenreeksen, kan deze nog steeds kwetsbaar zijn. Gebruik geparametriseerde aanroepen:

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

Geheimen van de verbindingsreeks beheren

Verbindingsstrings kunnen wachtwoorden, clientgeheimen en certificaatpaden bevatten. Sla ze nooit op in de broncode.

Omgevingsvariabelen gebruiken

Lees de volledige verbindingsreeks van een omgevingsvariabele:

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

Stel verbindingsreeksen samen uit afzonderlijke geheimen

Stel de verbindingsreeks samen uit aparte omgevingsvariabelen voor elke component:

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())

Verwijder wachtwoorden volledig

De meest veilige verbindingsreeks heeft helemaal geen wachtwoord. Gebruik Microsoft Entra ID-authenticatie met beheerde identiteit:

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 opslagopties

Milieu Aanbevolen opslag Aantekeningen
Lokale ontwikkeling Omgevingsvariabelen of gebruikersgeheimenbestand Check .env-bestanden niet in bij broncodebeheer.
Azure App Service / Container Apps App-instellingen of Key Vault-referenties Key Vault-referenties injecteren geheimen als omgevingsvariabelen tijdens runtime.
Kubernetes Kubernetes Secrets eller Azure Key Vault with CSI driver Geheimen worden gemonteerd als bestanden of omgevingsvariabelen.
CI/CD-pijplijnen Pijplijngeheimen / GitHub Actions-geheimen Gebruik ActiveDirectoryServicePrincipal of ActiveDirectoryAzurePipelines voor Azure SQL.

Important

Voeg .env, *.pfx en *.pem toe aan je .gitignore-bestand. Het per ongeluk vastleggen van geheimen in een repository is een van de meest voorkomende bronnen van lekken van inloggegevens.

Versleuteling configureren

Gebruik encryptie voor alle externe verbindingen

Stel encrypt=true in voor alle externe connecties. Versleuteling die alleen door de server nodig is, is kwetsbaar voor aanvallen van het tegenstander in het midden. De client moet encryptie afdwingen. Azure SQL maakt standaard encryptie mogelijk:

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

Gebruik TDS 8.0 voor de sterkste encryptie

TDS 8.0 (strict mode) voert de TLS-handshake uit voordat TDS-protocolgegevens worden uitgewisseld, waardoor protocoldowngrade-aanvallen worden voorkomen:

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

TDS 8.0 vereist SQL Server 2022 of Azure SQL.

Gebruik TrustServerCertificate niet in productie

Door TrustServerCertificate=true in te stellen, wordt de certificaatvalidatie uitgeschakeld, waardoor adversary-in-the-middle-aanvallen mogelijk worden:

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

Voor productie configureer je de juiste certificaatvalidatie. Zie Versleuteling en certificaten.

Stel een minimale TLS-versie in

Handhaaf TLS 1.2 of later om protocoldowngrade-aanvallen te voorkomen.

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

Het principe van minimale bevoegdheden toepassen

Gebruik gebruikers met databaserestrictie

Creëer afgesloten databasegebruikers die beperkt zijn tot één enkele database in plaats van serverniveau-logins die toegang krijgen tot meerdere databases.

-- 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];

Afzonderlijke lees- en schrijfverbindingen

Als je app aparte lees- en schrijfpaden heeft, maak dan aparte databasegebruikers met de juiste rechten.

// 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")

Vermijd het gebruik van SA of DBO in applicaties

De sa login en dbo gebruiker hebben onbeperkte toegang. Als een applicatie die als sa is verbonden een SQL-injectiekwetsbaarheid heeft, krijgt de aanvaller volledige controle over de databaseserver.

Beveilig Always Encrypted-sleutels

Wanneer je Always Encrypted gebruikt, beschermt de kolomhoofdsleutel (CMK) de kolomencryptiesleutels (CEK). Beveilig de CMK op de juiste manier:

Sleutelleverancier Aanbevolen procedure
Lokaal certificaat (PFX) Sla het PFX-bestand buiten de applicatiemap op. Stel beperkende bestandsrechten in. Gebruik een omgevingsvariabele voor het wachtwoord.
Windows-certificaatopslag Gebruik CurrentUser\My voor serviceaccounts. Stel certificaatmachtigingen in door te gebruiken certutil.
Azure Key Vault Gebruik managed identity voor toegang. Stel toegangsbeleid voor sleutelkluis in met het minste privilege. Schakel zacht verwijderen en beveiliging tegen opschonen in.

Voor sleutelproviderconfiguratie, zie Altijd Versleuteld.

Log veilig

Log geen verbindingsstrings of inloggegevens

Verbindingsstrings kunnen wachtwoorden bevatten die in logbestanden terechtkomen:

// 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)

Als je verbindingsdetails moet opnemen voor diagnostiek, redacteer dan eerst de verbindingsreeks:

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()
}

Log de gecensureerde waarde alleen als je hem nodig hebt voor kortstondige diagnostiek. Geef de voorkeur aan het afzonderlijk loggen van de servernaam, databasenaam en authenticatiemodus.

Log geen gevoelige queryparameters

Vermijd het loggen van parameterwaarden die persoonlijke of gevoelige gegevens bevatten:

// 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")

Gebruik logvlaggen zorgvuldig in productie

De parameter van log de driver kan SQL-statements en parameterwaarden uitvoeren:

Flag Risico Productieveilig?
1 (fouten) Low Ja
2 (berichten) Low Ja
4 (rijen) Medium, stelt gegevens bloot No
8 (SQL) Medium, stelt queries bloot Conditional
16 (params) Hoog, stelt parameterwaarden bloot No
32 (transacties) Low Ja
64 (debug) Hoog, maakt details van het protocol zichtbaar No

Voor productie gebruik log=1 (alleen fouten) of log=3 (fouten + berichten).

Controlelijst voor beveiliging

Area Recommendation
SQL-injectie Gebruik geparametriseerde queries voor alle gebruikersinvoer. Valideer dynamische identificaties aan de hand van een toestemmingslijst.
Credentials Gebruik Microsoft Entra managed identity waar mogelijk. Neem wachtwoorden nooit hardcoded op in de broncode.
Encryption Stel voor alle productieverbindingen encrypt=true of encrypt=strict in. Stel tlsmin=1.2in.
Certificates Gebruik niet TrustServerCertificate=true in productie. Valideer servercertificaten.
Minimale bevoegdheid Maak applicatie-specifieke databasegebruikers aan met alleen de vereiste rechten.
Secrets Sla verbindingsstrings op in omgevingsvariabelen, Key Vault of platformgeheime opslagen.
Logging Log geen verbindingsstrings, wachtwoorden of gevoelige parameterwaarden.
Bronbeheer Voeg .env, *.pfx en *.pem toe aan .gitignore.