icon_firefox[CVE-2016-1941] Delay following click events in file download dialog too short on OS X


Delay following click events in file download dialog too short on OS X


Announced: January 26, 2016
Reporter: Jordi Chancel
Impact: Moderate
Products: Firefox
Fixed in: Firefox 44


Description

Security researcher Jordi Chancel reported an issue on OS X where the delay between the download dialog getting focus and the button getting enabled was too short. If an attacker is able to induce the user to double-click in a specific location, they can then pass the second click through to the dialog below, leading to unintentional actions such as the running of downloaded software.


  • Introduction :

Cette nouvelle vulnérabilité de type ClickJacking est une variante d’une vulnérabilité préalablement reportée par moi-même et ayant été corrigée par la version 27 de Firefox qui avait été mise en avant dans leur avis de sécurité MFSA-2014-03 et dans le CVE-2014-1480.
Cette précédente vulnérabilité permettait également d’effectuer des attaques de type ClickJacking sur la boite de dialogue de téléchargement autorisant l’exécution d’un fichier directement après que celui-ci soit téléchargé.

L’impact et l’interaction utilisateur finale de cette 2ème vulnérabilité MFSA-2016-08 (corrigée dans la version 44 de Firefox) permettant l’exécution du fichier malveillant sont exactement similaires à la précédente vulnérabilité MFSA-2014-03 (corrigée dans la version 27 de Firefox).

  • Concept en image :

1.
Images démontrant la superposition entre le bouton « DoubleClickMe » contenu dans la fenêtre cachant la boite de téléchargement et le bouton utilisé par la boite de téléchargement permettant l’exécution du fichier malveillant



  • Exploitation et correction de MFSA-2014-03

1. Exploitation

L’exploitation de la vulnérabilité MFSA-2014-03 utilisait un défaut de sécurité autorisant un clique direct sur le bouton de la boite de téléchargement activant l’exécution automatique du fichier malveillant après que celui-ci soit téléchargé.
L’exploitation de MFSA-2016-08 utilise une faille de sécurité que le Patch de MFSA-2014-03 n’avait pas corrigé.

2. Correction

Le Patch correctif de la vulnérabilité MFSA-2014-03 ajoutait un temps de sécurité qui ne permettait plus à l’utilisateur de cliquer directement sur le bouton activant le téléchargement et l’exécution du fichier téléchargé avant que ce laps de temps puisse autoriser l’utilisateur à effectuer cette interaction.
(ce qui par conséquent corrigeait la 1ère vulnérabilité MFSA-2014-03 du fait que le clique sur le bouton désiré ne soit plus directement possible sans attendre que le laps de temps de sécurité débloque l’utilisation de ce bouton, de sorte que l’utilisateur décide lui-même ou non de cliquer sur le bouton de téléchargement et lui permette également de se rendre compte de l’ouverture de la boite de téléchargement sans que celui-ci puisse activer le téléchargement d’une façon non désirée).

  • Exploitation et correction de MFSA-2016-08

1. Exploitation (Ceci est une ébauche et sera révisée rapidement pour une meilleur compréhension et explication de l’exploitation concernée dans cette partie de l’article)

L’exploitation de MFSA-2016-08 utilise une défaut de sécurité qui n’avait pas été prévu dans l’activation du délais de sécurité mise en place dans le Patch de MFSA-2014-03. En effet, le Patch de la vulnérabilité MFSA-2014-03 définissait aussi que ce délais de sécurité soit mise en attente dans le cas ou la Boîte de Dialogue de Téléchargement (menant à l’exécution du fichier téléchargé) ne soit plus sélectionné au premier plan et que ce délais de sécurité ne puisse être réactivé uniquement dans le cas où la Boîte de Dialogue permettant l’exécution du fichier téléchargé ne soit à nouveau sélectionné au premier plan.
Cependant, dans un cas bien précis, la réactivation de ce délais de sécurité n’est pas effectué lorsque la Boîte de Dialogue de téléchargement est à nouveau remise au premier plan.
En effet ce délais de sécurité ne pouvait pas être réactivé dans le cas ou un élément (fenêtre de Firefox ou autre) surplombait la Boîte de Dialogue de téléchargement et que cette élément utilisait la fonction JavaScript « window.close(); » déclenchant ainsi l’événement « onUnload(); », ce qui avait comme conséquence de Bypasser le délais de sécurité précédemment mise en place dans le Patch de MFSA-2014-03 et donc de permettre à l’utilisateur d’activer de façon non désirée le téléchargement et l’exécution du fichier téléchargé après que le deuxième clique du « Double-Clique » nécessaire à l’exploitation de cette vulnérabilité puisse autoriser un clique sur le bouton de téléchargement
(qui était normalement soumis au délais de sécurité mais dont la mise en route ne pouvait s’effectuer du fait que l’événement JavaScript « onUnload(); » passait outre la sécurité mise en place dans le Patch de la vulnérabilité MFSA-2014-03).




2. Correction

(Le reste de l’article est en cours d’écriture et sera très bientôt disponible dans sa totalité. Veuillez revenir d’ici peu.)

Dans le Patch correctif de la vulnérabilité MFSA-2016-08 plusieurs codes ont été supprimés et d’autres ont été remplacés dans les librairies liés à la boite de dialogue de téléchargent et à l’activation/mise en attente/réactivation du délais de sécurité du bouton permettant le téléchargement et l’exécution du fichier malveillant de cette boite de dialogue

(a) Librairies de Firefox sur lesquelles des suppression de code on été appliqués sur le Patch correctif de la vulnérabilité MFSA-2016-08

Codes Supprimés, Ajoutés et Modifiés dans les librairies corrigeant la vulnérabilité MFSA-2016-08:


Lines 14-30 +++b/toolkit/mozapps/downloads/content/unknownContentType.xul
14 %uctDTD;
15 <!ENTITY % scDTD SYSTEM "chrome://mozapps/locale/downloads/settingsChange.dtd" >
16 %scDTD;
17 ]>
18
19 <dialog id="unknownContentType"
20 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
21 onload="dialog.initDialog();" onunload="if (dialog) dialog.onCancel();"
22 onblur="if (dialog) dialog.onBlur(event);" onfocus="dialog.onFocus(event);"
23 #ifdef XP_WIN
24 style="width: 36em;"
25 #else
26 style="width: 34em;"
27 #endif
28 screenX="" screenY=""
29 persist="screenX screenY"
30 aria-describedby="intro location whichIs type from source unknownPrompt"

Lines 3-18 +++b/toolkit/mozapps/downloads/nsHelperAppDlg.js
3 /*
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 */
8
9 const {utils: Cu, interfaces: Ci, classes: Cc, results: Cr} = Components;
10 Cu.import("resource://gre/modules/Services.jsm");
11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
12 XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
13 "resource://gre/modules/SharedPromptUtils.jsm");
14
15 ///////////////////////////////////////////////////////////////////////////////
16 //// Helper Functions
17
18 /**
19 * Determines if a given directory is able to be used to download to.
20 *
21 * @param aDirectory

Lines 534-568 nsUnknownContentTypeDialog.prototype = {
537 var openHandler = this.dialogElement("openHandler");
538 openHandler.parentNode.removeChild(openHandler);
539 var openHandlerBox = this.dialogElement("openHandlerBox");
540 openHandlerBox.appendChild(openHandler);
541 }
542
543 this.mDialog.setTimeout("dialog.postShowCallback()", 0);
544
545 this.delayHelper = new EnableDelayHelper({
546 disableDialog: () => {
547 this.mDialog.document.documentElement.getButton("accept").disabled = true;
548 },
549 enableDialog: () => {
550 this.mDialog.document.documentElement.getButton("accept").disabled = false;
551 },
552 focusTarget: this.mDialog
553 });
554 },
555
556 notify: function (aTimer) {
557 if (aTimer == this._showTimer) {
558 if (!this.mDialog) {
559 this.reallyShow();
* } else {
* // The user may have already canceled the dialog.
* try {
* if (!this._blurred) {
* this.mDialog.document.documentElement.getButton("accept").disabled = false;
* }
* } catch (ex) {}
* this._delayExpired = true;
560 }
561 // The timer won't release us, so we have to release it.
562 this._showTimer = null;
563 }
564 else if (aTimer == this._saveToDiskTimer) {
565 // Since saveToDisk may open a file picker and therefore block this routine,
566 // we should only call it once the dialog is closed.
567 this.mLauncher.saveToDisk(null, false);


Lines 636-666 nsUnknownContentTypeDialog.prototype = {
635 .getFormattedString("orderedFileSizeWithType",
636 [typeString, size, unit]);
637 }
638 else {
639 type.value = typeString;
640 }
641 },
642
* _blurred: false,
* _delayExpired: false,
* onBlur: function(aEvent) {
* this._blurred = true;
* this.mDialog.document.documentElement.getButton("accept").disabled = true;
* },
* onFocus: function(aEvent) {
* this._blurred = false;
* if (this._delayExpired) {
* var script = "document.documentElement.getButton('accept').disabled = false";
* this.mDialog.setTimeout(script, 250);
* }
* },
*
643 // Returns true if opening the default application makes sense.
644 openWithDefaultOK: function() {
645 // The checking is different on Windows...
646 #ifdef XP_WIN
647 // Windows presents some special cases.
648 // We need to prevent use of "system default" when the file is
649 // executable (so the user doesn't launch nasty programs downloaded
650 // from the web), and, enable use of "system default" if it isn't

+++b/toolkit/components/prompts/src/CommonDialog.jsm (-45 / +8 lines)
5 this.EXPORTED_SYMBOLS = ["CommonDialog"];
6
7 const Ci = Components.interfaces;
8 const Cr = Components.results;
9 const Cc = Components.classes;
10 const Cu = Components.utils;
11
12 Cu.import("resource://gre/modules/Services.jsm");
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
14 XPCOMUtils.defineLazyModuleGetter(this, "EnableDelayHelper",
15 "resource://gre/modules/SharedPromptUtils.jsm");
16
17
18 this.CommonDialog = function CommonDialog(args, ui) {
19 this.args = args;
20 this.ui = ui;
21 }
22
23 CommonDialog.prototype = {

CommonDialog.prototype = {
159 xulDialog.defaultButton = ['accept', 'cancel', 'extra1', 'extra2'][b];
160 else
161 button.setAttribute("default", "true");
162
163 // Set default focus / selection.
164 this.setDefaultFocus(true);
165
166 if (this.args.enableDelay) {
167 this.delayHelper = new EnableDelayHelper({
168 disableDialog: () => this.setButtonsEnabledState(false),
169 enableDialog: () => this.setButtonsEnabledState(true),
170 focusTarget: this.ui.focusTarget
171 });
172 }
173
174 // Play a sound (unless we're tab-modal -- don't want those to feel like OS prompts).
175 try {
176 if (xulDialog && this.soundID) {
177 Cc["@mozilla.org/sound;1"].
178 createInstance(Ci.nsISound).
179 playEventSound(this.soundID);

CommonDialog.prototype = {
225
226 setButtonsEnabledState : function(enabled) {
227 this.ui.button0.disabled = !enabled;
228 // button1 (cancel) remains enabled.
229 this.ui.button2.disabled = !enabled;
230 this.ui.button3.disabled = !enabled;
231 },
232
* onBlur : function (aEvent) {
* if (aEvent.target != this.ui.focusTarget)
* return;
* this.setButtonsEnabledState(false);
*
* // If we blur while waiting to enable the buttons, just cancel the
* // timer to ensure the delay doesn't fire while not focused.
* if (this.focusTimer) {
* this.focusTimer.cancel();
* this.focusTimer = null;
* }
* },
*
* onFocus : function (aEvent) {
* if (aEvent.target != this.ui.focusTarget)
* return;
* this.startOnFocusDelay();
* },
*
* startOnFocusDelay : function(delayTime) {
* // Shouldn't already have a timer, but just in case...
* if (this.focusTimer)
* return;
* // If no delay specified, use 250ms. (This is the normal case for when
* // after the dialog has been opened and focus shifts.)
* if (!delayTime)
* delayTime = 250;
* let self = this;
* this.focusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
* this.focusTimer.initWithCallback(function() { self.onFocusTimeout(); },
* delayTime, Ci.nsITimer.TYPE_ONE_SHOT);
* },
*
* onFocusTimeout : function() {
* this.focusTimer = null;
* this.setButtonsEnabledState(true);
* },
*
271 setDefaultFocus : function(isInitialLoad) {
272 let b = (this.args.defaultButtonNum || 0);
273 let button = this.ui["button" + b];
274
275 if (!this.hasInputField) {
276 let isOSX = ("nsILocalFileMac" in Components.interfaces);
277 if (isOSX)
278 this.ui.infoBody.focus();

+++b/toolkit/components/prompts/src/SharedPromptUtils.jsm
1 this.EXPORTED_SYMBOLS = [ "PromptUtils", "EnableDelayHelper" ];
2
3 const Cc = Components.classes;
4 const Ci = Components.interfaces;
5 const Cr = Components.results;
6 const Cu = Components.utils;
7
8 Cu.import("resource://gre/modules/Services.jsm");
9
10 this.PromptUtils = {
11 // Fire a dialog open/close event. Used by tabbrowser to focus the
12 // tab which is triggering a prompt.
13 // For remote dialogs, we pass in a different DOM window and a separate
14 // target. If the caller doesn't pass in the target, then we'll simply use
15 // the passed-in DOM window.
16 fireDialogEvent : function (domWin, eventName, maybeTarget) {
17 let target = maybeTarget || domWin;

this.PromptUtils = {
37 // Here we iterate over the object's original properties, not the bag
38 // (ie, the prompt can't return more/different properties than were
39 // passed in). This just helps ensure that the caller provides default
40 // values, lest the prompt forget to set them.
41 for (let propName in obj)
42 obj[propName] = propBag.getProperty(propName);
43 },
44 };
45
46 /**
47 * This helper handles the enabling/disabling of dialogs that might
48 * be subject to fast-clicking attacks. It handles the initial delayed
49 * enabling of the dialog, as well as disabling it on blur and reapplying
50 * the delay when the dialog regains focus.
51 *
52 * @param enableDialog A custom function to be called when the dialog
53 * is to be enabled.
54 * @param diableDialog A custom function to be called when the dialog
55 * is to be disabled.
56 * @param focusTarget The element used to watch focus/blur events.
57 */
58 this.EnableDelayHelper = function({enableDialog, disableDialog, focusTarget}) {
59 this.enableDialog = makeSafe(enableDialog);
60 this.disableDialog = makeSafe(disableDialog);
61 this.focusTarget = focusTarget;
62
63 this.disableDialog();;
64
65 this.focusTarget.addEventListener("blur", this, false);
66 this.focusTarget.addEventListener("focus", this, false);
67 this.focusTarget.ownerDocument.addEventListener("unload", this, false);
68
69 this.startOnFocusDelay();
70 };
71
72 this.EnableDelayHelper.prototype = {
73 get delayTime() {
74 return Services.prefs.getIntPref("security.dialog_enable_delay");
75 },
76
77 handleEvent : function(event) {
78 if (event.target != this.focusTarget &&
79 event.target != this.focusTarget.ownerDocument)
80 return;
81
82 switch (event.type) {
83 case "blur":
84 this.onBlur();
85 break;
86
87 case "focus":
88 this.onFocus();
89 break;
90
91 case "unload":
92 this.onUnload();
93 break;
94 }
95 },
96
97 onBlur : function () {
98 this.disableDialog();
99 // If we blur while waiting to enable the buttons, just cancel the
100 // timer to ensure the delay doesn't fire while not focused.
101 if (this._focusTimer) {
102 this._focusTimer.cancel();
103 this._focusTimer = null;
104 }
105 },
106
107 onFocus : function () {
108 this.startOnFocusDelay();
109 },
110
111 onUnload: function() {
112 this.focusTarget.removeEventListener("blur", this, false);
113 this.focusTarget.removeEventListener("focus", this, false);
114 this.focusTarget.ownerDocument.removeEventListener("unload", this, false);
115
116 if (this._focusTimer) {
117 this._focusTimer.cancel();
118 this._focusTimer = null;
119 }
120
121 this.focusTarget = this.enableDialog = this.disableDialog = null;
122 },
123
124 startOnFocusDelay : function() {
125 if (this._focusTimer)
126 return;
127
128 this._focusTimer = Cc["@mozilla.org/timer;1"]
129 .createInstance(Ci.nsITimer);
130 this._focusTimer.initWithCallback(
131 () => { this.onFocusTimeout(); },
132 this.delayTime,
133 Ci.nsITimer.TYPE_ONE_SHOT
134 );
135 },
136
137 onFocusTimeout : function() {
138 this._focusTimer = null;
139 this.enableDialog();
140 },
141 };
142
143 function makeSafe(fn) {
144 return function () {
145 // The dialog could be gone by now (if the user closed it),
146 // which makes it likely that the given fn might throw.
147 try {
148 fn();
149 } catch (e) { }
150 };
151 }




Vulnerability demonstration (video):


Comments are closed.