セキュアコーディング(Secure coding)は、セキュリティ脆弱性の意図しない混入を防ぐような方法でコンピュータのソフトウェアを開発する実践である。セキュアプログラミングともいわれる。一般に、欠陥、バグ、および論理の矛盾は、悪用されるソフトウェアの脆弱性の主な原因であり続けている。報告された何千もの脆弱性に関するセキュリティ専門家の分析によると、ほとんどの脆弱性が比較的少数の一般的なソフトウェアプログラミングエラーに起因することが明らかになっている。これらのエラーにつながる安全でないコーディング慣行を特定し、開発者に安全な代替案を教育することによって、組織はソフトウェアの展開前に脆弱性を撲滅する積極的な措置を講じることができる。

一部の学者は、サイバーセキュリティに関連する脅威に効果的に対処するためには、適切なセキュリティをシステムにコーディングまたは「組み込む」べきだと提案している。セキュリティがソフトウェアに設計されることで、内部関係者による攻撃からの保護が保証され、アプリケーションセキュリティへの脅威が軽減される。

原則 SEI CERT Top 10 Secure Coding Practicesにおいて、ソフトウェアプログラムにおける実装原則が示されている。

Validate input. 信頼できないデータソースからの入力は、すべて検証する必要があります。適切な入力検証を行うことで、多くのソフトウェアの脆弱性を防ぐことができる。検証すべきデータソースとしては、コマンドライン引数、ネットワーク・インターフェース、環境変数、ユーザーが管理するファイル、データベース(DBMS)から取得したデータなどがある。特にWebアプリケーションの場合は、ユーザーフォームからの入力だけでなく、クッキーやHTMLヘッダーなど、クライアントから受け取るすべての値を精査すべきである。

Sanitize data sent to other systems. 外部のシステムやコンポーネントにデータを渡す際は、その渡し先で問題が起きないようにデータを無害化(サニタイズ)する必要がある。データを受け取るサブシステム側(DBMSなど)が呼び出しの意図(コンテキスト)を理解できないため、入力検証の問題ではなく出力側の問題とされている。データを渡す側のプロセスがコンテキストを理解しているため、サブシステムなどを呼び出す前に責任を持ってデータを無害化する必要がある。これには、不正な文字の処理だけでなく、データ型の適切な取り扱いや、安全でない関数を使用しないといった配慮も含まれる。

Webページへの出力については、 XSS(クロスサイトスクリプティング)対策、データベース(DBMS)への命令については、SQLインジェクション対策、コマンドシェルへの命令:についてはOSコマンドインジェクション対策が必要である。

Heed compiler warnings. コンパイラはコードに対する最初の静的解析であり、その警告は潜在的な問題点やセキュリティ上の欠陥を示唆する重要な情報源である。この警告に対し、適切に対応するべきである。

警告を無視しない: プログラムが実行可能であったり、警告が大量に出力されたりすることを理由に、警告を無視してはいけない。 警告レベルの活用: コンパイラのオプション設定で警告レベルを上げることで、より多くの潜在的な問題を発見する。 追加ツールの利用: ️より多くのセキュリティ欠陥を検出し排除するために、コンパイラだけでなく、静的および動的解析ツールを併用することが推奨される。

Architect and design for security policies. セキュリティの設計と実装は、あらかじめ定められたセキュリティポリシーを実現するために行う。これは、「守るべきもの」を具体的に特定し、そこに資源を集中させることを意味する。すべての資産を均一に守ろうとするアプローチは、結果的に何も守れないことと同じであり、効果的ではない。

個別のアプリケーションやシステムで定義されたセキュリティポリシーに基づき、それを実現するためのソフトウェアアーキテクチャの作成、設計、そして実装を行う必要がある。

Keep it simple. 設計と実装は、可能な限りシンプルに保つべきという考え方である。これは「Economy of mechanism(効率的なメカニズム)」という設計原則と同じ考え方である。シンプルな設計は、開発からデバッグ、保守に至るまでの全工程でミスが起こる可能性を低くする。また、複雑な設計は攻撃を防ぐわけではなく、むしろミスの発生率を高め、結果として脆弱性が生まれる可能性を高める。複雑さは、実装・設定・使用時のエラーを増やすだけでなく、セキュリティ対策そのものを困難にしする。常にシンプルで小さなデザインを目指すべきである。

Default deny. アクセス制御は、デフォルトで「拒否」する設定を基本とすべきという考え方である。これは「Fail-safe defaults(フェイルセーフなデフォルト)」という設計原則と同じ考え方である。アクセスを許可する条件を明示的に指定し、それ以外はすべて拒否するという「許可ベース(ホワイトリスト方式)」の考え方を採用する。

Adhere to the principle of least privilege. すべてのプロセスやユーザーには、タスクの実行に必要最低限の権限(特権)のみを与えるべきという考え方である。これは「Least privilege(最小限の権限)」の設計原則と同じである。権限を昇格させる必要がある場合でも、その時間を可能な限り短くする必要がある。このアプローチにより、万が一攻撃を受けた際に、昇格した権限で不正なコードを実行されるリスクを大幅に低減できる。

Practice defense in depth. 多層防御(Defense in Depth)とは、根本的な対策や保険的な対策など、複数の異なる防御策を重ねて配置するセキュリティ戦略である。これは、単一の防御策が破られてもシステム全体が危険に晒されるのを防ぎ、被害を限定することを狙いとしている。例えば、「セキュアなプログラミング技術」と「セキュアな実行環境」を組み合わせることで、コード内に脆弱性が残っていたとしても、実際に攻撃される可能性を減らすことができる。

Use effective quality assurance techniques. 効果的な品質保証(QA)は、機能の確認だけでなく、脆弱性を特定し排除するためにも重要である。品質保証(QA)プログラムに、以下のようなセキュリティテストを組み込むことで、脆弱性を未然に発見できる。さらに、独立した第三者によるセキュリティレビューを行うことで、開発チーム内の思い込みや無効な前提条件を客観的に発見し、修正につなげられるため、よりセキュアなシステムを構築できる。

ファズテスト: 予期せぬデータを大量に送り、システムの挙動を監視するテスト。 ペネトレーションテスト: 実際に攻撃者の視点でシステムに侵入を試み、脆弱性を探すテスト。 ソースコード監査: プログラムの設計やコードを直接レビューし、問題点を発見する手法。

Adopt a secure coding standard. 開発では、使用するプログラミング言語やプラットフォームに応じたセキュアコーディング標準を採用し、それに従うべきという考えである。多くのセキュリティ対策は複数の箇所で共通して必要となるため、標準化することで効率的に実装できる。また、採用した標準に不備が見つかった場合でも、共通のルールで対応しているため、修正が容易になる。

バッファオーバーフローの防止

バッファオーバーフローは、一般的なソフトウェアのセキュリティ脆弱性であり、プロセスが固定長のバッファを超えてデータを格納しようとするときに発生する。例えば、アイテムを格納するためのスロットが8つある場合、9つのアイテムを格納しようとすると問題が発生する。コンピュータのメモリでは、オーバーフローしたデータが隣接する場所のデータを上書きし、セキュリティ脆弱性(スタックスマッシング)やプログラムの終了(セグメンテーション違反)を引き起こす可能性がある。

バッファオーバーフローを起こしやすいCプログラムの例を以下に示す。 int vulnerable_function(char * large_user_input) { char dst[SMALL]; strcpy(dst, large_user_input); } ユーザー入力が宛先バッファよりも大きい場合、バッファオーバーフローが発生する。

この安全でないプログラムを修正するには、strncpyを使用してバッファオーバーフローの可能性を防ぐ。 int secure_function(char * user_input) { char dst[BUF_SIZE]; // 最大BUF_SIZEバイトをコピーする strncpy(dst, user_input, BUF_SIZE); // バッファの最後の文字をNULに設定する dst[BUF_SIZE - 1] = '\0'; } もう一つの安全な代替策は、mallocを使用してヒープ上にメモリを動的に割り当てることである。 char * secure_copy(char * src) { size_t len = strlen(src); char * dst = (char *) malloc(len + 1); if (dst != NULL) { strncpy(dst, src, len); // NULL終端文字を追加する dst[len] = '\0';

return dst; } 上記のコードスニペットでは、プログラムはsrcの内容をdstにコピーしようと試み、同時にmallocの戻り値をチェックして、宛先バッファに十分なメモリが割り当てられたことを確認する。

書式文字列攻撃の防止

書式文字列攻撃とは、悪意のあるユーザーが、printf()のような書式設定を行う関数の引数として最終的に入力される特定の入力を提供することである。この攻撃では、攻撃者がスタックからの読み取りや書き込みを行う。C言語のprintf関数は、標準出力(stdout)に出力を行う。printf関数のパラメータが適切にフォーマットされていない場合、いくつかのセキュリティバグが混入する可能性がある。

以下に、書式文字列攻撃に対して脆弱なプログラムを示す。 int vulnerable_print(char * malicious_input) { printf(malicious_input); } プログラムに渡される悪意のある引数として「%s%s%s%s%s%s%s」のようなものがあり、これは不適切なメモリアクセスによりプログラムをクラッシュさせる可能性がある。

整数オーバーフローの防止

整数オーバーフローは、算術演算の結果が、利用可能な領域内に表現するには大きすぎる整数になったときに発生する。整数オーバーフローを適切にチェックしないプログラムは、潜在的なソフトウェアのバグやエクスプロイトを招き入れる。

以下は、xとyの和が定義された値MAX以下であることを確認しようとするC++の関数である。 bool sumIsValid_flawed(unsigned int x, unsigned int y) { unsigned int sum = x + y; return sum <= MAX; } このコードの問題点は、加算演算における整数オーバーフローをチェックしていないことである。xとyの和がunsigned intの最大値より大きい場合、加算演算はオーバーフローし、xとyの実際の和がMAXより大きいにもかかわらず、計算結果がMAX以下になる可能性がある。以下は、和がxとyの両方以上であることを確認することでオーバーフローをチェックする関数である。もし和がオーバーフローした場合、その和はx未満またはy未満になる。 bool sumIsValid_secure(unsigned int x, unsigned int y) { unsigned int sum = x + y; return sum >= x && sum >= y && sum <= MAX; }

ディレクトリトラバーサル(パストラバーサル)の防止

ディレクトリトラバーサルは、信頼できないソースから提供されたパスが、不正なファイルアクセスが可能になるような方法で解釈される脆弱性である。

例えば、ファイル名を受け取って記事を取得し、そのファイルを読み込んでパースするスクリプトを考える。このようなスクリプトは、ドッグフードに関する記事を取得するために、次のような架空のURLを使用するかもしれない。 https://www.example.net/cgi-bin/article.sh?name=dogfood.html スクリプトに入力チェックがなく、ファイル名が常に有効であると信頼している場合、悪意のあるユーザーはWebサーバーから設定ファイルを取得するためにURLを偽造することができる。 https://www.example.net/cgi-bin/article.sh?name=../../../../../etc/passwd スクリプトによっては、これにより/etc/passwdファイルが漏洩する可能性がある。このファイルはUnix系システムにおいて、(とりわけ)ユーザーID、ログイン名、ホームディレクトリのパス、シェルを含んでいる。(同様の攻撃についてはSQLインジェクションを参照のこと。)

脚注

参考文献

関連項目 セキュリティバグ バッファオーバーフロー クロスサイトスクリプティング SQLインジェクション OSコマンドインジェクション 書式文字列攻撃 ディレクトリトラバーサル