LinClean 프로젝트의 URL 위협 탐지를 위한 Open API PoC (평판 조회용) 입니다.
Google Safe Browsing, URLhaus, RDAP 세 가지 소스를 병렬로 조회하여 악성 URL을 종합 판정합니다.
분류
기술
용도
Framework
FastAPI
비동기 REST API
Language
Python 3.11+
타입 힌트, async/await
DB
SQLite + aiosqlite
URLhaus CSV 로컬 캐싱
ORM
SQLAlchemy 2.0 (async)
비동기 DB 접근
HTTP Client
httpx
비동기 외부 API 호출
Validation
Pydantic v2 + pydantic-settings
요청/응답 검증, 환경변수 관리
Linter
Ruff
코드 포맷팅 및 린트
app/
├── main.py # FastAPI 앱 생성, lifespan(URLhaus 주기 갱신)
├── api/
│ ├── deps.py # 의존성 주입 (DB 세션)
│ └── v1/
│ ├── router.py # v1 라우터 집합
│ └── endpoints/
│ └── check.py # /api/v1/check/* 엔드포인트
├── core/
│ ├── config.py # 환경변수 로드 (pydantic-settings)
│ ├── database.py # SQLAlchemy async engine/session
│ └── exceptions.py # 커스텀 HTTP 예외
├── models/
│ ├── base.py # SQLAlchemy DeclarativeBase
│ └── urlhaus.py # UrlRecent, UrlOnline 테이블
├── schemas/
│ └── check.py # URLRequest, CheckResponse, AggregatedResponse
└── services/
├── gsb.py # Google Safe Browsing API 호출
├── urlhaus.py # URLhaus CSV 다운로드 및 DB 조회
└── rdap.py # RDAP 도메인 등록 정보 조회
소스
방식
탐지 대상
Google Safe Browsing
Google API 실시간 조회
피싱, 멀웨어, 원치 않는 소프트웨어
URLhaus
CSV 다운로드 → SQLite 저장 → 로컬 조회
멀웨어 배포 URL
RDAP
RDAP 프로토콜 실시간 조회
신규 등록 도메인 (30일 미만)
Method
Path
설명
POST
/api/v1/check
통합 검사 — 모든 소스 병렬 조회 + 종합 판정
POST
/api/v1/check/gsb
Google Safe Browsing 단독 조회
POST
/api/v1/check/urlhaus
URLhaus DB 단독 조회
POST
/api/v1/check/rdap
RDAP 도메인 정보 단독 조회
URLhaus 자동 갱신 : 서버 시작 시 CSV 다운로드, 이후 60분 주기로 백그라운드 갱신
RDAP 인메모리 캐시 : 동일 도메인 24시간 캐싱 (도메인 등록일은 자주 변하지 않음)
글로벌 예외 처리 : 모든 예외를 일관된 JSON 응답으로 반환
POST /api/v1/check { "url": "https://example.com" }
|
v
+----------------------------------------------+
| Source Availability Check |
| |
| GSB_API_KEY set? --> add GSB task |
| URLhaus DB loaded? --> add URLhaus task |
| RDAP (always on) --> add RDAP task |
+----------------------------------------------+
|
v
+----------------------------------------------+
| asyncio.gather (parallel) |
| |
| +-------+ +---------+ +--------+ |
| | GSB | | URLhaus | | RDAP | |
| +---+---+ +----+----+ +---+----+ |
| | | | |
| v v v |
| is_threat is_threat is_threat |
| (matched) (in DB) (< 30 days) |
+----------------------------------------------+
|
| * on failure: is_threat=false
| with error in detail
v
+----------------------------------------------+
| threat_score calculation |
| |
| GSB is_threat=true --> +50 |
| URLhaus is_threat=true --> +50 |
| RDAP is_threat=true --> +20 |
| |
| sum (capped at 100) |
+----------------------------------------------+
|
v
+----------------------------------------------+
| verdict decision |
| |
| score >= 50 --> "malicious" |
| score > 0 --> "suspicious" |
| score == 0 --> "safe" |
+----------------------------------------------+
|
v
AggregatedResponse
소스
가중치
근거
Google Safe Browsing
+50
블랙리스트 매치 — 확인된 위협
URLhaus
+50
블랙리스트 매치 — 확인된 위협
RDAP
+20
신규 도메인은 위험 신호이지만 그 자체로 위협은 아님
점수 범위
verdict
의미
≥ 50
malicious
1개 이상의 블랙리스트에서 확인된 위협
1 ~ 49
suspicious
직접적 위협 증거는 없으나 의심 신호 존재 (신규 도메인 등)
0
safe
모든 소스에서 위협 미탐지
GSB
URLhaus
RDAP
합산
verdict
✗
✗
✗
0
safe
✗
✗
✓ (신규)
20
suspicious
✓
✗
✗
50
malicious
✓
✓
✓
100 (cap)
malicious
Google Safe Browsing (GSB)
항목
내용
제공사
Google
비용
무료 (일 10,000건)
커버리지
글로벌 — 피싱, 멀웨어, 소셜 엔지니어링
응답 속도
빠름 (100~300ms)
업데이트 주기
실시간
장점 : 가장 넓은 커버리지, Google 검색/Chrome과 동일한 데이터베이스 사용, 안정적인 인프라
단점 : API 키 필요, 일일 호출 제한 존재, Google 의존성
항목
내용
제공사
abuse.ch (커뮤니티 기반)
비용
완전 무료
커버리지
멀웨어 배포 URL 특화
데이터 방식
CSV 다운로드 → 로컬 DB 저장
업데이트 주기
5분마다 갱신 (본 프로젝트는 60분 주기 동기화)
장점 : API 키 불필요, 로컬 조회라 응답 매우 빠름 (1~5ms), Rate limit 없음, 오프라인 동작 가능
단점 : 멀웨어 URL만 커버 (피싱 미포함), 커뮤니티 기반이라 커버리지에 한계, CSV 초기 로딩 시간 소요
RDAP (Registration Data Access Protocol)
항목
내용
제공사
각 도메인 레지스트리 (ICANN 표준)
비용
완전 무료
커버리지
모든 gTLD/ccTLD 도메인 등록 정보
데이터 방식
rdap.org 부트스트랩 → 레지스트리 서버로 리다이렉트
표준
RFC 7480~7484
장점 : API 키 불필요, 전 세계 모든 TLD 지원, 표준 JSON 응답 (파싱 안정적), 도메인 나이 기반 휴리스틱 탐지 가능
단점 : 공식 Rate limit 없으나 과도한 호출 시 IP 차단 가능, SLA 없음 (레지스트리별 가용성 상이), 리다이렉트 구조라 응답 속도 불안정 (500ms~3s), 신규 도메인 탐지는 false positive 가능성 존재
검토 후 도입하지 않은 외부 서비스들과 그 이유입니다.
항목
내용
제공사
OpenDNS (Cisco)
방식
CSV 다운로드 또는 API 조회
심각한 Rate Limiting : API 호출 시 429 Too Many Requests 에러가 빈번하게 발생하여 안정적 운영 불가
CSV 전체 다운로드도 Rate limit 적용 — 주기적 갱신이 어려움
초기 구현 후 테스트 단계에서 제거됨
항목
내용
제공사
한국인터넷진흥원 (KISA) via 공공데이터포털
방식
REST API (API 키 필요)
.kr 도메인만 지원 : .com, .net 등 해외 도메인 조회 불가
피싱/악성 URL의 대부분이 해외 도메인을 사용하므로 커버리지가 극히 제한적
비표준 응답 형식 ("2024. 03. 17." 등) — 파싱 불안정
RDAP로 대체하여 모든 TLD를 커버
항목
내용
제공사
Google (Chronicle Security)
방식
REST API
무료 티어 제한 : 분당 4회, 일 500회 — 운영환경에서 사용 불가 수준
유료 플랜 비용이 높음 (Enterprise 전용 견적)
GSB와 탐지 소스가 상당 부분 중복 (VirusTotal 내부에서도 GSB 엔진 사용)
API 응답에 60개 이상의 엔진 결과가 포함되어 파싱 복잡도 증가
서비스
미채택 이유
Shodan
호스트/포트 스캔 특화, URL 평판 검사 용도와 불일치
AbuseIPDB
IP 기반 평판, URL/도메인 기반 검사에는 부적합
OpenPhish
무료 피드 업데이트가 느리고 (12시간), 상용 피드는 고비용
요청:
POST /api/v1/check
{ "url" : " https://example.com" }
테스트
POST /api/v1/check
{ "url" : " https://rewards-multibank.com" }
응답:
{
"url" : " https://example.com" ,
"verdict" : " safe" ,
"threat_score" : 0 ,
"sources" : [
{
"url" : " https://example.com" ,
"source" : " Google Safe Browsing" ,
"is_threat" : false ,
"detail" : { "matches" : [] },
"response_time_ms" : 150.23
},
{
"url" : " https://example.com" ,
"source" : " URLhaus" ,
"is_threat" : false ,
"detail" : { "found_in" : null , "threat" : null , "tags" : null , "host" : " example.com" },
"response_time_ms" : 2.15
},
{
"url" : " https://example.com" ,
"source" : " RDAP" ,
"is_threat" : false ,
"detail" : {
"domain" : " example.com" ,
"registrar" : " IANA" ,
"created_date" : " 1995-08-14T04:00:00Z" ,
"expiry_date" : " 2025-08-13T04:00:00Z" ,
"domain_age_days" : 10958 ,
"is_new_domain" : false
},
"response_time_ms" : 620.41
}
],
"checked_at" : " 2025-01-01T12:00:00Z" ,
"total_response_time_ms" : 680.55
}
테스트
{
"url" : " https://rewards-multibank.com/" ,
"verdict" : " malicious" ,
"threat_score" : 70 ,
"sources" : [
{
"url" : " https://rewards-multibank.com/" ,
"source" : " Google Safe Browsing" ,
"is_threat" : true ,
"detail" : {
"matches" : [
{
"threatType" : " SOCIAL_ENGINEERING" ,
"platformType" : " ANY_PLATFORM" ,
"threat" : {
"url" : " https://rewards-multibank.com/"
},
"cacheDuration" : " 300s" ,
"threatEntryType" : " URL"
}
]
},
"response_time_ms" : 144.89
},
{
"url" : " https://rewards-multibank.com/" ,
"source" : " URLhaus" ,
"is_threat" : false ,
"detail" : {
"match" : null
},
"response_time_ms" : 1.94
},
{
"url" : " https://rewards-multibank.com/" ,
"source" : " RDAP" ,
"is_threat" : true ,
"detail" : {
"domain" : " rewards-multibank.com" ,
"registrar" : " Dynadot Inc" ,
"created_date" : " 2026-04-05T13:06:09Z" ,
"expiry_date" : " 2027-04-05T13:06:09Z" ,
"domain_age_days" : 0 ,
"is_new_domain" : true
},
"response_time_ms" : 0
}
],
"checked_at" : " 2026-04-06T04:26:08.760818Z" ,
"total_response_time_ms" : 147.07
}
POST /api/v1/check/gsb
{ "url" : " http://testsafebrowsing.appspot.com/s/phishing.html" }
{
"url" : " http://testsafebrowsing.appspot.com/s/phishing.html" ,
"source" : " Google Safe Browsing" ,
"is_threat" : true ,
"detail" : {
"matches" : [
{
"threatType" : " SOCIAL_ENGINEERING" ,
"platformType" : " ANY_PLATFORM" ,
"threatEntryType" : " URL" ,
"threat" : { "url" : " http://testsafebrowsing.appspot.com/s/phishing.html" }
}
]
},
"response_time_ms" : 123.45
}
HTTP 상태
예외 클래스
발생 조건
500
ConfigurationError
필수 API 키 미설정 (예: GSB_API_KEY)
502
ExternalAPIError
외부 API 호출 실패 (GSB, RDAP 등)
503
ServiceUnavailableError
URLhaus DB 미로드 상태에서 조회 시도
환경변수 (.env 파일 생성 후 아래 값 입력)
변수
설명
필수
기본값
GSB_API_KEY
Google Safe Browsing API 키
필수
""
URLHAUS_UPDATE_INTERVAL_MIN
URLhaus DB 갱신 주기 (분)
선택
60
DEBUG
디버그 로깅 활성화
선택
false
GSB_API_KEY가 없으면 통합 검사에서 GSB를 건너뛰고 URLhaus + RDAP만 사용합니다.
python3 -m venv venv
source venv/bin/activate
pip install -e " .[dev]"
uvicorn app.main:app --reload
서버 실행 후 Swagger UI에서 확인: http://localhost:8000/docs