7:09 AM投稿記事の長さ:天声人語 × 14.4個 くらい

暗号化されたBLOB型のデータをphpMyAdmin上で復号して表示する

トップイメージ

皆様、いかがお過ごしであろうか? 今まで自分の事をフロントエンドエンジニアだと思っていたが近頃どんどんバックエンド寄りのエンジニアになってきている野地である。

自分は普段、ごくごく一般的なLAMP環境(使用言語はPHP)で開発をしているが、データベースを直接操作する時はいちいちコマンドラインから叩くのも面倒なのでphpMyAdminのお世話になっている。

データベースとバックエンドは切っても切れない関係にあるが、SQLを叩かずともブラウザから直にデータベースを操作できるphpMyAdminは便利なツールだ。

しかし最近、データの暗号化目的でBLOB型のデータを扱う案件を扱ったときに、phpMyAdminではそのデータを上手く表示できないことに気が付いた。

しかし、暗号化・復号処理を行っているのがPHPならばphpMyAdminを動かしているのもまたPHPである。

どうにかしてphpMyAdmin上で復号されたデータを手軽に見られないかと色々改造していたら何とか成功したので、その手順を説明したのが今回の記事である。

ただし、この方法は暗号化したデータを復号する手順をまるまるphpMyAdmin上に書くので、セキュリティリスクが上がってしまう可能性が高い。あくまで他人がアクセスできないテスト環境上のphpMyAdminにだけ適用するように注意しよう。

目次

  1. その前にBLOB型とは: バイナリ型のデータを格納できるデータ型
  2. 要件: テーブルの中身を表示したときに、BLOB型を勝手に復号して表示させたい
  3. まずはURLの末尾に「&display_blob=1」を追加する
  4. if文を追加し、設定のon&offができるようにする
  5. if文の中に復号処理を記述する
  6. URLの末尾に「&decrypt=1」を追加し、結果を表示する
  7. まとめ

その前にBLOB型とは: バイナリ型のデータを格納できるデータ型

BLOB型は通常バイナリ型のデータをデータベース(以下DB)に収めたい時に使うデータ型だ。

バイナリ型のデータで代表的なものは画像データだが、BLOB型を使えばDBに直接画像データを保存できる。

しかしご存じの通り画像データは普通のテキストデータよりも重いデータであることが殆どであり、特別な理由がない限りDBには画像が置いてあるディレクトリのパスを保存することが多い。

一方、今回の本題でもあるのだがBLOB型は元々バイナリ型であるデータを仕込む以外にも暗号化されたテキストデータを格納する場合にも使用される。

例えば顧客のデータが入ったDBを攻撃者が手に入れてしまったとしよう。

varchar型などの汎用的なデータ型で顧客データが保存されていた場合、攻撃者がDBを手に入れた時点で顧客データは完全に流出したことになってしまう。

しかし顧客データを暗号化してBLOB型のデータとして保存しておけば、攻撃者はDBに加え暗号化に使ったアルゴリズムとそれに使用する秘密鍵を手に入れなくてはならない。

大抵、データと暗号化アルゴリズム&秘密鍵は別の位置に保存されているため、比較的流出しやすいと言われているDBの情報だけでは顧客データを判別できないようにできるのだ。

要件: テーブルの中身を表示したときに、BLOB型を勝手に復号して表示させたい

BLOB型で暗号化したデータを保存することはよくあるケースなのだが、当然暗号であるのでDBを直接参照して表示されるのは暗号化されたデータだ。

それでいてちゃんと自分の作ったWebページに復号されたデータが表示されていれば何の問題も無いのだが、デバックをする際には直接、DBに何のデータが保存されたか見たい時もあるだろう。

そんなときにバイナリデータではDBの中で何が起こったのか人間には判別しがたい。せいぜいカラムやデータの容量が増えたか減ったかぐらいしか分からないだろう。

今回の主役であるphpMyAdminの初期設定ではバイナリデータをダウンロードできるリンクが表示されるだけで、データ自体を見るだけでもひと手間だ。

ダウンロードリンクが表示されているphpMyAdmin上のBLOB型データ
画像であればダウンロードして確認するのも自然な流れだが、暗号をダウンロードし、手動で解読するのは効率が良くない。

このphpMyAdmin上でデータを復号し、varchar型のようにテキストを表示してくれればデバックの効率も段違いだろう。

そんなわけでphpMyAdminをデバック専用に魔改造し、BLOB型のデータは自動で復号してから表示するようにしてしまおう、というのが今回の要件だ。

ちなみに今回の説明に用いる環境はWindows10(64bit)上のC:直下で動作させているXAMMP7.1.1 / PHP 7.1.1となる。

まずはURLの末尾に「&display_blob=1」を追加する

表示されているBLOB型のデータがダウンロードリンクでは話が始まらないので、テキストで表示するモードへ切り替えよう。

昔のphpMyAdminにはその設定をするUIがあったらしいが、現在は正直触ってほしくないのかそのUIは撤去され、機能だけが生き残っている状態だ。

機能をデフォルトのoffからonにするためにはテーブルの内容を表示している画面のURL末尾に「&display_blob=1」を追加し、そのページへ移動しよう。
アドレスバーに「&display_blob=1」を追加した様子

これでSESSIONへ設定が保存され、BLOB型のデータが直接表示されるようになる。

中身のバイナリデータが直接表示されたBLOB型データ

とは言っても表示されるのはデータ全文ではなく冒頭の何十文字だけだが、PHPの内部でちゃんとデータは持っているので心配は無用だ。

if文を追加し、設定のon&offができるようにする

先ほどURLで設定のon&offをしたように、復号処理もurlでon&offできるようにしたいと思う。

XAMPP環境では

“C:\xampp\phpMyAdmin\libraries\DisplayResults.php”

に改造したい「DisplayResults.php」ファイルがあるので、これを適当なエディタで開こう(phpMyAdminのバージョンによって名前が微妙に違う場合があるので注意)。

これを開くと長い一つのPHPクラスが表示されると思うが、だいたい5300行付近、

$_SESSION[‘tmpval’][‘display_blob’]

という文が条件の一部になっているif文を探す。

そしてそのif文の中にもう一つif文を作成し、list関数より前の内容を全てelse{}内に括ってしまおう。

新たに作成したif文の条件はisset($_GET[‘decrypt’]) && $_GET[‘decrypt’]と設定しておく。

この時点でコードは以下のようになっているはずだ。

  if (($_SESSION['tmpval']['display_binary']
    && $meta->type === self::STRING_FIELD)
    || ($_SESSION['tmpval']['display_blob']
    && stristr($meta->type, self::BLOB_FIELD))
  ) {
  
    if(isset($_GET['decrypt']) && $_GET['decrypt']){
      //ここに復号処理を書いていく
    }
    else{

      // in this case, restart from the original $content
      if (mb_check_encoding($content, 'utf-8')
        && !preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', $content)
      ) {
        // show as text if it's valid utf-8
        $result = htmlspecialchars($content);
      } else {
        $result = '0x' . bin2hex($content);
      }
    }
    list(
      $is_truncated,
      $result,
      // skip 3rd param
    ) = $this->_getPartialText($result);
  }
  

if文の中に復号処理を記述する

実際に変更を加える前、コードの中では何をやっているのかというと、表示される前のバイナリデータが$contentに格納されており、このデータを加工した後最終的に$resultへ格納するといった作業が行われている。

だがその作業はelse側に追いやってしまったので、代わりに復号化の処理をここに書いてやるのが目的だ。

$contentで受け取ったデータ復号し$resultに入れてやるという最初と最後を変えなければ良いのでそんなに迷うこともないだろう。

さて、復号処理をどんなコードで実装するのかはエンジニアによって様々なので、phpMyAdminに埋め込む復号コードもケースバイケースである。

今回は暗号化処理にCodeigniterのEncryptionライブラリを使用したのでその例を載せるが、各自復号化処理は自分の実装したモノに書き換えてほしい。

  if (($_SESSION['tmpval']['display_binary']
    && $meta->type === self::STRING_FIELD)
    || ($_SESSION['tmpval']['display_blob']
    && stristr($meta->type, self::BLOB_FIELD))
  ) {
    //decrypt
    if(isset($_GET['decrypt']) && $_GET['decrypt']){
  
      //復号処理
      $crypt = '設定した秘密鍵の文字列';
      $data = base64_decode(substr($content, 128));
      $salt = str_repeat("\0", 64);
      $prk = hash_hmac('sha512', $crypt, $salt, TRUE);
      $key = '';
      for ($key_block = '', $block_index = 1; strlen($key) < strlen($crypt); $block_index++){
        $key_block = hash_hmac('sha512', $key_block.'encryption'.chr($block_index), $prk, TRUE);
        $key .= $key_block;
      }
      $key = substr($key, 0, strlen($crypt));

      $iv = substr($data, 0, 16);
      $data = substr($data, 16);
      $result = openssl_decrypt($data, 'aes-128-cbc', $key, 1, $iv);
    }
    else{

      // in this case, restart from the original $content
      if (mb_check_encoding($content, 'utf-8')
        && !preg_match('/[\x00-\x08\x0B\x0C\x0E-\x1F\x80-\x9F]/u', $content)
      ) {
        // show as text if it's valid utf-8
        $result = htmlspecialchars($content);
      } else {
        $result = '0x' . bin2hex($content);
      }
    }
    list(
      $is_truncated,
      $result,
      // skip 3rd param
    ) = $this->_getPartialText($result);
  }
  

URLの末尾に「&decrypt=1」を追加し、結果を表示する

これでコード側の変更が済んだので、今度はブラウザに表示してあるphpMyAdminに戻ろう。

URLの末尾に「&decrypt=1」という文字を追加しページを移動すれば復号されたデータが表示されるはずだ。

アドレスバーに「&decrypt=1」を追加した様子

復号されたデータの表示されたBLOB型のデータ

falseという文字列が表示されていたりエラーが発生している場合は復号処理が間違っている可能性が高いので再度コードを確認してみよう。

ちなみに、これら設定はphpMyAdminの左上に表示されているアイコンのうち、ドアから矢印が出ているようなアイコン(マウスオーバーすると「empty session data」と表示される)をクリックすると初期値に戻るので、本来のBLOB型を表示したい場合はこれで元に戻そう。

セッションデータ初期化ボタンの位置

まとめ

今回の記事は比較的短めにまとまったかと思うが、いかがだったろうか。

くれぐれも注意してほしいのだが、今回のコードは攻撃者にとっては喉から手が出るほど欲しい暗号化アルゴリズムとキーをphpMyAdminの中に埋め込むという手段をとっていうので、外部からアクセスされることのないテストサーバー上のphpMyAdminにのみ適用してほしい。

セキュリティやそもそもバックエンドに関してはまだまだ知識の浅い筆者だが、これからはバックエンド側の記事もバリバリ書いていこうかと思いますのでよろしくお願いします。

コメントを付ける

入力エリアすべてが必須項目です。

内容をよくご確認の上、送信してください