
Seal Securityの例 — 脆弱なnpmアプリ(EJS CVE-2022-29078)が修正済みの密封バージョンに修正されました;GitHub Actions + Jenkins統合
最小限で意図的に脆弱性を持たせたNode.js/Expressアプリケーションです。エンドツーエンドで、Seal Securityが既知のCVEをどのように修復するかを実演します。脆弱な依存関係をシールドされた(バックポートされたドロップイン)バージョンに置き換えることで、宣言したバージョン範囲やコードを変更することなく実現します。
これは、CI/CDにおけるSeal CLIのフロントからバックまでのスモークテストとして設計されています。アプリを実行し、実際のエクスプロイトをトリガーし、Sealを実行し、同じエクスプロイトがブロックされるのを確認します。
| エコシステム | JavaScript / npm |
| 脆弱性のあるパッケージ | [email protected](2.7.4に解決) |
| CVE | CVE‑2022‑29078 — EJSサーバーサイドテンプレートインジェクション → リモートコード実行(CVSS 9.8) |
| シールド(修正済み)バージョン | Sealのnpmレジストリのejs 2.7.4-sp1 |
| 統合 | Seal CLIを1つのビルドステップとして — GitHub ActionsとJenkinsの両方で示す |
このアプリには、他にも有名な脆弱性のある依存関係(lodash 4.17.5、json5 0.5.1、got 6.7.1)が含まれており、Sealはそれぞれをシールドビルドに修復します。
アプリはURLクエリ文字列全体をそのままEJSレンダリング呼び出しに展開します:
const data = { name: 'World', ...req.query };
res.render('page', data, ...)
また、EJSはsettings['view options']オブジェクトを受け入れ、そのoutputFunctionNameの値がサニタイズされずにコンパイルされたテンプレート関数の本体に書き込まれます。そのため、攻撃者はNode.jsプロセスの権限でサーバー上で実行される任意のJavaScriptを注入できます。
通常のリクエスト
/?name=alice
Hello alice!がレンダリングされます。
エクスプロイトリクエスト
/?name=Hacker&settings[view%20options][outputFunctionName]=x;setTimeout(function()%7Bprocess.exit(1)%7D,3000);s
サーバーはsetTimeout(function(){ process.exit(1) }, 3000)を実行します。ページは最初にロードされ、RCEが成功したことを明確に表示します。数秒後にリロードするとERR_CONNECTION_REFUSEDが表示されます — 注入されたコードがサーバーを強制終了し、任意のコードが実行されたことを証明します。
3秒の遅延は意図的です:プロセスが終了する前にレスポンスがブラウザに届くようにし、フリーズしたタブではなく「エクスプロイト成功」ページが表示されてからきれいにクラッシュするようにします。
.
├── index.js # the vulnerable Express app
├── views/ # EJS templates
├── package.json / package-lock.json
├── Jenkinsfile # example Jenkins (Groovy) pipeline with the Seal stage
└── .github/workflows/
├── build-and-run.yml # build + expose the app for browser testing
└── seal-security.yml # run Seal remediation, then start the app
SealはSaaS、Sealホステッドです — 環境内には何もインストールされず、すべてのトラフィックはアウトバウンドHTTPS(TCP 443)のみです。修復を実行するには以下が必要です:
| シークレット/認証情報 | 用途 | 保存場所 |
|---|
これらはSettings → Secrets and variables → Actions(GitHub)またはManage Jenkins → Credentials(Jenkins)で設定してください。トークンをリポジトリにコミットしないでください。
アウトバウンド443に対して以下のSealホストを許可リストに追加してください:app.sealsecurity.io、authorization.sealsecurity.io、cli.sealsecurity.io、そしてシールドされたnpmパッケージ用に**npm.sealsecurity.io**。CLIバイナリはgithub.com / objects.githubusercontent.comからダウンロードされます。
npm install
npm start # → http://localhost:3001
http://localhost:3001/?name=aliceを開く(動作)、次に上記のエクスプロイトURL(サーバーがクラッシュ)。
Seal CLIは1つの追加ステップとして、npm installの後、パッケージングの前に実行されます。解決された依存関係をスキャンし、脆弱なものをシールドバージョンに書き換えます。リモート修正モードを使用します(ポリシーはSeal UIで一元管理)。
seal-community/cli-action を使用します:
- uses: seal-community/cli-action@latest
with:
mode: fix
fix_mode: remote
token: ${{ secrets.SEAL_TOKEN }}
target: package-lock.json # the lock file for this ecosystem
Actions → “Seal Security Remediation” → Run workflow から実行してください。.github/workflows/seal-security.yml を参照。
インストール後、パッケージング前に追加する1つのステージ。Jenkinsfile を参照:
stage('Seal') {
steps {
sh '''
curl -fsSL https://github.com/seal-community/cli/releases/download/latest/seal-linux-amd64-latest -o seal
chmod +x seal
./seal fix --mode remote "$SEAL_MANIFEST" # SEAL_MANIFEST=package-lock.json
'''
}
}
SEAL_TOKENはseal-token Jenkins認証情報から取得されます。SEAL_PROJECTをSealプロジェクトIDに設定してください。
seal fixの後、脆弱な依存関係はSealのレジストリからのシールドビルドに解決されます — package.jsonのバージョン範囲は変わりません:
シールドバージョンは、セキュリティ修正がバックポートされた同じパッケージであり、そのまま置き換えられます — コードの変更もメジャーバージョンアップグレードもありません。
修復されたアプリに対してエクスプロイトURLを再実行してください。インジェクションは実行されません:シールドされたejsは悪意のあるoutputFunctionNameを拒否し、アプリはペイロードを実行する代わりに**「Invalid parameter」**で応答します。サーバーは起動したままです。
seal fixを特定のマニフェスト/ロックファイルに向けます — npmの場合はpackage-lock.json。複数のマニフェストがあるリポジトリでは、マニフェストごとに1回seal fixを実行します。これで統合は完了です — 1つのステージ、アウトバウンドのみ、アプリケーションコードの変更はありません。
| Seal token | Seal CLIの認証 | GitHub Actions secret SEAL_TOKEN / Jenkins "Secret text" credential seal-token |
| ngrok token (オプション) | 実行中のアプリをブラウザでテストするために公開 | GitHub Actions secret NGROK_TOKEN |
| 依存関係 | 変更前 | 変更後(シールド) |
|---|
| ejs | 2.7.4 | 2.7.4‑sp1 |
| lodash | 4.17.5 | 4.17.5‑sp1 |
| json5 | 0.5.1 | 0.5.1‑sp1 |
| got | 6.7.1 | 6.7.1‑sp1 |