- 記事一覧 >
- ブログ記事
icaclsとSDDLファイルを使って大量のアクセス権を高速で付与
はじめに
Windows のアクセス権をコマンドラインで付与できる icacls
コマンドというのがあります。このコマンドを使ってアクセス権を SDDL 形式(Security Descriptor Definition Language)のテキストファイルにダンプできるのですが、そのダンプを使って、リストアもできます。
大量のアクセス権を処理するときは、SDDL 形式のファイルをプログラムで作って、リストアすれば良いのでは?ということで、やってみました。
本記事に SDDL 形式の網羅的な説明は有りません。(別途お調べください。)
複雑なアクセス権の組み合わせを Read,Write,Admin と単純に3分割して、大量にアクセス権を付与したときの一連の作業記録です。
【検証環境】
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
Python 3.8.10
Windows 10 Pro x64
PHP 7.4.26
AD にユーザー登録
「ldapadd ldaps://で AD(Active Directory)のユーザーをパスワード付きで登録」にて、ldapadd で登録できるようになった環境を前提とします。FreeBSD13、php、ldap インストール済みの環境で確認しました。
ここ →https://gist.github.com/itc-lab/2c57c4aadc44f3c50b9b286fe3f4ff82
の php プログラムを使って、AD(ActiveDirectory)に 70200 ユーザー、702 グループ作成し、各々のグループには、100 人ずつ所属するデータを作成します。
TEST001A ~ TEST100A は、A グループ所属
TEST001B ~ TEST100B は、B グループ所属
・・・
TEST001ZZ ~ TEST100ZZ は、ZZ グループ所属
という具合です。
# php create_ad_users_groups_ldif.php > dummy.ldif
# ldapadd -H ldaps://example.contoso.com -x -D "CN=administrator,CN=Users,DC=ad,DC=contoso,DC=com" -w "password" -f dummy.ldif
と ldapadd コマンドに ldif ファイルを読ませて登録完了です。
python 版もあります。→https://gist.github.com/itc-lab/c751e9c731365b744af3f3a27b8c41ad
python 版の場合は、
# python3 create_ad_users_groups_ldif.py > dummy.ldif
# ldapadd -H ldaps://example.contoso.com -x -D "CN=administrator,CN=Users,DC=ad,DC=contoso,DC=com" -w "password" -f dummy.ldif
です。
icacls /grant
まずは、icacls
コマンドの /grant
オプションでアクセス権登録してみます。
Admin 権=フルアクセス可(作成、変更、削除、アクセス権の付け替え可)
Read 権=参照可(フォルダの中身とファイルの内容を見るだけ可)
Write 権=更新可(作成、変更、削除可)
と定義し、
以下のようにします。Admin
: 全グループ Admin 権ありAdmin\aaa
: GroupA ~ GroupML(約半分のグループ)Admin 権ありAdmin\aaa\bbb
: GroupA ~ GroupML の内、TEST001~ TEST005の人(約半分のグループの内5人のメンバー)Admin 権あり
Read
: 全グループ Read 権ありRead\aaa
: GroupA ~ GroupML(約半分のグループ)Read 権ありRead\aaa\bbb
: GroupA ~ GroupML の内、TEST001~ TEST005の人(約半分のグループの内5人のメンバー)Read 権あり
Write
: 全グループ Write 権ありWrite\aaa
: GroupA ~ GroupML(約半分のグループ)Write 権ありWrite\aaa\bbb
: GroupA ~ GroupML の内、TEST001~ TEST005の人(約半分のグループの内5人のメンバー)Write 権あり
まず、普通に付与すると、継承されるのがデフォルトになっているため、継承の無効化を行います。
コマンドでは以下のように行います。
> icacls \\192.168.12.219\Share\Admin /inheritance:d
> icacls \\192.168.12.219\Share\Admin\aaa /inheritance:d
> icacls \\192.168.12.219\Share\Admin\aaa\bbb /inheritance:d
> icacls \\192.168.12.219\Share\Write /inheritance:d
> icacls \\192.168.12.219\Share\Write\aaa /inheritance:d
> icacls \\192.168.12.219\Share\Write\aaa\bbb /inheritance:d
> icacls \\192.168.12.219\Share\Read /inheritance:d
> icacls \\192.168.12.219\Share\Read\aaa /inheritance:d
> icacls \\192.168.12.219\Share\Read\aaa\bbb /inheritance:d
【icaclsヘルプ】
/inheritance:e|d|r
e - 継承を有効にします。
d - 継承を無効にし、ACE をコピーします。
r - 継承された ACE をすべて削除します。
以下の要領で1フォルダずつ付与していく方法を試します。
●Admin 権icacls \\192.168.12.219\Share\Admin /grant AD\GroupA:(OI)(CI)(F)
(OI) - オブジェクト継承 - ファイルに継承される
(CI) - コンテナー継承 - フォルダーに継承される
(OI)(CI) - 適用先=このフォルダー、サブフォルダーおよびファイル
F - フル アクセス権
●Read 権icacls \\192.168.12.219\Share\Read /grant AD\GroupA:(OI)(RX)
(OI) - オブジェクト継承 - このフォルダーとファイル - ファイルに継承される
RX - 読み取りと実行のアクセス権
●Write 権icacls \\192.168.12.219\Share\Write /grant AD\GroupA:(OI)(RX,W,D)
(OI) - オブジェクト継承 - このフォルダーとファイル - ファイルに継承される
RX - 読み取りと実行のアクセス権
W - 書き込み専用アクセス権
D - 削除アクセス権
バッチファイルに書き入れます。※手で書くと大変なので、プログラムで作成しました。
バッチファイルを実行して、icacls
を計 8406 回実行します。
実行ユーザーは、\\192.168.12.219\Share に対して、Administrators権限があるユーザーです。(以降も同様)
実行時間は、5分でした。
無事付与されているようです。
【 エントリの最大数について】
最初もっと大量にアクセス権を登録していたのですが、途中でエラーになりました。
C:\Users\kanri>icacls \\192.168.12.219\Share\Read\aaa\bbb /grant AD\TEST001GT:(OI)(RX)
\\192.168.12.219\Share\Read\aaa\bbb: パラメーターが間違っています。
0 個のファイルが正常に処理されました。1 個のファイルを処理できませんでした
原因は、以下のようにエントリ最大数に抵触していました。
↓
アクセス制御リスト内のアクセス制御エントリの最大数https://docs.microsoft.com/ja-jp/troubleshoot/windows-server/windows-security/error-add-user-to-security-permissions
「ACL の最大サイズは 64 キロバイト (KB) または約 1,820 ACEs です。」とあります。
実際に数えてみると、1フォルダに1820 件目を登録しようとしたときにエラーになりました。"約"と書かれているため、1820 件前後が限界と思われます。
ダンプリストア
icacls
を計 8406 回実行して付与したアクセス権をダンプします。
> icacls \\192.168.12.219\Share\* /save perms.txt /t /c
【icaclsヘルプ】
ICACLS <名前> /save
[/T] [/C] [/L] [/Q] 名前が一致するすべてのファイルとフォルダーの DACL を <ACL ファイル> に
格納して、後で /restore で指定できるようにします。SACL、所有者、整合性
ラベルは保存されません。
/T は、<名前> で指定されたディレクトリ以下のすべての一致するファイルと
ディレクトリに対してこの処理が実行されることを指定します。
/C は、どのようなファイル エラーが発生してもこの処理が続行されることを
指定します。ただしエラー メッセージは表示されます。
いろいろ書かれているファイルが得られます。
ダンプファイルの文字コードは、UTF-16LEです。エディタによっては文字化けする可能性があります。
一旦内容に関しては気にせず、リストアしてみます。
※付与先フォルダは、削除して、同じ形で再作成しておきます。
ダンプファイルをリストアします。
リストアに関しては、cmd.exeを管理者権限で起動が必要です。
Administrators所属であっても以下のエラーで起動できません。
C:\Users\kanri>icacls \\192.168.12.219\ /restore perms.txt
参照された特権またはグループのうち、一部の特権が呼び出し側に割り当てられていません。
0 個のファイルが正常に処理されました。0 個のファイルを処理できませんでした
cmd.exe
を管理者として実行後、以下のようにリストアします。
C:\Users\kanri>icacls \\192.168.12.219\Share\ /restore perms.txt
処理ファイル: \\192.168.12.219\Share\Admin
処理ファイル: \\192.168.12.219\Share\Read
処理ファイル: \\192.168.12.219\Share\Write
処理ファイル: \\192.168.12.219\Share\Admin\aaa
処理ファイル: \\192.168.12.219\Share\Admin\aaa\bbb
処理ファイル: \\192.168.12.219\Share\Read\aaa
処理ファイル: \\192.168.12.219\Share\Read\aaa\bbb
処理ファイル: \\192.168.12.219\Share\Write\aaa
処理ファイル: \\192.168.12.219\Share\Write\aaa\bbb
10 個のファイルが正常に処理されました。0 個のファイルを処理できませんでした
icacls /grant
コマンドを入力した場合、約5分かかりましたが、ほぼ一瞬で終わります。
【icaclsヘルプ】
ICACLS <ディレクトリ> [/substitute <旧 SID> <新 SID> [...]] /restore
[/C] [/L] [/Q]
格納されている DACL を <ディレクトリ> 内のファイルに適用します。
無事付与されているようです。
SDDL 紐解き
先ほど、一旦気にせずリストアしてみましたが、ダンプの内容について、改めて見てみます。
Admin
D:PAI(A;OICI;FA;;;S-1-5-21-・・・)(A;OICI;FA;・・・
となっています。
この部分を紐解いていきます。
(参考:https://atmarkit.itmedia.co.jp/ait/articles/0603/25/news016.html
)
Admin
処理対象フォルダ名です。icacls \\192.168.12.219\Share\ /restore
の場合、\\192.168.12.219\Share\Admin
を指します。
D:
DACL(Discretionary Access Control List)のフラグです。他にもO:owner_sid
など別のフラグもありますが、今回は、D:
しか登場しません。オブジェクトに対するアクセスの許可、あるいは拒否のアクセスエントリ一覧を指します。
→ 要するに、DACL は、ファイル・フォルダにつけられたアクセス権リストのようなものです。今回、これを書き換えるからD:
です。
PAI
D:
に対してのフラグ(オプション)です。
フラグ | 値 | 意味 |
---|---|---|
P | SE_DACL_PROTECTED | DACL が継承された ACE によって変更されないように保護する |
AR | SE_DACL_AUTO_INHERIT_REQ | DACL を子オブジェクトへ継承するように要求する |
AI | SE_DACL_AUTO_INHERITED | 継承によって作成された DACL であることを示す |
P: P
無しで、AI
の場合、SDDL に記載のアクセス権以外にも親フォルダのアクセス権も引き継ぐという意味になります。
AI: AI
無しで、P
だけの場合、どうなのかと言うと、違いが実感できるケースがありませんでした。さらに、P
だけでリストアして、ダンプすると、PAI
になっていました。
PAI: 上記事実により、要するに、「アクセス権を親フォルダから引き継がない」という意味で良いと思われます。
(A;
フラグ | 値 | 意味 |
---|---|---|
A | SDDL_ACCESS_ALLOWED | アクセス許可 |
の設定をするという意味です。他にも拒否だったり、有りますが、今回 A
しか登場しません。
;OICI;
アクセス権付与対象を示します。
フラグ | 値 | 意味 |
---|---|---|
CI | SDDL_CONTAINER_INHERIT | コンテナ継承 |
OI | SDDL_OBJECT_INHERIT | オブジェクト継承 |
で、icacls
コマンドで出てきた通り、(OI)(CI)
- 適用先=このフォルダー、サブフォルダーおよびファイル
の意味です。
;FA;
フラグ | 値 | 意味 |
---|---|---|
FA | SDDL_FILE_ALL | 全アクセス権(読み書き実行) |
で、アクセス権付与権限含む、全アクセス権です。
;;;
Active Directory のオブジェクトの ACL を変更するときに使う欄で、普通のアクセス権付与では空欄です。(今回、ここの書き方、結果については省略します。)
S-1-5-21-・・・
アカウント SID です。
なお、最初から SID が決まっているアカウントもあり、例えば、Administrators は、BA
です。
後は、各アカウント毎に括弧で囲われている部分を列挙すれば、良いです。※上述したように、1820 件前後までの上限があります。
Read 権D:PAI(A;OI;0x1200a9;;;S-1-5-・・・)
Write 権D:PAI(A;OI;0x1301bf;;;S-1-5-・・・)
となっています。(OI)
=このフォルダとファイル
で配下のフォルダまで伝わらないようにしています。0x1200a9
、0x1301bf
に関しては、先ほどの Admin 権のときは、FA
でしたが、フラグの論理和で指定されていて、Read 権は、
値 | 定数名 |
---|---|
00120089 | FILE_GENERIC_READ |
001200A0 | FILE_GENERIC_EXECUTE |
から論理和を取って、FILE_GENERIC_READ
| FILE_GENERIC_EXECUTE
= 0x1200a9
すなわち、icacls
コマンドで指定した(RX)
- 読み取りと実行のアクセス権
と同じ意味です。ただし、この部分に"RX"とは書けません。
Write権は、
値 | 定数名 |
---|---|
00010000 | DELETE |
00120089 | FILE_GENERIC_READ |
00120116 | FILE_GENERIC_WRITE |
001200A0 | FILE_GENERIC_EXECUTE |
から論理和を取って、DELETE
| FILE_GENERIC_READ
| FILE_GENERIC_WRITE
| FILE_GENERIC_EXECUTE
= 0x1301bf
となります。
すなわち、icacls
コマンドで指定した(RX,W,D)
RX
- 読み取りと実行のアクセス権
W
- 書き込み専用アクセス権
D
- 削除アクセス権
と同じ意味です。ただし、この部分に"RX,W,D"とは書けません。
SDDL 形式ファイル生成
ダンプ → SDDL 形式のファイルを出力 → リストア
とやってきましたが、最初から SDDL 形式のファイルが有れば、最速で大量のアクセス権を付与できます。
ただ、SDDL 形式のファイルを書いていくのは辛いですので、以下のフォーマットのテキストから SDDL 形式のファイルを出力するプログラムを作成しました。
[フォルダ]<tab>[admin or read or write]<tab>[ログインID or グループ名(sAMAccountName)]
・・・フォルダ数、アカウント数分改行区切りで列挙・・・
このフォーマットに決めたとき、SID が分からないといけないことに気付きました。ldapsearch で得るという手もありますが、LDAP の環境が必要になるのと、Windows 内部処理ではそんなことをしていないはずです。
アカウント名から SID が引ける LookupAccountName
という Windows API 関数を見つけましたので、C++で実装し、nametosid.exe [アカウント文字列]
で SID 文字列が返るようにしました。
nametosid.exe
のソースコードについては、こちらに公開しています。
実行例:
C:\Users\kanri>nametosid kanri@ad
S-1-5-21-312109860-4223824132-3422679046-73503
C:\Users\kanri>nametosid TEST001A@ad
S-1-5-21-312109860-4223824132-3422679046-2601
以下の Windows 版 php をC:\php
に配置したものとします。https://windows.php.net/downloads/releases/php-7.4.26-nts-Win32-vc15-x64.zip
Visual C++ 再頒布可能パッケージ(
vc_redist.x64.exe
)のインストールが必要です。→ダウンロード先リンクインストールされていない場合、以下のエラーになります。
VCRUNTIME140.dllが見つからないため、コードの実行を続行できません。プログラムを再インストールすると、この問題が解決する可能性があります。
なお、古いVisual Studio 2015 の Visual C++ 再頒布可能パッケージの場合、以下のエラーになりました。上のリンクの新しい方をインストールする必要があります。
PHP Warning: 'vcruntime140.dll' 14.0 is not compatible with this PHP build linked with 14.16 in Unknown on line 0
php.exeと同じところかパスが通るところにphp.iniを置いて、
extension=mbstring
の設定が必要です。
php create_sddl_from_list.php [出力先SDDL形式ファイル] [アクセス権リスト(上記フォーマット)]
でSDDL形式ファイルを出力するphpプログラムです。
ソースコードについては、こちらに公開しています。https://gist.github.com/itc-lab/74ac472398abcd352205a2207c9dd214
読み込ませる acl.txt を作成します。
起動します。
C:\Users\kanri>c:\php\php.exe create_sddl_from_list.php sddl.list acl.txt
何もエラーにならず、sddl.list も無事作成されています。
SDDL 形式ファイル読み込み
icacls /restore
を実行します。
※test\aaa\bbb フォルダは作成しておきます。
c:\Users\kanri>icacls \\192.168.12.219\Share\ /restore sddl.list
処理ファイル: \\192.168.12.219\Share\test
処理ファイル: \\192.168.12.219\Share\test\aaa
処理ファイル: \\192.168.12.219\Share\test\aaa\bbb
3 個のファイルが正常に処理されました。0 個のファイルを処理できませんでした
問題無く動きました。
確認してみます。
ヨシ!
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。