Disons que nous avons une Java application web qui utilise un secret partagé pour vérifier l'identité du client. Le secret est stocké sur le serveur, et le client transmet le secret sur SSL où il est vérifié:
String SECRET_ON_SERVER = "SomeLongRandomValue";
if (secretFromClient.equals(SECRET_ON_SERVER)) {
// request verified - client knows the secret
} else {
// request not verified - generate failed response
}
String.equals(String)
renvoie dès qu'un seul caractère ne correspond pas. Cela signifie qu'un attaquant, s'il peut suivre avec précision le temps nécessaire pour répondre, devrait théoriquement savoir combien de caractères de sa tentative - secretFromClient
- correspondent au secret du serveur, conduisant à une attaque par force brute plausible.
Mais la différence de timing semble être minuscule . Une enquête rapide suggère que les différences sont facilement inférieures à la milliseconde.
En théorie, c'est un exploit possible, et si vous êtes en mode super-paranoïa, vous devez supposer que la réponse est "Oui". Dans tous les autres cas, la réponse sera: "Non.".
Bien qu'il existe des articles publiés (dont un est lié dans la réponse de @Oasiscircle) qui affirment qu'ils sont capables de lancer des attaques de synchronisation réussies, il faut également lire attentivement les conditions préalables. Ces attaques "pratiques" publiées fonctionnent sur certains algorithmes sur un LAN avec un, au plus deux, bascule entre les deux. Ce qui implique un temps d'aller-retour constant, presque parfaitement fiable. Pour ce scénario, il est en effet pratique d'attaquer certains algorithmes via le timing, mais cela n'a aucun sens dans le contexte de la question.
En fait, je considère ces attaques à distance comme "tricher" . Le fait qu'une attaque soit à distance est sans importance si vous concevez soigneusement l'expérience afin que le retard soit néanmoins presque exactement prévisible.
Lorsque vous attaquez un serveur sur Internet, cette condition préalable ne tient pas (pas même à distance, le jeu de mots voulu ), même sur un serveur géographiquement et topologiquement proche.
En outre, attaquer une comparaison de chaînes via le timing n'est pas du tout la même chose que d'attaquer un calcul RSA. C'est beaucoup plus difficile car l'ensemble de l'opération ainsi que la différence mesurable sont beaucoup plus petits.
Une comparaison de chaînes d'un mot de passe (en supposant que vos mots de passe sont de taille "raisonnable") prend quelques centaines de cycles ou moins, dont l'éventuel échec initial du cache/TLB est de loin le facteur dominant le plus important, suivi de la branche terminal mal interprétée (qui se produit à la fois pour un match et un non-match). La différence entre une correspondance et une non-correspondance est peut-être d'une ou deux douzaines de nanosecondes.
Un changement de contexte prend plusieurs centaines de nanosecondes, tout comme un cache manquant. Les programmateurs fonctionnent généralement à une résolution de micro ou de millisecondes et effectuent un travail très non trivial (dans les centaines/milliers de nanosecondes) entre les moments difficiles à prévoir pour le moins.
Mesurer de manière fiable les différences à l'échelle nanoseconde n'est pas non plus entièrement trivial. Les temporisateurs programmables ordinaires n'ont presque pas la résolution requise. HPET sur le matériel de base est garanti pour fournir une résolution de 100 ns (par spécification) et dans la pratique, il descend à 1 ns sur de nombreuses implémentations. Cependant, cela fonctionne en générant une interruption . Cela signifie que vous pouvez planifier une minuterie à un moment précis avec une nanoseconde, mais vous ne pouvez pas vraiment l'utiliser pour mesurer des nanosecondes simples. De plus, l'interruption ajoute une surcharge et une incertitude de quelques dizaines de nanosecondes (... à quelques dizaines de nanosecondes que vous souhaitez mesurer!). Les compteurs de cycle doivent être sérialisés pour être précis. Ce qui les rend également plutôt inutiles pour mesurer avec précision un événement externe à une résolution en nanosecondes, car leur précision dépend de l'apparence du pipeline.
Il y a d'autres choses à considérer qui ajoutent du bruit imprévisible, comme les utilisateurs légitimes (oui, ils existent aussi!) Et interrompent la fusion.
Essayer de deviner quelque chose de nano à partir d'échantillons qui incluent plusieurs quelque chose de différent-nano ainsi que quelque chose de micro et plusieurs quelque chose de milli est une tâche herculéenne. C'est le bruit de plusieurs sources indépendantes à toutes les échelles.
Enfin, considérez la mention de "Java", ce qui signifie que par ex. un garbage collector peut être exécuté à un moment imprévisible (en tout cas, imprévisible pour un attaquant distant), provoquant une gigue imprévisible à une échelle inconnue (micro, milli?).
En théorie, vous pourriez bien sûr collecter un grand nombre d'échantillons, même à une résolution inférieure, disons à l'échelle de la microseconde, et éliminer statistiquement les différentes sources de bruit. Vous ne pourriez jamais dire avec certitude si un mot de passe est correct, mais vous pourrez éventuellement le dire avec une probabilité suffisamment élevée (disons 85% ou 90%, voire 99%), et vous pouvez ensuite vérifier manuellement ceux peu de candidats. Il est assez bien!
C'est possible , au moins en théorie, mais cela prendrait un énorme nombre d'échantillons même pour deviner un seul mot de passe. Et dire "énorme" est vraiment un euphémisme des proportions galactiques. Le nombre d'échantillons nécessaires implique pratiquement que vous devez paralléliser l'attaque, sinon cela prendra une éternité.
Maintenant, paralléliser sérieusement une telle attaque temporelle n'est pas facilement possible parce que vous êtes soumis à l'effet d'observateur (au même sens qu'en mécanique quantique).
. façon. Il n'y a rien que vous puissiez faire pour empêcher que cela se produise, donc la parallélisation ne fonctionne pas vraiment bien (je ne prends même pas en compte le fait que les interruptions passent généralement sur un seul cœur et qu'il n'y a qu'un seul fil de cuivre physique qui doit passer, donc même si le serveur a encore des cœurs inactifs, il peut très probablement être le cas qu'une sonde en affecte une autre).
D'un autre côté, l'exécution d'une attaque non parallèle massivement est vouée à l'échec car vous mourrez de vieillesse avant de trouver un mot de passe unique.
Il existe des succès publiés avec des attaques de synchronisation à distance . D'après le document - "... nous pouvons distinguer de manière fiable les différences de synchronisation à distance aussi faibles que 20µs." Alors oui, vous devriez vous inquiéter du sous-jacent implémentation de .equals()
(spoiler: non sécurisé). Implémentez .equals()
en utilisant une somme de XOR de caractères à comparer de manière indépendante du timing).
Voici une implémentation python comme exemple de comparaison d'octets indépendante du timing).
def equals(bytes1, bytes2):
if len(bytes1) != len(bytes2):
return False
else:
differences = 0
for a, b in Zip(bytes1, bytes2):
differences |= a ^ b
return differences == 0
Stocker un bon hachage cryptographique du secret sur le serveur (c'est-à-dire le traiter comme un mot de passe). Votre comparaison serait alors de prendre le hachage de la chaîne que le client vous envoie et de comparer les hachages.
Si le secret a une entropie suffisamment élevée, cela devrait éliminer les attaques de synchronisation et empêcher la fuite de la chaîne secrète réelle, car il devrait être pratiquement impossible de récupérer le secret du hachage.
D'un autre côté, si la quantité d'entropie dans le secret n'est pas suffisante pour empêcher les attaques par dictionnaire, cela ne suffit pas. Une comparaison de sortie anticipée peut encore permettre à l'attaquant d'apprendre les premiers octets du hachage; alors une attaque de dictionnaire ultérieure pourrait être en mesure de récupérer le secret de son hachage. (Voir aussi Attaques temporelles sur les hachages de mot de passe pour plus de discussion sur la possibilité de telles attaques temporelles.) Cela peut être évité en comparant les deux hachages en utilisant une méthode de comparaison à temps constant.
Ainsi, la solution la plus robuste serait de stocker un hachage du secret, de hacher la chaîne que le client vous envoie et de comparer les deux hachages à l'aide d'une méthode de comparaison sécurisée à temps constant. L'utilisation d'un haschich salé ne ferait pas de mal non plus.
La réponse de @ Ben pour comparer directement les hachages plutôt que les clés semble être l'approche de la meilleure pratique pour la tâche , et en passant devient également partielle solution au problème .
Cependant, il reste vulnérable à un certain niveau d'arbre de hachage déposé Rainbow: essayez les clés qui entraînent un hachage commençant par chaque lettre, puis celles commençant par la lettre trouvée et faisant défiler la seconde, etc. Le sel et le poivre permettraient peut-être de résoudre ce problème, mais s'il s'agit d'un problème de synchronisation, une meilleure solution au problème serait d'empêcher complètement l'attaque de synchronisation, en dormant après la comparaison jusqu'au multiple de 100 ms suivant (ou autre le temps est nettement plus long que le strcmp peut être).
Pour répondre à la question posée , nous devons cependant calculer l'ampleur du risque.
Sur une machine à 1 GHz, un cycle d'horloge est une nanoseconde. Un seul caractère d'une comparaison de chaînes doit être dans cet ordre, tout comme n'importe quel cache de CPU (~ 1 ns pour L1 à ~ 30 ns pour L3). Si je comprends bien, la plupart des fonctions de comparaison de chaînes n'obtiennent pas les lettres de la mémoire une par une, mais les obtiennent plutôt dans la taille de bloc de compromis la plus efficace. Même s'ils l'ont fait, un cache manquant nécessitant un accès DRAM ne sera, dans le pire des cas, que d'environ 100 ns/caractère.
@Oasiscircle renvoie à un article, citant leur WAN timing de 20us.
Cependant, il est trivial d'établir où une machine est hébergée. Quelqu'un qui loue une machine dans le même NOC ou la même batterie de serveurs connaîtra votre configuration matérielle exacte et n'aura peut-être qu'un ou deux commutateurs entre vous. S'ils louent deux serveurs, ils peuvent même mettre en place un système de test pour tester les temps de mesure via ce NOC afin d'obtenir leurs résultats le plus près possible.
Ce qui est peut-être plus intéressant dans ce document, c'est leur synchronisation LAN de 100 ns avec 2 000 mesures avec un taux d'erreur <5% (ou 200 ns avec 1 000 mesures: plus de mesures donneraient une résolution encore meilleure). Ils n'ont pas décrit leur matériel réseau, mais il se pourrait bien que s'il y a des cartes gigabit sur les machines d'attaque et cibles, elles pourraient obtenir une résolution encore meilleure.
À ce stade, un attaquant serait en mesure de distinguer la longueur à quelque part entre le 1 à 100 caractères le plus proche (en fonction de la qualité de vos accès au cache, du nombre de tests qu'ils exécutent, de la vitesse de leur réseau et d'une pléthore de points précis matériels ).
Puisqu'il faut supposer qu'il existe des attaques qui garantiraient des échecs de cache, qu'ils peuvent exécuter un nombre arbitraire de tests, qu'ils sélectionneront le matériel le mieux adapté pour attaquer le vôtre et que la résolution de leur attaque ne s'améliorera qu'au cours de la durée de vie de votre application et de votre serveur ... qui leur donne effectivement 1 caractère de résolution, et donc votre clé entière.
Donc, si votre système est susceptible d'avoir des attaquants techniquement avancés prêts à dépenser de l'argent pour cibler spécifiquement votre machine, alors c'est un risque clair, car ils ont une quasi-certitude de succès avec cette approche.
De même, si vous louez de l'espace dans un NOC, même les attaques au volant d'autres occupants du NOC sont un risque, du moins si cette comparaison est effectuée à l'aide d'un protocole qu'une attaque au volant pourrait découvrir.
Réponse courte: oui c'est un risque; empêcher le temps pour l'opération d'être affecté par le strcmp est trivial; mieux le faire.
Il ne s'agit pas de l'aspect pratique de l'attaque, il s'agit de l'habitude de faire les choses en toute sécurité. Il est trop courant que des bogues se faufilent parce que quelqu'un a pensé à échapper ou non à cette chaîne, car "ce ne sont que des chiffres, donc je peux simplement ignorer la validation". Ensuite, quelqu'un trouve comment exploiter cela.
L'utilisation d'une comparaison de chaînes cryptographiquement sécurisée est si facile et bon marché qu'il n'y a aucune raison de penser quand vous en avez vraiment besoin et quand vous n'en avez pas. Chaque fois que vous traitez avec des mots de passe, des jetons d'authentification ou similaires, utilisez un algorithme de comparaison de chaînes à temps constant.
if a.length != b.length return false
x = 0
for i = 0; i < a.length; i++ {
x |= a[i]^b[i]
}
return x == 0
Vous posez la mauvaise question. L'inquiétude est une émotion, et les émotions devraient alerter notre esprit sur les menaces, mais une fois alertées, nous devons réfléchir plus profondément à ce qu'est la menace. Il est souvent utile de mesurer un risque par rapport à d'autres risques. Les attaques temporelles sur la méthode des égaux sont réelles et potentiellement exploitables, mais vous pouvez avoir des menaces bien plus importantes à gérer. La question devient donc quelle devrait être votre réponse. Cela dépend largement de votre contexte.
Si vous lisez la litanie de réponses ici, vous verrez une énorme quantité de calculs, d'hypothèses et de facteurs atténuants. Le résultat final est que ce n'est pas un exploit terriblement facile à réaliser.
En fin de compte, la sécurité consiste à équilibrer les risques et les coûts. Nous n'avons jamais de ressources infinies pour faire face à tous les problèmes, nous sommes donc toujours obligés de choisir ceux que nous devons traiter.
Informez les développeurs qu'il s'agit d'un véritable exploit potentiel et que la comparaison des chaînes doit être effectuée à l'aide d'algorithmes sécurisés plutôt que directement. Les coûts ici sont vraiment minimes pour tout nouveau code, et cela élimine le potentiel de risques.
Pour tout code existant, envisagez de résoudre le problème s'il n'y a pas de circonstances atténuantes. Par exemple, ces attaques nécessitent des milliers et des milliers de tentatives. Si votre exploit est basé sur un mot de passe et que vous limitez les tentatives à seulement 3 tentatives, il sera presque impossible de révéler quoi que ce soit (disons le hachage) avec cet exploit. D'un autre côté, si vous avez un mécanisme de sécurité pour une cible de grande valeur qui n'est en aucun cas limitée par le taux, vous devriez envisager de corriger ce code.
Existe-t-il des exemples d'attaques de synchronisation <1 ms réussies sur Internet?
Ce à quoi vous pourriez penser, c'est si quelqu'un tentait de découvrir le secret en interne. Le timing pourrait être jugé à un niveau plus précis d'un point de vue interne. Si une telle situation vous inquiète, pourquoi ne pas modifier la fonction pour évaluer tous les caractères soumis au lieu de quitter le premier incorrect? De cette façon, le timing sera uniforme.
Il ne peut pas être déterminé hors contexte.
Une comparaison de chaînes comme celle-ci serait difficile à attaquer, comme décrit dans la réponse acceptée. Les délais sont tout simplement trop courts. Cependant, le contexte est essentiel. Si un pirate intelligent peut modeler le reste du code autour de ce bloc pour amplifier les timings, il pourra peut-être arriver quelque part.
Par exemple, s'il existait un moyen intelligent pour un pirate de faire appeler cette fonction 10 000 000 fois de suite avant de renvoyer quoi que ce soit au client, vous pourriez avoir des ennuis. Soudain, votre problème de plage inférieure à la milliseconde est devenu un problème de plusieurs secondes, et clairement visible contre le bruit du réseau.
C'est un cas où je recommanderais de passer à une comparaison à temps constant si votre modèle de menace inclut des attaques temporelles. Vous pourriez vous asseoir et analyser l'intégralité de votre base de code pour prouver qu'aucun attaquant ne peut jamais tirer de telles astuces de répétition intelligentes, puis inclure une étape d'assurance qualité pour vous assurer qu'aucun changement futur ne perturbe cette analyse. Ou vous pouvez simplement passer à une comparaison à temps constant. Potentiellement, ajoutez un commentaire disant: "La comparaison à temps constant est probablement exagérée, mais il était plus facile de protéger cette petite section de code contre les attaques de synchronisation que d'analyser l'ensemble du programme pour s'assurer que ce code n'est jamais utilisé à mauvais escient d'une manière qui pourrait conduire à une attaque de synchronisation. "
Il y aura quelques mineurs problèmes de lisibilité du code, et vous devrez peut-être passer par une étape d'assurance qualité pour prouver que la comparaison fonctionne réellement ainsi que String.equals. Il n'y aura probablement pas de coûts de performance. Si la plupart de vos utilisateurs sont légitimes et ont le secret, ils devront quand même comparer la chaîne entière.