


user3에 인라인 정책(createBucket) 추가
12) S3-Support 사용자 그룹에 EC2 인스턴스를 실행하고, 중지할 수 있도록 권한을 추가
1) aws configure 명령을 이용해서 Access Key를 등록





GetUserGroups.py
import subprocess
# 매개변수(cmd)로 전달된 쉘 명령어를 실행하고 그 결과를 반환하는 함수
def execute_command(cmd):
try:
return subprocess.run(cmd, capture_output=True, shell=True, encoding="cp949")
except Exception as e:
print(f"예외 발생 : {e}")
# IAM 사용자 목록을 조회
result = execute_command("aws iam list-users")
if result.stderr:
print(result.stderr)
else:
import json
user_list = []
users = json.loads(result.stdout)
for user in users["Users"]:
# 사용자 이름을 이용하여 사용자가 소속한 그룹을 조회
# aws iam list-groups-for-user --user-name 사용자이름
groups = execute_command(f"aws iam list-groups-for-user --user-name {user["UserName"]}")
groups = json.loads(groups.stdout)
if len(groups["Groups"]) == 0:
user["GroupName"] = ""
else:
temp = []
for group in groups["Groups"]:
temp.append(group["GroupName"])
user["GroupName"] = ", ".join(temp)
# GroupName이 없으면 "취약", 있으면 "양호"로 표시
if user["GroupName"] == "":
user["state"] = "취약"
else:
user["state"] = "양호"
user_list.append(user)
# 사용자 정보, 그룹 이름, 판정 결과를 출력
max_length = 0
for user in user_list:
if max_length < len(user["GroupName"]):
max_length = len(user["GroupName"])
for user in user_list:
print(f"""\
{user['UserName']:<20}\t\
{user['UserId']}\t\
{user['GroupName']:<{max_length}}\t\
{user['state']}\
""")

import boto3
# boto3 IAM 클라이언트 생성
client = boto3.client("iam")
# IAM 사용자 목록 조회
response = client.list_users()
# 결과 출력
for user in response["Users"]:
print(f"UserName: {user["UserName"]}")
print(f"UserId: {user["UserId"]}")
print(f"Arn: {user["Arn"]}")
print(f"CreateDate: {user["CreateDate"]}")
print(f"PasswordLastUsed: {user["PasswordLastUsed"]}")
print()

GetUserList.py
import boto3
from prettytable import PrettyTable
# boto3 IAM 클라이언트 생성
client = boto3.client("iam")
# IAM 사용자 목록 조회
response = client.list_users()
# PrettyTable 객체 생성
table = PrettyTable()
table.field_names = ["UserName", "UserId", "Arn", "CreateDate", "PasswordLastUsed"]
# 결과 출력
for user in response["Users"]:
table.add_row([
user["UserName"],
user["UserId"],
user["Arn"],
user["CreateDate"],
user.get("PasswordLastUsed", "N/A"),
])
print(table)

GetUserList.py
import boto3
import datetime
from prettytable import PrettyTable
def get_user_list():
# boto3 IAM 클라이언트 생성
client = boto3.client("iam")
# IAM 사용자 목록 조회
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam/client/list_users.html
response = client.list_users()
# PrettyTable 객체 생성
table = PrettyTable()
table.field_names = ["UserName", "UserId", "Arn", "CreateDate", "PasswordLastUsed"]
# 결과 출력
for user in response["Users"]:
table.add_row([
user["UserName"],
user["UserId"],
user["Arn"],
user["CreateDate"],
user.get("PasswordLastUsed", "N/A"), # PasswordLastUsed가 없을 수 있음
])
print(table)
# IAM 사용자에게 발급된 액세스 키 조회
def get_user_access_key():
client = boto3.client("iam")
res_users = client.list_users()
for user in res_users["Users"]:
# 사용자별로 액세스 키 목록을 조회
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam/client/list_access_keys.html
res_accesskeys = client.list_access_keys(UserName=user["UserName"])
for accesskey in res_accesskeys["AccessKeyMetadata"]:
print(f"UserName: {accesskey['UserName']}")
print(f"AccessKeyId: {accesskey['AccessKeyId']}")
print(f"Status: {accesskey['Status']}")
print(f"CreateDate: {accesskey['CreateDate']}")
# CreateDate가 22시간 보다 크면 판결 결과를 "취약"으로 표시 (그렇지 않은 "양호"로 표시)
if datetime.datetime.now() - accesskey["CreateDate"].replace(tzinfo=None) > datetime.timedelta(hours=30):
print("판결 결과: 취약")
else:
print("판결 결과: 양호")
print()
# __name__ 변수에는 직접 실행될 때는 "__main__"을 값으로 가지고,
# 모듈로 import될 때는 모듈 이름(파이썬 파일 이름, 여기에서는 GetUserList)을 값으로 가짐
# 직접 실행될 때만 get_user_list() 함수를 호출하도록 제한하는 코드
if __name__ == "__main__":
# get_user_list()
get_user_access_key()

checker.py
from account_checker import get_user_access_key, get_user_list
def show_menu():
print("*" * 20)
print("[1] IAM 사용자 목록 조회")
print("[2] IAM 사용자 액세스 키 조회")
print("[X] 종료")
print("*" * 20)
print()
def main():
while True:
show_menu()
menu = input("메뉴 선택: ")
if menu == "1":
get_user_list()
elif menu == "2":
get_user_access_key()
elif menu == "X" or menu == "x":
print("프로그램을 종료합니다.")
break
else:
print("잘못된 메뉴 선택입니다.")
if __name__ == "__main__":
main()


account_checker.py
import boto3
import datetime
from prettytable import PrettyTable
client = boto3.client("iam")
def get_user_list():
response = client.list_users()
return response["Users"]
def print_list(items):
if len(items) == 0:
print("출력할 정보가 없습니다.")
return
table = PrettyTable()
table.field_names = items[0].keys()
for item in items:
table.add_row(item.values())
print(table)
def get_user_access_key(users):
for user in users:
res_accesskeys = client.list_access_keys(UserName=user["UserName"])
for accesskey in res_accesskeys["AccessKeyMetadata"]:
print(f"UserName: {accesskey['UserName']}")
print(f"AccessKeyId: {accesskey['AccessKeyId']}")
print(f"Status: {accesskey['Status']}")
print(f"CreateDate: {accesskey['CreateDate']}")
# CreateDate가 22시간 보다 크면 판결 결과를 "취약"으로 표시 (그렇지 않은 "양호"로 표시)
if datetime.datetime.now() - accesskey["CreateDate"].replace(tzinfo=None) > datetime.timedelta(hours=30):
print("판결 결과: 취약")
else:
print("판결 결과: 양호")
print()
def get_user_group_info(users):
results = []
for user in users:
res = client.list_groups_for_user(UserName=user["UserName"])
if len(res["Groups"]) == 0:
user["GroupName"] = ""
user["state"] = "취약"
else:
group_names = [group["GroupName"] for group in res["Groups"]]
user["GroupName"] = ", ".join(group_names)
user["state"] = "양호"
results.append(user)
return results
if __name__ == "__main__":
users = get_user_list()
print_list(users)
get_user_access_key(users)
results = get_user_group_info(users)
print_list(results)
checker.py
from account_checker import get_user_access_key, get_user_list, **get_user_group_info, print_list**
def show_menu():
print("*" * 20)
print("[1] IAM 사용자 목록 조회")
print("[2] IAM 사용자 액세스 키 조회")
print("[3] IAM 사용자 그룹 정보 조회")
print("[X] 종료")
print("*" * 20)
print()

C:\Users\r2com\Downloads>
ssh -i Rookies009_MyKeyPair.pem ec2-user@3.87.248.229
-> 3.87.248.229=퍼블릭 IPv4 주소

import boto3
client = boto3.client("ec2")
def list_instances_with_key_pairs():
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_instances.html
instances = client.describe_instances()
for reservation in instances["Reservations"]:
for instance in reservation["Instances"]:
print(f"InstanceId: {instance['InstanceId']}")
print(f"PublicIpAddress: {instance['PublicIpAddress']}")
print(f"PrivateIpAddress: {instance['PrivateIpAddress']}")
if "KeyName" in instance:
print(f"KeyName: {instance['KeyName']}")
print(f"판정 결과: 양호")
else:
print(f"판정 결과: 취약")
print()
if __name__ == "__main__":
list_instances_with_key_pairs()

checker.py
from account_checker import get_user_access_key, get_user_list, get_user_group_info, print_list
from ec2_checker import list_instances_with_key_pairs
def show_menu():
print("*" * 20)
print("[1] IAM 사용자 목록 조회")
print("[2] IAM 사용자 액세스 키 조회")
print("[3] IAM 사용자 그룹 정보 조회")
print("[4] EC2 인스턴스 키 페어 체크")
print("[X] 종료")
print("*" * 20)
print()
def main():
while True:
show_menu()
menu = input("메뉴 선택: ")
if menu == "1":
print_list(get_user_list())
elif menu == "2":
get_user_access_key(get_user_list())
elif menu == "3":
print_list(get_user_group_info(get_user_list()))
elif menu == "4":
list_instances_with_key_pairs()
elif menu == "X" or menu == "x":
print("프로그램을 종료합니다.")
break
else:
print("잘못된 메뉴 선택입니다.")

ec2_checker.py
from s3_checker import check_s3_public_access, list_buckets, check_object_exists
def get_key_pairs():
key_pairs = client.describe_key_pairs()
return key_pairs["KeyPairs"]
def check_key_pair_s3_storage():
key_pairs = get_key_pairs()
buckets = list_buckets()
for key_pair in key_pairs:
key_name = key_pair["KeyName"]
print(f"KeyPair: {key_name}")
print("=" * 30)
for bucket in buckets:
bucket_name = bucket["Name"]
print(f"Bucket: {bucket_name}")
status = "버킷에 키가 저장되어 있지 않음 >> 판정 불가"
if check_object_exists(bucket_name, key_name+".pem"):
if check_s3_public_access(bucket_name):
status = "퍼블릭 버킷에 저장 >> 취약"
else:
status = "프라이빗 버킷에 저장 >> 양호"
print(f"판정 결과: {status}")
print()
if __name__ == "__main__":
# list_instances_with_key_pairs()
check_key_pair_s3_storage()
s3_checker.py
import boto3
from botocore.exceptions import ClientError
client = boto3.client('s3')
def list_buckets():
response = client.list_buckets()
return response["Buckets"]
def check_s3_public_access(bucket_name):
# 1. 버킷 정책 확인
try:
policy_status = client.get_bucket_policy_status(Bucket=bucket_name)
is_public = policy_status["PolicyStatus"]["IsPublic"]
if is_public:
# print(f"버킷 '{bucket_name}'은 퍼블릭 접근 가능합니다. (정책 기반)")
return True
except ClientError as e:
if e.response['Error']['Code'] == "NoSuchBucketPolicy":
# print(f"버킷 '{bucket_name}'에는 정책이 없습니다. 정책 기반 퍼블릭 접근 확인 불가.")
pass
else:
# print(f"버킷 정책 확인 중 오류 발생: {e}")
pass
# 2. ACL 확인
try:
acl = client.get_bucket_acl(Bucket=bucket_name)
for grant in acl['Grants']:
if grant['Grantee'].get('URI') == 'http://acs.amazonaws.com/groups/global/AllUsers':
if grant['Permission'] in ['READ', 'WRITE', 'FULL_CONTROL']:
# print(f"버킷 '{bucket_name}'은 퍼블릭 접근 가능합니다. (ACL 기반)")
return True
except ClientError as e:
print(f"ACL 확인 중 오류 발생: {e}")
# 3. 퍼블릭 접근 불가능으로 간주
# print(f"버킷 '{bucket_name}'은 퍼블릭 접근이 불가능합니다.")
return False
def check_object_exists(bucket_name, object_name):
try:
client.head_object(Bucket=bucket_name, Key=object_name)
return True
except: # S3.Client.exceptions.NoSuchKey
return False
checker.py
from ec2_checker import list_instances_with_key_pairs, check_key_pair_s3_storage
def show_menu():
print("*" * 20)
print("[1] IAM 사용자 목록 조회")
print("[2] IAM 사용자 액세스 키 조회")
print("[3] IAM 사용자 그룹 정보 조회")
print("[4] EC2 인스턴스 키 페어 체크")
print("[5] 퍼블릭 버킷에 키 페어 저장 여부 체크")
print("[X] 종료")
print("*" * 20)
print()
def main():
while True:
show_menu()
menu = input("메뉴 선택: ")
if menu == "1":
print_list(get_user_list())
elif menu == "2":
get_user_access_key(get_user_list())
elif menu == "3":
print_list(get_user_group_info(get_user_list()))
elif menu == "4":
list_instances_with_key_pairs()
elif menu == "5":
check_key_pair_s3_storage()
elif menu == "X" or menu == "x":
print("프로그램을 종료합니다.")
break
else:
print("잘못된 메뉴 선택입니다.")

from flask import Flask
app = Flask(__name__)
# 데이터 샘플
data = [
{"ID": 1, "Name": "홍길동", "age": 20},
{"ID": 2, "Name": "고길동", "age": 40},
{"ID": 3, "Name": "신길동", "age": 60},
]
# /list 요청을 처리하는 함수
@app.route("/list")
def list():
return data
if __name__ == "__main__":
app.run(debug=True)

-> 브라우저로 확인 > not found > html 구성으로 확인
app.py
# /list 요청을 처리하는 함수
@app.route("/list")
def list():
return render_template("list.html", mydata=data)
list.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
width: 50%;
margin: 20px auto;
border-collapse: collapse;
text-align: left;
}
th,
td {
padding: 8px 12px;
border: 1px solid #ddd;
}
th {
background-color: #f4f4f4;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
</style>
</head>
<body>
<h2 style="text-align: center">사용자 목록</h2>
<table>
<header>
<tr>
<th>ID</th>
<th>이름</th>
<th>나이</th>
</tr>
</header>
<tbody>
{% for row in mydata %}
<tr>
<td>{{ row.id }}</td>
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>

list.html
<tbody>
{% for row in mydata %}
<tr {% if row.id % 2==0 %} style="background-color: red" {% endif %}> ⇐ 조건문
<td>[{{ loop.index }}] {{ row.id }} ⇐ 루프의 인덱스 조회
</td>
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
</tr>
{% endfor %}
</tbody>
app.py
import boto3
# boto3를 이용해서 IAM 사용자 정보를 가져와서 반환하는 함수
def get_iam_users():
client = boto3.client('iam')
response = client.list_users()
return response["Users"]
list.html
<body>
<h2 style="text-align: center">사용자 목록</h2>
<table>
<header>
<tr>
<th>ID</th>
<th>사용자명</th>
<th>ARN</th>
<th>생성일시</th>
</tr>
</header>
<tbody>
{% for row in mydata %}
<tr>
<td>{{ row.UserId }}</td>
<td>{{ row.UserName }}</td>
<td>{{ row.Arn }}</td>
<td>{{ row.CreateDate }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>

from myboto3.account_checker import get_user_list, get_user_access_key_data
@app.route("/users/accesskeys")
def users_accesskeys():
data = get_user_access_key_data(get_user_list())
return render_template("users_accesskeys.html", data=data)
account_checker.py
def get_user_access_key_data(users):
results = []
for user in users:
res_accesskeys = client.list_access_keys(UserName=user["UserName"])
for accesskey in res_accesskeys["AccessKeyMetadata"]:
if datetime.datetime.now() - accesskey["CreateDate"].replace(tzinfo=None) > datetime.timedelta(hours=55):
accesskey["State"] = "취약"
else:
accesskey["State"] = "양호"
results.append(accesskey)
return results
users_accesskeys.html
<body>
<h2 style="text-align: center">사용자별 액세스키 목록</h2>
<table>
<header>
<tr>
<th>사용자명</th>
<th>액세스키</th>
<th>생성일시</th>
<th>판정결과</th>
</tr>
</header>
<tbody>
{% for row in data %}
<tr>
<td>{{ row.UserName }}</td>
<td>{{ row.AccessKeyId }}</td>
<td>{{ row.CreateDate }}</td>
<td {% if row.State=="취약" %} style="color: red" {% endif %}>
{{ row.State }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>

table {
/* width: 50%; */
margin: 20px auto;
border-collapse: collapse;
text-align: left;
}
th,
td {
padding: 8px 12px;
border: 1px solid #ddd;
}
th {
background-color: #f4f4f4;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
list.html, users_accesskeys.html 공통
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/static/style.css">
</head>
app.py
@app.route("/user/<username>/accesskeys")
def user_accesskeys(username):
data = get_user_access_key_data([{"UserName": username}])
return render_template("users_accesskeys.html", data=data)
list.html
<tbody>
{% for row in mydata %}
<tr>
<td>{{ row.UserId }}</td>
<td>
<a href="/user/{{row.UserName}}/accesskeys">{{ row.UserName }}</a>
</td>
<td>{{ row.Arn }}</td>
<td>{{ row.CreateDate }}</td>
</tr>
{% endfor %}
</tbody>

<tbody>
{% for row in data %}
<tr>
<td>{{ row.UserName }}</td>
<td>{{ row.AccessKeyId }}</td>
<td>{{ row.CreateDate }}</td>
<td {% if row.State=="취약" %} style="color: red" {% endif %}>
{{ row.State }}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="4"><a href="/list">사용자 목록으로 이동</a></td>
</tr>
</tfoot>

app.py
@app.route("/")
def main():
return render_template("main.html")
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h1>AWS 취약점 점검 서비스</h1>
<h2><a href="/list">IAM 사용자 목록 조회</a></h2>
<h2><a href="/users/accesskeys">사용자별 액세스키 검증</a></h2>
</body>
</html>

<!-- list.html -->
<tfoot>
<tr>
<td colspan="4"><a href="/">메인으로 이동</a></td>
</tr>
</tfoot>
<!-- users_accesskeys.html -->
<tfoot>
<tr>
<td colspan="4">
<a href="/">메인으로 이동</a> |
<a href="/list">사용자 목록으로 이동</a>
</td>
</tr>
</tfoot>
import secrets
secrets.token_hex(32)
account_checker.py
def delete_user_access_key(username, accesskey):
client.delete_access_key(UserName=username, AccessKeyId=accesskey)
app.py
from flask import Flask, render_template, flash, redirect, url_for
from myboto3.account_checker import get_user_list, get_user_access_key_data, delete_user_access_key
app.secret_key = '6e9d2c8e1a5b6c9f8438e1b6c9a5d1e2f7a8b9c1d2e3f4g5h6i7j8k9l0m1n2o3'
@app.route("/user/<username>/accesskey/<accesskey>/delete")
def delete_access_key(username, accesskey):
try:
delete_user_access_key(username, accesskey)
flash(f"액세스키를 정상적으로 삭제했습니다.")
return redirect("/")
except Exception as e:
flash(f"액세스키를 삭제하는데 실패했습니다. {str(e)}")
return redirect(url_for("users_accesskeys"))
main.html, users_accesskeys.html
... (생략) ...
{% with messages = get_flashed_messages() %}
{% for message in messages %}
<script>
alert("{{ message }}");
</script>
{% endfor %}
{% endwith %}
</body>

account_checker.py
def check_all_users_mfa(users):
results = []
for user in users:
res = client.list_mfa_devices(UserName=user["UserName"])
if len(res["MFADevices"]) == 0:
user["MFADevices"] = 0
user["State"] = "취약"
else:
user["MFADevices"] = len(res["MFADevices"])
user["State"] = "양호"
results.append(user)
return results
app.py
from myboto3.account_checker import get_user_list, get_user_access_key_data, delete_user_access_key, check_all_users_mfa
@app.route("/users/mfa/check")
def check_users_mfa():
data = check_all_users_mfa(get_user_list())
return render_template("check_users_mfa.html", data=data)
check_users_mfa.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h2 style="text-align: center">사용자별 MFA 설정 현황</h2>
<table>
<header>
<tr>
<th>사용자ID</th>
<th>사용자명</th>
<th>MFA 설정 여부</th>
<th>판정결과</th>
</tr>
</header>
<tbody>
{% for row in data %}
<tr>
<td>{{ row.UserId }}</td>
<td>{{ row.UserName }}</td>
<td>{{ row.MFADevices }}</td>
<td {% if row.State=="취약" %} style="color: red" {% endif %}>
{{ row.State }}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="4"><a href="/">메인으로 이동</a></td>
</tr>
</tfoot>
</table>
</body>
</html>
main.html
<body>
<h1>AWS 취약점 점검 서비스</h1>
<h2><a href="/list">IAM 사용자 목록 조회</a></h2>
<h2><a href="/users/accesskeys">사용자별 액세스키 검증</a></h2>
<h2><a href="/users/mfa/check">사용자별 MFA 설정 현황</a></h2>
{% with messages = get_flashed_messages() %}
{% for message in messages %}
<script>
alert("{{ message }}");
</script>
{% endfor %}
{% endwith %}
</body>

account_checker.py ⇒ password_policy() 함수를 구현
app.py ⇒ check_password_policy() 라우터 함수를 구현
password_policy.html 생성 ⇒ 형식에 맞춰서 출력
main.html ⇒ 링크를 추가
account_checker.py
def password_policy():
response = client.get_account_password_policy()
return response["PasswordPolicy"]
**app.py**
from myboto3.account_checker import get_user_list, get_user_access_key_data, delete_user_access_key, check_all_users_mfa, password_policy'
@app.route("/check_password_policy")
def check_password_policy():
try:
data = password_policy()
except:
data = {}
return render_template("password_policy.html", data=data)
**password_policy.html**
Document
{% if data %}
<p>암호 정책이 아래와 같이 설정되어 있습니다.</p>
<table>
<tr>
<th>최소 암호 길이</th>
<td>{{data.MinimumPasswordLength}}</td>
</tr>
<tr>
<th>특수 문자 필수 여부</th>
<td>{{data.RequireSymbols}}</td>
</tr>
<tr>
<th>숫자 필수 여부</th>
<td>{{data.RequireNumbers}}</td>
</tr>
<tr>
<th>대문자 필수 여부</th>
<td>{{data.RequireUppercaseCharacters}}</td>
</tr>
<tr>
<th>소문자 필수 여부</th>
<td>{{data.RequireLowercaseCharacters}}</td>
</tr>
<tr>
<th>암호 변경 허용 여부</th>
<td>{{data.AllowUsersToChangePassword}}</td>
</tr>
<tr>
<th>암호 만료 여부</th>
<td>{{data.ExpirePasswords}}</td>
</tr>
<tr>
<th>최대 암호 사용 기간</th>
<td>{{data.MaxPasswordAge}}</td>
</tr>
<tr>
<th>암호 재사용 방지 기간</th>
<td>{{data.PasswordReusePrevention}}</td>
</tr>
<tr>
<th>강제 암호 만료 여부</th>
<td>{{data.HardExpiry}}</td>
</tr>
<tr>
<td colspan="2">
<a href="/">메인으로 이동</a>
</td>
</tr>
</table>
{% else %}
<p>암호 정책이 설정되어 있지 않으므로, 취약 합니다. </p>
{% endif %}
```
main.html
<body>
<h1>AWS 취약점 점검 서비스</h1>
<h2><a href="/list">IAM 사용자 목록 조회</a></h2>
<h2><a href="/users/accesskeys">사용자별 액세스키 검증</a></h2>
<h2><a href="/users/mfa/check">사용자별 MFA 설정 현황</a></h2>
<h2><a href="/check_password_policy">IAM 패스워드 정책</a></h2>
{% with messages = get_flashed_messages() %}
{% for message in messages %}
<script>
alert("{{ message }}");
</script>
{% endfor %}
{% endwith %}
</body>

-> 암호 정책 설정 x

-> 암호정책 0
account_checker.py
def get_account_permissions(users):
# 반환할 값을 저장할 리스트
results = []
for user in users:
# 사용자 이름을 추출
username = user["UserName"]
# 사용자에게 연결된 정책을 조회
attached_policies = client.list_attached_user_policies(UserName=username)
inline_policies = client.list_user_policies(UserName=username)
# 사용자가 속한 그룹을 조회
groups = client.list_groups_for_user(UserName=username)
# 반환할 정보의 구조를 정의
user_data = {
"UserName": username,
"AttachedPolicies": [], # 관리형 정책
"InlinePolicies": [], # 인라인 정책
"GroupsPolicies": [] # 그룹에 할당된 정책
}
# 관리형 정책을 user_data에 추가
for policy in attached_policies.get("AttachedPolicies", []):
policy_arn = policy["PolicyArn"]
policy_name = policy["PolicyName"]
# 관리형 정책 문서의 내용을 조회
policy_details = client.get_policy(PolicyArn=policy_arn)
policy_version = client.get_policy_version(PolicyArn=policy_arn, VersionId=policy_details["Policy"]["DefaultVersionId"])
policy_document = policy_version["PolicyVersion"]["Document"]
user_data["AttachedPolicies"].append({
"PolicyName": policy_name,
"PolicyDocument": policy_document
})
# 인라인 정책을 user_data에 추가
for policy in inline_policies.get("PolicyNames", []):
policy_name = policy
# 인라인 정책 문서의 내용을 조회
policy_doc = client.get_user_policy(UserName=username, PolicyName=policy)
policy_doc = policy_doc.get("PolicyDocument")
user_data["InlinePolicies"].append({
"PolicyName": policy_name,
"PolicyDocument": policy_doc
})
# 그룹에 할당된 정책을 user_data에 추가
for group in groups.get("Groups", []):
group_name = group["GroupName"]
# 그룹에 할당된 관리형 정책과 인라인 정책을 조회
group_attached_policies = client.list_attached_group_policies(GroupName=group_name)
group_inline_policies = client.list_group_policies(GroupName=group_name)
# 그룹에 할당된 정책을 저장할 딕셔너리를 정의
group_data = {
"GroupName": group_name, # 그룹 이름
"AttachedPolicies": [], # 그룹에 할당된 관리형 정책
"InlinePolicies": [] # 그룹에 할당된 인라인 정책
}
# 그룹에 할당된 관리형 정책을 group_data에 추가
for policy in group_attached_policies.get("AttachedPolicies", []):
policy_arn = policy["PolicyArn"]
policy_name = policy["PolicyName"]
# 관리형 정책 문서의 내용을 조회
policy_detail = client.get_policy(PolicyArn=policy_arn)
policy_version = client.get_policy_version(PolicyArn=policy_arn, VersionId=policy_details["Policy"]["DefaultVersionId"])
policy_document = policy_version["PolicyVersion"]["Document"]
group_data["AttachedPolicies"].append({
"PolicyName": policy_name,
"PolicyDocument": policy_document
})
# 그룹에 할당된 인라인 정책을 group_data에 추가
for policy in group_inline_policies.get("PolicyNames", []):
policy_name = policy
# 인라인 정책 문서의 내용을 조회
policy_doc = client.get_group_policy(GroupName=group_name, PolicyName=policy)
policy_doc = policy_doc.get("PolicyDocument")
group_data["InlinePolicies"].append({
"PolicyName": policy_name,
"PolicyDocument": policy_doc
})
user_data["GroupsPolicies"].append(group_data)
results.append(user_data)
return results

users_permissions.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/static/style.css">
<style>
td {
background-color: white;
}
</style>
</head>
<body>
<h2 style="text-align: center">계정별 할당된 권한 조회</h2>
<table>
<header>
<tr>
<th>사용자명</th>
<th>유형</th>
<th>권한정책</th>
</tr>
</header>
{% for user in data %}
<tr>
<td rowspan="{{ (user.AttachedPolicies | length if user.AttachedPolicies | length > 0 else 1) +
(user.InlinePolicies | length if user.InlinePolicies | length > 0 else 1) +
(user.GroupsPolicies | length if user.GroupsPolicies | length > 0 else 1) }}">
{{ user.UserName }}</td>
{% if user.AttachedPolicies | length == 0 %}
<td>관리형</td>
<td>x</td>
</tr>
{% endif %}
{% for policy in user.AttachedPolicies %}
{% if loop.index == 1 %}
<td rowspan="{{ user.AttachedPolicies | length }}">관리형</td>
<td>{{ policy.PolicyName }}</td>
</tr>
{% else %}
<tr>
<td>{{ policy.PolicyName }}</td>
</tr>
{% endif %}
{% endfor %}
{% if user.InlinePolicies | length == 0 %}
<tr>
<td>인라인</td>
<td>x</td>
</tr>
{% endif %}
{% for policy in user.InlinePolicies %}
{% if loop.index == 1 %}
<tr>
<td rowspan="{{ user.InlinePolicies | length }}">인라인</td>
<td>{{ policy.PolicyName }}</td>
</tr>
{% else %}
<tr>
<td>{{ policy.PolicyName }}</td>
</tr>
{% endif %}
{% endfor %}
{% if user.GroupsPolicies | length == 0 %}
<tr>
<td>그룹</td>
<td>x</td>
</tr>
{% else %}
<tr>
<td>그룹</td>
<td>
<table>
{% for policy in user.GroupsPolicies %}
{% if loop.index == 1 %}
<tr>
<th>그룹명</th>
<th>유형</th>
<th>권한정책</th>
</tr>
<tr>
<td>{{ policy.GroupName }}</td>
<td></td>
<td></td>
</tr>
{% else %}
<tr>
<td>{{ policy.GroupName }}</td>
<td></td>
<td></td>
</tr>
{% endif %}
{% endfor %}
</table>
</td>
</tr>
{% endif %}
{% endfor %}
</table>
</body>
</html>
main.html
<body>
<h1>AWS 취약점 점검 서비스</h1>
<h2><a href="/list">IAM 사용자 목록 조회</a></h2>
<h2><a href="/users/accesskeys">사용자별 액세스키 검증</a></h2>
<h2><a href="/users/mfa/check">사용자별 MFA 설정 현황</a></h2>
<h2><a href="/check_password_policy">IAM 패스워드 정책</a></h2>
<h2><a href="/users/permissions">사용자별 권한 조회</a></h2>
{% with messages = get_flashed_messages() %}
{% for message in messages %}
<script>
alert("{{ message }}");
</script>
{% endfor %}
{% endwith %}
</body>

<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/static/style.css">
<style>
td {
background-color: white;
}
</style>
<script>
function toggle(event) {
// 이벤트 발생 대상(span) 가져오기
const toggleIcon = event.target;
// 해당 span의 다음 요소(pre 태그) 가져오기
const preElement = toggleIcon.nextElementSibling;
// 토글 처리
if (preElement.style.display === "none") {
preElement.style.display = "block";
toggleIcon.textContent = "[-]";
} else {
preElement.style.display = "none";
toggleIcon.textContent = "[+]";
}
}
</script>
</head>
<body>
<h2 style="text-align: center">계정별 할당된 권한 조회</h2>
<table>
<tr>
<th>사용자명</th>
<th>유형</th>
<th>권한정책</th>
</tr>
{% for user in data %}
<tr>
<td rowspan="{{ (user.AttachedPolicies | length if user.AttachedPolicies | length > 0 else 1) +
(user.InlinePolicies | length if user.InlinePolicies | length > 0 else 1) +
(user.GroupsPolicies | length if user.GroupsPolicies | length > 0 else 1) }}">
{{ user.UserName }}</td>
{% if user.AttachedPolicies | length == 0 %}
<td>관리형</td>
<td>x</td>
</tr>
{% endif %}
{% for policy in user.AttachedPolicies %}
{% if loop.index == 1 %}
<td rowspan="{{ user.AttachedPolicies | length }}">관리형</td>
<td>
{{ policy.PolicyName }}
<span onclick='toggle(event)'>[+]</span>
<pre id="user_attached_{{loop.index | string}}" style="display:none">{{ policy.PolicyDocument | tojson(indent=4) }}</pre>
</td>
</tr>
{% else %}
<tr>
<td>
{{ policy.PolicyName }}
<span onclick='toggle(event)'>[+]</span>
<pre id="user_attached_{{loop.index | string}}" style="display:none">{{ policy.PolicyDocument | tojson(indent=4) }}</pre>
</td>
</tr>
{% endif %}
{% endfor %}
{% if user.InlinePolicies | length == 0 %}
<tr>
<td>인라인</td>
<td>x</td>
</tr>
{% endif %}
{% for policy in user.InlinePolicies %}
{% if loop.index == 1 %}
<tr>
<td rowspan="{{ user.InlinePolicies | length }}">인라인</td>
<td>
{{ policy.PolicyName }}
<span onclick='toggle(event)'>[+]</span>
<pre id="user_inline_{{loop.index | string}}" style="display:none">{{ policy.PolicyDocument | tojson(indent=4) }}</pre>
</td>
</tr>
{% else %}
<tr>
<td>
{{ policy.PolicyName }}
<span onclick='toggle(event)'>[+]</span>
<pre id="user_inline_{{loop.index | string}}" style="display:none">{{ policy.PolicyDocument | tojson(indent=4) }}</pre>
</td>
</tr>
{% endif %}
{% endfor %}
{% if user.GroupsPolicies | length == 0 %}
<tr>
<td>그룹</td>
<td>x</td>
</tr>
{% else %}
<tr>
<td>그룹</td>
<td>
<table style="width: 100%">
<tr>
<th>그룹명</th>
<th>유형</th>
<th>권한정책</th>
</tr>
<tr>
{% for policy in user.GroupsPolicies %}
<td rowspan="{{ (policy.AttachedPolicies | length if policy.AttachedPolicies | length > 0 else 1) +
(policy.InlinePolicies | length if policy.InlinePolicies | length > 0 else 1) }}">
{{ policy.GroupName }}</td>
{% if policy.AttachedPolicies | length == 0 %}
<td>관리형</td>
<td>x</td>
</tr>
{% endif %}
{% for attached in policy.AttachedPolicies %}
{% if loop.index == 1 %}
<td rowspan="{{policy.AttachedPolicies | length if policy.AttachedPolicies | length > 0 else 1}}">관리형</td>
<td>
{{ attached.PolicyName }}
<span onclick='toggle(event)'>[+]</span>
<pre id="group_attached_{{loop.index | string}}" style="display:none">{{ attached.PolicyDocument | tojson(indent=4) }}</pre>
</td>
</tr>
{% else %}
<td>
{{ attached.PolicyName }}
<span onclick='toggle(event)'>[+]</span>
<pre id="group_attached_{{loop.index | string}}" style="display:none">{{ attached.PolicyDocument | tojson(indent=4) }}</pre>
</td>
</tr>
{% endif %}
{% endfor %}
{% if policy.InlinePolicies | length == 0 %}
<td>인라인</td>
<td>x</td>
</tr>
{% endif %}
{% for inline in policy.InlinePolicies %}
{% if loop.index == 1 %}
<td rowspan="{{policy.InlinePolicies | length if policy.InlinePolicies | length > 0 else 1}}">인라인</td>
<td>
{{ inline.PolicyName }}
<span onclick='toggle(event)'>[+]</span>
<pre id="group_inline_{{loop.index | string}}" style="display:none">{{ inline.PolicyDocument | tojson(indent=4) }}</pre>
</td>
</tr>
{% else %}
<td>
{{ inline.PolicyName }}
<span onclick='toggle(event)'>[+]</span>
<pre id="group_inline_{{loop.index | string}}" style="display:none">{{ inline.PolicyDocument | tojson(indent=4) }}</pre>
</td>
</tr>
{% endif %}
{% endfor %}
{% endfor %}
</table>
</td>
</tr>
{% endif %}
{% endfor %}
</table>
</body>
</html>

ec2_checker.py
import boto3
from s3_checker import check_s3_public_access, list_buckets, check_object_exists
client = boto3.client("ec2")
def get_ec2_security_group_details():
# 결과를 저장할 리스트를 정의
results = []
# 모든 EC2 인스터스를 조회
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_instances.html
instances = client.describe_instances()
# 인스턴스별 인스턴스ID와 보안그룹 정보를 추출
for reservation in instances["Reservations"]:
for instance in reservation["Instances"]:
instance_id = instance["InstanceId"]
security_groups = instance["SecurityGroups"]
result_sgs = []
# 보안그룹의 정보를 조회
for sg in security_groups:
sg_id = sg["GroupId"]
# 보안그룹의 상세 정보를 조회
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_security_groups.html
sg_details = client.describe_security_groups(GroupIds=[sg_id])
# 해당 보안그룹의 인바운드 규칙을 저장할 리스트
for sg_detail in sg_details["SecurityGroups"]:
inbound_rules = []
for rule in sg_detail.get("IpPermissions", []):
inbound_rules.append(rule)
outbound_rules = []
for rule in sg_detail.get("IpPermissionsEgress", []):
outbound_rules.append(rule)
result_sgs.append({
"SecurityGroupId": sg_id,
"InboundRules": inbound_rules,
"OutboundRules": outbound_rules
})
results.append({
"InstanceId": instance_id,
"SecurityGroups": result_sgs})
return results
def list_instances_with_key_pairs():
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2/client/describe_instances.html
instances = client.describe_instances()
for reservation in instances["Reservations"]:
for instance in reservation["Instances"]:
print(f"InstanceId: {instance['InstanceId']}")
print(f"PublicIpAddress: {instance['PublicIpAddress']}")
print(f"PrivateIpAddress: {instance['PrivateIpAddress']}")
if "KeyName" in instance:
print(f"KeyName: {instance['KeyName']}")
print(f"판정 결과: 양호")
else:
print(f"판정 결과: 취약")
print()
def get_key_pairs():
key_pairs = client.describe_key_pairs()
return key_pairs["KeyPairs"]
def check_key_pair_s3_storage():
key_pairs = get_key_pairs()
buckets = list_buckets()
for key_pair in key_pairs:
key_name = key_pair["KeyName"]
print(f"KeyPair: {key_name}")
print("=" * 30)
for bucket in buckets:
bucket_name = bucket["Name"]
print(f"Bucket: {bucket_name}")
status = "버킷에 키가 저장되어 있지 않음 >> 판정 불가"
if check_object_exists(bucket_name, key_name+".pem"):
if check_s3_public_access(bucket_name):
status = "퍼블릭 버킷에 저장 >> 취약"
else:
status = "프라이빗 버킷에 저장 >> 양호"
print(f"판정 결과: {status}")
print()
if __name__ == "__main__":
# list_instances_with_key_pairs()
# check_key_pair_s3_storage()
a = get_ec2_security_group_details()
import json
a = json.dumps(a, indent=4)
print(a)
app.py
from myboto3.ec2_checker import get_ec2_security_group_details
@app.route("/ec2/securitygroups")
def ec2_securitygroups():
data = get_ec2_security_group_details()
return render_template("ec2_securitygroups.html", data=data)
ec2_securitygroups.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h2 style="text-align: center">사용자 목록</h2>
<table>
<header>
<tr>
<th>인스턴스 ID</th>
<th>보안그룹 ID</th>
<th>인바운드 규칙</th>
<th>아웃바운드 규칙</th>
</tr>
</header>
<tbody>
{% for instance in data %}
<tr>
<td rowspan="{{ instance.SecurityGroups|length }}">{{ instance.InstanceId }}</td>
{% for sg in instance.SecurityGroups %}
{% if loop.index > 1 %}
<tr>
{% endif %}
<td>{{ sg.SecurityGroupId }}</td>
<td>
{% for rule in sg.InboundRules %}
<ul>
<li>FromPort: {{ rule.FromPort }}</li>
<li>Source:
{% if rule.IpRanges | length > 0 %}
{{ rule.IpRanges[0].CidrIp }} ({{ rule.IpRanges[0].Description }})
{% else %}
{{ rule.UserIdGroupPairs[0].GroupId }} ({{ rule.UserIdGroupPairs[0].Description }})
{% endif %}
</li>
</ul>
{% endfor %}
</td>
<td>
{% for rule in sg.OutboundRules %}
<ul>
<li>ToPort: {{ rule.ToPort }}</li>
<li>IpRages: {{ rule.IpRanges | join(", ") }}</li>
</ul>
{% endfor %}
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
</body>
</html>
main.html
<body>
<h1>AWS 취약점 점검 서비스</h1>
<h2><a href="/list">IAM 사용자 목록 조회</a></h2>
<h2><a href="/users/accesskeys">사용자별 액세스키 검증</a></h2>
<h2><a href="/users/mfa/check">사용자별 MFA 설정 현황</a></h2>
<h2><a href="/check_password_policy">IAM 패스워드 정책</a></h2>
<h2><a href="/users/permissions">사용자별 권한 조회</a></h2>
<h2><a href="/ec2/securitygroups">EC2 인스턴스에 연결된 보안그룹 조회</a></h2>
{% with messages = get_flashed_messages() %}
{% for message in messages %}
<script>
alert("{{ message }}");
</script>
{% endfor %}
{% endwith %}
</body>

ec2_checker.py
def get_nacl_details():
results = []
subnets = client.describe_subnets()
for subnet in subnets['Subnets']:
subnet_id = subnet['SubnetId']
vpc_id = subnet['VpcId']
nacls = client.describe_network_acls(Filters=[{'Name': 'association.subnet-id', 'Values': [subnet_id]}])
subnet_data = {
"VpcId": vpc_id,
"SubnetId": subnet_id,
"NetworkAcls": []
}
for nacl in nacls['NetworkAcls']:
nacl_id = nacl['NetworkAclId']
entries = []
for entry in nacl['Entries']:
entries.append({
"RuleNumber": entry['RuleNumber'],
"Protocol": entry.get('Protocol') if entry.get('Protocol') != '-1' else 'ALL',
"RuleAction": entry['RuleAction'],
"Egress": entry['Egress'],
"CidrBlock": entry.get('CidrBlock', 'ALL'),
"Ipv6CidrBlock": entry.get('Ipv6CidrBlock', 'ALL'),
"PortRange": entry.get('PortRange', 'ALL'),
})
subnet_data['NetworkAcls'].append({
"NetworkAclId": nacl_id,
"IsDefault": nacl['IsDefault'],
"Entries": entries
})
results.append(subnet_data)
return results
app.py
from myboto3.ec2_checker import get_ec2_security_group_details, get_nacl_details
@app.route("/subnet/nacl")
def subnet_nacl():
data = get_nacl_details()
return render_template("subnet_nacl.html", data=data)
subnet_nacl.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<h2 style="text-align: center">서브넷에 연결된 NACL 조회</h2>
<table>
<header>
<tr>
<th>서브넷</th>
<th>VPC</th>
<th>NACL</th>
<th>기본 NACL 여부</th>
<th>인바운드 규칙</th>
<th>아웃바운드 규칙</th>
</tr>
</header>
<tbody>
{% for row in data %}
<tr>
<td>{{ row.SubnetId }}</td>
<td>{{ row.VpcId }}</td>
<td>{{ row.NetworkAcls[0].NetworkAclId }}</td>
<td>{{ row.NetworkAcls[0].IsDefault }}</td>
<td>
{% for rule in row.NetworkAcls[0].Entries %}
{% if not rule.Egress %}
<ul>
<li>Rule Number: {{ rule.RuleNumber }}</li>
<li>Protocol: {{ rule.Protocol }}</li>
<li>Rule Action: {{ rule.RuleAction }}</li>
<li>CIDR: {{ rule.CidrBlock }}</li>
<li>Port Range: {{ rule.PortRange }}</li>
</ul>
{% endif %}
{% endfor %}
</td>
<td>
{% for rule in row.NetworkAcls[0].Entries %}
{% if rule.Egress %}
<ul>
<li>Rule Number: {{ rule.RuleNumber }}</li>
<li>Protocol: {{ rule.Protocol }}</li>
<li>Rule Action: {{ rule.RuleAction }}</li>
<li>CIDR: {{ rule.CidrBlock }}</li>
<li>Port Range: {{ rule.PortRange }}</li>
</ul>
{% endif %}
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr>
<td colspan="4"><a href="/">메인으로 이동</a></td>
</tr>
</tfoot>
</table>
</body>
</html>
main.html
<body>
<h1>AWS 취약점 점검 서비스</h1>
<h2><a href="/list">IAM 사용자 목록 조회</a></h2>
<h2><a href="/users/accesskeys">사용자별 액세스키 검증</a></h2>
<h2><a href="/users/mfa/check">사용자별 MFA 설정 현황</a></h2>
<h2><a href="/check_password_policy">IAM 패스워드 정책</a></h2>
<h2><a href="/users/permissions">사용자별 권한 조회</a></h2>
<h2><a href="/ec2/securitygroups">EC2 인스턴스에 연결된 보안그룹 조회</a></h2>
<h2><a href="/subnet/nacl">서브넷에 연결된 NACL 조회</a></h2>
{% with messages = get_flashed_messages() %}
{% for message in messages %}
<script>
alert("{{ message }}");
</script>
{% endfor %}
{% endwith %}
</body>


-> 최종 완성된 형태 다이어그램
vpc 생성

가용영역 정할때 : ec2 > 인스턴스 유형 > 네트워킹 > 가용영역 유형 확인

2) NACL 설정을 확인


들어오는것, 나가는거 모두 허용되어있음
-> 인바운드 규칙은 서브넷 내에게 제공하는 서비스 포드 한 해서만 허용하는 것이 안전한 설정이다
-> 아웃바운드는 전체를 허용하는 것이 맞
보안그룹 생성




















