Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo aborda as práticas de segurança para aplicações Go que se ligam ao SQL Server utilizando o go-mssqldb driver. Aborda a prevenção de injeção SQL, gestão de credenciais, configuração de encriptação e o princípio do menor privilégio.
Prevenir a injeção SQL
A injeção SQL é a vulnerabilidade mais comum na segurança de bases de dados. O go-mssqldb driver fornece consultas parametrizadas que separam o código SQL dos valores dos dados. Use sempre parâmetros para a entrada fornecida pelo utilizador.
Utilizar consultas parametrizadas
Os parâmetros são enviados separadamente do texto SQL, por isso a entrada do utilizador nunca pode ser interpretada como código 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))
Nunca concatene a entrada do utilizador em SQL
A concatenação de strings permite aos atacantes injetar SQL arbitrário:
// 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;--" ...
Nomes dinâmicos de tabelas ou colunas
Não pode usar consultas parametrizadas para nomes de tabelas, nomes de colunas ou outros identificadores. Se a sua aplicação exigir identificadores dinâmicos, valide-os contra uma lista de autorizações:
// 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))
}
Caution
Nunca coloque entrada fornecida pelo utilizador diretamente nos identificadores SQL, mesmo com a fuga de parênteses. Valide sempre com base numa lista de valores permitidos.
Procedimentos armazenados e injeção SQL
Os procedimentos armazenados não impedem automaticamente a injeção de SQL. Se o procedimento usar EXECUTE ou sp_executesql com cadeias concatenadas internamente, pode ainda ser vulnerável. Use chamadas parametrizadas:
// Parameters are passed safely to the procedure.
_, err := db.ExecContext(ctx, "dbo.SearchEmployees",
sql.Named("searchTerm", userInput))
Gerir segredos da cadeia de ligação
As strings de ligação podem conter palavras-passe, segredos do cliente e caminhos de certificados. Nunca os guarde no código-fonte.
Utilizar variáveis de ambiente
Leia a cadeia de ligação completa a partir de uma variável de ambiente:
connString := os.Getenv("MSSQL_CONNECTION_STRING")
if connString == "" {
log.Fatal("MSSQL_CONNECTION_STRING environment variable is required")
}
db, err := sql.Open("sqlserver", connString)
Construir cadeias de ligação a partir de segredos individuais
Monte a cadeia de ligação a partir de variáveis de ambiente separadas para cada 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())
Eliminar completamente as palavras-passe
A cadeia de ligação mais segura não tem qualquer palavra-passe. Use autenticação Microsoft Entra ID com identidade gerida:
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")
Opções de armazenamento secreto
| Meio Ambiente | Armazenamento recomendado | Notes |
|---|---|---|
| Desenvolvimento local | Variáveis de ambiente ou ficheiro de secretos de utilizador | Não faças check-in de ficheiros .env no controlo de versões. |
| Serviço de Aplicações do Azure / Container Apps | Definições da aplicação ou referências ao Key Vault | As referências do Key Vault injetam segredos como variáveis de ambiente em tempo de execução. |
| Kubernetes | Kubernetes Secrets ou Azure Key Vault com o driver CSI | Os segredos são montados como ficheiros ou variáveis de ambiente. |
| Pipelines de CI/CD | Segredos do pipeline / segredos do GitHub Actions | Use ActiveDirectoryServicePrincipal ou ActiveDirectoryAzurePipelines para SQL do Azure. |
Importante
Adicione .env, *.pfx, e *.pem ao seu .gitignore ficheiro. Incluir acidentalmente segredos num repositório é uma das fontes mais comuns de fugas de credenciais.
Configurar criptografia
Usar encriptação para todas as ligações remotas
Defina encrypt=true para todas as ligações remotas. A encriptação que só é exigida pelo servidor é vulnerável a ataques adversários no meio. O cliente tem de impor encriptação. O SQL do Azure permite a encriptação por defeito:
sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true
Use o TDS 8.0 para a encriptação mais forte
O TDS 8.0 (modo estrito) realiza a negociação TLS antes de serem trocados quaisquer dados do protocolo TDS, impedindo ataques de downgrade do protocolo:
sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=strict
O TDS 8.0 requer SQL Server 2022 ou SQL do Azure.
Não use o TrustServerCertificate em produção
Definir TrustServerCertificate=true desativa a validação do certificado, o que permite ataques de intermediário:
// DEVELOPMENT ONLY. Never deploy to production.
connString := "sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&TrustServerCertificate=true"
Para produção, configure a validação adequada dos certificados. Veja Encriptação e certificados.
Defina uma versão mínima de TLS
Imponha o TLS 1.2 ou superior para evitar ataques de degradação do protocolo.
sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true&tlsmin=1.2
Aplicar o princípio do menor privilégio
Utilize utilizadores no âmbito da base de dados
Criar utilizadores de bases de dados contidas com âmbito para uma única base de dados em vez de logins ao nível do servidor que acedam a múltiplas bases de dados.
-- 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];
Ligações de leitura e de escrita separadas
Se a sua aplicação tiver caminhos distintos de leitura e escrita, crie utilizadores de base de dados separados com permissões apropriadas.
// 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")
Evite usar sa ou dbo em aplicações
O sa login e dbo o utilizador têm acesso irrestrito. Se uma aplicação ligada a sa tiver uma vulnerabilidade de injeção SQL, o atacante ganha controlo total sobre o servidor de base de dados.
Proteger chaves sempre encriptadas
Quando utiliza o Always Encrypted, a chave mestra da coluna (CMK) protege as chaves de encriptação da coluna (CEK). Garantir o CMK de forma adequada:
| Fornecedor-chave | Melhores práticas |
|---|---|
| Certificado local (PFX) | Armazene o ficheiro PFX fora do diretório da aplicação. Defina permissões restritivas de ficheiros. Use uma variável de ambiente para a palavra-passe. |
| Armazenamento de Certificados do Windows | Use CurrentUser\My para contas de serviço. Defina permissões de certificados usando certutil. |
| Azure Key Vault | Use identidade gerida para acesso. Defina políticas de acesso ao cofre de chaves com o menor privilégio. Ative a eliminação recuperável e a proteção contra eliminação permanente. |
Para a configuração do fornecedor de chaves, veja Sempre Encriptado.
Registe em segurança
Não loges cadeias de ligação ou credenciais
As strings de ligação podem conter palavras-passe que acabam em ficheiros de registo:
// 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 tiver de registar detalhes de ligação para diagnóstico, rediga primeiro a cadeia de ligação:
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()
}
Registe o valor expurgado apenas quando for necessário para diagnóstico de curta duração. Prefiro registar o nome do servidor, nome da base de dados e modo de autenticação separadamente.
Não registre parâmetros sensíveis de consulta
Evite registar valores de parâmetros que contenham dados pessoais ou sensíveis:
// 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")
Utilize as opções de registo com cuidado num ambiente de produção
O parâmetro do log driver pode gerar instruções SQL e valores de parâmetro:
| Flag | Risco | Produção segura? |
|---|---|---|
1 (erros) |
Baixo | Sim |
2 (mensagens) |
Baixo | Sim |
4 (filas) |
Médio, expõe dados | No |
8 (SQL) |
Medium, mostra consultas | Conditional |
16 (parâmetros) |
Alta, expõe os valores dos parâmetros | No |
32 (transações) |
Baixo | Sim |
64 (depuração) |
Alta, expõe detalhes do protocolo | No |
Para produção, usar log=1 (apenas erros) ou log=3 (erros + mensagens).
Lista de verificação de segurança
| Area | Recommendation |
|---|---|
| Injeção de SQL | Use consultas parametrizadas para todas as entradas do utilizador. Valide os identificadores dinâmicos com base numa lista de permissões. |
| Credentials | Use a identidade gerida da Microsoft Entra sempre que possível. Nunca codifique palavras-passe diretamente no código-fonte. |
| Encryption | Defina encrypt=true ou encrypt=strict para todas as ligações de produção. Defina tlsmin=1.2. |
| Certificates | Não uses TrustServerCertificate=true em produção. Validar os certificados do servidor. |
| Privilégio mínimo | Crie utilizadores de bases de dados específicos para aplicações apenas com as permissões necessárias. |
| Secrets | Armazene as cadeias de ligação em variáveis de ambiente, no Key Vault ou em repositórios de segredos da plataforma. |
| Logging | Não loges strings de ligação, palavras-passe ou valores de parâmetros sensíveis. |
| Controlo de origem | Adicionem .env, *.pfx, e *.pem a .gitignore. |