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