JWT 동작 원리와 5가지 보안 취약점: 세션의 한계에서 토큰 인증까지
세션 기반 인증의 확장성 문제와 JWT가 등장한 배경부터 토큰 구조(Header, Payload, Signature)와 서명 검증 원리를 Python 코드와 함께 설명한다. alg none 공격, 알고리즘 혼동 같은 실제 보안 취약점과 안전한 구현 체크리스트까지.
웹 애플리케이션이 단일 서버에서 돌아가던 시절을 상상해보세요. 사용자가 로그인하면 서버는 세션 ID를 생성하고, 이를 메모리나 데이터베이스에 저장했다. 브라우저는 쿠키로 이 세션 ID를 받아 매 요청마다 함께 보냈죠.
하지만 2010년대 들어 웹 서비스가 급격히 성장하면서 문제가 생겼다.
서버가 여러 대로 늘어났다고 가정해보겠다. 사용자가 1번 서버에서 로그인했는데, 다음 요청이 2번 서버로 가면? 2번 서버는 이 세션 ID를 모릅니다. 세션 정보가 1번 서버에만 있거든요.
해결책은 있었다. 세션을 공유 데이터베이스(Redis, 메모리 캐시)에 저장하는 거죠. 하지만 이제 모든 요청마다 데이터베이스 조회 가 필요했다. 대규모 트래픽이 오면? 데이터베이스가 병목이 된다.
더 큰 문제는 마이크로서비스 아키텍처였다. 사용자 서비스, 주문 서비스, 결제 서비스가 각각 다른 서버에서 실행된다. 주문 서비스에서 "이 사용자가 인증된 사용자인가?"를 확인하려면 어떻게 해야 할까요?
전통적인 방식이라면:
이는 네트워크 지연, 장애 전파, 복잡한 의존성을 만들었다.
모바일 앱, 다른 도메인의 프론트엔드에서 API를 호출할 때도 문제였다. 쿠키는 Same-Origin Policy 때문에 제약이 많았거든요.
한 줄 요약: JWT는 서버 상태 저장 없이도 인증 정보를 안전하게 전달하기 위해 만들어졌다.
JWT 토큰 수명 주기
JWT는 인증에 필요한 정보를 암호화된 토큰에 담아서, 서버가 별도 저장소 없이도 토큰만으로 사용자를 확인할 수 있게 해주는 기술이다.
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.
RFC 7519에서 정의한 JWT는 두 당사자 간에 클레임을 전달하는 컴팩트하고 URL-safe한 수단 이다. 클레임은 JSON 객체로 인코딩되어 디지털 서명되거나 MAC으로 무결성이 보호된다.
JWT는 점(.)으로 구분된 세 부분으로 구성된다:
JWT를 공부하다 보면 JWS, JWE, JWK 같은 비슷한 용어가 나옵니다. 혼동하기 쉬운 이 개념들을 정리해보겠다:
| 용어 | 설명 |
|---|---|
| JWT | 클레임을 JSON으로 표현하는 토큰. JWS 또는 JWE의 컨테이너 |
| JWS | 서명된 JWT. 페이로드가 Base64로 인코딩만 됨 (누구나 읽을 수 있음) |
| JWE | 암호화된 JWT. 페이로드가 암호화됨 (키 없이 읽을 수 없음) |
| JWK | 암호화 키의 JSON 표현. 키를 안전하게 공유하기 위한 포맷 |
| JWKS | JWK Set. .well-known/jwks.json으로 공개키 배포 |
대부분 "JWT"라고 부르는 것은 사실 JWS 이다. 서명은 있지만 암호화는 안 된 토큰이죠.
이 차이가 모든 것을 바꿨다.
JWT 인증은 크게 네 단계로 이루어집니다:
이제 각 단계를 자세히 살펴보겠다.
사용자가 로그인 요청을 보냅니다:
POST /login HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"username": "user123",
"password": "password123"
}
서버는 사용자 인증 후 JWT를 생성한다. JWT는 세 부분으로 구성된다:
Header (헤더)
{
"alg": "HS256",
"typ": "JWT"
}
alg: 서명 알고리즘 (HS256 = HMAC + SHA-256)typ: 토큰 타입Payload (페이로드)
{
"sub": "user123",
"name": "홍길동",
"role": "user",
"iat": 1516239022,
"exp": 1516242622
}
sub (Subject): 사용자 식별자iat (Issued At): 발급 시간 (Unix timestamp)exp (Expiration): 만료 시간Signature (서명)
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
)
서버에서 JWT를 생성하는 전체 과정을 코드로 살펴보겠다:
import json
import hmac
import hashlib
import base64
import time
def base64url_encode(data: bytes) -> str:
"""Base64 URL 인코딩 (패딩 제거)"""
return base64.urlsafe_b64encode(data).decode('utf-8').rstrip('=')
def create_jwt(payload: dict, secret: str, algorithm: str = "HS256") -> str:
"""JWT 토큰 생성"""
# 1. 헤더 생성
header = {"alg": algorithm, "typ": "JWT"}
header_encoded = base64url_encode(json.dumps(header).encode('utf-8'))
# 2. 페이로드에 발급시간과 만료시간 추가
payload["iat"] = int(time.time())
payload["exp"] = payload["iat"] + 3600 # 1시간 후 만료
payload_encoded = base64url_encode(json.dumps(payload).encode('utf-8'))
# 3. 서명 생성
message = f"{header_encoded}.{payload_encoded}".encode('utf-8')
signature = hmac.new(
secret.encode('utf-8'),
message,
hashlib.sha256
).digest()
signature_encoded = base64url_encode(signature)
# 4. 최종 JWT 조합
return f"{header_encoded}.{payload_encoded}.{signature_encoded}"
# 사용 예시
secret_key = "your-256-bit-secret-key-here-min-32chars"
user_payload = {
"sub": "user123",
"name": "홍길동",
"role": "user"
}
token = create_jwt(user_payload, secret_key)
print(token)
# 출력: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwibmFtZSI6...
실제 생성된 JWT는 이렇게 생겼다:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIiwibmFtZSI6Iuq1kOq4uOuPmSIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTE2MjQyNjIyfQ.4Adcj3UFYzPUVaVF43FmMab6RlaQD8A9V8wFzzht-y0
점(.)으로 구분된 세 부분이 각각 Header, Payload, Signature이다. Header와 Payload는 Base64로 인코딩만 된 것이므로 누구나 디코딩해서 읽을 수 있다. 비밀정보는 절대 넣으면 안 됩니다!
클라이언트는 받은 토큰을 저장하고, API 요청 시 Authorization 헤더에 포함한다:
GET /api/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Bearer는 토큰 타입을 나타내는 접두사이다. OAuth 2.0 스펙에서 정의한 형식이죠.
여기가 JWT의 핵심이다. 서버는 데이터베이스 조회 없이 토큰만으로 검증한다:
def verify_jwt(token: str, secret: str) -> dict | None:
"""JWT 토큰 검증"""
try:
# 1. 토큰을 세 부분으로 분리
parts = token.split('.')
if len(parts) != 3:
return None
header_encoded, payload_encoded, signature_encoded = parts
# 2. 서명 재생성
message = f"{header_encoded}.{payload_encoded}".encode('utf-8')
expected_signature = hmac.new(
secret.encode('utf-8'),
message,
hashlib.sha256
).digest()
expected_signature_encoded = base64url_encode(expected_signature)
# 3. 서명 비교 (타이밍 공격 방지를 위해 compare_digest 사용)
if not hmac.compare_digest(signature_encoded, expected_signature_encoded):
print("서명 불일치 - 토큰이 변조되었거나 비밀키가 다름")
return None
# 4. 페이로드 디코딩
# Base64 패딩 복원
padding = 4 - len(payload_encoded) % 4
if padding != 4:
payload_encoded += '=' * padding
payload_json = base64.urlsafe_b64decode(payload_encoded)
payload = json.loads(payload_json)
# 만료 시간 확인
if payload.get('exp', 0) < time.time():
print("토큰 만료됨")
return None
return payload
except Exception as e:
print(f"검증 실패: {e}")
return None
# 사용 예시
result = verify_jwt(token, secret_key)
if result:
print(f"인증 성공: {result['name']}님 환영합니다")
else:
print("인증 실패")
검증 과정의 핵심 포인트:
RFC 7519는 세 가지 타입의 클레임을 정의한다:
등록된 클레임 (Registered Claims)
표준에서 정의한 예약된 클레임들이다:
{
"iss": "https://example.com",
"sub": "user123",
"aud": "client-app",
"exp": 1516242622,
"nbf": 1516239022,
"iat": 1516239022,
"jti": "unique-token-id"
}
| 클레임 | 설명 |
|---|---|
iss (Issuer) | 토큰 발급자 |
sub (Subject) | 토큰의 주체 (보통 사용자 ID) |
aud (Audience) | 토큰 수신자 |
exp (Expiration) | 만료 시간 |
nbf (Not Before) | 유효 시작 시간 |
iat (Issued At) | 발급 시간 |
jti (JWT ID) | 토큰 고유 식별자 |
| 공개 클레임 (Public Claims) |
IANA 레지스트리에 등록되거나 URI로 네임스페이스가 지정된 클레임:
{
"https://example.com/is_admin": true,
"https://example.com/department": "engineering"
}
비공개 클레임 (Private Claims)
서비스 간 합의로 사용하는 커스텀 클레임:
{
"name": "홍길동",
"role": "admin",
"permissions": ["read", "write", "delete"]
}
대칭키: HMAC SHA-256 (HS256)
동일한 비밀키로 서명과 검증을 수행한다:
import hmac
import hashlib
def sign_hs256(message: str, secret: str) -> bytes:
"""HS256 서명 생성"""
return hmac.new(
secret.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).digest()
# 장점: 빠름, 구현 간단
# 단점: 검증하는 모든 서버가 비밀키를 알아야 함
비대칭키: RSA SHA-256 (RS256)
개인키로 서명하고 공개키로 검증한다:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
# 키 쌍 생성
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public_key = private_key.public_key()
def sign_rs256(message: str, private_key) -> bytes:
"""RS256 서명 생성 (개인키 사용)"""
return private_key.sign(
message.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA256()
)
def verify_rs256(message: str, signature: bytes, public_key) -> bool:
"""RS256 서명 검증 (공개키 사용)"""
try:
public_key.verify(
signature,
message.encode('utf-8'),
padding.PKCS1v15(),
hashes.SHA256()
)
return True
except:
return False
# 장점: 검증 서버에 비밀키 공유 불필요 (공개키만 배포)
# 단점: 연산이 느림
RS256은 마이크로서비스에서 유용한다. 인증 서버만 개인키를 갖고, 다른 서비스들은 공개키로 토큰을 검증할 수 있거든요.
실제 운영에서는 보안을 위해 두 종류의 토큰을 사용한다:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "dGVzdC1yZWZyZXNoLXRva2Vu",
"token_type": "Bearer",
"expires_in": 900
}
| 토큰 | 설명 |
|---|---|
| Access Token | API 인증용 단기 토큰. 15분-1시간 유효, 메모리/쿠키에 저장 |
| Refresh Token | Access Token 갱신용. 7-30일 유효, HttpOnly 쿠키 또는 서버 DB에 저장 |
| 왜 두 개로 나눌까요? |
토큰 갱신 흐름
POST /auth/refresh HTTP/1.1
Content-Type: application/json
{
"refresh_token": "dGVzdC1yZWZyZXNoLXRva2Vu"
}
서버 응답:
{
"access_token": "새로운_액세스_토큰",
"expires_in": 900
}
JWT는 놀이공원 손목밴드 와 같다.
손목밴드에는 위조 방지 홀로그램(서명)이 있어서, 직원은 홀로그램만 확인하면 유효한 밴드인지 알 수 있다. 입장 시간(iat), 유효 시간(exp), 이용 가능 범위(role)도 모두 밴드에 적혀 있죠.
토큰이 만료되면?
# 만료된 토큰으로 요청
response = requests.get('/api/profile', headers={'Authorization': f'Bearer {expired_token}'})
# 401 Unauthorized
# Refresh Token으로 새 Access Token 발급
response = requests.post('/auth/refresh', json={'refresh_token': refresh_token})
new_access_token = response.json()['access_token']
서명 알고리즘이 다르면?
서버는 반드시 허용된 알고리즘 목록을 체크해야 한다:
ALLOWED_ALGORITHMS = ["HS256", "RS256"]
def verify_jwt_secure(token: str, secret: str) -> dict | None:
header = decode_header(token)
# 알고리즘 화이트리스트 검증
if header.get('alg') not in ALLOWED_ALGORITHMS:
raise SecurityError(f"허용되지 않은 알고리즘: {header.get('alg')}")
# ... 이후 검증 로직
JWT Algorithm Confusion 공격
JWT가 편리하지만, 잘못 구현하면 심각한 보안 취약점이 된다.
1) 알고리즘 무시 공격 (alg: none)
가장 치명적인 공격 중 하나이다. JWT 헤더의 alg 필드를 none으로 설정하면 서명 없이 토큰을 만들 수 있다:
{
"alg": "none",
"typ": "JWT"
}
공격자가 만든 위조 토큰:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.
서명 부분이 비어 있지만, 취약한 라이브러리는 이를 유효한 토큰으로 처리한다.
실제 사례 - Auth0 JWT 취약점:
Auth0의 JWT 라이브러리에서 alg: none 알고리즘 처리 관련 취약점이 발견되었다. Stytch 블로그에 따르면, Auth0는 여러 차례 보안 사고를 경험했는데, 그 중 JWT 관련 이슈로는 특정 조건에서 토큰 검증이 우회될 수 있는 문제가 있었다. 공격자는 대소문자 변형(alg:NoNe)을 사용하여 필터링을 우회했고, 임의의 사용자로 위장한 토큰을 생성할 수 있었다.
2) 약한 비밀키 (Weak Secret)
HMAC 알고리즘은 비밀키의 강도에 의존한다:
# 취약한 예시 - 절대 사용 금지
secret = "password123" # 사전 공격으로 쉽게 추측 가능
secret = "secret" # jwt-cracker로 수 초 내 크랙
secret = "company_name" # 공개 정보로 추측 가능
# 안전한 예시
import secrets
secret = secrets.token_urlsafe(32) # 256비트 랜덤 문자열
# 예: "a8f5f167f44f4964e6c998dee827110c-Kx9dM2pQ7nR3sT5v"
공격자는 hashcat 같은 도구로 일반적인 비밀키를 브루트포스 한다.
3) 알고리즘 혼동 공격 (Algorithm Confusion)
서버가 RS256을 사용하는데, 공격자가 HS256으로 바꿔서 공격하는 기법이다:
// 원래 토큰 (RS256 - 비대칭키)
{
"alg": "RS256",
"typ": "JWT"
}
// 공격자가 조작한 토큰 (HS256 - 대칭키)
{
"alg": "HS256",
"typ": "JWT"
}
공격 원리:
4) JWK 주입 공격
JWT 헤더에 jwk 필드를 포함시켜 자신의 공개키로 토큰을 검증하도록 만드는 공격이다:
{
"alg": "RS256",
"jwk": {
"kty": "RSA",
"n": "공격자의_공개키_값",
"e": "AQAB"
}
}
취약한 서버는 헤더에 포함된 키로 검증을 수행하여, 공격자가 임의의 토큰을 생성할 수 있게 된다.
CVE-2018-0114: node-jose 토큰 위조
node-jose 라이브러리는 jwk 속성이 헤더에 지정되기만 하면 인증되지 않은 공격자가 토큰에 임의의 키 쌍을 포함시켜 토큰에 서명할 수 있게 허용했다.
| 항목 | 설명 |
|---|---|
| 알고리즘 화이트리스트 | none 알고리즘 명시적 거부 |
| 강력한 비밀키 | 최소 32바이트 랜덤 문자열 사용 |
| 알고리즘 고정 | 헤더의 alg를 믿지 말고 서버에서 강제 지정 |
| 짧은 만료시간 | Access Token은 15분-1시간으로 제한 |
| JWK 검증 | 외부에서 제공된 키 사용 금지 |
| HTTPS 적용 | 토큰 전송 시 반드시 암호화된 채널 사용 |
한 줄 요약: JWT는 분산 환경에서 세션 저장소 없이도 안전한 인증을 가능하게 하지만, 올바른 구현이 보안의 핵심이다.
JWT를 더 깊이 이해하려면 인증 체계 전반을 알아야 한다. OAuth 2.0 Authorization Code Flow 동작 원리에서 JWT가 실제 서비스에서 어떻게 활용되는지 확인해보세요.
JWT의 세 부분(Header.Payload.Signature)은 각각 Base64URL로 인코딩된다. 중요한 점은 Payload가 암호화되지 않는다는 것이다. Base64 디코딩만으로 내용을 확인할 수 있다. 따라서 비밀번호, 신용카드 번호 같은 민감 정보는 절대 JWT Payload에 포함하면 안 된다.
none 알고리즘 공격은 JWT 초기의 대표적 취약점이었다. 공격자가 헤더의 alg 필드를 none으로 설정하면 서명 검증 자체를 건너뛴다. 2015년 이후 대부분의 라이브러리에서 패치됐지만, 커스텀 구현에서는 여전히 발견된다.
JWK(JSON Web Key) Set 엔드포인트를 통해 공개키를 배포하는 구조에서, 공격자가 JWT 헤더에 자신의 JWK를 직접 포함시키는 공격이 가능하다. jku(JWK Set URL) 클레임에 공격자 서버 URL을 지정하면, 서버가 공격자의 공개키로 서명을 검증하게 된다.
kid(Key ID) 파라미터는 서버가 여러 키 중 하나를 선택하는 데 사용된다. 이 값이 파일 경로로 해석되는 구현에서는 kid: "../../dev/null"처럼 경로 조작(Path Traversal)을 통해 빈 문자열을 비밀키로 사용하도록 유도할 수 있다.
Token Sidejacking은 XSS 취약점을 통해 로컬스토리지나 메모리에 저장된 JWT를 탈취하는 공격이다. 탈취된 토큰은 만료될 때까지 유효하며, 서버 측에서 무효화할 방법이 제한적이다.
방어 전략으로는 토큰 바인딩(Token Binding)이 있다. 클라이언트의 TLS 세션이나 핑거프린트에 토큰을 바인딩하면 다른 환경에서 재사용할 수 없다. 또한 짧은 만료 시간(15분 이하)과 Refresh Token Rotation을 조합하면 탈취 토큰의 유효 기간을 최소화할 수 있다.
단일 서버 환경에서는 서버 측 세션이 여전히 더 안전하다. 즉시 무효화가 가능하고, 민감 정보가 클라이언트에 노출되지 않는다. JWT는 마이크로서비스 아키텍처에서 서비스 간 인증에 적합하다. 각 서비스가 독립적으로 토큰을 검증할 수 있어 인증 서버에 대한 의존도를 줄인다.
현실에서는 Access Token(JWT, 15분)과 Refresh Token(Opaque, 서버 저장)의 조합이 가장 많이 사용된다. Auth0, Okta 같은 IDaaS 플랫폼이 이 패턴을 표준화했다.
실무에서 JWT 관련 보안 사고의 대부분은 라이브러리 취약점이 아니라 구현 실수에서 발생한다. 가장 흔한 실수 5가지를 정리한다.
첫째, 토큰에 민감한 정보를 포함하는 것이다. JWT의 페이로드는 Base64URL로 인코딩될 뿐 암호화되지 않는다. 누구나 디코딩하여 내용을 볼 수 있다. 비밀번호, 주민등록번호, 신용카드 번호 같은 정보는 절대 포함하면 안 된다. 필요한 경우 JWE(JSON Web Encryption)를 사용해야 한다.
둘째, 시크릿 키의 강도가 부족한 것이다. HMAC-SHA256의 경우 키 길이가 256비트(32바이트) 이상이어야 한다. "secret", "password123" 같은 약한 키는 사전 공격으로 쉽게 추측된다. jwt.io의 기본 예제에 사용되는 "your-256-bit-secret"을 그대로 프로덕션에 사용하는 사례도 있다.
셋째, 토큰 만료를 설정하지 않는 것이다. exp 클레임 없이 발급된 JWT는 영원히 유효하다. 토큰이 한 번 유출되면 무한정 악용될 수 있다. 액세스 토큰은 15분 이하, 리프레시 토큰은 7일 이하로 설정하는 것이 권장된다.
넷째, aud(audience)와 iss(issuer) 클레임의 검증을 생략하는 것이다. 다른 서비스용으로 발급된 JWT가 우리 서비스에서 유효하게 처리되는 토큰 혼동(Token Confusion) 공격이 가능해진다. 멀티테넌트 환경에서 이 문제는 특히 심각하다.
다섯째, 로그아웃 처리를 하지 않는 것이다. JWT는 서버 측 상태가 없어 개별 토큰을 무효화하기 어렵다. Redis 기반 토큰 블랙리스트, 토큰 버전 관리(사용자별 token_version 필드), 또는 짧은 만료 시간 + 리프레시 토큰 회전(Refresh Token Rotation) 전략으로 대응한다.
실무에서는 JWT와 서버 세션을 결합한 하이브리드 방식이 점점 보편화되고 있다. 짧은 수명의 JWT(15분)를 액세스 토큰으로, 서버 측 저장소에 관리되는 리프레시 토큰을 조합하는 패턴이다.
이 방식에서 리프레시 토큰은 Redis나 데이터베이스에 저장되므로 즉시 폐기가 가능하다. 사용자가 로그아웃하거나 계정이 정지되면 리프레시 토큰을 삭제하여 새로운 액세스 토큰 발급을 차단한다. 기존 액세스 토큰은 최대 15분 후 자연 만료된다.
리프레시 토큰 회전(Rotation)도 핵심이다. 리프레시 토큰을 사용할 때마다 새로운 토큰을 발급하고 이전 토큰을 무효화한다. 만약 무효화된 토큰이 재사용되면 토큰 탈취로 간주하고 해당 사용자의 모든 세션을 종료한다. 이 패턴은 Auth0, Firebase Auth 등 주요 인증 서비스에서 기본 제공된다.
마이크로서비스 환경에서 JWT의 가치는 더 커진다. 서비스 간 통신에서 매번 인증 서버에 토큰을 검증하면 병목이 발생한다. JWT는 서비스가 자체적으로 서명을 검증할 수 있어 인증 서버 의존성을 제거한다. 이것이 Kubernetes의 ServiceAccount 토큰이 JWT 형식인 이유다.
이러한 하이브리드 접근은 보안과 성능의 균형점을 제공한다. 순수 JWT는 확장성이 뛰어나지만 즉시 폐기가 어렵고, 순수 세션은 폐기가 용이하지만 확장성에 제약이 있다. 두 방식의 장점을 결합함으로써 현대적 인증 시스템의 요구사항을 충족시킬 수 있다.
인증 보안 관련
관련 MITRE ATT&CK 기법
JWT 보안 사고 사례
AI 활용 안내 이 글은 AI(Claude)의 도움을 받아 작성되었습니다. 인용된 통계와 사례는 참고 자료에 명시된 출처에 근거하며, 설명을 위한 일부 표현은 각색되었습니다.
면책 조항 본 글은 보안 인식 제고를 위한 교육 목적으로 작성되었습니다. 언급된 공격 기법을 실제로 시도하는 행위는 「정보통신망법」, 「형법」 등에 따라 처벌받을 수 있으며, 본 블로그는 이에 대한 법적 책임을 지지 않습니다.