クロスサイトスクリプティングについてメモ
いつまでたっても覚えない。メモしよう
仕組み
説明の前提条件
攻撃者のサイトのURL
http://cracker/index.html
index.htmlの内容
ターゲットサイト
http://target/
入力データの情報を、そのままブラウザに返してしまう。
例) 存在しないコンテンツa.htmlにアクセスがあった場合に、"a.htmlは存在しません"エラーを返す等(a.htmlは入力データで、エラーメッセージに利用してる)
攻撃の流れ
1. 騙される人が攻撃者のサイトにアクセス
2. リンクをクリック
3. ターゲットサイトに、
は存在しない。
ターゲットサイトがブラウザに、"ファイル名(スクリプト部分)は存在しません"的なエラーメッセージを返す。
このファイル名部分が、スクリプトのため、ブラウザで実行されてしまう。
ポイント
ターゲットサイトがユーザーからの入力情報をレスポンスデータとして出力する場合に影響あり。
ただ、これ自体は悪ではない。
スクリプト文字列が含まれてるのに、そのまま返すのが問題。
出力しないよう入力チェックしてないのが問題。
クエリ文字列にスクリプトを含めるのも可能。
追加
上記サイト非常に良い!
攻撃説明コードつき!
反射型クロスサイトスクリプティング(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"
以下ログの場合
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: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"
AWS S3 SDKメモ(書き掛け)
参考
AWS SDK を使用したリクエストの実行 - Amazon Simple Storage Service
→AWS SDK説明
Python | チュートリアル、API、SDK、ドキュメント | AWS 開発者センター
→AWS SDK Python概要
aws-doc-sdk-examples/python/example_code at master · awsdocs/aws-doc-sdk-examples · GitHub
→AWS SDK Pythonサンプルコード
Amazon S3 Examples — Boto 3 Docs 1.9.62 documentation
S3 — Boto 3 Docs 1.9.62 documentation
→AWS SDK Python S3解説
サンプルコード
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について
参考
0からREST APIについて調べてみた - Qiita
今さら聞けないWebAPIの実装方式RESTとSOAPの違い - Qiita
REST - SOAP とRESTの違いについてわかりやすく教えていただけませんでしょうか?|teratail
RESTとは何か、SOAPとは何か。 - 感謝のプログラミング
http://techtarget.itmedia.co.jp/tt/news/1806/13/news01.html
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.