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

FIO30-C. ユーザからの入力を使って書式指定文字列を組み立てない

FIO30-C. ユーザからの入力を使って書式指定文字列を組み立てない

信頼できない外部からの入力(tainted value)を含む書式指定文字列を使って、書式付き入出力関数を呼び出さないこと。書式指定文字列の内容を制御できると、プロセスをクラッシュさせたり、スタックの内容を閲覧したり、メモリの内容を閲覧したり、任意のメモリ領域に書き込みを行ったりすることが可能になる。その結果、脆弱なプロセスの実行権限で任意のコードを実行できる [Seacord 2013b]。書式付き出力関数が特に危険なのは、それらの関数を使ってどのようなことができるかを多くのプログラマが理解していないからである。たとえば、%n 変換指定子を使用すると特定のアドレスに整数値を書き込むことができる。

違反コード

以下のコード例に示す incorrect_password() 関数は、ユーザ認証において呼び出され、指定したユーザが見つからないかパスワードが間違っている場合にエラーメッセージを表示する。この関数は、ユーザ名を変数 user が参照する文字列として受け取る。これは、認証されていないユーザからの信頼できないデータが入力源となる典型的な例である。関数はエラーメッセージを組み立て、そのメッセージを C 標準ライブラリ関数 fprintf() を使って stderr に出力している。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void incorrect_password(const char *user) {
  int ret;
  /* ユーザ名は256文字以下に制限されている */
  static const char msg_format[] = "%s cannot be authenticated.\n";
  size_t len = strlen(user) + sizeof(msg_format);
  char *msg = (char *)malloc(len);
  if (msg == NULL) {
    /* エラー処理 */
  }
  ret = snprintf(msg, len, msg_format, user);
  if (ret < 0) {
    /* エラー処理 */
  } else if (ret >= len) {
    /* 切り捨てられた出力の処理 */
  }
  fprintf(stderr, msg);
  free(msg);
}

incorrect_password() 関数は、メッセージのサイズを計算し、動的メモリを割り当て、snprintf() 関数を呼び出して割り当てられたメモリにメッセージを作成する。user が参照する文字列の長さは 256 文字以下になることが分かっているので、加算演算時に整数オーバーフローが発生しないかどうかはチェックしていない。%s 変換指定子は snprintf() 呼び出しの中で user が参照する文字列に置き換えられるため、確保する領域は、(null バイト終端文字を考慮すると)結果として作成される文字列に加えて 1 バイト分大きくしておく必要がある。snprintf() は、複数の場所で同じメッセージを出力する場合や、他のやり方ではメッセージを作成するのがむずかしい場合に一般的に使われる関数である。しかし、信頼できないユーザからの入力を含む msgfprintf() 呼び出しにおける書式指定文字列引数として渡しているため、書式指定文字列の脆弱性を作り込んでしまっている。

適合コード (fputs())

以下の適合コードでは、fprintf()fputs() に置き換えることで問題を修正している。fputs() は、msg を書式指定文字列として扱わず、そのまま stderr に出力する。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void incorrect_password(const char *user) {
  int ret;
  /* ユーザ名は256文字以下に制限されている */
  static const char msg_format[] = "%s cannot be authenticated.\n";
  size_t len = strlen(user) + sizeof(msg_format);
  char *msg = (char *)malloc(len);
  if (msg == NULL) {
    /* エラー処理 */
  }
  ret = snprintf(msg, len, msg_format, user);
  if (ret < 0) {
    /* エラー処理 */
  } else if (ret >= len) {
    /* 切り捨てられた出力の処理 */
  }
  fputs(msg, stderr);
  free(msg);
}
適合コード (fprintf())

より簡単な適合コードは、信頼できないユーザからの入力を、書式指定文字列の一部ではなく可変引数の1つとして fprintf() へ渡す方法である。このように修正することで、書式指定文字列の脆弱性の可能性を排除することができる。

#include <stdio.h>

void incorrect_password(const char *user) {
  static const char msg_format[] = "%s cannot be authenticated.\n";
  fprintf(stderr, msg_format, user);
}
違反コード (POSIX)

以下のコード例は、最初の違反コードと同じであるが、fprintf() 関数の代わりに POSIX の syslog() 関数を使用している[IEEE Std 1003.1:2013]。この関数もまた書式指定文字列の脆弱性を引き起こしやすい。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

void incorrect_password(const char *user) {
  int ret;
  /* ユーザ名は256文字以下に制限されている */
  static const char msg_format[] = "%s cannot be authenticated.\n";
  size_t len = strlen(user) + sizeof(msg_format);
  char *msg = (char *)malloc(len);
  if (msg == NULL) {
    /* エラー処理 */
  }
  ret = snprintf(msg, len, msg_format, user);
  if (ret < 0) {
    /* エラー処理 */
  } else if (ret >= len) {
    /* 切り捨てられた出力の処理 */
  }
  syslog(LOG_INFO, msg);
  free(msg);
}

syslog() 関数は BSD 4.2 で最初に導入され、Linux やその他の UNIX システムでサポートされている。Windows システムでは利用できない。

適合コード (POSIX)

以下の適合コードでは、信頼できないユーザからの入力を、書式指定文字列に含めるのではなく syslog() の可変引数の1つとして渡している。

#include <syslog.h>

void incorrect_password(const char *user) {
  static const char msg_format[] = "%s cannot be authenticated.\n";
  syslog(LOG_INFO, msg_format, user);
}
リスク評価

ユーザからの入力を含む書式指定文字列を使用すると、攻撃者は、脆弱なプロセスをクラッシュさせたり、スタックの内容を閲覧したり、メモリの内容を閲覧したり、任意のメモリ領域への書き込みを行ったりすることができる。その結果、脆弱なプロセスの実行権限で任意のコードを実行することができる。

ルール

深刻度

可能性

修正コスト

優先度

レベル

FIO30-C

P18

L1

自動検出(最新の情報はこちら

ツール

バージョン

チェッカー

説明

CodeSonar
4.5p1

IO.INJ.FMT
MISC.FMT

Format string injection
Format string

Compass/ROSE



Coverity
2017.07

TAINTED_STRING

実装済み
GCC
4.3.5

-Wformat-security フラグを使用すると、このルールの違反を検出できる。

Klocwork
2018

SV.FMTSTR.GENERIC
SV.TAINTED.FMTSTR


LDRA tool suite
9.7.1

86 D

Partially Implemented
Parasoft C/C++test 10.3

SECURITY-05

SECURITY-08

SECURITY-36


Polyspace Bug Finder R2016a Tainted string format

Input format argument is from an unsecure source

PVS-Studio

6.23

V618
Splint
3.1.1


関連する脆弱性

このルールに違反したために生じた書式指定文字列の脆弱性の例としては、EttercapSamba の脆弱性がある。

Ettercap v.NG-0.7.2 では、ncurses のユーザインタフェースに書式指定文字列に関する欠陥が存在する。ec_curses.c 内の curses_msg() 関数が wdg_scroll_print() を呼び出し、wdg_scroll_print() が書式指定文字列とそのパラメータを取得し vw_printw() に渡す。curses_msg() 関数はそのパラメータの1つを書式指定文字列として使用する。このパラメータはユーザから入力されるデータを含むため、書式指定文字列の脆弱性につながった。

Samba の AFS ACL マッピング VFS プラグインでは、ユーザから与えられたファイル名を適切に無害化せずに書式指定文字列に含めて snprintf() に渡していた。afsacl.so ライブラリを使うことで、AFS ファイルシステム上のファイルに Windows NT アクセスコントロールリストを設定することができるが、このライブラリを使ってアクセスコントロールを行っている共有にユーザが書き込むことができる場合、攻撃可能となる。

関連するガイドライン

(テーブルの説明)

Taxonomy

Taxonomy item

Relationship

CERT/Oracle版 Java コーディングスタンダード IDS06-J. ユーザからの入力を使って書式指定文字列を組み立てない Prior to 2018-01-12: CERT: Unspecified Relationship
CERT Perl Secure Coding Standard IDS30-PL. Exclude user input from format strings Prior to 2018-01-12: CERT: Unspecified Relationship
ISO/IEC TR 24772:2013 Injection [RST] Prior to 2018-01-12: CERT: Unspecified Relationship
ISO/IEC TS 17961:2013 Including tainted or out-of-domain input in a format string [usrfmt] Prior to 2018-01-12: CERT: Unspecified Relationship
CWE 2.11 CWE-134, Uncontrolled Format String 2017-05-16: CERT: Exact
CWE 2.11 CWE-20, Improper Input Validation 2017-05-17: CERT: Rule subset of CWE
参考資料
[IEEE Std 1003.1:2013] XSH, System Interfaces, syslog
[Seacord 2013b] Chapter 6, "Formatted Output"
[Viega 2005] Section 5.2.23, "Format String Problem"
翻訳元

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

FIO30-C. Exclude user input from format strings (revision 165)

Top へ

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