JPCERT コーディネーションセンター

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

 

 

このルールの違反を一部検出できる。特に、setgid() 呼出しの直前に setuid() 呼出しが行われている場合

Klocwork

9.1

SV.FIU.PERMISSIONS
SV.USAGERULES.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)

Top へ

Topへ
最新情報(RSSメーリングリストTwitter