Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
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. |