- 記事一覧 >
- ブログ記事

AD(Active Directory)の中身を覗いてみた - ldapsearch,php,PowerShell
はじめに
WindowsのAD(Active Directory)は、LDAPのように、ツリー構造のデータベースで管理されています。そのため、OpenLDAPのldapsearchコマンドで中身を見られます。
ldapsearch以外にもいろいろな方法で中身を見てみました。
Active Directoryとは?LDAPとは?ldapsearchとは?の説明は割愛します。
【検証環境】
Windows Server 2019 Datacenter Desktop
ドメインコントローラーに昇格したActive Directory
FreeBSD13(本ブログ別記事「FreeBSD-13.0-RELEASEにapache2,php,postgresql,openldapをインストール」まで実施したサーバーです。)
OpenLDAP: ldapsearch 2.4.59
PHP 7.4.23
以下、全て、 ユーザー:dn: CN=山田 太郎,CN=Users,DC=ad,DC=contoso,DC=com を見ていきます。画面で入力できるところは、適当にできるだけ埋めました。
ldapsearchで見る
ADがあるWindows Server 2019とは別のFreeBSD13のサーバーからldapsearchコマンドで見てみます。
dc=ad,dc=contoso,dc=com
部分は、ドメイン名によります。今回は、ドメインがad.contoso.comのため、こうなります。cn=administrator,cn=users,dc=ad,dc=contoso,dc=com
は、ADサーバーのAdministratorのことです。password
は、ADがあるWindows Server 2019のAdministratorのパスワード192.168.12.219
は、ADがあるWindows Server 2019のIPアドレスです。
# ldapsearch -x -D "cn=administrator,cn=users,dc=ad,dc=contoso,dc=com" -w "password" -h "192.168.12.219" -b "CN=山田 太郎,CN=Users,DC=ad,DC=contoso,DC=com" -s base
# extended LDIF
#
# LDAPv3
# base <CN=山田 太郎,CN=Users,DC=ad,DC=contoso,DC=com> with scope baseObject
# filter: (objectclass=*)
# requesting: ALL
#
# \E5\B1\B1\E7\94\B0 \E5\A4\AA\E9\83\8E, Users, ad.contoso.com
dn:: Q0495bGx55SwIOWkqumDjixDTj1Vc2VycyxEQz1hZCxEQz1jb250b3NvLERDPWNvbQ==
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn:: 5bGx55SwIOWkqumDjg==
sn:: 5bGx55Sw
c: JP
l:: 6LGK55Sw5biC4peL4peL55S6
st:: 5oSb55+l55yM
title:: 6YOo6ZW3
description:: 5bGx55SwIOWkqumDjuOBruiqrOaYjg==
postalCode: 000-0000
postOfficeBox:: 56eB5pu4566xWA==
physicalDeliveryOfficeName:: 5bGx55SwIOWkqumDjuOBruS6i+alreaJgA==
telephoneNumber: 090-0000-0000
givenName:: 5aSq6YOO
・・・(略)・・・
objectSid:: AQUAAAAAAAUVAAAAJGuaEgRxwvsG8AHMTwQAAA==
adminCount: 1
accountExpires: 132854364000000000
logonCount: 0
sAMAccountName: yamada.taro
sAMAccountType: 805306368
otherFacsimileTelephoneNumber: 000-000-0001
otherMobile: 090-0000-0000
managedObjects:: Q04944Kw44Or44O844OXWCxDTj1Vc2VycyxEQz1hZCxEQz1jb250b3NvLERDP
WNvbQ==
userPrincipalName: yamada.taro@ad.contoso.com
otherIpPhone: 000-000-00002
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=ad,DC=contoso,DC=com
dSCorePropagationData: 20210923090647.0Z
dSCorePropagationData: 16010101000000.0Z
msDS-PhoneticFirstName:: 44K/44Ot44Km
msDS-PhoneticLastName:: 44Ok44Oe44OA
msDS-PhoneticDepartment:: 44Kx44Kk44Ko44Kk44Kt44Kr44Kv44OW
msDS-PhoneticCompanyName:: 44Kr44OW44K344Kt44Ks44Kk44K344Oj44Oe44Or44Oe44Or
msDS-PhoneticDisplayName:: 44Ok44Oe44OAIOOCv+ODreOCpg==
mail: yamada.taro@contoso.com
manager:: Q04955Sw5LitIOS4iuWPuCxDTj1Vc2VycyxEQz1hZCxEQz1jb250b3NvLERDPWNvbQ==
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
2バイト文字が有るところは、base64になっています。
また、objectSidその他、バイナリデータが格納されているところもbase64になっています。ちなみに、当たり前ですが、バイナリデータをbase64デコードして表示すると文字化けするだけで、読めません。
バイナリデータが文字化けしても構わない場合、以下のようにbase64部分をデコードできます。
# ldapsearch -x -D "cn=administrator,cn=users,dc=ad,dc=contoso,dc=com" -w "password" -h "192.168.12.219" -b "CN=山田 太郎,CN=Users,DC=ad,DC=contoso,DC=com" -s base | perl -MMIME::Base64 -MEncode=decode -n -00 -e 's/\n +//g;s/(?<=:: )(\S+)/decode("UTF-8",decode_base64($1))/eg;binmode(STDOUT, ":utf8");print'
# extended LDIF
#
# LDAPv3
# base <CN=文字化け,CN=Users,DC=ad,DC=contoso,DC=com> with scope baseObject
# filter: (objectclass=*)
# requesting: ALL
#
# \E5\B1\B1\E7\94\B0 \E5\A4\AA\E9\83\8E, Users, ad.contoso.com
dn:: CN=山田 太郎,CN=Users,DC=ad,DC=contoso,DC=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn:: 山田 太郎
sn:: 山田
c: JP
l:: 豊田市○○町
st:: 愛知県
title:: 部長
description:: 山田 太郎の説明
postalCode: 000-0000
postOfficeBox:: 私書箱X
physicalDeliveryOfficeName:: 山田 太郎の事業所
telephoneNumber: 090-0000-0000
givenName:: 太郎
・・・(略)・・・
objectSid:: 文字化け
adminCount: 1
accountExpires: 132854364000000000
logonCount: 0
sAMAccountName: yamada.taro
sAMAccountType: 805306368
otherFacsimileTelephoneNumber: 000-000-0001
otherMobile: 090-0000-0000
managedObjects:: CN=グループX,CN=Users,DC=ad,DC=contoso,DC=com
userPrincipalName: yamada.taro@ad.contoso.com
otherIpPhone: 000-000-00002
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=ad,DC=contoso,DC=com
dSCorePropagationData: 20210923090647.0Z
dSCorePropagationData: 16010101000000.0Z
msDS-PhoneticFirstName:: タロウ
msDS-PhoneticLastName:: ヤマダ
msDS-PhoneticDepartment:: ケイエイキカクブ
msDS-PhoneticCompanyName:: カブシキガイシャマルマル
msDS-PhoneticDisplayName:: ヤマダ タロウ
mail: yamada.taro@contoso.com
manager:: CN=田中 上司,CN=Users,DC=ad,DC=contoso,DC=com
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
phpで見る
ldap_search関数等、ldap_*関数で検索するプログラムを作りました。
※dc=ad,dc=contoso,dc=com以下全てのエントリが表示されます。
なお、objectSid、objectGUIDは、バイナリデータですが、特殊な仕様のため、仕様に合わせた文字列化処理が必要でした。
objectSidは、https://stackoverflow.com/questions/39533560/php-ldap-get-user-sidのfunction bin_to_str_sid($binary_sid) {
を
objectGUIDは、https://www.null-byte.org/development/php-active-directory-ldap-authentication/のpublic static function GUIDtoString($ADguid)
を実装して、正しく動作しました。(あらゆるケースで正しいかは不明です。)
また、userParametersもバイナリデータですが、かなり特殊な仕様になっていて、なんとか確認できましたが、長くなるため、以下のphpは、bin2hex
しているだけです。詳細は、下記「userParametersについて」を参照してください。
その他のバイナリデータについてもbin2hex
しているだけになります。
ad.contoso.com
は、ADがあるWindows Server 2019のサーバー名です。(名前解決が必要です。)password
は、ADがあるWindows Server 2019のAdministratorのパスワード$ldaprdn = 'ad' . "\\" . $username;
は、ドメイン名\ユーザー名
です。このままの場合、ad\Administrator
になります。dc=ad,dc=contoso,dc=com
部分は、ドメイン名によります。今回は、ドメインがad.contoso.comのため、こうなります。
<?php
$adServer = "ldap://ad.contoso.com";
$ldap = ldap_connect($adServer);
$username = "Administrator";
$password = "password";
$ldaprdn = 'ad' . "\\" . $username;
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
$bind = ldap_bind($ldap, $ldaprdn, $password);
if ($bind) {
$filter = "objectclass=*";
$rt = ldap_search($ldap, "dc=ad,dc=contoso,dc=com", $filter);
$results = array();
$entry_id = @ldap_first_entry($ldap, $rt);
while ($entry_id != false) {
$result = array();
$vals = @ldap_get_attributes($ldap, $entry_id);
$attrnum = intval($vals["count"]);
for ($n = 0; $n < $attrnum; $n ++) {
$attr = $vals[$n];
$key = strtolower($attr);
$num = intval($vals[$attr]["count"]);
if ($num != 0) {
$result[$key] = array();
for ($no = 0; $no < $num; $no ++) {
$value = $vals[$attr][$no];
$result[$key][$no] = $value;
}
if (isset($result[$key]) && count($result[$key]) == 1) {
$result[$key] = $result[$key][0];
}
}
}
$result["0dn"] = @ldap_get_dn($ldap, $entry_id);
Bin2String($result);
$results[] = $result;
$entry_id = @ldap_next_entry($ldap, $entry_id);
}
@ldap_close($ldap);
foreach ($results as $result) {
ksort($result);
foreach ($result as $key => $value) {
$attr = strcmp($key, "0dn") !== 0 ? $key : "dn";
if (is_array($value)) {
echo $attr.": ".implode("; ", $value).";\n";
} else {
echo $attr.": ".$value.";\n";
}
}
echo "\n";
}
} else {
$msg = "Invalid bind dn / password";
echo $msg;
}
function Bin2String(&$result)
{
$conv_func_attrs["SIDtoString"] = array("objectsid");
$conv_func_attrs["GUIDtoString"] = array("objectguid", "msdfsr-replicationgroupguid", "msdfsr-contentsetguid");
$conv_func_attrs["bin2hex"] = array("auditingpolicy", "dsasignature", "dnsrecord", "ipsecdata", "samdomainupdates", "msds-generationid", "userparameters");
foreach ($conv_func_attrs as $func => $attrs) {
foreach ($attrs as $attr) {
if (isset($result[$attr]) && !empty($result[$attr])) {
$result[$attr] = @$func($result[$attr]);
}
}
}
}
function SIDtoString($value)
{
$sid = @unpack('C1rev/C1count/x2/N1id', $value);
$subAuthorities = [];
if (!isset($sid['id']) || !isset($sid['rev'])) {
throw new \UnexpectedValueException(
'The revision level or identifier authority was not found when decoding the SID.'
);
}
$revisionLevel = $sid['rev'];
$identifierAuthority = $sid['id'];
$subs = isset($sid['count']) ? $sid['count'] : 0;
for ($i = 0; $i < $subs; $i++) {
$subAuthorities[] = unpack('V1sub', hex2bin(substr(bin2hex($value), 16 + ($i * 8), 8)))['sub'];
}
return 'S-'.$revisionLevel.'-'.$identifierAuthority.implode(
preg_filter('/^/', '-', $subAuthorities)
);
}
function GUIDtoString($ADguid)
{
$guidinhex = str_split(bin2hex($ADguid), 2);
$guid = "";
$first = array_reverse(array_slice($guidinhex, 0, 4));
foreach ($first as $value) {
$guid .= $value;
}
$guid .= "-";
$second = array_reverse(array_slice($guidinhex, 4, 2, true), true);
foreach ($second as $value) {
$guid .= $value;
}
$guid .= "-";
$third = array_reverse(array_slice($guidinhex, 6, 2, true), true);
foreach ($third as $value) {
$guid .= $value;
}
$guid .= "-";
$fourth = array_slice($guidinhex, 8, 2, true);
foreach ($fourth as $value) {
$guid .= $value;
}
$guid .= "-";
$last = array_slice($guidinhex, 10, 16, true);
foreach ($last as $value) {
$guid .= $value;
}
return $guid;
}
実行してみます。
※実際には、dc=ad,dc=contoso,dc=com以下全てのエントリが表示されますが、抜粋して1エントリだけの結果例です。
# vi ADSearch.php
(上記プログラムを記述して上書き)
# php ADSearch.php
・・・(他のエントリについては略)・・・
dn: CN=山田 太郎,CN=Users,DC=ad,DC=contoso,DC=com;
accountexpires: 132854364000000000;
admincount: 1;
badpasswordtime: 0;
badpwdcount: 0;
c: JP;
cn: 山田 太郎;
co: 日本;
codepage: 0;
company: 株式会社丸丸;
countrycode: 392;
department: 経営企画部;
description: 山田 太郎の説明;
directreports: CN=佐藤 部下,CN=Users,DC=ad,DC=contoso,DC=com;
displayname: 山田 太郎;
distinguishedname: CN=山田 太郎,CN=Users,DC=ad,DC=contoso,DC=com;
dscorepropagationdata: 20210923090647.0Z; 16010101000000.0Z;
givenname: 太郎;
homedirectory: C:\profile\yamadashome;
info: 電話
メモ;
instancetype: 4;
l: 豊田市○○町;
lastlogoff: 0;
lastlogon: 0;
logoncount: 0;
mail: yamada.taro@contoso.com;
managedobjects: CN=グループX,CN=Users,DC=ad,DC=contoso,DC=com;
manager: CN=田中 上司,CN=Users,DC=ad,DC=contoso,DC=com;
memberof: CN=グループX,CN=Users,DC=ad,DC=contoso,DC=com;
msds-phoneticcompanyname: カブシキガイシャマルマル;
msds-phoneticdepartment: ケイエイキカクブ;
msds-phoneticdisplayname: ヤマダ タロウ;
msds-phoneticfirstname: タロウ;
msds-phoneticlastname: ヤマダ;
name: 山田 太郎;
objectcategory: CN=Person,CN=Schema,CN=Configuration,DC=ad,DC=contoso,DC=com;
objectclass: top; person; organizationalPerson; user;
objectguid: 2226c0e2-03df-4696-a09c-8c8e051b2b66;
objectsid: S-1-5-21-312109860-4223824132-3422679046-1103;
otherfacsimiletelephonenumber: 000-000-0001;
otherhomephone: 000-0000-0000;
otheripphone: 000-000-00002;
othermobile: 090-0000-0000;
otherpager: 000-0000-0000;
physicaldeliveryofficename: 山田 太郎の事業所;
postalcode: 000-0000;
postofficebox: 私書箱X;
primarygroupid: 513;
profilepath: \\192.168.12.219\profile\yamada.taro;
pwdlastset: 132768645397378719;
samaccountname: yamada.taro;
samaccounttype: 805306368;
scriptpath: C:\Windows\SYSVOL\domain\scripts\test.bat;
sn: 山田;
st: 愛知県;
streetaddress: 1-1;
telephonenumber: 090-0000-0000;
title: 部長;
useraccountcontrol: 512;
userparameters: ・・・16進数表記文字列(略)・・・
userprincipalname: yamada.taro@ad.contoso.com;
usnchanged: 16472;
usncreated: 16408;
whenchanged: 20210923095937.0Z;
whencreated: 20210923082520.0Z;
wwwhomepage: https://www.contoso.com/;
・・・(他のエントリについては略)・・・
日本語が表示されています。
objectSid、objectGUIDが文字列で表示されています。
userParametersが特殊な仕様のバイナリデータのため、bin2hex
で16進数表記で表示しています。詳細は、下記「userParametersについて」を参照してください。
LDPで見る
Windows Server 2019に最初から入っている LDP というツールを使って、Active Directoryのデータを見ることができます。それを使って見てみます。
Administratorでログインしているとします。
ファイル名を指定して実行のところに ldp を入力して起動します。
「接続」→「接続」をクリックします。
サーバー:localhost
ポート:389
とし、「OK」をクリックします。
「接続」→「バインド」をクリックします。
バインドの種類:現在のログオン ユーザーとしてバインド
バインド後トラフィックを暗号化するにチェック
とし、「OK」をクリックします。
「表示」→「ツリー」をクリックします。
ベースDN:CN=Users,DC=ad,DC=contoso,DC=com
とし、「OK」をクリックします。
あとは、左側ペインのツリーを開いて、見たいエントリをダブルクリックになります。
日本語が表示されています。
objectSid、objectGUIDが文字列で表示されています。
userParametersが<ldp: Binary blob 1370 bytes>
となっていて、中身が分かりません。sAMAccountType: 805306368 = ( NORMAL_USER_ACCOUNT );
など、独自の表記になっている部分があります。
LDPでOpenLDAPにも繋いでみましたが、接続、閲覧できました。ただ、詳しく調べていませんが、ハングアップしました。
PowerShellで見る
PowerShellのコマンドレットGet-ADUser
で見てみます。
いろいろな検索方法がありますが、SamAccountName
がyamada.taro
のプロパティを全て表示するという意味の
Get-ADUser yamada.taro -Properties *
を実行します。
日本語が表示されています。
objectSid、objectGUIDが文字列で表示されています。
userParametersは文字化けしていて、中身が分かりません。
userParametersについて
こちら(stackoverflow)のclass TSProperty
とfunction userParameters($userParameters){
で抽出してみましたが、どちらも同じ結果で、一部読めるところがありますが、文字化けしました。
※phpのバージョンなどの影響が有るかもしれませんが、これ以上調査していません。
userParametersをphpで解析してprint_rした結果:
PowerShellで書かれているこちら(gist.github.com)を見つけました。ConvertFrom-UserParameter.ps1
をパスが通るところに置いて実行します。
. ConvertFrom-UserParameter.ps1
で読み込んで、function ConvertFrom-UserParameter {
を実行可能とし、(Get-ADUser yamada.taro -Properties "userParameters").userParameters
は、Get-ADUser yamada.taro -Properties "userParameters"
でuserParametersだけの属性、値のペアを取り出して、.userParameters
で値だけ取り出しています。
それをパイプで、function ConvertFrom-UserParameter
の引数として渡しています。
PS C:\Users\Administrator> . ConvertFrom-UserParameter.ps1
PS C:\Users\Administrator> (Get-ADUser yamada.taro -Properties "userParameters").userParameters | ConvertFrom-UserParameter -ShowAll
Name Value
---- -----
CtxCfgPresent 1428032432
CtxCfgFlags1 {INHERITMAXDISCONNECTIONTIME, DISABLECCM, INHERITAUTOCLIENT, INHERITMAXIDLETIME...}
CtxShadow EnableInputNotify
CtxMinEncryptionLevel 1
CtxWorkDirectory C:\Windows\system32
CtxWorkDirectoryW C:\Windows\system32
CtxWFHomeDir C:\RDSprofile\yamadashome
CtxWFHomeDirW C:\RDSprofile\yamadashome
CtxWFProfilePath \\192.168.12.219\RDSprofile\yamada.taro
CtxWFProfilePathW \\192.168.12.219\RDSprofile\yamada.taro
CtxInitialProgram C:\Windows\system32\notepad.exe
CtxInitialProgramW C:\Windows\system32\notepad.exe
表示されました!

その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。