JetBrains RiderでSnykプラグインを使って脆弱性チェックしてみた
ゲームソリューション部の えがわ です。
今回は、JetBrains Rider(以下Rider)でSnykプラグインを使って脆弱性チェックを行ってみます。
Snykとは
Snykは、依存関係やコードの脆弱性を検出してくれるセキュリティプラットフォームです。
IDE用のプラグインも提供されており、Riderでも利用できます。
Snykは以下のような脆弱性検出機能を提供しています
- コード内のセキュリティ問題の特定
- 依存ライブラリの既知の脆弱性検出
- コンテナイメージの脆弱性スキャン
- IaC(Infrastructure as Code)の設定ミス検出
RiderでのSnykプラグイン導入手順
1. Snykアカウントの作成
まずはSnyk公式サイトでアカウントを作成します。
無料プランでも個人開発には十分な機能が使えます。
2. Riderへのプラグインインストール
Riderを起動し「設定」→「プラグイン」を開きます。
Marketplaceを選択し、検索ボックスで「Snyk」と入力し、プラグインをインストールします。
初回インストール時には同意を求められるポップアップが表示されます。
インストール後、Riderを再起動します。
Snykプラグインインストール(別方法)
RiderのUIからではなく、プラグインをダウンロードして任意のフォルダに配置することでインストールすることも可能です。
こちらのURLよりIntelliJ用のSnykプラグインをダウンロードすることができます。
Riderのプラグインフォルダに解凍した中身を配置することでプラグインのインストールが可能です。
3. Snykのセットアップと認証
画面左下の「Snyk」アイコン(犬のパッチ君)を選択します。
設定画面を開き「Connect IDE to Snyk」ボタンを押下するとブラウザに遷移し認証を行うことが可能です。
(オプション)組織で使用する場合はOrganization(組織ページのURLのSlug)も設定します。
脆弱性スキャンをやってみる
実際にスキャンを行ってみます。
本日スキャンするコードはこちら
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <string>
// 1. バッファオーバーフロー
void VulnerableBuffer(const char* input) {
char buffer[8];
// バッファサイズをチェックせずコピー(バッファオーバーフロー)
strcpy(buffer, input);
std::cout << "Buffer: " << buffer << std::endl;
}
// 2. パストラバーサル
void SaveData(const std::string& filename, const std::string& data) {
// 入力値をそのままファイル名に使用(パストラバーサル脆弱性)
std::ofstream ofs(filename);
ofs << data;
ofs.close();
std::cout << "Saved data to " << filename << std::endl;
}
// 3. コマンドインジェクション
void RunCommand(const std::string& userInput) {
// 入力値をそのままコマンドに(コマンドインジェクション)
std::string cmd = "echo " + userInput;
system(cmd.c_str());
}
int main() {
std::cout << "脆弱性デモ開始\n";
// 1. バッファオーバーフロー
std::cout << "[1] バッファオーバーフローの例\n";
char userInput[256];
std::cout << "何か入力してください(長い文字列を入力すると危険!): ";
std::cin.getline(userInput, 256);
VulnerableBuffer(userInput);
// 2. パストラバーサル
std::cout << "\n[2] ファイル保存の例(パストラバーサル脆弱性)\n";
std::string filename;
std::cout << "保存するファイル名を入力してください: ";
std::getline(std::cin, filename);
SaveData(filename, "これはテストデータです");
// 3. コマンドインジェクション
std::cout << "\n[3] コマンド実行の例(コマンドインジェクション)\n";
std::string cmdInput;
std::cout << "コマンドに渡す文字列を入力してください: ";
std::getline(std::cin, cmdInput);
RunCommand(cmdInput);
std::cout << "\n脆弱性デモ終了\n";
return 0;
}
このコードには3つの脆弱性が隠れています。
バッファオーバーフロー
メモリ上に確保した領域(バッファ)の境界を超えてデータを書き込んでしまう脆弱性です。
サイズチェックなしにstrcpy
関数を使うと、メモリ破壊やプログラムのクラッシュを引き起こす可能性があります。
パストラバーサル
ユーザー入力をそのままファイルパスとして使用することで、意図しないディレクトリのファイルにアクセスされる脆弱性です。
../../../etc/passwd
のような入力で重要なシステムファイルにアクセスされる危険があります。
コマンドインジェクション
ユーザー入力をそのままシステムコマンドに組み込むことで、意図しないコマンドが実行される脆弱性です。
hello; rm -rf /
のような入力で悪意のあるコマンドが実行される可能性があります。
スキャン結果
ちゃんと3つの脆弱性を検知できています。
脆弱性の内容も確認することが可能です。
脆弱性の種類によっては自動で修正を行えるDeepCode AIを使用することが可能です。
バッファオーバーフローは使用できるのでGenerate AI fixを押下すると修正例が提示されました。
Apply fixで適応させます。
脆弱性も2つに減っていますね!
strncpy
になり、メモリ領域内に収まるようになりました。
void VulnerableBuffer(const char* input) {
char buffer[8];
// バッファサイズをチェックせずコピー(バッファオーバーフロー)
strncpy(buffer, input, sizeof(buffer) - 1); // Copy up to sizeof(buffer) - 1 characters
std::cout << "Buffer: " << buffer << std::endl;
buffer[sizeof(buffer) - 1] = '\0'; // Ensure null termination
}
修正後のコード
DeepCode AIとサンプルを参考に修正したコードはこちら
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <regex>
// 1. バッファオーバーフロー対策
void SafeBuffer(const char* input) {
char buffer[8];
// strncpyを使い、終端も保証する
std::strncpy(buffer, input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0'; // 念のため終端
std::cout << "Buffer: " << buffer << std::endl;
}
// 2. パストラバーサル対策
bool IsSafeFilename(const std::string& filename) {
// ファイル名に危険な文字列が含まれていないかチェック
// 例: ../ や ..\ などのパストラバーサルや、絶対パス(C:\や/)を禁止
if (filename.find("..") != std::string::npos) return false;
if (filename.find('/') != std::string::npos) return false;
if (filename.find('\\') != std::string::npos) return false;
if (filename.empty()) return false;
// ファイル名は英数字と_と.のみ許可(例として)
std::regex safePattern("^[A-Za-z0-9_.]+$");
return std::regex_match(filename, safePattern);
}
void SafeSaveData(const std::string& filename, const std::string& data) {
if (!IsSafeFilename(filename)) {
std::cout << "不正なファイル名です: " << filename << std::endl;
return;
}
std::ofstream ofs(filename);
if (!ofs) {
std::cout << "ファイルを開けませんでした: " << filename << std::endl;
return;
}
ofs << data;
ofs.close();
std::cout << "Saved data to " << filename << std::endl;
}
// 3. コマンドインジェクション対策
void SafeRunCommand(const std::string& userInput) {
// 入力値に危険な文字が含まれていないかチェック
// 例: 英数字とスペースのみ許可
std::regex safePattern("^[A-Za-z0-9 ]*$");
if (!std::regex_match(userInput, safePattern)) {
std::cout << "不正な文字が含まれています。コマンド実行を中止します。" << std::endl;
return;
}
// 安全な場合のみコマンドを実行
std::string cmd = "echo " + userInput;
system(cmd.c_str());
}
...以下略
ファイル名、ユーザー入力のチェック処理を実装することで、脆弱性の修正が完了したと判定してくれます。
さいごに
RiderにSnykプラグインを導入することで、開発効率を落とさずにセキュリティ対策を強化できます。
今回はコードのスキャンを行ってみましたが、特にオープンソースライブラリを多用する開発では、定期的な脆弱性チェックが重要です。
この記事がどなたかの参考になれば幸いです。