1. 記事一覧 >
  2. ブログ記事
Windows
category logo

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 を見ていきます。画面で入力できるところは、適当にできるだけ埋めました。

ユーザー: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-sidfunction 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 を入力して起動します。

ファイル名を指定して実行のところに ldp を入力して起動


「接続」→「接続」をクリックします。

「接続」→「接続」をクリック


サーバー:localhost
ポート:389
とし、「OK」をクリックします。

サーバーポート


「接続」→「バインド」をクリックします。

「接続」→「バインド」をクリック


バインドの種類:現在のログオン ユーザーとしてバインド
バインド後トラフィックを暗号化するにチェック
とし、「OK」をクリックします。

バインドの種類バインド後トラフィックを暗号化するにチェック


「表示」→「ツリー」をクリックします。

「表示」→「ツリー」をクリック


ベースDN:CN=Users,DC=ad,DC=contoso,DC=com
とし、「OK」をクリックします。

ベースDN


あとは、左側ペインのツリーを開いて、見たいエントリをダブルクリックになります。

左側ペインのツリーを開いて、見たいエントリをダブルクリック


エントリをダブルクリック 下側


日本語が表示されています。
objectSid、objectGUIDが文字列で表示されています。
userParametersが<ldp: Binary blob 1370 bytes>となっていて、中身が分かりません。
sAMAccountType: 805306368 = ( NORMAL_USER_ACCOUNT );など、独自の表記になっている部分があります。


LDPでOpenLDAPにも繋いでみましたが、接続、閲覧できました。ただ、詳しく調べていませんが、ハングアップしました。


PowerShellで見る

PowerShellのコマンドレットGet-ADUserで見てみます。
いろいろな検索方法がありますが、
SamAccountNameyamada.taroのプロパティを全て表示するという意味の

Get-ADUser yamada.taro -Properties *

を実行します。

Get-ADUser yamada.taro -Properties *


Get-ADUser yamada.taro -Properties * 下側


日本語が表示されています。
objectSid、objectGUIDが文字列で表示されています。
userParametersは文字化けしていて、中身が分かりません。


userParametersについて

こちら(stackoverflow)class TSPropertyfunction userParameters($userParameters){で抽出してみましたが、どちらも同じ結果で、一部読めるところがありますが、文字化けしました。
※phpのバージョンなどの影響が有るかもしれませんが、これ以上調査していません。


userParametersをphpで解析してprint_rした結果:

userParametersを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

表示されました!