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

POS37-C. 権限の破棄は確実に行う

POSIX の setuid() 関数はセマンティクスが複雑、かつプラットフォーム固有の動作をする[Open Group 2004]。

プロセスに適切な権限がある場合、setuid() は呼び出し元プロセスの実ユーザ ID、実効ユーザ ID、SSUID (saved set-user-ID) を uid に設定する。

プロセスに適切な権限はないが、uid が実ユーザ ID または SSUID と等しい場合、setuid() は実効ユーザ ID を uid に設定する。実ユーザ ID と SSUID は変更しない。

「適切な権限」の意味はプラットフォームにより異なる。たとえば、Solaris では setuid() にとって適切な権限とは、PRIV_PROC_SETID 権限がプロセスの有効な権限の集合に含まれていることを意味する。BSD では、実効ユーザ ID (EUID) がゼロ(つまりプロセスが root 権限で実行されている)、または uid=geteuid() であることを意味する。Linux では、プロセスが CAP_SETUID ケーパビリティを持つことを意味する。あるいは、実効ユーザ ID (EUID) が0でなく、実ユーザ ID (RUID) や saved set-user-ID (SSUID) とも等しくない場合に setuid(geteuid()) は失敗する。

この複雑な動作のため、必要な権限の破棄に失敗することがある。たとえば、Linux Kernel の バージョン 2.2.0 から 2.2.15 では、ケーパビリティビットがゼロに設定されていると setuid(getuid()) が期待通りに権限を破棄しないため、不適切な権限を悪用する攻撃 (insufficient privilege attack)に対して脆弱である。予防のため、対象とする実装の微妙な動作やエラー条件を入念に確認しておくこと。

違反コード

以下のコードは、ほとんどの POSIX システムで問題なくコンパイルできるが、権限の破棄が確実に成功したことを明示的にチェックしていない。チェック前に行われる一連の権限変更の内容によっては、危険をもたらす可能性がある。

/* 特権で実行することを意図したコード */

/* 一時的に権限を破棄 */
if (seteuid(getuid()) != 0) {
  /* エラー処理 */
}

/* より低い権限で実行することを意図したコード */

if (need_more_privileges) {
  /* 特権を復元 */
  if (seteuid(0) != 0) {
    /* エラー処理 */
  }

  /* 特権で実行することを意図したコード */
}

/* ... */

/* 永久に権限を破棄 */
if (setuid(getuid()) != 0) {
  /* エラー処理 */
}

/*
 * ここは低い権限で実行することを意図した
 * コードだが、権限の破棄に失敗すると
 * 攻撃者は権限を復元できる
 */

プログラムが setuid root プログラムとして実行される場合、UID の状態は以下のように変化するだろう。

説明

コード

EUID

RUID

SSUID

プログラム起動時

 

0

ユーザ

0

一時的な引き下げ

seteuid(getuid())

ユーザ

ユーザ

0

復元

seteuid(0)

0

ユーザ

0

恒久的な引き下げ

setuid(getuid())

ユーザ

ユーザ

ユーザ

復元 (攻撃者)

setuid(0) (失敗)

ユーザ

ユーザ

ユーザ

プログラムが権限の復元に失敗した場合、あとでその権限を永久に引き下げることはできなくなる。

説明

コード

EUID

RUID

SSUID

プログラム起動時

 

0

ユーザ

0

一時的な引き下げ

seteuid(getuid())

ユーザ

ユーザ

0

復元

seteuid(0)

ユーザ

ユーザ

0

恒久的な引き下げ

setuid(getuid())

ユーザ

ユーザ

0

復元 (攻撃者)

setuid(0)

0

0

0

適合コード

以下の適合コードは、メール転送エージェントとしてよく知られている sendmail に実装されたもので、root 権限が正常に破棄されたかどうかを確認している[Wheeler 2003]。権限を永久に破棄した(はずの)後に setuid() の呼び出しが成功するのであれば、権限は意図したとおりに破棄されていない。

/* 特権で実行することを意図したコード */

/* 一時的に権限を破棄 */
if (seteuid(getuid()) != 0) {
  /* エラー処理 */
}

/* より低い権限で実行することを意図したコード */

if (need_more_privileges) {
  /* 権限を復元 */
  if (seteuid(0) != 0) {
    /* エラー処理 */
  }

  /* 特権で実行することを意図したコード */
}

/* ... */

/* 永久に権限を破棄 */
if (setuid(getuid()) != 0) {
  /* エラー処理 */
}

if (setuid(0) != -1) {
  /* 特権を復元可能。エラー処理を行う */
}

/*
 * より低い権限で実行することを意図したコード;
 * 攻撃者は権限を復元できない
 */
適合コード

よりよい解決法は、権限を永久に破棄しようとする前に、適切な権限があることを確認することである。

/* 特権 ID を後のために保存 */
uid_t privid = geteuid();

/* 特権で実行することを意図したコード */

/* 一時的に権限を破棄 */
if (seteuid(getuid()) != 0) {
  /* エラー処理 */
}

/* より低い権限で実行することを意図したコード */

if (need_more_privileges) {
  /* 権限を復元 */
  if (seteuid(privid) != 0) {
    /* エラー処理 */
  }

  /* 特権で実行することを意図したコード */
}

/* ... */

/* 必要なら特権を復元 */
if (geteuid() != privid) {
  if (seteuid(privid) != 0) {
    /* エラー処理 */
  }
}

/* 永久に権限を破棄 */
if (setuid(getuid()) != 0) {
  /* エラー処理 */
}

if (setuid(0) != -1) {
  /* 特権を復元可能。エラー処理を行う */
}

/*
 * より低い権限で実行することを意図したコード;
 * 攻撃者は権限を復元できない
 */
補助グループ ID

プロセスは実効グループ ID 以外に複数の補助グループ ID を持つことができる。補助グループはファイルへのアクセス許可に使われる。getgroups() 関数は、補助グループ ID を含む配列を返すが、実装によっては、この配列に実効グループ ID が含まれていることもある。setgroups() 関数は、補助グループ ID を設定するが、実装によっては、同時に実効グループ ID を設定するものもある。setgroups() 関数を使用するには通常は特権が必要である。POSIX では getgroups() 関数は定義されているが、setgroups() は定義されていない。

通常、setuid() および関連する呼び出しによって補助グループ ID を変更することはない。ただし、setuid-root プログラムは、補助グループ ID を変更してから、root 権限を破棄するかもしれない。この後、補助グループ ID は変更できなくなり、それらを破棄するために必要な権限は失われてしまう。そのため、プログラムで root 権限を破棄する直前に補助グループ ID を破棄することが推奨される。

POS36-C. 権限は正しい順序で破棄する」に補助グループ ID の破棄方法が説明されている。補助グループ ID を確実に破棄するために、以下の eql_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);
}
システム固有の機能

多くのシステムには権限管理に関する可搬性のない固有の機能がある。これを知らずにコードを書いていると、権限昇格の脆弱性を作り込んでしまうかもしれない。以下のセクションでこのような固有の機能のひとつについて説明する。

ファイルシステムアクセス権限 (Linux)

Linux 上のプロセスは、fsuid および fsgid という 2 つの値を持っている。これらの値は、ファイルシステム上のファイルへのアクセス時に使用される権限を示している。通常は、実効ユーザ ID と実効グループ ID の値が反映されるが、setfsuid() および setfsgid() 関数を使用してそれらを変更することができる。euidegid の変更は通常 fsuidfsgid にも適用されるため、root 権限を破棄するプログラムでは fsuidfsgid に安全な値を設定する心配をする必要はない。ただし、このような想定が崩れ去ってしまうようなカーネルバグが少なくとも 1 つ存在した ([Chen 2002]、[Tsafrir 2008])。そのため、賢明なプログラムでは、fsuidfsgid の値が権限の破棄後に問題を起こすような値になっていないかチェックするべきだろう。

リスク評価

権限が破棄されたかどうかチェックしないままでは、プログラムに何かしらの欠陥があった場合、権限の高いユーザやグループアカウントでシステムが侵害される可能性がある。

ルール

深刻度

可能性

修正コスト

優先度

レベル

POS37-C

P18

L1

自動検出

ツール

バージョン

チェッカー

説明

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-273, Failure to check whether privileges were dropped successfully
参考情報
[Chen 2002] "Setuid Demystified"
[Dowd 2006] Chapter 9, "Unix I: Privileges and Files"
[Open Group 2004] setuid()
getuid()
seteuid()
[Tsafrir 2008] "The Murky Issue of Changing Process Identity: Revising 'Setuid Demystified'"
[Wheeler 2003] Section 7.4, "Minimize Privileges"
翻訳元

これは以下のページを翻訳したものです。

POS37-C. Ensure that privilege relinquishment is successful (revision 61)

Top へ

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