web-dev-qa-db-ja.com

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

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の代わりにshortとして宣言されているintに関係していると考えていますが、実際にはわかりません。

任意のアイデア?

148
Jason

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

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

これは、正しい長さの長い文字列(例:65600)がチェックに合格し、バッファをオーバーフローさせることを意味します。


size_tを使用して、unsigned shortではなくstrlen()の結果を保存し、lenbufferのサイズを直接エンコードする式と比較します。たとえば、次のとおりです。

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

問題はここにあります:

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

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

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

これは、コピーされるデータの量を、バッファの実際のサイズからヌル終了文字の1を引いたものに制限します。次に、追加の保護手段として、バッファーの最後のバイトにヌル文字を設定します。これは、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によって参照される場所からバッファに文字列をコピーできます。 (FreeBSD 9.3システム上の)strlen(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、マイナスがあることを確認するテストnullの場合。

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

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

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

28
Daniel Rudy

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;
}
11
Patrick Roberts

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

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

Cからの問題のある文字列は、私見では解決できません。コールの前には常にいくつかの制限を設けた方が良いでしょうが、それでも役には立ちません。境界チェックがないため、バッファオーバーフローは常に発生する可能性があり、残念ながら発生します。

4
Friedrich

strlenの2回以上の呼び出しに伴うセキュリティの問題を超えて、通常、長さが正確にわかっている文字列に対して文字列メソッドを使用しないでください[ほとんどの文字列関数では、使用すべき本当に狭いケースがあります-最大長は保証できるが、正確な長さはわからない文字列]。入力文字列の長さと出力バッファの長さがわかれば、領域のコピーの大きさを把握し、memcpy()を使用して問題のコピーを実際に実行する必要があります。わずか1〜3バイト程度の文字列をコピーする場合、strcpymemcpy()を上回る可能性がありますが、多くのプラットフォームでは、memcpy()は、文字列。

セキュリティによってパフォーマンスが犠牲になる状況がいくつかありますが、これは安全なアプローチが()より速いアプローチである状況です。場合によっては、入力を提供するコードがそれらが正常に動作することを保証でき、不適切な入力に対する保護がパフォーマンスを妨げる場合、奇妙な動作の入力に対して安全ではないコードを書くことが合理的かもしれません。文字列の長さを1回だけ確認することでbothのパフォーマンスとセキュリティが向上しますが、文字列の長さを手動で追跡する場合でもセキュリティを保護するためにもう1つのことができます。 、ソース文字列に期待するのではなく、明示的に末尾の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
supercat