web-dev-qa-db-ja.com

sprintf(...)の危険性を理解する

[〜#〜] owasp [〜#〜] は言う:

「strcpy()、strcat()、sprintf()、vsprintf()などのCライブラリ関数は、nullで終了する文字列を操作し、境界チェックを実行しません。」

sprintfフォーマットされたデータを文字列に書き込みますint sprintf(char * str、const char * format、...);

例:

sprintf(str, "%s", message); // assume declaration and 
                             // initialization of variables

OWASPのコメントを理解すると、sprintfを使用する危険性は

1)messageの長さ>strの長さの場合、バッファオーバーフローが発生します

そして

2)message\0でnullで終了しない場合、messagestrmessageのメモリアドレスを超えて、バッファオーバーフローが発生する

確認/拒否してください。ありがとう

22
Kevin Meredith

あなたは両方の問題について正しいですが、実際には両方とも同じ問題です(配列の境界を超えてデータにアクセスすることです)。

最初の問題の解決策は、バッファサイズを引数として受け入れる _std::snprintf_ を代わりに使用することです。

2番目の問題の解決策は、snprintfに最大長の引数を与えることです。例えば:

_char buffer[128];

std::snprintf(buffer, sizeof(buffer), "This is a %.4s\n", "testGARBAGE DATA");

// std::strcmp(buffer, "This is a test\n") == 0
_

文字列全体を保存する場合(たとえば、sizeof(buffer)が小さすぎる場合)、snprintfを2回実行します。

_int length = std::snprintf(nullptr, 0, "This is a %.4s\n", "testGARBAGE DATA");

++length;           // +1 for null terminator
char *buffer = new char[length];

std::snprintf(buffer, length, "This is a %.4s\n", "testGARBAGE DATA");
_

(おそらく、これをvaまたは可変個のテンプレートを使用する関数に適合させることができます。)

24
strager

あなたの主張はどちらも正しい。

言及されていない追加の問題があります。パラメータの型チェックはありません。書式文字列とパラメータを一致させないと、未定義の望ましくない動作が発生する可能性があります。例えば:

char buf[1024] = {0};
float f = 42.0f;
sprintf(buf, "%s", f);  // `f` isn't a string.  the Sun may explode here

これはデバッグが特に厄介です。

上記のすべてが、sprintfとその兄弟を使用してはならないという結論に至るまで、多くのC++開発者を導きます。実際、上記の問題をすべて回避するために使用できる機能があります。 1つはストリームで、言語に直接組み込まれています。

#include <sstream>
#include <string>

// ...

float f = 42.0f;

stringstream ss;
ss << f;
string s = ss.str();

...そして、私のようにsprintfを使用することを好む人にとってもう1つの人気のある選択肢は ブーストフォーマットライブラリ から来ています:

#include <string>
#include <boost\format.hpp>

// ...

float f = 42.0f;
string s = (boost::format("%1%") %f).str();

「sprintfを使用しない」というマントラを採用すべきですか?自分で決める。通常、その仕事に最適なツールがあり、あなたが何をしているかに応じて、sprintfはそれだけかもしれません。

10
John Dibling

はい、それは主にバッファオーバーフローの問題です。ただし、バッファオーバーフローは、ソフトウェアやシステムのセキュリティを回避するためにシステムクラッカーが使用する主要な攻撃ベクトルであるため、これらは今日非常に深刻なビジネスです。このようなものをユーザー入力に公開すると、プログラム(またはコンピューター自体)のキーをクラッカーに渡す可能性が非常に高くなります。

OWASPの観点から、Webサーバーを作成しているとしましょう。sprintfを使用して、ブラウザーが渡す入力を解析します。

ここで、悪意のある誰かが、選択したバッファに収まるよりもはるかに大きい文字列をWebブラウザに渡したとします。彼の余分なデータは代わりに近くのデータを上書きします。彼が十分に大きくすると、彼のデータの一部は、データではなくWebサーバーの指示を介してコピーされます。これで、彼は私たちのウェブサーバーに彼のコードを実行させることができます。

4
T.E.D.

番号が付けられた2つの結論は正しいですが、不完全です。

追加のリスクがあります:

_char* format = 0;
char buf[128];
sprintf(buf, format, "hello");
_

ここで、formatはNULLで終了していません。 sprintf()もそれをチェックしません。

3
MSalters

Sprintf関数を特定の形式指定子と一緒に使用すると、2種類のセキュリティリスクが生じます。(1)書き込むべきではないメモリを書き込む。 (2)メモリの読み取りはできません。バッファに一致するサイズパラメータを指定してsnprintfを使用すると、必要のないものは書き込まれません。パラメータによっては、読み込めないものを読み取る場合があります。動作環境や他のプログラムの動作に応じて、不適切な読み取りによる危険は、不適切な書き込みによる危険よりも深刻ではない場合があります。

1
supercat

あなたの解釈は正しいようです。ただし、ケース#2は実際にはバッファオーバーフローではありません。メモリアクセス違反の詳細です。それは単なる用語ですが、それでもまだ大きな問題です。

1
bta

sprintfのバッファサイズ宣言を削除する方法の簡単な例を述べました(もちろん、意図した場合!)いいえsnprintfは関与しません....

:これはAPPEND/CONCATENATIONの例です。 こちらをご覧ください

0
PYK

Sprintf()は、各文字列の末尾にASCII 0文字を文字列ターミネータとして追加することを覚えておくことが非常に重要です。したがって、宛先バッファには少なくともn + 1バイトが必要です(印刷するには「HELLO」という単語。6バイトのバッファが必要です。5ではありません)

以下の例ではわかりにくいかもしれませんが、2バイトの宛先バッファでは、2番目のバイトがASCII 0文字で上書きされます。バッファに1バイトしか割り当てられていない場合、これにより、バッファオーバーランが発生します。

char buf[3] = {'1', '2'};
int n = sprintf(buf, "A");

また、sprintf()の戻り値にはヌル終了文字が含まれていないことにも注意してください。上記の例では、2バイトが書き込まれましたが、関数は「1」を返します。

以下の例では、クラスメンバー変数 'i'の最初のバイトがsprintf()(32ビットシステムの場合)によって部分的に上書きされます。

struct S
{
    char buf[4];
    int i;
};


int main()
{
    struct S s = { };
    s.i = 12345;

    int num = sprintf(s.buf, "ABCD");
    // The value of s.i is NOT 12345 anymore !

    return 0;
}
0
Jacob N.