概要
やりたいこと
AWS WAF ログを分析するため、各カラムを指定するクエリをまとめる。
AWS WAF ログは Kinesis Firehose 経由で S3 に保存されており、AWS Glue Crawler を利用して Athena 用のテーブルを作成している。
Athena を使った AWS WAF ログを抽出する方法は下記。
AWS WAF ログと 型
AWS WAF ログのテーブル定義クエリを見ると、 string や int 等の定番の型以外に struct と array という型が存在する。
これらのカラムの値へのアクセスを知りたい。
CREATE EXTERNAL TABLE `waf_logs`(
`timestamp` bigint,
`formatversion` int,
`webaclid` string,
`terminatingruleid` string,
`terminatingruletype` string,
`action` string,
`terminatingrulematchdetails` array<
struct<
conditiontype:string,
location:string,
matcheddata:array<string>
>
>,
`httpsourcename` string,
`httpsourceid` string,
`rulegrouplist` array<string>,
`ratebasedrulelist` array<
struct<
ratebasedruleid:string,
limitkey:string,
maxrateallowed:int
>
>,
`nonterminatingmatchingrules` array<
struct<
ruleid:string,
action:string
>
>,
`httprequest` struct<
clientip:string,
country:string,
headers:array<
struct<
name:string,
value:string
>
>,
uri:string,
args:string,
httpversion:string,
httpmethod:string,
requestid:string
>
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
'paths'='action,formatVersion,httpRequest,httpSourceId,httpSourceName,nonTerminatingMatchingRules,rateBasedRuleList,ruleGroupList,terminatingRuleId,terminatingRuleMatchDetails,terminatingRuleType,timestamp,webaclId')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://athenawaflogs/WebACL/'
Athena でネストされた配列の検索
この辺りを読む。
array
以下のようなネストされた配列を検索したい(header カラム)。
[{name=Host, value=52.198.21.86}, {name=User-Agent, value=Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:77.0) Gecko/20100101 Firefox/77.0}, {name=Accept, value=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8}]
UNNSET 演算子を利用し、ネストされた配列の要素を複数行に展開する。
SELECT
timestamp,
header
FROM
waflog,
UNNEST(httprequest.headers) t(header)
1つのログに対して、レコードが 3 つ返却される。
timestamp | header |
1593341485433 | {name=Host, value=52.198.21.86} |
1593341485433 | {name=User-Agent, value=Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:77.0) Gecko/20100101 Firefox/77.0} |
1593341485433 | {name=Accept, value=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8} |
struct
カラム名. オブジェクト名という指定で値を取り出せる。
SELECT
column.object
FROM
table
json
json_extract 演算子を利用する。
SELECT
json_extract(json, '$.limitkey') AS limitkey
FROM
table
Athena クエリ
ratebasedrulelist
このリクエストで動作したレートベースのルールのリスト。
`ratebasedrulelist` array<
struct<
ratebasedruleid:string,
limitkey:string,
maxrateallowed:int
>
>,
Athena で SELECT * で引っかかるログサンプル。
[{ratebasedruleid=arn:aws:wafv2:ap-northeast-1:111111111111_MANAGED:regional/ipset/e47a0358-3c11-4794-bbd8-7933f451cdcc_4b8cf1f7-0805-4145-81dc-39425bca4b92_IPV4/4b8cf1f7-0805-4145-81dc-39425bca4b92, limitkey=IP, maxrateallowed=100}]
クエリ
SELECT
ratebasedrules,
ratebasedrules.limitkey,
ratebasedrules.maxrateallowed,
ratebasedrules.ratebasedruleid
FROM
waflog,
UNNEST(ratebasedrulelist) t(ratebasedrules)
結果。
ratebasedrules | limitkey | maxrateallowed | ratebasedruleid |
{ratebasedruleid=arn:aws:wafv2:ap-northeast-1:111111111111_MANAGED:regional/ipset/e47a0358-3c11-4794-bbd8-7933f451cdcc_4b8cf1f7-0805-4145-81dc-39425bca4b92_IPV4/4b8cf1f7-0805-4145-81dc-39425bca4b92, limitkey=IP, maxrateallowed=100} | IP | 100 | arn:aws:wafv2:ap-northeast-1:1111111111111_MANAGED:regional/ipset/e47a0358-3c11-4794-bbd8-7933f451cdcc_4b8cf1f7-0805-4145-81dc-39425bca4b92_IPV4/4b8cf1f7-0805-4145-81dc-39425bca4b92 |
nonterminatingmatchingrules
このリクエストにカウント(検知)したルールグループ内の終了しないルールのリスト。
`nonterminatingmatchingrules` array<
struct<
ruleid:string,
action:string
>
>,
Athena で SELECT * で引っかかるログサンプル。
[{"action":"COUNT","ruleid":"AWSManagedRulesAnonymousIpList"}]
クエリ。
SELECT
nontermrules,
nontermrules.ruleid,
nontermrules.action
FROM
waflog,
UNNEST(nonterminatingmatchingrules) t(nontermrules)
結果
nontermrules | ruleid | action |
{“action”:”COUNT”,”ruleid”:”AWSManagedRulesSQLiRuleSet”} | “AWSManagedRulesSQLiRuleSet” | “COUNT” |
terminatingrulematchdetails
リクエストに一致した終了ルールに関する詳細情報。
`terminatingrulematchdetails` array<
struct<
conditiontype:string,
location:string,
matcheddata:array<string>
>
>,
Athena で SELECT * で引っかかるログサンプル。
[{conditiontype=SQL_INJECTION, location=BODY, matcheddata=[999, and, 1, =, 1]}]
クエリ
SELECT
ruleMatchDetails,
ruleMatchDetails.conditionType,
ruleMatchDetails.location,
ruleMatchDetails.matchedData
FROM
waflog,
UNNEST(terminatingRuleMatchDetails) t(ruleMatchDetails)
結果。
ruleMatchDetails | conditionType | location | matchedData |
{conditiontype=SQL_INJECTION, location=BODY, matcheddata=[999, and, 1, =, 1]} | SQL_INJECTION | BODY | [999, and, 1, =, 1] |
rulegrouplist
リクエストで動作したルールグループのリスト。
公式ドキュメントで定義されていた構造体が下記。
この定義では rulegrouplist 内のカラムを Athena で指定できない。
`rulegrouplist` array<string>,
クラスメソッドの記事で紹介されていた構造体が下記。
これなら rulegrouplist 内のネストされた値を Athena で指定できる。(Glueで定義する)。
`rulegrouplist` array<
struct<
ruleGroupId: string,
terminatingRule:
struct<
ruleId: string,
action: string
>,
nonTerminatingMatchingRules: array<
struct<
action: string,
ruleId: string
>
>,
excludedRules: array<
struct<
exclusionType: string,
ruleId: string
>
>
>
>,
Athena で SELECT * で引っかかるログサンプル。
[{rulegroupid=AWS#AWSManagedRulesCommonRuleSet, terminatingrule=null, nonterminatingmatchingrules=[], excludedrules=null}, {rulegroupid=AWS#AWSManagedRulesKnownBadInputsRuleSet, terminatingrule=null, nonterminatingmatchingrules=[], excludedrules=null}, {rulegroupid=AWS#AWSManagedRulesAmazonIpReputationList, terminatingrule=null, nonterminatingmatchingrules=[], excludedrules=null}, {rulegroupid=AWS#AWSManagedRulesAnonymousIpList, terminatingrule=null, nonterminatingmatchingrules=[], excludedrules=null}, {rulegroupid=AWS#AWSManagedRulesSQLiRuleSet, terminatingrule={ruleid=SQLi_BODY, action=BLOCK}, nonterminatingmatchingrules=[], excludedrules=null}]
クエリ
SELECT
groupList,
groupList.rulegroupid,
groupList.terminatingrule,
groupList.terminatingrule.ruleid,
groupList.terminatingrule.action,
groupList.nonterminatingmatchingrules,
groupList.excludedrules
FROM
waflog,
UNNEST(ruleGroupList) t(groupList)
結果
groupList | rulegroupid | terminatingRule | ruleId | action |
{rulegroupid=AWS#AWSManagedRulesSQLiRuleSet, terminatingrule={ruleid=SQLi_BODY, action=BLOCK}, nonterminatingmatchingrules=[], excludedrules=null} | AWS#AWSManagedRulesSQLiRuleSet | {ruleid=SQLi_BODY, action=BLOCK} | SQLi_BODY | BLOCK |
httprequest
リクエストに関するメタデータ。headers以外は普通に取れる。
`httprequest` struct<
clientip:string,
country:string,
headers:array<
struct<
name:string,
value:string
>
>,
uri:string,
args:string,
httpversion:string,
httpmethod:string,
requestid:string
>
Athena で SELECT * で引っかかるログサンプル。
{clientip=126.161.124.35, country=JP, headers=[{name=Host, value=test-security-aws.net}, {name=Pragma, value=no-cache}, {name=Cache-Control, value=no-cache}, {name=User-Agent, value=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36}, {name=Origin, value=https://test-security-aws.net}, {name=Sec-WebSocket-Version, value=13}, {name=Accept-Encoding, value=gzip, deflate, br}, {name=Accept-Language, value=ja,en-US;q=0.9,en;q=0.8}, {name=Cookie, value=language=en; welcomebanner_status=dismiss; continueCode=E3OzQenePWoj4zk293aRX8KbBNYEAo9GL5qO1ZDwp6JyVxgQMmrlv7npKLVy; cookieconsent_status=dismiss; io=qAOYC_-1DVlUIRoFAHZ7}, {name=Sec-WebSocket-Key, value=pUT8hICo89P4LYUOaPbR5g==}, {name=Sec-WebSocket-Extensions, value=permessage-deflate; client_max_window_bits}], uri=/socket.io/, args=EIO=3&transport=websocket&sid=qAOYC_-1DVlUIRoFAHZ7, httpversion=HTTP/1.1, httpmethod=GET, requestid=null}
クエリ
SELECT
httprequest,
httprequest.clientip,
httprequest.country,
httprequest.uri,
httprequest.args,
httprequest.httpMethod,
httprequest.httpVersion
FROM
waflog
結果。
clientip | country | uri | args | httpMethod | httpVersion |
126.161.124.35 | JP | /socket.io/ | EIO=3&transport=polling&t=NAf3WRS | GET | HTTP/2.0 |
httprequest hearder
hearder が難しい。データが存在したりしなかったりするため、name, value の構造体となっている。
ざっくり heaer にどんな値が入っているか確認するクエリ。
SELECT
header
FROM
waflog,
UNNEST(httprequest.headers) t(header)
UserAgent を取得したい場合。
SELECT
header,
header.value
FROM
waflog,
UNNEST(httprequest.headers) t(header)
WHERE
header.name = 'user-agent'
検知モードで動かしているときに検知したリクエストログだけ取り出したい
いずれかのルールに検知されたログを取り出したい。
nonterminatingmatchingrules というカラムを利用する。こいつはリクエストがルールに一致するが終了しない、つまり検知(COUNT)したルールのリスト。
以下の条件に当てはまるもの。
- nonterminatingmatchingrules が存在する
- nonterminatingmatchingrules の action が COUNT
nontermrules.action に COUNT 以外の値が入るのかは疑問だが、WHERE で絞り込みを付けておく。
SELECT
terminatingruleid,
action,
nontermrules
FROM
waflog,
UNNEST(nonterminatingmatchingrules) t(nontermrules)
WHERE
nontermrules.action = 'COUNT'
コメント