TAXII 프로토콜의 이해
TAXII는 Trusted Automated Exchange of Intelligence Information의 약어로 HTTPS를 통해 사이버 위협 정보를 공유하는 프로토콜을 의미합니다. TAXII 프로토콜은 2.1 버전까지 나왔지만, 아직까지 개발자들이 참조할만한 국문 자료가 없어서 그런지 국내에서 구현체를 보기 힘들었습니다.
TAXII 규격을 완전히 이해하려면 STIX와 CybOX 규격(STIX 2.0에 통합됨)을 함께 살펴봐야 하지만, 이 글에서는 한국정보보호산업협회(KISIA)를 통한 배포 사례가 있는 TAXII 2.0 버전 위주로 프로토콜을 설명하고, 2.1 버전과 2.0 버전의 차이에 대해 보완하여 설명합니다.
참조 구현체
TAXII 2.1 서버의 참조 구현체로는 OASIS-OPEN이 공개한 medallion이 있습니다. 파이썬으로 구현되어 있으며 실제 어떤 식으로 TAXII 프로토콜이 동작하는지 간단히 확인할 수 있습니다.
정보 공유 모델
OASIS CTI TC에서 정의한 정보 공유 모델은 컬렉션과 채널이 있습니다.
컬렉션
컬렉션은 CTI 객체를 관리하는 논리적 저장소입니다. TAXII 클라이언트와 서버가 요청, 응답 형태로 정보를 교환합니다.
채널
채널은 구독을 걸어놓은 다수의 클라이언트가 서버에서 새로운 데이터를 받아갈 수 있도록 지원하는 통신 모델입니다. 그러나, TAXII 2.0, 2.1 모두 채널에 대한 명세를 정의하고 있지 않습니다.
주요 개념
디스커버리
TAXII 디스커버리는 클라이언트가 네트워크에서 가용한 TAXII 서버가 존재하는지 검색해서 찾을 수 있는 방법을 제공합니다. 인터넷에 공개된 서버라면 DNS SRV 레코드 전달 방식을 사용하고, 인가된 클라이언트만 접속하도록 허용한다면 디스커버리 엔드포인트를 지정해야 합니다. 탐색 URL을 미리 설정해주어야 한다는 의미입니다.
API 루트
API 루트는 TAXII 컬렉션 그룹을 제공합니다. 하나의 TAXII 서버는 여러 개의 API 루트를 지원할 수 있습니다. 이후 모든 요청은 API 루트 경로를 포함합니다.
통신 헤더
클라이언트 헤더
모든 TAXII 요청은 HTTP 헤더에 Accept
헤더를 포함해야 합니다:
Accept: application/vnd.oasis.taxii+json; version=2.0
서버 헤더
모든 TAXII 응답은 HTTP 헤더에 Content-Type
헤더를 포함해야 합니다:
Content-Type: application/vnd.oasis.taxii+json; version=2.0
주요 API
TAXII는 REST API 방식으로 정보를 주고 받습니다. Accept
와 Content-Type
헤더에 기술된 것처럼, 모든 요청과 응답은 JSON 형식을 따릅니다.
GET /taxii
클라이언트가 TAXII 서비스의 디스커버리를 위해 사용합니다. 클라이언트가 서버를 찾는 과정이므로, API 루트까지 정확하게 알고 있다면 이 단계는 생략할 수 있습니다.
서버는 요청을 받으면 아래 예시와 같이 응답합니다.
{
"title": "Some TAXII Server",
"description": "This TAXII Server contains a listing of...",
"contact": "string containing contact information",
"default": "<https://example.com/api1/>",
"api_roots": [
"<https://example.com/api1/>",
"<https://example.com/api2/>",
"<https://example.net/trustgroup1/>"
]
}
- (필수)
title
: TAXII 서버 이름 - (선택)
description
: TAXII 서버에 대한 설명 - (선택)
contact
: 관리자 정보(이메일 주소 등) - (선택)
default
: 기본 API 루트.api_roots
에 존재하는 API 루트 URL 중 하나이어야 합니다. - (선택)
api_roots
: 서버가 제공하는 API 루트 URL 목록. 서버는 클라이언트의 권한에 따라 이 목록을 필터링할 수 있습니다.
GET /[API_ROOT_NAME]
클라이언트가 API 루트를 조회할 때 사용합니다. 이 요청은 API 루트에 대한 메타데이터 조회이므로, 컬렉션 조회 시 이 과정은 생략할 수 있습니다.
서버는 요청을 받으면 아래와 같이 응답합니다.
{
"title": "Malware Research Group",
"description": "A trust group setup for malware researchers",
"versions": ["taxii-2.0"],
"max_content_length": 9765625
}
- (필수)
title
: API 루트 제목 - (선택)
description
: API 루트에 대한 설명 - (필수)
versions
: 이 API 루트가 지원하는 TAXII 프로토콜 버전을 나열합니다. 2.0 버전을 지원하면taxii-2.0
으로 명기합니다. - (필수)
max_content_length
: HTTP 요청 시 전송 가능한 최대 바이트 수. 이 크기를 넘어가면 서버가 HTTP 상태 코드 413 (Request Entity Too Large)으로 응답할 수 있습니다.
GET /[API_ROOT_NAME]/collections
서버가 지정된 API 루트에서 관리되는 컬렉션 목록을 조회할 떄 사용합니다. 조회 대상 컬렉션을 정확히 알고 있다면, 이 과정은 생략할 수 있습니다.
서버는 요청을 받으면 아래와 같이 응답합니다.
{
"collections": [
{
"id": "91a7b528-80eb-42ed-a74d-c6fbd5a26116",
"title": "High Value Indicator Collection",
"description": "This data collection is for collecting high value IOCs",
"can_read": true,
"can_write": false,
"media_types": [
"application/vnd.oasis.stix+json; version=2.0"
]
},
{
"id": "52892447-4d7e-4f70-b94d-d7f22742ff63",
"title": "Indicators from the past 24-hours",
"description": "This data collection is for collecting current IOCs",
"can_read": true,
"can_write": false,
"media_types": [
"application/vnd.oasis.stix+json; version=2.0"
]
}
]
}
제공할 컬렉션이 하나도 없으면 collections
키 자체가 생략되고 {}
로 응답합니다. 각 컬렉션 정보의 속성은 아래와 같습니다:
- (필수)
id
: 컬렉션 식별자에 해당하는 GUID 값 - (필수)
title
: 컬렉션 제목 - (선택)
description
: 컬렉션 설명 - (필수)
can_read
: 읽기 가능 여부(GET
요청)에 대해true
,false
로 표시 - (필수)
can_write
: 쓰기 가능 여부(POST
요청)에 대해true
,false
로 표시 - (선택)
media_types
: 컬렉션에서 지원하는 객체 유형 목록. 생략하면application/vnd.oasis.stix+json
로 간주합니다.
GET /[API_ROOT_NAME]/collections/[ID]
클라이언트가 컬렉션에 저장된 위협 데이터를 검색하거나 목록을 조회할 때 사용합니다. [ID]
에 GET /[API_ROOT_NAME]/collections/
으로 확인한 컬렉션의 id
를 입력합니다. 전체 데이터 조회는 다음 예시와 같이 수행할 수 있습니다: GET /default/collections/91a7b528-80eb-42ed-a74d-c6fbd5a26116/objects/
서버는 요청을 받으면 아래와 같이 응답합니다.
{
"id": "bundle--6773340e-59ad-4548-92b9-ad313f6bd61c",
"objects": [
{
"_collection_id": "91a7b528-80eb-42ed-a74d-c6fbd5a26116",
"created": "2021-05-03T15:07:11.425Z",
"first_observed": "2021-05-03T15:07:11.425123Z",
"id": "observed-data--951af112-02e3-46cb-8852-d2f7dd34a673",
"labels": ["LOGPRESSO"],
"last_observed": "2021-05-03T15:07:11.425123Z",
"modified": "2021-05-03T15:07:11.425Z",
"name": "observed-ipv4-10.10.10.1",
"number_observed": 1,
"objects": {
"0": { "type": "ipv4-addr", "value": "27.72.56.98" },
"1": { "type": "ipv4-addr", "value": "124.160.96.249" }
},
"type": "observed-data"
}
],
"spec_version": "2.0",
"type": "bundle"
}
예시에서는 1개의 객체만 나오지만 실제로는 objects
키의 배열에 여러 개의 STIX 객체를 포함할 수 있습니다. STIX 도메인 객체는 공격 패턴 (Attack Pattern), 캠페인 (Campaign), 침해 지표 (Indicator), 관측 데이터 (Observed Data) 등으로 구성되고, 관측 데이터는 CybOX (Cyber Observable) 명세를 이용하여 표현됩니다. STIX에 대해서는 쏘마 기술 블로그에 잘 정리된 내용이 있으니 이를 참조하시면 좋습니다.
컬렉션 목록을 페이징하려면 요청 헤더에 Range
를 포함시켜 보내면 됩니다.
GET .../collections/91a7b528-80eb-42ed-a74d-c6fbd5a26116/objects/ HTTP/1.1
Range: items 0-49
Accept: application/vnd.oasis.stix+json; version=2.0
인덱스는 0부터 시작하며, 끝 값까지 포함합니다. 즉, 0-49
는 50개의 항목 조회를 의미합니다. 서버가 Range
헤더를 포함한 요청을 받으면 아래와 같이 Content-Range
헤더에 조회 범위와 전체 갯수를 포함하여 응답합니다.
HTTP/1.1 206 Partial Content
Content-Range: items 0-49/500
특정 시점 이후 데이터를 수신하려면 데이터 목록 조회 시 쿼리 스트링에 added_after
매개변수를 포함할 수 있습니다. 타임스탬프는 UTC (GMT+0) 기준입니다.
GET .../collections/91a7b528-80eb-42ed-a74d-c6fbd5a26116/objects/?added_after=2021-05-03T00:00:00.000Z HTTP/1.1
Accept: application/vnd.oasis.stix+json; version=2.0
GET /[API_ROOT_NAME]/collections/[ID]/manifest/
컬렉션에 저장된 데이터를 조회하는 대신, 컬렉션에 대한 정보를 조회하여 추가 정보 수신 여부를 결정할 수 있습니다. 예를 들어, 클라이언트가 GET /default/collections/91a7b528-80eb-42ed-a74d-c6fbd5a26116/manifest/
요청을 보내면 아래와 같이 컬렉션의 메타데이터를 확인할 수 있습니다.
{
"objects": [
{
"id": "indicator--29aba82c-5393-42a8-9edb-6a2cb1df070b",
"date_added": "2016-11-01T03:04:05Z",
"versions": ["2016-11-03T12:30:59.000Z","2016-12-03T12:30:59.000Z"],
"media_types": ["application/vnd.oasis.stix+json; version=2.0"]
},
{
"id": "indicator--ef0b28e1-308c-4a30-8770-9b4851b260a5",
"date_added": "2016-11-01T10:29:05Z",
"versions": ["2016-11-03T12:30:59.000Z"],
"media_types": ["application/vnd.oasis.stix+json; version=2.0"]
}
]
}
POST /[API_ROOT_NAME]/collections/[ID]/objects/
이 요청은 서버에게 지정된 컬렉션에 데이터를 추가하도록 합니다. 참고로, TAXII 2.0에서 컬렉션 데이터를 추가 할 수 있지만 삭제는 지원하지 않습니다. 국내 구현체 중에는 커스텀 확장을 통해 revoke
를 지원하는 사례가 있습니다.
예를 들어, POST /default/collections/91a7b528-80eb-42ed-a74d-c6fbd5a26116/objects/
을 통해 다음과 같은 데이터를 추가할 수 있습니다:
{
"type": "bundle",
"objects": [
{
"type": "observed-data"
"id": "observed-data--951af112-02e3-46cb-8852-d2f7dd34a673",
"created": "2021-05-03T15:07:11.425Z",
"modified": "2021-05-03T15:07:11.425Z",
"labels": ["LOGPRESSO"],
"first_observed": "2021-05-03T15:07:11.425123Z",
"last_observed": "2021-05-03T15:07:11.425123Z",
"number_observed": 1,
"objects": {
"0": { "type": "ipv4-addr", "value": "27.72.56.98" },
"1": { "type": "ipv4-addr", "value": "124.160.96.249" }
}
}
]
}
TAXII 2.0과 2.1의 주요 차이
두 버전의 가장 큰 차이는 두 가지입니다.
- 삭제 지원: 2.1은 DELETE 메소드를 통한 컬렉션 데이터의 삭제를 지원합니다.
- item 단위의 페이징 제거: 새로 추가된
limit
매개변수와 응답의more
속성을 이용하도록 바뀌었습니다.
로그프레소의 TAXII 지원
로그프레소 소나 및 마에스트로에서는 아래의 쿼리 커맨드를 통해 TAXII 서버 연동을 지원합니다.
taxii-discovery
: TAXII 서버에서 API 루트를 포함한 일반 정보를 조회합니다.taxii-api-root
: TAXII 서버에서 지정된 API 루트 정보를 조회합니다.taxii-collections
: 지정된 TAXII 서버의 API 루트에서 컬렉션 목록을 조회합니다.taxii-objects
: 지정된 TAXII 서버의 컬렉션에서 객체 목록을 조회합니다.taxii-add-observed-ip
: 지정된 TAXII 서버의 컬렉션에 IP 주소 객체를 추가합니다.taxii-delete-object
: 지정된 TAXII 서버의 컬렉션에서 객체를 삭제합니다(전용 확장 혹은 2.1 버전 대응).
참고자료
완전한 TAXII 및 STIX 명세를 확인하시려면 아래 링크를 방문하세요.