앵하니의 더 나은 보안

딥러닝으로 OTP 추측이 가능할까 본문

보안 기술/ETC

딥러닝으로 OTP 추측이 가능할까

앵한 2024. 8. 14. 11:18

취약점 진단 대상 시스템에서 취약점 도출이 안돼서 뭐라도 잡아내려 MFA의 OTP를 딥러닝을 통해 학습시켜

OTP 발급 신청에 따른 OTP 결과 값 예측이 가능한지 확인해보려 했다.

 

결과적으로는 실패했지만 그 과정을 한번 살펴보자.

 

학습시킬 샘플 OTP 값을 뽑아내기 위한 수집/분석 요소로는 아이디와 OTP 발급 신청에 따른 서버 응답 시간을 택했다.

 

먼저 아래는 OTP 요청 아이디와 응답 패킷 내 존재하는 서버 시간을 추출해 파일로 저장하는 소스코드다.

해당 시스템에서는 OTP를 5분단위로 전송이 가능해

time.sleep(301)을 통해 5분 1초 간격으로 OTP 요청 및 수집을 반복시켰다.

import requests
from datetime import datetime
import time

def convert_gmt_to_utc(gmt_time_str):
    time_format = "%a, %d %b %Y %H:%M:%S %Z"

    date_object = datetime.strptime(gmt_time_str, time_format)
    unix_time = time.mktime(date_object.timetuple())
    return unix_time

userId_list = ["id_1","id_2","id_3"]

try:
    while True:
        for userId in userId_list:
            response = requests.post("https://service_domain/OTP_send",data = {"UserId":userId}, headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.6478.127 Safari/537.36","Referer":"https://e-joystore.joymax.com/Index/Login.aspx"})
            response_data = response.headers.get('Date')
            reponse_Unix_time = str(int(convert_gmt_to_utc(response_data)))
            print(reponse_Unix_time+" : "+response.text)

            if "-3" not in response.text:
                try:
                    with open("otp_requested.txt", 'a') as file:
                        content = file.write(userId+", "+reponse_Unix_time+"\n")
                except FileNotFoundError:
                    with open("otp_requested.txt", 'w') as file:
                        content = file.write(userId+", "+reponse_Unix_time+"\n")
        time.sleep(301)
except KeyboardInterrupt:
    print("The Tool was stopped by user's interrupt")

 

그리고 OTP는 gmail을 통해 수신되는데, 이 값이 요청한 시간에 따라 유효한 OTP 값이다.

해당 값을 ID,time,result OTP 순으로 나열하기 위해 수신된 이메일을 전부 파일형태로 다운로드 한 후 파이썬을 이용해
수집한다.(eml 파일의 내용은 base64로 인코딩 돼 있기에 내용 확인 및 저장을 위해 디코딩이 필요하다.)

#디렉토리 내 파일 스캔
#리스트화 된 파일들 싹 읽어서 날짜는 유닉스타임으로, 그리고 코드 입력
#저장 이후 a로 파일 계속 붙여넣기...
import os
import base64
from datetime import datetime

unix_time = ""
base64_decoded = ""
eml_dir = input("폴더 경로 입력 > ")
file_list = os.listdir(eml_dir)
for file_name in file_list:
    with open(eml_dir+"/"+file_name,'r') as eml_file:
        file_content = eml_file.readlines()
        for line in file_content:
            if "Date" in line[:5]:
                #PDT to Unix time
                date_time = line[6:].replace(" (PDT)","").strip()
                dt = datetime.strptime(date_time, "%a, %d %b %Y %H:%M:%S %z")
                unix_time = str(int(dt.timestamp()))
            elif "RW1haWwgQXV0aGVudGljYXRpb24gQ29kZSA6" in line:
                #base64 디코딩 이후 Email Authentication Code : 를 기준으로 잘라서 뒤에 데이터 변수에 할당
                base64_decoded = base64.b64decode(line).decode().split("Email Authentication Code : ")[1]
            
            if unix_time and base64_decoded:
                with open("id_1.txt",'a') as file:
                    #이후 날짜랑 변수할당한거 각각 파일에 작성
                    data_worked = unix_time+", "+base64_decoded+"\n"
                    file.write(data_worked)
                unix_time = ""
                base64_decoded = ""

 

 

그리고 수집된 데이터 id_1.txt, id_2.txt, id_3.txt 파일을 파일 하나(final_combined_data.txt)로 합친다.

from datetime import datetime
import pytz

unix_time = ""
OTP_code = ""
email = input("email > ")
with open(email+".txt",'r', encoding='utf-8') as eml_file:
    file_content = eml_file.readlines()
    for line in file_content:
        if "Date" in line[:5]:
            #KST to Unix time
            kST_time = line[6:]
            year, month, day, _, am_pm, time = kST_time.split(" ")
            hour, minute = time.split(":")
            hour = int(hour)
            if am_pm == "오후" and hour != 12:
                hour += 12
            elif am_pm == "오전" and hour == 12:
                hour = 0
            dt = datetime(int(year[:-1]), int(month[:-1]), int(day[:-1]), hour, int(minute))
            kst = pytz.timezone('Asia/Seoul')
            localized_dt = kst.localize(dt)

            unix_time = str(int(localized_dt.timestamp()))
            #초가 없어서 +- 60까지 포함시켜야 함 ㅜ

        elif "Email Authentication" in line:
            OTP_code = line.split("Email Authentication Code : ")[1]
        
        if unix_time and OTP_code:
            with open(email+"_parsed.txt",'a') as file:
                #이후 날짜랑 변수할당한거 각각 파일에 작성
                data_worked = unix_time+", "+OTP_code
                file.write(data_worked)
            unix_time = ""
            OTP_code = ""

 

이후 해당 파일을 기반으로 딥러닝을 수행하는 소스코드를 작성한다.

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.preprocessing import LabelEncoder
import numpy as np
from joblib import dump

# Let's assume you have input data X and output data Y
# X = ...
# Y = ...
input_list = []
output_list = []
learning_data = []
with open('final_combined_data.txt','r') as readfile:
    learning_data = readfile.readlines()

for learning_data_one in learning_data:
    separate_learning_data = learning_data_one.split(",")
    unix_time = separate_learning_data[0]
    id = separate_learning_data[1]
    tmp_list = [unix_time, id]
    input_list.append(tmp_list)
    output_list.append(separate_learning_data[2])

# input_list에서 unix time과 문자열을 분리합니다.
unix_time = [int(i[0]) for i in input_list]  # unix time을 문자열에서 정수로 변환합니다.
strings = [i[1] for i in input_list]

# 문자열을 벡터로 변환합니다.
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(strings)

print(X)

# unix time을 2차원 배열로 변환합니다 (각 unix time은 배열입니다).
unix_time = np.array(unix_time).reshape(-1, 1)

# unix time과 벡터화된 문자열을 연결합니다.
input_data = np.concatenate((unix_time, X.toarray()), axis=1)

# output_data를 인코딩합니다.
encoder = LabelEncoder()
#output_data = encoder.fit_transform(output_list)
output_data = [int(x, 16) for x in output_list]

# 데이터를 학습 데이터와 테스트 데이터로 분할합니다.
X_train, X_test, y_train, y_test = train_test_split(input_data, output_data, test_size=0.2, random_state=42)

# 선형 회귀 모델을 생성하고 학습 데이터로 학습시킵니다.
model = LinearRegression()
model.fit(X_train, y_train)

# 학습된 모델을 파일로 저장합니다.
dump(model, 'trained_model.joblib') 


def predict_output(new_input):
    # new_input에서 unix time과 문자열을 분리합니다.
    new_unix_time = new_input[0]
    new_string = new_input[1]

    # 문자열을 벡터로 변환합니다.
    new_X = vectorizer.transform([new_string])

    # unix time을 2차원 배열로 변환합니다.
    new_unix_time = np.array(new_unix_time).reshape(-1, 1)

    # unix time과 벡터화된 문자열을 연결합니다.
    new_input_data = np.concatenate((new_unix_time, new_X.toarray()), axis=1)

    # 출력을 예측합니다.
    predicted_output = model.predict(new_input_data)

    # 예측된 출력을 16진수 문자열로 변환합니다.
    return hex(int(predicted_output[0]))[2:]

# 예측 함수를 테스트합니다.
new_input = [1721211011, 'id_1']
print(f"입력 데이터 {new_input}에 대한 예측 출력: {predict_output(new_input)}")
#B461DF28B2877DF

 

학습 시킨 뒤 해당 모델을 사용해 id_1 아이디에서 1721211011 시간에 보낸 OTP값을 추측하라고 마지막에 질의하는데, 이 값들은 기존 학습 데이터에 존재하는 값이라 B461DF28B2877DF가 나와야 하지만 영 엉뚱한 값을 도출해냈다.

 

 

딥러닝 소스코드도 전부 gpt가 작성해줘서 어디가 틀렸는지도 가늠하기 힘들어

여기서 더 진행해보려면 딥러닝에 대한 공부가 필요할 듯 하다.

 

수집데이터 양 자체도 많지 않았을 뿐더러, 딥러닝으로 공부를 어느정도 한 동료에게 듣기로서는 수집데이터 user id와 시간 데이터 만으로 패턴을 학습하고 정확한 결과를 예측하려면 샘플 데이터가 엄청 방대하게 필요 (100~1000만 단위) 할 것 이라는 말을 들었다. 그 말 대로라면 5분단위로 데이터를 수집해야해서 현실성 없을 것 같다는 생각을 했다. 😅

 

수집 데이터가 부족해서 그런 것일 수도 있고, 소스코드가 잘못됐을 수도 있고 그렇다.

아무튼 OTP 샘플 수집을 통한 결과 예측에는 실패했다.

'보안 기술 > ETC' 카테고리의 다른 글

XZ Utils Backdoor(CVE-2024-3094)  (0) 2024.06.23
Log4shell(CVE-2021-44228)  (0) 2024.06.23
(reversing.kr) Easy CrackMe  (2) 2023.11.22
(reversing.kr) Easy Keygen  (2) 2023.11.22
AWS Pentesting tool PACU  (0) 2022.11.10
Comments