오늘 학습 주제
1. 시큐어 코딩 - 보안기능
시큐어 코딩 - 보안 기능
- 적절한 인증 없는 중요 기능 허용 -
적절한 인증 없는 중요 기능 허용 | |
정의 | 보안기능(인증, 접근제어, 기밀성, 암호화, 권한관리 등)을 부적절하게 구현 시 발생할 수 있는 보안 약점 |
공격 목적 및 발생되는 문제 | 권한 도용, 주요정보 변조 |
공격 원리 | 적절한 인증 없는 중요기능 허용, 부적절한 인가 이용 |
예방법 | • 클라이언트의 보안 검사를 우회하여 서버에 접근하지 못하도록 설계하고 중요한 정보가 있는 페이지는 재인증을 적용 • 안전하다고 검증된 라이브러리나 프레임워크 (Django authentication system, Flask-Login 등)를 사용 |
========================================= 안전하지 않은 코드 =========================================
new_pwd = request.POST.get('new_password','')
# 로그인한 사용자 정보
user = '%s' % escape(request.session['userid'])
# 현재 password와 일치 여부를 확인하지 않고 수정함
sha = hashlib.sha256(new_pwd.encode())
update_password_from_db(user, sha.hexdigest())
=========================================== 안전한 코드 =============================================
# login_required decorator를 사용해 login된 사용자만 접근하도록 처리
@login_required
def change_password(request):
new_pwd = request.POST.get('new_password','')
crnt_pwd = request.POST.get('current_password','')
# 로그인한 사용자 정보를 세션에서 가져온다.
user = '%s' % escape(request.session['userid'])
crnt_h = hashlib.sha256(crnt_pwd.encode())
h_pwd = crnt_h.hexdigest()
# DB에서 기존 사용자의 Hash된 패스워드 가져오기
old_pwd = get_password_from_db(user)
# 패스워드를 변경하기 전 사용자에 대한 재인증을 수행한다.
if old_pwd == h_pwd:
new_h = hashlib.sha256(new_pwd.encode())
[ 문제점 ]
패스워드 수정 시 수정을 요청한 패스워드와 DB에 저장된 사용자 패스워드 일치 여부를 확인하지 않고 처리
패스워드의 재확인 절차도 생략
[ 해결 ]
DB에 저장된 사용자 패스워드와 변경을 요청한 패스워드의 일치 여부를 확인하고,
변경 요청한 패스워드와 재확인 패스워드가 일치하는지 확인 후 DB의 패스워드를 수정
- 부적절한 인가 -
부적절한 인가 | |
정의 | 프로그램이 모든 가능한 실행 경로에 대해서 접근 제어를 검사하지 않거나 불완전하게 검사하는 경우 발생할 수 있는 보안 약점 |
공격 목적 및 발생되는 문제 | 정보 유출 |
공격 원리 | 접근 가능한 실행경로를 통해 정보를 유출 |
예방법 | • 응용 프로그램이 제공하는 정보와 기능이 가지는 역할에 맞게 분리 개발함으로써 공격자에게 노출되는 공격 노출면(Attack Surface)을 최소화하고 사용자의 권한에 따른 ACL(Access Control List)을 관리 |
========================================= 안전하지 않은 코드 =========================================
def delete_content(request):
action = request.POST.get('action', '')
=========================================== 안전한 코드 =============================================
@login_required
# 해당 기능을 수행할 권한이 있는지 확인
@permission_required('content.delete', raise_exception=True)
def delete_content(request):
[ 문제점 ]
사용자의 권한 확인을 위한 별도의 통제가 적용하지 않음
[ 해결 ]
세션에 저장된 사용자 정보를 통해 해당 사용자가 수행할 작업에 대한 권한이 있는지 확인한 후
권한이 있는 경우에만 작업을 수행
- 중요한 자원에 대한 잘못된 권한 설정 -
중요한 자원에 대한 잘못된 권한 설정 | |
정의 | 응용프로그램이 중요한 보안관련 자원에 대해 읽기 또는 수정하기 권한을 의도하지 않게허가할 경우, 권한을 갖지 않은 사용자가 해당 자원을 사용하게 되는 보안 약점 |
공격 목적 및 발생되는 문제 | 주요 데이터 접근, 권한 외 자원 사용 |
공격 원리 | 사용자 검증 과정이 부적합한 자원에 접근 |
예방법 | • 설정 파일, 실행 파일, 라이브러리 등은 관리자에 의해서만 읽고 쓰기가 가능하도록 설정 • 설정 파일과 같이 중요한 자원을 사용하는 경우 허가 받지 않은 사용자가 중요한 자원에 접근 가능한지 검사 |
========================================= 안전하지 않은 코드 =========================================
# 모든 사용자가 읽기, 쓰기, 실행 권한을 가지게 된다.
os.chmod('/root/system_config', 0o777)
=========================================== 안전한 코드 =============================================
# 소유자 외에는 아무런 권한을 주지 않음.
os.chmod('/root/system_config', 0o700)
[ 문제점 ]
/root/system_config 파일에 대해서 모든 사용자가 읽기, 쓰기, 실행 권한을 가지는 상황
[ 해결 ]
주요 파일에 대해서는 최소 권한만 할당
파일의 소유자라고 하더라도 기본적으로 읽기 권한만 부여해야 하며,
부득이하게 쓰기 권한이 필요한 경우에만 제한적으로 쓰기 권한을 부여
- 취약한 암호화 알고리즘 사용 -
취약한 암호화 알고리즘 사용 | |
정의 | base64, sha123과 같은 취약한 알고리즘 사용시 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 암호문 무력화 |
공격 원리 | 알고리즘을 분석해 무력화 |
예방법 | • 자신만의 암호화 알고리즘을 개발하는 것은 위험 • 학계 및 업계에서 이미 검증된 표준화된 알고리즘을 사용 • DES, RC5 보다는 3TDEA, AES, SEED 등의 안전한 암호알고리즘으로 대체하여 사용 권고 |
========================================= 안전하지 않은 코드 =========================================
# 취약함 암호화 알고리즘인 DES를 사용하여 안전하지 않음
cipher_des = DES.new(key, DES.MODE_ECB)
=========================================== 안전한 코드 =============================================
# 안전한 알고리즘인 AES 를 사용하여 안전함.
cipher_aes = AES.new(key, AES.MODE_CBC, iv)
[ 문제점 ]
취약한 DES 알고리즘으로 암호화
[ 해결 ]
취약한 DES 알고리즘 대신 안전한 AES 암호화 알고리즘을 사용
- 암호화되지 않은 중요정보 -
암호화되지 않은 중요정보 | |
정의 | 중요 정보가 제대로 보호되지 않을 경우, 보안 문제가 발생하거나 데이터의 무결성이 손상 |
공격 목적 및 발생되는 문제 | 정보 유출 |
공격 원리 | 시스템의 중요 정보가 포함된 데이터를 평문으로 송·수신 또는 저장 시 인가되지 않은 사용자 에게 민감한 정보가 노출 |
예방법 | • 개인정보(주민등록번호, 여권번호 등), 금융정보(카드번호, 계좌번호 등), 패스워드 등 중요정보를 저장하거나 통신채널로 전송할 때는 반 드시 암호화 과정을 거쳐야 하며 중요정보를 읽거나 쓸 경우에 권한인증 등을 통해 적합한 사용자만 중요정보에 접근 • SSL 또는 HTTPS 등과 같은 보안 채널을 사용 (필수) • 보안 채널을 사용하지 않고 브라우저 쿠키에 중요 데이터를 저장하는 경우, 쿠키 객체에 보안속성을 설정해(Ex. secure = True) 중요 정보 의 노출을 방지 |
========================================= 안전하지 않은 코드 =========================================
# 암호화되지 않은 패스워드를 DB에 저장
curs.execute(
'UPDATE USERS SET PASSWORD=%s WHERE USER_ID=%s',
password,
user_id
)
=========================================== 안전한 코드 =============================================
ef update_pass(dbconn, password, user_id, salt):
# 단방향 암호화를 이용하여 패스워드를 암호화
hash_obj = SHA256.new()
hash_obj.update(bytes(password + salt, 'utf-8'))
hash_pwd = hash_obj.hexdigest()
curs = dbconn.cursor()
curs.execute(
'UPDATE USERS SET PASSWORD=%s WHERE USER_ID=%s',
(hash_pwd, user_id)
)
[ 문제점 ]
사용자로부터 전달받은 패스워드 암호화 누락
[ 해결 ]
해시 알고리즘을 이용하여 단방향 암호화 이후에 패스워드를 저장
- 하드코드된 중요정보 -
암호화되지 않은 중요정보 | |
정의 | 프로그램 코드 내부에 하드코드된 패스워드를 포함하고, 이를 이용해 내부 인증에 사용하거나 외부 컴포넌트와 통신을 하는 경우 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 정보 노출 |
공격 원리 | 악의적인 사용자가 코드를 열어서 해당 값을 확인 |
예방법 | • 패스워드는 암호화 후 별도의 파일에 저장하여 사용 • 중요 정보 암호화 시 상수가 아닌 암호화 키를 사용 • 암호화가 잘 되었더라도 소스코드 내부에 상수 형태의 암호화 키를 주석으로 달거나 저장하지 않도록 함 |
========================================= 안전하지 않은 코드 =========================================
# user, passwd가 소스코드에 평문으로 하드코딩되어 있음
dbconn = pymysql.connect(
host='127.0.0.1',
port='1234',
user='root',
passwd='1234',
db='mydb',
charset='utf8',
)
=========================================== 안전한 코드 =============================================
with open(config_path, 'r') as config:
# 설정 파일에서 user, passwd를 가져와 사용
dbconf = json.load(fp=config)
# 암호화되어 있는 블록 암호화 키를 복호화 해서 가져오는
# 사용자 정의 함수
blockKey = get_decrypt_key(dbconf['blockKey'])
# 설정 파일에 암호화되어 있는 값을 가져와 복호화한 후에 사용
dbUser = decrypt(blockKey, dbconf['user'])
dbPasswd = decrypt(blockKey, dbconf['passwd'])
dbconn = pymysql.connect(
host=dbconf['host']
port=dbconf['port'],
user=dbUser,
passwd=dbPasswd,
db=dbconf['db_name'],
charset='utf8',
)
[ 문제점 ]
소스코드에 패스워드 또는 암호화 키와 같은 중요 정보를 하드코딩 하는 경우
중요 정보가 노출될 수 있어 위험
[ 해결 ]
패스워드와 같은 중요 정보는 안전한 암호화 방식으로 암호화 후 별도의 분리된 공간(파일)에 저장해야 하며
암호화된 정보 사용 시 복호화 과정을 거친 후 사용
암호화된 키는 API로 사용
암호화된 키는 개발자도 알 수 없어야 함
- 충분하지 않은 키 길이 사용 -
충분하지 않은 키 길이 사용 | |
정의 | 암호화 및 복호화에 사용되는 키의 길이를 짧게 하여 생기는 보안 약점 |
공격 목적 및 발생되는 문제 | 암호화된 데이터와 패스워드의 복호화 유출 |
공격 원리 | 공격자가 짧은 시간 안에 키를 찾아 공격 |
예방법 | • RSA 알고리즘은 적어도 2,048 비트 이상의 길이를 가진 키와 함께 사용 • 대칭 암호화 알고리즘(Symmetric Encryption Algorithm)의 경우에는 적어도 128비트 이상의 키를 사용(암호 강도 112비트 이상) |
========================================= 안전하지 않은 코드 =========================================
def make_rsa_key_pair():
# RSA키 길이를 2048 비트 이하로 설정하는 경우 안전하지 않음
private_key = RSA.generate(1024)
public_key = private_key.publickey()
def make_ecc():
# ECC의 키 길이를 224비트 이하로 설정하는 경우 안전하지 않음
ecc_curve = registry.get_curve('secp192r1')
=========================================== 안전한 코드 =============================================
def make_rsa_key_pair():
# RSA 키 길이를 2048 비트 이상으로 길게 설정
private_key = RSA.generate(2048)
public_key = private_key.publickey()
def make_ecc():
# ECC 키 길이를 224 비트 이상으로 설정
ecc_curve = registry.get_curve('secp224r1')
[ 문제점 ]
보안성이 강한 RSA 알고리즘을 사용하는 경우에도
키 사이즈를 작게 설정하면 프로그램의 보안약점 가능
[ 해결 ]
RSA, DSA의 경우 키의 길이는 적어도 2048 비트를
ECC의 경우 224 비트 이상으로 설정해야 안전
- 적절하지 않은 난수 값 사용 -
적절하지 않은 난수 값 사용 | |
정의 | 예측 가능한 난수를 사용해서 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 데이터 유출, 권한 획득 |
공격 원리 | 난수 발생기를 분석해서 시스템 공격 |
예방법 | • 난수 발생기에서 시드(Seed)를 사용하는 경우에는 고정된 값을 사용하지 않고 예측하기 어려운 방법으로 생성된 값을 사용하는데 python에서 random 모듈은 주로 보안 목적이 아닌 게임, 퀴즈 및 시뮬레이션을 위해 설계된 것을 사용하게 되면 위험 • 세션 ID, 암호화키 등 주요 보안 기능을 위한 값을 생성하고 주요 보안 기능을 수행하는 경우에는 random 모듈보다 암호화 목적으로 설 계된 secrets 모듈을 사용 |
========================================= 안전하지 않은 코드 =========================================
# 시스템 현재 시간 값을 시드로 사용하고 있으며, 주요 보안 기능을 위한
# 난수로 안전하지 않다
for i in range(6):
random_str += str(random.randrange(10))
=========================================== 안전한 코드 =============================================
# 보안기능에 적합한 난수 생성용 secrets 라이브러리 사용
for i in range(6):
random_str += str(secrets.randbelow(10))
[ 문제점 ]
고정된 seed 값을 보안이나 암호를 목적으로 사용하는 취약한 random 라이브러리 적용
[ 해결 ]
secrets 라이브러리를 사용해 6자리의 난수 값을 생성
========================================= 안전하지 않은 코드 =========================================
# random 라이브러리를 보안 기능에 사용하면 위험하다
return “”.join(random.choice(RANDOM_STRING_CHARS) for i in range(32))
=========================================== 안전한 코드 =============================================
# 보안 기능과 관련된 난수는 secrets 라이브러리를 사용해야 안전하다
return “”.join(secrets.choice(RANDOM_STRING_CHARS) for i in range(32))
[ 문제점 ]
random 라이브러리를 사용
[ 해결 ]
패스워드나 인증정보 및 보안토큰 생성에 사용하는 경우
안전한 secrets 라이브러리로 생성한 난수를 이용
- 취약한 패스워드 허용 -
취약한 패스워드 허용 | |
정의 | 사용자에게 강한 패스워드 조합규칙을 요구하지 않아 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 사용자 계정 탈취 |
공격 원리 | 난수 발생기를 분석해서 시스템 공격 |
예방법 | • 패스워드 생성 시 강한 조건 검증을 수행 • 패스워드는 숫자와 영문자, 특수문자 등을 혼합하여 사용하고, 주기적으로 변경하여 사용 • 안전한 패스워드를 생성하기 위해서는 「패스워드 선택 및 이용 안내서」에서 제시하는 패스워드 설정 규칙을 적용 |
========================================= 안전하지 않은 코드 =========================================
if password != confirm_password:
return make_response("패스워드가 일치하지 않습니다", 400)
else:
usertable = User()
usertable.userid = userid
usertable.password = password
# 패스워드 생성 규칙을 확인하지 않고 회원 가입
db.session.add(usertable)
db.session.commit()
return make_response("회원가입 성공", 200)
=========================================== 안전한 코드 =============================================
if password != confirm_password:
return make_response("패스워드가 일치하지 않습니다.", 400)
if not check_password(password):
return make_response("패스워드 조합규칙에 맞지 않습니다.", 400)
else:
usertable = User()
usertable.userid = userid
usertable.password = password
db.session.add(usertable)
db.session.commit()
return make_response("회원가입 성공", 200)
def check_password(password):
# 3종 이상 문자로 구성된 8자리 이상 패스워드 검사 정규식 적용
PT1 = re.compile('^(?=.*[A-Z])(?=.*[a-z])[A-Za-z\d!@#$%^&*]{8,}$')
PT2 = re.compile('^(?=.*[A-Z])(?=.*\d)[A-Za-z\d!@#$%^&*]{8,}$')
PT3 = re.compile('^(?=.*[A-Z])(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$')
PT4 = re.compile('^(?=.*[a-z])(?=.*\d)[A-Za-z\d!@#$%^&*]{8,}$')
PT5 = re.compile('^(?=.*[a-z])(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$')
PT6 = re.compile('^(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,}$')
# 문자 구성 상관없이 10자리 이상 패스워드 검사 정규식
PT7 = re.compile('^[A-Za-z\d!@#$%^&*]{10,}$')
for pattern in [PT1, PT2, PT3, PT4, PT5, PT6, PT7]:
if pattern.match(password):
return True
return False
[ 문제점 ]
사용자가 입력한 패스워드에 대한 복잡도 검증 없이 가입 승인 처리를 수행
[ 해결 ]
사용자 계정 보호를 위해 회원가입 시 패스워드 복잡도와 길이를 검증 후 가입 승인처리를 수행
코드 내의 특수문자(‘!@#$%^&*’)는 기업 내부 정책에 따라 변경하여 사용하면 되며
패스워드를 숫자로만 10자리로 구성할 경우 취약할 수 있으니
사용자가 안전한 패스워드로 변경할 수 있도록 안내
- 부적절한 전자서명 확인 -
부적절한 전자서명 확인 | |
정의 | 프로그램, 라이브러리, 코드의 전자서명에 대한 유효성 검증이 적절하지 않아 공격자의 악의적인 코드가 실행 가능한 보안약점 |
공격 목적 및 발생되는 문제 | 데이터 유출, 권한 획득 |
공격 원리 | 클라이언트와 서버 사이의 주요 데이터 전송, 파일 다운로드 시 변조된 데이터를 통해 발생 |
예방법 | • 주요 데이터 전송 또는 다운로드 시 데이터에 대한 전자서명을 함께 전송하고 수신측에서는 전달 받은 전자 서명을 검증해 파일의 변조 여부를 확인 |
========================================= 안전하지 않은 코드 =========================================
def verify_data(request):
# 클라이언트로부터 전달받은 데이터(전자서명을 수신 처리 하지 않음)
encrypted_code = request.POST.get("encrypted_msg", "") # 암호화된 파이썬 코드
# 서버의 대칭키 로드 (송수신측이 대칭키를 이미 공유했다고 가정)
with open(f"{PATH}/keys/secret_key.out", "rb") as f:
secret_key = f.read()
# 대칭키로 클라이언트가 전달한 파이썬 코드 복호화
# (decrypt_with_symmetric_key 함수는 임의의 함수명으로 세부적인 복호화 과정은 생략함)
origin_python_code = decrypt_with_symmetric_key(secret_key, encrypted_code)
# 클라이언트로부터 전달 받은 파이썬 코드 실행
eval(origin_python_code)
return render(
request,
"/verify_success.html",
{"result": "파이썬 코드를 실행했습니다."},
)
=========================================== 안전한 코드 =============================================
# 전자서명 검증에 사용한 코드는 의존한 파이썬 패키지 및 송신측 언어에 따라
# 달라질 수 있으며, 사전에 공유한 공개키로 복호화한 전자서명과 원본 데이터 해시값의
# 일치 여부를 검사하는 코드를 포함
def verify_digit_signature (
origin_data: bytes, origin_signature: bytes, client_pub_key: str ) -> bool:
hashed_data = SHA256.new(origin_data)
signer = SIGNATURE_PKCS1_v1_5.new(RSA.importKey(client_pub_key))
return signer.verify(hashed_data, base64.b64decode(origin_signature))
def verify_data(request):
# 클라이언트로부터 전달받은 데이터
encrypted_code = request.POST.get("encrypted_msg", "") # 암호화된 파이썬 코드
encrypted_sig = request.POST.get("encrypted_sig", "") # 암호화된 전자서명
# 서버의 대칭(비밀)키 및 공개키 로드
with open(f"/keys/secret_key.out", "rb") as f:
secret_key = f.read()
with open(f"/keys/public_key.out", "rb") as f:
public_key = f.read()
# 대칭키로 파이썬 코드 및 전자서명 복호화
origin_python_code = decrypt_with_symmetric_key(symmetric_key, encrypted_code)
origin_signature = decrypt_with_symmetric_key(symmetric_key, encrypted_sig)
# 클라이언트의 공개키를 통해 파이썬 코드(원문)와 전자서명을 검증
verify_result = verify_digit_signature(origin_python_code, origin_signature, client_pub_key)
# 전자서명 검증을 통과했다면 파이썬 코드 실행
if verify_result:
eval(origin_python_code)
return render(request, "/verify_success.html",
{"result": "전자서명 검증 통과 및 파이썬 코드를 실행했습니다."},
)
else:
return render(request, "/verify_failed.html",
{"result": "전자서명 또는 파이썬 코드가 위/변조되었습니다."},
)
[ 문제점 ]
송신측이 데이터와 함께 전달한 전자서명을 수신측에서 별도로 처리하지 않고
데이터를 그대로 신뢰해 데이터 내부에 포함된 파이썬 코드가 실행
[ 해결 ]
중요한 정보 또는 기능 실행으로 연결되는 데이터를 전달하는 경우, 반드시 전자서명을 함께 전송해야 하며
수신측에서는 전자서명을 확인해 송신측에서 보낸 데이터의 무결성을 검증
만약 송수신 측 언어가 다른 경우 사용한 암호 라이브러리에 따라 데이터 인코딩 방식에 차이가 있으니
반드시 코드 배포 전 서명 검증에 필요한 복호화 과정이 정상적으로 잘 처리되는지 검증
- 부적절한 인증서 유효성 검증 -
부적절한 인증서 유효성 검증 | |
정의 | 인증서의 유효성 검사가 적절치 않아 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 정보 유출, 사용자 데이터 무결성 손상 |
공격 원리 | 공격자가 호스트와 클라이언트 사이의 통신 구간을 가로채 신뢰하는 엔티티 인 것처럼 속이고 이로 인해 대상 호스트가 신뢰 가능한 것으로 믿고 악성 호스트에 연결하거나 신뢰된 호스트로부터 전달받은 것처럼 보이는 스푸핑 된(또는 변조된 데이터)를 수신 |
예방법 | • 데이터 통신에 인증서를 사용하는 경우 송신측에서 전달한 인증서가 유효한지 검증한 후 데이터를 송수신 • 언어에서 기본으로 제공되는 검증 함수가 존재하지 않거나 일반적이지 않은 방식으로 인증서를 생성한 경우 암호화 패키지를 사용해 별 도의 검증 코드를 작성 |
========================================= 안전하지 않은 코드 =========================================
HOST, PORT = "127.0.0.1", 7917
def connect_with_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
# 보안 정책 수동 설정
context = ssl.SSLContext()
# SSLContext 생성자를 직접 호출할 때, CERT_NONE이 기본값
# 상대방을 인증하지 않기 때문에 통신하고자하는 서버의 신뢰성을 보장할 수 없음
context.verify_mode = ssl.CERT_NONE
with context.wrap_socket(sock) as ssock:
try:
=========================================== 안전한 코드 =============================================
CURRENT_PATH = os.getcwd()
HOST_NAME = "test-server"
HOST, PORT = "127.0.0.1", 7917
SERVER_CA_PEM = f"{CURRENT_PATH}/rsa_server/CA.pem" # 서버로부터 전달받은 CA 인증서
def connect_with_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
# PROTOCOL_TLS_CLIENT 프로토콜을 추가하여 인증서 유효성 검사와 호스트 이름 확인을 위한
# context를 구성. verify_mode가 CERT_REQUIRED로 설정됨
# check_hostname이 True로 설정됨
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# 서버로부터 전달받은 CA 인증서를 context에 로드
# CERT_REQUIRED로 인해 필수
context.load_verify_locations(SERVER_CA_PEM)
# 호스트 이름(HOST_NAME)이 일치하지 않으면 통신 불가
# 생성된 소켓과 context wrapping 시 server_hostname이 실제 서버에서
# 등록(server.csr)한 호스트 명과 일치해야 함
with context.wrap_socket(sock, server_hostname=HOST_NAME) as ssock:
try:
[ 문제점 ]
클라이언트 측에서 통신 대상 서버를 인증하지 않고 접속하는 상황
서버를 신뢰할 수 없으며 클라이언트 시스템에 영향을 주는 악성 데이터를 수신
[ 해결 ]
SSL 연결 시 PROTOCOL_TLS_CLIENT 프로토콜을 추가해 인증서 유효성 검사와
호스트 이름 확인을 위한 context를 구성하면 verify_mode가 CERT_REQUIRED로 설정되며
서버의 인증서 유효성을 검증
- 사용자 하드디스크에 저장되는 쿠키를 통한 정보 노출 -
사용자 하드디스크에 저장되는 쿠키를 통한 정보 노출 | |
정의 | 개인정보, 인증 정보 등이 영속적인 쿠키(Persistent Cookie)에 저장되어 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 정보 유출 |
공격 원리 | 쿠키를 탈취해 정보 유출 |
예방법 | • 쿠키의 만료시간은 세션 지속 시간을 고려하여 최소한으로 설정하고 영속적인 쿠키에는 사용자 권한 등급, 세션 ID 등 중요 정보가 포함하지 않는다 |
========================================= 안전하지 않은 코드 =========================================
# 쿠키의 만료시간을 1년으로 과도하게 길게 설정하고 있어 안전하지 않다
res.set_cookie('rememberme', 1, max_age=60*60*24*365)
=========================================== 안전한 코드 =============================================
# 쿠키의 만료시간을 적절하게 부여하고 secure 및 httpOnly 옵션을 활성화 한다.
res.set_cookie('rememberme', 1, max_age=60*60, secure=True, httponly=True)
[ 문제점 ]
쿠키의 만료시간을 과도하게 길게 설정
[ 해결 ]
만료 시간은 해당 기능에 맞춰 최소로 설정
- 주석문 안에 포함된 시스템 주요 정보 -
주석문 안에 포함된 시스템 주요 정보 | |
정의 | 주석문에 정보(아이디, 패스워드 등)가 입력되어 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 의도치 않은 정보 유출 |
공격 원리 | 소스코드에 접근 후 주석을 검색하여 정보 획득 |
예방법 | • 주석에는 아이디, 패스워드 등 보안과 관련된 내용을 기입하지 말 것 |
========================================= 안전하지 않은 코드 =========================================
# 주석문에 포함된 중요 시스템의 인증 정보
# id = admin
# passwd = passw0rd
result = login(id, passwd)
=========================================== 안전한 코드 =============================================
# 주석문에 포함된 민감한 정보는 삭제
result = login(id, passwd)
[ 문제점 ]
편리성을 위해 아이디, 패스워드 등 중요정보를 주석문 안에 작성
[ 해결 ]
프로그램 개발 시에 주석문 등에 남겨놓은 사용자 계정이나 패스워드 등의 정보는 개발 완료 후 확실하게 삭제
- 솔트 없이 일방향 해시 함수 사용 -
솔트 없이 일방향 해시 함수 사용 | |
정의 | 중요정보를 저장할 때 무작위 문자열이나 숫자를 넣어준느 솔트(salt)를 사용하지 않아 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 해시값 유출 |
공격 원리 | 해시값이 미리 계산된 레인보우 테이블을 이용해 해시값 해독 |
예방법 | • 패스워드와 같은 중요 정보를 저장할 경우, 임의의 길이인 데이터를 고정된 크기의 해시값으로 변환해주는 일방향 해시함수를 이용하여 저장 • 솔트값은 사용자별로 유일하게 생성해야 하며, 이를 위해 사용자별 솔트 값을 별도로 저장하는 과정이 필요 |
========================================= 안전하지 않은 코드 =========================================
# salt 없이 생성된 해시값은 강도가 약해 취약하다
h = hashlib.sha256(pw.encode())
=========================================== 안전한 코드 =============================================
# 보안기능에 적합한 난수 생성용 secrets 라이브러리 사용
# 솔트 값을 사용하면 길이가 짧은 패스워드로도 고강도의 해시를 생성할 수 있다.
# 솔트 값은 사용자별로 유일하게 생성해야 하며, 패스워드와 함께 DB에 저장해야 한다
salt = secrets.token_hex(32)
h = hashlib.sha256(salt.encode() + pw.encode())
[ 문제점 ]
salt 없이 길이가 짧은 패스워드를 해시 함수에 전달해 원문이 공격자에 의해 쉽게 유추
[ 해결 ]
짧은 길이의 패스워드로 강도 높은 해시값을 생성하기 위해서는 반드시 솔트 값을 함께 전달
- 무결성 검사 없는 코드 다운로드 -
무결성 검사 없는 코드 다운로드 | |
정의 | 원격지에 위치한 소스코드 또는 실행 파일을 무결성 검사 없이 다운로드 후 이를 실행하여 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 데이터 무결성 위협, 악성 코드 삽입, 백도어 생성 |
공격 원리 | 호스트 서버의 변조, DNS 스푸핑(Spoofing) 또는 전송 시의 코드 변조 등의 방법을 이용해 공격자가 악의적인 코드를 실행 |
예방법 | • DNS 스푸핑(Spoofing)을 방어할 수 있는 DNS lookup을 수행하고 코드 전송 시 신뢰할 수 있는 암호 기법을 이용해 코드를 암호화함 • 다운로드한 코드는 작업 수행을 위해 필요한 최소한의 권한으로 실행 하도록 함 • 소스코드는 신뢰할 수 있는 사이트에서만 다운로드해야 하고 파일의 인증서 또는 해시값을 검사해 변조되지 않은 파일인지 확인하여야 함 |
========================================= 안전하지 않은 코드 =========================================
# 신뢰할 수 없는 사이트에서 코드를 다운로드
url = "https://www.somewhere.com/storage/code.py"
# 원격 코드 다운로드
file = requests.get(url)
remote_code = file.content
=========================================== 안전한 코드 =============================================
config = configparser.RawConfigParser()
config.read(‘sample_config.cfg’)
url = "https://www.somewhere.com/storage/code.py"
remote_code_hash = config.get('HASH', 'file_hash')
# 원격 코드 다운로드
file = requests.get(url)
remote_code = file.content
sha = hashlib.sha256()
sha.update(remote_code)
# 다운로드 받은 파일의 해시값 검증
if sha.hexdigest() != remote_code_hash:
raise Exception(‘파일이 손상되었습니다.’)
[ 문제점 ]
원격에서 파일을 다운로드한 뒤 파일에 대한 무결성 검사를 수행하지 않아 파일 변조 등으로 인한 피해가 발생
[ 해결 ]
다운로드한 파일과 해당 파일의 해시값 비교 등을 통해 무결성 검사를 거치고 코드를 실행
- 반복된 인증시도 제한 기능 부재 -
반복된 인증시도 제한 기능 부재 | |
정의 | 일정 시간 내에 여러 번의 인증 시도 시 계정 잠금 또는 추가 인증 방법 등의 충분한 조치가 수행되지 않아 발생하는 보안 약점 |
공격 목적 및 발생되는 문제 | 계정 탈취 |
공격 원리 | 공격자는 성공할 법한 계정과 패스워드들을 사전(Dictionary)으로 만들고 무차별 대입(brute-force)하여 로그인 성공 및 권한 획득 |
예방법 | • 최대 인증시도 횟수를 적절한 횟수로 제한하고 설정된 인증 실패 횟수를 초과할 경우 계정을 잠금 하거나 추가적인 인증 과정을 거쳐서 시스템에 접근이 가능하도록 한다 • 최대 인증시도 횟수를 적절한 횟수로 제한하고 설정된 인증 실패 횟수를 초과할 경우 계정을 잠금 하거 나 추가적인 인증 과정을 거쳐서 시스템에 접근이 가능하도록 한다 • 인증시도 횟수를 제한하는 방법 외에 CAPTCHA나 Two-Factor 인증 방법도 설계 시부터 고려 필요 |
========================================= 안전하지 않은 코드 =========================================
# 인증 시도에 따른 제한이 없어 반복적인 인증 시도가 가능
if sha.hexdigest() == hashed_passwd:
return render(request, '/index.html', {'state':'login_success'})
else:
return render(request, '/login.html', {'state':'login_failed'})
=========================================== 안전한 코드 =============================================
if sha.hexdigest() == hashed_passwd:
# 로그인 성공 시 실패 횟수 삭제
LoginFail.objects.filter(user_id=user_id).delete()
return render(request, '/index.html', {'state':'login_success'})
# 로그인 실패 기록 가져오기
if LoginFail.objects.filter(user_id=user_id).exists():
login_fail = LoginFail.objects.get(user_id=user_id)
COUNT = login_fail.count
else:
COUNT = 0
if COUNT >= LOGIN_TRY_LIMIT:
# 로그인 실패횟수 초과로 인해 잠금된 계정에 대한 인증 시도 제한
return render(request, "/account_lock.html", {"state": "account_lock"})
else:
# 로그인 실패 횟수 DB 기록
# 첫 시도라면 DB에 insert,
# 실패 기록이 존재한다면 update
LoginFail.objects.update_or_create(
user_id=user_id,
defaults={"count": COUNT + 1},
)
[ 문제점 ]
사용자 로그인 시도에 대한 횟수를 제한하지 않는 코드
[ 해결 ]
사용자 로그인 시도에 대한 횟수를 제한하여 무차별 공격에 대응
'SK shieldus Rookies 16기' 카테고리의 다른 글
[SK shieldus Rookies 16기] 클라우드 기반 스마트 융합 보안 과정 교육 정리(12일차) (0) | 2023.11.07 |
---|---|
[SK shieldus Rookies 16기] 클라우드 기반 스마트 융합 보안 과정 교육 정리(11일차) (0) | 2023.11.06 |
[SK shieldus Rookies 16기] 클라우드 기반 스마트 융합 보안 과정 교육 정리(9일차) (0) | 2023.11.02 |
[SK shieldus Rookies 16기] 클라우드 기반 스마트 융합 보안 과정 교육 정리(8일차) (0) | 2023.11.01 |
[SK shieldus Rookies 16기] 클라우드 기반 스마트 융합 보안 과정 교육 정리(7일차) (0) | 2023.10.31 |