포스트

Authentication Vulnerabilities (PortSwigger Academy) - MFA 취약점

Multi-Factor Authentication 구현에서 발생할 수 있는 취약점과 2FA 우회 방식을 정리

Authentication Vulnerabilities (PortSwigger Academy) - MFA 취약점

Multi-Factor Authentication 취약점

많은 웹사이트는 아직도 비밀번호 하나만으로 로그인을 처리한다.
이 방식은 구현이 단순하고 사용하기도 편하지만, 공격자 입장에서도 익숙한 구조다. 비밀번호가 유출되거나 재사용된 경우에는 계정이 그대로 노출될 수 있다.

이런 한계를 보완하기 위해 등장한 것이 Multi-Factor Authentication(MFA) 이다.
사용자가 단순히 비밀번호만 알고 있는지 확인하는 데서 끝나는 것이 아니라, 서로 다른 종류의 인증 요소를 함께 확인하는 방식이다.

보통 다음과 같은 요소들이 사용된다.

  • Something you know — 비밀번호, PIN
  • Something you have — OTP 토큰, 스마트폰
  • Something you are — 지문, 얼굴 인식

이 중 두 가지를 함께 사용하는 형태를 일반적으로 Two-Factor Authentication(2FA) 라고 부른다.

비밀번호 하나만 노리는 공격은 현실에서 흔하게 발생한다. 하지만 비밀번호와 별도의 물리적 인증 수단까지 동시에 탈취하는 것은 훨씬 어렵다.
그래서 2FA는 단일 인증 방식보다 분명히 더 안전한 구조다.

다만 보안 기능은 언제나 구현 방식에 따라 실제 효과가 달라진다.
2FA 역시 설계가 허술하면 공격자가 우회하거나 심지어 완전히 무력화할 수 있다.


동일한 인증요소를 사용하는 2FA의 문제

MFA의 핵심은 서로 다른 인증 요소를 함께 검증하는 것이다.
겉보기에는 두 단계를 거치더라도 실제로는 같은 인증 요소를 두 번 확인하는 구조라면 기대하는 만큼의 보안 효과가 나오지 않는다.

대표적인 예가 이메일 기반 2FA다.

사용자가 비밀번호를 입력하고 이어서 이메일로 받은 인증 코드를 입력한다고 가정해 보자.
겉으로 보면 비밀번호 + 코드 두 단계를 거치는 것처럼 보이지만, 이메일에 접근하는 것 자체가 결국 이메일 계정의 로그인 정보를 알고 있는지에 달려 있다.

결국 이 경우에는 서로 다른 두 인증 요소를 사용하는 것이 아니라,
knowledge factor를 다른 방식으로 한 번 더 확인하는 구조가 된다.

그래서 이런 방식은 “2단계 인증”처럼 보일 수는 있지만, 엄밀하게 보면 강한 의미의 MFA라고 보기는 어렵다.


2FA 토큰은 어떻게 전달되는가

2FA에서 사용되는 인증 코드는 보통 사용자가 가지고 있는 장치에서 확인한다.
보안이 중요한 서비스에서는 전용 하드웨어 토큰을 제공하기도 한다. 은행 보안 토큰이나 회사 VPN 로그인 장치를 떠올리면 이해하기 쉽다.

이런 장치들은 보통 기기 자체에서 인증 코드를 생성한다.
코드가 네트워크를 통해 전달되는 구조가 아니기 때문에 상대적으로 안전하다.

비슷한 이유로 Google Authenticator 같은 OTP 앱도 널리 사용된다.
이 역시 스마트폰에서 직접 코드를 생성한다는 점이 핵심이다.

반면 일부 사이트는 인증 코드를 SMS 문자로 전송한다.
형식상으로는 여전히 “something you have”를 확인하는 방식이지만, 보안적으로는 몇 가지 약점이 존재한다.

첫 번째 문제는 코드가 기기에서 생성되는 것이 아니라 통신망을 통해 전달된다는 점이다.
이 과정에서 코드가 가로채질 가능성이 생긴다.

또 하나의 위험 요소는 SIM swapping 공격이다.
공격자가 피해자의 전화번호로 새 SIM 카드를 발급받는 데 성공하면, 피해자에게 전달되는 모든 문자 메시지를 가로챌 수 있다. 그 안에는 2FA 인증 코드도 포함될 수 있다.

그래서 같은 2FA라고 해도 코드가 생성되는 방식과 전달 방식에 따라 보안 수준 차이가 꽤 크다.


2FA가 아예 우회되는 경우

2FA가 적용되어 있으면 최소한 인증 코드까지는 검증할 것이라고 기대하게 된다.
하지만 실제 서비스들을 보면 구현이 허술해서 두 번째 단계가 사실상 의미 없는 경우도 존재한다.

예를 들어 로그인 흐름이 다음과 같다고 가정해 보자.

  1. username / password 입력
  2. 서버가 이를 확인하고 2FA 코드 입력 페이지로 이동
  3. 인증 코드 입력

겉보기에는 정상적인 2단계 로그인처럼 보인다.
문제는 어떤 시스템에서는 첫 번째 단계가 끝난 시점에 이미 로그인 세션이 생성되는 경우가 있다는 점이다.

이런 경우에는 한 가지를 확인해 볼 필요가 있다.

2FA 코드를 입력하지 않은 상태에서도 로그인 후 전용 페이지에 접근할 수 있는가?

만약 서버가 실제로 두 번째 단계를 제대로 검증하지 않는다면, 사용자는 2FA를 완료하지 않았는데도 계정 페이지나 민감한 기능에 접근할 수 있다.

이 경우는 흔히 2FA simple bypass로 이어진다.


Flawed two-factor verification logic

또 다른 문제는 2FA 단계가 서로 제대로 연결되지 않는 경우다.
즉, 첫 번째 단계에서 로그인한 사용자와 두 번째 단계에서 인증 코드를 제출하는 사용자가 같은지 제대로 확인하지 않는 상황이다.

예를 들어 로그인 과정이 다음과 같이 동작한다고 가정해 보자.

사용자는 먼저 일반 로그인 단계에서 username과 password를 입력한다.

1
2
3
4
POST /login-steps/first HTTP/1.1
Host: vulnerable-website.com

username=shane&password=blog

서버는 로그인 정보를 확인한 뒤 2FA 단계로 넘기면서 계정을 식별하기 위한 쿠키를 내려준다.

1
2
HTTP/1.1 200 OK
Set-Cookie: account=shane

그 다음 사용자는 두 번째 인증 단계 페이지로 이동한다.

1
2
GET /login-steps/second HTTP/1.1
Cookie: account=shane

이후 인증 코드를 제출할 때 서버는 이 쿠키 값을 이용해 어떤 계정에 대해 2FA를 검증해야 하는지 판단한다.

1
2
3
4
5
POST /login-steps/second HTTP/1.1
Host: vulnerable-website.com
Cookie: account=shane

verification-code=123456

문제는 서버가 이 쿠키 값을 그대로 신뢰하는 경우다.

공격자는 자신의 계정으로 1단계 로그인을 통과한 뒤,
2단계 요청을 보낼 때 account 쿠키 값을 다른 사용자로 변경할 수 있다.

1
2
3
4
5
POST /login-steps/second HTTP/1.1
Host: vulnerable-website.com
Cookie: account=victim-user

verification-code=123456

이렇게 되면 서버는 공격자가 실제로는 자신의 계정으로 로그인했음에도 불구하고,
마치 다른 사용자의 2FA 인증을 검증하는 것처럼 처리하게 된다.

겉보기에는 단순한 쿠키 조작처럼 보이지만, 실제로는 꽤 심각한 문제다.
서버가 인증 흐름을 세션으로 관리하지 않고 클라이언트가 보내는 값에 의존하고 있기 때문이다.


왜 위험한가?

이 취약점이 특히 위험해지는 경우는 인증 코드 brute-force가 가능한 상황이다.

공격자는 다음과 같은 방식으로 공격을 진행할 수 있다.

  1. 자신의 계정으로 로그인 1단계 통과
  2. 2FA 요청에서 account 값을 피해자 계정으로 변경
  3. 인증 코드를 반복적으로 시도

이 경우 공격자는 피해자의 비밀번호를 알 필요조차 없다.
username만 알고 있다면 인증 코드를 brute-force하는 방식으로 계정 접근이 가능해질 수 있다.

원래 2FA는 비밀번호 유출 상황에서도 계정을 보호하기 위한 추가 방어선이다.
하지만 이런 구현에서는 오히려 비밀번호 없이 계정 접근이 가능한 구조가 될 수 있다.


방어 관점에서 볼 포인트

이런 문제를 막기 위해서는 두 번째 인증 단계가 단순히 “코드가 맞는지”만 확인해서는 안 된다.

서버는 다음 두 가지를 반드시 확인해야 한다.

  • 첫 번째 인증을 통과한 사용자가 누구인지
  • 현재 인증 코드를 제출하는 사용자가 동일한 계정인지

특히 다음과 같은 구현 방식은 피하는 것이 좋다.

  • 2FA 대상 계정을 클라이언트 쿠키 값으로 식별하는 방식
  • 2FA 완료 전인데도 완전한 로그인 세션을 생성하는 방식
  • 인증 코드에 대한 시도 횟수 제한이 없는 방식

2FA는 단순히 인증 단계를 하나 더 추가하는 기능이 아니다.
각 단계가 서로 정확하게 연결되어 있어야 하며, 그 흐름을 서버가 확실하게 통제해야 한다.


정리

2FA는 비밀번호 기반 인증보다 훨씬 강력한 보호 수단이다.
하지만 그 효과는 서로 다른 인증 요소를 올바르게 검증하고 각 인증 단계가 안전하게 연결되어 있을 때만 제대로 나타난다.

이메일처럼 동일한 인증 요소를 다른 방식으로 확인하는 구조는 기대하는 만큼 강력하지 않을 수 있고,
SMS 기반 인증은 가로채기나 SIM swapping 같은 위험을 안고 있다.

무엇보다 중요한 것은 구현 로직 자체의 안정성이다.
첫 번째 인증 단계만으로 로그인 상태가 생성되거나, 2FA 검증 대상 계정을 클라이언트 입력에 의존하는 구조는 인증 체계를 쉽게 무력화할 수 있다.

결국 MFA의 핵심은 “단계를 몇 개 추가했는가”가 아니라
각 단계가 무엇을 검증하고 있으며 서버가 그 흐름을 얼마나 엄격하게 관리하는가에 있다.

BurpSuite 예제

Lab: 2FA borken logic


출처: PortSwigger Academy

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.