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()
は、複数の場所で同じメッセージを出力する場合や、他のやり方ではメッセージを作成するのがむずかしい場合に一般的に使われる関数である。しかし、信頼できないユーザからの入力を含む msg
を fprintf()
呼び出しにおける書式指定文字列引数として渡しているため、書式指定文字列の脆弱性を作り込んでしまっている。
適合コード (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 |
Format string injection |
Compass/ROSE |
|
|
|
Coverity |
2017.07
|
TAINTED_STRING |
実装済み |
GCC |
4.3.5
|
|
|
Klocwork |
2018
|
|
|
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
|
|
|
関連する脆弱性
このルールに違反したために生じた書式指定文字列の脆弱性の例としては、Ettercap と Samba の脆弱性がある。
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)