티스토리 뷰

aws cli 버전 : aws-cli/2.1.11 Python/3.7.9 Windows/10 exe/AMD64 prompt/off

python 버전 : 3.8.5

CIS Benchmark 다운로드 링크 : www.cisecurity.org/blog/foundational-cloud-security-with-cis-benchmarks/

 

Blog | Foundational Cloud Security with CIS Benchmarks

Implementiong foundational cloud security systems to harden environments protect against cyber-attacks and misconfiguration.

www.cisecurity.org

에드센스가 안붙네요.. 그래도 짬짬이 시간 들여서 포스팅하는데 뭔가 별론가 봅니다 흠..

암튼 이번 내용은 90일 이상 사용하지 않은 자격증명(password, access key)이 있는지 확인하는 내용입니다.

콘솔에서는 위와 같이 컬럼을 셋팅해서 확인 가능합니다.

Access Key ID 컬럼이 None이 아니고 Access key last used가 90 days 이상이면 취약이라고 보면 되겠죠? 패스워드는 현재 시간을 기준으로 Console last sign-in 시간을 비교해보면 될 것 같습니다.

또한 키 옆에 Inactive인 부분은 제거 대상으로, Active이지만 None인 경우도 검토 대상으로 보면 됩니다.

자격 증명 보고서는 이제 다들 뽑으실 것 같아서 이 복잡한(?) 조건을 풀어서 보시죠.

[password 관련 표]

user password_enabled password_last_used 비고
<root_account> not_supported not_supported 최근 기록 있으면 취약(password_last_used 참고)
일반계정 TRUE YYYY-MM-DD~  
일반계정 FALSE YYYY-MM-DD~ 활성화 시켰다가 비활성화한 경우
일반계정 TRUE N/A 활성화는 했지만 로그인X
일반계정 FALSE N/A  

 

사실 기준에서는 password_last_used 데이터가 없을 경우 password_last_changed 데이터를 통해서 점검을 하라고 되어있지만 본질적인 의미에서 봤을 때 password_last_used만 체크하는 것이 맞다고 생각합니다.(password_enabled가 TRUE인 경우만 점검 수행)

[access key 관련 표]

access_key_1_active access_key_1_last_rotated access_key_1_last_used_date access_key_2_active access_key_2_last_rotated access_key_2_last_used_date 비고
TRUE YYYY-MM-DD~ N/A FALSE N/A N/A  
TRUE YYYY-MM-DD~ YYYY-MM-DD~ FALSE YYYY-MM-DD~ N/A  
TRUE YYYY-MM-DD~ N/A TRUE YYYY-MM-DD~ N/A  

 

access key 관련 표는 경우의 수가 너무 많아서(...) 이 정도만 작성하겠습니다. 나머지는 한 번 직접 써보세요. 그래야 실력이 늘고 학습이 됩니다.

중요한건 access_key_x_active가 TRUE면 무조건 rotated 값이 있어야 하고, FALSE 여도 rotated 값이 있을 수 있습니다.

이런 데이터들을 그냥 지나치면 안되고 항상 가정을 하셔야 합니다.

(잘못된 예시) access_key_x_active가 FALSE네 점검 안해도 된다 꿀이네 ㅋㅋ

(올바른 예시) access_key_x_active가 FALSE인데 rotated(또는 used_date) 값이 최근이네? (만약 보안담당자라면) 내가 비활성화(또는 활성화)한게 없는데 이거 좀 찾아봐야겠다. cloudtrail 로그에서 해당 사용자 계정 검색해봐야지(또는 SIEM 이던가 등등)

위와 같이 단순히 현상을 넘기지 말고 항상 어떤 상황일 때 어떻게 쓸 수 있을지 다각도로 생각해보고 가정을 해야 실제로 사고에 대한 대응이나 프로젝트를 하거나 응용을 하실 수가 있습니다.(물론 위와 같이 단순하지만은 않겠죠 당연히)

암튼 꼰대같은 소리는 그만하고 python으로 코딩하면,

은 내일로,.. 오늘 피곤하군요

---------- 210307 추가 ----------

여러가지로 큰 일들과 이후로 계속 대응하느라 너무 피곤해서 블로그를 진행하질 못했네요.

아래는 key1에 대한 코드입니다. 비슷하게 key2도 검증가능 할 것으로 보입니다.(최적화의 길은 항상 열려있습니다.)

import subprocess
import json
import base64
import maya

SEC = 1
MIN = 60 * SEC
HOUR = 60 * MIN
DAY = 24 * HOUR
MONTH = 30 * DAY
YEAR = 12 * MONTH


def get_string(b_obj, idx=0):
    str_tmp = b_obj.decode()
    # -1이 들어올 경우 전체 값 반환
    if idx == -1:
        return str_tmp.splitlines()
    else:
        return str_tmp.splitlines()[idx]


def get_json(b_obj):
    str_tmp = b_obj.decode()
    return json.loads(str_tmp)


def exec_cmd(tmp_cmd):
    try:
        tmp_data = subprocess.check_output(tmp_cmd, shell=True, stderr=subprocess.STDOUT)
    except subprocess.CalledProcessError as e:
        error_msg = get_string(e.output, 1)
        if 'NoSuchEntity' in error_msg:
            aws_acct = [int(tmp) for tmp in error_msg.split(' ') if tmp.isdigit()]
            print(f'[-] AWS ACCOUNT{aws_acct} has no password policy.')
        elif 'GenerateCredentialReport' in error_msg:
            print(f'[-] {error_msg}')
        else:
            print(error_msg)
            
        return b'Error'

    return tmp_data


if __name__ == '__main__':
    cmd = (
        'aws iam generate-credential-report '
        '--query "State" --output text'
    )

    while True:
        gcr_state = get_string(exec_cmd(cmd))
        if gcr_state == 'COMPLETE':
            print('[+] CREDENTIAL REPORT GENERATED!')
            break
        elif gcr_state == 'Error':
            # print('[-] ERROR')
            continue
        else:
            print('[*] CREDENTIAL REPORT GENERATING...')

    cmd = (
        'aws iam get-credential-report '
        '--query "Content"'
    )
    gcr_result = get_string(base64.b64decode(exec_cmd(cmd)), -1)
    del gcr_result[0]

    current_time = maya.now().epoch

    for gr in gcr_result:
        tmp_data = gr.split(',')
        user_id = tmp_data[0]
        is_pw_enabled = tmp_data[3]
        pw_last_used = tmp_data[4]

        print()
        if pw_last_used != 'N/A':
            # print(maya.Datetime.today())
            if current_time - maya.parse(pw_last_used).epoch < (MONTH * 3):
                print(f'{user_id}\'s password safe.')
            else:
                print(f'{user_id}\'s password vuln.')

        # get data about key1 & key2
        is_key1_active, key1_last_rotated, key1_last_used = tmp_data[8:11]
        is_key2_active, key2_last_rotated, key2_last_used = tmp_data[13:16]
        # print(user_id, is_key1_active, key1_last_rotated, key1_last_used)
        # print(user_id, is_key2_active, key2_last_rotated, key2_last_used)

        if is_key1_active == 'false' and key1_last_rotated == 'N/A' and key1_last_used == 'N/A':
            print(f'{user_id}\'s key1 safe.')
            continue
        elif is_key1_active == 'true' and key1_last_used != 'N/A':
            if current_time - maya.parse(key1_last_used).epoch < (MONTH * 3):
                print(f'{user_id}\'s key1 safe.')
            else:
                print(f'{user_id}\'s key1 vuln.')
        elif is_key1_active == 'true' and key1_last_used == 'N/A':
            if current_time - maya.parse(key1_last_rotated).epoch < (MONTH * 3):
                print(f'{user_id}\'s key1 safe.')
            else:
                print(f'{user_id}\'s key1 vuln.')
        else:
            print(f'{user_id} review required.')

아래는 실제 출력 결과입니다.

만지다가 너무 오랜만에 좀 수정하고 실행하니 부족한 부분이 많네요.

비밀번호 없으면 출력해주는 기능이라던가 이런 것들은 추가하셔서 사용하시면 될 것 같습니다~

감사합니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함