취약한 사용자 인증(Broken User Authentication)은 애플리케이션이 사용자 인증을 적절하게 처리하지 못해, 공격자가 다른 사용자의 계정에 무단으로 접근할 수 있는 취약성입니다. 이는 약한 비밀번호 정책, 세션 관리의 부적절함, 다중 인증 미사용 등으로 인해 발생할 수 있습니다.
잠재적 영향
계정 탈취: 공격자가 사용자 계정을 탈취하여 민감한 정보에 접근할 수 있습니다.
권한 상승: 공격자가 관리자 권한을 획득하여 시스템을 조작하거나 손상시킬 수 있습니다.
데이터 유출: 민감한 데이터가 공격자에게 노출될 수 있습니다.
해결 방법
강력한 비밀번호 정책: 최소 길이, 복잡성 요구 사항 등을 설정하여 강력한 비밀번호를 사용하게 합니다.
다중 인증(MFA): 추가적인 인증 수단을 사용하여 보안을 강화합니다.
세션 관리 강화: 세션 타임아웃, 세션 고정 공격 방지 등을 구현합니다.
보안 라이브러리 사용: Django, Flask, FastAPI 등과 같은 프레임워크에서 제공하는 인증 및 세션 관리 기능을 사용합니다.
취약한 코드 및 안전한 코드 예시
Unsafe Django code & Safe Django code
설명:
취약한 코드: 비밀번호 정책이 없고, 세션 관리가 부적절하여 공격자가 쉽게 계정을 탈취할 수 있습니다.
안전한 코드: 강력한 비밀번호 정책을 적용하고, 로그인 성공 시 세션을 안전하게 관리하며, 민감한 데이터 접근을 보호합니다.
Unsafe Flask code & Safe Flask code
설명:
취약한 코드: 비밀번호 정책이 없고, 세션 관리가 부적절하여 공격자가 쉽게 계정을 탈취할 수 있습니다.
안전한 코드: 강력한 비밀번호 정책을 적용하고, 로그인 성공 시 세션을 안전하게 관리하며, 민감한 데이터 접근을 보호합니다.
Unsafe FastAPI code & Safe FastAPI code
설명:
취약한 코드: 비밀번호 검증이 약하고, 세션 관리가 부적절하여 공격자가 쉽게 계정을 탈취할 수 있습니다.
안전한 코드: 강력한 비밀번호 정책을 적용하고, 토큰 기반 인증을 사용하여 세션을 안전하게 관리하며, 민감한 데이터 접근을 보호합니다.
# Unsafe Django code
from django.contrib.auth import authenticate
def login_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
# 로그인 성공
...
else:
# 로그인 실패
...
# Safe Django code
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required
from django.conf import settings
import re
def login_view(request):
username = request.POST['username']
password = request.POST['password']
if not re.match(settings.USERNAME_REGEX, username) or not re.match(settings.PASSWORD_REGEX, password):
return HttpResponse("Invalid credentials", status=400)
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# 로그인 성공
...
else:
# 로그인 실패
...
@login_required
def sensitive_view(request):
# 민감한 데이터 처리 로직
return HttpResponse("Sensitive data")
# Unsafe Flask code
from flask import Flask, request
from werkzeug.security import check_password_hash
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password, password):
# 로그인 성공
...
else:
# 로그인 실패
...
# Safe Flask code
from flask import Flask, request, session, redirect, url_for
from werkzeug.security import check_password_hash
from flask_login import LoginManager, login_user, login_required
import re
app = Flask(__name__)
login_manager = LoginManager()
login_manager.init_app(app)
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
if not re.match(USERNAME_REGEX, username) or not re.match(PASSWORD_REGEX, password):
return "Invalid credentials", 400
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password, password):
login_user(user)
# 로그인 성공
return redirect(url_for('dashboard'))
else:
# 로그인 실패
return "Invalid credentials", 401
@login_required
@app.route('/dashboard')
def dashboard():
# 민감한 데이터 처리 로직
return "Sensitive data"
# Unsafe FastAPI code
from fastapi import FastAPI, Request
app = FastAPI()
@app.post("/login")
async def login(request: Request):
form_data = await request.form()
username = form_data['username']
password = form_data['password']
user = get_user_by_username(username)
if user and user.password == password:
# 로그인 성공
...
else:
# 로그인 실패
...
# Safe FastAPI code
from fastapi import FastAPI, Request, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi_login import LoginManager
import re
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
manager = LoginManager("SECRET", token_url="/auth/token")
@manager.user_loader
def load_user(username: str):
return get_user_by_username(username)
@app.post("/auth/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
username = form_data.username
password = form_data.password
if not re.match(USERNAME_REGEX, username) or not re.match(PASSWORD_REGEX, password):
raise HTTPException(status_code=400, detail="Invalid credentials")
user = load_user(username)
if user and user.password == password:
access_token = manager.create_access_token(data={"sub": username})
return {"access_token": access_token, "token_type": "bearer"}
else:
raise HTTPException(status_code=401, detail="Invalid credentials")
@app.get("/dashboard")
async def dashboard(current_user = Depends(manager)):
# 민감한 데이터 처리 로직
return {"detail": "Sensitive data"}