인증 손상(Authentication Broken)

  • 웹 애플리케이션의 인증 메커니즘이 취약하여 공격자가 불법적으로 계정에 접근할 수 있는 보안 문제
  • 잘못된 인증 구현, 약한 비밀번호 정책, 인증 우회 등의 원인으로 발생한다

인증 구현 (Authentication Implementation)

웹 애플리케이션에서 인증은 사용자의 신원을 확인하는 과정으로 다양한 방법으로 구현할 수 있다.


1. HTTP 네이티브 인증(HTTP Native Authentication)

  • HTTP 네이티브 인증은 브라우저와 서버 간의 기본적인 인증 방식으로, HTTP 프로토콜에서 제공하는 인증 메커니즘을 활용
  • 대표적인 방식으로는 Basic Authentication과 Digest Authentication이 있다.

1) Basic Authentication (기본 인증)

클라이언트가 서버에 요청을 보낼 때, username:password 형태의 정보를 Base64로 인코딩하여 Authorization 헤더에 포함한다.
Base64는 단순한 인코딩 방식이므로 보안성이 낮으며, 반드시 HTTPS와 함께 사용해야 한다.

GET /protected HTTP/1.1
Host: example.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=  // Base64(username:password)

문제점

  1. 패킷을 가로채면 Base64 인코딩된 인증 정보를 쉽게 디코딩할 수 있다.
  2. 인증 정보가 클라이언트에 지속적으로 저장되므로 노출 위험이 크다.

2) Digest Authentication (다이제스트 인증)

Basic Authentication의 보안 문제를 개선한 방식으로, 암호화된 해시값을 사용하여 인증한다.
하지만, 현대 웹에서는 거의 사용되지 않으며, 대신 OAuth, JWT 등의 방식이 선호된다.


2. 네이티브하지않은 인증(Non-Native Authentication)

네이티브하지 않은 인증 방식은 웹 애플리케이션에서 HTTP의 기본 인증이 아닌 사용자 정의 방식으로 구현된 인증을 의미한다.

1) 세션 기반 인증 (Session-Based Authentication)

  • 사용자가 로그인하면 서버에서 세션을 생성하고, 세션 ID를 쿠키에 저장하여 인증을 유지하는 방식
  • 서버가 사용자 상태를 관리하므로 보안성이 높지만, 서버 부담이 크다
const session = require("express-session");

app.use(
  session({
    secret: "secret_key",
    resave: false,
    saveUninitialized: true,
    cookie: { secure: true },
  })
);

app.post("/login", (req, res) => {
  req.session.user = { id: 1, name: "Kimsu" };
  res.send("로그인 성공");
});
  • ✅ 장점: 서버에서 관리하므로 보안성이 높다.
  • ❌ 단점: 다중 서버 환경에서는 세션 동기화가 필요하다.

(2) 토큰 기반 인증 (Token-Based Authentication)

  • 사용자가 로그인하면 JWT(JSON Web Token)과 같은 토큰을 발급받아 인증하는 방식
  • 클라이언트는 요청 시 이 토큰을 Authorization 헤더에 포함하여 서버에 보냄
  • 서버에서는 토큰의 유효성을 검증하여 인증을 수행
// JWT 기반 인증
const jwt = require("jsonwebtoken");

app.post("/login", (req, res) => {
  const user = { id: 1, name: "김수정" };
  const token = jwt.sign(user, "secret_key", { expiresIn: "1h" });
  res.json({ token });
});

app.get("/protected", (req, res) => {
  const token = req.headers.authorization?.split(" ")[1];
  jwt.verify(token, "secret_key", (err, decoded) => {
    if (err) return res.status(401).send("인증 실패");
    res.send(`환영합니다, ${decoded.name}`);
  });
});
  • ✅ 장점: 상태를 저장하지 않으므로 확장성이 높음.
  • ❌ 단점: 토큰이 탈취되면 만료 전까지 공격자가 사용할 수 있음.

3) OAuth 및 OpenID 인증

  • OAuth는 소셜 로그인(예: Google, Facebook, Kakao 로그인)에 사용된다.
  • 사용자 대신 OAuth 제공자(Google 등)가 인증을 수행하며, 액세스 토큰을 발급하여 애플리케이션이 사용자 정보를 가져올 수 있다.

  • ✅ 장점: 사용자가 별도의 회원가입 없이 인증 가능.
  • ❌ 단점: OAuth 제공자의 장애가 발생하면 로그인 불가.


3. 무작위 대입 공격(brute-force attack)

  • 공격자가 가능한 모든 조합의 아이디와 비밀번호를 시도하여 인증을 우회하는 공격 방식

1) 공격 방식

(1) 사전 공격(Dictionary Attack)

일반적으로 사용되는 비밀번호 리스트(예: 123456, password, qwerty 등)를 활용하여 로그인 시도

(2) 일반 무작위 대입 공격

모든 가능한 문자 조합을 시도하여 비밀번호를 찾음

(3) 크리덴셜 스터핑(Credential Stuffing)

다른 사이트에서 유출된 계정 정보를 사용하여 로그인 시도

2) 방어 방법

(1) 계정 잠금

일정 횟수 이상 로그인 실패 시 계정을 일시적으로 잠금

(2) CAPTCHA 사용

비정상적인 로그인 시도를 방지

(3) 비밀번호 복잡성 요구

영문, 숫자, 특수문자를 조합한 비밀번호 사용

(4) 2FA (Two-Factor Authentication, 이중 인증)

로그인 시 추가적인 인증을 요구

// 로그인 시도 제한
const loginAttempts = {};

app.post("/login", (req, res) => {
  const { username, password } = req.body;

  if (loginAttempts[username] && loginAttempts[username] >= 5) {
    return res.status(429).send("로그인 시도 제한됨");
  }

  if (username === "admin" && password === "securepassword") {
    loginAttempts[username] = 0; // 로그인 성공 시 초기화
    return res.send("로그인 성공");
  }

  loginAttempts[username] = (loginAttempts[username] || 0) + 1;
  res.status(401).send("로그인 실패");
});

📝 정리

  • HTTP 네이티브 인증은 보안성이 낮아 실무에서는 거의 사용되지 않음.
  • 네이티브하지 않은 인증(세션 기반, 토큰 기반, OAuth 등)은 현대 웹에서 널리 사용됨.
  • 무작위 대입 공격(Brute-Force Attack) 을 방지하려면 계정 잠금, CAPTCHA, 2FA 등의 보안 조치가 필요함.
  • 안전한 인증 구현을 위해 HTTPS를 사용하고, 강력한 암호 정책을 적용해야 함.

대응 방안

1. 서드파티 인증 사용(Third-Party Authentication)

  • 서드파티 인증은 Google, Facebook, Apple 등의 외부 인증 제공자를 활용하는 방식
  • OAuth 2.0, OpenID Connect(OIDC) 등을 사용하여 사용자 인증을 수행하며, 애플리케이션은 인증을 직접 구현하지 않고, 외부 제공자에게 위임한다

  • ✅ 장점

    • 자체 인증 시스템을 구축할 필요가 없어 보안성 향상
    • 사용자가 기존 계정을 활용할 수 있어 UX 개선.
    • OAuth 제공자가 보안 관리를 수행하므로 보안 업데이트 유지 용이
  • ❌ 단점

    • OAuth 제공자 의존성이 존재 (제공자가 변경될 경우 서비스 수정 필요)
    • 인증 시스템이 OAuth 제공자의 장애에 영향을 받을 수 있다
// OAuth 2.0 인증
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;

passport.use(
  new GoogleStrategy(
    {
      clientID: "GOOGLE_CLIENT_ID",
      clientSecret: "GOOGLE_CLIENT_SECRET",
      callbackURL: "/auth/google/callback",
    },
    (accessToken, refreshToken, profile, done) => {
      return done(null, profile);
    }
  )
);

app.get(
  "/auth/google",
  passport.authenticate("google", { scope: ["profile", "email"] })
);
app.get(
  "/auth/google/callback",
  passport.authenticate("google", { failureRedirect: "/" }),
  (req, res) => {
    res.redirect("/dashboard");
  }
);

2. Single Sign-On(SSO)과 통합

  • Single Sign-On(SSO)은 한 번의 로그인으로 여러 서비스에 접근할 수 있도록 하는 인증 시스템
  • 대표적인 SSO 프로토콜은 SAML(Security Assertion Markup Language)과 OAuth/OpenID Connect가 있다
  • SSO는 대기업, SaaS 플랫폼 등에서 많이 사용되며 보안성과 관리 효율성을 높일 수 있다

✅ 장점

  • 사용자는 한 번만 로그인하면 여러 서비스에 접근할 수 있다
  • 조직 내에서 통합된 인증 정책을 적용할 수 있다
  • 비밀번호 관리 부담 감소

❌ 단점

  • SSO 제공가 장애를 일으키면 모든 서비스 로그인 불가
  • SSO 계정이 탈취되면 심각한 보안 문제 발생
  • 예제 (SAML 기반 SSO 인증 흐름)

SAML 기반 SSO 인증 절차

사용자가 서비스(A)에 로그인 시도 서비스(A)는 SSO 인증 서버(IdP)로 리디렉션 IdP가 인증을 수행한 후 SAML 응답을 서비스(A)로 전달 서비스(A)는 SAML 응답을 검증하고 로그인 성공 처리


자체 인증 시스템 구현

1. 사용자 명, 이메일 주소를 요청

사용자 계정을 생성할 때, 정확한 정보 입력과 안전한 계정 관리가 필요합니다.

1. 1회용 이메일 계정 금지

  • 임시 이메일을 활용한 계정 생성 및 악용을 방지하기 위해 일회성 이메일 도메인을 차단합니다.
  • 예: tempmail.com, 10minutemail.com 등의 도메인 차단.

2. 암호 재설정 보안

  • 사용자가 비밀번호를 재설정할 때, 이메일 또는 SMS 인증을 요구하여 보안성을 높입니다.
  • 비밀번호 재설정 링크는 일정 시간(예: 10분) 후 자동 만료되도록 설정합니다.

3. 복잡한 비밀번호 설계

  • 비밀번호는 최소 8~12자 이상, 대문자, 소문자, 숫자, 특수문자 포함하도록 강제.
  • 사용자가 이전에 사용한 비밀번호 재사용 금지.
  • 비밀번호 입력 시 브루트 포스 공격 방지를 위한 지연(Delay) 적용.
// 비밀번호 정책 적용
const passwordSchema = new RegExp(
  "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$"
);

if (!passwordSchema.test(userPassword)) {
  return res
    .status(400)
    .send("비밀번호는 대문자, 소문자, 숫자, 특수문자를 포함해야 합니다.");
}

2. 안전하게 암호 저장

  • 암호화되지 않은 비밀번호 저장은 심각한 보안 위험을 초래한다
  • 따라서 해싱 및 솔팅 기법을 적용하여 안전하게 저장해야 한다

1. 해싱 암호 (Password Hashing)

  • 비밀번호를 데이터베이스에 저장할 때, 일방향 해시 함수를 사용하여 원래 값을 알 수 없도록 만든다
  • 일반적인 해싱 알고리즘: bcrypt, argon2, PBKDF2 등

2. 솔팅 해시 (Salting Hash)

  • 단순 해싱만 하면 동일한 비밀번호는 동일한 해시 값을 가지므로 솔트(Salt) 값을 추가하여 보안을 강화할 수 있다
// bcrypt를 사용한 안전한 비밀번호 저장
const bcrypt = require("bcrypt");

const saltRounds = 10;
const hashedPassword = await bcrypt.hash(userPassword, saltRounds);

await saveToDatabase({ username, password: hashedPassword });

3. 멀티 팩터 인증 (Multifactor Authentication, MFA)

  • 사용자 로그인 시 2단계 인증(2FA)을 적용하여 계정 보안을 강화한다
  • 예) 비밀번호 + SMS 인증 코드, 비밀번호 + OTP(Google Authenticator)

3. 로그아웃 기능과 보안

1. 사용자 열거 금지

  • 로그인 페이지에서 “이메일이 존재하지 않습니다” 와 같은 메시지를 제공하면 공격자가 계정 존재 여부를 추측할 수 있다
  • 동일한 응답을 반환하여 보안성을 유지

2. 잠재적인 열거 취약점 방지

  • API 응답을 통일하여 사용자 정보 유출 방지
  • 예) “로그인 정보를 확인하세요”와 같이 모든 실패 응답을 동일하게 설정

3. 캡챠(CAPTCHA) 구현

  • 봇을 이용한 자동화된 로그인 시도를 방지하기 위해 CAPTCHA 적용
  • Google reCAPTCHA 활용 가능
// reCAPTCHA 적용
<form action="/login" method="POST">
  <input type="text" name="username">
  <input type="password" name="password">
  <div class="g-recaptcha" data-sitekey="YOUR_RECAPTCHA_SITE_KEY"></div>
  <button type="submit">로그인</button>
</form>
<script src="https://www.google.com/recaptcha/api.js"></script>

정리

  • 서드파티 인증(OAuth, OpenID Connect) 을 사용하면 보안성을 높이고, 인증 관리를 위임할 수 있다
  • SSO를 활용하면 사용자 경험을 개선하고, 여러 서비스 간 인증을 효율적으로 관리할 수 있다
  • 자체 인증 시스템을 구축할 경우, 안전한 비밀번호 정책, 해싱 암호화, MFA, CAPTCHA 등을 적용하여 보안을 강화해야 한다

🗒️ 요약

  1. 해커들은 사용자의 자격증명을 도용하기위해 인증 시스템을 공격한다.
  2. 개발자는 웹사이트르 보호하기위해 OAuth 로그인이나 Single Sign-On ID 제공자같은 서드 파티 인증시스템을 사용할 수 있다.
  3. 자체 인증 시스템을 구현한다면 사용자를 명확화할 수 있는 사용자명이나 email을 가져야하고, 비밀번호의 암호화가 필요하다.
  4. 이메일 주소의 유효성을 확인할 수 있는 방법은 해당 이메일로 유효한 임시토큰을 보내는 방법이있다.
  5. 비밀번호 재설정이나 찾기의 경우도 같은 방법으로 유효한 임시토큰을 주고 일정시간이 지나면 만료되도록해야한다.

댓글남기기