curl
セキュリティ
cve

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

概要

libcurl 7.57.0 以前には、リダイレクト時に情報漏えいに関する脆弱性が存在します。

動作確認

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

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

ウェブアプリケーションサーバは Ruby の sinatra を 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 のリリースページからソースを取得します。

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

% 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 以上にアップデートする

参考