OWASP dit:
"Les fonctions de la bibliothèque C telles que strcpy (), Strcat (), sprintf () et vsprintf () Fonctionnent sur des chaînes à terminaison nulle Et n'effectuent aucune vérification des limites."
sprintf écrit les données formatées dans la chaîne int sprintf (char * str, const char * format, ...);
Exemple:
sprintf(str, "%s", message); // assume declaration and
// initialization of variables
Si je comprends le commentaire de l’OWASP, les dangers de l’utilisation de sprintf sont que
1) si message 's length> _ {str' s, il y a un débordement de tampon
et
2) si message ne termine pas null avec \0
, alors message pourrait être copié dans (str au-delà de l'adresse mémoire de message, provoquant un débordement de tampon
S'il vous plaît confirmer/refuser. Merci
Vous avez raison sur les deux problèmes, bien qu'ils soient vraiment le même problème (qui consiste à accéder à des données au-delà des limites d'un tableau).
Une solution à votre premier problème consiste plutôt à utiliser snprintf
, qui accepte une taille de mémoire tampon comme argument.
Une solution à votre deuxième problème consiste à donner un argument de longueur maximale à s[n]printf
. Par exemple:
char buffer[128];
snprintf(buffer, sizeof(buffer), "This is a %.4s\n", "testGARBAGE DATA");
// strcmp(buffer, "This is a test\n") == 0
Si vous souhaitez stocker la chaîne entière (par exemple, dans le cas où sizeof(buffer)
est trop petit), exécutez snprintf
deux fois:
// Behaviour is different in SUSv2; see
// "conforming to" section of man 3 sprintf
int length = snprintf(NULL, 0, "This is a %.4s\n", "testGARBAGE DATA");
++length; // +1 for null terminator
char *buffer = malloc(length);
snprintf(buffer, length, "This is a %.4s\n", "testGARBAGE DATA");
(Vous pouvez probablement insérer ceci dans une fonction utilisant va
.)
Vos deux affirmations sont correctes.
Il y a un problème supplémentaire non mentionné. Il n'y a pas de vérification de type sur les paramètres. Si vous ne concordez pas la chaîne de format et les paramètres, cela pourrait entraîner un comportement indéfini et indésirable. Par exemple:
char buf[1024] = {0};
float f = 42.0f;
sprintf(buf, "%s", f); // `f` isn't a string. the Sun may explode here
Cela peut être particulièrement désagréable à déboguer.
Tout ce qui précède conduit de nombreux développeurs C++ à la conclusion qu'il ne faut jamais utiliser sprintf
et ses frères. En effet, il existe des installations que vous pouvez utiliser pour éviter tous les problèmes ci-dessus. One, stream, est construit directement dans la langue:
#include <sstream>
#include <string>
// ...
float f = 42.0f;
stringstream ss;
ss << f;
string s = ss.str();
... et un autre choix populaire pour ceux qui, comme moi, préfèrent encore utiliser sprintf
provient des bibliothèques boost Format :
#include <string>
#include <boost\format.hpp>
// ...
float f = 42.0f;
string s = (boost::format("%1%") %f).str();
Devriez-vous adopter le mantra "ne jamais utiliser sprintf"? Décider vous-même. Il existe généralement un meilleur outil pour le travail et, selon ce que vous faites, sprintf
pourrait bien l'être.
Oui, il s’agit principalement de débordements de mémoire tampon. Cependant, ce sont des affaires sérieuses de nos jours, car les dépassements de mémoire tampon sont le principal vecteur d’attaque utilisé par les pirates informatiques pour contourner la sécurité des logiciels ou du système. Si vous exposez quelque chose comme ceci aux entrées de l'utilisateur, il y a de fortes chances pour que vous remettiez les clés de votre programme (ou même de votre ordinateur lui-même) aux pirates.
Du point de vue de OWASP, supposons que nous écrivons un serveur Web et utilisons sprintf pour analyser l'entrée qu'un navigateur nous transmet.
Supposons maintenant qu'une personne malveillante transmette à notre navigateur Web une chaîne beaucoup plus grande que la mémoire tampon que nous avons choisie. Ses données supplémentaires écraseront les données proches. S'il le rend suffisamment volumineux, certaines de ses données seront copiées sur les instructions du serveur Web plutôt que sur ses données. Il peut maintenant demander à notre serveur Web d’exécuter son code .
Vos 2 conclusions numérotées sont correctes, mais incomplètes.
Il y a un risque supplémentaire:
char* format = 0;
char buf[128];
sprintf(buf, format, "hello");
Ici, format
n'est pas terminé par NULL. sprintf()
ne vérifie pas cela non plus.
La fonction sprintf, lorsqu'elle est utilisée avec certains spécificateurs de format, pose deux types de risque pour la sécurité: (1) l'écriture en mémoire ne devrait pas; (2) lire la mémoire, il ne devrait pas. Si snprintf est utilisé avec un paramètre de taille qui correspond à la mémoire tampon, il n'écrira rien. Selon les paramètres, il peut toujours lire des choses qu'il ne devrait pas faire. En fonction de l'environnement d'exploitation et de ce que fait un programme, le risque lié à des lectures incorrectes peut être ou non moins grave que celui d'une écriture incorrecte.
Votre interprétation semble être correcte. Cependant, votre cas n ° 2 n'est pas vraiment un débordement de tampon. C'est plus une violation d'accès à la mémoire. C'est juste la terminologie cependant, c'est toujours un problème majeur.
J'ai à peu près donné un petit exemple montrant comment vous pouvez vous débarrasser de la déclaration de taille de tampon pour le sprintf (si vous aviez l'intention de le faire, bien sûr!) Et sans snprintf envolved ... .
Note : Ceci est un exemple APPEND/CONCATENATION, jetez un oeil à ici
Il est très important de se rappeler que sprintf () ajoute le caractère ASCII 0 comme terminateur de chaîne à la fin de chaque chaîne. Par conséquent, le tampon de destination doit avoir au moins n + 1 octets (pour imprimer le mot "BONJOUR", un tampon de 6 octets est requis, PAS 5)
Dans l'exemple ci-dessous, cela peut ne pas sembler évident, mais dans le tampon de destination à 2 octets, le deuxième octet sera remplacé par le caractère ASCII 0. Si seulement 1 octet était alloué à la mémoire tampon, cela entraînerait un dépassement de la mémoire tampon.
char buf[3] = {'1', '2'};
int n = sprintf(buf, "A");
Notez également que la valeur de retour de sprintf () n'inclut PAS le caractère de fin nul. Dans l'exemple ci-dessus, 2 octets ont été écrits, mais la fonction retourne '1'.
Dans l'exemple ci-dessous, le premier octet de la variable de membre de classe 'i' serait partiellement remplacé par sprintf () (sur un système 32 bits).
struct S
{
char buf[4];
int i;
};
int main()
{
struct S s = { };
s.i = 12345;
int num = sprintf(s.buf, "ABCD");
// The value of s.i is NOT 12345 anymore !
return 0;
}