2015年11月19日木曜日

IoTpack(SORACOM)でProxy(Squid)


"cloudpack"には"SORACOM"さんのサービスを利用した「IoTpack」という商品があります。


この商品は簡単にいえば、SORACOMのVPCと自分のVPCを"VPC Peering"で接続し、
"SORACOM Air"のSIMを利用したデバイスからの通信を、インターネットに出ることなく
自分のVPC上のサーバで処理することができるものとなっています。


ということで下記のようにIoTpackを利用して"Air SIM"のiPhoneのHTTP(S)通信が
自分のVPC上のProxyサーバ(Squid)経由でインターネットに出て行くようにしてみました。


EC2上には下記のようにSquidを用意しておきます。
("no_cache deny all"でキャッシュしないようにしています)
$ sudo yum -y install squid
...

$ sudo cat /etc/squid/squid.conf
http_port         3128
visible_hostname  cloudpack
http_access allow all
no_cache deny     all

$ sudo service squid start
Starting squid:                                            [  OK  ]

環境はIoTpackとしてもらったので、
いきなりiPhoneからの上記のProxy用のEC2の"Private IP"にPingできてました。


さらに上述のProxyサーバを利用するには、
iPhoneのプロファイルを調整する必要があります。

プロファイルは下記のツールで「構成プロファイル」として作成することができます。
iPhone 構成ユーティリティ 2.2 (Mac)
今回の構成プロファイルは次のように作成しています。



「アクセスポイント」に関してはSORACOMの情報を入力し、
さらに「プロキシサーバとポート」で上記のProxyサーバの情報を入れています。

このプロファイルはMacにiPhoneをUSBでつなぎ、
上記ツールからインストールすることができます。(既存のプロファイルは削除しています)


この状態で、iPhoneからSafariで適当なURL(http://cloudpack.jp)にアクセスすると、
Squidのログより、iPhoneのHTTP(S)の通信がProxy経由になっていることが確認できます。
1447883093.478    173 100.64.128.67 TCP_MISS/200 34431 GET http://cloudpack.jp/ - DIRECT/54.239.194.249 text/html
1447883093.622      9 xxx.xxx.xxx.xxx TCP_MISS/200 2037 GET http://cloudpack.jp/js/jquery.browser.min.js - DIRECT/54.239.194.249 application/x-javascript
1447883093.630     17 xxx.xxx.xxx.xxx TCP_MISS/200 19145 GET http://cloudpack.jp/css/common/layout.css - DIRECT/54.239.194.249 text/css
1447883093.631     18 xxx.xxx.xxx.xxx TCP_MISS/200 16280 GET http://cloudpack.jp/css/index.css - DIRECT/54.239.194.249 text/css
1447883093.634     13 xxx.xxx.xxx.xxx TCP_MISS/200 4054 GET http://cloudpack.jp/js/jquery.easing-1.3.min.js - DIRECT/54.239.194.249 application/x-javascript
1447883093.634     17 xxx.xxx.xxx.xxx TCP_MISS/200 22487 GET http://cloudpack.jp/js/common.js - DIRECT/54.239.194.249 application/x-javascript
1447883093.665     52 xxx.xxx.xxx.xxx TCP_MISS/200 34120 GET http://cloudpack.jp/css/common/general.css - DIRECT/54.239.194.249 text/css
...

さらに、ユーザー認証(Basic)もかけてみます。Squidの設定は下記の通りです。
$ sudo cat /etc/squid/squid.conf
http_port         3128
visible_hostname  cloudpack
no_cache deny     all
auth_param basic  program /usr/lib64/squid/ncsa_auth /etc/squid/squid.htpasswd
auth_param basic  children 5
auth_param basic  credentialsttl 1 hours
acl               password proxy_auth REQUIRED
http_access allow password
http_access deny  all

$ sudo service squid restart
Stopping squid: ................                           [  OK  ]
Starting squid: .                                          [  OK  ]

ちゃんと認証画面も表示されました。


ログを見ると、今度は、そのユーザーがどこに接続したかまで、わかるようになります。
1447890752.703     13 xxx.xxx.xxx.xxx TCP_MISS/200 601 GET http://cloudpack.jp/img/index/visual_bg.gif suzuki DIRECT/54.239.194.50 image/gif
1447890752.717     12 xxx.xxx.xxx.xxx TCP_MISS/200 651 GET http://cloudpack.jp/img/common/bg_01.png suzuki DIRECT/54.239.194.50 image/png
1447890752.730     10 xxx.xxx.xxx.xxx TCP_MISS/200 2454 GET http://cloudpack.jp/img/index/btn_bg.gif suzuki DIRECT/54.239.194.50 image/gif
1447890752.730      9 xxx.xxx.xxx.xxx TCP_MISS/200 816 GET http://cloudpack.jp/img/common/line02.gif suzuki DIRECT/54.239.194.50 image/gif
1447890752.739     14 xxx.xxx.xxx.xxx TCP_MISS/200 1858 GET http://cloudpack.jp/img/index/attention_bg.gif suzuki DIRECT/54.239.194.50 image/gif
...

今回は典型的な例としてProxyを試してみましたが、
このようにiPhoneからの(SIMでの)通信も閉域網内の通信として扱え、
いろいろと(社内サーバで処理)できそうなことがわかりました。

夢が広がります。

2015年11月7日土曜日

本ブログのLambda関係の記事をまとめてみた


"AWS re:Invent 2015"の下記Lambda関係の発表を機に書きだしましたが、
【AWS発表】AWS Lambdaのアップデート – Python, VPC, 実行時間の延長, スケジュールなど
何を書いたか、自分でもわからなくなってきたので、一旦まとめてみました。
Cognitoがない...

2015年11月6日金曜日

Lambda(Python)からKinesisにPut(API)してLambda(Python)でGet(Event)する


こんな感じです。


API(Boto3)でPutするのは、まあ当たり前ですが、Getに関しては、
Lambdaの"event source"にKinesisというものがあり、それを設定することにより
イベントドリブンでKinesisのデータをLambdaで処理することができます。

ということで、作ってみます。

Kinesisの作成


AWSマネジメントコンソールより下記のように作成します。


IAMの設定


Lambdaに設定するIAMロール(lambda_basic_exection)にKinesisが扱える
マネージドポリシー(AmazonKinesisFullAccess)をアタッチします。

 

KinesisへPutするLambdaファンクション


コードは、こんな感じです。
import logging
import boto3

def lambda_handler(event, context):

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

    try:
        logger.info(event)
        response = boto3.client('kinesis').put_record(
            StreamName = "test",
            Data = "test",
            PartitionKey = "test",
        )
        logger.info(response)
        return response

    except Exception as e:
        logger.error(e)
        raise e

実行すると、レスポンスは次のように返ってきます。
{
  "ShardId": "shardId-000000000000",
  "ResponseMetadata": {
    "HTTPStatusCode": 200,
    "RequestId": "eef4ccde-840d-11e5-9d26-6b863e58e437"
  },
  "SequenceNumber": "49556079134761214959115455796946952254907110183804076034"
}

Kinesisのモニタリングで確認しても、
ちゃんとPutレコードがカウントされていることがわかります。


KinesisからGetするLambdaファンクション


コードは、こんな感じです。
import logging
import base64

def lambda_handler(event, context):

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

    try:

        logger.info(event)

        payloads = []
        for record in event['Records']:
            payload = base64.b64decode(record["kinesis"]["data"])
            payloads.append(payload)
        logger.info(payloads)

        return payloads

    except Exception as e:
        logger.error(e)
        raise e

そして、Event Sourceとして、Kinesisを追加します。


CloudWatch Logsを確認すると、ちゃんとKinesisのEvent Sourceを設定した、
Lambdaファンクションが実行されログが出力されていることがわかります。


CloudWatchのMetricsでもGetリクエストで確認することができます。


DynamoDBのテーブルからランダムにレコードを取得する(全件とってからの処理なので良い実装ではないけど...)


全件とってからの処理なので良い実装ではないけど、場合によっては使えるので、
TIPSを貯める目的として...

DynamoDBのテーブルは、こんなシンプルな感じで、

そこからランダムな100件のリストを作成したいと思います。
要件は下記とします。
  • レコード数はそれほど多くない(全件取得してもLambda内で処理できる)
  • レコード数が100件未満の場合もあるため、リストの値は重複OK
で、Python(Lambda)で楽にできないかなー、とググってたら、いい記事見つけました。
Python Tips:リストの中から要素をランダムにピックアップしたい
"random.choice(リスト)"っていうのを使うと、リスト中の値から、
ランダムに一件を取得できるようです。

ということで、コード(Lambda)は下記のとおりです。
(余計な処理も入ってますが...)
import logging
import random
import boto3

image_url = "http://xxxxxxxxxxxxxx.cloudfront.net/"

def lambda_handler(event, context):

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

    try:

        logger.info(event)
        table = boto3.resource("dynamodb").Table("image")

        response = table.scan()
        logger.info(response)

        items = []
        if response["Count"] != 0:
            for i in range(0, 100):
                id = random.choice(response["Items"])["id"]
                items.append({ "id": id, "url": image_url + id + ".png"})
        logger.info(items)

        return items

    except Exception as e:
        logger.error(e)
        raise e

上記を実行すると、次のようなDynamoDBのテーブルのレコードから、
100件ランダムに取得した(重複あり)リストを作成することができました。
[
    {
        "url": "http://xxxxxxxxxxxxxx.cloudfront.net/fa4ee1f2838b11e59f9436cb5329c05c.png",
        "id": "fa4ee1f2838b11e59f9436cb5329c05c"
    },
    {
        "url": "http://xxxxxxxxxxxxxx.cloudfront.net/436f0c8c838b11e59f9436cb5329c05c.png",
        "id": "436f0c8c838b11e59f9436cb5329c05c"
    },
    ...
]

2015年11月2日月曜日

"Amazon Linux"にRundeckをインストール


下記を見ればインストール方法は、だいたいわかると思います。
http://rundeck.org/downloads.html

そして、実際にインストールしてみます。

まずはJavaが利用できることの確認です。
$ java -version
java version "1.7.0_75"
OpenJDK Runtime Environment (amzn-2.5.4.0.53.amzn1-x86_64 u75-b13)
OpenJDK 64-Bit Server VM (build 24.75-b04, mixed mode)

次にRPMでYumのリポジトリ(Rundeck用)をインストールします。
$ sudo rpm -Uvh http://repo.rundeck.org/latest.rpm
http://repo.rundeck.org/latest.rpm を取得中
警告: /var/tmp/rpm-tmp.dQklaS: ヘッダー V4 RSA/SHA1 Signature、鍵 ID e2d1065b: NOKEY
準備しています...              ################################# [100%]
更新中 / インストール中...
   1:rundeck-repo-4-0                 ################################# [100%]

最後にYumでRundeckをインストールします。
$ sudo yum install rundeck
...
インストール:
  rundeck.noarch 0:2.6.1-1.12.GA

依存性関連をインストールしました:
  rundeck-config.noarch 0:2.6.1-1.12.GA

完了しました!

これで、インストールは終わりです。

次は設定ファイルを調整してプラウザでRundeckの管理ページに
アクセスできるようにします。

調整するといっても、下記のファイルの、
$ cat /etc/rundeck/rundeck-config.properties
#loglevel.default is the default log level for jobs: ERROR,WARN,INFO,VERBOSE,DEBUG
loglevel.default=INFO
rdeck.base=/var/lib/rundeck

#rss.enabled if set to true enables RSS feeds that are public (non-authenticated)
rss.enabled=false
# change hostname here
grails.serverURL=http://localhost:4440
dataSource.dbCreate = update
dataSource.url = jdbc:h2:file:/var/lib/rundeck/data/rundeckdb;MVCC=true;TRACE_LEVEL_FILE=4
この部分を
grails.serverURL=http://localhost:4440
次のように
grails.serverURL=http://xxx.xxx.xxx.xxx:4440
実際にアクセスするIPアドレスに変更するだけです。

そして最後に"rundeckd"サービスを起動します。
$ sudo service rundeckd start
Starting rundeckd:                                         [  OK  ]

すると下記のように管理画面にアクセスでき(当然セキュリティグループは調整済み)


「Username: admin / Password: admin」でログインすることができます。


LambdaのVPNサポートがきたら、必要なくなるケースが増えるかも...
【AWS発表】AWS Lambdaのアップデート – Python, VPC, 実行時間の延長, スケジュールなど

Lambdaの"API endpoint"を作成してみた


下記ではAPI Gatewayからバックエンドの設定にLambdaを指定して、
LambdaをHTTP(S)から実行できるようにしました。
"API Gateway"のバックエンドを"Lambda"にしてJSONデータをエコーさせる
しかし、Lambda側でも"API endpoint"というものを作成して、
HTTP(S)から実行できるようにできます。

といっても結局API Gatewayと連携させてるだけなので、結果としては上述した方法と
同じことをしていることになります。

具体的な方法は下記となります。


"API endpoint type"に"API Gateway"を選択します。(それしかありませんが...)
("Security"はデフォルトがIAMでしたが、要件的にOpenにしています)


作成すると"API endpoint URL"(API Gateway)が発行されます。


実際に、API Gatewayの方でも確認してみると、上記で作成したものが表示されています。


ちなみにLambda側の"API endpoint"を削除しても、API Gateway側は残ってました。

で、じゃあ、どっちの方法で行うべきか?って話になると思いますが、
自分的には、Lambdaの"API endpoint"の方で行なった方がいいかなー、と思ってます。

なぜなら、API Gatewayの方で行うと、Lambdaの"API endpoint"には何も表示されず、
そのLambdaがどこから実行されるかが、わかりにくくなってしまうからです。

まあ、API GatewayのリソースがLambdaごとに必ず別れてしまうっていデメリット(?)
もありますが...

2015年10月23日金曜日

メトリックスフィルタでLambdaから"CloudWatch Logs"に出力されたログの件数をメトリックスにする


下図のように、LambdaのログはCloudWatch Logsに出力されますが、
さらにメトリックスフィルタ機能を使うと、そのログの(パターンにマッチした)件数を
メトリックスにして、グラフ表示したりアラーム設定したりでkるようになります。


CloudWatch Logsのロググループからロググループを指定してメトリックスフィルタを
作成します。


ログをフィルタリングするパターンを指定します。


メトリックス名を設定します。


メトリックスフィルターが作成完了しました。


下記のようにCloudWatchのメトリックスとして、パターンに該当したログの件数が
グラフ等で確認することができます。


あとは、このメトリックスに対して、アラームを設定したり、他ツールと連携したりと、
いつものCloudWatchを利用したパターンが適用できるはずです。

2015年10月22日木曜日

"apache-loggen"でダミーのアクセスログを作成する


下記の"apache-loggen"というツールを利用すると、
ダミーのアクセスログを簡単に作成することができます。
apache_log_gen

Amazon Linux上だと下記のように簡単にインストールできます。
$ sudo gem install apache-loggen --no-ri --no-rdoc -V
GET https://rubygems.org/latest_specs.4.8.gz
302 Moved Temporarily
GET https://rubygems.global.ssl.fastly.net/latest_specs.4.8.gz
200 OK
GET https://rubygems.org/quick/Marshal.4.8/apache-loggen-0.0.4.gemspec.rz
302 Moved Temporarily
GET https://rubygems.global.ssl.fastly.net/quick/Marshal.4.8/apache-loggen-0.0.4.gemspec.rz
200 OK
GET https://rubygems.org/quick/Marshal.4.8/json-1.8.3.gemspec.rz
302 Moved Temporarily
GET https://rubygems.global.ssl.fastly.net/quick/Marshal.4.8/json-1.8.3.gemspec.rz
200 OK
Installing gem apache-loggen-0.0.4
Downloading gem apache-loggen-0.0.4.gem
GET https://rubygems.org/gems/apache-loggen-0.0.4.gem
302 Moved Temporarily
GET https://rubygems.global.ssl.fastly.net/gems/apache-loggen-0.0.4.gem
Fetching: apache-loggen-0.0.4.gem (100%)
200 OK
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/Gemfile
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/LICENSE.txt
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/README.md
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/Rakefile
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/apache-loggen.gemspec
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/bin/apache-loggen
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/lib/apache-loggen-boot.rb
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/lib/apache-loggen.rb
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/lib/apache-loggen/base.rb
/usr/local/share/ruby/gems/2.0/gems/apache-loggen-0.0.4/lib/apache-loggen/version.rb
/usr/local/bin/apache-loggen
Successfully installed apache-loggen-0.0.4
1 gem installed

こんな感じでログを作成できます。
$ apache-loggen --rotate=300 test.log
可能な限りログを"test.log"に出力し続け、300秒でログファイル(test.log)を
ローテーションしてます。
"t2.micro"インスタンス(gp2 8G)で実行していますが、下記のようなログが、
300秒で1500万行ほど作成されました。
$ head test.log
116.168.225.172 - - [19/Oct/2015:18:17:44 +0000] "GET /item/electronics/3607 HTTP/1.1" 200 125 "/item/books/70" "Mozilla/5.0 (Windows NT 6.0; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
92.36.171.201 - - [19/Oct/2015:18:17:44 +0000] "GET /item/sports/4471 HTTP/1.1" 200 87 "/category/jewelry" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.77 Safari/535.7"
152.174.149.220 - - [19/Oct/2015:18:17:44 +0000] "GET /category/toys HTTP/1.1" 200 72 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; YTB730; GTB7.2; EasyBits GO v1.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)"
96.195.53.225 - - [19/Oct/2015:18:17:44 +0000] "GET /category/finance HTTP/1.1" 200 114 "/item/books/597" "Mozilla/5.0 (Windows NT 6.0; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
156.48.113.173 - - [19/Oct/2015:18:17:44 +0000] "GET /item/toys/2687 HTTP/1.1" 200 129 "/category/books" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; YTB730; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)"
28.192.173.67 - - [19/Oct/2015:18:17:44 +0000] "GET /category/software HTTP/1.1" 200 117 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.77 Safari/535.7"
52.36.40.92 - - [19/Oct/2015:18:17:44 +0000] "POST /search/?c=Electronics HTTP/1.1" 200 120 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
148.99.222.197 - - [19/Oct/2015:18:17:44 +0000] "GET /category/music HTTP/1.1" 200 128 "/category/finance" "Mozilla/5.0 (Windows NT 6.0; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
200.156.28.119 - - [19/Oct/2015:18:17:44 +0000] "GET /category/toys HTTP/1.1" 200 107 "/category/giftcards" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
188.132.216.195 - - [19/Oct/2015:18:17:44 +0000] "GET /item/electronics/3284 HTTP/1.1" 200 123 "/search/?c=Electronics" "Mozilla/5.0 (Windows NT 6.0; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"

せっかくなので、"grep"を使って特定の(最初の2行の)IP以外のログを
簡単に抽出してみます。
 grep -v -e '116\.168\.225\.172' -e '92\.36\.171\.201' test.log | head
152.174.149.220 - - [19/Oct/2015:18:17:44 +0000] "GET /category/toys HTTP/1.1" 200 72 "-" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; YTB730; GTB7.2; EasyBits GO v1.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)"
96.195.53.225 - - [19/Oct/2015:18:17:44 +0000] "GET /category/finance HTTP/1.1" 200 114 "/item/books/597" "Mozilla/5.0 (Windows NT 6.0; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
156.48.113.173 - - [19/Oct/2015:18:17:44 +0000] "GET /item/toys/2687 HTTP/1.1" 200 129 "/category/books" "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; YTB730; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C)"
28.192.173.67 - - [19/Oct/2015:18:17:44 +0000] "GET /category/software HTTP/1.1" 200 117 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.77 Safari/535.7"
52.36.40.92 - - [19/Oct/2015:18:17:44 +0000] "POST /search/?c=Electronics HTTP/1.1" 200 120 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
148.99.222.197 - - [19/Oct/2015:18:17:44 +0000] "GET /category/music HTTP/1.1" 200 128 "/category/finance" "Mozilla/5.0 (Windows NT 6.0; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
200.156.28.119 - - [19/Oct/2015:18:17:44 +0000] "GET /category/toys HTTP/1.1" 200 107 "/category/giftcards" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
188.132.216.195 - - [19/Oct/2015:18:17:44 +0000] "GET /item/electronics/3284 HTTP/1.1" 200 123 "/search/?c=Electronics" "Mozilla/5.0 (Windows NT 6.0; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
176.87.120.37 - - [19/Oct/2015:18:17:44 +0000] "POST /search/?c=Games+Music HTTP/1.1" 200 98 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"
64.150.185.107 - - [19/Oct/2015:18:17:44 +0000] "GET /category/books HTTP/1.1" 200 113 "-" "Mozilla/5.0 (Windows NT 6.0; rv:10.0.1) Gecko/20100101 Firefox/10.0.1"
問題なく該当するIPを含む行(最初の2行)が除外されました。

って処理を、もっと大量なデータでEMR(HIVE)を使って試してみる準備です。
(最終的にはLambdaから実行)

2015年10月16日金曜日

"AWS WAF"の情報源をまとめてみた


下記の記事で、いろいろとWAFをいじってきたので、
改めてWAFの情報源をまとめてみました。

AWSブログ

【AWS発表】新サービス: AWS WAF

AWS re:Invent 2015 | (SEC323) NEW LAUNCH! Securing Web Applications with AWS WAF


SlideShare



Youtube



AWSホームページ

"AWS WAF"に対して"sqlmap"で"SQL injection"の攻撃をしてみた


下記の記事でIPアドレスでのブロックは確認できたので、
"AWS WAF"を使って特定のIPアドレスからのアクセスをブロック
今回はSQL injectionのブロックを確認してみようと思います。
次のようなQuery Stringでパラメータを渡すパターンで簡単に試してみます。
$ curl http://d3q07yq8vdynnt.cloudfront.net/test.txt?id=1
test

まずはWAFの設定です。

Condition(SQL injection match condition) の作成


Query StringをURLデコードした結果に対してSQL Injectionのチェックを行うように
設定します。

 

ルールの設定


上記のConditionに引っかかったらアクションするようにルールを設定します。


Web ACLの設定


ルールに対するアクションがBlockになっていることを確認します。


WAFにSQL injectionの攻撃


WAFの設定ができたら、実際にWAFに対してSQL injectionの攻撃を行うツールを
導入します。今回は下記のsqlmapを使いました。


上図のリンクよりダウンロード、展開して、展開したディレクトリに入り、
下記のコマンドを実行します。
$ python sqlmap.py -u http://d3q07yq8vdynnt.cloudfront.net/test.txt?id=1
         _
 ___ ___| |_____ ___ ___  {1.0-dev-nongit-20151015}
|_ -| . | |     | .'| . |
|___|_  |_|_|_|_|__,|  _|
      |_|           |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting at 08:46:17

[08:46:18] [INFO] testing connection to the target URL
[08:46:18] [INFO] heuristics detected web page charset 'ascii'
[08:46:18] [INFO] checking if the target is protected by some kind of WAF/IPS/IDS
[08:46:18] [CRITICAL] heuristics detected that the target is protected by some kind of WAF/IPS/IDS
do you want sqlmap to try to detect backend WAF/IPS/IDS? [y/N]
[08:46:24] [WARNING] dropping timeout to 10 seconds (i.e. '--timeout=10')
[08:46:24] [INFO] testing if the target URL is stable
[08:46:24] [INFO] target URL is stable
[08:46:24] [INFO] testing if GET parameter 'id' is dynamic
[08:46:24] [WARNING] GET parameter 'id' does not appear dynamic
[08:46:24] [WARNING] heuristic (basic) test shows that GET parameter 'id' might not be injectable
[08:46:24] [INFO] testing for SQL injection on GET parameter 'id'
[08:46:24] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[08:46:25] [INFO] testing 'MySQL >= 5.0 boolean-based blind - Parameter replace'
[08:46:25] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause'
[08:46:25] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[08:46:25] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause'
[08:46:25] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[08:46:26] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace'
[08:46:26] [INFO] testing 'MySQL inline queries'
[08:46:26] [INFO] testing 'PostgreSQL inline queries'
[08:46:26] [INFO] testing 'Microsoft SQL Server/Sybase inline queries'
[08:46:26] [INFO] testing 'MySQL > 5.0.11 stacked queries (SELECT - comment)'
[08:46:26] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[08:46:26] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[08:46:26] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[08:46:26] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (SELECT)'
[08:46:27] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[08:46:27] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind'
[08:46:27] [INFO] testing 'Oracle AND time-based blind'
[08:46:27] [INFO] testing 'Generic UNION query (NULL) - 1 to 10 columns'
[08:46:27] [WARNING] using unescaped version of the test because of zero knowledge of the back-end DBMS. You can try to explicitly set it using option '--dbms'
[08:46:30] [INFO] testing 'MySQL UNION query (NULL) - 1 to 10 columns'
[08:46:31] [WARNING] GET parameter 'id' is not injectable
[08:46:31] [CRITICAL] all tested parameters appear to be not injectable. Try to increase '--level'/'--risk' values to perform more tests. Also, you can try to rerun by providing either a valid value for option '--string' (or '--regexp') If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could retry with an option '--tamper' (e.g. '--tamper=space2comment')
[08:46:31] [WARNING] HTTP error codes detected during run:
403 (Forbidden) - 209 times

[*] shutting down at 08:46:31
すると、攻撃が始まります。最後に「403 (Forbidden) - 209 times」と出力され、
攻撃がブロック(403)されていることがわかります。

プロックされたリクエストの確認


下図のようにAWSコンソールから確認できます。

 

ブロックされたリクエストで実際にアクセスしてみると、ちゃんとブロックされていることがわかります。
$ curl http://d3q07yq8vdynnt.cloudfront.net/test.txt?id=1%20UNION%20ALL%20SELECT%20NULL--%20
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: dqrrId9UiGO0klLIXFZq2dnJ7VQ7mbBCeY-24G5qdx_ZaGto8WGIgA==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>

CloudWatchとの連携


Blockの様子は次のようにCloudWatchで確認することもできます。