コラム概要
■こんな方におすすめ
- アプリのセキュリティについて少し踏み込んだ内容を知りたい
- アプリの脆弱性について知識をつけたい
- 開発/技術担当の方
■難易度:★☆☆
■ポイント:
- C /C++言語には想定以上の量の数値・文字列が入力されることで関数を上書きしてしまう
- バッファオーバーフローがある。
- バッファオーバーフローを悪用した攻撃では、悪意あるコードの実行が可能である。
- 脆弱性を防ぐために、プログラム言語の問題について知り、適切に対策することが重要である。
1.はじめに
プログラムを書く際、気を付けなければならないポイントはたくさんありますが、
その中の一つにプログラミング言語特有の問題というものがあります。
今回は時に誤動作や不正な動作を引き起こす、
C/C++言語に特有のバッファオーバーフローについて解説したいと思います。
2.バッファオーバーフローとは?
バッファオーバーフローとは、
「想定以上の量の数値・文字列が入力されることで他の数値・文字列を上書きしてしまう」
という現象を指します。今回は実験のため、以下のような関数を用意いたしました。
char generateBufferOverflow()
{
char szCheck[8] = {0x00, };
char szName[10] = {0x00, };
char szPassWord[10] = {0x00, };
printf(“Input your Name (Less than 10 characters). : “);
scanf(“%s”, szName);
printf(“Input your PassWord (Less than 10 characters). : “);
scanf(“%s”, szPassWord);
printf(“Your Name : %s\n”, szName);
if (strcmp(szName, “Admin”) == 0 && strcmp(szPassWord, “PassWord”) == 0) szCheck[0] = 1;
if (szCheck[0] != 0) printf(“Welcome, Admin.\n”);
else printf(“Error!\n”);
return szCheck[0];
}
簡単に説明すると、名前に”Admin”、パスワードに”PassWord”と入力することで
“Admin”だと認識されるような関数です。
(会員サイトにログインする際のユーザー/パスワードのようなイメージです。)
名前とパスワードには10文字分のバッファを用意し、入力の受付にはscanf()という関数を使用します。
↓普通に使用される分には、以下のように問題なく動作する関数となっています。
<画像1:名前とパスワードが一致していない場合>
<画像2:名前とパスワードが一致し、Admin認証が成功した場合>
<画像3:パスワードと名前のバッファ管理状況(バッファの位置が隣接しているイメージ)>
しかし、ここで意図的にパスワード入力時に10文字を超える入力を行うと……
<画像4:パスワードに10文字以上の入力を行った場合>
↑このようにパスワードが名前を上書きしてしまいました。これが「バッファオーバーフロー」です。
これは以下画像のように、
名前を管理するバッファとパスワードを管理するバッファが隣接しているために発生しています。
<画像5:パスワードに10文字以上の入力を行った場合のバッファ管理状況>
続いて、より長い文字列を入力してみると……
<画像6:パスワードに数十文字の入力を行った場合>
このように名前とパスワードが正しくないにも関わらず”Admin”であると認識されてしまいました。
これもCheckバッファが名前管理バッファやパスワード管理バッファと隣接しているために発生しています。
どういうことが起こっているかというと、“Admin”かどうかの判定は
Checkバッファの最初の文字が「0」であればFalseとなりエラーになりますが、
「0」以外であれば正しいと判断する関数を設定しています。
ですので、今回0以外である“*”がCheckバッファの先頭に当てはまるためAdminと判定します。
<画像7:パスワードに数十文字の入力を行った場合のバッファ管理状況>
3.応用:バッファオーバーフロー攻撃について
このバッファオーバーフローは誤動作の原因となるだけでなく、
悪意あるプログラムを実行させてしまう脆弱性になることがあります。
例えば、関数Aから関数Bを呼び出した後に、また関数Bから関数Aへ戻ってくることができるよう、
関数Bを呼び出したタイミングのアドレスを保存しています。
これをリターンアドレスと呼びます。
このリターンアドレスは、先ほどのバッファと非常に近い位置に保存されるため、
入力次第ではリターンアドレスを任意の値で書き換えることが可能です。
さらにこのリターンアドレスを悪意のあるコード
(バッファオーバーフローを引き起こす文字列に仕込みます)に飛ぶ設定をすることで、
悪意のあるコードを実行させることが可能になります。
<画像8:バッファオーバーフロー攻撃を行った場合のバッファ管理状況>
このような攻撃手法をバッファオーバーフロー攻撃と呼びます。
4.対策:バッファオーバーフローの対策とは?
では、このバッファオーバーフローを防ぐにはどのようなプログラムにするべきでしょうか?
それは、“文字数の指定”を組み込んだプログラムにすることです。
①scanf()を使用する場合
scanf()では文字数を制限して読み込ませることが可能です。
scanf()を使用する場合は以下のように記述します。
printf(“Input your Name (Less than 10 characters). : “);
scanf(“%9[^\n]%[^\n]”, szName); scanf(“%c”);
printf(“Input your PassWord (Less than 10 characters). : “);
scanf(“%9[^\n]%[^\n]”, szPassWord); scanf(“%c”);
ただしscanf()を用いる場合はフォーマットの記述が若干面倒です。(本題とは関係がないため割愛します)。
<画像9:scanf()を書き直した場合の動作 – 画像4とは異なり名前が変更されていない。>
②fgets()を使用する場合
fgetsは読み込む文字数を指定する必要があるため、バッファオーバーフローを発生させにくい関数です。
fgets()を使用する場合は以下のように記述します。
printf(“Input your Name (Less than 10 characters). : “);
fgets(szName, 10, stdin);
while(fgetc(stdin) != ‘\n’);
if ((pTempAddr = strchr(szName, ‘\n’)) != NULL) *pTempAddr = ‘\0’;
printf(“Input your PassWord (Less than 10 characters). : “);
fgets(szPassWord, 10, stdin);
while(fgetc(stdin) != ‘\n’);
if ((pTempAddr = strchr(szPassWord, ‘\n’)) != NULL) *pTempAddr = ‘\0’;
※ただしfgets()は改行文字も文字列内に取り込んでしまうため、
元と同じ動作をさせたい場合は改行文字を削除する必要があります。
<画像10:fgets()を用いて書き直した場合の動作 – 画像4とは異なり名前が変更されていない。>
また、①②どちらの場合でも文字数に制限を付けたため、余った入力を読み飛ばすといった作業が必要になります。
5.まとめ
いかがだったでしょうか。
C/C++言語はバッファの管理がプログラム製作者に任せられるため、
実装に気を付けないと今回のような脆弱性を生み出してしまう可能性があります。
脆弱性といえばプラットフォーム特有のものだという印象が強いですが、
このようにプログラミング言語特有のものも存在します。
そのため、セキュリティ対策には多面的な観点から検討する必要があります。
当サイトでは、アプリのセキュリティについて詳しくわかる資料をご用意しています。
アプリのセキュリティや対策についてご興味を持たれた方は、
ぜひダウンロードしていただけますと幸いです。
また、弊社では
アプリへの不正な解析・改ざん行為(クラッキング)を防ぐ対策製品
「CrackProof」の開発・販売を行っております。
詳しく話を聞いてみたいと思われた方は、
当サイトからお気軽にお問い合わせください。
お問い合わせはこちら
※参考:
IPA セキュア・プログラミング講座 C/C++言語編
第1章 総論 バッファオーバーフローを理解するための基礎知識
(https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c005.html )
IPA セキュア・プログラミング講座 C/C++言語編
第10章 著名な脆弱性対策 バッファオーバーフロー: #1 概要
(https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c901.html )