CVE-2018-1000007 - libcurl における情報漏えいに関する脆弱性

# 概要

[libcurl](https://curl.haxx.se/libcurl/) 7.57.0 以前には、リダイレクト時に情報漏えいに関する脆弱性が存在します。

# 動作確認

libcurl は様々なファイル転送プロトコルに対応したライブラリです。

curl コマンドでも使用されているため、ウェブアプリケーションサーバを2つ（アプリケーション1、アプリケーション2）起動し、curl コマンドでリクエストした際の結果を curl を実行するクライアントとサーバの両方で確認します。

ウェブアプリケーションサーバは Ruby の [sinatra](http://sinatrarb.com/) を bundler 経由で起動します。

## 環境作成

### ウェブアプリケーションの作成

最初にウェブアプリケーションを作成します。

```
% mkdir ~/cve
% cd ~/cve
% bundle init
% vi Gemfile  # 次の内容に編集
% cat Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem 'sinatra'
gem 'sinatra-contrib'
```

次に gem をインストールします。

```
% bundle i --path vendor/bundle
```

最初にアクセスするアプリケーション1の内容です。

```first_app.rb                                                                     
Bundler.require
get '/' do
  request.env.each do |k, v|
    puts "#{k}: #{v}" if k =~ /^HTTP_/
  end

  redirect 'http://127.0.0.1:8002'
end
```

`/` にアクセスされると、リクエストされたヘッダを出力し、アプリケーション2の URL へリダイレクトするようにレスポンスを返します。

次はリダイレクト先のアプリケーション2の内容です。

```second_app.rb
Bundler.require

get '/' do
  request.env.each do |k, v|
    puts "#{k}: #{v}" if k =~ /^HTTP_/
  end
  "OK"
end
```

`/` にアクセスされるとリクエストされたヘッダを出力したあとで、本文 `OK` を出力します。

## curl のインストール

[curl のリリースページ](https://github.com/curl/curl/releases)からソースを取得します。

ソースを展開し、任意のパスにインストールします。

```
% tar xvzf curl-7.61.1.tar.gz
% cd curl-7.61.1
% ./configure CFLAGS="-m64" --prefix=/path/to/curl-7.61.1
% make
% make install
```

対策済みと未対策のバージョンそれぞれ同様の作業を行うのですが、どちらかすでにインストール済みの場合はそちらのインストールは不要です。

## アプリケーションの起動

まず、最初にアクセスするアプリケーション1を起動します。

```
% bundle exec ruby first_app.rb -o 0.0.0.0 -p 8001
```

次に、リダイレクト先のアプリケーション2を起動します。ポートが重複しないようにしています。

```
% bundle exec ruby second_app.rb -o 0.0.0.0 -p 8002
```

## 動作確認

### 未対策のバージョン

まずは未対策のバージョン（7.54.0）で確認します。

```
% curl --version        
curl 7.54.0 (x86_64-apple-darwin17.0) libcurl/7.54.0 LibreSSL/2.0.20 zlib/1.2.11 nghttp2/1.24.0
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-proxy
% curl --verbose -H "Authorization: s3cr3t" -L http://localhost:8001/
```

`--verbose` はリクエストとレスポンスのヘッダを出力するオプション、`-H` はリクエスト時のヘッダを指定するオプション、`-L` はリダイレクトレスポンスだった場合にリダイレクト先にリダイレクトするオプションです。

出力は次のようになります。

```
% curl --verbose -H "Authorization: s3cr3t" -L http://localhost:8001/
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8001 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8001 (#0)
> GET / HTTP/1.1
> Host: localhost:8001
> User-Agent: curl/7.54.0
> Accept: */*
> Authorization: s3cr3t
>
< HTTP/1.1 302 Found
< Content-Type: text/html;charset=utf-8
< Location: http://127.0.0.1:8002
< Content-Length: 0
< X-Xss-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Server: WEBrick/1.3.1 (Ruby/2.4.4/2018-03-28)
< Date: Mon, 08 Oct 2018 09:56:56 GMT
< Connection: Keep-Alive
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://127.0.0.1:8002'
* Rebuilt URL to: http://127.0.0.1:8002/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8002 (#1)
> GET / HTTP/1.1
> Host: 127.0.0.1:8002
> User-Agent: curl/7.54.0
> Accept: */*
> Authorization: s3cr3t
>
< HTTP/1.1 200 OK
< Content-Type: text/html;charset=utf-8
< Content-Length: 2
< X-Xss-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Server: WEBrick/1.3.1 (Ruby/2.4.4/2018-03-28)
< Date: Mon, 08 Oct 2018 09:56:56 GMT
< Connection: Keep-Alive
<
* Connection #1 to host 127.0.0.1 left intact
OK% 
```

リダイレクト先へアクセスした際のリクエストヘッダに最初のアクセスに付加した `Authorization: s3cr3t` が含まれている事が確認できます。

アプリケーション1の出力ログは次の通りです。

```
WARNING: If you plan to load any of ActiveSupport's core extensions to Hash, be
sure to do so *before* loading Sinatra::Application or Sinatra::Base. If not,
you may disregard this warning.
[2018-10-08 18:56:52] INFO  WEBrick 1.3.1
[2018-10-08 18:56:52] INFO  ruby 2.4.4 (2018-03-28) [x86_64-darwin17]
== Sinatra (v2.0.4) has taken the stage on 8001 for development with backup from WEBrick
[2018-10-08 18:56:52] INFO  WEBrick::HTTPServer#start: pid=80076 port=8001
HTTP_HOST: localhost:8001
HTTP_USER_AGENT: curl/7.54.0
HTTP_ACCEPT: */*
HTTP_AUTHORIZATION: s3cr3t
HTTP_VERSION: HTTP/1.1
127.0.0.1 - - [08/Oct/2018:18:56:56 +0900] "GET / HTTP/1.1" 302 - 0.0027
127.0.0.1 - - [08/Oct/2018:18:56:56 JST] "GET / HTTP/1.1" 302 0
- -> /
```

ここで `HTTP_AUTHORIZATION` ヘッダが含まれるのは問題ありません。

次はリダイレクト先のアプリケーション2の出力ログです。

```
WARNING: If you plan to load any of ActiveSupport's core extensions to Hash, be
sure to do so *before* loading Sinatra::Application or Sinatra::Base. If not,
you may disregard this warning.
[2018-10-08 18:56:48] INFO  WEBrick 1.3.1
[2018-10-08 18:56:48] INFO  ruby 2.4.4 (2018-03-28) [x86_64-darwin17]
== Sinatra (v2.0.4) has taken the stage on 8002 for development with backup from WEBrick
[2018-10-08 18:56:48] INFO  WEBrick::HTTPServer#start: pid=80027 port=8002
HTTP_HOST: 127.0.0.1:8002
HTTP_USER_AGENT: curl/7.54.0
HTTP_ACCEPT: */*
HTTP_AUTHORIZATION: s3cr3t
HTTP_VERSION: HTTP/1.1
127.0.0.1 - - [08/Oct/2018:18:56:56 +0900] "GET / HTTP/1.1" 200 2 0.0038
127.0.0.1 - - [08/Oct/2018:18:56:56 JST] "GET / HTTP/1.1" 200 2
- -> /
```

`HTTP_AUTHORIZATION: s3cr3t` が取得できています。

### 対策済みバージョン

次に対策済みバージョン（7.61.1）で同様に確認します。

```
% curl-7.61.1/bin/curl --version
curl 7.61.1 (x86_64-apple-darwin17.5.0) libcurl/7.61.1 zlib/1.2.11 libidn2/2.0.4
Release-Date: 2018-09-05
Protocols: dict file ftp gopher http imap ldap ldaps pop3 rtsp smtp telnet tftp
Features: AsynchDNS IDN IPv6 Largefile libz UnixSockets
```

コマンドの出力は次のようになります。

```
% curl-7.61.1/bin/curl --verbose -H "Authorization: s3cr3t" -L http://localhost:8001/                 [~/sandbox/curl]
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 8001 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8001 (#0)
> GET / HTTP/1.1
> Host: localhost:8001
> User-Agent: curl/7.61.1
> Accept: */*
> Authorization: s3cr3t
>
< HTTP/1.1 302 Found
< Content-Type: text/html;charset=utf-8
< Location: http://127.0.0.1:8002
< Content-Length: 0
< X-Xss-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Server: WEBrick/1.3.1 (Ruby/2.4.4/2018-03-28)
< Date: Mon, 08 Oct 2018 10:04:47 GMT
< Connection: Keep-Alive
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://127.0.0.1:8002'
* Rebuilt URL to: http://127.0.0.1:8002/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8002 (#1)
> GET / HTTP/1.1
> Host: 127.0.0.1:8002
> User-Agent: curl/7.61.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html;charset=utf-8
< Content-Length: 2
< X-Xss-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Server: WEBrick/1.3.1 (Ruby/2.4.4/2018-03-28)
< Date: Mon, 08 Oct 2018 10:04:47 GMT
< Connection: Keep-Alive
<
* Connection #1 to host 127.0.0.1 left intact
OK%
```

リダイレクト先へアクセスする際に`Authorization` ヘッダが含まれていません。

アプリケーション1のログは次の通りです。起動時のログは省略しています。

```
HTTP_HOST: localhost:8001
HTTP_USER_AGENT: curl/7.61.1
HTTP_ACCEPT: */*
HTTP_AUTHORIZATION: s3cr3t
HTTP_VERSION: HTTP/1.1
127.0.0.1 - - [08/Oct/2018:19:04:47 +0900] "GET / HTTP/1.1" 302 - 0.0011
127.0.0.1 - - [08/Oct/2018:19:04:47 JST] "GET / HTTP/1.1" 302 0
```

アプリケーション2のログは次の通りです。

```
HTTP_HOST: 127.0.0.1:8002
HTTP_USER_AGENT: curl/7.61.1
HTTP_ACCEPT: */*
HTTP_VERSION: HTTP/1.1
127.0.0.1 - - [08/Oct/2018:19:18:02 +0900] "GET / HTTP/1.1" 200 2 0.0004
127.0.0.1 - - [08/Oct/2018:19:18:02 JST] "GET / HTTP/1.1" 200 2
- -> /
```

無事、`HTTP_AUTHORIZATION` ヘッダが出力できていません。

# 何が問題なのか

例えば Basic 認証ではユーザ名とパスワードを Base64 エンコードした値をもとに `Authorization` ヘッダとして次のように指定します。

```
Authorization: Basic aWQ6cGFzcw==
```

`aWQ6cGFzcw==` がエンコードした値ですが、Base64 は可逆なので次のように簡単に確認する事が出来ます。

```
% ruby -e 'require "base64"; puts Base64.decode64("aWQ6cGFzcw==")'
id:pass
```

つまり秘密にすべき情報がリダイレクト先の本来必要のないサイトに漏れてしまう事になります。

# 対策方法

- 7.58.0 以上にアップデートする

# 参考

- [CVE \- CVE\-2018\-1000007](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000007)
- [curl \- HTTP authentication leak in redirects \- CVE\-2018\-1000007](https://curl.haxx.se/docs/CVE-2018-1000007.html)
- [JVNDB\-2018\-001586 \- JVN iPedia \- 脆弱性対策情報データベース](https://jvndb.jvn.jp/ja/contents/2018/JVNDB-2018-001586.html)