Skip to content

Instantly share code, notes, and snippets.

@BenMakesGames
Last active March 13, 2023 15:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BenMakesGames/30ead90618b3b66d4179ecf1fcdd63a6 to your computer and use it in GitHub Desktop.
Save BenMakesGames/30ead90618b3b66d4179ecf1fcdd63a6 to your computer and use it in GitHub Desktop.

I translated the following methods from Java, for use in C#, over a year ago (sometime in early 2022, or late 2021). Maybe there's better versions of the same elsewhere; I dunno. If this is the best that you - o weary traveller of the internet - could find, then I hope you find them useful for your purposes.

Buy Me a Coffee at ko-fi.com

EncodeFilterValue

You know SQL injection attacks? Well, there's LDAP injection attacks, too.

The following method escapes values which you're tossing into LDAP search strings.

Example usage: var filter = $"(sAMAccountName={EncodeFilterValue(username)})";

// from https://github.com/apache/directory-ldap-api/blob/master/ldap/model/src/main/java/org/apache/directory/api/ldap/model/filter/FilterEncoder.java
// licensed under https://www.apache.org/licenses/LICENSE-2.0
public static string EncodeFilterValue(string value)
{
    var sb = new StringBuilder();
    var escaped = false;
    var hexPair = false;
    var hex = '\0';

    foreach (var c in value)
    {
        switch (c)
        {
            case '*':
                if (escaped)
                {
                    sb.Append("\\5C");

                    if (hexPair)
                    {
                        sb.Append(hex);
                        hexPair = false;
                    }
                    escaped = false;
                }

                sb.Append("\\2A");
                break;

            case '(':
                if (escaped)
                {
                    sb.Append("\\5C");

                    if (hexPair)
                    {
                        sb.Append(hex);
                        hexPair = false;
                    }

                    escaped = false;
                }

                sb.Append("\\28");
                break;

            case ')':
                if (escaped)
                {
                    sb.Append("\\5C");

                    if (hexPair)
                    {
                        sb.Append(hex);
                        hexPair = false;
                    }

                    escaped = false;
                }

                sb.Append("\\29");
                break;

            case '\0':
                if (escaped)
                {
                    sb.Append("\\5C");

                    if (hexPair)
                    {
                        sb.Append(hex);
                        hexPair = false;
                    }

                    escaped = false;
                }

                sb.Append("\\00");
                break;

            case '\\':
                if (escaped)
                {
                    sb.Append("\\5C");
                    escaped = false;
                }
                else
                {
                    escaped = true;
                    hexPair = false;
                }

                break;

            case >= '0' and <= '9':
            case >= 'a' and <= 'f':
            case >= 'A' and <= 'F':
                if (escaped)
                {
                    if (hexPair)
                    {
                        sb.Append('\\').Append(hex).Append(c);
                        escaped = false;
                        hexPair = false;
                    }
                    else
                    {
                        hexPair = true;
                        hex = c;
                    }
                }
                else
                {
                    sb.Append(c);
                }

                break;

            default:
                if (escaped)
                {
                    sb.Append("\\5C");

                    if (hexPair)
                    {
                        sb.Append(hex);
                        hexPair = false;
                    }

                    escaped = false;
                }

                sb.Append(c);
                break;
        }
    }

    if (escaped)
    {
        sb.Append("\\5C");
    }

    return sb.ToString();
}

EncodeDistinguishedName

Again: you must be wary of LDAP injection attacks. Never trust user input.

// from https://github.com/apache/directory-ldap-api/blob/master/ldap/model/src/main/java/org/apache/directory/api/ldap/model/name/Rdn.java
// licensed under https://www.apache.org/licenses/LICENSE-2.0
public static string EncodeDistinguishedName(string value)
{
    var sb = new StringBuilder();

    for (var i = 0; i < value.Length; i++)
    {
        var c = value[i];

        switch (c)
        {
            case ' ':
                if (i > 0 && i < value.Length - 1)
                {
                    sb.Append(c);
                }
                else
                {
                    sb.Append('\\');
                    sb.Append(c);
                }
                break;

            case '#':
                if (i != 0)
                    sb.Append(c);
                else
                {
                    sb.Append('\\');
                    sb.Append(c);
                }
                break;

            case '"':
            case '+':
            case ',':
            case ';':
            case '=':
            case '<':
            case '>':
            case '\\':
                sb.Append('\\');
                sb.Append(c);
                break;

            case (char)0x7F:
                sb.Append("\\7F");
                break;

            case >= (char)0x00 and <= (char)0x0F:
                sb.Append("\\0");
                sb.Append(HEX_CHAR[c & 0x0F]);
                break;

            case >= (char)0x10 and <= (char)0x1F:
                sb.Append("\\1");
                sb.Append(HEX_CHAR[c & 0x0F]);
                break;

            default:
                sb.Append(c);
                break;
        }
    }

    return sb.ToString();
}

private static readonly char[] HEX_CHAR = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};

DecodeSid

You may sometimes ask a library for a SID, and get a byte[], but you're probably used to thinking of SIDs as strings. This method turns a byte[] SID into a string.

//From https://ldapwiki.com/wiki/ObjectSID
public static string DecodeSid(byte[] sid)
{
    string result = "S-";

    // byte(0) - revision level
    result += sid[0].ToString();

    // byte(1) - count of sub-authorities
    int countSubAuths = sid[1] & 0xFF;

    // byte(2-7) - 48 bit authority
    long authority = 0;
    for (int i = 2; i <= 7; i++)
    {
        authority |= (long)sid[i] << 8 * (5 - (i - 2));
    }
    result += $"-{authority:X}";

    // iterate all the sub-auths and then countSubAuths x 32 bit sub authorities
    int offset = 8;
    int size = 4;
    for (int j = 0; j < countSubAuths; j++)
    {
        long subAuthority = 0;
        for (int k = 0; k < size; k++)
        {
            subAuthority |= (long)(sid[offset + k] & 0xFF) << 8 * k;
        }
        result += $"-{subAuthority}";
        offset += size;
    }

    return result;
}

Final Thought: Max Length of a SID

256 is the MS-stated/official max length for a SID; in practice they never get longer than 184 characters.

If you're storing SIDs in a DB, play it safe and just give 'em 256 characters. Allowing +72 characters doesn't actually cost you anything, and MS says SIDs can be longer. (Maybe Windows 12, or whatever, will introduce longer SIDs. Who knows.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment