バッファオーバーフロー攻撃に脆弱なコードとは
※この作品は2070年が舞台という設定です
#include <stdio.h> #include <string.h> int main() { char password[10] = ""; // パスワード入力バッファ。初期値は「」 char result[10] = "NG"; // 判定結果格納用文字列。初期値は「NG」 printf("Input password: "); //「Input password: 」と画面に出力する gets(password); // 入力内容を一行読み取り、変数passwordにセットする // もし、変数passwordにセットされた文字列が「1234」なら if (strcmp(password, "1234") == 0) { // 判定結果「OK」を変数resultにセットする strcpy(result, "OK"); } // もし、判定結果が「OK」なら if (strcmp(result, "OK") == 0) { printf("Login OK"); // 「Login OK」と画面に出力する } else { // そうでなければ、 printf("Error"); // 「Error」と画面に出力する } printf("\n"); // 画面出力を改行する return 0; }
$ gcc sample1.c -o sample1 $ ./sample1 Input password: 1234 Login OK
$ ./sample1 Input password: 12345 Error
$ ./sample1 Input password: 1234567890OK Login OK
再びソースコードを見てみましょう。
char password[10] = ""; // パスワード入力バッファ。初期値は「」 char result[10] = "NG"; // 判定結果格納用文字列。初期値は「NG」
ここで、半角10文字を格納できる入れ物を2個用意しています。一つはpassword、もう一つはresultです。
そして、
gets(password); // 入力内容を一行読み取り、変数passwordにセットする
ここで、入力内容を読み取って、passwordにセットしています。
しかし、passwordに入る文字数は10文字です。もしここで入力内容が10文字を越えてしまうとどうなるでしょうか?
先ほどの例のように「1234567890OK」と入力すると、passwordには10文字分の「1234567890」までしか入りません。そして、入りきらなかった分は、隣のresultにはみ出してしまうのです。その結果、resultが「OK」と書き換えられてしまいます。
パスワードが間違っているにもかかわらず、判定結果を格納するresultが「OK」に書き換わってしまったため、
// もし、判定結果が「OK」なら if (strcmp(result, "OK") == 0) { printf("Login OK"); // 「Login OK」と画面に出力する
この条件分岐において、「Login OK」と出力されてしまうというわけです。
なお、gets()
は、バッファオーバーフローの脆弱性を引き起こす代表例であり、バッファオーバーフローを解説するための十八番となっています。使いやすいことから初心者向けプログラミング演習などではよく使用される関数ですが、このようにログインを回避するなど危険な動作を起こしうるため、本番の開発では使用してはいけません。代わりに、バッファのサイズを指定できるfgets()
を使用するのが定番です。それにより、指定したサイズを超えて書き込まれることを防げます。
しかし、gets()
だけがバッファオーバーフローの原因というわけではありません。例えば、文字列をコピーするstrcpy()
関数もバッファのサイズを指定する引数がないことに注目してください。strcpy()
関数は配列の長さではなくヌル文字を文字列の終端であると認識するため、正しくヌル終端されていない文字列が引数に渡されると、文字列の長さを誤認識してしまいます。想定より長い文字列がコピー先に書き込まれることになると、gets()
と同様にバッファオーバーフローが発生します。対策としては、代わりにバッファのサイズを指定できるmemcpy()
を用いることができます。