web-dev-qa-db-fra.com

Meilleures techniques que le cryptage des paramètres url

Je suis un programmeur travaillant sur une application où le seul choix/vs/délai était d'implémenter un cryptage symétrique sur les valeurs des paramètres url. Les données sont de nature insensible, mais nous devions empêcher les agents de vente de jeter un œil sur les pistes des uns et des autres. (Les clés sont générées lors de la création de la session et sont cryptographiquement solides.) Les sessions devraient se terminer fréquemment.

La hiérarchie des rôles était Manager--> Supervisor--> Agents. Les structures de données ne tiennent actuellement pas compte de ces rôles de manière à appliquer strictement qui peut voir quoi. Obtenir ces informations de la base de données n'était pas du tout simple. (Base de données récursive.)

Je sais que cette technique est bien en bas de la liste comme défense contre la manipulation des paramètres. Quelle aurait été une meilleure technique?

Contraintes:
La vérification basée sur les rôles n'est pas une option.

[Informations supplémentaires] Les URL créées et envoyées au client avant que j'apporte des modifications ressemblent à:

https://www.example.com/agent/?producerId=12345

La surface de menace spécifique ici est la manipulation des paramètres contre ?agentId=12345. Les ID d'agent sont attribués de manière unique à chaque agent. Donc, si l'agent A souhaite consulter les statistiques de l'agent B, il aurait pu saisir agentId = 22222 afin de consulter les devis de cet agent et les statistiques de vente actuelles.

Encore une fois, la vérification basée sur les rôles n'était pas une option pour moi: je n'ai pas pu apporter de modifications à la base de données OR le niveau de persistance.

Ma solution a été d'utiliser une clé de chiffrement créée par session (en utilisant la classe KeyGenerator de Java) et de chiffrer les URL sortantes envoyées au client. Alors maintenant, l'url ressemble à ceci:

https://www.example.com/agent/?producerId=<ciphertext>

Maintenant, si quelqu'un essaie agentId = 22222, le serveur déchiffrera ce qu'il pense est un texte chiffré et créera finalement une séquence de caractères invalide.

(Cela laisse ouverte la possibilité de trouver un agentId pourrait existant, mais il est peu probable qu'il soit pertinent pour la personne qui effectue l'attaque.

Je soulignerai que cette question ne concerne pas la sécurité optimale (qui serait une vérification basée sur les rôles pour garantir l'accès aux ressources) et d'essayer de resserrer la sécurité dans une zone grise.

La solution de chiffrement des paramètres m'a été recommandée par l'un de nos responsables de la sécurité. J'ai eu un point à retenir que je n'avais pas envisagé sur cette solution - les URL cassées - et j'utiliserai que ainsi que le problème de maintenance créé par cette solution pour argumenter le temps d'appliquer les règles d'accès de façon moins provisoire.

21
avgvstvs

Bonne question! Merci d'avoir expliqué la menace contre laquelle vous essayez de vous défendre. J'ai modifié ma réponse en conséquence.

Résumé. Votre principale défense devrait être contrôle d'accès. Vous devez limiter les utilisateurs qui peuvent voir quelles pages. Détails ci-dessous.

Contrôle d'accès dans les applications Web. Ce que vous devez faire est de vérifier que l'utilisateur est autorisé à accéder aux données que vous allez afficher sur une page, avant de leur permettre de voir ces données. Cela revient essentiellement au contrôle d'accès: vous voulez des contrôles qui limitent quels utilisateurs peuvent voir quelles données, en fonction d'une politique d'autorisation.

Il semble que vous ayez une séquence de pages, une pour chaque agent:

http://www.example.com/agent/?producerId=12345
http://www.example.com/agent/?producerId=12346
http://www.example.com/agent/?producerId=12347
...

où les ID de producteur (ID d'agent) sont potentiellement devinables ou prévisibles. Vous voulez vous assurer que l'agent 12345 peut voir http://www.example.com/agent/?producerId=12345 mais pas sur les autres pages. D'ACCORD.

Il s'agit d'une situation standard et la défense standard est: le contrôle d'accès.

Pour implémenter le contrôle d'accès, vous codez l'application Web afin que chaque page vérifie si l'utilisateur est autorisé à afficher cette page avant d'autoriser l'utilisateur à afficher cette page. Par exemple, pour la page répertoriée ci-dessus, la logique implémentant cette page vérifierait l'identité de l'utilisateur actuellement connecté. Si l'ID de l'utilisateur connecté correspond à l'ID producteur du paramètre de page, vous lui montrez les informations. Si l'ID ne correspond pas, vous ne leur montrez pas les informations: s'il s'agit d'un autre utilisateur, vous leur montrez une page d'erreur (avec des informations sur la façon d'accéder), ou si l'utilisateur ne s'est pas encore connecté, vous redirigez les à une page de connexion.

Cela ne cassera pas les signets. Il ne nécessite pas de modifications de la base de données, de modifications de la couche de persistance ou de contrôle d'accès basé sur les rôles. Cela nécessite que vous ayez un moyen de rechercher l'identité de l'utilisateur actuellement connecté et de l'associer à son ID de fournisseur. De plus, si vous souhaitez autoriser le responsable et les superviseurs à voir les données de tous les autres agents, vous devez trouver un moyen de rechercher l'utilisateur actuellement connecté et de déterminer s'il s'agit d'un responsable ou d'un superviseur ou non. Si vous souhaitez autoriser uniquement le gestionnaire/superviseur de l'agent à afficher sa page (pas tous les autres gestionnaires/superviseurs), vous devez avoir un moyen de déterminer le gestionnaire/superviseur de chaque agent. Ce sont des exigences assez basiques et minimales; il est difficile de voir comment vous pourriez les éviter.

Comme le souligne @symbcbean correctement, il s'agit d'une erreur très courante fréquemment rencontrée dans les applications Web. Un exemple typique pourrait être un site qui utilise une valeur de paramètre devinable pour identifier une ressource et n'authentifie pas correctement l'utilisateur. Par exemple, supposons que les commandes reçoivent un numéro de commande séquentiel:

https://www.example.com/show_order.php?id=1234
https://www.example.com/show_order.php?id=1235
https://www.example.com/show_order.php?id=1236
...

et supposons que toute personne connaissant l'URL puisse voir la commande. Ce serait mauvais, car cela signifie que toute personne qui connaît (ou devine) le numéro de commande peut voir la commande, même si elle n'est pas autorisée à le faire. Il s'agit de l'un des Top Ten OWASP risques de sécurité des applications Web: Références d'objet direct non sécurisées . Pour plus d'informations, je recommande de lire les ressources disponibles sur OWASP. OWASP dispose de nombreuses ressources sur la sécurité des applications Web.

Autres commentaires. D'autres ont suggéré d'utiliser SSL. Bien que cela n'empêche pas la falsification des paramètres, il s'agit d'une bonne pratique de sécurité générale qui se défend contre d'autres types de problèmes. L'utilisation de SSL est simple: configurez simplement votre site Web pour utiliser https, au lieu de http (et idéalement, activez HSTS et définissez le bit secure sur tous les cookies).

De plus, il est souvent préférable d'éviter de stocker des informations confidentielles dans les paramètres d'URL, toutes choses étant égales par ailleurs. Vous pouvez stocker les informations confidentielles en état de session ou dans la base de données.

16
D.W.

En bref: Ne cryptez pas les paramètres d'URL, utilisez une recherche distincte .

En outre, l'utilisation de HTTPS est fondamentalement non négociable si vous souhaitez une mesure de sécurité des applications Web. C'est obligatoire en 2015. Familiarisez-vous avec TLS 1.1+.


Ce que les développeurs veulent faire

What developers want to do

Ce que les développeurs devraient faire à la place

enter image description here

7
Scott Arciszewski

mais nous devions empêcher les agents de vente de jeter un œil sur les pistes des uns et des autres

Cela implique plutôt que le client est un navigateur - envoyez-vous la clé en clair à un moment donné?

Le polynôme est correct, vous devez utiliser SSL. Cela ne résoudra pas le problème des utilisateurs tapant des valeurs adjacentes à une URL ressemblant à quelque chose comme:

https://www.example.com/show_order.php?id=1234
https://www.example.com/show_order.php?id=1235
https://www.example.com/show_order.php?id=1236
...

Il est tout à fait possible de générer un jeton d'authentification côté serveur en fonction des paramètres qui doivent être présentés pour valider la demande. Idéalement, vous utiliseriez un code d'authentification de message (MAC) pour cela, mais un hachage fonctionnerait aussi si vous faites attention. par exemple. en PHP ...

 print "<a href='show_order.php?id=" . $id . "&valid=" . md5($id . crypto_key()) . "'>...

Ce qui est validé simplement par:

if ($_GET['valid'] != md5($_GET['id'] . crypto_key()) {
   die('not authorized');
}

Ici, crypto_key() renvoie une clé cryptographique statique (générez-la en tirant, disons, 128 bits de /dev/urandom Et en la stockant dans la base de données).

Mais vous devez toujours contrôler l'accès au code qui génère l'URL.

3
symcbean

Voici ma solution

$id=1234;
$en_id = encrypString( $id);

et je crée l'url comme

https://www.example.com/show_order.php?id=$en_id

l'url ressemblera

https://www.example.com/show_order.php?id=9muEYh4lShFDeCnXqoNpxucs42Fuz5Nexq1IUGWYEffffe88yRbJu

et de l'autre côté je déchiffre

$en_id= decryptString($_GET['id']);

les fonctions de cryptage et de décryptage sont

function encrypString($plaintext) {
         # --- ENCRYPTION ---

        $key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");//change this

        # show key size use either 16, 24 or 32 byte keys for AES-128, 192
        # and 256 respectively
        $key_size =  strlen($key);
        //echo "Key size: " . $key_size . "\n";


        # create a random IV to use with CBC encoding
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);

        # creates a cipher text compatible with AES (Rijndael block size = 128)
        # to keep the text confidential 
        # only suitable for encoded input that never ends with value 00h
        # (because of default zero padding)
        $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key,
                                     $plaintext, MCRYPT_MODE_CBC, $iv);

        # prepend the IV for it to be available for decryption
        $ciphertext = $iv . $ciphertext;

        # encode the resulting cipher text so it can be represented by a string
        $ciphertext_base64 = base64_encode($ciphertext);

        return  rawurlencode($ciphertext_base64);//important rawurlencode for + symbol in url

    }


decryptString($ciphertext_base64) {
        # --- DECRYPTION ---

        $key = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");//change this

        # show key size use either 16, 24 or 32 byte keys for AES-128, 192
        # and 256 respectively
        $key_size =  strlen($key);
        //echo "Key size: " . $key_size . "\n";

        # create a random IV to use with CBC encoding
        $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_DEV_URANDOM);

        $ciphertext_dec = base64_decode($ciphertext_base64);

        # retrieves the IV, iv_size should be created using mcrypt_get_iv_size()
        $iv_dec = substr($ciphertext_dec, 0, $iv_size);

        # retrieves the cipher text (everything except the $iv_size in the front)
        $ciphertext_dec = substr($ciphertext_dec, $iv_size);

        # may remove 00h valued characters from end of plain text
        $plaintext_dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key,
                                    $ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);

        return rawurldecode($plaintext_dec);
    }
2
Valentin Ursuleac

Pour éviter la falsification des paramètres, je viens toujours d'envoyer un hachage avec les valeurs en texte brut.

par exemple. prenez cette URL que vous souhaitez sécuriser

https://www.mysite.com/somepage?param1=abc&param2=xyz

Sur le serveur, votre modèle hacherait toutes les valeurs d'URL avec un sel secret

string salt = "A1B2C3D4...";
string param1 = "abc";
string param2 = "xyz";
string hash = YourFavoriteHashingAlgorithm(param1 + param2 + salt);
// result hash = "Y83YMB38DX83YUHFIEIGKDHSEUG"

puis vous envoyez ce hachage avec les autres valeurs d'URL

https://www.mysite.com/somepage?param1=abc&param2=xyz&hash=Y83YMB38DX83YUHFIEIGKDHSEUG

Maintenant, lorsque vous recevez une demande à cette URL, vous prenez à nouveau les paramètres qui vous sont présentés et les hachez via le même algorithme. Le hachage que vous venez de générer doit correspondre au hachage qui vous est présenté, sinon envoyez-leur un résultat 400 "Bad Request" !.

La bonne chose est que vos paramètres sont toujours lisibles par les humains, et toute votre logique de validation existante peut rester la même.

1
raterus

Ne pas utiliser entrée utilisateur

(parce que vous ne pouvez pas faire confiance it)

Cette réponse étend le accepté avec ce qui me semble une simplification significative.

Maintenant, d'après votre description et d'après ma meilleure compréhension , vous avez dit que vous vouliez empêcher Sales Agent A (à savoir 12345) pour jeter un œil à Sales Agent B (à savoir 54321) données.

Supprimez simplement le paramètre agentId de la chaîne de requête et récupérez-le depuis la session

L'URL devient https://example.org/show_order.php

En interne, l'application doit extraire l'ID de l'agent commercial du principal stocké dans la session. Je suis excessivement rouillé en PHP donc j'utiliserai un pseudo code

SELECT * FROM sales where salesman_id = ?1;
[1 = getPrincipalSalesId()]

Cette requête ignorera simplement tout ce qui vient du client. Il ne nécessite aucune modification de la couche de persistance. Il ne nécessite même pas l'implémentation de RBAC (contrôle d'accès basé sur les rôles), mais tout concerne cette fonction getPrincipalSalesId.

Il s'agit essentiellement du même code que vous utilisez pour générer l'URL, mais cette fois, vous insérez cette valeur dans la requête, ce qui en fait implicite .