Säkerhetsbästa praxis med go-mssqldb

Den här artikeln behandlar säkerhetsrutiner för Go-applikationer som ansluter till SQL Server via drivrutinengo-mssqldb. Den behandlar SQL-injektionsprevention, legitimationshantering, krypteringskonfiguration och principen om minsta privilegium.

Förhindra SQL-inmatning

SQL-injektion är den vanligaste säkerhetssårbarheten i databaser. Drivrutinen go-mssqldb tillhandahåller parameteriserade frågor som separerar SQL-kod från datavärden. Använd alltid parametrar för användargenererad inmatning.

Använda parametriserade frågor

Parametrar skickas separat från SQL-texten, så användarinmatning kan aldrig tolkas som SQL-kod:

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

Koppla aldrig ihop användarens inmatning i SQL

Strängkonkatenering tillåter angripare att injicera godtycklig SQL:

// 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;--" ...

Dynamiska tabell- eller kolumnnamn

Du kan inte använda parametriserade frågor för tabellnamn, kolumnnamn eller andra identifierare. Om din applikation kräver dynamiska identifierare, validera dem mot en tillåtslista:

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

Försiktighet

Placera aldrig inmatning från användaren direkt i SQL-identifierare, inte ens om den omges med hakparenteser. Validera alltid mot en tillåten lista över tillåtna värden.

Lagrade procedurer och SQL-injektion

Lagrade procedurer förhindrar inte automatiskt SQL-injektion. Om proceduren använder EXECUTE eller sp_executesql med sammanfogade strängar internt kan den fortfarande vara sårbar. Använd parameteriserade samtal:

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

Hantera hemligheter för anslutningssträngar

Anslutningssträngar kan innehålla lösenord, klienthemligheter och certifikatvägar. Spara dem aldrig i källkoden.

Använda miljövariabler

Läs hela reťazec pripojenia från en miljövariabel:

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

Skapa anslutningssträngar från enskilda hemligheter

Sätt ihop reťazec pripojenia från separata miljövariabler för varje komponent:

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

Eliminera lösenord helt

Den säkraste anslutningssträngen har inget lösenord alls. Använd Microsoft Entra ID-autentisering med hanterad identitet:

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

Hemliga lagringsalternativ

Miljö Rekommenderad lagring Notes
Lokal utveckling Miljövariabler eller användarhemlighetsfil Kontrollera .env inte filer i versionshanteringen.
Azure App Service / Container Apps Appinställningar eller referenser till Key Vault Key Vault-referenser infogar hemligheter som miljövariabler vid körning.
Kubernetes Kubernetes Secrets eller Azure Key Vault med CSI-drivrutin Hemligheter monteras som filer eller miljövariabler.
CI/CD-pipelines Pipeline-hemligheter / GitHub Actions-hemligheter Använd ActiveDirectoryServicePrincipal eller ActiveDirectoryAzurePipelines för Azure SQL.

Viktigt!

Lägg till .env, *.pfx, och *.pem i din .gitignore fil. Att av misstag sätta in hemligheter i ett arkiv är en av de vanligaste källorna till läckor av inloggningsuppgifter.

Konfigurera kryptering

Använd kryptering för alla fjärranslutningar

Ställ encrypt=true in för alla fjärranslutningar. Kryptering som endast krävs av servern är sårbar för attacker från angriparen i mitten. Klienten måste upprätthålla kryptering. Azure SQL möjliggör kryptering som standard:

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

Använd TDS 8.0 för den starkaste krypteringen

TDS 8.0 (strikt läge) utför TLS-handskakning innan några TDS-protokolldata utbyts, vilket förhindrar protokollnedgraderingsattacker:

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

TDS 8.0 kräver SQL Server 2022 eller Azure SQL.

Använd inte TrustServerCertificate i produktion

Inställning TrustServerCertificate=true inaktiverar certifikatvalidering, vilket möjliggör attacker mot angriparen i mitten:

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

För produktion, konfigurera korrekt certifikatvalidering. Se Kryptering och certifikat.

Sätt en minsta TLS-version

Upprätthåll TLS 1.2 eller senare för att förhindra protokollnedgraderingsattacker.

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

Tillämpa principen om lägsta behörighet

Använd databasbegränsade användare

Skapa användare av en begränsad databas som är begränsade till en enda databas istället för servernivåinloggningar som får tillgång till flera databaser.

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

Separata läs- och skrivanslutningar

Om din app har tydliga läs- och skrivvägar, skapa separata databasanvändare med lämpliga behörigheter.

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

Undvik att använda SA eller DBO i applikationer

Inloggningen sa och användaren dbo har obegränsad åtkomst. Om en applikation som är ansluten som sa är sårbar för SQL-injektion får angriparen full kontroll över databasservern.

Skydda alltid krypterade nycklar

När du använder Always Encrypted skyddar kolumnens huvudnyckel (CMK) kolumnkrypteringsnycklarna (CEK). Skydda CMK på lämpligt sätt:

Nyckelleverantör Bästa praxis
Lokalt certifikat (PFX) Lagra PFX-filen utanför applikationskatalogen. Sätt restriktiva filbehörigheter. Använd en miljövariabel för lösenordet.
Windows certifikatlager Använd CurrentUser\My för tjänstekonton. Sätt certifikatbehörigheter genom att använda certutil.
Azure 密钥保管库 Använd managed identity för åtkomst. Sätt nyckelvalvsåtkomstpolicyer med minsta privilegium. Aktivera mjuk borttagning eller rensningsskydd.

För nyckelleverantörskonfiguration, se Alltid krypterat.

Logga säkert

Logga inte anslutningssträngar eller inloggningsuppgifter

Anslutningssträngar kan innehålla lösenord som hamnar i loggfiler:

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

Om du måste registrera anslutningsdetaljer för diagnostik, redigera reťazec pripojenia först:

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

Logga det redigerade värdet endast när du behöver det för kortlivade diagnoser. Föredra att logga servernamn, databasnamn och autentiseringsläge separat.

Logga inte känsliga frågeparameter.

Undvik att logga parametervärden som innehåller personuppgifter eller känsliga data:

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

Använd loggflaggor noggrant i produktionen

Drivrutinens log-parameter kan mata ut SQL-satser och parametervärden:

Flag Risk Säker för produktion?
1 (fel) Låg Ja
2 (meddelanden) Låg Ja
4 (rader) Medium, visar data No
8 (SQL) Medium, exponerar frågor Villkorlig
16 (parametrar) Hög, exponerar parametervärden No
32 (transaktioner) Låg Ja
64 (felsökning) Hög, exponerar protokolldetaljer No

För produktion, använd log=1 (endast fel) eller log=3 (fel + meddelanden).

Checklista för säkerhet

Area Recommendation
SQL-injektion Använd parameteriserade frågor för all användarinmatning. Validera dynamiska identifierare mot en tillåtslista.
Credentials Använd Microsoft Entra managed identity när det är möjligt. Koda aldrig lösenord i källkoden.
Encryption Ange encrypt=true eller encrypt=strict för alla produktionsanslutningar. Ange tlsmin=1.2.
Certificates Använd inte TrustServerCertificate=true i produktion. Validera servercertifikat.
Minsta privilegium Skapa applikationsspecifika databasanvändare med endast nödvändiga behörigheter.
Secrets Lagra anslutningssträngar i miljövariabler, Key Vault eller plattformars hemlighetslager.
Logging Logga inte anslutningssträngar, lösenord eller känsliga parametervärden.
Källkontroll Lägg till .env, *.pfx, och *.pem till .gitignore.