أمان تطبيقات Go: أهم 10 ثغرات أمنية عمليًا
Go (Golang) — لغة تشتهر بأدائها العالي وآليات الأمان المدمجة. ومع ذلك، مثل أي أداة، لا تحمي المطور من الأخطاء بشكل كامل. تنشأ العديد من الثغرات ليس بسبب اللغة نفسها، بل بسبب الاستخدام غير الصحيح لإمكانياتها: العمل مع الغوروتينات، القنوات، المؤشرات أو المكتبات الخارجية.
في هذه المقالة، سنحلل أكثر 10 ثغرات أمنية شيوعًا في تطبيقات Go. كل نقطة تحتوي على مثال كود حقيقي، شرح للمشكلة، ونصيحة عملية لإصلاحها. ستكون المادة مفيدة لمطوري Go المبتدئين وكذلك للمهندسين ذوي الخبرة الذين يرغبون في تحديث معرفتهم.
1. حقن SQL عبر دمج السلاسل النصية
الخطأ الأكثر كلاسيكية هو تكوين استعلام SQL عن طريق دمج السلاسل النصية مع بيانات المستخدم. توفر Go برامج تشغيل ممتازة (مثل database/sql)، لكنها لا تحمي من الأخطاء الساذجة.
الكود المعرض للخطر:
func getUser(db *sql.DB, username string) (*User, error) { query := "SELECT id, name FROM users WHERE username = '" + username + "'" rows, err := db.Query(query) // ...}يمكن للمهاجم إرسال admin' OR '1'='1 والحصول على جميع المستخدمين.
الحل: استخدم دائمًا الاستعلامات ذات المعلمات.
func getUserSafe(db *sql.DB, username string) (*User, error) { query := "SELECT id, name FROM users WHERE username = $1" row := db.QueryRow(query, username) // ...}2. حالة السباق (race condition) في الغوروتينات
Go هي لغة ذات دعم قوي للتزامن، ولكن بدون مزامنة، يؤدي الوصول إلى البيانات المشتركة من غوروتينات مختلفة إلى سلوك غير محدد.
الكود المعرض للخطر:
var counter int
func increment() { for i := 0; i < 1000; i++ { counter++ // غير ذري }}
func main() { go increment() go increment() time.Sleep(time.Second) fmt.Println(counter) // النتيجة غير متوقعة}الحل: استخدم الميوتكس (sync.Mutex) أو العمليات الذرية (sync/atomic).
var mu sync.Mutexvar counter int
func incrementSafe() { for i := 0; i < 1000; i++ { mu.Lock() counter++ mu.Unlock() }}3. تسرب الغوروتينات (goroutine leak)
إذا تم حظر غوروتين قيد التشغيل إلى الأبد (على سبيل المثال، في انتظار بيانات في قناة لن تُغلق أبدًا)، فإنه "يتسرب"، مما يستهلك الذاكرة.
الكود المعرض للخطر:
func process(jobs <-chan int) { for j := range jobs { go func(job int) { time.Sleep(time.Hour) // عمل طويل fmt.Println(job) }(j) }}إذا لم يتم إغلاق القناة jobs، فستظل الغوروتينات معلقة إلى الأبد.
الحل: استخدم السياق (context.Context) لإلغاء العمليات وتحكم دائمًا في عمر الغوروتينات.
func processSafe(ctx context.Context, jobs <-chan int) { for j := range jobs { select { case <-ctx.Done(): return default: go func(job int) { select { case <-time.After(time.Hour): fmt.Println(job) case <-ctx.Done(): return } }(j) } }}4. معالجة الأخطاء غير الصحيحة (panic/recover)
استخدام panic() لمعالجة الأخطاء العادية هو ممارسة سيئة. إذا لم يتم التقاط panic، سينهار التطبيق. ولكن الإفراط في استخدام recover() قد يخفي مشاكل حرجة.
الكود المعرض للخطر: