「ChatGPT」は“夢の静的解析ツール”になれるのか? コード診断の新時代ChatGPTはSASTツールになるのか【前編】

コーディングの世界に生成AIの波が押し寄せている。「ChatGPT」が「SAST」に関する開発プロセスを変える可能性があるという。どの程度実用的なのか。サンプルコードを使いながらChatGPTの実力を探る。

2024年08月08日 05時00分 公開
[Matthew SmithTechTarget]

 テキストや画像などを自動生成する人工知能(AI)技術「生成AI」(ジェネレーティブAI)は、プロンプト(生成AIに対して出す質問や指示)からソースコードを生成するなど、さまざまな活用法がある。MicrosoftのAIアシスタント「Microsoft Copilot」をはじめとするツールは、開発者が書いているソースコードに基づいて、次に書くべきソースコードを提案する。

 一方で生成AIとの“共同プログラミング”は、セキュリティに関する疑問を呼び起こしている。AI技術の強みは自己学習能力だ。そうであれば、自身が出力したソースコードの安全性を確保する方法をAIモデルに教えれば、脆弱(ぜいじゃく)性のあるソースコードを書く問題を緩和できるのではないか――こうしたアイデアが浮かんでくる。

 これを実現するためのヒントが「SAST」(静的アプリケーションセキュリティテスト)にある。SASTはソースコードを分析して脆弱性を特定する手法だ。SASTツールを使うことで、以降に示す攻撃を引き起こす可能性のある「ソースコード中の問題」を探し出すことができる。それと同じことをChatGPTで実行できるかどうかを確認しよう。

「ChatGPTでSAST」はどうやる?

  • SQLインジェクション攻撃
    • データベース操作に悪意のあるSQLクエリ(データベース操作の命令)を挿入する攻撃。
  • OSコマンドインジェクション攻撃
    • OSが実行するコマンドに悪意のある命令を挿入する攻撃。
  • サーバーサイドテンプレートインジェクション(SSTI)攻撃
    • サーバが実行するコマンドに悪意のある命令を挿入する攻撃。

※編集者注:SASTツールの中には、ソースコード分析に加えて実行可能ファイルやバイトコードも分析するものがある。だが本連載においては、実行可能ファイルまたはバイトコード(プログラミング言語と機械語の中間言語)分析については扱わないこととする。

 これらの攻撃を引き起こす可能性のある「ソースコード中の問題」をChatGPTで探し出すための確認作業は次の通りだ。

例1.シンプルな問題

 まず生成AIが、自身の生成したソースコードにある脆弱性を見つけることができるかどうかを確認する必要がある。セキュリティテストツールベンダーStackHawkが提供しているサンプルコードを使用して、生成AIが問題を発見できるかどうかを確認しよう。

app.post("/records", (request, response) => {
  const data = request.body;
  connection.query('SELECT * FROM health_records where id = ?', [data.id], (err, rows) => {
    if(err) throw err;
    response.json({data:rows});
  });
});

 これはスクリプト(簡易プログラム)言語「JavaScript」で記述した、単純なSQLインジェクションの例だ。1行目では、「/records」というURLに対してPOSTリクエスト(データをサーバに送信するためのリクエスト)が来たときに実行される関数を定義している。2行目で「data」変数を宣言して、エンドユーザーが送信したデータを含む値「request.body」で初期化している。3行目の「connection.query」はデータベースに対してSQLクエリを送信する関数だ。これを使って、「health_records」というテーブルから、エンドユーザーが送信した「id」と一致するIDを取得するSQLクエリを送信する。それ以降はSQLクエリの実行中にエラーが生じた場合の処理だ。

 ここで、エンドユーザーが送信したidをSQLクエリにそのまま挿入している箇所は脆弱な部分だ。エンドユーザーがdata.idの部分に悪意のある命令を挿入すると、意図しないSQLクエリが実行される可能性がある。ある程度経験を積んだセキュリティ専門家やソフトウェア開発者ならば、この脆弱性を見つけ出すことは難しくない。

 ChatGPTが問題を見つけ出せるかどうかをテストするには、ソースコードを入力した上で、「このソースコードは安全ですか?」と質問する。以下はChatGPTの返答の例だ。

 あなたが提供したソースコードは、SQLインジェクション攻撃に対して脆弱なため、安全ではありません。このソースコードでは、「request.body」に含まれる「id」の値が、検証やエスケープ処理(注1)を通すことなく直接SQLクエリに挿入されています。これによって、攻撃者はidの値を操作して、サーバ内のデータベースに任意のSQLクエリを実行させることができます。

 以下は、プレースホルダー(後から値を入力するための仮の文字)を使って安全性を向上させたSQLクエリです。SQLクエリに直接データを埋め込まないようにすることで、SQLインジェクションを防ぐことができます。

※注1:クエリやコマンドに影響する特殊な文字を、代わりの文字に置き換えて特殊な意味を持たせないようにする処理。

app.post("/records", (request, response) => (
  const data request.body;
  const query = "SELECT FROM health records WHERE id = ?";
  connection.query(query, [data.id], (err, rows) => {
    if (err) (
      console.error(err);
      response.status(500).send("An error occurred");
      return;
    }
    response.json((data: rows));
  });
});

この修正されたソースコードでは、

  • SQLクエリは、「id」用のプレースホルダーである「?」で定義されています。
  • 「id」は、クエリに値を安全に挿入する「connection.query」関数への配列パラメータとして渡されます。

(以下省略)

 筆者の例では、ChatGPTは正しく脆弱性を特定し、セキュリティ対策とエラー処理を施したソースコードを提示した。ChatGPTの修正は正しいものの、例として入力したソースコードの脆弱性は単純であり、対処法もよく知られたものだ。

例2.複雑な問題

 次は、研究機関MITREが公開している、脆弱性の種類を識別するための共通基準「CWE」(共通脆弱性タイプ一覧)にあるプログラムを使用してみよう。

void host_lookup(char *user_supplied_addr){
  struct hostent *hp;
  in_addr_t *addr;
  char hostname[64];
  in_addr_t inet_addr(const char *cp);
  
  /* user_supplied_addrが変換可能な形式かどうかを確認する処理 */
  validate_addr_form(user_supplied_addr);
  addr = inet_addr(user_supplied_addr);
  hp = gethostbyaddr( addr, sizeof(struct in_addr), AF_INET);
  strcpy(hostname, hp->h_name);
}

 このソースコードはプログラミング言語「C」で記述された、IPアドレスからホスト名を解決(逆引き)するためのプログラムだ。エンドユーザーが与えたIPアドレス「user_supplied_addr」に対応するホスト名を取得することを目的としている。2行目は、ホストマシンに関する情報を格納するための構造体(異なるデータ型をまとめて扱えるようにしたデータ構造)「hostent」へのポインタ「hp」を宣言している。ポインタは、データの格納先を示す値のことだ。続いて3行目では、IPアドレスを格納するためのポインタ「addr」を「in_addr_t」型で宣言している。4行目の「hostname」は、ホスト名を格納するための文字列用の配列だ。5行目では、IPアドレス形式の文字列をバイナリ形式に変換する関数「inet_addr」のプロトタイプ宣言(関数の存在と構造をあらかじめ宣言すること)をしている。それ以降では、「validate_addr_form」で検証した入力値のうちIPアドレスをaddrが、ホスト名をhostnameが参照できるようにしている。

 ただし先で述べたように、このソースコードには以下の脆弱性があり、1つ目の例よりも複雑な修正が必要だ。

  • バッファオーバーフロー
    • バッファ(一時記憶領域)があふれて、プログラムの不具合が起こること。
    • この例では、プログラムが、64文字用のhostnameに対して、それ以上の長さの文字列をコピーしようとする可能性がある。
  • 戻り値のチェック不足
    • 関数が適切な戻り値を返すかどうかを確認していないこと。
    • この例では関数「gethostbyaddr()」の結果を確認していないため、関数が何も値を返さない場合はエラーが発生する。
  • NULLポインタ参照
    • ポインタが何も指していない状態(NULL)になること。
    • gethostbyaddr()の呼び出しが失敗し、その戻り値として適切なポインタが変数「hp」に格納されない可能性がある。

 ChatGPTにこのソースコードを入力し、1つ目の例と同じように「このソースコードは安全ですか?」と質問してみる。すると答えは以下のようになった。

 提供されたソースコードは、エンドユーザーが指定したIPアドレスに基づいてホスト名を検索することを目的としています。ただし、このソースコードには、幾つかの問題と潜在的な脆弱性があります。

 1.inet addrの使用。inet addrは、IP(インターネットプロトコル)のバージョン「IPv4」のIPアドレスを、ドット付き10進表記からバイナリ形式に変換するための関数です。入力が無効で変換できない場合、inet_addrは無効な値を受け取ったことを示す値「INADDR_NONE」(通常は「-1」)を返します。このソースコードは、inet_addrの呼び出しが失敗した場合の処理を実装しておらず、gethostbyaddrを無効な引数で呼び出した場合、予期しない動作につながる可能性があります。

 2.NULLポインタ参照の可能性。gethostbyaddrは、hostentへのポインタを返すか、エラーが発生した場合(ホストが見つからない場合など)はNULLを返します。このソースコードは、hpがNULLかどうかを確認せずに、hostnetの中にある「h_name」(正式なホスト名)を参照する操作「hp->h_name」を使用しています。これにより、一般的な脆弱性であるNULLポインタ参照が生じる可能性があります。

 3.バッファオーバーフローのリスク。このソースコードは、strcpyを使用してホスト名を固定長(64文字)の配列であるhostnameにコピーしていますが、ホスト名がhostnameに収まるかどうかを確認していません。これは典型的なバッファオーバーフローの脆弱性です。長いホスト名が隣接するメモリを上書きし、任意の命令が実行されたりプログラムのクラッシュを引き起こしたりする可能性があります。

 4.エラー処理の欠如。このソースコードには、無効なIPアドレス形式、IPアドレス変換の失敗、DNS(ドメインネームシステム)解決の問題など、さまざまな障害状況に対するエラー処理が全体的に欠けています。

 ChatGPTはソースコードの問題点を正しく認識し、修正済みのソースコードを提示した。だがChatGPTが提示したソースコードは安全なのか。ChatGPTに聞いてみよう。

 提供されたソースコードには幾つかのセキュリティ上の考慮事項があり、潜在的なバッファオーバーフローの脆弱性があるため、完全には安全とは言えません。潜在的な問題点と、それらに対処するために推奨されることを以下に示します。

 1.バッファオーバーフロー。strcpy()を使用してホスト名をローカルバッファ(hostname)にコピーするのは危険です。gethostbyaddr()が返したホスト名がバッファサイズ(NULL終端文字<注2>を含む64バイト)よりも長い場合、hostnameがオーバーフローします。これによってメモリの破損、アプリケーションのクラッシュ、任意コード実行に発展するおそれがあります。このリスクを軽減するには、strcpy()の代わりに「strncpy()」(注3)を使用し、文字列がNULL終端されていることを確認する必要があります。

 2.IPアドレス形式の検証。validate_addr_form()は、エンドユーザーが入力したIPアドレスが正しい形式かどうかを検証するための関数です。こうした検証自体は良いことですが、この関数の実装が重要です。エンドユーザーが入力したIPアドレスを「inet_aton()」(注4)に渡す前に、この値が正しいIPアドレス形式であることをきちんと確認する必要があります。

 3.エラー処理。このソースコードは、inet_aton()とgethostbyaddr()の実行が失敗したかどうかをチェックしますが、IPアドレスが無効な場合はvalidate_addr_form()も0以外の値を返すと想定しています。この関数が堅牢であることを確認する必要があります。

※注2:文字列の終わりを示す特殊な文字。
※注3:指定された長さ(n)までの文字列をコピーし、必要に応じてNULL終端を追加する関数。
※注4:IPアドレス形式の文字列をバイナリ形式に変換し、成功または失敗したことを返す関数。

 ChatGPTは、最初に与えたソースコードを修正するためのソースコードを追加で提示した。自身が生成したソースコードをさらに修正したということは、最初のやりとりだけではChatGPTが全ての問題を解消できるとは限らないということだ。

 「このソースコードは安全ですか?」という質問とともに、継続的に生成されたソースコードをChatGPTに与え続けると、より多くの“エラー”が見つかった。例えば3回目の質問でChatGPTは、gethostbyaddr()は古い関数であり、使用しない方が望ましいことを指摘した。最初からそれを報告しなかったのはなぜなのか。

 さらに心配なのは、ソースコードが「安全なソースコード」になるまでに何回質問する必要があるのかという点だ。開発者や企業ごとに安全なソースコードの定義が異なるため、この質問に対する答えを見つけるのは簡単ではない。


 次回は、ChatGPTをはじめとする生成AIがSASTツールの代替になり得るかどうかを議論する。

TechTarget発 エンジニア虎の巻

米国TechTargetの豊富な記事の中から、開発のノウハウや技術知識など、ITエンジニアの問題解決に役立つ情報を厳選してお届けします。

ITmedia マーケティング新着記事

news036.jpg

「缶入りのただの水」を評価額14億ドルのビジネスに育てたLiquid Deathのヤバいマーケティング戦略
「渇きを殺せ(Murder Your Thirst)」という刺激的なメッセージにエッジの利いた商品デ...

news064.jpg

「日本の食料自給率38%」への不安感は8割越え
クロス・マーケティングは、大気中の二酸化炭素濃度や紫外線量の増加による地球温暖化の...

news066.jpg

「マツキヨココカラ公式アプリ」が「Temu」超えの初登場1位 2024年のEコマースアプリトレンド
AdjustとSensor Towerが共同で発表した「モバイルアプリトレンドレポート 2024 :日本版...