なぜこのコードはバッファオーバーフロー攻撃に対して脆弱なのですか?


148
int func(char* str)
{
   char buffer[100];
   unsigned short len = strlen(str);

   if(len >= 100)
   {
        return (-1);
   }

   strncpy(buffer,str,strlen(str));
   return 0;
}

このコードはバッファオーバーフロー攻撃に対して脆弱であり、私はその理由を理解しようとしています。の代わりにとlen宣言されていることに関係していると思いますが、本当にわかりません。shortint

何か案は?


3
このコードには複数の問題があります。C文字列はnullで終了することを思い出してください。
ドミトリチュバロフ2015

4
@DmitriChubarov、文字列をnullで終了しないことは、文字列がの呼び出し後に使用された場合にのみ問題になりますstrncpy。この場合、そうではありません。
R Sahu、2015

43
このコードの問題strlenは、計算されて有効性チェックに使用され、その後不条理に再計算されるという事実から直接流れます。これはDRYエラーです。2番目strlen(str)をに置き換えた場合、lenのタイプに関係なく、バッファオーバーフローの可能性はありませんlen。答えはこの点に対処していません、彼らはそれをなんとか回避するだけです。
ジムバルター、2015

3
@CiaPan:Wennがnullで終了していない文字列を渡すと、strlenは未定義の動作を示します。
Kaiserludi、2015

3
@ジムバルターいや、私はそれらをそこに残しておくと思います。たぶん他の誰かが同じ愚かな誤解を抱き、そこから学ぶでしょう。彼らがあなたを怒らせたら、彼らにフラグを立ててください、誰かがやって来てそれらを削除するかもしれません。
Asad Saeeduddin 2015

回答:


192

ほとんどのコンパイラーの最大値unsigned shortは65535です。

それ以上の値はラップされるため、65536は0になり、65600は65になります。

つまり、適切な長さの長い文字列(65600など)はチェックに合格し、バッファがオーバーフローします。


not size_tの結果を格納し、のサイズを直接エンコードする式と比較するために使用します。だから例えば:strlen()unsigned shortlenbuffer

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);

2
@PatrickRoberts理論的にはそうです。ただし、コードの10%がランタイムの90%を担っているので、セキュリティよりもパフォーマンスを優先させないでください。また、コードが時間の経過とともに変化することを覚えておいてください。これは、前のチェックがなくなったことを突然意味します。
orlp 2015

3
バッファオーバーフローを防ぐには、単にlenstrncpyの3番目の引数として使用します。もう一度strlenを使用するのは、とにかくばかげています。
ジムバルター、2015

15
/ sizeof(buffer[0])sizeof(char)-Cは常に 1であることに注意してください(charにgazillionビットが含まれている場合でも)。これは、別のデータ型を使用する可能性がない場合は不要です。それでも...完全な回答に対する称賛(そしてコメントに反応してくれてありがとう)。
ジムバルター2015

3
@ rr-:char[]char*同じではありません。が暗黙的にに変換される多くの状況があります。たとえば、関数の引数の型として使用する場合とまったく同じです。ただし、の変換は行われません。char[]char*char[]char*sizeof()
ディートリッヒエップ2015

4
@Controllはbuffer、ある時点でのサイズを変更すると、式が自動的に更新されるためです。の宣言はbuffer実際のコードのチェックからかなり離れている可能性があるため、これはセキュリティにとって重要です。したがって、バッファのサイズを変更するのは簡単ですが、サイズが使用されるすべての場所で更新することを忘れます。
orlp 2015

28

問題はここにあります:

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

文字列がターゲットバッファーの長さより長い場合でも、strncpyはそれをコピーします。バッファのサイズではなく、文字列の文字数をコピーする数として使用しています。これを行う正しい方法は次のとおりです。

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '\0';

これにより、コピーされるデータの量は、バッファーの実際のサイズからnull終了文字の1を引いたものに制限されます。次に、追加の保護手段として、バッファーの最後のバイトをnull文字に設定します。この理由は、strlen(str)<len-1の場合、strncpyは終端のnullを含めてnバイトまでコピーするためです。そうでない場合、nullはコピーされず、バッファが終了していないためクラッシュシナリオが発生します。ストリング。

お役に立てれば。

編集:さらに調査し、他のユーザーから入力すると、関数の可能なコーディングは次のようになります。

int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '\0';
    return(0);
  }

文字列の長さがわかっているので、memcpyを使用して、strによって参照される場所から文字列をバッファにコピーできます。strlen(3)のマニュアルページ(FreeBSD 9.3システム上)では、次のように述べられています。

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

文字列の長さにnullが含まれていないと私は解釈します。そのため、len + 1バイトをコピーしてnullを含め、テストでは、長さ<バッファーのサイズ-2であることを確認します。バッファーが位置0から始まるため、マイナス1と、スペースがあることを確認するためにもう1つマイナスします。ヌル。

編集:結局のところ、何かのサイズは1で始まり、アクセスは0で始まるため、98バイトを超えるとエラーが返されるため、以前の-2は正しくありませんが、99バイトを超える必要があります。

編集:unsigned shortについての答えは、表現できる最大長が65,535文字であるため、通常は正しいですが、文字列がそれより長い場合、値が折り返されるため、実際には問題ではありません。これは、75,231(0x000125DF)を取得し、上位16ビットをマスクして9695(0x000025DF)を取得するようなものです。長さのチェックではコピーが許可されるため、これで私が見る唯一の問題は65,535を超える最初の100文字ですが、すべての場合に文字列の最初の100文字までしかコピーされず、文字列はnullで終了します。したがって、ラップアラウンドの問題があっても、バッファがオーバーフローすることはありません。

文字列の内容とそれを何に使用するかによって、これ自体がセキュリティリスクをもたらす場合とそうでない場合があります。人間が読めるのが単なるテキストの場合、通常は問題ありません。切り捨てられた文字列を取得するだけです。ただし、URLやSQLコマンドシーケンスなどの場合は、問題が発生する可能性があります。


2
確かに、それは問題の範囲を超えています。このコードは、charポインターが渡される関数を明確に示しています。関数の範囲外では、気にしません。
Daniel Rudy

「strが格納されているバッファ」- これは、ここでの問題であるバッファオーバーフローではありません。そして、すべての回答には「問題」があります。これはfunc... のシグネチャを考えると避けられないことであり、NULで終了する文字列を引数として取る、これまでに作成された他のすべて C関数です。入力がNULで終了しない可能性を引き出すことは完全に無知です。
ジムバルター2015

「それは問題の範囲を超えています」-悲しいことに、一部の人々が理解する能力を超えています。
ジムバルター、2015

「問題はここにあります」-そうですが、重要な問題がまだありません。つまり、テスト(len >= 100)が1つの値に対して実行されましたが、コピーの長さが別の値に指定されていました...これDRYの原則に違反しています。呼び出すだけでstrncpy(buffer, str, len)、バッファオーバーフローの可能性が回避され、strncpy(buffer,str,sizeof(buffer) - 1)... よりも動作が少なくなりますが、ここでは、と同等に遅いだけmemcpy(buffer, str, len)です。
ジムバルター2015

@JimBalterそれは質問の範囲を超えていますが、私は余談です。テストで使用される値とstrncpyで使用される値が2つの異なるものであることを理解しています。ただし、一般的なコーディング慣行では、コピーの制限はsizeof(buffer)-1である必要があるため、コピーでのstrの長さは問題ではありません。strncpyは、nullに到達するかnバイトをコピーするときに、バイトのコピーを停止します。次の行は、バッファの最後のバイトがnull文字であることを保証します。コードは安全です。前のステートメントはそのままです。
Daniel Rudy

11

を使用している場合でもstrncpy、カットオフの長さは渡された文字列ポインターに依存します。その文字列の長さ(ポインタに対するヌルターミネータの位置)がわかりません。したがって、strlen一人で電話をかけると、脆弱性が高まります。より安全にしたい場合は、を使用してくださいstrnlen(str, 100)

修正される完全なコードは次のとおりです。

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}

@ user3386109 strlen次に、バッファの最後を越えてアクセスしませんか?
Patrick Roberts

2
@ user3386109あなたが指摘していることは、orlpの答えを私のものと同じくらい無効にします。strnlenとにかくorlpが示唆していることが正しいと思われるのに、なぜ問題が解決しないのかわかりません。
Patrick Roberts、

1
「strnlenがここで何かを解決することはないと思います」-もちろんそうです。オーバーフローを防ぎbufferます。「strは2バイトのバッファーを指す可能性があるため、どちらもNULではありません。」- のどの実装に当てはまるため、これは無関係ですfunc。ここでの質問は、入力がNULで終了しないため、UBではなく、バッファオーバーフローに関するものです。
ジムバルター2015

1
「strnlenに渡される2番目のパラメーターは、最初のパラメーターが指すオブジェクトのサイズでなければなりません。そうでない場合、strnlenは無意味です」-これは完全であり、まったくナンセンスです。strnlenの2番目の引数が入力文字列の長さである場合、strnlenはstrlenと同等です。どのようにしてその番号を取得するのでしょうか。もし持っていれば、なぜstr [n] lenを呼び出す必要があるのでしょうか。それはstrnlenがまったく対象としないことです。
ジムバルター2015

1
1この答えは、それはOPのコードと同等ではありませんので、不完全ではあるが-はstrncpy NUL-パッドとNULが終了しない、strcpyのNUL-終了し、一方、およびNULパッド、それはしない、に反して問題を解決上記のばかげた、無知なコメント。
ジムバルター、2015

4

ラッピングの答えは正しいです。しかし、私が言及しなかったと思う問題があります(len> = 100)

Lenが100の場合、100要素をコピーし、末尾の\ 0はありません。それは明らかに、適切に終了した文字列に依存する他の関数が元の配列をはるかに超えることを意味します。

Cから問題のある文字列は、私見では解決できません。電話をかける前に、いくつかの制限を設定することをお勧めしますが、それでも助けにはなりません。境界チェックがないため、バッファオーバーフローは常に発生する可能性があり、残念ながら発生します。


問題のある文字列解決可能です。適切な関数を使用するだけです。I. e。ない strncpy()と友人が、同様の機能の割り当てメモリstrdup()や友人を。これらはPOSIX-2008標準に含まれているため、一部の専用システムでは使用できませんが、かなり移植性があります。
cmaster-モニカを復活させる2015年

「適切に終了した文字列に依存するその他の関数」- bufferこの関数に対してローカルであり、他の場所では使用されません。実際のプログラムでは、それがどのように使用されるかを調べる必要があります... NUL終了が正しくない場合があります(元のstrncpyの使用は、UNIXの14バイトディレクトリエントリを作成するためでした-NULで埋められ、NULで終了していません)。「Cの問題のある文字列はIMHOで解決できません」-Cははるかに優れたテクノロジーに追い越された恐ろしい言語ですが、十分な規律が使用されれば、安全なコードを書くことができます。
ジムバルター、2015

あなたの観察は私には見当違いのようです。if (len >= 100)は、チェックパスしたときではなく、チェックが失敗したときの条件です。つまり、NULターミネーターのない正確に100バイトがコピーされるケースはありません。その長さは、失敗条件に含まれるためです。
Patrick Roberts、

@ cmaster。この場合、あなたは間違っています。常に境界を越えて書くことができるので、それは解決できません。はい、それは未定義の行動ですが、それを完全に防ぐ方法はありません。
フリードリヒ

@ジム・バルター。それは問題ではありません。私はこのローカルバッファの境界を上書きする可能性があるため、他のデータ構造が破損する可能性は常にあります。
フリードリヒ

3

strlen複数回の呼び出しに関連するセキュリティの問題を超えて、長さが正確にわかっている文字列に対して文字列メソッドを一般的に使用しないでください[ほとんどの文字列関数では、それらを使用する必要がある本当に狭いケースがあります-最大の文字列に対して長さは保証されますが、正確な長さは不明です]。入力文字列の長さと出力バッファの長さがわかったら、コピーする領域の大きさを把握し、それを使用memcpy()して問題のコピーを実際に実行する必要があります。可能ですがstrcpyアウトパフォームする可能性があるmemcpy()だけで1-3バイト程度の文字列をコピーするとき、多くのプラットフォーム上でmemcpy()大きな文字列を扱うときより2倍の速さである可能性が高いです。

セキュリティは、性能を犠牲に来るいくつかの状況がありますが、これは安全なアプローチがある状況で速い1。場合によっては、奇妙な動作をする入力に対して安全ではないコードを書くことは妥当です。入力を提供するコードが正しく動作することを保証でき、動作が正しくない入力を防ぐとパフォーマンスが低下する場合です。文字列の長さが1回だけチェックされるようにするとパフォーマンスとセキュリティの両方が向上しますが文字列の長さを手動で追跡する場合でも、セキュリティを保護するために追加の処理を行うことができます。末尾のnullがあると予想されるすべての文字列に対して、末尾のnullを明示的に書き込みますソース文字列にそれがあることを期待するよりも。したがって、strdup同等のものを作成している場合:

char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

memcpyがlen+1バイトを処理した場合、通常、最後のステートメントは省略できますが、別のスレッドがソース文字列を変更すると、結果はNULで終了しない宛先文字列になる可能性があります。


3
複数回の通話に関連するセキュリティの問題strlenについて説明しいただけますか?
Bogdan Alexandru

1
@BogdanAlexandru:strlen呼び出して、返された値に基づいて何らかのアクションを実行すると(おそらく最初にそれを呼び出した理由でした)、繰り返し呼び出し(1)すると、常に最初の呼び出しと同じ答えが得られます。その場合、それは単に無駄な作業であるか、(2)時々(別のスレッド(おそらく別のスレッド)がその間に文字列を変更したため)に別の答えが返されることがあります。その場合、長さ(たとえば、バッファの割り当て)は、他のこと(バッファへのコピー)を行うコードとは異なるサイズを想定する場合があります。
スーパーキャット2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.