программирование,

Преобразование LDAP objectSid из base64 в SSDL

Oct 20, 2021 · 9 мин. на прочтение
Преобразование LDAP objectSid из base64 в SSDL
Поделиться

В этой статье я описываю способ преобразования LDAP objectSid из base64 в привычный формат.

  • Вот так выглядит SID пользователя в Active Directory

    S-1-5-21-2562418665-3218585558-1813906818-1576.

  • Вот так выглядит его бинарное представление

    010500000000000515000000e967bb98d6b7d7bf82051e6c28060000.

  • А хранится он в закодированном виде, в формате base64

    AQUAAAAAAAUVAAAA6We7mNa317+CBR5sKAYAAA==.

В base64 хранятся и многие другие свойства объектов, но, как правило, декодируются они в текстовый формат.

Я привык работать с SID-ами получая их через PowerShell или используя ADUC или ADSIEdit. Их можно увидеть в свойствах объектов. Но когда потребовалось получить SID пользователя в Linux-системе я воспользовался средством ldapsearch и был удивлён, он возвращает не привычный SID, а base64 строку. Оказывается, что SID хранится там не в привычном формате, а в бинарном виде, закодированном в base64. Преобразовать строку из base64 труда не составляет никакого. Но на выходе получаются бинарные данные и они совсем никак не похожи на привычный вид SID. Очевидно, что их нужно как-то к этому виду привести, но как это сделать было не очевидно.

В .NET для этого есть SecurityIdentifier Class, у которого есть метод ToString(), который возвращает SID в привычном формате. Но в моём случае эта библиотека мне была недоступна. Во-первых мне нужно было получать и обрабатывать SID не в Windows среде, во вторых с использованием JavaScript. Я не нашёл готового варианта и решил сделать всё сам. После долгих поисков я нашёл алгоритм в виде bash-скрипта, который выполняет нужные действия. Ниже я привожу скрипт со своими комментариями.

В конце статьи Вы так же найдёте реализацию на JavaScript.

Выглядит громоздко, но по сути, после того как мы декодировали base64 в hexadecimal, нужно просто из массива шестнадцатеричных символов сформировать правильную структуру (массив) и каждый элемент преобразовать из шестнадцатеричного в десятичный.

#!/bin/bash

# Базовое значение objectSid, полученное из LDAP, закодированное Base-64

OBJECT_ID="AQUAAAAAAAUVAAAA6We7mNa317+CBR5sKAYAAA=="

# Декодируем полученный OBJECT_ID hex-dump-ом и сохраняем в массиве
# echo -n (не печатать завершающий символ новой строки)
# base64 -d (decode) -i (при декодировании игнорировать небуквенные символы)
# hexdump -v (заставляет отображать все входные данные) -e (задаёт формат строки)
# в нашем случае мы заставляем hexdump выводить символ группами по 2, разделяя их пробелом и заносим каждую группу в массив. В результате получаем массив, содержащий в каждом элементе по 2 символа.
# "01 05 00 00 00 00 00 05 15 00 00 00 E9 67 BB 98 D6 B7 D7 BF 82 05 1E 6C 28 06 00 00"

G=($(echo -n $OBJECT_ID | base64 -d -i | hexdump -v -e '1/1 " %02X"'))

# SID in HEX
# собираем структуру SID-а по описанному алгоритму (ссылка ниже)и получаем такую строку:
# 01-05-000000000005-15000000-E967BB98-D6B7D7BF-82051E6C-28060000

SID_HEX=${G[0]}-${G[1]}-${G[2]}${G[3]}${G[4]}${G[5]}${G[6]}${G[7]}-${G[8]}${G[9]}${G[10]}${G[11]}-${G[12]}${G[13]}${G[14]}${G[15]}-${G[16]}${G[17]}${G[18]}${G[19]}-${G[20]}${G[21]}${G[22]}${G[23]}-${G[24]}${G[25]}${G[26]}${G[27]}${G[28]}

# SID Structure: https://technet.microsoft.com/en-us/library/cc962011.aspx
# LESA = Little Endian Sub Authority
# BESA = Big Endian Sub Authority
# LERID = Little Endian Relative ID
# BERID = Big Endian Relative ID

# Далее собираем отдельные элементы для будущего преобразования

# просто создаём массивы

BESA2=${G[8]}${G[9]}${G[10]}${G[11]}
BESA3=${G[12]}${G[13]}${G[14]}${G[15]}
BESA4=${G[16]}${G[17]}${G[18]}${G[19]}
BESA5=${G[20]}${G[21]}${G[22]}${G[23]}
BERID=${G[24]}${G[25]}${G[26]}${G[27]}${G[28]}
LESA1=${G[2]}${G[3]}${G[4]}${G[5]}${G[6]}${G[7]}

# ${BESA2:6:2} - вывести из массива 2 символа начиная с шестого. Не забываем, что нумерация идёт с нуля. И заносим их в массив.

LESA2=${BESA2:6:2}${BESA2:4:2}${BESA2:2:2}${BESA2:0:2}
LESA3=${BESA3:6:2}${BESA3:4:2}${BESA3:2:2}${BESA3:0:2}
LESA4=${BESA4:6:2}${BESA4:4:2}${BESA4:2:2}${BESA4:0:2}
LESA5=${BESA5:6:2}${BESA5:4:2}${BESA5:2:2}${BESA5:0:2}
LERID=${BERID:6:2}${BERID:4:2}${BERID:2:2}${BERID:0:2}

#получаем строку вида 000000000005-00000015-98BB67E9-BFD7B7D6-6C1E0582-00000628

LE_SID_HEX=${LESA1}-${LESA2}-${LESA3}-${LESA4}-${LESA5}-${LERID}

# Начальное значение SID, которое используется для создания фактического SID.

SID="S-1"

# Преобразование LE_SID_HEX в десятичные значения и добавление его в SID в виде строки

# IFS - Специальная переменная оболочки IFS определяет, как Bash распознает границы слов при разделении последовательности символьных строк.
# read -r не позволяет обратной косой черте экранировать любые символы
# read -a поместить данные в массив.
# ADDR - имя массива

IFS='-' read -ra ADDR <<< "${LE_SID_HEX}"

#На этом этапе у нас в массиве ADDR следующие значения:
# ADDR[0] - 000000000005
# ADDR[1] - 00000015
# ADDR[2] - 98BB67E9
# ADDR[3] - BFD7B7D6
# ADDR[4] - 6C1E0582
# ADDR[5] - 00000628

#Начинаем их обрабатывать. Символ "собачка" @ в качестве элемента массива - это способ получить все элементы массива. Мы это делаем в цикле, чтобы последовательно обработать каждый элемент.

for OBJECT in "${ADDR[@]}"; do

#$((16#${OBJECT})) - это простое HEX2DEC преобразование - из шестнадцатеричной формы в десятичную.

  SID=${SID}-$((16#${OBJECT}))
  
done

echo ${SID}

#На выходе получаем S-1-5-21-2562418665-3218585558-1813906818-1576

Теперь, зная и понимая алгоритм, не составит труда выполнить эти действия на любом языке программирования. Ниже пример того же самого на JavaScript для Node.js. Код почти не отличается от вышеприведённого, поэтому я не стал его так подробно комментировать.

    sidToString(base64) {
      //Конвертируем строку base64 в Buffer, а потом его преобразуем в HEX
        const buffer = Buffer.from(base64, 'base64')
        const array = buffer.toString('hex') //010500000000000515000000e967bb98d6b7d7bf82051e6c28060000
        const G = array.toString().match(/.{1,2}/g)

        /* G array
        [
          '01', '05', '00', '00', '00',
          '00', '00', '05', '15', '00',
          '00', '00', 'e9', '67', 'bb',
          '98', 'd6', 'b7', 'd7', 'bf',
          '82', '05', '1e', '6c', '28',
          '06', '00', '00'
        ]
        */

        const BESA2=`${G[8]}${G[9]}${G[10]}${G[11]}`
        const BESA3=`${G[12]}${G[13]}${G[14]}${G[15]}`
        const BESA4=`${G[16]}${G[17]}${G[18]}${G[19]}`
        const BESA5=`${G[20]}${G[21]}${G[22]}${G[23]}`
        const BERID=`${G[24]}${G[25]}${G[26]}${G[27]}`
        const LESA1=`${G[2]}${G[3]}${G[4]}${G[5]}${G[6]}${G[7]}`

        const LESA2=`${BESA2.substr(6,2)}${BESA2.substr(4,2)}${BESA2.substr(2,2)}${BESA2.substr(0,2)}`
        const LESA3=`${BESA3.substr(6,2)}${BESA3.substr(4,2)}${BESA3.substr(2,2)}${BESA3.substr(0,2)}`
        const LESA4=`${BESA4.substr(6,2)}${BESA4.substr(4,2)}${BESA4.substr(2,2)}${BESA4.substr(0,2)}`
        const LESA5=`${BESA5.substr(6,2)}${BESA5.substr(4,2)}${BESA5.substr(2,2)}${BESA5.substr(0,2)}`
        const LERID=`${BERID.substr(6,2)}${BERID.substr(4,2)}${BERID.substr(2,2)}${BERID.substr(0,2)}`

        const LE_SID_HEX=`${LESA1}-${LESA2}-${LESA3}-${LESA4}-${LESA5}-${LERID}`

        const ADDR=LE_SID_HEX.split('-')

        const SID = "S-1-" + ADDR.map(x => parseInt(x, 16)).join('-')
        return SID
    }

Ссылка на Gist на Github

Возможно, что рассмотренные примеры можно реализовать как-то иначе. Но в таком виде код лучше читается, что, в свою очередь, упрощает понимания алгоритма. Если вы знаете другие способы преобразования objectSid из base64 в SSDL (Security Descriptor Definition Language) формат, то напишите об этом в комментариях.

Заходите в группу Telegram
Если есть вопросы или хотите пообщаться, то заходите в мою группу Telegram.