Migliori pratiche di sicurezza con go-mssqldb

Questo articolo tratta le pratiche di sicurezza per le applicazioni Go che si collegano a SQL Server utilizzando il go-mssqldb driver. Si occupa della prevenzione dell'iniezione SQL, della gestione delle credenziali, della configurazione della crittografia e del principio del minor privilegio.

Prevenire l'iniezione SQL

SQL injection è la vulnerabilità più comune nella sicurezza dei database. Il go-mssqldb driver fornisce query parametrizzate che separano il codice SQL dai valori dei dati. Usa sempre i parametri per l'input fornito dall'utente.

Usare query con parametri

I parametri vengono inviati separatamente dal testo SQL, quindi l'input dell'utente non può mai essere interpretato come codice 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))

Mai concatenare input dell'utente in SQL

La concatenazione delle stringhe consente agli attaccanti di iniettare SQL arbitrario:

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

Nomi dinamici di tabelle o colonne

Non puoi usare query parametrizzate per nomi di tabelle, colonne o altri identificatori. Se la tua applicazione richiede identificatori dinamici, validali rispetto a una lista di permessi:

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

Attenzione

Non inserire mai input fornito dall'utente direttamente negli identificatori SQL, anche con la fuga di bracket. Convalida sempre rispetto a un elenco di valori consentiti.

Stored procedure e SQL injection

Le stored procedure non impediscono automaticamente l'iniezione SQL. Se la procedura utilizza EXECUTE o sp_executesql con stringhe concatenate internamente, può comunque essere vulnerabile. Usa chiamate parametrizzate:

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

Gestisci i segreti delle stringhe di connessione

Le stringhe di connessione possono contenere password, segreti client e percorsi di certificato. Non memorizzarli mai nel codice sorgente.

Utilizzare le variabili di ambiente

Leggi l'intera stringa di connessione da una variabile di ambiente:

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

Costruire stringhe di connessione a partire da singoli segreti

Assembla la stringa di connessione da variabili di ambiente separate per ogni componente:

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

Elimina completamente le password

La stringa di connessione più sicura non ha alcuna password. Usa l'autenticazione Microsoft Entra ID con identità gestita:

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

Opzioni di archiviazione segreta

Ambiente Archiviazione consigliata Note
Sviluppo locale Variabili di ambiente o file di segreti utente Non archiviare i file .env nel controllo del codice sorgente.
Servizio app di Azure / Container Apps Impostazioni dell'app o riferimenti a Key Vault I riferimenti di Key Vault iniettano i segreti come variabili di ambiente in fase di esecuzione.
Kubernetes Kubernetes Secrets o Azure Key Vault con driver CSI I segreti sono montati come file o variabili di ambiente.
Pipeline CI/CD Segreti della pipeline / segreti di GitHub Actions Usa ActiveDirectoryServicePrincipal o ActiveDirectoryAzurePipelines per Azure SQL.

Importante

Aggiungi .env, *.pfx, e *.pem al tuo .gitignore file. Inviare accidentalmente segreti in un repository è una delle fonti più comuni di fughe di credenziali.

Configurare la crittografia

Usa la crittografia per tutte le connessioni remote

Imposta encrypt=true per tutte le connessioni remote. La crittografia richiesta solo dal server è vulnerabile ad attacchi avversari nel mezzo. Il client deve far rispettare la crittografia. Azure SQL abilita la crittografia di default:

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

Usa TDS 8.0 per la crittografia più forte

TDS 8.0 (modalità rigorosa) esegue il handshake TLS prima che vengano scambiati dati del protocollo TDS, prevenendo attacchi di downgrade del protocollo:

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

TDS 8.0 richiede SQL Server 2022 o Azure SQL.

Non usare TrustServerCertificate in produzione

Impostare TrustServerCertificate=true disabilita la validazione del certificato, che consente attacchi avversari nel mezzo:

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

Per la produzione, configura una validazione corretta dei certificati. Vedi Crittografia e certificati.

Imposta una versione TLS minima

Applicare TLS 1.2 o successivo per prevenire attacchi di downgrade del protocollo.

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

Usare il principio dei privilegi minimi

Usa utenti con ambito delimitato al database

Creare utenti di database indipendenti limitati a un singolo database anziché account di accesso a livello di server che consentono l'accesso a più database.

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

Connessioni separate di lettura e scrittura

Se la tua app ha percorsi distinti di lettura e scrittura, crea utenti separati del database con permessi appropriati.

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

Evita di usare sa o dbo nelle applicazioni

Il sa login e l'utente dbo hanno accesso senza restrizioni. Se un'applicazione connessa a sa presenta una vulnerabilità di iniezione SQL, l'attaccante ottiene il pieno controllo sul server del database.

Proteggi le chiavi di Always Encrypted

Quando si utilizza Always Encrypted, la chiave master della colonna (CMK) protegge le chiavi di crittografia della colonna (CEK). Proteggi adeguatamente il CMK:

Fornitore di chiave Procedura consigliata
Certificato locale (PFX) Memorizza il file PFX fuori dalla cartella applicativa. Imposta permessi di file restrittivi. Usa una variabile di ambiente per la password.
Archivio certificati Windows Usare CurrentUser\My per account di servizio. Imposta i permessi dei certificati usando certutil.
Azure Key Vault (Archivio chiavi di Azure) Usa l'identità gestita per l'accesso. Imposta le politiche di accesso al caveau con il minor privilegio. Abilitare l'eliminazione temporanea e la protezione dalla rimozione definitiva.

Per la configurazione del provider di chiavi, vedi Sempre criptato.

Registrare in sicurezza

Non registrare stringhe di connessione o credenziali

Le stringhe di connessione possono contenere password che finiscono nei file di log:

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

Se devi registrare i dettagli della connessione per la diagnostica, maschera prima la stringa di connessione:

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

Registra il valore redatto solo quando è necessario per attività diagnostiche di breve durata. Preferisci registrare separatamente il nome del server, il nome del database e la modalità di autenticazione.

Non registrare i parametri sensibili delle query

Evita di registrare i valori dei parametri che contengono dati personali o sensibili:

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

Usa con attenzione i flag di registro in produzione

Il parametro del log driver può fornire istruzioni SQL e valori di parametro:

Flag Rischio Produzione sicura?
1 (errori) Low
2 (messaggi) Low
4 (righe) Medium, espone i dati No
8 (SQL) Medium, espone le query Conditional
16 (params) Alto, espone i valori dei parametri No
32 (transazioni) Low
64 (debug) Alto, rivela i dettagli del protocollo No

Per la produzione, usa log=1 (solo errori) oppure log=3 (errori + messaggi).

Elenco di controllo della sicurezza

Area Raccomandazione
Attacco di tipo SQL injection Usa query parametrizzate per tutti gli input dell'utente. Validare gli identificatori dinamici rispetto a una lista di permessi.
Credentials Usa Microsoft Entra managed identity quando possibile. Non inserire mai le password direttamente nel codice sorgente.
Encryption Impostare encrypt=true o encrypt=strict per tutte le connessioni di produzione. Imposta tlsmin=1.2.
Certificates Non usare TrustServerCertificate=true nell'ambiente di produzione. Valida i certificati del server.
Privilegio minimo Crea utenti database specifici per applicazioni con solo i permessi richiesti.
Secrets Archivia le stringhe di connessione nelle variabili di ambiente, in Key Vault o negli archivi dei segreti della piattaforma.
Logging Non registrare stringhe di connessione, password o parametri sensibili.
Controllo del codice sorgente Aggiungi .env, *.pfx, e *.pem a .gitignore.