OVH Cloud OVH Cloud

Problème C++

106 réponses
Avatar
Steph
boujour

J'ai créé un programme dont voici une partie :


#include <iostream>
using namespace std;

int main (void)

{
char pw[20];
const char valide[20]="12H12";

cout << " Password :";
cin >> pw;

while (strcmp(pw,valide) = =1)

{

cout << " ACCESS REFUSED\n ";
cout << " Password :";
cin >> pw;

}


cout << "\n ACCESS GARANTED ";
}

Le problème est que si j'entre seulement le premiere lettre ou chiffre il
accepte alors que celles qui suivent sont fausses. Comment je peux faire
pour que le programme teste un à un les caratères?

merci

10 réponses

Avatar
Vincent Lascaux
les 2 opèrent donc en temps non constant; temps proportionnel à la plus
petite chaine pour strcmp; temps variable (nul ou prop. à la longueur)
pour string=
Il y a des chances pour que strcmp s'arrête "en gros" dès que les chaines

different, non ?

--
Vincent

Avatar
Michel Decima
In news:449b1880$0$886$,
Sylvain typed:
kanze wrote on 22/06/2006 11:17:

La question est plutôt : en quoi un code simple et correct
serait un plus ici ? Le code initialement posté plante
carrément dès que l'utilisateur entre quelque chose d'imprévu.
On peut bien le « corriger » pour qu'il marche, tout en
gardant le char[], mais à coût de pas mal de complications en
plus. Or qu'avec std::string, il marche comme il faut d'office.


évaluons les "pas mal de complication":

char pw[20]; // donné par le PO
int cnt = 0; // sorti nécessaire ensuite
for (; cnt < 20; ){
int c = getchar();
if (c == 'r' || c == 'n')
break;
pw[cnt++] = c;
}

5 lignes de code et une déclaration, grosse complication en effet.


Je rajoute une 6eme ligne pour afficher le tout, l'include qui va bien,
et je teste:

#include <stdio.h>
int main() {
char pw[20]; // donné par le PO
int cnt = 0; // sorti nécessaire ensuite
for (; cnt < 20; ){
int c = getchar();
if (c == 'r' || c == 'n')
break;
pw[cnt++] = c;
}
printf("cnt=%dnpw='%s'n", cnt, pw);
}

$ ./leSoftQuiVerifieLePassphrase < /dev/null
cnt
pw='ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'

$ ./leSoftQuiVerifieLePassphrase < /dev/zero
cnt
pw=''

$ printf ABCD | ./leSoftQuiVerifieLePassphrase
cnt
pw='ABCDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'

L'incohérence entre la taille des donnees d'entree et la valeur de cnt,
ainsi que la presence des ÿ a l'affichage me suggere qu'il faut encore
faire quelque chose ici avant de passer aux problemes d'attaque
cryptographiques.

On pourrait par exemple:
- tester EOF plutot que 'r'
- forcer la terminaison de pw par un ''
- distinguer les 2 cas d'arret de la boucle (fin de ligne/trop de
caracteres)
- etc.
et on va forcement depasser les 5 lignes.


Avatar
kanze
Sylvain wrote:
kanze wrote on 22/06/2006 11:17:

La question est plutôt : en quoi un code simple et correct
serait un plus ici ? Le code initialement posté plante
carrément dès que l'utilisateur entre quelque chose
d'imprévu. On peut bien le « corriger » pour qu'il marche,
tout en gardant le char[], mais à coût de pas mal de
complications en plus. Or qu'avec std::string, il marche
comme il faut d'office.


évaluons les "pas mal de complication":

char pw[20]; // donné par le PO
int cnt = 0; // sorti nécessaire ensuite
for (; cnt < 20; ){
int c = getchar();
if (c == 'r' || c == 'n')
break;
pw[cnt++] = c;
}

5 lignes de code et une déclaration, grosse complication en
effet.


Assez grosse, apparamment, que tu n'as pas réussi à le faire
correctement. Michel a déjà signalé le problème de EOF. Il y a
aussi la question du contenu réel du buffer : a priori, il
faudrait soit ajouter un '' à la fin (et la place pour ce ''
n'est pas prévu -- c'est une source classique de débordement de
buffer), soit remplir la reste du buffer avec des caractères de
rembourage. Il y a aussi la question de l'état où tu laisses le
flux. (Si l'utilisateur entre plus de vingt caractères, ce sont
des lectures plus tard dans le programme qui va les voir.)

Alors que :
std::string line ;
std::getline( std::cin, line ) ;
line.resize( 20, '' ) ;
résoud tous ces problèmes. En trois lignes, et d'une façon bien
plus lisible.

note que je n'avais pas indiqué que string n'était pas un bon
choix, même si je sais, ne pas avoir vanter la STL est déjà
une faute coupable


Vanter une solution compliquée qui ne marche pas, par rapport à
une solution simple qui march, c'est bien une faute coupable, je
crois.

En fait, le seul cas que je peux imaginer où la solution
avec char[] a un avantage ici, c'est si on est payé à la
ligne.


comment leur dire qu'il est des monde utilisant le langage C++
qui ne disposent pas d'un runtime illimité et pas de
sacro-sainte STL ??....


Quel compilateur n'est pas fourni avec la bibliothèque
standard aujourd'hui ? (Je parle, évidemment, des
implémentations herbergées. Parce que dans le monde des
processeurs imbriqués, c'est vrai qu'il pourrait bien manquer
std::string. Et std::cin, et stdin. Ce n'est donc visiblement
pas ce monde-là dont il est question.)

Moi, c'est bien connu que je n'aime pas particulièrement la
STL ; je suis très critique vis-à-vis certains aspects de sa
conception. Mais je préfère toujours une solution standard et
idiomatique qui marche à une solution bricolée qui ne marche
pas. Quand un outil standard fait l'affaire, je ne sais pas
pourquoi j'irais chercher ailleurs.

(En passant, std::string ne fait pas partie de ce qui était
classiquement la STL.)

J'avoue ne pas comprendre ces histoires de « balancée » et
d'autres. C'est peut-être une /préjudice/ personnelle, mais
je "préférence" ? trouve qu'une solution simple et correcte
soit plus pertinante à une solution complexée et erronée.

Quant à la performance : sur ma machine, les opérations sur
std::string sont toujours bien plus rapide que le temps
qu'il me faut pour tapper le texte en entrée. Au point
où on pourrait carrément ignorer leur temps d'exécution.


hein ?!? quel rapport avec les perf de saisies ? (de qui tape
vite sans regarder?)


Le rapport, c'est simple. Combien de temps va-t-on passer
là-dedans ? Et est-ce que l'utilisation de std::string va le
ralentir d'une façon mesurable, c-à-d plus de quelque percent du
temps total ? Même en tappant vite, il va falloir prèsqu'une
séconde pour entrer un mot de passe de 4-5 caractères. Tandis
que sur ma machine (un vieux Sparc, vraiment pas rapide), le
temps d'exécution du code que j'ai suggéré ci-dessus va se
mesurer en dizaines de microsécondes. Alors, même si tu as une
solution dix fois plus vite, qu'est-ce que ça change ?

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
Gabriel Dos Reis wrote:
Sylvain writes:

| Gabriel Dos Reis wrote on 22/06/2006 05:48:
| > Sylvain writes:
| > | Gabriel Dos Reis wrote on 22/06/2006 05:13:
| > | > et quel est le rapport avec l'âge du capitaine ?

| > | et aussi qu'à la question: "Comment je peux faire
| > | pour que le programme teste un à un les caratères?"
| > | | une réponse, un peu plus sécuritaire, est:
| > | | const size_t PASS_LENGTH = ?; // could be a hash block size
| > | | bool state = true;
| > | for (register int i = 0; i < PASS_LENGTH; ++i)
| > | state &= (candidate[i] == reference[i]);
| > | | au moins ici le temps ne varira pas avec la saisie.
| > Parce que ?

| parce que PASS_LENGTH est une constante ...

Et alors, le mot de passe de reference a aussi une longeur
constante. Explication à revoir.


J'avoue que moi, il y a un truc que je n'ai pas compris dans
l'affaire. Pourquoi est-ce que je m'amuserai à mesurer les temps
de strcmp (de l'extérieur du programme, ce qui n'est pas
évident, vue que les aléas des temps perdus dans les
entrées/sorties et le schéduleur risquent d'être plusieurs
ordres de grandeur plus importantes), quand il me suffit de
faire strings sur l'exécutable, et alors, le mot de passe
apparaît en claire.

Il y a beaucoup de niveau de sécurité. C'est clair que si je me
sers de strcmp directement sur le mot de passe, j'ai un niveau
assez faible. Mais les variations dans le temps d'exécution de
strcmp est loin d'être le maillon le plus faible. Si on accepte
que le programme initial ne présente pas suffisamment de
sécurité pour l'application en question, on commencerait sans
doute par ne pas lire le mot de passe sur stdin/cin, mais plutôt
sur une liaison sécurisée dont on peut vérifier la provenance,
et qui assure que toute communication sur un reseau est
encryptée. Et on ne comparerait pas les mots de passe eux-même,
mais un hachage ou un encryptage à sens unique (MD5, SHA1, ou
plus).

Et, sans doute, on essaierait d'abord à faire un programme qui
marche, sans erreurs. S'acharner sur les variations dans le
temps d'exécution de strcmp, quand on a un débordement de
buffer, me semble ne pas savoir gerer les priorités.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
kanze
Jean-Marc Bourguet wrote:
Arnaud Meurgues writes:

Question : l'introduction de nop aléatoires ne
pourraient-ils pas être plus simple tout en étant suffisante
pour empêcher de déduire quoique ce soit du temps d'analyse
?


De l'aleatoire asser sur pour ce genre d'application c'est
difficile a faire d'une part, d'autre part ca ne fait
qu'ajouter du bruit qu'on peut filtrer au prix d'un facteur
constant.


Oui, mais...

Qui va s'amuser à essayer de faire de telles mesures quand on
pourrait récupérer le mot de passe en clair au moyen du
programme strings ? (De telles attaques sont connues, mais
elles sont assez compliquées à mettre en oeuvre, et ne se
justifient certainement pas dans le cas où on lit le mot de
passe depuis stdin/cin, pour le comparer en clair avec le mot de
passe en clair dans le binaire. Il y a tellement d'autres
possibilités plus simples.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
kanze
Sylvain wrote:

[...]
pour conclure mes commentaires sur cette 'simple' vérification
de passphrase, je recommanderais de:

- stocker (une fois pour toute) un hash du passphrase
référence (il peut ne pas être génant de l'avoir en clair si
le soft est vraiment protégé en lecture, mais il vaut mieux
éviter).

- boucler sur un nombre borné et petit d'essais (pas de while
infini)

- faire la saisie via un conteneur protégé (tel string) ou en
comptant les caractères reçus

- hasher le passphrase candidat

- faire une boucle (binaire ou booléenne) sans shortcut et sur
l'intégralité de la taille des digest générés. (seule cette
opération est sensible au temps).


Et dans la pratique, tous les digest que je connais ont une
taille fixe.

- introduire un temps d'attente croissant entre chaque essai raté

- bloquer le service pendant un temps significativement long
après épuisement du nombre d'essais avant de le réinitialiser
(un service par agent ayant autorité de déblocage est souvent
non indispensable et toujours pénible).


Et voilà ce qui est sensé. J'ajouterais :

-- ne pas lire le mot de passe de stdin/cin, mais d'une source
que je pourrais vérifier. (Dans la mesure du possible,
évidemment. Un script CGI reçoit normalement toutes les
informations en provenance du POST sur stdin/cin. Mais
alors, on vérifie au démarrage du programme qu'on est bien
dans le contexte du serveur HTTP.)

Mais surtout, toutes les applications ne sont pas si critique.
(Mais malheureusement, beaucoup qui le sont ne prenent pas
toutes les précautions que tu délimites.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Sylvain
Vincent Lascaux wrote on 23/06/2006 04:42:
les 2 opèrent donc en temps non constant; temps proportionnel à la plus
petite chaine pour strcmp; temps variable (nul ou prop. à la longueur)
pour string= >
Il y a des chances pour que strcmp s'arrête "en gros" dès que les chaines

different, non ?

c'est même une certitude ! j'ai "expliqué" que la longueur de la plus

petite chaine limitait la recherche en me plaçant en fait dans le cas de
d'une sous-chaine comparée à une chaine qui la contiendrait.

dans le cas plus courant, la comparaison pourra s'arréter dès le premier
car. (toujours dès le car. différent).

Sylvain.


Avatar
Sylvain
kanze wrote on 23/06/2006 10:52:

J'avoue que moi, il y a un truc que je n'ai pas compris dans
l'affaire. Pourquoi est-ce que je m'amuserai à mesurer les temps
de strcmp (de l'extérieur du programme, ce qui n'est pas
évident, vue que les aléas des temps perdus dans les
entrées/sorties et le schéduleur risquent d'être plusieurs
ordres de grandeur plus importantes), quand il me suffit de


c'est inexact (ça m'empêche pas une mesure).

faire strings sur l'exécutable, et alors, le mot de passe
apparaît en claire.


comment fais-tu cela si le code en question est un PAM rigoureusement
non accessible en lecture ?

Il y a beaucoup de niveau de sécurité. C'est clair que si je me
sers de strcmp directement sur le mot de passe, j'ai un niveau
assez faible. Mais les variations dans le temps d'exécution de
strcmp est loin d'être le maillon le plus faible. Si on accepte


voir mes autres recommandations.

que le programme initial ne présente pas suffisamment de
sécurité pour l'application en question, on commencerait sans
doute par ne pas lire le mot de passe sur stdin/cin, mais plutôt
sur une liaison sécurisée dont on peut vérifier la provenance,
et qui assure que toute communication sur un reseau est
encryptée.


regarde le % de serveurs web faisant cela ...

Et on ne comparerait pas les mots de passe eux-même,
mais un hachage ou un encryptage à sens unique (MD5, SHA1, ou


ceci sert à ne pas avoir de limitation en taille sur les passphrases (à
simplifier le stockage, la gestion de passphrases originellement de
taille varaible) *et* à permettre une comparaison à temps constant
(merci d'aller dans mon sens).

Et, sans doute, on essaierait d'abord à faire un programme qui
marche, sans erreurs. S'acharner sur les variations dans le
temps d'exécution de strcmp, quand on a un débordement de
buffer, me semble ne pas savoir gerer les priorités.


s'il fallait parler d'acharnements !....

je suis un peu las de répéter pour la 5, 8ième ? fois dans ce fil que la
saisie devait être protéger et qu'elle l'était quand j'ai évoqué le
problème des temps - cet archarnement à dénigrer ce que vous n'aviez
introduit vous-même est étonnant et consternant !!

Sylvain.

Avatar
Sylvain
Michel Decima wrote on 23/06/2006 10:04:



#include <stdio.h>
int main() {
char pw[20]; // donné par le PO
int cnt = 0; // sorti nécessaire ensuite
for (; cnt < 20; ){
int c = getchar();
if (c == 'r' || c == 'n')
break;
pw[cnt++] = c;
}
printf("cnt=%dnpw='%s'n", cnt, pw);
}

$ ./leSoftQuiVerifieLePassphrase < /dev/null
cnt
pw='ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'

$ ./leSoftQuiVerifieLePassphrase < /dev/zero
cnt
pw=''

$ printf ABCD | ./leSoftQuiVerifieLePassphrase
cnt
pw='ABCDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'

L'incohérence entre la taille des donnees d'entree et la valeur de cnt,
ainsi que la presence des ÿ a l'affichage me suggere qu'il faut encore
faire quelque chose ici avant de passer aux problemes d'attaque
cryptographiques.


je dois re-re-répéter ?
je ne ferais jamais de strcmp (ni strlen, ni printf) donc le passphrase
saisi n'a *aucun* besoin d'être terminé par un
j'ai juste besoin de sa longueur pour injecter les /cnt/ bytes(!) dans
un message digest.

et btw, j'ai plus envie de faire un memset(pw, 0xFF, 20) qu'un
remplissage à zéro.

Sylvain.


Avatar
Sylvain
kanze wrote on 23/06/2006 10:36:

Assez grosse, apparamment, que tu n'as pas réussi à le faire
correctement. Michel a déjà signalé le problème de EOF. Il y a


ce n'est un problème que pour ceux qui n'ont pas compris que je ne fais
aucun traitement chaine de caractères sur pw

aussi la question du contenu réel du buffer : a priori, il
faudrait soit ajouter un '' à la fin (et la place pour ce ''
n'est pas prévu -- c'est une source classique de débordement de


sa non précense est intentionnel

buffer), soit remplir la reste du buffer avec des caractères de


cela dépends du hash que l'on voudra appliquer, on peut s'autoriser à
hasher les cnt car. saisies ou un buffer de lg fixe paddé de FFh
(la préférence viendrait de l'algo de hash).

rembourage. Il y a aussi la question de l'état où tu laisses le
flux. (Si l'utilisateur entre plus de vingt caractères, ce sont
des lectures plus tard dans le programme qui va les voir.)


je mets plutot un flush avant chaque nouvel input.


Alors que :
std::string line ;
std::getline( std::cin, line ) ;
line.resize( 20, '' ) ;
résoud tous ces problèmes. En trois lignes, et d'une façon bien
plus lisible.


c'est bien !

Vanter une solution compliquée qui ne marche pas, par rapport à
une solution simple qui march, c'est bien une faute coupable, je
crois.


c'est que tu n'as pas compris le but de la routine.

Quel compilateur n'est pas fourni avec la bibliothèque
standard aujourd'hui ? (Je parle, évidemment, des


?!? google vendeur de compilos ...

Le rapport, c'est simple. Combien de temps va-t-on passer
là-dedans ? Et est-ce que l'utilisation de std::string va le
ralentir d'une façon mesurable, c-à-d plus de quelque percent du


mais on se tape complètement du temps mis à taper/à remplir le buffer
stockant pw - ça n'a jamais été la question ni un point que j'ai commenté.

Sylvain.