この記事はkdnaktの1人 Advent Calendar 2020の17日目の記事です(1日遅れ)。
2020年は会社でKotlin dojoを主催して週1回30分Kotlinと戯れていました。12月はその集大成ということで、Kotlin/NativeでHTTPサーバーを作ってみたいと思います。どこまでできるか、お楽しみ……。
[telnetでディレクトリトラバーサル]
ここまでの実装内容では、telnetコマンドでアクセスした場合に、ディレクトリトラバーサルを実行できてしまいます。
ディレクトリトラバーサルは、親ディレクトリを示す../を含むパスをリクエストすることで、本来であれば公開されていないファイルやディレクトリにアクセスする攻撃手法です。
実際に、自作HTTPサーバーにtelnetでアクセスしてみます。
前提として、以下のようなディレクトリ構成であるとします。publicディレクトリが本来公開されているディレクトリです。publicTestディレクトリはテストコードのためのディレクトリであり、HTTPサーバーとして公開を意図したディレクトリではありません。
. ├── public │ └── index.html └── publicTest └── Sample.txt
publicTest/Sample.txtの中身は以下のようになっています。
First line Second line Third line
telnetコマンドでHTTPサーバーにアクセスし、publicTestディレクトリのファイルをリクエストしてみます。
すると、以下のようにSample.txtの中身がHTTPレスポンスとして返ってきます。
$ telnet localhost 8080 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GET /../publicTest/Sample.txt HTTP/1.1 HTTP/1.1 200 OK Content-Length: 33 First line Second line Third line
ディレクトリトラバーサル攻撃を防ぐ方法はいくつかありますが、ここではリクエストされたパスに..が含まれる場合に、エラーを返すようにします。
まず、FileReaderTest.ktに以下のテストを追加します。テスト用のディレクトリから、本番用のディレクトリを読み込むという内容です。
@Test
fun testDirectoryTraversal() {
val reader = FileReader("publicTest/../public/index.html")
assertFailsWith<NotFoundException> { reader.content() }
}
テストを実行すると、まだ実装していないので以下のようなエラーメッセージが出て失敗します。
kotlin.AssertionError: Expected an exception of com.kdnakt.kttpd.NotFoundException to be thrown, but was completed successfully.
FileReader.ktを以下のように修正します。リクエストされたパスに..が含まれる場合にはNotFoundExceptionを投げています。
fun content(): String {
(略)
if (path.contains("..")) {
throw NotFoundException()
}
(略)
}
改めてテストを実行すると、成功しました。
念のためtelnetでアクセスして確認すると、以下のようにSample.txtではなく404エラーが表示されました。
$ telnet localhost 8080 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GET /../publicTest/Sample.txt HTTP/1.1 HTTP/1.1 404 Not Found Content-Length: 13 404 Not Found
[まとめ]
- 単純なディレクトリトラバーサル対策を実装した
- 実装中のコードは以下のリポジトリにまとめてある