6 توصيات لتطوير تطبيقات Go الآمنة

في السنوات الأخيرة ، انتشرت Golang على نطاق أوسع وأوسع. جعلت المشاريع الناجحة مثل Docker و Kubernetes و Terraform رهانات ضخمة على لغة البرمجة هذه. أصبح Go هو المعيار الفعلي لإنشاء أدوات سطر الأوامر. وإذا تحدثنا عن الأمن ، اتضح أنه في هذا المجال في Go ، كل شيء في حالة ممتازة. وهي ، منذ عام 2002 ، تم تسجيل ثغرة أمنية واحدة فقط في Golang في سجل CVE.

ومع ذلك ، فإن حقيقة عدم وجود ثغرات في لغة البرمجة لا تعني أن أي تطبيق مكتوب بهذه اللغة سيكون آمنًا تمامًا. إذا لم يلتزم المطور بتوصيات معينة ، فقد يقوم بإنشاء تطبيق غير محمي حتى في مثل هذه اللغة. في حالة Go ، يمكنك العثور على توصيات مماثلة بالرجوع إلى مواد OWASP .



صاغ مؤلف المقال ، الذي ننشر ترجمته اليوم ، بناءً على بيانات OWASP ، 6 توصيات لتطوير تطبيقات آمنة على Go.

1. تحقق من إدخال المستخدم


هناك حاجة إلى التحقق من صحة البيانات المدخلة من قبل المستخدم ليس فقط لضمان حسن سير العمل في التطبيق. ويهدف أيضًا إلى مكافحة المتسللين الذين يحاولون تعطيل النظام باستخدام البيانات المعدة بطريقة خاصة. علاوة على ذلك ، يساعد فحص بيانات المستخدم المستخدمين على العمل مع التطبيق بثقة أكبر ، لأنه يحميهم من الأخطاء الشائعة. على سبيل المثال ، من خلال تحليل أوامر المستخدم ، يمكنك منع محاولة حذف عدة سجلات في نفس الوقت في موقف قد يؤدي فيه هذا الإجراء إلى خلل في النظام.

يمكنك استخدام حزم Go القياسية للتحقق من إدخال المستخدم. على سبيل المثال ، تساعد حزمة strconv تحويل بيانات السلسلة إلى أنواع أخرى من البيانات. يدعم Go أيضًا ، بفضل regexp ، التعبيرات العادية. يمكن استخدامها لتنفيذ البرامج النصية التحقق من صحة البيانات المعقدة. على الرغم من حقيقة أنه في بيئة التطوير على Go Go ، عادةً ما تُعطى للمكتبات القياسية ، توجد حزم خارجية تهدف إلى التحقق من البيانات. على سبيل المثال ، المدقق . هذه الحزمة تجعل من السهل التحقق من هياكل البيانات المعقدة أو القيم الفردية. على سبيل المثال ، يتحقق الكود التالي من بنية User للتأكد من صحة عنوان البريد الإلكتروني الموجود فيه:

 package main import (  "fmt"  "gopkg.in/go-playground/validator.v9" ) type User struct {  Email string `json:"email" validate:"required,email"`  Name string `json:"name" validate:"required"` } func main() {  v := validator.New()  a := User{    Email: "a",  }  err := v.Struct(a)  for _, e := range err.(validator.ValidationErrors) {    fmt.Println(e)  } } 

2. استخدام قوالب HTML


XSS (البرمجة النصية عبر المواقع ، البرمجة النصية عبر المواقع) هي نقطة ضعف خطيرة وواسعة النطاق. تسمح مشكلة عدم الحصانة XSS للمهاجم بضخ التعليمات البرمجية الضارة في التطبيق والتي قد تؤثر على البيانات التي تم إنشاؤها بواسطة التطبيق. على سبيل المثال ، قد يرسل شخص ما شفرة JavaScript إلى التطبيق ، كجزء من سلسلة استعلام في عنوان URL. عندما يعالج التطبيق مثل هذا الطلب ، يمكن تنفيذ شفرة JavaScript هذه. نتيجة لذلك ، اتضح أن مطور التطبيق يجب أن يتوقع ذلك وينظف البيانات التي يتلقاها المستخدم.

يحتوي Go على حزمة html / template تتيح لك إنشاء تعليمات HTML البرمجية المحمية من الأجزاء الضارة. نتيجة لذلك ، فإن المتصفح الذي يعرض التطبيق الذي تمت مهاجمته ، بدلاً من تنفيذ تعليمات برمجية مثل <script>alert('You've Been Hacked!');</script> ، والذي يعلم المستخدم بأنه تم اختراقه ، سوف ينظر إلى شفرة JavaScript الضارة كأنها نص عادي . إليك ما يبدو عليه خادم HTTP باستخدام قوالب HTML:

 package main import (  "html/template"  "net/http" ) func handler(w http.ResponseWriter, r *http.Request) {  param1 := r.URL.Query().Get("param1")  tmpl := template.New("hello")  tmpl, _ = tmpl.Parse(`{{define "T"}}{{.}}{{end}}`)  tmpl.ExecuteTemplate(w, "T", param1) } func main() {  http.HandleFunc("/", handler)  http.ListenAndServe(":8080", nil) } 

توجد مكتبات تابعة لجهات خارجية يمكنك استخدامها عند تطوير تطبيقات الويب أثناء التنقل. لنفترض أن هذه هي مجموعة أدوات Gorilla على الويب . تتضمن مجموعة الأدوات هذه مكتبات تساعد المطور ، على سبيل المثال ، على ترميز القيم في ملفات تعريف ارتباط المصادقة. وهنا مشروع آخر - nosurf . هذه حزمة HTTP تساعد على منع هجمات CSRF .

3. حماية المشروع الخاص بك من حقن SQL


إذا لم تكن جديدًا في تطوير الويب ، فمن المحتمل أن تعرف عن هجمات حقن SQL (باستخدام طريقة لحقن كود SQL التعسفي في الاستعلامات). لا تزال مشكلة عدم الحصانة المقابلة في المرتبة الأولى في ترتيب OWASP Top 10 . لحماية التطبيقات من حقن SQL ، تحتاج إلى النظر في بعض الميزات. لذا ، فإن أول شيء يجب التأكد منه هو أن المستخدم الذي يتصل بقاعدة البيانات سيكون له امتيازات محدودة. يوصى ، بالإضافة إلى ذلك ، بمسح البيانات التي أدخلها المستخدم ، كما ذكرنا بالفعل ، أو للهروب من الأحرف الخاصة واستخدام وظيفة HTMLEscapeString من حزمة html/template .

ولكن الشيء الأكثر أهمية في الحماية ضد حقن SQL هو استخدام الاستعلامات ذات المعلمات (التعبيرات المعدة). في Go ، لا يتم إعداد التعبيرات للاتصال ، ولكن لقاعدة البيانات. فيما يلي مثال على استخدام الاستعلامات ذات المعلمات:

 customerName := r.URL.Query().Get("name") db.Exec("UPDATE creditcards SET name=? WHERE customerId=?", customerName, 233, 90) 

ولكن ماذا لو كان محرك قاعدة البيانات لا يدعم استخدام التعبيرات المعدة مسبقًا؟ ولكن ماذا لو كان هذا يؤثر على أداء الاستعلام؟ في مثل هذه الحالات ، يمكنك استخدام الدالة db.Query() ، ولكن يجب عليك أولاً تذكر مسح إدخال المستخدم. لمنع الهجمات باستخدام حقن SQL ، يمكنك استخدام مكتبات الجهات الخارجية ، مثل sqlmap .

تجدر الإشارة إلى أنه على الرغم من كل الجهود المبذولة لحماية التطبيقات من هجمات SQL ، لا يزال المهاجمون ينجحون في بعض الأحيان في هذه الهجمات. قل - من خلال تبعيات التطبيق الخارجي. لزيادة مستوى أمان المشروع ، يمكنك استخدام الأدوات المناسبة للتحقق من أمان التطبيق. على سبيل المثال ، أدوات منصة Sqreen .

4. تشفير المعلومات الهامة


إذا تعذر على شخص قراءة سلسلة معينة ، على سبيل المثال ، في تشفير BASE64 ، فإن هذا لا يعني أن المعلومات المخبأة فيه محمية بشكل موثوق. لذلك ، يجب تشفير المعلومات المهمة ، وحمايتها من المتسللين الذين يمكنهم الوصول إلى البيانات المشفرة. عادةً معلومات مشفرة مثل كلمات مرور قاعدة البيانات وكلمات مرور المستخدم وبيانات المستخدم الشخصية.

في إطار مشروع OWASP ، يتم تقديم بعض التوصيات بشأن خوارزميات التشفير المفضلة. على سبيل المثال ، هو bcrypt ، PDKDF2 ، Argon2 ، scrypt . هناك حزمة Go ، crypto ، والتي تحتوي على تطبيقات موثوقة لمختلف خوارزميات التشفير. فيما يلي مثال باستخدام خوارزمية bcrypt :

 package main import (  "database/sql"  "context"  "fmt"  "golang.org/x/crypto/bcrypt" ) func main() {  ctx := context.Background()  email := []byte("john.doe@somedomain.com")  password := []byte("47;u5:B(95m72;Xq")  hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)  if err != nil {    panic(err)  }  stmt, err := db.PrepareContext(ctx, "INSERT INTO accounts SET hash=?, email=?")  if err != nil {    panic(err)  }  result, err := stmt.ExecContext(ctx, hashedPassword, email)  if err != nil {    panic(err)  } } 

يرجى ملاحظة أنه حتى عند استخدام التشفير ، يجب عليك الاهتمام بالنقل الآمن للمعلومات بين الخدمات. على سبيل المثال ، يجب عدم نقل كلمة مرور المستخدم في مكان ما بنص عادي ، حتى إذا تم تخزينها في شكل مشفر. عند نقل البيانات على الإنترنت ، يجدر بنا أن نبدأ من افتراض أنه يمكن اعتراضها من قبل المهاجم الذي يجمع البيانات من الطلبات المنفذة داخل نظام معين. يمكن للمهاجم مطابقة المعلومات التي تم جمعها مع البيانات الواردة من أنظمة أخرى ، ونتيجة لذلك يمكن كسر المشروع الذي يهمه.

5. فرض HTTPS


في هذه الأيام ، تتطلب معظم المتصفحات مواقع تفتح معها لدعم HTTPS. سيعرض Chrome ، على سبيل المثال ، إشعارًا في شريط العناوين إذا تم تبادل البيانات مع الموقع دون استخدام HTTPS. في منظمة تدعم مشروعًا معينًا ، قد يتم تطبيق سياسة أمان تهدف إلى تنظيم تبادل آمن للبيانات بين الخدمات التي تشكل هذا المشروع. نتيجةً لذلك ، لضمان أمان الاتصالات ، من الضروري الانتباه ليس فقط إلى الاستماع إلى التطبيق على المنفذ 443. يجب أن يكون لدى المشروع الشهادات المناسبة ، فمن الضروري تنظيم الاستخدام القسري لـ HTTPS لمنع المهاجم من التبديل إلى تبادل بيانات HTTP.

فيما يلي تطبيق مثال يفرض HTTPS:

 package main import (  "crypto/tls"  "log"  "net/http" ) func main() {  mux := http.NewServeMux()  mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {    w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")    w.Write([]byte("This is an example server.\n"))  })  cfg := &tls.Config{    MinVersion:        tls.VersionTLS12,    CurvePreferences:     []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},    PreferServerCipherSuites: true,    CipherSuites: []uint16{      tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,      tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,      tls.TLS_RSA_WITH_AES_256_GCM_SHA384,      tls.TLS_RSA_WITH_AES_256_CBC_SHA,    },  }  srv := &http.Server{    Addr:     ":443",    Handler:   mux,    TLSConfig:  cfg,    TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler), 0),  }  log.Fatal(srv.ListenAndServeTLS("tls.crt", "tls.key")) } 

يرجى ملاحظة أن التطبيق يستمع على المنفذ 443. وهنا هو الخط المسؤول عن الاستخدام القسري ل HTTPS:

 w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains") 

بالإضافة إلى ذلك ، قد يكون تحديد اسم الخادم في تكوين TLS منطقيًا:

 config := &tls.Config{ServerName: "yourSiteOrServiceName"} 

يوصى دائمًا باستخدام تشفير البيانات المرسلة بين أجزاء النظام ، حتى إذا كان تطبيق الويب عبارة عن شركة دردشة داخلية. تخيل العواقب المحتملة للهجوم الذي يعترض فيه المهاجم بيانات التطبيق المرسلة عبر شبكة. كلما كان ذلك ممكنًا ، يجدر الإعداد لهجمات محتملة على المشروع ، في محاولة لتعقيد حياة المهاجمين قدر الإمكان.

6. كن حذرا حول معالجة الأخطاء وتسجيل.


هذا العنصر هو الأخير في قائمتنا ، لكنه بالتأكيد بعيد عن الأخير من حيث الأهمية. نحن هنا نتحدث عن معالجة الأخطاء والتسجيل.

من أجل مواجهة المشاكل الناشئة في الإنتاج في الوقت المناسب ، تحتاج إلى تزويد التطبيق بالأدوات المناسبة. في الوقت نفسه ، يجب أن تكون حذرًا جدًا في رسائل الخطأ التي تظهر للمستخدمين. يجب أن لا تخبر تفاصيل المستخدم عن الخطأ الذي حدث. الحقيقة هي أن المهاجم يمكنه استخدام هذه المعلومات لمعرفة الخدمات والتقنيات المستخدمة في المشروع. علاوة على ذلك ، تجدر الإشارة إلى أنه على الرغم من أن السجلات عادةً ما يتم إدراكها في ضوء إيجابي ، إلا أنه يتم تخزين هذه السجلات في مكان ما. وإذا وقع سجل التطبيق في الأيدي الخطأ ، فإن تحليل المعلومات منه يمكن أن يساعد في تنفيذ هجوم على المشروع.

أول شيء يجب مراعاته هنا هو أنه لا توجد استثناءات لـ Go. هذا يعني أن الأخطاء في التطبيقات المكتوبة في Go لا تتعامل مع الأخطاء بنفس طريقة معالجة الأخطاء في التطبيقات المكتوبة بلغات أخرى. عادة ما يبدو مثل هذا:

 if err != nil {    //   } 

بالإضافة إلى ذلك ، يحتوي Go على مكتبة قياسية للعمل مع سجلات تسمى log . هنا مثال بسيط على استخدامه:

 package main import (  "log" ) func main() {  log.Print("Logging in Go!") } 

توجد مكتبات تابعة لجهات أخرى لتنظيم التسجيل. على سبيل المثال ، هذا هو logrus ، glog ، loggo . فيما يلي مثال صغير لاستخدام logrus :

 package main import (  "os"  log "github.com/sirupsen/logrus" ) func main() {  file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND, 0644)  if err != nil {    log.Fatal(err)  }  defer file.Close()  log.SetOutput(file)  log.SetFormatter(&log.JSONFormatter{})  log.SetLevel(log.WarnLevel)  log.WithFields(log.Fields{    "animal": "walrus",    "size":  10,  }).Info("A group of walrus emerges from the ocean") } 

وأخيرًا ، عند التعامل مع البيانات التي تدخل في السجلات ، استخدم توصيات الأمان التي ناقشناها بالفعل. على وجه الخصوص ، واضحة وتشفير هذه البيانات.

النتائج


التوصيات الواردة هنا هي الحد الأدنى الذي يجب أن يحتوي عليه المشروع المكتوب في Go. ولكن إذا كان المشروع المعني عبارة عن أداة مساعدة لسطر الأوامر ، فلن يحتاج إلى تنفيذ حماية لحركة المرور المرسلة عبر الشبكة. تنطبق النصائح المتبقية على أي نوع من التطبيقات تقريبًا. إذا كنت ترغب في استكشاف عميق لمشكلة تطوير تطبيقات آمنة على الذهاب ، ألق نظرة على كتاب OWASP حول هذا الموضوع. وهنا مستودع يحتوي على روابط لأدوات مختلفة تهدف إلى ضمان أمن تطبيقات Go.

مهما فعلت في مجال تأمين التطبيقات الخاصة بك ، تذكر أنه يمكن دائمًا تحسين الأمان وأن المهاجمين يبحثون باستمرار عن طرق جديدة للهجوم. لذلك ، فإن حماية التطبيق هي ما عليك القيام به ، ليس من حالة إلى أخرى ، ولكن باستمرار.

أعزائي القراء! كيف تحمي تطبيقاتك المكتوبة في Go؟

Source: https://habr.com/ru/post/ar484614/


All Articles