Log4j Writeup – Google CTF
**Challenge URL:** [https://log4j-web.2022.ctfcompetition.com/](https://log4j-web.2022.ctfcompetition.com/)
Upon connecting to the challenge you are presented a Python Flask Webserver
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-1.png)
Challenge Source Code
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image.png)
We are provided a Dockerfile – This can be used to deploy the Challenge Environment Locally
**Challenge Analysis**
Docker Build Instructions
I had originally planned to build the docker image with the intent of viewing the log files produced by the program. (Scroll down to view contents of log4j2.xml)
sudo docker build  .
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-2.png)
sudo docker run –privileged -p 1337:1337 578eea6f503a
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-3.png)
sudo docker ps
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-4.png)
sudo docker exec -it e0aa27c10738 bash
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-5.png)
**Important Files**
/Log4j/server/app.py
/log4/chatbot/src/main/resources/log4j2.xml
/log4j/chatbot/src/main/java/com/google/app/app.java
**/Log4j/server/app.py**
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-6.png)
What we can see is that when the webserver accepts a post request at / it will take the user provided input and pass it into the chat function which has two parameters.
Chat function / class
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-7.png)
The program being ran is a Java application which returns output (via stdout) back to the user via an AJAX request performed In JavaScript.
I noticed it also appears to print() stderr each run also, I originally expected this to print to console for the running python app on the linux server.
**/log4j/chatbot/src/main/java/com/google/app/app.java**
    package com.google.app;     import org.apache.logging.log4j.LogManager;    import org.apache.logging.log4j.Logger;    import java.lang.System;    import java.time.format.DateTimeFormatter;    import java.time.LocalDateTime;    import java.util.Arrays;     public class App {      public static Logger LOGGER = LogManager.getLogger(App.class);      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);      }       private static void doCommand(String cmd, String[] args) {        switch(cmd) {          case "help":            doHelp();            break;          case "repeat":            System.out.println(args[1]);            break;          case "time":            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/M/d H:m:s");            System.out.println(dtf.format(LocalDateTime.now()));            break;          case "wc":            if (args[1].isEmpty()) {              System.out.println(0);            } else {              System.out.println(args[1].split(" ").length);            }            break;          default:            System.out.println("Sorry, you must be a premium member in order to run this command.");        }      }      private static void doHelp() {        System.out.println("Try some of our free commands below! \nwc\ntime\nrepeat");      }    }
We can see the flag is stored in an environment variable on the machine, and stored as a variable in this java application.
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-8.png)
We can see during each run LOGGER.info(“msg: {}”, args); is called passing in the user provided input and logging it..
This seems like an exploit is possible via the log file via the log4j JNDI exploit, but that is not the case.
The log4j2.xml file is for the newest version of log4j; it is not the vulnerable version of log4j running and a JNDI exploit will not actually work.
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-9.png)
What we can see is log4j is only configured to output to console. (What is parsed and printed by the python webserver during each run)
**What we know:**
Our flag is stored in a environment variable
JNDI Lookups are not vulnerable / likely disabled
STDERR can be output
This suggests to test different lookups containing ${} and conversion characters in our input.
[https://logging.apache.org/log4j/2.x/manual/lookups.html](https://logging.apache.org/log4j/2.x/manual/lookups.html)
Log4j supports environment lookups, this allows you to print environment variables to logs
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-10.png)
There is a four different commands that can be ran as a “free user”, at first I tried the following:
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-11.png)
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-12.png)
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-13.png)
After looking at different syntax options and what is passed into the program and supported by log4j I had eventually noticed some odd behavior from the python web application.
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-14.png)
If we place a % before the expected forward slash it is interpreted by Log4j as a conversion character during logger.info() resulting in an error being output to console via STDERR.
Python Flask Webserver code – How our input is interpreted / split
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-15.png)
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-16.png)
**So what is happening?**
We are triggering the Java application to run into an error because we are inserting an invalid conversion character where it expects a plain text string due to improper input validation.
**How can we exploit this to leak the flag?**
I had originally tried various conversion characters from researching and invalid options to test
%d{${env:FLAG}}
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-17.png)
%\\${env:FLAG}
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-18.png)
Java Lookup
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-19.png)
[https://logging.apache.org/log4j/2.x/manual/lookups.html](https://logging.apache.org/log4j/2.x/manual/lookups.html)
[https://logging.apache.org/log4j/2.x/log4j-core/apidocs/src-html/org/apache/logging/log4j/core/lookup/JavaLookup.html](https://logging.apache.org/log4j/2.x/log4j-core/apidocs/src-html/org/apache/logging/log4j/core/lookup/JavaLookup.html)
This lead me to look up different Log4j Lookups & their source code, What we need to have happen is trigger an error / exception while having our variable (Environment Variable Lookup) interpreted., using conversion characters this was not going to happen.
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-20.png)
If you look above you will notice one important thing that takes place when we run a Java Lookup (We pass a string as an argument and if it does not match it is then passed again to IllegalArgumentException.)
That means running ${java:${env:FLAG}} will trigger STDERR output again and print back the flag given the code for IllegalArgumentException uses the string in its error.
![](https://alexdonofrio.com/wp-content/uploads/2022/07/image-21.png)
Boom we have our flag ![?](https://s.w.org/images/core/emoji/14.0.0/svg/1f642.svg)