In this article
やった!機能を作ったら、Denoアプリでオープンリダイレクトの脆弱性を作り込んでしまった
ここでは、JavaScriptとTypeScriptで人気が高まっているランタイム、Denoを使ったシンプルな「Hello World」ウェブアプリのセットアップ方法を、手順を追って説明します。具体的には、次のとおりです。
リダイレクト用クエリパラメーターに対応したDenoウェブアプリを作成し、開発者がロードマップに沿って機能を開発する中で、意図せずセキュリティ上の穴を生み出してしまう可能性について学びます。
Deno「Hello World」コードチュートリアルを始めよう
事前準備として、Denoプロジェクトを実行し、ウェブアプリを操作するために必要なツールがそろったDenoウェブ開発環境にアクセスできることを確認してください。
次の手順に沿って、Denoの「Hello World」アプリを作成します。
1. 開発環境にDenoをインストールします。まだインストールしていない場合は、[Deno公式ウェブサイト](https://deno.land/#installation)でインストール手順を確認できます。
2. 新しいDenoプロジェクトを作成します。新しいディレクトリを作成し、deps.tsファイルを用意して初期化します。このファイルには依存関係を記述しますが、今回はシンプルなアプリケーションなので依存関係はありません。
```bash
mkdir deno-hello-world
cd deno-hello-world
echo "" > deps.ts
```
3. 新しいserver.tsファイルを作成します。このファイルにはサーバーのロジックを記述します。ここでserve関数をstd/httpモジュールからインポートし、サーバーをセットアップして受信リクエストを待ち受けます。
4. サーバーのロジックを追加します。server.tsファイルに次のコードを追加してください。
```typescript
import { serve } from "https://deno.land/std/http/server.ts";
const httpHandler = async (request: Request): Promise<Response> => {
let bodyContent = 'Hello World';
return new Response(bodyContent, {
status: 200,
headers: {
"content-type": "text/html"
}
})
};
serve(httpHandler, { hostname: "0.0.0.0", port: 8080 });
console.log("HTTP server is running on http://localhost:8080/");
```5. サーバーを実行します。server.tsファイルを保存し、ターミナルで次のコマンドを実行してサーバーを起動します。
```bash
deno run --allow-net server.ts
```
これで完了です。シンプルなDenoウェブアプリができ、「Hello World!」と表示されます。
Denoアプリケーションでリクエストをリダイレクトする
上記の動作するコードを更新して、メインの/インデックスルートから、ユーザーの詳細を表示する/userルートへクエリパラメーターを渡せるようにしましょう。
まず、2つ目の/userルートを追加します。大きな依存関係やウェブフレームワークを導入せずに済むよう、リクエストされたパスをシンプルな条件分岐でチェックします。
httpHandlerのコードを次のように更新します。
```typescript
const httpHandler = async (request: Request): Promise<Response> => {
let bodyContent = 'Hello World';
const requestURL = new URL(request.url);
if (requestURL.pathname === "/user") {
bodyContent = `
<h1> User: admin </h1>
<ul>
<li> Name: Roberto </li>
<li> Email: roberto@example.com </li>
</ul>
`
return new Response(bodyContent, {
status: 200,
headers: {
"content-type": "text/html"
}
})
} else {
return new Response(bodyContent, {
status: 200,
headers: {
"content-type": "text/html"
}
})
}
};
```
次に、リダイレクト機能を実装します。/?redirect=/userへのリクエストを受け取ったときに、ウェブアプリケーションがクエリパラメーターredirectを解析し、ユーザー詳細ページにリクエストをリダイレクトするようにします。
httpHandler関数の冒頭で、requestURL変数を拡張し、リダイレクト用クエリパラメーターの条件チェックも追加しましょう。その値に応じて、Locationリダイレクトヘッダーを含むHTTPレスポンスを返します。
```js
const requestURL = new URL(request.url);
const redirectTo = requestURL.searchParams.get('redirect');
if (redirectTo) {
return new Response(null, {
status: 302,
headers: {
'location': redirectTo
}
})
}
```リダイレクト機能が想定どおりに動作するかテストするには、次のページをリクエストして、メインのインデックスページからユーザー詳細ページへリダイレクトします。/?redirect=/user。curlを使った実行例を見てみましょう。
```bash
@lirantal ➜ /workspaces/deno-open-redirect-vulnerability-blog-post (main) $ curl "http://localhost:8080/?redirect=/user" -vv
v
* Trying ::1:8080...
* connect to ::1 port 8080 failed: Connection refused
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /?redirect=/user HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< location: /user
< vary: Accept-Encoding
< content-length: 0
< date: Mon, 06 Nov 2023 14:19:36 GMT
<
* Connection #0 to host localhost left intact
```本番環境にリリースする準備はできていますか?
もう少し詳しく見てみましょう…
Denoアプリケーションにおけるオープンリダイレクトのセキュリティ脆弱性
オープンリダイレクトの脆弱性は、未検証のリダイレクトやフォワードとも呼ばれ、ウェブアプリケーションでよく見られる重大なセキュリティ上の欠陥です。この脆弱性は、フィッシング攻撃、認証情報の窃取、悪意あるコンテンツの配信など、深刻なサイバーセキュリティ上の問題につながる可能性があります。
JavaScriptやJava、Pythonなどの言語におけるセキュアコーディングのベストプラクティスについて、詳しい情報を知りたい方は、Snyk Learnのオープンリダイレクトレッスンをご覧ください。
オープンリダイレクトの脆弱性とは?
オープンリダイレクトの脆弱性は、アプリケーションがパラメーターを受け取り、検証せずにその値へユーザーをリダイレクトすると発生します。この脆弱性を悪用すると、攻撃者はユーザーを元のウェブサイトから、任意の悪意あるサイトへ誘導できます。Node.jsアプリケーションでのオープンリダイレクトの簡単な例を見てみましょう。
```javascript
app.get('/dashboard, function(req, res){
var redirect = req.query.redirect;
res.redirect(redirect);
});
```
Denoプロジェクトの場合も同様です。
```javascript
const httpHandler = async (request: Request): Promise<Response> => {
const requestURL = new URL(request.url);
const redirectTo = requestURL.searchParams.get('redirect');
if (redirectTo) {
return new Response(null, {
status: 302,
headers: {
'location': redirectTo
}
})
}
});
serve(httpHandler);
```上記のコードでは、アプリケーションがクエリパラメーターredirectで指定されたURLへユーザーをリダイレクトします。攻撃者は、悪意あるURLを転送先として指定し、ユーザーを意図しない場所へ誘導できます。
今回作成したDenoアプリケーションの完全に動作するコード例はこちらで確認できます:https://github.com/snyk-snippets/deno-open-redirect-vulnerability-blog-post
オープンリダイレクトの脆弱性を悪用する
シンプルなDenoウェブアプリケーションでは、悪意あるユーザーが次のようなリンクを作成して、リダイレクト機能を悪用する可能性があります。
```
https://example.com/?dummyParameter1=value1&dummyParameter2=value2&redirect=https://attacker.com/
```dummyParameter1のようなダミーのクエリパラメーターを使用しても、Denoアプリケーションのコード自体には影響しません。しかし、URLが長くなるため、リダイレクト用パラメーターとその値が危険な兆候であることに、ユーザーが気づきにくくなります。
さらに、悪意あるユーザーはURL短縮サービスを使って意図を隠すこともできます。疑いを持たないユーザーは、リダイレクト先を確認するためにURLを展開しない可能性が高いでしょう。
SnykでDenoウェブアプリのオープンリダイレクトの脆弱性を検出する方法
Snykは、コードベースやオープンソースの依存関係に含まれる脆弱性の特定と修正を支援する、開発者ファーストの強力なセキュリティツールです。オープンリダイレクトの脆弱性など、セキュリティ上の弱点の検出に特に役立ちます。
現在、SnykはTypeScriptとNode.jsのプロジェクトに対応していますが、Denoには対応していません。ただし、JavaScriptでアプリを開発している場合は、脆弱なオープンソース依存関係がないかpackage.jsonファイルをスキャンするとともに、このオープンリダイレクトの脆弱性のようなコードのセキュリティ問題を引き起こす可能性がある、自分のJavaScriptコードもスキャンすることをおすすめします。
SnykでJavaScriptアプリケーションのオープンリダイレクトの脆弱性を検出するには、開発・デプロイのパイプラインにSnykを統合します。Snyk CLIをインストールし、JavaScript、TypeScript、またはNode.jsアプリケーションに対してSnykのテストを実行します。手順の例は次のとおりです。
1. Snyk CLIをグローバルにインストールします。
```bash
npm install -g snyk
```2. アプリケーションのディレクトリでSnykのテストを実行します。
```bash
snyk code test
```Snykはアプリケーションのコードをスキャンし、オープンリダイレクトの脆弱性を含む、検出された脆弱性を報告します。また、脆弱性の修正方法に関する詳細な情報も提供するため、アプリケーションのセキュリティを維持するうえで欠かせないツールです。

実はSnykは、コードを書いている最中にIDE内で安全でないコードの脆弱性を見つけることもできます。次の例は、Expressライブラリを使ったNode.jsウェブアプリケーションのコードです。コントローラーミドルウェアを使い、管理者ユーザーのログイン後にユーザーをリダイレクトしています。この例では、Snykが多数のセキュリティ脆弱性を検出し、その中にはオープンリダイレクトの問題も含まれています。
右側のペインでは、Snykがオープンリダイレクトの脆弱性について示す情報を確認できます。
サニタイズされていない入力が、異なるファイルやコードパス間をどのように流れるのかを説明し、脆弱性のあるコードの具体的な行を示します。
このオープンリダイレクトの脆弱性について学べるリンクを提示します(Snykのレッスンはこちら!)。
他のオープンソースプロジェクトでこのオープンリダイレクトの脆弱性を修正したコード例を複数示し、コードの差分も提供します。

特にサイバーセキュリティでは、予防は治療に勝ります。オープンリダイレクトの脆弱性について理解し、Snykのようなツールを活用することで、ウェブアプリケーションの安全性と信頼性を維持できます。
Deno Dev Containerを設定する
このセクションでは、GitHub Codespaces内のDeno開発コンテナーの設定について説明します。postCreateCommandファイル内のdevcontainer.jsonやforwardedPortsなど、特定の設定を変更します。セクション全体を通じて、開発環境で実用的なDenoウェブアプリケーションをセットアップするための例とヒントを紹介します。
Dev ContainerのpostCreateCommandを設定する
devcontainer.jsonファイルのpostCreateCommandは、開発コンテナーのビルドと起動後に実行されるコマンド、または一連のコマンドです。この設定を使えば、コンテナーを起動するたびに手動で行っている作業を自動化できるため、重要です。
たとえば、postCreateCommandを使ってプロジェクトの依存関係をインストールしたり、プロジェクトをビルドしたり、アプリケーションサーバーを起動したりできます。
Denoプロジェクトでは、postCreateCommandを使って開発コンテナー内でウェブアプリケーションを自動起動できます。postCreateCommandの設定例を紹介します。
```json
{
"postCreateCommand": "deno run --allow-net server.ts"
}
```Denoのポートフォワーディング設定
今回はコマンドラインで実行するDenoプログラムではなく、実際のウェブアプリケーションです。そのため、Dev Containerがリクエストを受け付けるポートを公開し、ホスト環境からアクセスできるようにする必要があります。ここで使うのがDev Containerのポートフォワーディング設定です。
devcontainer.jsonファイルのforwardedPorts設定では、ホストマシンに公開するコンテナー内のポートを指定します。
たとえば、この記事で紹介したDenoウェブアプリケーションがポート8080で待ち受ける場合、forwardedPorts設定を使って、このポートをホストマシンに転送する必要があります。
Denoウェブアプリのポートフォワーディングを設定するには、devcontainer.jsonファイルにforwardedPorts設定を追加します。次の例をご覧ください。
```json
{
"forwardedPorts": [8080]
}
```これで、Denoウェブアプリケーションはコンテナー内のポート8080で実行され、ホストマシンからhttp://localhost:8080にアクセスできます。GitHub Codespacesなどのリモートクラウド環境で実行している場合は、次のようなCodespaceのURLからアクセスしてください:https://orange-invention-pe8918638bd1nf81-8080.app.github.dev/
次のステップ
DenoとDev Container、そして何より、機能を開発する際にセキュリティ脆弱性を作り込まない方法について学びました。ふう、盛りだくさんでしたね。
この記事で紹介した完全に動作するコードは、GitHubのSnyk Snippetsコードリポジトリで確認できます:https://github.com/snyk-snippets/deno-open-redirect-vulnerability-blog-post