POS36-C. 権限は正しい順序で破棄する
setuid かつ setgid されたプログラムの実効ユーザIDおよび実効グループIDが実ユーザIDおよび実グループIDと異なっている状態から権限を破棄する場合、ユーザレベルの権限だけでなくグループ権限も破棄することが重要である。その際、正しい順序で権限を破棄しなければならない。
POSIX は setgid()
の動作を次のように定義している [Open Group 2004]。
プロセスに適切な権限がある場合、
setgid()
は、呼び出し元プロセスの実グループ ID、実効グループ ID、SSGID (saved set-group-ID) をgid
に設定するものとする。プロセスに適切な権限はないが、
gid
が実グループ ID または SSGID と等しい場合、setgid()
は実効グループ ID をgid
に設定するものとする。実グループ ID と SSGID は変更されない。
違反コード
以下のコードでは、権限を実ユーザの権限に降格し、同様にグループ権限も降格している。しかし、その順序が間違っている。setgid()
関数は root 権限で実行する必要があるが、その直前の setuid()
呼び出しにより、実効ユーザ ID をゼロ以外の値にしてしまっている。その結果、任意のコードを実行できるような脆弱性がプログラム中に見つかった場合、攻撃者は元のグループ権限を取り戻すことができる。
/* 間違った順序で root 権限を破棄している */ if (setuid(getuid()) == -1) { /* エラー条件の処理 */ } if (setgid(getgid()) == -1) { /* エラー条件の処理 */ } /* 権限の破棄順序が間違っているため、破棄したはずのグループ権限を * 再度取得できてしまう */
適合コード
以下の適合コードでは、グループ権限を破棄したあとでユーザレベルの権限を破棄しているため、操作は意図した通りに実行される。
/* 正しい順序で root 権限を破棄している */ if (setgid(getgid()) == -1) { /* エラー条件の処理 */ } if (setuid(getuid()) == -1) { /* エラー条件の処理 */ } /* * 正しい順序で権限を破棄しているため、破棄したグループ権限は再取得できない */
補助グループ ID
プロセスは実効グループ ID 以外に複数の補助グループ ID を持つことができる。補助グループはファイルへのアクセス許可に使われる。getgroups()
関数は、補助グループ ID を含む配列を返すが、実装によっては、この配列に実効グループ ID が含まれていることもある。setgroups()
関数は、補助グループ ID を設定するが、実装によっては、同時に実効グループ ID を設定するものもある。setgroups()
関数を使用するには通常は特権が必要である。POSIX では getgroups()
関数は定義されているが、setgroups()
は定義されていない。
通常、setuid()
および関連する呼び出しによって補助グループ ID を変更することはない。ただし、setuid-root プログラムは、補助グループ ID を変更してから、root 権限を破棄するかもしれない。この後、補助グループ ID は変更できなくなり、それらを破棄するために必要な権限は失われてしまう。そのため、プログラムで root 権限を破棄する直前に補助グループ ID を破棄することが推奨される。以下のコードは、setgroups()
関数をサポートするシステム上で、補助グループ ID を設定する set_sups()
関数を定義している。
/* 2 つのグループリストが同等な場合にゼロ以外を返す。 差分が egid だけのケースを考慮に入れている */ int eql_sups(const int cursups_size, const gid_t* const cursups_list, const int targetsups_size, const gid_t* const targetsups_list) { int i; int j; const int n = targetsups_size; const int diff = cursups_size - targetsups_size; const gid_t egid = getegid(); if (diff > 1 || diff < 0 ) { return 0; } for (i=0, j=0; i < n; i++, j++) { if (cursups_list[j] != targetsups_list[i]) { if (cursups_list[j] == egid) { i--; /* j 番目のエントリをスキップするためにカウンタを戻す */ } else { return 0; } } } /* ここに到達した場合 i==targetsups_size である. 2つのグループリストが同等となるのは j==cursups_size (egid をスキップしたか元々存在しない場合) または egid が cursups の最終エントリとなっている場合, のいずれか */ return j == cursups_size || (j+1 == cursups_size && cursups_list[j] == egid); } /* 補助グループリストを設定する, 成功した場合は 0 を返す */ int set_sups(const int target_sups_size,const gid_t* const target_sups_list) { #ifdef __FreeBSD__ const int targetsups_size = target_sups_size + 1; gid_t* const targetsups_list = (gid_t* const) malloc(sizeof(gid_t) * targetsups_size); if (targetsups_list == NULL) { /* エラー処理 */ } memcpy(targetsups_list+1, target_sups_list, target_sups_size * sizeof(gid_t) ); targetsups_list[0] = getegid(); #else const int targetsups_size = target_sups_size; const gid_t* const targetsups_list = target_sups_list; #endif if (geteuid() == 0) { /* setgroups 実行可能 */ if (-1 == setgroups(targetsups_size, targetsups_list)) { /* エラー処理 */ } } else { int cursups_size = getgroups( 0, NULL); gid_t* cursups_list = (gid_t*) malloc( sizeof(gid_t) * cursups_size); if (cursups_list == NULL) { /* エラー処理 */ } if (-1 == getgroups( cursups_size, cursups_list)) { /* エラー処理 */ } if (!eql_sups(cursups_size, cursups_list, targetsups_size, targetsups_list)) { if (-1 == setgroups(targetsups_size, targetsups_list)) { /* 失敗するかも... :( */ /* エラー処理 */ } } free( cursups_list); } #ifdef __FreeBSD__ free( targetsups_list); #endif return 0; }
リスク評価
権限を破棄する際に正しい手順で行わないと、攻撃者は破棄したはずの権限を取得する可能性がある。
ルール |
深刻度 |
可能性 |
修正コスト |
優先度 |
レベル |
---|---|---|---|---|---|
POS36-C |
高 |
中 |
中 |
P12 |
L1 |
自動検出(最新の情報はこちら)
ツール |
バージョン |
チェッカー |
説明 |
---|---|---|---|
Compass/ROSE |
|
|
このルールの違反を一部検出できる。特に、 |
9.1 |
SV.FIU.PERMISSIONS |
|
関連するガイドライン
ISO/IEC TR 24772 | Privilege Sandbox Issues [XYO] |
MITRE CWE | CWE-250, Execution with unnecessary privileges CWE-696, Incorrect behavior order |
参考資料
[Chen 2002] | "Setuid Demystified" |
[Dowd 2006] | Chapter 9, "UNIX I: Privileges and Files" |
[Open Group 2004] | setuid() setgid() |
[Tsafrir 2008] | "The Murky Issue of Changing Process Identity: Revising 'Setuid Demystified'" |
翻訳元
これは以下のページを翻訳したものです。
POS36-C. Observe correct revocation order while relinquishing privileges (revision 50)