バッファオーバーフローとは?仕組み・被害例・対策をわかりやすく解説

Vulnerability

「バッファオーバーフロー」という言葉は聞いたことがあるけど、実際にどんな仕組みで攻撃されるのか、よくわからない。

そんなふうに感じているエンジニアは多いと思います。バッファオーバーフローは1988年のMorrisワーム以来、繰り返し悪用されてきた脆弱性の古典であり、2020年代に入った今も、組み込み機器やC言語で書かれたレガシーシステムでの発見が後を絶ちません。

この記事では、バッファオーバーフローの仕組み・攻撃者がどう悪用するか・そして開発者と運用担当者がそれぞれ取れる対策を、防御の視点から現場目線で解説します。

バッファオーバーフローとは?仕組み・被害例・対策をわかりやすく解説

バッファオーバーフローとは?(概要・なぜ重要か)

バッファオーバーフロー(Buffer Overflow)とは、プログラムがデータを格納するために確保したメモリ領域(バッファ)に、想定以上のデータを書き込んでしまう脆弱性です。

「バッファ」は日本語で言えば「一時的な記憶場所」です。たとえば、ユーザー名を入力させる画面で「最大20文字まで格納する」とプログラムが設計されていたとします。ところが入力チェックが不十分だと、21文字目以降のデータが隣接するメモリ領域に「はみ出して」上書きされます。これがバッファオーバーフローです。

はみ出したデータがプログラムの制御に関わるメモリ領域を書き換えた場合、攻撃者は処理の流れを乗っ取り、任意のコードを実行できてしまいます。

なぜ今も重要か?
JavaやPythonのような現代的な言語はメモリ管理が自動化されており、バッファオーバーフローが起きにくい設計になっています。しかし、以下の環境では今もリスクが残っています。

C/C++で書かれたシステム: ルーター・カメラ・PLC等の組み込み機器、ゲームエンジン、OSカーネル
レガシーシステム: 10〜20年前に構築されたサーバーサイドプログラム
オープンソースライブラリ: 依存関係として取り込んでいるCライブラリに脆弱性が潜む場合がある

攻撃の仕組み(敵を知る)

1. メモリの構造とバッファ

プログラムが実行されるとき、OSはそのプログラムのためにメモリを割り当てます。このメモリはいくつかの領域(セグメント)に分かれており、主に「スタック」と「ヒープ」が重要です。

スタックは関数の呼び出し情報(ローカル変数・戻りアドレスなど)を積み重ねる領域です。戻りアドレスとは「この関数が終わったら次にどこを実行するか」を示すポインタで、スタック上に記録されています。

2. 攻撃者がバッファを溢れさせる方法

攻撃者が送り込むデータがどのような影響を与えるか、概念として把握しておきましょう。

# 脆弱なCコードの概念例 # gets() は入力長チェックをしないため危険な関数 void login(void) { char username[20]; # 20バイトのバッファを確保 gets(username); # 21バイト以上が入力されるとはみ出す }

攻撃者が20バイトを超える文字列を入力すると、はみ出した部分がスタック上の隣接データ(戻りアドレスを含む)を上書きします。攻撃者が戻りアドレスを自分が仕込んだシェルコードの位置に書き換えることで、関数終了時に意図した処理を実行させることができます。これを「リターンアドレスの上書き(Return Address Overwrite)」と呼びます。

3. スタックベースとヒープベースの違い

バッファオーバーフローには大きく2種類あります。

種類 対象領域 発生しやすいパターン
スタックベース スタック(関数のローカル変数・戻りアドレス) 固定長バッファへの無制限入力
ヒープベース ヒープ(動的確保されたメモリ) malloc等で確保したバッファへの過剰書き込み

スタックベースは比較的発見・悪用されやすく、ヒープベースはより複雑な技術を要しますが、モダンな保護機構を回避する際によく使われます。

具体的な防御手順

1. コンパイラの保護機能を有効にする

GCC/Clangには、コンパイル時にバッファオーバーフローの影響を軽減する保護機能が組み込まれています。

# スタックプロテクター(スタックカナリア)を有効化 # 戻りアドレスの前にカナリア値を置き、改ざんを検知する gcc -fstack-protector-strong -o myapp myapp.c # FORTIFY_SOURCE: 危険な関数(strcpy, gets等)を安全版に置換 gcc -D_FORTIFY_SOURCE=2 -O2 -o myapp myapp.c # ASLR との組み合わせ用に PIE を有効化 gcc -fPIE -pie -o myapp myapp.c

「スタックカナリア」とは、スタック上の重要データの前に置く番人のような値です。関数終了時にこの値が変わっていたら、バッファが溢れたと判断してプログラムを強制終了します。カナリアが値を見張っている様子が炭鉱のカナリアに似ていることから名付けられました。

2. OSレベルの保護機能を確認する

現代のLinuxカーネルは複数のハードウェア・OSレベル保護を提供しています。

# ASLR(アドレス空間配置ランダム化)の確認 # 0=無効 / 1=部分的 / 2=完全(推奨) cat /proc/sys/kernel/randomize_va_space # 2 になっていない場合は有効化 echo 2 | sudo tee /proc/sys/kernel/randomize_va_space # 恒久設定(再起動後も維持) sudo tee /etc/sysctl.d/99-aslr.conf << 'EOF' kernel.randomize_va_space = 2 EOF sudo sysctl --system

ASLR(Address Space Layout Randomization)は、スタック・ヒープ・ライブラリがメモリ上のどこに配置されるかをランダム化します。攻撃者が書き換えたアドレスへジャンプしようとしても、毎回アドレスが変わるため、攻撃成功率を大幅に下げられます。

# DEP/NX(データ実行防止)の確認 # バイナリが NX(No-eXecute)ビット付きでビルドされているか確認 readelf -l /path/to/binary | grep GNU_STACK # Flags 欄に E が含まれていなければ NX 有効(安全) # checksec ツールで一括確認(インストール: pip install checksec.py) checksec --file=/path/to/binary

NX(No-eXecute)ビットは、スタックやヒープ上のデータ領域をコードとして実行することを禁止するハードウェア機能です。仮にバッファを書き換えられても、その領域にあるシェルコードを実行させることができなくなります。

3. 安全な関数を使う(開発者向け)

自社開発のシステムがある場合、危険な関数を安全な代替品に置き換えることが根本対策になります。

危険な関数 安全な代替関数 理由
gets() fgets(buf, sizeof(buf), stdin) gets は入力長を制限できない
strcpy(dst, src) strncpy(dst, src, sizeof(dst)-1) コピー長の上限を指定できる
sprintf(buf, fmt, …) snprintf(buf, sizeof(buf), fmt, …) 書き込みバイト数を制限できる
strcat(dst, src) strncat(dst, src, n) 連結後の最大長を指定できる

コードレビューや静的解析ツール(Coverity、cppcheck、clang-analyzer 等)を使って危険な関数を検出する仕組みを CI/CD パイプラインに組み込むと、属人的なチェックに依存しない体制を作れます。

中小企業でも今日からできること

「自社ではCのコードを書いていないから関係ない」と感じた方も、以下の点は確認しておく価値があります。

使用しているルーターやNASのファームウェアを最新化する: ネットワーク機器のCプログラムにバッファオーバーフローが発見されることは多く、ベンダーのアップデート情報を定期的に確認する習慣が重要です
脆弱なサードパーティライブラリを把握する: Webアプリケーションが間接的に使っているCライブラリ(ImageMagick、libpng 等)に脆弱性が出た場合、すぐに把握できる仕組み(SCA ツール / SBOM 管理)を整える
外部委託先の開発物のセキュリティ要件を明文化する: 受託開発のシステムに「コンパイラ保護オプションの有効化」「静的解析の実施」を契約上の要件として盛り込む
WAFでリクエスト長を制限する: WebベースのAPIへの過剰に長いリクエストをWAFで遮断することで、攻撃の入口を絞る

根本的な対策は開発者が担いますが、運用サイドでも「使用ソフトウェアの脆弱性情報を追う」「ネットワーク境界でできることをやる」の2点は今日から始められます。

よくある誤解と注意点

「Webアプリだから関係ない」: PythonやJavaで書かれたWebアプリ自体は比較的安全ですが、そのアプリが呼び出す画像処理ライブラリや、インフラ機器のファームウェアにはC/C++が使われています。クラウド時代でも完全に無縁とは言えません
「ASLRがあれば安心」: ASLRは攻撃の難易度を上げますが、情報漏洩脆弱性との組み合わせ(アドレスリーク)で回避されることがあります。多層防御として他の保護も組み合わせることが重要です
「古いシステムだからどうせパッチが出ない」: パッチが出ないシステムこそ、ネットワーク分離・WAF・モニタリング強化で攻撃の影響範囲を限定する必要があります。「パッチが当たらない=放置」は最も危険な判断です
「コンパイラ保護をONにするとパフォーマンスが下がる」: スタックカナリアやFORTIFY_SOURCEのオーバーヘッドは現代のシステムではほぼ無視できるレベルです。パフォーマンスを理由に無効化する判断は再考してください

本記事のまとめ

項目 内容
脆弱性の本質 確保したメモリ領域に想定外のデータが書き込まれ、制御フローが乗っ取られる
主な被害 任意コード実行・権限昇格・サービス停止・データ漏洩
コンパイラ対策 スタックカナリア(-fstack-protector-strong)、FORTIFY_SOURCE、PIEを有効化
OS対策 ASLR(randomize_va_space=2)、NX/DEPの有効化を確認
コード対策 gets/strcpy等の危険関数を排除し、静的解析をCIに組み込む
運用対策 ファームウェア更新の習慣化、WAFによるリクエスト長制限、SBOM管理

バッファオーバーフローは「古典的な脆弱性」ですが、組み込み機器やレガシーシステムが現役である限り、現代のインフラにも存在し続ける脅威です。開発・運用の両サイドから多層防御を積み重ねることが、根本的な対策になります。

Linuxのメモリ保護設定全般については、姉妹サイトLinuxMaster.JPでも関連する権限管理・カーネル設定の記事を公開しています。あわせてご参照ください。

自社システムのバッファオーバーフロー対策、どこから手をつけるべき?

使用しているソフトウェアに潜む脆弱性の把握から、コンパイラ設定の見直し、OS保護機能の確認まで、情シス1人体制でも取り組める順番でお届けします。
正しいセキュリティ知識を体系的に身につけたい方へ、メルマガで実践的なセキュリティ対策ノウハウをお届けしています。

コメント

タイトルとURLをコピーしました