Kotlin/NativeでHTTPサーバーを作るアドベントカレンダー(17日目:ディレクトリトラバーサル対策)

この記事はkdnaktの1人 Advent Calendar 2020の17日目の記事です(1日遅れ)。

 

2020年は会社でKotlin dojoを主催して週1回30分Kotlinと戯れていました。12月はその集大成ということで、Kotlin/NativeでHTTPサーバーを作ってみたいと思います。どこまでできるか、お楽しみ……。

 

 

[telnetディレクトリトラバーサル]

ここまでの実装内容では、telnetコマンドでアクセスした場合に、ディレクトリトラバーサルを実行できてしまいます。

 

ディレクトリトラバーサルは、親ディレクトリを示す../を含むパスをリクエストすることで、本来であれば公開されていないファイルやディレクトリにアクセスする攻撃手法です。

www.ipa.go.jp

ja.wikipedia.org

 

実際に、自作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

 

[まとめ]

github.com