LOG4J

CHALLENGE INFO : Talk with the most advanced AI. https://log4j-web.2022.ctfcompetition.com

After visiting the challenge URL, we have a chatbot interface with an input field.

Looking into the source code, we see the following:

Python 3.8.10 running a Flask web server in the frontend. Java 11.0.15 in the backend with Log4j 2.17.2 . In this version JNDI lookups are disabled by default and Log4Shell is not exploitable. The versions by running the Docker container and looking into pom.xml .

Source_Code_tree

Python code serves frontend.

import os
import subprocess
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route("/", methods=['GET', 'POST'])
def start():
    if request.method == 'POST':
        text = request.form['text'].split(' ')
        cmd = ''
        if len(text) < 1:
            return ('invalid message', 400)
        elif len(text) < 2:
            cmd = text[0]
            text = ''
        else:
            cmd, text = text[0], ' '.join(text[1:])
        result = chat(cmd, text)
        return result
    return render_template('index.html')
def chat(cmd, text):
    # run java jar with a 10 second timeout
    res = subprocess.run(['java', '-jar', '-Dcmd=' + cmd, 'chatbot/target/app-1.0-SNAPSHOT.jar', '--', text], capture_output=True, timeout=10)
    print(res.stderr.decode('utf8'))
    return res.stdout.decode('utf-8')
if __name__ == '__main__':
    port = os.environ['PORT'] if 'port' in os.environ else 1337
    app.run(host='0.0.0.0', port=port)

The backend is called by starting a Java process like this:

def chat(cmd, text):
    # run java jar with a 10 second timeout
    res = subprocess.run(['java', '-jar', '-Dcmd=' + cmd, 'chatbot/target/app-1.0-SNAPSHOT.jar', '--', text], capture_output=True, timeout=10)
    print(res.stderr.decode('utf8'))
    return res.stdout.decode('utf-8')

Looking at JAVA code,

public static void main(String[]args) {
  String flag = System.getenv("FLAG");
  if (flag == null || !flag.startsWith("CTF")) {
      LOGGER.error("{}", "Contact admin");
  }

  LOGGER.info("msg: {}", args);
  // TODO: implement bot commands
  String cmd = System.getProperty("cmd");
  if (cmd.equals("help")) {
    doHelp();
    return;
  }
  if (!cmd.startsWith("/")) {
    System.out.println("The command should start with a /.");
    return;
  }
  doCommand(cmd.substring(1), args);
}

Flag is hidden in the environment variable FLAG.

The LOGGER.info call is the only one calling Log.

The command is fetched from the system property Commands must start with a / - except for help. Next, the command and the arguments are passed tCommand(cmd.substring(1), args)

We finally see the available commands in app.java:

- help or /help just prints a help message
- /repeat will print the first (and only) argument to stdout
- /time will print the current time
- /wc will print the number of arguments passed

Chatbot_Commands

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <Appenders>
        <Console name="Console" target="SYSTEM_ERR">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} executing ${sys:cmd} - %msg %n">
            </PatternLayout>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="debug">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

This configures how Log4j will behave: Logs are written to stderr (remember, we don’t collect that in the Python frontend) The system properties lookup ${sys:cmd} prints the cmd property in every log line Afterwards, %msg holds the message, followed by a line break %n

Our cmd is passed as-is to the Java process and injected into the pattern layout:

%d{HH:mm:ss.SSS} %-5level %logger{36} executing ${sys:cmd} - %msg %n

Log4j still has environment lookups which could enable us to read the flag via ${env:FLAG}. A first try would be to inject this in the cmd command. This will print the FLAG to stderr on the backend, but sadly this is not captured by the frontend.

%formatstringhereeee

After trying alot, suddenly I came across %formatstringhereeee command, that raise an exception Chatbot_Commands

Log4j will resolve patterns recursively so that we can combine conversion patterns and environment lookups. This will also trigger an exception, but the exception does not leak the resolved environment lookup. Yet.

To encounter that problem we can use java lookup with env lookup to extract the environment variable FLAG.

Combining this with raising exception , we can leak the flag.

%formatstringhereeeeeeee${java:${env:FLAG}}

Flag.txt

Flag : CTF{d95528534d14dc6eb6aeb81c994ce8bd}