اذهب إلى المحتوى

هجمات الاختطاف بالنقر Clickjacking في جافاسكربت


ابراهيم الخضور

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

فكرة الهجوم

إن فكرة الهجوم بسيطة جدًا، فقد كانت طريقة الهجوم على فيسبوك مثلًا كما يلي:

  1. جُذب المستخدم إلى الصفحة المشبوهة بطريقة ما.
  2. احتوت الصفحة رابطًا لا يبدو خطرًا بعناوين، مثل: "طريقك إلى الثروة"، أو "انقر هنا، أمر مضحك جدًا".
  3. وضعت الصفحة المشبوهة فوق هذا الرابط نافذةً ضمنيةً <iframe> شفافة ترتبط خاصية src فيه بالموقع "facebook.com"، بحيث ظهر زر "أعجبني" فوق الرابط مباشرةً، وعادة يُنفَّذ ذلك باستخدام الخاصية z-index.
  4. وبالتالي نقر الزائر على الزر عندما حاول النقر على الرابط.

مثال توضيحي

ستبدو الصفحة المشبوهة كالتالي، مع الانتباه إلى أنّ النافذة الضمنية <iframe> هنا نصف شفافة،أما في الصفحات المشبوهة فستكون النافذة الضمنية شفافةً تمامًا:

<style>
iframe { /* النافذة الضمنية من الصفحة الضحية */
  width: 400px;
  height: 100px;
  position: absolute;
  top:0; left:-20px;
  opacity: 0.5; /* الشفافية 0 في الصفحة المشبوهة */
  z-index: 1;
}
</style>

<div>Click to get rich now:</div>

<!-- العنوان على الصفحة الضحية -->
<iframe src="/clickjacking/facebook.html"></iframe>

<button>Click here!</button>

<div>...And you're cool (I'm a cool hacker actually)!</div>

يحوي الملف "facebook.html" الشيفرة التالية:

<!DOCTYPE HTML>
<html>

<body style="margin:10px;padding:10px">

  <input type="button" onclick="alert('Like pressed on facebook.html!')" value="I LIKE IT !">

</body>

</html>

ويحوي الملف "index.html" الشيفرة التالية:

<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>

<body>

  <style>
    iframe {
      width: 400px;
      height: 100px;
      position: absolute;
      top: 5px;
      left: -14px;
      opacity: 0.5;
      z-index: 1;
    }
  </style>

  <div>Click to get rich now:</div>

  <!-- The url from the victim site -->
  <iframe src="facebook.html"></iframe>

  <button>Click here!</button>

  <div>...And you're cool (I'm a cool hacker actually)!</div>

</body>
</html>

ستظهر النتيجة كالتالي:

تظهر النافذة الضمنية <"iframe src="facebook.html> في المثال السابق نصف شفافة، بحيث يمكن رؤيتها عند تحريك المؤشر فوق الزر، وعند النقر على الزر فإننا ننقر في الواقع على النافذة الضمنية التي ستكون مخفيّةً كونها شفافةً تمامًا. فإن أمكن للمستخدم الوصول إلى حسابه مباشرةً دون الحاجة لتسجيل الدخول (عندما تكون ميزة "تذكرني remember me" مفعلة)، فسيضيف النقر على النافذة الضمنية إعجابًا Like، بينما سينقر في تويتر على زر المتابعة Follow.

إليك نتيجة المثال السابق لكن بواقعية أكثر عندما نسند القيمة "0" إلى الخاصية opacity:

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

اقتباس

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

الأسلوب الدفاعي للمدرسة التقليدية

يعود جزء من الآلية الدفاعية القديمة إلى JavaScript التي تمنع فتح الصفحة في نافذة ضمنية، وهو ما يُعرف بكسر الإطار framebusting، ويبدو الأمر كالتالي:

if (top != window) {
  top.location = window.location;
}

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

منع الانتقال إلى الأعلى

يُمكن منع الانتقال الناتج عن تغيير قيمة الخاصية top.location في معالج الحدث beforeunload. حيث ستحدد الصفحة الموجودة في الأعلى (مرفقة بالصفحة التي تعود للمتسلل) معالجًا يمنع الانتقال إليها كالتالي:

window.onbeforeunload = function() {
  return false;
};

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

سنرى في المثال التالي تنفيذ الفكرة عمليًا، حيث يحتوي الملف "iframe.html" الشيفرة التالية:

<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>

<body>

  <div>Changes top.location to javascript.info</div>

  <script>
    top.location = 'https://javascript.info';
  </script>

</body>

</html>

ويحتوي الملف "index.html" الشيفرة التالية:

<!doctype html>
<html>

<head>
  <meta charset="UTF-8">

  <style>
    iframe {
      width: 400px;
      height: 100px;
      position: absolute;
      top: 0;
      left: -20px;
      opacity: 0;
      z-index: 1;
    }
  </style>

  <script>
    function attack() {

      window.onbeforeunload = function() {
        window.onbeforeunload = null;
        return "Want to leave without learning all the secrets (he-he)?";
      };

      document.body.insertAdjacentHTML('beforeend', '<iframe src="iframe.html">');
    }
  </script>
</head>

<body>

  <p>After a click on the button the visitor gets a "strange" question about whether they want to leave.</p>

  <p>Probably they would respond "No", and the iframe protection is hacked.</p>

  <button onclick="attack()">Add a "protected" iframe</button>

</body>
</html>

وستكون النتيجة كما يلي:

الخاصية Sandbox

تقيِّد الخاصية sandbox عدة أشياء منها عملية التنقل، حيث لا يمكن للنافذة الضمنية المقيّدة تغيير قيمة top.location، ولذلك يمكننا استخدام هذه الخاصية لتخفيف القيود بالشكل sandbox="allow-scripts allow-forms"، وهكذا سنسمح باستخدام النماذج والسكربتات، لكن في الوقت نفسه لا نسمح بالتنقل عندما لا نضيف الخيار allow-top-navigation، وبالتالي سنمنع تغيير القيمة top.location.

والشيفرة المستخدمة في ذلك هي:

<iframe sandbox="allow-scripts allow-forms" src="facebook.html"></iframe>

ويمكن طبعًا الالتفاف على أسلوب الحماية البسيط هذا.

خيارات X-Frame

يمكن للترويسة X-Frame-Options المتعلقة بالواجهة الخلفية أن تسمح بعرض صفحة ضمن نافذة ضمنية أو تمنعه، كما ينبغي أن تُرسَل على شكل ترويسة HTTP حصرًا، إذ سيتجاهلها المتصفح إن وجدها ضمن العنصر meta، أي ستكون الشيفرة التالية بلا فائدة:

<meta http-equiv="X-Frame-Options">

يمكن أن تأخذ الترويسة إحدى القيم التالية:

  • DENY: لن تظهر الصفحة ضمن نافذة ضمنية أبدًا.
  • SAMEORIGIN: يمكن أن تُعرَض الصفحة ضمن نافذة ضمنية إن كان للصفحة الأم وللنافذة الضمنية الأصل نفسه.
  • ALLOW-FROM domain: يمكن أن تُعرَض الصفحة داخل نافذة ضمنية إن انتمت الصفحة الأم إلى نطاق محدد سلفًا.

مثلًا: يستخدم موقع تويتر الخيار X-Frame-Options: SAMEORIGIN بحيث تكون النتيجة نافذةً ضمنيةً فارغةـ وتكون النتيجة:

<iframe src="https://twitter.com"></iframe>

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

إظهار النافذة بوظيفة معطلة

للترويسة X-Frame-Options تأثير جانبي، حيث لن تتمكن الصفحات الأخرى من عرض صفحتنا ضمن نافذة ضمنية حتى لو كان ذلك لسبب وجيه، لذلك سنجد حلولًا أخرى مثل تغطية الصفحة بعنصر <div> له التنسيق: height: 100%; width: 100%;‎، وعندها سيعترض كل نقرات الفأرة، ثم يُزال هذا العنصر عندما يتحقق الشرط window == top، أو عندما لا نجد مبررًا لحماية الصفحة.

سيظهر الأمر كالتالي:

<style>
  #protector {
    height: 100%;
    width: 100%;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 99999999;
  }
</style>

<div id="protector">
  <a href="/" target="_blank">Go to the site</a>
</div>

<script>
  // there will be an error if top window is from the different origin
// سيحدث خطأ إذا كانت النافذة العليا من مصدر مختلف، لكن هنا لا مشكلة
  // but that's ok here
  if (top.document.domain == document.domain) {
    protector.remove();
  }
</script>

يوضح المثال التالي ما فعلناه، حيث يحتوي الملف "iframe.html" على الشيفرة التالية:

<!doctype html>
<html>

<head>
  <meta charset="UTF-8">

  <style>
    #protector {
      height: 100%;
      width: 100%;
      position: absolute;
      left: 0;
      top: 0;
      z-index: 99999999;
    }
  </style>

</head>

<body>

<div id="protector">
  <a href="/" target="_blank">Go to the site</a>
</div>

<script>

  if (top.document.domain == document.domain) {
    protector.remove();
  }

</script>

  This text is always visible.

  But if the page was open inside a document from another domain, the div over it would prevent any actions.

  <button onclick="alert(1)">Click wouldn't work in that case</button>

</body>
</html>

وسيحتوي الملف "index.html" على هذه الشيفرة:

<!doctype html>
<html>

<head>
  <meta charset="UTF-8">
</head>
<body>

  <iframe src="iframe.html"></iframe>

</body>
</html>

وستظهر النتيجة كالتالي:

الخاصية samesite لملف تعريف الارتباط

يمكن للخاصية samesite أن تمنع هجوم الاختطاف بالنقر، حيث لن يُرسَل ملف تعريف الارتباط cookie الذي يحملها إلى أي موقع إلا إذا فُتح مباشرةً، وليس عن طريق نافذة ضمنية أو أي شيء مشابه. يمكن الاطلاع على معلومات أكثر ضمن مقال "ملفات تعريف الارتباط وضبطها في مستندات JavaScript".

فلو امتلك موقع مثل فيسبوك الخاصية samesite ضمن ملف تعريف الارتباط على الشكل التالي:

Set-Cookie: authorization=secret; samesite

فلن يُرسل ملف تعريف الارتباط عندما يُفتح فيسبوك في نافذة ضمنية عائدة لموقع آخر، وبالتالي سيخفق الهجوم.

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

الخلاصة

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

لا يعَد الهجوم "عميقًا" من ناحية أولية، فكل ما يفعله المخترق هو اعتراض نقرة الفأرة، لكن من ناحية أخرى إذا علم المخترق بظهور عملية أخرى بعد النقر، فربما يستخدم رسائل ماكرةً تجبر المستخدم على النقر عليها أيضًا، وهنا سيكون الهجوم خطرًا، إذ لا نخطط لاعتراض هذه الأفعال عند تصميم واجهة المستخدم UI، وقد تظهر نقاط الضعف في أماكن لا يمكن توقعها أبدًا. لهذايُنصح باستخدام الأمر X-Frame-Options: SAMEORIGIN ضمن الصفحات أو المواقع التي لا نريد عرضها ضمن نوافذ ضمنية، واستخدام العنصر <div> لتغطية الصفحة عند عرضها ضمن نوافذ ضمنية، ومع ذلك يجب توخي الحذر.

ترجمة -وبتصرف- للفصل clickjacking attack من سلسلة The Modern JavaScript Tutorial.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...