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)



