لماذا يقع حتى مطوري Go في فخ القراصنة؟
\n\nتشتهر Go بسرعتها وبساطتها وأدوات الأمان المدمجة فيها. لكن تهدئة النفس بعبارة «أنا أكتب بلغة Go، إذن أنا بأمان» هي وهم خطير. حقن SQL وهجمات XSS ليست من بقايا الماضي. فهي لا تزال تسقط المواقع وتسرق قواعد البيانات يومياً، وGo ليست استثناءً.
\n\nالمشكلة هي أن Go تمنح المطور أدوات قوية، لكنها لا تستخدمها نيابة عنك. يمكنك عن طريق الخطأ لصق استعلام SQL باستخدام fmt.Sprintf أو عرض تعليق مستخدم دون ترميز - وسيحصل المهاجم على وصول إلى قاعدة بياناتك أو يسرق ملفات تعريف الارتباط للزوار.
في هذه المقالة، سنحلل أخطر نوعين من الهجمات - حقن SQL و البرمجة النصية عبر المواقع (XSS) - ونوضح كيفية منعهما باستخدام Go الأصلي والمكتبات الشائعة. ستحصل ليس على نظرية، بل على قوالب كود جاهزة يمكنك إدراجها في مشروعك اليوم.
\n\nحقن SQL في Go: كيف تنشأ وكيف تقضي عليها من الجذور
\n\nالخطأ النموذجي: ربط السلاسل النصية
\n\nتخيل أن لديك نقطة نهاية لجلب مستخدم بواسطة المعرف. الطريقة الأكثر سذاجة (وخطورة) لكتابة الاستعلام تبدو هكذا:
\n\npackage main\n\nimport (\n "database/sql"\n "fmt"\n "log"\n _ "github.com/lib/pq"\n)\n\nfunc getUserByID(db *sql.DB, userID string) {\n // خطير! ربط السلاسل النصية\n query := fmt.Sprintf("SELECT id, name, email FROM users WHERE id = '%s'", userID)\n rows, err := db.Query(query)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... معالجة\n}\n\nإذا قام المهاجم بتمرير السلسلة 1' OR '1'='1 في userID، سيتحول الاستعلام إلى:
SELECT id, name, email FROM users WHERE id = '1' OR '1'='1'\n\nوفويلا - سيحصل على جميع مستخدمي قاعدة البيانات. وإذا أدخل 1'; DROP TABLE users; --، فستفقد الجدول.
الحل: الاستعلامات المعلمة (العبارات المحضرة)
\n\nمشغل Go لقواعد البيانات PostgreSQL و MySQL و SQLite يدعم العبارات المحضرة. إنها تفصل كود SQL عن البيانات، ويقوم المشغل تلقائياً بترميز الأحرف الخطرة.
\n\nfunc getUserByIDSafe(db *sql.DB, userID string) {\n // آمن: استعلام معلم\n query := "SELECT id, name, email FROM users WHERE id = $1"\n rows, err := db.Query(query, userID)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... معالجة\n}\n\nلاحظ $1 - هذا هو مكان الحجز للوسيطة الأولى. في MySQL يُستخدم الرمز ?. الآن حتى لو كان userID يحتوي على 1' OR '1'='1، سيتعامل معه المشغل كقيمة نصية، وليس كجزء من كود SQL.
العمل مع حزمة sqlx
\n\nمكتبة github.com/jmoiron/sqlx تجعل الكود أنظف. إنها تدعم المعلمات المسماة والمسح التلقائي إلى الهياكل:
import "github.com/jmoiron/sqlx"\n\ntype User struct {\n ID int `db:"id"`\n Name string `db:"name"`\n Email string `db:"email"`\n}\n\nfunc getUserByIDSqlx(db *sqlx.DB, userID string) (*User, error) {\n user := &User{}\n // معلمة مسماة :id\n err := db.Get(user, "SELECT * FROM users WHERE id = :id", sqlx.Named("id", userID))\n if err != nil {\n return nil, err\n }\n return user, nil\n}\n\nهام: حتى إذا كنت تستخدم ORM مثل GORM أو Ent، تأكد من تكوينها لاستخدام العبارات المحضرة. بعض الإصدارات القديمة من ORM كانت تسمح باستعلامات «خام».
\n\nXSS في Go: عندما يطلق موقعك النار على قدمه
\n\nكيف يحدث هجوم XSS
\n\nتحدث XSS (البرمجة النصية عبر المواقع) عندما تقوم بإدراج محتوى مستخدم على الصفحة دون ترميز. شر