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

مخطط CORS يوضح التواصل بين موقعين على الويب في المتصفح.

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

عند إضافة رمز إلى CodeQL ، من الأفضل التحقق دائمًا من الاستعلامات والأطر ذات الصلة المتوفرة بالفعل حتى لا نعيد اختراع العجلة. بالنسبة لمعظم اللغات ، لدى CodeQL بالفعل استعلام CORS يغطي العديد من الحالات الافتراضية. أسهل وأبسط طريقة لتنفيذ الكورس هي من خلال تحديد يدويًا Access-Control-Allow-Origin و Access-Control-Allow-Credentials رؤوس الاستجابة. من خلال نمذجة الأطر للغة (على سبيل المثال ، Django و Fastapi و Flask) ، يمكن لـ CodeQL تحديد مكان تعيين تلك الرؤوس في الكود. بناءً على هذه النماذج من خلال البحث عن قيم رأس محددة ، يمكن لـ CODEQL العثور على أمثلة بسيطة من CORS ومعرفة ما إذا كانت تتطابق مع القيم الضعيفة.

في المثال التالي ، يمكن الوصول إلى الموارد غير المصادقة على الخوادم من خلال المواقع التعسفية.

func saveHandler(w http.ResponseWriter, r *http.Request) { 
    w.Header().Set("Access-Control-Allow-Origin", "*") 
}

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

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

 /** Provides a class for modeling new HTTP header-write APIs. */
  module HeaderWrite {
    /**
     * A data-flow node that represents a write to an HTTP header.
     *
     * Extend this class to model new APIs. If you want to refine existing API models,
     * extend `HTTP::HeaderWrite` instead.
     */
    abstract class Range extends DataFlow::ExprNode {
      /** Gets the (lower-case) name of a header set by this definition. */
      string getHeaderName() { result = this.getName().getStringValue().toLowerCase() }

بعض الطرق المفيدة مثل getHeaderName و getHeaderValue يمكن أن تساعد أيضًا في تطوير الاستعلامات الأمنية المتعلقة بالرؤوس ، مثل سوء التكوين. على عكس مثال الكود السابق ، فإن النمط أدناه هو مثال على تكوين سوء التكوين الذي يكون تأثيره أكثر تأثيرًا.

func saveHandler(w http.ResponseWriter, r *http.Request) { 
    w.Header().Set("Access-Control-Allow-Origin", 
    r.Header.Get("Origin"))
    w.Header().Set("Access-Control-Allow-Credentials", 
    "true") 
}

إن عكس رأس Origin request والسماح لبيانات الاعتماد يسمح لموقع ويب مهاجم بتقديم طلبات كمستخدم تم تسجيل الدخول الحالي في المستخدم ، مما قد يؤدي إلى تسوية تطبيق الويب بأكمله.

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

/**
 * An `Access-Control-Allow-Credentials` header write.
 */
class AllowCredentialsHeaderWrite extends Http::HeaderWrite {
    AllowCredentialsHeaderWrite() {
        this.getHeaderName() = headerAllowCredentials()
    }
}

/**
 * predicate for CORS query.
 */
predicate allowCredentialsIsSetToTrue(DataFlow::ExprNode allowOriginHW) {
        exists(AllowCredentialsHeaderWrite allowCredentialsHW |
                allowCredentialsHW.getHeaderValue().toLowerCase() = "true"

هنا ، HTTP::HeaderWrite يتم استخدام الفصل ، كما تمت مناقشته سابقًا ، كطابق فائق ل AllowCredentialsHeaderWrite، الذي يجد كل كتابة الرأس عن القيمة Access-Control-Allow-Credentials. بعد ذلك ، عندما يتحقق استعلام CORS الخاطئين من تمكين بيانات الاعتماد ، فإننا نستخدم ALLEMREDENISATIONSEADERWRITE كأحد المصادر الممكنة للتحقق.

إن أبسط طريقة للمطورين لتعيين سياسة CORS هي إعداد الرؤوس على استجابات HTTP في خادمهم. من خلال نمذجة جميع الحالات التي يتم فيها تعيين الرأس ، يمكننا التحقق من هذه الحالات CORS في استعلام CORS لدينا.

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

أطراف النمذجة في codeql

جهاز كمبيوتر مع اثنين من Windows Open يعرض رمز آمن.

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

بالنسبة للأطر ، سننظر في Go Language في اختيارنا لأنها لديها دعم كبير للكورس. يوفر GO بضعة أطر عمل CORS ، ولكن معظمهم يتبعون بنية CORS Gin ، وهو إطار عمل CORS الوسيطة لإطار عمل GIN على الويب. فيما يلي مثال على تكوين الجن لكورز:

package main

import (
  "time"

  "github.com/gin-contrib/cors"
  "github.com/gin-gonic/gin"
)

func main() {
  router := gin.Default()
  router.Use(cors.New(cors.Config{
    AllowOrigins:     ()string{"https://foo.com"},
    AllowMethods:     ()string{"PUT", "PATCH"},
    AllowHeaders:     ()string{"Origin"},
    ExposeHeaders:    ()string{"Content-Length"},
    AllowCredentials: true,
    AllowOriginFunc: func(origin string) bool {
      return origin == "https://github.com"
    }
  }))
  router.Run()
}

الآن بعد أن صممنا على غرار router.Use طريقة و cors.New – ضمان ذلك cors.Config يتم وضع الهيكل في مرحلة ما في router.Use وظيفة للاستخدام الفعلي – يجب علينا بعد ذلك التحقق من الجميع cors.Config هياكل للرؤوس المناسبة.

بعد ذلك ، نجد حقول الرؤوس المناسبة التي نريد تصميمها. للحصول على استعلام سوء تكوين الكورس الأساسي ، سنصمم AllowOriginsو AllowCredentialsو AllowOriginFunc. يمكن استخدام طلبات السحب الخاصة بي لإضافة GinCors و RSCORS إلى CODEQL كمراجع إذا كنت مهتمًا برؤية كل ما يذهب إلى إضافة إطار عمل إلى CODEQL. أدناه سأناقش بعض أهم التفاصيل.

 /**
   * A variable of type Config that holds the headers to be set.
   */
  class GinConfig extends Variable {
    SsaWithFields v;

    GinConfig() {
      this = v.getBaseVariable().getSourceVariable() and
      v.getType().hasQualifiedName(packagePath(), "Config")
    }

    /**
     * Get variable declaration of GinConfig
     */
    SsaWithFields getV() { result = v }
  }

قمت بنمذجة نوع التكوين باستخدام ssawithfields ، وهو أ مهمة ثابتة واحدة مع الحقول. باستخدام getSourceVariable()، يمكننا الحصول على المتغير الذي تم تعيين الهيكل إليه ، والذي يمكن أن يساعدنا في معرفة مكان استخدام التكوين. يتيح لنا ذلك العثور على متغيرات المسار التي تحتوي على بنية تكوين CORS عبر قاعدة الشفرة ، بما في ذلك تلك التي يتم تهيئتها غالبًا على هذا النحو:

func main() {
...
// We can now track the corsConfig variable for further updates,such as when one of the fields is updated.
corsConfig:= cors.New(cors.Config{
...
})}

الآن بعد أن أصبح لدينا المتغير الذي يحتوي على الهيكل ذي الصلة ، نريد أن نجد جميع الحالات التي يتم فيها كتابة المتغير. من خلال القيام بذلك ، يمكننا فهم قيم الخصائص ذات الصلة التي تم تعيينها لها ، وبالتالي تحديد ما إذا كان تكوين CORS قد تم تكوينه.

 /**
   * A write to the value of Access-Control-Allow-Origins header
   */
  class AllowOriginsWrite extends UniversalOriginWrite {
    DataFlow::Node base;
	
	// This models all writes to the AllowOrigins field of the Config type
    AllowOriginsWrite() {

      exists(Field f, Write w |
        f.hasQualifiedName(packagePath(), "Config", "AllowOrigins") and
        w.writesField(base, f, this) and

		// To ensure we are finding the correct field, we look for a write of type string (SliceLit)
        this.asExpr() instanceof SliceLit
      )

    }

    /**
     * Get config variable holding header values
     */
    override GinConfig getConfig() {
      exists(GinConfig gc |
        (
          gc.getV().getBaseVariable().getDefinition().(SsaExplicitDefinition).getRhs() =
            base.asInstruction() or
          gc.getV().getAUse() = base
        ) and
        result = gc
      )
    }
  }

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

كتابة استفسارات تكوين سوء التكوين في codeql

يتم فصل مشكلات CORS إلى نوعين: أولئك الذين ليس لديهم أوراق اعتماد (حيث نبحث عن * أو NULL) و CORS مع بيانات الاعتماد (حيث نبحث عن انعكاس الأصل أو NULL). إذا كنت ترغب في الاحتفاظ باستعلام CODEQL بسيطًا ، فيمكنك إنشاء استعلام واحد لكل نوع من قابلية الضعف في CORS وتعيين شدته وفقًا لذلك. بالنسبة للغة GO ، يحتوي CodeQL فقط على نوع من الاستعلام “CORS مع بيانات الاعتماد” لأنه ينطبق على جميع التطبيقات.

دعنا نربط النماذج التي أنشأناها للتو أعلاه لنرى كيف تم استخدامها في استعلام Go Cors Disconfiguration نفسه.

from DataFlow::ExprNode allowOriginHW, string message
where
  allowCredentialsIsSetToTrue(allowOriginHW) and
  (
    flowsFromUntrustedToAllowOrigin(allowOriginHW, message)
    or
    allowOriginIsNull(allowOriginHW, message)
  ) and
  not flowsToGuardedByCheckOnUntrusted(allowOriginHW)
...
select allowOriginHW, message

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

/**
 * Holds if the provided `allowOriginHW` HeaderWrite's parent ResponseWriter
 * also has another HeaderWrite that sets a `Access-Control-Allow-Credentials`
 * header to `true`.
 */
predicate allowCredentialsIsSetToTrue(DataFlow::ExprNode allowOriginHW) {
  exists(AllowCredentialsHeaderWrite allowCredentialsHW |
    allowCredentialsHW.getHeaderValue().toLowerCase() = "true"
  |
    allowOriginHW.(AllowOriginHeaderWrite).getResponseWriter() =
      allowCredentialsHW.getResponseWriter()
  )
  or
...

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

  exists(UniversalAllowCredentialsWrite allowCredentialsGin |
    allowCredentialsGin.getExpr().getBoolValue() = true
  |
    allowCredentialsGin.getConfig() = allowOriginHW.(UniversalOriginWrite).getConfig() and
    not exists(UniversalAllowAllOriginsWrite allowAllOrigins |
      allowAllOrigins.getExpr().getBoolValue() = true and
      allowCredentialsGin.getConfig() = allowAllOrigins.getConfig()
    )
    or
    allowCredentialsGin.getBase() = allowOriginHW.(UniversalOriginWrite).getBase() and
    not exists(UniversalAllowAllOriginsWrite allowAllOrigins |
      allowAllOrigins.getExpr().getBoolValue() = true and
      allowCredentialsGin.getBase() = allowAllOrigins.getBase()
    )
  )
}

إذا لم يتم تعيين CORS من خلال رأس ، فإننا نتحقق من استخدام أطر عمل CORS UniversalAllowCredentialsWrite. لتصفية جميع الحالات التي يتم تعيين قيمة أصلها المقابلة على “*” ، نستخدم not الكلمة الرئيسية CODEQL على UniversalAllowAllOriginsWrite، لأن هذه لا تنطبق على هذه الضعف. flowsFromUntrustedToAllowOrigin و allowOriginIsNull اتبع منطقًا مشابهًا للتأكد من أن حقوق الرأس الناتجة ضعيفة.

عندما تقوم بتصميم استفسارات CodeQL للكشف عن نقاط الضعف المتعلقة بـ CORS ، لا يمكنك استخدام نهج يناسب الجميع. بدلاً من ذلك ، يجب عليك تخصيص استفساراتك مع كل إطار ويب لسببين:

  • كل إطار ينفذ سياسات الكورس بطريقتها الخاصة
  • تعتمد أنماط الضعف على سلوك الإطار

على سبيل المثال ، رأينا من قبل في Gin Cors أن هناك AllowOriginFunc. بعد النظر في الوثائق أو تجربة الكود ، يمكننا أن نرى أنه قد يتغلب AllowOrigins. لتحسين استفسارنا ، يمكننا كتابة استعلام CODEQL الذي يبحث عنه AllowOriginFuncS التي تعود دائمًا إلى حد ما ، والتي ستؤدي إلى ضعف شدة في حالة إقرانها بأوراق الاعتماد.

خذ هذا معك

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

إذا كانت هذه المدونة مفيدة في مساعدتك في كتابة استفسارات CodeQL ، فلا تتردد في فتح أي شيء ترغب في مشاركته مع المجتمع فينا CodeQl Community Packs.

أخيراً، GitHub Code Security يمكن أن تساعدك في تأمين مشروعك من خلال اكتشاف وقوع إصلاح للأخطاء مثل كورس سوء التكوين!

استكشف المزيد Github Security Lab منشورات المدونة>

كتبه

كيفن Stubbings

Source link


اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *