8768 sujets

Développement web côté serveur, CMS

Pages :
Bonjour,

J'ai une requête MYSQL qui charge du texte avec des entités HTML.

INSERT INTO base SET texte = 'c'est l'été';

Le premier point-virgule clos la requête, c'est comme si je chargeais :

INSERT INTO base SET texte = 'c'';

Je pensais que les apostrophes protégeaient le contenu.

Voyez-vous une solution ?
Modérateur
Salut,

Je passe en coup de vent.

C'est faux...

INSERT INTO base (texte) VALUES('c'est l'été');

Modifié par niuxe (07 Oct 2024 - 17:40)
Bonjour niuxe,

Ces deux requêtes sont équivalentes :

INSERT INTO table (a, b, c) VALUES (1,2,3)

INSERT INTO table SET a=1, b=2, c=3

Je trouve la deuxième plus claire, surtout s'il y a un paquet de champs à charger.

Mon problème ne provient pas de la syntaxe de la requête qui est bonne pour MySQL, voir ce document.
boteha_2 a écrit :
Ces deux requêtes sont équivalentes :

INSERT INTO table (a, b, c) VALUES (1,2,3);

INSERT INTO table SET a=1, b=2, c=3;

Oui, c'est vrai. La première requête est écrite selon les standards SQL, la deuxième est propre à MySQL.

J'évite la deuxième autant que faire ce peu car j'aime bien rester dans les standards SQL***, mais elle est tout à fait valide.

___
*** De toute façon j'évite d'utiliser MySQL au profit de postgreSQL.
Salut

Si je dis pas de bétises et si tu veux pas échaper les chars, utilise des requête préparer :

$texte = 'c'est l'été';
$stmt = $pdo->prepare("INSERT INTO base (texte) VALUES (:texte)");
$stmt->bindParam(':texte', $texte);
$stmt->execute();


EDIT : en faite je sais même pas si tu utilise php Smiley smile
Modifié par JENCAL (08 Oct 2024 - 10:34)
Bonjour,

Merci de votre suivi.
J'utilise PHP et MySQL que je connais assez bien (je ne connais pas postgreSQL)

Je pense que le problème concerne PHP plus que MySQL.

Je vais essayer vos différentes solutions :
Caractère d'échappement
Requête préparée

Je pense qu'il existe une autre solution propre à ce problème :

INSERT INTO base SET texte =  htmlentities ($texte, ENT_QUOTES, 'UTF-8') ;


Je reviens après mes essais.
Modifié par boteha_2 (08 Oct 2024 - 12:51)
Les fonctions htmlentities ou htmlspecialchars, comme leurs noms l'indiquent, sont à utiliser lors de l'affichage de données dans un document html pour éviter les failles XSS.

Il n'est pas recommandé de transformer les données en entités html avant l'insertion en bdd pour plusieurs bonnes raisons (aucun apport concernant la sécurité, les données pourront être utilisées dans un autre contexte que html et il faut alors reconvertir les entités, les requêtes de tri pourraient avoir des résultats inattendus, etc.)

Un peu de lecture à ce sujet : https://zestedesavoir.com/articles/2489/ne-pas-confondre-faille-par-injection-sql-et-faille-xss/

Via PHP, l'utilisation des requêtes préparées est la solution recommandée :
https://www.php.net/manual/fr/mysqli.quickstart.prepared-statements.php
Modifié par Pitet (08 Oct 2024 - 13:58)
Bonjour Pitet,

D'accord avec toi mais avec quelques remarques.

htmlentities est une bonne protection contre les injections SQL.

En cas de changement de version SQL tu ne risques pas une énorme galère avec les caractères accentués (j'ai connu ça...).

Pour la recherche, il suffit de la faire porter sur champ dupliqué avec html_entities_decode.

Idem si autre contexte que html.
Salut boteha

malheureusement htmlentities n'est en rien une bonne pratique contre les injections sql

htmlentities($username, ENT_QUOTES) va convertir les caractères spéciaux HTML, comme les guillemets simples ', les guillemets doubles " ou les chevrons < et > en leurs équivalents HTML (&quot;, &#39;, etc.). Cela protège contre les failles XSS lors de l'affichage dans une page web.
Cependant, cela ne change pas le fait que le contenu malveillant peut toujours être interprété dans la requête SQL.

dans un formulaire php avec des datas style username :

$username_safe = htmlentities($username, ENT_QUOTES);


il suffit de mettre par exemple
' OR '1'='1
et hop.. on a une injection et 0 protection

la requête envoyé à mysql sera :
SELECT * FROM users WHERE username = '' OR '1'='1'

bingo!
Modifié par JENCAL (08 Oct 2024 - 15:50)
Bonjour JENCAL,

Ok, c'est compris.

Pour info j'ai fait l'essai avec échappement.
Le script PHP s'exécute mais signale une erreur mysql et n'enregistre pas la valeur.

Je ferai un essai en requête préparée.
Modérateur
Olivier C a écrit :

Oui, c'est vrai. La première requête est écrite selon les standards SQL, la deuxième est propre à MySQL.

J'évite la deuxième autant que faire ce peu car j'aime bien rester dans les standards SQL***, mais elle est tout à fait valide.

___
*** De toute façon j'évite d'utiliser MySQL au profit de postgreSQL.


Hello,

La 2e forme de requête, je ne la connaissais pas. Mais j'abonde nettement dans le sens d'Olivier. Rester dans le standard sera largement mieux en tout point de vue. Le jour où l'on veut migrer sur une autre base de données, ce sera beaucoup moins fastidieux Smiley sweatdrop

@Olivier : +1000....
@Jencal : +1 (en effet, htmlentities ne protège pas des injections SQL). Ça protège des injections de script JS.

@boteha : J'ai testé ta requête avec l'écriture standard. Ça fonctionne très bien. Si tu as un souci, ça veut surement vouloir dire que le problème est ailleurs. Le conseil d'Olivier à propos de la requête préparée est très pertinent.
Modifié par niuxe (09 Oct 2024 - 16:25)
Bonjour,

Je suis d'accord avec tout, mais...

Quand il y a beaucoup de champs à remplir la requête standard est une purge.
Idem quand des champs sont conditionnels.

En MuSQL :

$insert = "INSERT INTO base SET champ_1 = $champ_1";

if (champ_2 != '') $insert  .= " , champ_2 = $champ_2";


Je fais bientôt un test les requêtes préparées.
boteha_2 a écrit :
Bonjour,


En MuSQL :

$insert = "INSERT INTO base SET champ_1 = $champ_1";

if (champ_2 != '') $insert  .= " , champ_2 = $champ_2";




Avec ce genre de pratique, malheureusement tu n'appliques aucun contrôle de données, tu ouvre la porte à toutes pratiques malveillante.
Modifié par JENCAL (10 Oct 2024 - 10:27)
Modérateur
JENCAL a écrit :


Avec ce genre de pratique, malheureusement tu n'appliques aucun contrôle de données, tu ouvre la porte à toutes pratiques malveillante.


un gros +1 Mais ça ne résout pas son problème sous-jacent.

@boteha_2 : Comment est la définition de ta table "base" ? Si je ne dis pas de bétise, tu peux tout simplement déclarer ta table comme ceci :

CREATE TABLE IF NOT EXISTS base(
    id INT PRIMARY KEY AUTO_INCREMENT,
    un_champ VARCHAR(255) NOT NULL DEFAULT ''
)


Autres manières de faire à partir de $_POST. Comme je te l'ai indiqué précédemment, ton histoire de SET dans ton insertion, c'est bof bof. Voici un exemple¹

le html :

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>form</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.9.0/dist/css/foundation.min.css" crossorigin="anonymous">
    <style>
        main{
            margin: 50px 0;
        }
        .button, input[type="text"]{
            margin:0;
        }
        .cell:last-child{
            justify-content: flex-end;
        }
        .error{
            color:#cc4b37;
        }
    </style>
    </head>
    <body>
        <main>
            <form method="post" class="grid-container">
                <div class="grid-x grid-margin-x">
                    <label class="cell medium-4">
                        <strong>Prenom</strong>
                        <input type="text" name="firstname" value="<?= !empty($_POST['firstname'])? $_POST['firstname'] : ''?>">
                    </label>
                    <label class="cell medium-4">
                        <strong>Nom</strong>
                        <input type="text" name="lastname" value="<?= !empty($_POST['lastname'])? $_POST['lastname'] : ''?>">
                        <?= isset($_POST['errors']['lastname'])? "<span class='error'>".$_POST['errors']['lastname']."</span>" : "" ?>
                    </label>
                    <div class="cell medium-4 flex-container flex-dir-column">
                        <button type="submit" class="button expanded">envoyer</button>
                    </div>
                </div>
            </form>
        </main>
    </body>
</html>


le contrôleur:

<?php
if(!empty($_POST)){
    // nettoie
    $clean_methods = [
        'trim',
        'strip_tags',
        'htmlspecialchars',
    ];
    foreach($clean_methods as $method){
        $_POST = array_map($method, $_POST);
    }

    // validation
    foreach($_POST as $k => $v){
        if($k === 'lastname' && empty($v)){
            $_POST['errors'][$k] = "Ce champ ne doit pas être vide";
        }
    }

    // insertion en base
    if(!isset($_POST['errors'])){
        $fields = array_filter($_POST, function($v, $k){
            return !empty($v);
        },  ARRAY_FILTER_USE_BOTH);
        $keys_fields = array_keys($fields);

        $sql = vsprintf("INSERT INTO base (%s) VALUES(:%s)", [
            implode(', ', $keys_fields), 
            implode(', :', $keys_fields)
        ]);

        $statement = $pdo->prepare($sql);
        foreach($fields as $k => $v){
            $statement->bindParam(':'.$k, $v);
        }
        $statement->execute();
    }
}
?>

_____
¹ Si une personne peut vérifier ce code. Je ne fais plus de php depuis un bon moment. Je peux avoir fait une ou plusieurs erreurs.
Modifié par niuxe (10 Oct 2024 - 14:10)
Bonjour niuxe,

Merci pour ton code, je vais y penser.
Le mien est plus basique mais je le connais bien.

Pour les requêtes préparées je sais que vous avez raison, je vais y songer sérieusement.

Afin de contrer les injections SQL j'utilise quand même une protection bien connue : le typage des variables risquant d'être injectées.

$string = strval ($string);
$number = intval ($number);
etc...


Cela ne vaut pas les requêtes préparées, on est d'accord, mais en réalité c'est assez efficace.
Modifié par boteha_2 (10 Oct 2024 - 19:21)
Bonjour,

Merci de votre suivi.

Je me suis replongé dans le problème à tête reposé.

1) Le problème du champ coupé par un point-virgule n'a rien à voir avec la requête SQL.
J'importe un fichier .csv depuis Excel.

Pour séparer les cellules je dois demander :
$v = valeur-1;valeur_2;valeur_3; etc...
$vv = explode (';', $v);


Dès qu'une valeur contient un point-virgule il y a forcément un problème...

Comme malgré tout je préfère des entités html j'utilise htmlentities après l'explode.

htmlentities (iconv ('cp1252', 'UTF-8//TRANSLIT', $vv[10]), ENT_QUOTES, 'UTF-8')


Cette saleté d'Excel ne pouvant pas être codée en UTF-8 je suis obligé de recoder avec iconv.

Bon, maintenant cela fonctionne que la requête soit de type MySQL avec SET ou SQL avec VALUES.

2) Dans l'idée de me préparer aux requêtes préparées j'ai adoptée la requête avec VALUES.

Je commence par remplir un array (), puis :

foreach ($tabrequete AS $kkk => $vvv)
{
$champ .= ', ' . $kkk;
$valeur .= ', ' . $vvv;
}

$requete = "INSERT INTO base (" . mb_substr ($champ, 2) . ") VALUES (" . mb_substr ($valeur, 2) . ")";


Moins élégant que le code de niuxe mais ça marche.

Comme il s'agit d'un programme d'administration je n'ai aucun risque d'injection, donc une requête préparée me semble inutile.

Je vais me lancer dans les requêtes préparées pour le traitement des variables entrées par les utilisateurs, je reviendrais vers vous si problème.
Modifié par boteha_2 (13 Oct 2024 - 20:00)
boteha_2 a écrit :
Bonjour,
Comme il s'agit d'un programme d'administration je n'ai aucun risque d'injection, donc une requête préparée me semble inutile.


C'est pas parce que j'ai mis des volets chez moi, que les cambrioleurs peuvent pas cambrioler Smiley smile

j'exagère mais c'est pareil
Salut,

je comprends pas tout a tes explications mais bon Smiley sweatdrop

Je n'utilise presque jamais excel mais je trouve très étonnant que tu ne puisse pas importer (ou exporter ? j'ai pas compris..) un csv en utf8 avec excel.

Ensuite, j'ai l'impression que tu parses le fichier csv en php. Cela ne me semble pas une bonne idée de le faire avec explode Smiley sweatdrop .
Je dirais de plutôt regarder du coté des fonctions php pour manipuler du csv : https://www.php.net/manual/en/function.str-getcsv.php (ou du coté des trucs avec des regex genre https://www.php.net/manual/fr/function.preg-split.php )