Sécurité AJAX avec enregistrement dans la base de donnée

Fermé
adamantyn Messages postés 7 Date d'inscription dimanche 8 mars 2015 Statut Membre Dernière intervention 17 juin 2019 - Modifié le 16 juin 2019 à 13:52
adamantyn Messages postés 7 Date d'inscription dimanche 8 mars 2015 Statut Membre Dernière intervention 17 juin 2019 - 17 juin 2019 à 11:59
Bonjour,

Je suis en train de construire un panier qui s'incrémente sans rechargement de page en AJAX.
Le code fonctionne, mais j'ai un doute sur la sécurité.
Si il y a une meilleure manière de faire, dite le moi aussi SVP.

La partie AJAX:

<script>
function save_to_db(cart_id, newQuantity) {
$.ajax({
url : "update_cart_quantity.php",
data : "cart_id="+cart_id+"&new_quantity="+newQuantity+"&member_id="+<?php echo $member_id?> ,
type : 'post',
dataType: "json",
success : function(response) {
if(response === "Empty cart"){
$("#total-quantity").text(0);
$("#total-price").text(0);
$("#shopping-cart-table").html("<h2><b style='color:red'>Your cart is empty.</b></h2>");
}

if(newQuantity == 0){
$("#item-container-"+cart_id).remove();
}

var totalQuantity = 0;
var totalItemPrice = 0;
$.each(response, function(key, cartDetails) {
totalItemPrice = totalItemPrice + parseInt(cartDetails.price) * parseInt(cartDetails.quantity);
totalQuantity = totalQuantity + parseInt(cartDetails.quantity);

if(cartDetails.cart_id == cart_id){
$("#input-quantity-"+cart_id).val(cartDetails.quantity);
$("#cart-price-"+cart_id).text("$"+cartDetails.price * cartDetails.quantity);
}
});

$("#total-quantity").text(totalQuantity);
$("#total-price").text(totalItemPrice);
$("#total-price-foot").text(totalItemPrice);
}
});
}
</script>



La partie SQL:

<?php
require 'connectDB.php';

$member_id = $_POST["member_id"]; // you can your integerate authentication module here to get logged in member

if($_POST["new_quantity"] == 0){
mysqli_query($conn,"DELETE FROM tbl_cart WHERE id = '".$_POST["cart_id"]."' AND member_id = ".$member_id."");
}
elseif ($_POST["new_quantity"] == 1 || $_POST["new_quantity"] == -1) {
mysqli_query($conn,"UPDATE tbl_cart SET quantity =
(CASE WHEN quantity + '".$_POST["new_quantity"]."' > 0
THEN quantity + '".$_POST["new_quantity"]."' ELSE quantity END)
WHERE id='".$_POST["cart_id"]."' AND member_id = ".$member_id."");
}


$db_query = mysqli_query($conn,"SELECT tbl_product.id, tbl_product.price, tbl_cart.quantity, tbl_cart.id as cart_id
FROM tbl_product, tbl_cart WHERE
tbl_product.id = tbl_cart.product_id AND tbl_cart.member_id = ".$member_id."");


while ($row = $db_query->fetch_assoc()) {
$cartDetails[] = $row;
}

if(isset($cartDetails)){
$response = json_encode($cartDetails);
echo $response;
}
else{
$response = json_encode("Empty cart");
echo $response;
}
?>



Merci d'avance.
(j'ai posté sur le forum PHP, déplacez le vers Javascript si c'est mieux)

Configuration: Windows / Firefox 67.0

3 réponses

tpez Messages postés 330 Date d'inscription lundi 4 juillet 2016 Statut Membre Dernière intervention 17 juin 2019 39
16 juin 2019 à 15:55
Bonjour,
Je ne connais pas vraiment le contexte de ton application, mais quelques éléments de ton code semblent représenter un rique.
Pour commencer je vais faire un petit rappel sur les tech que tu utilises :
- Le JS / AJAX est exécuté côté client, par conséquent le code ajax est directement lisible/éditable depuis la console navigateur via le débugger par exemple.
- Le PHP lui est exécuté côté serveur, de ce fait aucun code php n'est lisible par le client.

Voilà un problème pour commencer :

-
data : "cart_id="+cart_id+"&new_quantity="+newQuantity+"&member_id="+<?php echo $member_id?>
Il y a baucoup à dire sur cette unique ligne de code. Ceci est modifiable par l'utilisateur ! Il peut donc directement changer "cart_id", "newQuantity" dans le code javascript mais cela n'est pas forcément un problème, le plus gros problème réside dans le "member_id" quelqu'un pourrait aisément changer ce member_id pour ajouter un article à un autre client !

Aucune action critique, ne doit être effectuer dans JS.
Pour la suite il me faudrait d'autres informations tel que :
- Comment sont définis les variables :
- newQuantity
- cart_id (quel est le but de cette variable)
- A quoi resemble ta base de données ?
- Tables
- Colonnes des tables
- Y a t-il des informations personnelles stockées sur la DB ?
- A quoi resemble le front ?
- Code html
- L'application a t-elle recours à des méthodes d'authentifications (SESSIONS, JWT...)?
- Si tu développes sur un serveur accessible peux-tu nous communiquer l'adresse url pour simplifier l'audit ?
0
adamantyn Messages postés 7 Date d'inscription dimanche 8 mars 2015 Statut Membre Dernière intervention 17 juin 2019
16 juin 2019 à 16:13
SVP, comment poster du code en couleur?
0
tpez Messages postés 330 Date d'inscription lundi 4 juillet 2016 Statut Membre Dernière intervention 17 juin 2019 39
Modifié le 16 juin 2019 à 16:30
Pour avoir des couleurs en postant du code php il suffite d'écire <c0de php></c0de> il faut juste remplacer les "0" par des "o". Sinon il suffit d'appuyer sur l'icone <> (juste après l'icon S)l'orsque tu écris un message et de choisir le langage que tu vas mettre entre les deux balises.


Petite astuce pour coder en php :
<?php echo "string ".$value; ?>

possède un équivalent plus légé syntaxiquement qui est :
<?= "string ".$value; ?>


Je regarde les informations que tu m'as fourni et je réfléchis aux potentiels problèmes dans l'après midi.
0
adamantyn Messages postés 7 Date d'inscription dimanche 8 mars 2015 Statut Membre Dernière intervention 17 juin 2019
Modifié le 16 juin 2019 à 16:44
Merci,

-cart_id : est une variable qui va sélectionner un id dans la DB côté serveur.
-la base de donnée a une table product et une table cart, avec les champs :
id
product_id
quantity
member_id
-Oui il y aura des informations personnelles dans la DB
-Il y aura une méthode d’authentification, la variable member_id aura le numéro de l'utilisateur
-Désolé je développe en local

Voilà le code complet du front, le back est déjà posté(update_cart_quantity.php):

<?php
require 'connectDB.php';

$member_id = 2; 
?>
<html>
<head>
 <title>Shopping Cart</title>
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link href="style.css" type="text/css" rel="stylesheet" />
 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
 <script src="jquery-3.2.1.min.js"></script>

 <script>
 function save_to_db(cart_id, newQuantity) {
  $.ajax({
   url : "update_cart_quantity.php",
   data : "cart_id="+cart_id+"&new_quantity="+newQuantity+"&member_id="+<?php echo $member_id?> ,
   type : 'post',
   dataType: "json",
   success : function(response) {
    if(response === "Empty cart"){
     $("#total-quantity").text(0);
     $("#total-price").text(0);
     $("#shopping-cart-table").html("<h2><b style='color:red'>Your cart is empty.</b></h2>");
    }

    if(newQuantity == 0){
     $("#item-container-"+cart_id).remove();
    }

    var totalQuantity = 0;
    var totalItemPrice = 0;
    $.each(response, function(key, cartDetails) {
     totalItemPrice = totalItemPrice + parseInt(cartDetails.price) * parseInt(cartDetails.quantity);
     totalQuantity = totalQuantity + parseInt(cartDetails.quantity);

     if(cartDetails.cart_id == cart_id){
      $("#input-quantity-"+cart_id).val(cartDetails.quantity);
      $("#cart-price-"+cart_id).text("$"+cartDetails.price * cartDetails.quantity);
     }
    });

    $("#total-quantity").text(totalQuantity);
    $("#total-price").text(totalItemPrice);
    $("#total-price-foot").text(totalItemPrice);
   }
  });
 }
</script>
</head>

<body>
 <?php
 $db_query = mysqli_query($conn,"SELECT tbl_product.*, tbl_cart.quantity, tbl_cart.id as cart_id FROM tbl_product, tbl_cart WHERE
  tbl_product.id = tbl_cart.product_id AND tbl_cart.member_id =  ".$member_id."");

  while ($row = $db_query->fetch_assoc()) {
   $cartItem[] = $row;
  }

  $item_quantity = 0;
  $item_price = 0;
  if (! empty($cartItem)) {
   foreach ($cartItem as $item) {
    $item_quantity = $item_quantity + $item["quantity"];
    $item_price = $item_price + ($item["price"] * $item["quantity"]);
   }
  }
  ?>

  <!--Shopping cart header-->
  <div id="shopping-cart">
   <div class="txt-heading">
    <div class="txt-heading-label"><a href='index.php' style="text-decoration: none">Shopping Cart</a></div>
    <div class="cart-status">
     <?php
     if (! empty($cartItem)) {?>
      <div id="total-quantity"><?php echo $item_quantity; ?></div>
      <div>
       <strong>$</strong>
       <strong id="total-price"><?php echo $item_price; ?></strong>
      </div>
      <?php
     }
     ?>

    </div>
    <a id="btnCart" href="cart.php" class="float-right">
     <i class="material-icons" alt="Cart" title="Cart">shopping_cart</i>
    </a>
   </div>
   <?php
   if (! empty($cartItem)) {
    ?>
    <!--Shopping cart table-->
    <div id="shopping-cart-table" class="shopping-cart-table">
     <div class="cart-item-container header">
      <div class="cart-info title">Title</div>
      <div class="cart-info options">Options</div>
      <div class="cart-info quantity">Quantity</div>
      <div class="cart-info unit-price">Unit price</div>
      <div class="cart-info price">Price</div>
     </div>
     <?php
     foreach ($cartItem as $item) {
      ?>
      <div id="item-container-<?php echo $item["cart_id"]; ?>" class="cart-item-container">
       <div class="cart-info title">
        <a href="" style="text-decoration:none"><img src='<?php echo $item["image"]; ?>' width="50" height="40" style="border-radius:50%" />
         <?php echo $item["name"]; ?></a>
        </div>

        <div class="cart-info options">
         <form method='post' action=''>
          <select name='taille'>
           <option value="S">S</option>
           <option value="M">M</option>
           <option value="L">L</option>
           <option value="XL">XL</option>
           <option value="XXL">XXL</option>
          </select>
         </form>
        </div>

        <div class="cart-info quantity">
         <div class="btn-increment-decrement" onClick="save_to_db(<?php echo $item["cart_id"]; ?>, -1)"><i class="material-icons">remove_circle_outline</i></div>
         <input class="input-quantity" id="input-quantity-<?php echo $item["cart_id"]; ?>" value="<?php echo $item["quantity"]; ?>" readonly>
         <div class="btn-increment-decrement" onClick="save_to_db(<?php echo $item["cart_id"]; ?>, 1)"><i class="material-icons">add_circle_outline</i></div>
        </div>

        <div class="cart-info unit-price">
         <?php echo "$". ($item["price"]); ?>
        </div>

        <div class="cart-info price" id="cart-price-<?php echo $item["cart_id"]; ?>">
         <?php echo "$". ($item["price"] * $item["quantity"]); ?>
        </div>

        <div class="cart-info action">
         <div class="btn-increment-decrement" onClick="save_to_db(<?php echo $item["cart_id"]; ?>, 0)"><i class="material-icons" alt="Delete" title="Remove Item">delete</i></div>
         <div>
          Delete
         </div>
        </div>
       </div>
       <?php
      }
      ?>
      <div id="shopping-cart-footer" class="cart-item-container footer">
       <div class="cart-info title">Total</div>
       <strong>$
        <strong id="total-price-foot"><?php echo $item_price; ?></strong></strong>
       </div>
       <?php
      }
      ?>
     </div>
    </div>


   </body>
   </html>

0
tpez Messages postés 330 Date d'inscription lundi 4 juillet 2016 Statut Membre Dernière intervention 17 juin 2019 39
16 juin 2019 à 21:46
Bon alors selon moi, Oui il y a des problèmes de sécurité.
Premièrement il est absolument nécéssaire d'utiliser un système comme JWT pour vérifier que l'ajout du produit pour un utilisateur ne vienne pas d'un autre utilisateur.
Grossièrement sans token (jeton JWT) :
Nous avons deux clients (ClientA, ClientB).
ClientA se connecte, change son membre_id par celui du ClientB dans le JS et ajoute un objet.
Par conséquent ClientB obtient un produit dans son panier alors qu'il ne l'a jamais ajouté.
Avec un jeton JWT :
Nous avons deux clients (ClientA, ClientB).
ClientA se connecte, obtient un JWT et change son membre_id par celui du ClientB dans le JS et ajoute un objet.
Seulement nous faisons une vérification de membre_id avec le Jeton en question et l'on s'aperçoit que ClientB ne possède pas le token qui a été utilisé pour ajouter le produit.
Par conséquent l'objet n'est pas ajouté au panier.


Mais même si tu ajoutes un système de token actuellement cela ne changerait pas grand chose car aucun filtre n'est appliqué du côté serveur, il est donc possible de faire une injection SQL pour contourner le système de token, ou pour lire la base de données.

Mon conseil est le suivant :
Utiliser un framework comme laravel qui gère les systèmes de token JWT nativement et qui permet d'appliquer des filtres sur des forms et les requêtes SQL pour éviter les injections.
0
adamantyn Messages postés 7 Date d'inscription dimanche 8 mars 2015 Statut Membre Dernière intervention 17 juin 2019
Modifié le 16 juin 2019 à 23:31
Merci pour votre réponse.

Je vais intégrer la panier à un site plus important qui n'est pas écrit avec un framework. Donc ça me paraît difficile d'utiliser cette solution.

Si l'utilisateur s'est connecté avec son mon de passe et identifiant, et que le
 member_id
est écrit en php, donc côté serveur, et prend le numéro de la session, comment il peut être changé, je ne comprends pas?
J'ai aussi essayer de changer
le cart_id
directement dans l'inspecteur, ca ne fonctionne pas, le résultat d'une opération est toujours ce que l'on espère, donc pas de hack

Je poste le lien d'un exemple, est-ce que vous pourriez me montrer comment vous ajoutez un produit à un nouvel utilisateur, '1' ou '3' par exemple? L'utilisateur actuel étant le numéro '2'.
https://adamantin.000webhostapp.com/tests/cart-custom/cart.php

Voilà à quoi ressemble la DB:

id product_id quantity member_id
54 1 1 2
56 3 1 2
57 2 1 2
0
tpez Messages postés 330 Date d'inscription lundi 4 juillet 2016 Statut Membre Dernière intervention 17 juin 2019 39
17 juin 2019 à 00:16
Il y a une partie que je ne saisie pas... Comment l'application sait que je suis le client "2" sur cette page ?
https://adamantin.000webhostapp.com/tests/cart-custom/index.php
Je ne me suis pas enregistré en tant que tel et ce n'est pas non plus en cookie, alors comment l'application sait que je suis client "2" ?

D'autre part le fait de passer une commande en GET comme suit :
?action=add&code=wristWear03 est à proscrire car c'est également un problème de sécurité (campagne de phishing ajoutant un objet au panier) de plus le simple fait de recharger la page provoque l'ajout de l'objet dans le panier.
0
tpez Messages postés 330 Date d'inscription lundi 4 juillet 2016 Statut Membre Dernière intervention 17 juin 2019 39
17 juin 2019 à 00:27
Je viens de trouver comment est défini l'utilisateur mais dans un cas concrèt il ne serait pas défini dans le code PHP en brute alors est-il possible d'ajouter très rapidement un lien pour atteindre les différents utilisateurs pour faire tous les tests ?
0
adamantyn Messages postés 7 Date d'inscription dimanche 8 mars 2015 Statut Membre Dernière intervention 17 juin 2019
Modifié le 17 juin 2019 à 01:10
Je ne suis pas sur de comprendre la question, dans le concret je fais : $member_id = $_SESSION['user_id']; ou $_SESSION['user_id'] est le numéro de l'utilisateur enregistré.
Quel lien vous voulez?

Oui je sais pour le GET, ca je comprends pourquoi ce n'est pas sécurisé.
C'est juste la partie du panier qui m'intéresse pour l'instant.
0
tpez Messages postés 330 Date d'inscription lundi 4 juillet 2016 Statut Membre Dernière intervention 17 juin 2019 39
17 juin 2019 à 07:37
Je ne vois pas de $_SESSION mais je vois juste ça dans ton code
$member_id = 2;

J'aurais voulu un moyen pour changer d'utilisateur passer de utilisateur "2" à "3" ou "1" par exemple
0
adamantyn Messages postés 7 Date d'inscription dimanche 8 mars 2015 Statut Membre Dernière intervention 17 juin 2019
Modifié le 17 juin 2019 à 12:00
Je voulais mettre le $_SESSION sur le site final.
J'ai mis en place un moyen de changer l'utilisateur de la même manière sur le site de démo:
https://adamantin.000webhostapp.com/tests/cart-custom/index.php
0