読者です 読者をやめる 読者になる 読者になる

MySQL脆弱性 CVE-2016-6662 について (2016 09 13現在)

これは何

MySQLにリモートでコード実行される可能性のある脆弱性 CVE-2016-6662 が発覚。その概要と対応をまとめる。

目次

  • これは何
  • 目次
  • 資料
  • 対象バージョン
  • ざっくり言うと?
  • 設定ファイルが書き換えられるとなぜ攻撃になる?
  • 今回の脆弱性
    • my.cnfに追記する
    • ファイルが無い状態から作る
  • 対応方針案

資料

CVE-2016-6662  http://legalhackers.com/advisories/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.html

対象バージョン

2016 09 13日現在の全てのバージョン。 具体的に言うと、現時点での最新のバージョンの以下を含む、それ以前のバージョンに脆弱性が存在。

  • 5.7.15
  • 5.6.33
  • 5.5.52

要するにパッチがまだない。 MySQL cloneの MariaDBとPerconaDBにも影響。

ざっくり言うと?

SQLインジェクションを使って、 my.cnf を上書きできる。 攻撃するには以下の条件が必要

設定ファイルが書き換えられるとなぜ攻撃になる?

mysqld_safeがrootプロセスで起動して、mysqldプロセスがmysqlユーザに落として起動される。mysqld_safeのスクリプト(配置場所によるが、例えば /usr/local/mysql/bin/mysqld_safe)のset_malloc_lib()でshared library がmysqlサーバ起動前に読み込まれて--malloc-lib=LIB へセットされる。 設定ファイルを書き換えられるということは、この変数で読み込む値を my.cnfの [mysqld]か[mysqld_safe]セクションで好きに設定できるということ。そうすると、任意のライブラリ(任意コード)がmysqlサーバ起動時にroot権限でされる(mysqld_safe実行時なので)。 3.23.55以前のバージョンだと

SELECT 'malicious config entry' INTO OUTFILE '/var/lib/mysql/my.cnf'

このコマンドでファイルが作れて、設定ファイルとして読み込むことができる。ファイル作成の例としては /tmp/ 下に作成してみると

$ mysql -uroot -p -e "SELECT 'malicious config entry' INTO OUTFILE '/tmp/my_temp'"
$ ls -ltr /tmp/my_temp
-rw-rw-rw- 1 mysql mysql 23 Sep 13 16:50 /tmp/my_temp
$ cat /tmp/my_temp
malicious config entry

今はこの脆弱性は修正されている。MySQLは 0666 permissionsのファイル(というかotherに書き込み権限がある場合)を設定ファイルとして読み込まないようになっている。具体的には以下のエラーが出て読み込まない。

Warning: World-writable config file '/etc/my.cnf' is ignored

しかし、MySQL logging関数で出力されたファイルは 0660 になっているので、設定ファイルとして読み込むことができる。これが今回の問題 

今回の脆弱性

my.cnfに追記する

仮に my.cnfが以下の様なmysqlユーザの持ち物だった場合(例えば以下のようなパーミッションだった場合)

root@debian:~/# ls -l /etc/my.cnf
-rw-r--r-- 1 mysql mysql 72 Jul 28 17:20 /etc/my.cnf

以下の内容をファイルに追記できる。

mysql> set global general_log_file = '/etc/my.cnf';
mysql> set global general_log = on;
mysql> select '
    '>
    '> ; injected config entry
    '>
    '> [mysqld]
    '> malloc_lib=/tmp/mysql_exploit_lib.so
    '>
    '> [separator]
    '>
    '> ';
mysql> set global general_log = off;

ファイルが無い状態から作る

/etc/ 以下にmy.cnfが無い場合には、/etc/ ディレクトリのパーミッションで縛られる。root かつ 0644 だったばあいには mysqlユーザは作ることができない。 どのようなファイルが作られるか、/tmp/以下で試してみる。この方法で作られたファイルは以下のようなPermissionになる 。

$ ls -ltr /tmp/my_temp.txt
-rw-rw---- 1 mysql mysql 685074 Sep 13 16:33 /tmp/my_temp.txt

しかし、この脆弱性だけでは、my.cnfに loggingの最初の行(version情報など)が入ってしまい、設定ファイルとして機能しない。 さらに他の脆弱性 CVE-2016-6663 (2016 09 13現時点では詳細非公開) を使用すると、(余計な情報を消して?)FILE権限なしに、任意の内容を書き込むことができる。

一次対応策

MySQL 5.5だと以下のような設定ファイルが置ける  参考: http://dev.mysql.com/doc/refman/5.5/en/option-files.html

/etc/my.cnf         Global options
/etc/mysql/my.cnf   Global options
SYSCONFDIR/my.cnf   Global options
$MYSQL_HOME/my.cnf  Server-specific options
defaults-extra-file The file specified with --defaults-extra-file=file_name, if any
~/.my.cnf   User-specific options
  • /etc/my.cnf がmysqlユーザで書き込み権限がないこと(例えば rootで 0644 )を確認する
  • /usr/local/mysql 直下が mysqlユーザの書き込み権限がないこと (例えば以下のようになっていること。mysqlユーザはここにmy.cnfを配置できない)を確認する
$ ls -ltrd /usr/local/mysql/
drwxr-xr-x 13 root mysql 4096 Sep 13 17:27 /usr/local/mysql/
  • ファイルを置く権限がある場合には、 root パーミッションで作った my.cnf をダミーとして置いておく。作成も上書きも出来ないように。
  • /etc/mysql/ ディレクトリが存在しないことを確認する
  • /etc/sysconfig/ が mysql ユーザの権限がないことを確認する
$ ls -ltrd /etc/sysconfig/
drwxr-xr-x. 7 root root 4096 Sep 13 17:17 /etc/sysconfig/
  • extra fileとして /home/simainte/my.cnf がある場合はそれが root 0600 になっていることを確認する
  • /home/mysql/ に root 0600 で my.cnfを配置しておく。または /home/mysql/ を削除する

根本対応

パッチはまだない。mysqld_safe で shared_libraryを読み込まないという手もある。

Vmware ESXi でVMホストのディスクが圧迫されてインスタンスが落ちる問題(原因と対策)

背景

バックアップ用DBを作るために、でかいディスクを持つ物理サーバの上にVmware ESXiをたてた。 DBに他のSlaveからデータをscpしてみたら途中で突然インスタンスが落ちた。起動もスナップショットの操作も出来ない状態。 インスタンスへのディスク割り当ては200G。メモリは8G割り当て。 scpしたファイルは40G程度で、開きVMホストの元の空き容量は30G程度だった。

原因

datastoreを更新してみると空きがほぼ0になっていた。 詳細なファイルを見てみると以下のようなファイルが大きかった。(ホスト名を hostnameとする)

  • hostname.vmdk
  • hostname-49271ad6.vswp
    • 8G。メモリオーバーコミットをする設定だと、物理マシンのメモリと直接マッピングされずにインスタンスのメモリはディスクに書かれる見たい。
  • hostname-000001.vmdk, hostname-000002.vmdk
    • 合計で27G
    • 今回の犯人。スナップショットをとった後にインスタンス内で更新があると、その差分を出すためにredoログ(差分)としてここに書かれる。
  • vmware-1.log
    • 合計で数M。今回の問題とは無関係なので略

対策

  • メモリオーバーコミットをさせない。インスタンスのメモリ設定で「すべてのゲストメモリを予約(すべてロック)」設定を入れる。
  • スナップショットを全部消す。そうするとredoログが吐かれないのでVMホストのディスク圧迫が起こらない。

コメント

スナップショットが取れないのは不便だが、物理マシンと同じだと思えばまあよし。 ある程度構築して本番投入するまではスナップショットを使って、ほとんど変更が無くなったらスナップショットを全部消すなどの運用でカバーできそう。

Dockerを立ち上げたいがホストOSのルーティングと衝突して上がらない件

背景

Scientific Linux 7.2 で、Dockerを立ち上げたときにエラーが出て上がらなかった。問題と解決。

エラーメッセージ

# docker daemon --debug 最後の行に以下のもの。

FATA[0001] Error starting daemon: Error initializing network controller: Error creating default "bridge" network: failed to allocate gateway (192.168.17.0): Address already in use

解決方法

(すでに解決して消えてしまったのでうろ覚えだが)もうちょっと前に 192.168.0.0/16のネットワークが割り当てられないみたいなエラーが出ていた。 ホストOSですでにそのネットワークに対してルーティングが入っていてDockerに割り当てることができなかった。Dockerはデフォルトでこのネットワークを使おうとする。指定したければ --bip=CIDR で指定すればよい。

172.16.0.0/12 もホストでルーティング入ってしまっていたが、使わないのでいったん落とす。

# ip route del 172.16.0.0/12 
# docker daemon --debug -s overlay --bip=172.16.1.1/24

Bridge IPを指定してやる。ここで、どうもIPはdocker0というインターフェースに使うようで、それ経由でDocker界に疎通するっぽい。なので、ネットワークアドレスを指定するとこれまた割り当てられないよ!というエラーがでる。 参考までに、docker0インターフェース。

$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.16.1.1  netmask 255.255.255.0  broadcast 0.0.0.0

RHEL(Scientific Linux)7系でrouting追加ができない件(問題と解決)

背景

殆ど初めてRHEL7系を使った。7系でRouting設定に微妙にはまったのでメモ。

問題

以下のコマンドで追加しようとしたらエラーがでた。

# ip route add 172.16.0.0/12
RTNETLINK answers: No such device

解決

インターフェース名を指定してあげなくちゃダメだった。

# ip route add 172.16.0.0/12 dev eth0

6系のrouteコマンド使った時には適当に疎通できるインターフェースを判別してやってくれた気がするけど。正直大体static-routesファイルや route-eth* に書いてしまってnetwork restartしてたりしたのでどうだったかははっきりしない。

initscriptの記述 LSB記法とchkconfig

背景

kibana をインストールするときに、initscriptを自分で作成した。その時に疑問に思った initscriptの記法についてメモしておく。 https://github.com/akabdog/scripts/blob/master/kibana4_init ここらへんを参考に自分でPATHなど変更して使った。上の方にBEGIN INIT INFOで始まるブロックがあるが、それの意味を調べた。

参考

INIT INFO

LSB(Linux Standard Base)の記法。ディストリビューションによらない共有の記述みたい。chkconfigはRedhat系だけっぽい。

### BEGIN INIT INFO
# Provides:          kibana
# Required-Start:    $local_fs $remote_fs $network
# Should-Start:      $time
# Required-Stop:     $local_fs $remote_fs $network
# Default-Start:     3 5
# Default-Stop:      0 1 2 6
# Short-Description: Kibana 4
# Description:       Service controller for Kibana 4
### END INIT INFO
  • Required-Start: ここで指定されたserviceを起動した後でこのinitscriptのプロセスを起動する
  • Default-Start: 起動するrunlevel を指定する。ネットワーク経由でアクセスする前提のプロセスについては大体3(マルチユーザモード)と5(マルチユーザモード+Xディスプレイマネージャ)。2はマルチユーザモードだけどネットワークを起動しない。

chkconfig と書いてあるinitscriptもあるけど?

elasticsearch はLSBの記法に加えて、内容が重複するものが書いてあった。

# chkconfig:   2345 80 20
# description: Starts and stops a single elasticsearch instance on this system
#

### BEGIN INIT INFO
# Provides: Elasticsearch
# Required-Start: $network $named
# Required-Stop: $network $named
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: This service manages the elasticsearch daemon
# Description: Elasticsearch is a very scalable, schema-free and high-performance search solution supporting multi-tenancy and near realtime search.
### END INIT INFO

chkconfigの記法では、priority が数値で指定できるが、LSBの記法だとRequired-startとかで前後関係しか指定できない。priorityの間に新しいのが入ると大規模な(数値の)シフトがおこるっぽい。 内容が重複しているので、LSBとchkconfigでどちらが優先されるのか調べたところchkconfigコマンドのmanによれば、LSBの記述が優先されるみたい。

読書メモ:達人に学ぶDB設計 徹底指南書

【参考】

達人に学ぶDB設計 徹底指南書 kindleで買って、一旦50%くらいまでさらっと読んだ状態。ざっくりER図がかけて、正規化が何かぼんやりわかるところまで。パフォーマンスチューニングについてはこれから。

【データベース設計】

やること

  • エンティティの抽出( どういう機能を入れたいか、またそれに必要なデータの抽出)
  • エンティティの定義
  • 正規化
  • ER図の作成

エンティティはデータを属性(attribute)という形で保持する。テーブルにおける「列」と同義。 テーブル名は、すべて複数形または複数名詞でかける。原理的に同じ種類のものが複数入っているため。

【テーブルに対する制約】

  • NOTNULL制約:この行は絶対にNULLにならないというのがわかっていればNULL禁止にする。列単位の設定。可能な限り設定したい。
  • 一意制約:ある列の組について一意性を求める。主キーでは当然この制約があるが、ほかの列にも設定可能
  • CHECK制約:列の取りうる値の範囲の制限。数字や文字列をあらかじめ決めておく。

【正規化】

正規化とそれによってつくられる正規形。 第1~第5正規形がある。数値があがると正規化のレベルが上がる。 正規化をするほどデータ整合性は高まるが、検索性能が劣化する。実用レベルでは第3正規形までを考えれば十分。 それぞれの概要

  • 第1:一つのセルには一つの値だけ
  • 第2:テーブル内に部分関数従属があるものを、完全関数従属のみのテーブルを作る。
    • 関数従属性を満たす。X列の値を決めればY列の値が1つに決まる。「会社IDと会社名」、「社員IDと社員名」が同一テーブルにある場合にはそれぞれ部分関数従属。それぞれテーブルを分ければ完全関数従属。これをやるメリット:社員がいるかどうか変わらない会社を登録できない。MULLかダミーの値を突っ込むことになる。独立していれ会社テーブルにだけ登録できる。
  • 第3:2段階の関数従属(推移的関数従属)をなくす。すべてのテーブルについて非キー列がキー列に従属するようにする。

メリットデメリット

  • メリット: 更新のミスを防ぐ。テーブルの持つ意味が明確になる。
  • デメリット:テーブルの数が増えるとSQLで結合が必要になり、パフォーマンスが低下する。

1テーブルには一つのエンティティ情報を含める。正規化の逆操作は結合。

【ER図】

スタンダードなER記は主に二つ。

  • IE(Information Engineering)表記法(通称鳥の足) 
  • IDEFIX

テーブルの表現方法はどちらも同じ。テーブル名、上に主キー、下に非主キー属性のもの。FKにはその旨をかく。外部キー:Foreign key(FK)。主キー:Primary Key(PK)。 テーブル間の関係は、基本的に1:多。多:多の場合には1:多に分解する。 あるテーブルの主キーが他のテーブルに列として含まれているかどうかというのが重要。

IE表記

  • "ー" は相手のエンティティと対応するレコード数が1(カーディナリティが1)。
  • "○" はゼロ、鳥脚は複数。二つ組み合わせて、ゼロ以上の複数を表す。

IDEFIX表記

関連実体

多:多は関連実体で解決する。 学生:講義 とかは多:多になってしまう。二つのテーブルを紐つけようとすると無理やり学生テーブルに「講義コード」列をいれることになる。そうすると講義未登録の学生はNOTNULL制約によりレコード追加できなくなる。 それを防ぐために間に関連実体を挟む。受講エンティティを間に挟んで1:多の関係を二つにする。「学生」「講義」のそれぞれの主キーを組み合わせたキーを主キーとする。

【パフォーマンス劣化を防ぐ】

joinは重い処理。複数(特に3つ以上)を結合すると思い。むしろ非正規化してjoinを使わない方法も。 ただし、非正規化のテーブルに対してUPDATEをかけるとものすごくレコードが多かったりする。正規化されていればテーブル一つにちょっと更新をかければよいことが多い。 データ整合性とパフォーマンスはトレードオフ。原則的には正規化する。非正規化は最後の手段。劇薬。

VagrantのCent6.6 のBoxを使ったらsshできなかった

背景

新しいMacを買って、VirtualBoxVagrantを使ってAnsibleの足場環境を作りたい。CentOSVMを立てようとした。 http://www.vagrantbox.es/ ここから適当なBoxであるCentOS6.6をダウンロードしてきて使おうとしたがエラーが出てうまく vagrant up できないように見えた。

エラーメッセージ

default: Warning: Authentication failure. Retrying...

使おうとしたBox

https://github.com/tommy-muehle/puppet-vagrant-boxes/releases/download/1.0.0/centos-6.6-x86_64.box

問題と解決

vagrant boxはvagrantユーザのパスワードがvagrantになっているっぽいので、それでパスワードログインする。 VM内のauthorized_keysのパーミッションが間違っていて、0664みたいになっていた。これを0600に修正して問題なく vagrant ssh できるようになった。