Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Ten artykuł omawia praktyki bezpieczeństwa dla aplikacji Go, które łączą się z SQL Server za pomocą sterownikago-mssqldb. Dotyczy on zapobiegania wtryskowi SQL, zarządzania poświadczeniami, konfiguracji szyfrowania oraz zasady najmniejszych uprawnień.
Zapobieganie wstrzyknięciu kodu SQL
SQL injection jest najczęstszą luką w bezpieczeństwie bazy danych. Sterownik go-mssqldb dostarcza parametryzowane zapytania, które oddzielają kod SQL od wartości danych. Zawsze używaj parametrów wejściowych dostarczanych przez użytkownika.
Używanie zapytań sparametryzowanych
Parametry są wysyłane oddzielnie od tekstu SQL, więc dane wejściowe użytkownika nigdy nie mogą być interpretowane jako kod SQL:
// 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))
Nigdy nie łącz danych wejściowych użytkownika do SQL
Konkatenacja łańcuchów pozwala atakującym wstrzykiwać dowolny kod 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;--" ...
Nazwy tabel lub kolumn dynamicznych
Nie można używać parametryzowanych zapytań do nazw tabel, kolumn ani innych identyfikatorów. Jeśli Twoja aplikacja wymaga identyfikatorów dynamicznych, zweryfikowaj je na podstawie listy dozwolonych:
// 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))
}
Uwaga
Nigdy nie umieszczaj danych wejściowych dostarczonych przez użytkownika bezpośrednio w identyfikatorach SQL, nawet przy użyciu nawiasów kwadratowych jako mechanizmu ucieczki. Zawsze waliduj według listy dozwolonych wartości.
Procedury przechowywane i wstrzykiwanie SQL
Procedury przechowywane nie zapobiegają automatycznie wstrzyknięciu SQL. Jeśli procedura wewnętrznie używa EXECUTE lub sp_executesql z połączonymi ciągami, nadal może być podatna na ataki. Używaj parametryzowanych wywołań:
// Parameters are passed safely to the procedure.
_, err := db.ExecContext(ctx, "dbo.SearchEmployees",
sql.Named("searchTerm", userInput))
Zarządzanie sekretami parametry połączenia
Ciągi połączeń mogą zawierać hasła, tajemnice klienta oraz ścieżki certyfikatów. Nigdy nie przechowuj ich w kodzie źródłowym.
Używanie zmiennych środowiskowych
Odczytaj pełne parametry połączenia ze zmiennej środowiskowej:
connString := os.Getenv("MSSQL_CONNECTION_STRING")
if connString == "" {
log.Fatal("MSSQL_CONNECTION_STRING environment variable is required")
}
db, err := sql.Open("sqlserver", connString)
Buduj ciągi połączeń z pojedynczych sekretów
Złóż parametry połączenia z oddzielnych zmiennych środowiskowych dla każdego komponentu:
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())
Całkowicie usuń hasła
Najbezpieczniejsze parametry połączenia w ogóle nie zawierają hasła. Użyj uwierzytelniania Microsoft Entra ID z tożsamością zarządzaną:
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")
Opcje tajnego przechowywania
| Środowisko | Zalecane przechowywanie | Notatki |
|---|---|---|
| Rozwój lokalny | Zmienne środowiskowe lub plik sekretów użytkownika | Nie zatwierdzaj plików .env w kontroli wersji. |
| Azure App Service / Container Apps | Ustawienia aplikacji lub odniesienia do Key Vault | Odwołania do usługi Key Vault wstrzykują wpisy tajne jako zmienne środowiskowe w czasie wykonywania. |
| Kubernetes | Obiekty Secret platformy Kubernetes lub Azure Key Vault ze sterownikiem CSI | Sekrety są montowane jako pliki lub zmienne środowiskowe. |
| Potoki CI/CD | Sekrety potoku / Sekrety GitHub Actions | Use ActiveDirectoryServicePrincipal or ActiveDirectoryAzurePipelines for Azure SQL. |
Ważna
Dodaj .env, *.pfx i *.pem do pliku .gitignore. Przypadkowe dodanie tajnych danych do repozytorium jest jedną z najczęstszych przyczyn wycieku danych uwierzytelniających.
Konfigurowanie szyfrowania
Używaj szyfrowania dla wszystkich zdalnych połączeń
Ustaw encrypt=true dla wszystkich połączeń zdalnych. Szyfrowanie wymagane tylko przez serwer jest podatne na ataki przeciwnika pośrodku. Klient musi egzekwować szyfrowanie. Azure SQL domyślnie umożliwia szyfrowanie:
sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true
Użyj TDS 8.0 do najsilniejszego szyfrowania
TDS 8.0 (tryb ścisły) wykonuje uścisk TLS przed wymianą jakichkolwiek danych protokołu TDS, zapobiegając atakom na obniżenie poziomu protokołu:
sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=strict
TDS 8.0 wymaga SQL Server 2022 lub Azure SQL.
Nie używaj TrustServerCertificate w produkcji
Ustawienie TrustServerCertificate=true wyłącza weryfikację certyfikatów, co umożliwia ataki typu man-in-the-middle:
// DEVELOPMENT ONLY. Never deploy to production.
connString := "sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&TrustServerCertificate=true"
Do produkcji należy skonfigurować odpowiednią walidację certyfikatów. Zobacz Szyfrowanie i certyfikaty.
Ustaw minimalną wersję TLS
Wymusz TLS 1.2 lub nowszy, aby zapobiec atakom na obniżenie poziomu protokołu.
sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true&tlsmin=1.2
Stosowanie zasady najniższych uprawnień
Używaj użytkowników ograniczonych do bazy danych
Twórz zamkniętych użytkowników baz danych ograniczonych do jednej bazy zamiast logowania na poziomie serwera, które mają dostęp do wielu baz danych.
-- 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];
Oddzielne połączenia odczytu i zapisu
Jeśli Twoja aplikacja ma wyraźne ścieżki odczytu i zapisu, stwórz osobne użytkowników bazy danych z odpowiednimi uprawnieniami.
// 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")
Unikaj stosowania SA lub DBO w aplikacjach
Konto sa i użytkownik dbo mają nieograniczony dostęp. Jeśli aplikacja połączona jako sa ma podatność umożliwiającą wstrzyknięcie kodu SQL, atakujący zyskuje pełną kontrolę nad serwerem bazy danych.
Chroń klucze Always Encrypted
Gdy używasz Always Encrypted, klucz główny kolumny (CMK) chroni klucze szyfrujące kolumny (CEK). Odpowiednio zabezpiecz CMK:
| Dostawca kluczy | Najlepsze rozwiązanie |
|---|---|
| Certyfikat lokalny (PFX) | Przechowywaj plik PFX poza katalogiem aplikacji. Ustaw ograniczające uprawnienia do plików. Użyj zmiennej środowiskowej jako hasła. |
| Magazyn certyfikatów systemu Windows | Użyj CurrentUser\My w przypadku kont serwisowych. Ustaw uprawnienia certyfikatu, używając certutil. |
| Azure Key Vault | Używaj zarządzanej tożsamości do dostępu. Ustaw polityki dostępu do sejfu kluczy z najmniejszymi uprawnieniami. Włącz usuwanie miękkie i ochronę przed trwałym usunięciem. |
Aby uzyskać konfigurację dostawcy klucza, zobacz Zawsze szyfrowane.
Loguj się bezpiecznie
Nie loguj ciągów połączeń ani poświadczeń
Ciągi połączeń mogą zawierać hasła, które trafiają do plików logów:
// 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)
Jeśli musisz zapisać szczegóły połączeń do diagnostyki, najpierw zredaguj parametry połączenia:
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()
}
Rejestruj ocenzurowaną wartość tylko wtedy, gdy jest potrzebna do krótkotrwałej diagnostyki. Wolę rejestrować nazwę serwera, nazwę bazy danych oraz tryb uwierzytelniania osobno.
Nie loguj wrażliwych parametrów zapytań
Unikaj logowania wartości parametrów zawierających dane osobowe lub wrażliwe:
// 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")
Ostrożnie używaj flag dziennika w środowisku produkcyjnym
Parametr log sterownika może zwracać instrukcje SQL i wartości parametrów:
| Flag | Ryzyko | Bezpieczny dla produkcji? |
|---|---|---|
1 (błędy) |
Low | Yes |
2 (wiadomości) |
Low | Yes |
4 (wioski) |
Średni, ujawnia dane | No |
8 (SQL) |
Medium, eksponuje zapytania | Warunkowy |
16 (params) |
Wysoki, eksponuje wartości parametrów | No |
32 (transakcje) |
Low | Yes |
64 (debugowanie) |
Wysoki poziom, ujawnia szczegóły protokołu | No |
W produkcji używaj log=1 (tylko błędy) lub log=3 (błędy + komunikaty).
Lista kontrolna zabezpieczeń
| Area | Zalecenie |
|---|---|
| Wstrzyknięcie kodu SQL | Do wszystkich danych wejściowych używaj parametryzowanych zapytań. Zweryfikuj identyfikatory dynamiczne względem listy dozwolonych. |
| Credentials | W miarę możliwości używaj tożsamości zarządzanej Microsoft Entra. Nigdy nie koduj na stałe haseł w kodzie źródłowym. |
| Encryption | Ustaw encrypt=true lub encrypt=strict dla wszystkich połączeń w środowisku produkcyjnym. Ustaw wartość tlsmin=1.2. |
| Certificates | Nie używaj TrustServerCertificate=true w środowisku produkcyjnym. Weryfikuj certyfikaty serwera. |
| Najmniej uprzywilejowane | Utworzenie użytkowników bazy danych specyficznych dla aplikacji z jedynie wymaganymi uprawnieniami. |
| Secrets | Przechowywać ciągi połączeń w zmiennych środowiskowych, Key Vault lub w tajnych magazynach platformy. |
| Logging | Nie loguj ciągów połączeń, haseł ani wrażliwych parametrów. |
| Kontrola źródła | Dodaj .env, *.pfx, oraz *.pem do .gitignore. |