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

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 ファイルを読ませて登録完了です。

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分でした。


無事付与されているようです。

icaclsでアクセス権付与結果


【 エントリの最大数について】
最初もっと大量にアクセス権を登録していたのですが、途中でエラーになりました。

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 は、どのようなファイル エラーが発生してもこの処理が続行されることを

指定します。ただしエラー メッセージは表示されます。

いろいろ書かれているファイルが得られます。

icaclsダンプ結果

ダンプファイルの文字コードは、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 を <ディレクトリ> 内のファイルに適用します。

無事付与されているようです。

icaclsでアクセス権付与結果 リストア


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:に対してのフラグ(オプション)です。

フラグ意味
PSE_DACL_PROTECTEDDACL が継承された ACE によって変更されないように保護する
ARSE_DACL_AUTO_INHERIT_REQDACL を子オブジェクトへ継承するように要求する
AISE_DACL_AUTO_INHERITED継承によって作成された DACL であることを示す

PP無しで、AIの場合、SDDL に記載のアクセス権以外にも親フォルダのアクセス権も引き継ぐという意味になります。
AIAI無しで、Pだけの場合、どうなのかと言うと、違いが実感できるケースがありませんでした。さらに、Pだけでリストアして、ダンプすると、PAIになっていました。
PAI: 上記事実により、要するに、「アクセス権を親フォルダから引き継がない」という意味で良いと思われます。


(A;

フラグ意味
ASDDL_ACCESS_ALLOWEDアクセス許可

の設定をするという意味です。他にも拒否だったり、有りますが、今回 A しか登場しません。


;OICI;
アクセス権付与対象を示します。

フラグ意味
CISDDL_CONTAINER_INHERITコンテナ継承
OISDDL_OBJECT_INHERITオブジェクト継承

で、icacls コマンドで出てきた通り、
(OI)(CI) - 適用先=このフォルダー、サブフォルダーおよびファイル
の意味です。


;FA;

フラグ意味
FASDDL_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)=このフォルダとファイル
で配下のフォルダまで伝わらないようにしています。
0x1200a90x1301bfに関しては、先ほどの Admin 権のときは、FAでしたが、フラグの論理和で指定されていて、Read 権は、

定数名
00120089FILE_GENERIC_READ
001200A0FILE_GENERIC_EXECUTE

から論理和を取って、
FILE_GENERIC_READ | FILE_GENERIC_EXECUTE = 0x1200a9
すなわち、
icacls コマンドで指定した
(RX) - 読み取りと実行のアクセス権
と同じ意味です。ただし、この部分に"RX"とは書けません。

Write権は、

定数名
00010000DELETE
00120089FILE_GENERIC_READ
00120116FILE_GENERIC_WRITE
001200A0FILE_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のソースコードについては、こちらに公開しています。

https://github.com/itc-lab/samaccountname2sid

実行例:

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 を作成します。

読み込ませる acl.txt

起動します。

C:\Users\kanri>c:\php\php.exe create_sddl_from_list.php sddl.list acl.txt

何もエラーにならず、sddl.list も無事作成されています。

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 個のファイルを処理できませんでした

問題無く動きました。


確認してみます。

sddl.list アクセス権付与結果


ヨシ!

loading...