6 أفضل ممارسات الأمان لـ Go

تم إعداد ترجمة للمقال خصيصًا لطلاب دورة مطور Golang .





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

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

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


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

لاختبار إدخال المستخدم ، يمكنك استخدام حزم Go الأصلية مثل strconv للتعامل مع تحويل السلاسل إلى أنواع بيانات أخرى. يدعم Go أيضًا التعبيرات العادية regexpلإجراء الفحوصات المعقدة. على الرغم من أن 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. الاستغلال الرئيسي هو أنه يمكن للمهاجم حقن كود خبيث في التطبيق لتعديل الناتج. على سبيل المثال ، قد يرسل شخص ما شفرة JavaScript كجزء من سلسلة استعلام في عنوان URL. عندما يعرض أحد التطبيقات قيمة المستخدم ، يمكن تنفيذ تعليمات JavaScript البرمجية. لذلك ، كمطور ، يجب أن تكون على دراية بهذا الاحتمال ومسح البيانات التي أدخلها المستخدم.

يحتوي Go على حزمة html / template لتشفير ما يعود التطبيق إلى المستخدم. وهكذا ، بدلا من المتصفح يقوم بإدخال مثل<script>alert(‘ !’);</script>سيحذر. يمكنك ترميز الإدخال ، وسيعمل التطبيق على معالجة الإدخال كرمز HTML نموذجي مطبوع في المستعرض. سيبدو خادم 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)
}


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

3. حماية نفسك من حقن SQL


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

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

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


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

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

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


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

لديه OWASP عدة توصيات بشأن ما تستخدم خوارزميات التشفير، على سبيل المثال، bcrypt، PDKDF2، Argon2أو scrypt. لحسن الحظ ، هناك حزمة Go تتضمن تطبيقات موثوقة لتشفير المعلومات - التشفير . الكود التالي هو مثال للاستخدام 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 أيضًا مكتبة مدمجة للعمل مع السجلات. يبدو أبسط رمز مثل هذا:

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.

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


. — , , , .NET, Node.js Java, Docker.

.

All Articles