웹 애플리케이션을 개발할 때 외부로부터의 악의적인 공격을 방지하는 것은 매우 중요합니다.
이번 포스트에서는 JavaScript와 PHP를 이용해 회원가입 과정에서 발생한 악의적인 공격을 어떻게 식별하고, 이를 방지하기 위해 어떤 보안 조치를 적용했는지 설명하겠습니다.
특히, 악의적인 요청을 방지하기 위한 고유한 토큰 생성 방식과 클라이언트와 서버 간의 시간 차이를 고려한 검증 방안을 소개합니다.
1. 오늘 발생한 악의적인 회원가입 공격 시도
오늘 웹 애플리케이션에서 비정상적인 회원가입 요청이 감지되었습니다.
해당 요청은 외부에서 의도적으로 서버로 잘못된 가입 요청을 보내 서버의 취약점을 탐지하려는 시도로 의심되었습니다.
비정상적인 회원가입 요청 시 서버에서는 500 에러가 발생했고, 데이터베이스 정보나 테이블명을 확인하려는 시도가 있었지만 성공하지 못했습니다.
이러한 공격을 통해 보안의 필요성을 다시 한 번 인지하게 되었고, 이를 방지하기 위한 조치를 더욱 강화하게 되었습니다.
2. 악의적인 회원가입 공격 대응 방안
악의적인 회원가입 요청은 일반적으로 자동화된 스크립트나 봇을 통해 서버의 취약점을 노리는 방식으로 발생합니다.
CAPTCHA와 같은 기능을 바로 적용할 수 없었기 때문에, 이런 요청을 방지하기 위해 토큰을 이용한 검증을 추가했습니다.
이유는 회원가입 전 사용자의 활동 내역이 전혀 없었고 회원가입만 시도하는 패턴이 반복되었기 때문입니다.
CAPTCHA를 바로 적용하기에는 시간이 걸려서 적용하지 못했지만, 이를 보완하기 위해 토큰을 통한 검증을 먼저 도입했습니다.
3. 고유한 토큰 생성하기 (JavaScript)
아래 코드는 JavaScript로 고유한 토큰을 생성하는 방식입니다. 출처, 타임스탬프, 사용자 ID, 받는 곳 정보를 결합하여 토큰을 만들고 이를 SHA-256으로 암호화합니다.
// 출처, 타임스탬프, 사용자 아이디, 받는 곳 설정
const source = "start";
const timestamp = Math.floor(Date.now() / 60000); // 현재 시간 타임스탬프 (분 단위)
const userId = email; // 로그인한 사용자 ID (변수로 전달된 이메일 값)
const recipient = "end";
// 토큰을 위한 문자열 결합
const rawToken = `${source} ${timestamp} ${userId} ${recipient}`;
// 토큰 암호화 (SHA256)
async function generateCsrfToken(rawToken) {
const encoder = new TextEncoder();
const data = encoder.encode(rawToken);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
// 고유 토큰 생성
generateCsrfToken(rawToken).then(csrfToken => {
console.log("생성된 토큰: ", csrfToken);
// 필요에 따라 토큰을 저장하거나 사용할 수 있습니다
});
위 코드에서는 타임스탬프를 분 단위로 사용하여 클라이언트와 서버 간의 시간 차이가 큰 문제가 되지 않도록 했습니다. 또한, 암호화에는 SHA-256 알고리즘을 사용하여 보안성을 높였습니다.
4. 토큰 검증하기 (PHP)
클라이언트에서 생성된 토큰을 서버에서 검증하기 위해 PHP 코드를 사용합니다. 서버에서 동일한 규칙으로 토큰을 생성한 뒤 클라이언트에서 전송된 토큰과 비교합니다.
<?php
// 세션 시작
session_start();
// 사용자 요청을 받았다고 가정
$email = $_POST['email'] ?? 'unknown@example.com'; // 사용자의 이메일 (예시)
// 출처, 타임스탬프, 사용자 아이디, 받는 곳 설정
$source = "start";
$timestamp = floor(time() / 60); // 현재 시간 타임스탬프 (분 단위)
$user_id = $email; // 로그인한 사용자 ID
$recipient = "end";
// 토큰을 위한 문자열 결합
$raw_token = $source . " " . $timestamp . " " . $user_id . " " . $recipient;
// 토큰 암호화 (SHA256)
$csrf_token_generated = hash('sha256', $raw_token);
// 클라이언트에서 전송된 토큰
$csrf_token_received = $_POST['csrf_token'] ?? '';
// 토큰 비교
if (hash_equals($csrf_token_generated, $csrf_token_received) ) {
echo "토큰이 유효합니다. 요청을 처리합니다.";
} else {
echo "토큰이 유효하지 않습니다. 요청을 차단합니다.";
exit;
}
?>
5. 시간 차이를 고려한 검증
서버와 클라이언트 간의 시간 차이는 항상 존재할 수 있으므로, 타임스탬프를 분 단위로 처리하고 1분 이내의 차이는 허용하도록 설정하였습니다. 이 방법으로 토큰의 유효성을 유지하면서, 작은 시간 차이로 인해 토큰이 무효화되는 것을 방지할 수 있습니다.
또한, 왜 토큰을 세션에서 미리 생성하지 않고 요청 시마다 바로 생성하여 검증하는지를 설명드리겠습니다. 만약 세션에서 미리 랜덤한 숫자를 암호화한 데이터를 폼에 추가해 사용한다면, 공격자가 이를 세션에서 복사하여 폼에 넣어 보낼 가능성이 있습니다.
따라서, 토큰은 요청이 발생하는 순간에 바로 생성하고 즉시 검증하여 미리 준비된 토큰을 사용하는 것을 방지하고 보안을 강화하였습니다.
6. 결론
회원가입 과정에서 발생할 수 있는 악의적인 공격은 간단한 원리지만, 웹 애플리케이션 보안에 큰 위협이 될 수 있습니다. 이번 포스트에서는 회원가입 과정에서 악의적인 공격을 방지하기 위해 고유 토큰을 JavaScript와 PHP로 생성하고 검증하는 방법을 다뤘습니다.
출처, 타임스탬프, 사용자 ID 등의 요소를 결합하고 암호화하여 고유한 토큰을 만드는 방법을 통해 악의적인 요청으로부터 안전한 웹 애플리케이션을 구축할 수 있습니다.
추가적으로, 타임스탬프를 분 단위로 처리하고, 시간 차이를 허용하는 방식으로 서버와 클라이언트 간의 유효성을 보장할 수 있었습니다. 이를 통해 보다 안전한 웹 환경을 만드는 데 기여할 수 있을 것입니다.
'개발 > 개발 필기' 카테고리의 다른 글
센트리 사용 (1) | 2024.11.24 |
---|---|
CSS 클래스 기반 스타일 관리로 유지보수성과 효율성 강화 (8) | 2024.11.23 |
네이버와 구글 검색어 자동완성 데이터를 가져오는 방법 (4) | 2024.11.18 |
Git 브랜치 충돌 해결: Main에 Develop 강제로 덮어씌우는 방법 (2) | 2024.11.15 |
네이버 API 검색 결과에서 언론사 이름 추출하기 (19) | 2024.11.06 |