Code Day's Night

ichikawayのブログ

dockerコマンドでよく使うもの amazonlinuxベース

普段はdocker-composeで操作していても、たまに使い捨てコンテナを扱いたい時がある。
そんな時にdockerコマンドだけで済ませる方法。
dockerイメージの作成、コンテナ起動、コンテナのシェル操作、コンテナ削除の説明。

Dockerfile

amazonlinuxの最新を取得して、最低限のコマンドを入れる

FROM amazonlinux:latest

RUN yum update -y && yum install -y \
less \
procps \
net-tools 

build

Dockerfileを置いて、それを元にタグ名 ichi/awslinux を起動

docker build -t ichi/awslinux:1.0 .

コンテナ起動

nameオプションでコンテナの名前をつける。 最後にプロセスが終了しないようにtail -fを使ってコンテナを起動し続ける。

docker run -d --name ichi-awstest ichi/awslinux:1.0  tail -f /dev/null

docker ps

動いているコンテナを表示するには、 docker ps を使う。
ビルドして動作していない、もしくは動作終了したコンテナまで表示するには docker ps -a を使う。

コンテナにログイン

コンテナ名を指定してbashを起動する。これでコンテナの中のシェルが操作できる。

docker exec -it ichi-awstest bash

コンテナ削除

コンテナを停止し、docker rmでコンテナ削除。 image rmでビルドしたイメージを削除。

docker stop ichi-awstest
docker rm ichi-awstest
docker images
docker image rm ichi/awslinux:1.0

Dockerfileを使わない場合

docker pull node:8.9.4-alpine

docker run -d --name node-alpine -it node:8.9.4-alpine

上記--nameでコンテナ名を指定していない場合は自動で付与されるため
docker ps のNAMESにある名前を使う

docker exec -it node-alpine /bin/sh

SQLでdatetimeの差を秒の整数で取得

MySQLのDATETIMEカラムの差を秒で取得する方法

startとendというカラムがDATETIMEだった場合、時間の差は

select TIMEDIFF(`end`, `start`) 

で取得できる。この時、 00:12:13のようなフォーマットで返る

これをさらにtime_to_sec関数で整数値の秒にする。

select TIME_TO_SEC( TIMEDIFF(`end`, `start`) ) 

この時間の平均が出したければ、下記のようにすればよい

select AVG( TIME_TO_SEC( TIMEDIFF(`end`, `start`) ) ) 

CakePHPではDBカラムのSQLインジェクションに注意!

CakePHPアドベントカレンダー12日目の記事です。

市川@cakephperです。ちゃんとCakePHP使ってますよ!
丹精込めてVAddyを作ってます。
最近コンビニで常陸野ネストビール セッションIPAが売っててテンションが上がりますね。

CakePHP2,3のDBカラム名の扱い

CakePHP2の公式ドキュメントでは下記の注意書きがあります。

CakePHP は、配列の値部分のみエスケープします。決して キーにユーザーデータをセットしないでください。SQLインジェクションの脆弱性になります。

CakePHPのORMでWHERE句を生成する場合に、カラム名やSQLスニペットはエスケープされません。
あまりないパターンかもしれませんが、ユーザから送信されたリクエストデータの配列をそのまま条件に指定している場合はSQLインジェクションにつながります。

CakePHP3 公式ドキュメントでも同じく注意書きがあります。(該当箇所はリンク先の下の方の赤い枠で囲まれた箇所です)

具体例

例えば何かしらの検索条件のフォームがあったとして、ユーザが条件を選択して送信した
$this->request->data['Model']

['type' => 1, 'keyword' => 'あいうえお'];

であった場合、この送信された配列データをそのまま下記のようにfindメソッドの条件に渡している場合は危険です。

$this->Model->find('all', ['conditions' => $this->request->data['Model']]);

このように送信されたデータが連想配列になっていて、フォームの検索項目を増やしていけば自然と検索条件も増えていくため一見便利なように見えますが、配列のキーは何もエスケープされずにWHERE句のカラム名として使われます。
受け取ったリクエストデータの配列のキーが改ざんされた場合はSQLインジェクションの脆弱性が発生します。

CakePHP3の場合は、where()メソッドに渡す配列のキーがエスケープされずそのまま渡されますので同様に注意が必要です。

対応策

ユーザからのリクエストデータが配列の場合は、キーを信用せず値だけ使うようにしましょう。

$condition['type'] = $this->request->data('Model.type');
$condition['keyword'] = $this->request->data('Model.keyword');

追記(2017/12/26)

CakePHP2の、ControllerクラスのpostConditions()にこのSQLインジェクションの脆弱性があり、2017/12/16に修正されています。
Securityコンポーネントを使わずに、postConditions()だけを使ってfindの条件(conditions)を作成してfind()に渡している場合は危険ですのでご注意ください。
Make postConditions() less permissive. by markstory · Pull Request #11526 · cakephp/cakephp · GitHub

基本的には信用するフィールドのみ受け付けるか、最新のCakePHP2を使うか、私が作っているSecurePHPライブラリのように配列のキーで受け付ける文字列を制限してNGのものは削除するのが良いかと思います。 GitHub - ichikaway/securephp: SecurePHP is the library for security

Webアプリケーションにおけるタイミング攻撃の実現性 リモートから簡単にパスワードクラックできるのか?

これはPHPアドベントカレンダー2017 8日目の記事です。

今年もBrewDog ホッピークリスマスIPAの季節になりましたね。美味しいクラフトビールに恵まれて幸せです。 市川@cakephperです。 丹精込めてVAddyを作ってます。

経緯

2017年9月にLaravel5.5にタイミング攻撃が存在するという脆弱性が CVE-2017-14775 として公開されました。 修正のPull Requestはこちらです。
[5.5] [Security] Close remember_me Timing Attack Vector

日本語の記事として、Innovator Japan Engineers’ Blogのlocakdiskさんの記事があります。
Laravel の脆弱性 CVE-2017-14775 の対処法

この修正を見て、そもそも最近の高速なCPUや複雑なネットワーク、サーバ環境においてリモートからのタイミング攻撃は可能なのか?という疑問が浮かびました。 今回はPHPを例に説明しますが、結論の箇所はPHPに関係なく他の言語でも同様の結論です。

タイミング攻撃とは

タイミング攻撃とは、処理速度や消費電力の違いを利用して機密情報を取得するサイドチャネル攻撃の一種です。 例として、処理が遅いデバイスで入力値の時間の変化によってパスワードを1文字ずつ解析するものがあります。

PHPのタイミング攻撃

PHPの比較演算子 ===== では、文字列がマッチする長さによって処理時間が異なります。

例えば、'abcde' == 'aaaaa' と、'abcde' == 'abcde' では微妙に処理時間が異なります。 PHPはC言語で実装され、==, ===では下記のような動作になります。

  • 比較する文字列の長さが異なると即falseを返す
  • 同じ長さの文字列は、前方から1文字ずつ比較し、異なった時点でfalseを返す

これにより、パスワード文字列などが効率的に取得できます。

PHPでの対応策

PHPでは、安全な文字列比較関数として hash_equals()がPHP5.6から用意されています。

タイミング攻撃の利点

パスワードを破る場合、総当たりのブルートフォース攻撃がありますが、これは多くのパターンを試行しなくてはならず、時間がかかります。 例えば数字のみのパスワード6桁の場合、最大で100万回の試行が必要です。

タイミング攻撃がうまくいくと仮定すると、最小で60回程度の試行、1回では判別できないことが多いので、何度か試行してその100倍、1000倍程度の回数になります。1000倍でも6万回。 ネットワークの遅延など、ランダムな遅延が発生するので、1文字に対してある程度の回数を試行し、統計処理をすると解析できるというものです。 ただ、揺れが大きく多くの試行が発生する場合は、結局ブルートフォースと同じぐらいの試行回数となる可能性もあるため、どれぐらいの精度で判別できるかが焦点になります。

疑問点の整理

文字列比較のようなμsec程度の処理の差を、ネットワーク越しのWebサーバから検出できるのかというのが今回の疑問点です。 文字列比較の処理時間に比べて、下記のような他の要素が圧倒的に時間がかかるものがある環境で統計的に解析できるのが信じられません。

  • 同じネットワークの経路を通るとは限らず、混雑などの影響の遅延がばらつく
  • Webサーバのアクセス数や、同時に起こっている別の人の処理内容は異なっている
  • Webサーバの手前のキャッシュサーバや多段構成の要素の影響

また解析できたとしても試行回数が多くなって、それはブルートフォースと比べてパフォーマンスが良いのかという点も気になります。

結論

同じような疑問をもっている人がいて、検証した論文が2015年に公開されています。
Web Timing Attacks Made Practical

ここでの結論としては、

  • インターネット経由の場合はμsecレベルの差は検出できない
  • LAN内であっても1μsec程度の処理差だと検出できない(一般的なサーバCPUの文字比較程度だと無理)

となっています。1μsecを超えるようなもう少し重い処理であれば、LAN内なら検出できる可能性はあるようです。

インターネットで広く提供しているサイトであればタイミング攻撃を恐れる必要はないと言えますが、今回hash_equals()を覚えた人は今後は使えるところは使っていきましょう!

修正箇所

今回の修正箇所を見ると、 f:id:ichikaway:20171208090127p:plain

Pull Requestの修正コードは一応問題ないのですが、修正された内容は、まず $userを評価してfalseならhash_equals()は処理されない点が興味深いですね。 今回のコードでは問題になりませんが、$userの結果がtrue/falseによって時間差が生じますので、下手をすると別の問題を引き起こしてたかもしれません。

そもそも元のコードPHPは比較演算子を使ってるわけではなく、SQLのWHERE句でトークンの条件を指定していただけです。それをPHPのタイミング攻撃が!と言ってわざわざSQLから取得した結果をもとにトークンの一致をhash_equals()を使ってやっています。 たぶんこのPull Requestを送ってる人もタイミング攻撃の詳細はあまりよくわかってなく、とりあえずそれっぽいところにはhash_equals()を使うという表面的な考えしかないように見えます。

感想

今回はLaravelの脆弱性修正箇所を見て、これは本当に必要な修正だったのだろうかという疑問が残ります。 フレームワーク作者はフレームワークの機能を作るのに注力しているため、セキュリティの問題があるという指摘とPull Requestがきた場合、SQLインジェクションのようなわかりやすいものは別として、 今回のような影響小さそうだけど良くわらからないから面倒だからという場合は、あまり考えずに取り込んでしまうのではないでしょうか。

そもそも現在の環境でほぼ起こらない問題を指摘して、PHPのお作法的にはこうなってるからしたほうがいいよ(詳細は知らんけど)ぐらいの理解の人が声をあげていくのは良いのだろうか、コア開発者をうんざりさせるだけなのではないか、そう感じました。

この話とはちょっとそれますが、面白い記事があったのでリンクを貼っておきます。
「セキュリティを「必要悪」にした犯人は業界自身ではないか?」

それでは良い金曜日を!

Route53のDNSレコード操作のみ許可するIAMロール

Route53を操作するIAMで、下記の要件を満たすロール設定

  • コンソール画面での操作を想定
  • 特定ゾーンのみのレコードの作成、更新、削除を許可
  • ゾーン一覧の表示
  • ゾーンの削除は不可
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:GetHostedZone",
                "route53:ListHostedZonesByName",
                "route53:ListResourceRecordSets",
                "route53:ChangeResourceRecordSets",
                "route53:ListTagsForResource",
                "route53:ListTagsForResources"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/Z3PYYYYYY",
                "arn:aws:route53:::hostedzone/Z3PXXXXXX"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "route53:ListHostedZones"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

Resourceの arn:aws:route53:::hostedzone/Z3PYYYYYY は、アクセスを許可したいドメインのホストゾーンIDを記載する。今回は2つのゾーンを許可している。

Route53の管理ページで、ゾーンの一覧が表示できないとコンソールでの操作が面倒(ゾーンIDを含むURLに直接遷移が必要)なため、 route53:ListHostedZonesを全てのリソース*で許可する。
全てのリソースで許可する理由は、route53:ListHostedZonesは、リソースの範囲を特定ゾーンに絞れないため。

参考URL docs.aws.amazon.com

口座振替の依頼書で銀行印が必要なのは1ページ目だけ(捨印は任意)

支払い方法が口座振替(引き落とし)に限られている場合、自分の銀行印を押して提出する必要がある。

一般的には、1枚目は銀行用、2枚目が委託業者用、3枚目がお客様控えになっていると思う。2、3枚目は写しになっているが、なぜかそこにも銀行印を押すようなフォーマットの用紙がある。(写しだから1枚目と同じ記述になっている)

 

しかし、2枚目、3枚目は銀行以外のところで保管されるため、銀行印は押したくない。そこで口座振替依頼書に記載された電話番号(今回はSMBC系列)にかけて聞いてみた。

結論は下記。

  • 銀行用と書かれた1枚目のみ銀行印を押せば良い
  • 2枚目以降は銀行印は不要。何も押印しないか、認印を押せばよい
  • 捨印は、1枚目も含めて任意であり必須ではない

 

まぁ、普通に考えればそうだよねという結論だが、振替依頼書のフォーマットによっては2枚目、3枚目にも銀行印が必要という記述があって(写しだから)、何も考えないとそのまま銀行印を押してしまうケースは多いと思う。

 

2枚目は基本的に支払先が管理する用紙のため、申し込み先のお店やスクールなどが管理する。ここに銀行印を押したものを管理されたくないので今回調査した。

 

pandasでデータを間引く(リサンプリング)

大量の時系列データをプロットする場合、プロット数が多くなりすぎてグラフが綺麗にでないことがある。 その場合は、データを間引くわけだが、単純にfor文で回して一定期間を処理するのも良いが、pandasを使えば1発でできる。

この間引くという機能はresampleというメソッドを使えば可能。

pandas.DataFrame.resample — pandas 0.19.2 documentation

df_orig = pd.read_csv(file, names=['date','val'],parse_dates=['date'],infer_datetime_format=True,index_col='date')  #CSVからデータをロード

df = df_orig.resample("D").max()   #1日単位の最大値を取得
df = df_orig.resample("H").sum()   #1時間単位の合計値を取得
df = df_orig.resample("10T").mean()   #10分単位の平均値を取得

pyenvで入れたpython3をcronなどから利用する方法

pyenvを使ってインストールしたpython3をcronなどから利用する場合、他のユーザからも実行できるように、まずは.pyenvを/usr/local/pyenvなどに置く。

git clone https://github.com/yyuu/pyenv.git /usr/local/pyenv

vi ~/.bashrc
export PYENV_ROOT="/usr/local/pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

source ~/.bashrc

pyenv install 3.6.0
pyenv global 3.6.0

pyenv global 3.6.0としておけば、/usr/local/pyenv/shims/pythonが3.6のバージョンになる。 あとは他のユーザがこの /usr/local/pyenv/shims/python を使えばok。 pipでインストールされたmatplotlibやpandasもpython3.6のものとなる。

MacやLinuxにpyenvを入れてお手軽にpython3環境を構築

MacやLinuxにpyenv入れて、python3とmatplotlibなどをインストールする場合
.pyenvの場所は自由に指定できるので、/usr/local/pyenvなどでもok。

git clone https://github.com/yyuu/pyenv.git ~/.pyenv

vi ~/.bashrc
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

source ~/.bashrc

pyenv install -l でインストール可能なバージョンがリストアップされるので、必要なバージョンをインストール。
このユーザで使うpythonのバージョンを指定。

pyenv install 3.6.0
pyenv global 3.6.0

pipはインストール済みのため、matplotlibなどをインストール

pip -V
pip install matplotlib numpy pandas
pip list

Macだとpython3でそのままmatplotlibを動かすとframeworkエラーが出るので下記で対応

qiita.com

vi ~/.matplotlib/matplotlibrc
backend : TkAgg

AWS Linuxでpython2.7に切り替えてmatplotlibをインストール

メモ

AWS Linuxでは、python2.6が利用されているため、それを2.7に切り替える。 python2.6のpipでは古すぎてmatplotlibがインストールできないため。

alternatives --display python
alternatives --set python /usr/bin/python2.7
python -V

pipをインストールし、matplotlibをインストールする

yum install python27-devel python27-pip
easy_install pip
pip install --upgrade pip

pip install matplotlib numpy

最後に、グラフを描画してhoge.pngに保存するスクリプトを動かして、実際にmatplotlibが動くか確認。

import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1,1,1)

x = [0,1,2,3,4,5]
y = [10,20,30,40,50,60]

ax.plot(x,y)
plt.savefig('./hoge.png')