Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Artikel ini membahas praktik keamanan untuk aplikasi Go yang tersambung ke SQL Server dengan menggunakan go-mssqldb driver. Ini membahas pencegahan injeksi SQL, manajemen kredensial, konfigurasi enkripsi, dan prinsip hak istimewa terkecil.
Mencegah injeksi SQL
Injeksi SQL adalah kerentanan keamanan database yang paling umum. Driver go-mssqldb menyediakan kueri berparameter yang memisahkan kode SQL dari nilai data. Selalu gunakan parameter untuk input yang disediakan pengguna.
Menggunakan kueri berparameter
Parameter dikirim secara terpisah dari teks SQL, sehingga input pengguna tidak pernah dapat ditafsirkan sebagai kode 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))
Jangan pernah menggabungkan input pengguna ke dalam SQL
Penggabungan string memungkinkan penyerang untuk menyuntikkan SQL arbitrer:
// 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;--" ...
Nama tabel atau kolom dinamis
Anda tidak dapat menggunakan kueri berparameter untuk nama tabel, nama kolom, atau pengidentifikasi lainnya. Jika aplikasi Anda memerlukan ID dinamis, validasi terhadap daftar yang diizinkan:
// 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
Jangan pernah menempatkan input yang disediakan pengguna secara langsung di pengidentifikasi SQL, bahkan dengan tanda kurung yang melarikan diri. Selalu validasi menggunakan daftar nilai yang diizinkan.
Prosedur tersimpan dan injeksi SQL
Prosedur tersimpan tidak secara otomatis mencegah injeksi SQL. Jika prosedur menggunakan EXECUTE atau sp_executesql dengan string yang terkonkatenasi secara internal, prosedur masih rentan. Gunakan panggilan berparameter:
// Parameters are passed safely to the procedure.
_, err := db.ExecContext(ctx, "dbo.SearchEmployees",
sql.Named("searchTerm", userInput))
Kelola informasi rahasia string koneksi
String koneksi dapat berisi kata sandi, rahasia klien, dan jalur sertifikat. Jangan pernah menyimpannya dalam kode sumber.
Menggunakan variabel lingkungan
Baca string koneksi lengkap dari variabel lingkungan:
connString := os.Getenv("MSSQL_CONNECTION_STRING")
if connString == "" {
log.Fatal("MSSQL_CONNECTION_STRING environment variable is required")
}
db, err := sql.Open("sqlserver", connString)
Membangun string koneksi dari masing-masing rahasia
Kumpulkan string koneksi dari variabel lingkungan terpisah untuk setiap komponen:
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())
Hilangkan kata sandi sepenuhnya
string koneksi yang paling aman tidak memiliki kata sandi sama sekali. Menggunakan autentikasi Microsoft Entra ID dengan identitas terkelola:
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")
Opsi penyimpanan rahasia
| Lingkungan | Penyimpanan yang direkomendasikan | Notes |
|---|---|---|
| Pengembangan lokal | Variabel lingkungan atau file rahasia pengguna | Jangan memeriksa .env file ke dalam kontrol sumber. |
| Azure App Service / Aplikasi Kontainer | Setelan aplikasi atau referensi Key Vault | Referensi Key Vault menyisipkan rahasia sebagai variabel lingkungan pada waktu proses. |
| Kubernetes | Kubernetes Secrets atau Azure Key Vault dengan driver CSI | Rahasia dipasang sebagai file atau variabel lingkungan. |
| Jalur Pemrosesan CI/CD | Rahasia alur / rahasia GitHub Actions | Gunakan ActiveDirectoryServicePrincipal atau ActiveDirectoryAzurePipelines untuk Azure SQL. |
Important
Tambahkan .env, *.pfx, dan *.pem ke file .gitignore Anda. Secara tidak sengaja melakukan rahasia ke repositori adalah salah satu sumber kebocoran kredensial yang paling umum.
Konfigurasikan enkripsi
Gunakan enkripsi untuk semua koneksi jarak jauh
Atur encrypt=true untuk semua koneksi jarak jauh. Enkripsi yang hanya diperlukan oleh server rentan terhadap serangan musuh di tengah. Klien harus menerapkan enkripsi. Azure SQL mengaktifkan enkripsi secara default:
sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true
Gunakan TDS 8.0 untuk enkripsi terkuat
TDS 8.0 (mode ketat) melakukan jabat tangan TLS sebelum data protokol TDS dipertukarkan, mencegah serangan penurunan peringkat protokol:
sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=strict
TDS 8.0 memerlukan SQL Server 2022 atau Azure SQL.
Jangan gunakan TrustServerCertificate dalam produksi
Pengaturan TrustServerCertificate=true menonaktifkan validasi sertifikat, yang memungkinkan serangan musuh di tengah:
// DEVELOPMENT ONLY. Never deploy to production.
connString := "sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&TrustServerCertificate=true"
Untuk produksi, konfigurasikan validasi sertifikat yang tepat. Lihat Enkripsi dan sertifikat.
Menetapkan versi TLS minimum
Terapkan TLS 1.2 atau yang lebih baru untuk mencegah serangan penurunan peringkat protokol.
sqlserver://<user>:<password>@<server>:1433?database=AdventureWorks2025&encrypt=true&tlsmin=1.2
Menerapkan prinsip hak istimewa terendah
Gunakan pengguna dengan cakupan database
Buat pengguna database mandiri yang terbatas pada satu database, bukan login tingkat server yang mengakses beberapa 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];
Koneksi baca dan tulis terpisah
Jika aplikasi Anda memiliki jalur baca dan tulis yang berbeda, buat pengguna database terpisah dengan izin yang sesuai.
// 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")
Hindari menggunakan sa atau dbo dalam aplikasi
Login sa dan dbo pengguna memiliki akses tidak terbatas. Jika aplikasi yang terhubung memiliki sa kerentanan injeksi SQL, penyerang mendapatkan kendali penuh atas server database.
Lindungi kunci Selalu Terenkripsi
Saat Anda menggunakan Always Encrypted, kunci master kolom (CMK) melindungi kunci enkripsi kolom (CEK). Amankan CMK dengan benar:
| Penyedia kunci | Praktik terbaik |
|---|---|
| Sertifikat lokal (PFX) | Simpan file PFX di luar direktori aplikasi. Tetapkan izin file yang terbatas. Gunakan variabel lingkungan untuk kata sandi. |
| Penyimpanan Sertifikat Windows | Gunakan CurrentUser\My untuk akun layanan. Atur izin sertifikat dengan menggunakan certutil. |
| Azure Key Vault | Gunakan identitas terkelola untuk akses. Tetapkan kebijakan akses brankas kunci dengan hak istimewa paling sedikit. Aktifkan penghapusan sementara dan perlindungan penghapusan menyeluruh. |
Untuk konfigurasi penyedia kunci, lihat Selalu Dienkripsi.
Log dengan aman
Jangan mencatat string koneksi atau kredensial
String koneksi dapat berisi kata sandi yang berakhir di file 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)
Jika Anda harus merekam detail koneksi untuk diagnostik, suntingan string koneksi terlebih dahulu:
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()
}
Catat ke log nilai yang disamarkan hanya saat Anda memerlukannya untuk diagnostik sementara. Lebih suka mencatat nama server, nama database, dan mode otentikasi secara terpisah.
Jangan mencatat parameter kueri sensitif
Hindari mencatat nilai parameter yang berisi data pribadi atau sensitif:
// 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")
Gunakan bendera log dengan hati-hati dalam produksi
Parameter driver log dapat menghasilkan pernyataan SQL dan nilai parameter:
| Flag | Risiko | Produksi aman? |
|---|---|---|
1 (kesalahan) |
Low | Yes |
2 (pesan) |
Low | Yes |
4 (baris) |
Sedang, mengekspos data | No |
8 (SQL) |
Sedang, mengekspos kueri | Bersyarat |
16 (param) |
Tinggi, menampilkan nilai parameter | No |
32 (transaksi) |
Low | Yes |
64 (debug) |
Tinggi, menampilkan detail protokol | No |
Untuk produksi, gunakan log=1 (hanya kesalahan) atau log=3 (kesalahan + pesan).
Daftar periksa keamanan
| Area | Recommendation |
|---|---|
| Injeksi SQL | Gunakan kueri berparameter untuk semua input pengguna. Validasikan pengenal dinamis terhadap daftar yang diizinkan. |
| Credentials | Gunakan identitas terkelola Microsoft Entra jika memungkinkan. Jangan pernah membuat kode keras kata sandi di sumber. |
| Encryption | Atur encrypt=true atau encrypt=strict untuk semua koneksi produksi. Atur tlsmin=1.2. |
| Certificates | Jangan gunakan TrustServerCertificate=true dalam produksi. Validasikan sertifikat server. |
| Hak istimewa terkecil | Buat pengguna database khusus aplikasi hanya dengan izin yang diperlukan. |
| Secrets | Simpan string koneksi di variabel lingkungan, Key Vault, atau penyimpanan rahasia platform. |
| Logging | Jangan mencatat string koneksi, kata sandi, atau nilai parameter sensitif. |
| Kontrol sumber | Tambahkan .env, *.pfx, dan *.pem ke .gitignore. |