pikesaku’s blog

個人的な勉強メモです。記載内容について一切の責任は持ちません。

AWS S3のアクセス制御設定について

以下3種類あり

種類 説明
ACL バケット・オブジェクト単位で設定可能
BucketPolicy PolicyDocument利用のため、柔軟な設定が可能。オブジェクト単位不向き、バケット単位向き。
IAM PolicyDocument利用のため、柔軟な設定が可能。BukcetPolicyと違いIAMユーザー単位で制御が可能

 
参考URLから引用

f:id:pikesaku:20181210002028p:plain

クロスサイトスクリプティングについてメモ

いつまでたっても覚えない。メモしよう
 

仕組み

説明の前提条件

 
攻撃者のサイトのURL
http://cracker/index.html
 
index.htmlの内容

 
ターゲットサイト
http://target/
入力データの情報を、そのままブラウザに返してしまう。
例) 存在しないコンテンツa.htmlにアクセスがあった場合に、"a.htmlは存在しません"エラーを返す等(a.htmlは入力データで、エラーメッセージに利用してる)
 
 

攻撃の流れ

 
1. 騙される人が攻撃者のサイトにアクセス

2. リンクをクリック

3. ターゲットサイトに、

は存在しない。
ターゲットサイトがブラウザに、"ファイル名(スクリプト部分)は存在しません"的なエラーメッセージを返す。
このファイル名部分が、スクリプトのため、ブラウザで実行されてしまう。
  

ポイント

ターゲットサイトがユーザーからの入力情報をレスポンスデータとして出力する場合に影響あり。
ただ、これ自体は悪ではない。
スクリプト文字列が含まれてるのに、そのまま返すのが問題。
出力しないよう入力チェックしてないのが問題。
クエリ文字列にスクリプトを含めるのも可能。

追加

https://sites.google.com/site/ryouheivision/home/qing-baosekyuriti/kurosusaitosukuriputingu-xss-no-zhong-lei

上記サイト非常に良い!
攻撃説明コードつき!

反射型クロスサイトスクリプティング(Reflected XSS)
→このページで説明した内容

格納型クロスサイトスクリプティング(Stored XSS)
→登録した内容がDBに格納され、何かしらの操作で、ユーザーに表示されるwebサイトが対象

登録時の入力値チェック、表示時のサニタイズに不備があり発生。

攻撃者は登録しておけばいいだけ。

DOM Based XSS
javaスクリプト自体の不具合を悪用。スクリプトがブラウザに出力する機能の不具合を悪用し、悪意のある別のスクリプトを出力し、ブラウザに実行させる。
おそらく、元のスクリプトが引数を受け付けるのが前提で、不正な引数つきのリンクを何かしらの方法で実行させる。

ログ画像化(線表示2)

pikesaku.hatenablog.com
上記との違いはクエリ文字列の情報もURLと同じ方法で反映した点。
赤線がURL
青線がクエリ文字列
 
クエリ文字列のキー文字列をハッシュ化。バリュー文字列は無視。
クエリが複数ある場合は、キー文字列をソートし&で繋げたものをハッシュ化。
 
例)以下ログの場合(クエリ1つ)

192.168.56.1 - - [08/Jul/2018:12:33:46 +0900] "GET /wp/wp-login.php?action=lostpassword HTTP/1.1" 200 2049 "http://192.168.56.101/wp/wp-login.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36"

"action"をハッシュ化
 
例)以下ログの場合(クエリ2つ)

192.168.56.1 - - [08/Jul/2018:12:33:47 +0900] "GET /wp/wp-login.php?action=lostpassword&hoge=fuga HTTP/1.1" 200 2049 "http://192.168.56.101/wp/wp-login.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36"

"action&hoge"をハッシュ化
 
 

コード

apache_log_trans_to_image.py

# -*- coding:utf-8 -*-
import argparse
import apache_log_trans_to_image_lib as alti

parser = argparse.ArgumentParser(description='apache log to graph')
parser.add_argument('log', help='log file', type=argparse.FileType('r'))
parser.add_argument('--hash', help='define hash type', type=str, choices=['md5', 'sha256'], default='md5')
parser.add_argument('--unit', help='unit of urls', type=int, default=10)
args = parser.parse_args()


if __name__ == '__main__':
    data = alti.get_data(args.log, args.unit)
    data = alti.change_data_for_graph(data, args.hash)
    alti.output_graph(data, args.unit, args.hash)

apache_log_trans_to_image_lib.py

# -*- coding:utf-8 -*-


def get_data(log, unit):
    import apache_log_parser
    import itertools

    def chk_key(line):
        required_key = ('request_url_path', 'remote_host', 'request_url_query_dict')
        for key in required_key:
            if not key in line:
                return False
        return True

    def chk_ext(line):
        request_url_path = line['request_url_path']
        except_ext = ('gif', 'jpg', 'png', 'ico', 'css', 'js', 'woff', 'ttf', 'svg')
        ext = request_url_path.split('.')[-1].lower()
        if ext in except_ext:
            return False
        return True

    data = dict()
    parser = apache_log_parser.make_parser('%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"')

    for line in log:
        line = line.strip()
        line = parser(line)
        if not chk_key(line):
            continue
        if not chk_ext(line):
            continue
        host = line['remote_host']
        request_url_path = line['request_url_path']
        request_url_query = '&'.join(sorted(list(line['request_url_query_dict'].keys())))

        if host in data:
            data[host].append([request_url_path, request_url_query])
        else:
            data[host] = [[request_url_path, request_url_query]]

    for host,request_data_list in data.items():
        request_data_list = list(itertools.zip_longest(*[iter(request_data_list)]*unit))
        request_data_list[-1] = [request_data for request_data in request_data_list[-1] if request_data is not None]
        data[host] = request_data_list
    return data


def change_data_for_graph(data, h):
    changed_data = dict()
    for host,request_data_list in data.items():
        units_of_nums = list()
        for part in request_data_list:
            nums = list()
            for line in part:
                request_url_num = trans_str_to_num(line[0], h)
                request_query_num = trans_str_to_num(line[1], h)
                nums.append([request_url_num, request_query_num])
            units_of_nums.append(nums)
        changed_data[host] = units_of_nums
    return changed_data


def trans_str_to_num(s, h):
    import hashlib
    import re
    s = s.encode('UTF-8')
    if h == 'md5':
        m = hashlib.md5()
    if h == 'sha256':
        m = hashlib.sha256()
    m.update(s)
    h = m.hexdigest()
    # hは16進数32桁
    # 4桁づつ、リストにする。
    # https://stackoverflow.com/questions/13673060/split-string-into-strings-by-length
    nums = [ int(i, 16) for i in re.split('(.{4})', h)[1::2] ]
    return nums


def output_graph(data, unit, h):
    import numpy as np
    import matplotlib.pyplot as plt

    if h == 'md5':
        xtick = 8
    if h == 'sha256':
        xtick = 16

    # https://stackoverflow.com/questions/24943991/change-grid-interval-and-specify-tick-labels-in-matplotlib
    for ip,units_of_nums in data.items():
        seq = 0
        for unit_of_nums in units_of_nums:
            url_nums = list()
            query_nums = list()
            for num in unit_of_nums:
                url_nums.extend(num[0])
                query_nums.extend(num[1])
            url_x, url_y = (range(len(url_nums)), url_nums)
            query_x, query_y = (range(len(query_nums)), query_nums)
            fig, ax = plt.subplots()
            major_xticks = np.arange(0, unit*xtick+1, xtick)
            minor_xticks = np.arange(0, unit*xtick+1, 1)
            major_yticks = np.arange(0, 65535+1, 10000)
            minor_yticks = np.arange(0, 65535+1, 1000)

            ax.set_xticks(major_xticks)
            ax.set_xticks(minor_xticks, minor=True)
            ax.set_yticks(major_yticks)
            ax.set_yticks(minor_yticks, minor=True)
            ax.grid(which='both')
            ax.grid(which='minor', alpha=0.2)
            ax.grid(which='major', alpha=0.8)
            plt.plot(url_x, url_y, color='red', lw=0.5)
            plt.plot(query_x, query_y, color='blue', lw=0.5)

            # 目盛を表示する場合、以下をコメントアウト
            plt.yticks(color='None')
            plt.xticks(color='None')
            #

            plt.xlim([0, unit*xtick])
            plt.ylim([0, 65535])
            plt.savefig(ip + '_' + str(seq) + '.png')
            plt.close()
            seq += 1

アウトプット

全て--unitはデフォルトの10の場合

以下ログの場合

192.168.56.1 - - [08/Jul/2018:12:33:46 +0900] "GET /wp/wp-login.php?action=lostpassword HTTP/1.1" 200 2049 "http://192.168.56.101/wp/wp-login.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36"

f:id:pikesaku:20181125013753p:plain
 

以下ログの場合

192.168.56.1 - - [08/Jul/2018:12:33:46 +0900] "GET /wp/wp-login.php?action=lostpassword HTTP/1.1" 200 2049 "http://192.168.56.101/wp/wp-login.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36"
192.168.56.1 - - [08/Jul/2018:12:33:47 +0900] "GET /wp/wp-login.php?action=lostpassword HTTP/1.1" 200 2049 "http://192.168.56.101/wp/wp-login.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36"

f:id:pikesaku:20181125013845p:plain
 

以下ログの場合

192.168.56.1 - - [08/Jul/2018:12:33:46 +0900] "GET /wp/wp-login.php?action=lostpassword HTTP/1.1" 200 2049 "http://192.168.56.101/wp/wp-login.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36"
192.168.56.1 - - [08/Jul/2018:12:33:47 +0900] "GET /wp/wp-login.php?action=lostpassword HTTP/1.1" 200 2049 "http://192.168.56.101/wp/wp-login.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36"
192.168.56.1 - - [08/Jul/2018:12:33:47 +0900] "GET /wp/wp-login.php?action=lostpassword&hoge=fuga HTTP/1.1" 200 2049 "http://192.168.56.101/wp/wp-login.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36"

f:id:pikesaku:20181125013914p:plain

AWS S3 SDKメモ(書き掛け)

メモ

  • 認証はAWSアカウント、IAMユーザーで行う。認証情報を記載したファイル生成が必要。
  • 一時的な認証情報の利用も可能。(認証が有効な時間が限定される)

 
 

Python SDK(boto3)メモ

AMIで利用時もboto3インストール必要

# sudo pip install boto3

 
 

サンプルコード

import boto3
import botocore
import random, string

# Create an S3 client
s3 = boto3.client('s3')

# Call S3 to list current buckets
response = s3.list_buckets()

# Get a list of all bucket names from the response
buckets = [bucket['Name'] for bucket in response['Buckets']]

# Create bucket name
bn = ''.join([random.choice(string.ascii_letters + string.digits).lower() for i in range(16)])

bn = 'vz6g2zdx3ry3dpiv'

# Check whether my-bucket exists
if bn in buckets:
    print(bn + 'already exists!')
else:
    s3.create_bucket(Bucket=bn)

# Upload file to bucket
fn = '/etc/hosts'
fn_k = fn.split('/')[-1]
s3.upload_file(fn, bn, fn_k)

# Download file from bucket
try:
    s3.download_file(bn, fn_k, 'downloaded_file')
except botocore.exceptions.ClientError as e:
    if e.response['Error']['Code'] == "404":
        print("The object does not exist.")
    else:
        raise


API種類

低レベルAPIと高レベルAPIがあり。
Clientは低レベルAPI
Resourcesは高レベルAPI
Boto3 で S3 のオブジェクトを操作する(高レベルAPIと低レベルAPI) - Qiita
 
 

CORS

Cross-Origin Resource Sharing (CORS) の略
別Webサイトからの呼び出しの場合でも、許可する設定。
Boto3でコンテンツに対し設定可能。コードは以下参照。
Configuring Amazon S3 Buckets — Boto 3 Docs 1.9.62 documentation
呼び出し元URLやメソッドなどを定義
 
 

CORS設定の取得コード

import boto3

# Create an S3 client
s3 = boto3.client('s3')

# Call S3 to get CORS configuration for selected bucket
result = s3.get_bucket_cors(Bucket='my-bucket')

設定がない場合、以下エラーになる。

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/botocore/client.py", line 320, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/usr/local/lib/python2.7/site-packages/botocore/client.py", line 623, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (NoSuchCORSConfiguration) when calling the GetBucketCors operation: The CORS configuration does not exist

 
 

CORS設定の取得コード

import boto3

# Create an S3 client
s3 = boto3.client('s3')

# Create the CORS configuration
cors_configuration = {
    'CORSRules': [{
        'AllowedHeaders': ['Authorization'],
        'AllowedMethods': ['GET', 'PUT'],
        'AllowedOrigins': ['*'],
        'ExposeHeaders': ['GET', 'PUT'],
        'MaxAgeSeconds': 3000
    }]
}

# Set the new CORS configuration on the selected bucket
s3.put_bucket_cors(Bucket='my-bucket', CORSConfiguration=cors_configuration)

 
 

ACL設定の取得コード

import boto3

# Create an S3 client
s3 = boto3.client('s3')

# Call to S3 to retrieve the policy for the given bucket
result = s3.get_bucket_acl(Bucket='my-bucket')
print(result)

続き
Amazon S3 Examples — Boto 3 Docs 1.9.62 documentation
のサンプルコードの
Working with Amazon S3 Bucket Policies

アクセスコントロールリスト (ACL) の概要 - Amazon Simple Storage Service
を見て、ACL詳細を調査する。

WebAPIについて

種類は大きく2つあり

SOAP

  • SimpleObject Access Protocolの略
  • リクエスト/レスポンスともにXML
  • XML形式(SOAPメッセージ)が規格で定められており、データをやりとりするプロトコルの意味もあり。
  • WSDLはWeb Services Description Languageの略でWebサービスインタフェース記述言語。SOAPメッセージを独自に定義し、API利用者、API提供者双方で保持することで、SOAPメッセージのやりとりが可能
  • サービス指向・URLは操作に対応

 例) ユーザー削除の場合 /deleteUser
 

REST

  • Representational State Transferの略
  • あくまで設計思想でありSOAPのような規約ではなし。RESTfulは設計思想に忠実であることを示す。
  • セッション・状態管理はしない。やりとりは、それ自体で完結。(完結せず次のリクエストも必要な場合、セッション・状態管理が必要)
  • リクエストはHTTPメソッド、レスポンスはXMLJSONなど。規約はなし。
  • リソース指向・URLは操作対象に対応

 例) ユーザー削除の場合 /Userに対しHTTP DELETEメソッド実行

AWS CLI S3コマンドinclude・excludeメモ

ポイント

・順番に評価され、より後で評価されたもので決定。この点を考慮しinclude・exclude両方指定した方が直感的かも。
 例1) --exclude "*" --include "*.txt" →txt拡張子だけがマッチ
 例2) --include "*.txt" --exclude "*" →全てマッチ
・*は全てにマッチ
・?は1文字にマッチ
・[文字集合]、[^文字集合]で文字クラス(?)的な指定が可能
 

aws s3 helpより抜粋

   Use of Exclude and Include Filters
       Currently, there is no support for the use of UNIX style wildcards in a
       command's  path  arguments.   However,  most  commands  have  --exclude
       "<value>" and --include  "<value>"  parameters  that  can  achieve  the
       desired  result.   These  parameters perform pattern matching to either
       exclude or include a particular file or object.  The following  pattern
       symbols are supported.

          o *: Matches everything

          o ?: Matches any single character

          o [sequence]: Matches any character in sequence

          o [!sequence]: Matches any character not in sequence

       Any  number of these parameters can be passed to a command.  You can do
       this by providing an --exclude or --include  argument  multiple  times,
       e.g.   --include  "*.txt"  --include  "*.png".  When there are multiple
       filters, the rule is the filters that appear later in the command  take
       precedence  over filters that appear earlier in the command.  For exam-
       ple, if the filter parameters passed to the command were

          --exclude "*" --include "*.txt"

       All files will be excluded from the command  except  for  files  ending
       with  .txt   However, if the order of the filter parameters was changed
       to

          --include "*.txt" --exclude "*"

       All files will be excluded from the command.