본문으로 건너뛰기

"Cloudflare" 태그로 연결된 3개 게시물개의 게시물이 있습니다.

모든 태그 보기

Workers Spectre 재현

· 약 5분

Spectre는 2018년 이야기라고 생각하고 있었는데, 그게 아니었어요. 멀티테넌트 서버리스에서는 아직 진행 중인 문제입니다.

Cloudflare가 2026년 8월 19일 자사 Workers 환경에서 원격 Spectre 공격을 재현한 연구를 공개했습니다. 에든버러 대학교와 공동 연구입니다. 결과 문장이 짧고 셉니다. 프로덕션 환경에서 초당 최대 12비트, 정확도 99%로 안정적으로 유출했습니다.

무엇을 어떻게 빼냈나

피해자 Worker 안에 JWT 토큰을 두고 비트 단위로 읽어 냈습니다. 첫 바이트가 문자 e, 이진값 0b01100101로 정확히 나왔습니다.

초당 12비트는 느리게 들립니다. JWT 하나가 수백 바이트라면 몇 시간이 걸립니다. 하지만 공격이 성립하는지 여부에서는 속도가 본질이 아닙니다. 정확도 99%로 안정적이라는 쪽이 문제입니다. 오류율이 높으면 재시도로 시간이 곱해지는데, 그게 아니라는 뜻입니다.

원격 타이머라는 전제가 무너졌다

Spectre 계열 공격에는 정밀한 시간 측정이 필요합니다. 캐시에 있는 값을 읽는 시간과 없는 값을 읽는 시간의 차이가 신호이기 때문입니다. 그 차이가 나노초 단위입니다.

2018년 이후 브라우저와 런타임이 취한 대응 중 하나가 타이머를 무디게 만드는 것이었습니다. Cloudflare Workers도 타이머를 동결(frozen timer)했습니다. Worker 안에서 시간을 재도 값이 변하지 않게 만든 것입니다. 멀티스레딩과 공유 메모리도 껐습니다.

이 연구가 그 전제를 치웁니다. 외부 서버로 나가는 WebSocket 연결 하나로 충분했습니다. 그쪽에서 고해상도 타임스탬프를 받아 오면 됩니다. 몇 번의 표본만으로 중앙값 기준 밀리초 미만 해상도를 얻었습니다.

여기에 증폭 기법을 붙였습니다. L1 캐시의 tree-based PLRU 교체 정책을 이용해 단일 캐시 사건에서 나온 시간 차이를 임의로 키웠습니다. 나노초 차이를 밀리초 해상도로 읽을 수 있게 만드는 단계입니다.

isolate가 프로세스가 아니라서

Cloudflare Workers는 수만 테넌트를 같은 OS 프로세스 안에서 돌립니다. 분리 단위가 프로세스가 아니라 V8 isolate입니다. 이 선택이 효율을 만듭니다. 프로세스를 띄우는 비용 없이 요청이 들어오면 즉시 실행되고, cold start가 사실상 사라집니다.

대가는 분리 강도입니다. 같은 프로세스 안이면 주소 공간이 이어져 있습니다. Worker 프로세스 안에서 임의 읽기가 한 번 성립하면 다른 테넌트의 데이터에 닿습니다.

공격자가 피해자와 같은 프로세스에 들어가는 방법도 단순했습니다.

fetch("https://victim.example")

피해자 스크립트를 호출하면 스케줄러가 바로 그 프로세스 안에 피해자 Worker 인스턴스를 띄웁니다. Durable Objects와 지속 WebSocket 연결로 isolate를 계속 살려 뒀습니다.

gadget은 두 개를 썼습니다. 첫 번째는 압축된 힙 포인터를 유출합니다. isolate의 힙 기준 주소를 얻는 단계입니다. 두 번째는 TypedArray의 backing store를 이용한 speculative type confusion으로, 공격자가 만든 임의의 64비트 포인터에서 값을 읽습니다. 기준 주소를 알아낸 뒤 임의 주소로 넘어가는 구성입니다.

2021년 방어는 왜 놓쳤나

Cloudflare가 2021년에 내놓은 방어가 Dynamic Process Isolation(DyPrIs)입니다. 의심스러워 보이는 스크립트를 찾아 별도 프로세스로 격리합니다. 하드웨어 성능 카운터로 분기 예측 실패 같은 신호를 감시합니다.

이 방어가 통하지 않은 이유가 둘입니다.

첫째, 시점입니다. DyPrIs는 스크립트 실행이 끝난 뒤에 격리했습니다. 그런데 이 공격은 WebSocket keep-alive로 실행을 몇 시간에서 하루까지 이어 갑니다. 격리가 적용될 시점이 오지 않습니다.

둘째, 지표가 희석됐습니다. DyPrIs는 분기 예측 실패 횟수를 iTLB 접근 횟수로 정규화합니다. 그런데 WebSocket 트래픽 자체가 iTLB 활동을 늘립니다. 분모가 커지니 비율이 탐지 문턱 아래로 내려갑니다.

방어를 우회한 방식이 취약점을 새로 찾은 게 아니라, 정상 기능인 지속 연결을 쓴 것이라는 점이 인상적입니다. 탐지 지표를 정할 때 그 지표가 정상 트래픽으로 흔들릴 수 있는지가 중요하다는 사례입니다.

새로 붙인 방어

이후 적용한 것이 셋입니다.

V8 샌드박스입니다. JavaScript 힙의 상당 부분에서 원시 64비트 포인터를 제거합니다. 이 연구가 쓴 speculative type confusion gadget은 임의의 64비트 포인터를 만들어 넣는 방식이었으니, 그 재료를 없애는 접근입니다. Cloudflare는 해당 gadget을 "재사용하기 더 어렵게" 만든다고 표현했습니다. 불가능하다고 하지 않았습니다.

Memory Protection Keys(MPK)입니다. 2025년 9월에 배포했습니다. isolate별 힙이 하드웨어로 강제되는 접근 경계 뒤에 놓입니다. 소프트웨어 검사가 아니라 CPU가 막습니다. 같은 프로세스 안이라는 구조를 유지하면서 분리를 강화하려면 이 방향이 남습니다.

DyPrIs 개선입니다. 장시간 실행과 I/O 위주 워크로드를 다루도록 바꿨고, 실행이 끝난 뒤가 아니라 실행 중에 탐지합니다. 위 두 회피 경로를 각각 겨냥한 수정입니다.

회피 경로대응
실행 종료 후 격리실행 중 탐지로 전환
iTLB 정규화 희석장시간, I/O 위주 워크로드 처리
임의 64비트 포인터 조립V8 샌드박스
같은 프로세스 내 힙 접근MPK 하드웨어 경계

실제 악용 흔적은 지난 3년간 발견되지 않았다고 밝혔습니다.

남는 질문

이 글에서 가장 오래 남는 대목은 결과 수치가 아니라 구조 쪽입니다. isolate 기반 멀티테넌시는 cold start를 없애려는 선택이었고, 그 선택이 만든 공격면을 하드웨어 기능(MPK)으로 메우는 단계에 와 있습니다. 소프트웨어 격리를 얇게 만든 대가를 CPU 기능으로 지불하는 구조입니다.

NGINX Rift처럼 코드 결함을 고치는 사례와 다른 종류의 문제입니다. 고쳐야 할 한 줄이 없습니다. 아키텍처 선택의 청구서라서, 방어도 계층을 더 쌓는 형태가 됩니다.

DB를 운영하는 쪽에서 읽으면 결론이 좀 밋밋합니다. 서버리스 런타임 위에 올려 둔 코드가 다루는 비밀은 언제든 같은 프로세스 이웃에게 노출될 수 있다고 가정하는 편이 낫습니다. DB 접속 자격증명을 Worker 환경 변수에 두고 오래 살려 두는 구성이라면, 자격증명 수명을 짧게 잡는 것이 이 종류의 공격에 대한 가장 현실적인 대응입니다.

참고

Cloudflare Agents Week

· 약 4분

Cloudflare는 매년 몇 차례 "week"라는 이름으로 발표를 몰아서 내놓는데, 8월 3일부터 7일까지는 처음으로 에이전트가 주인공이었어요. 주간 회고 글 기준으로 발표를 훑으면, 개별 제품보다 그것들이 가리키는 방향이 더 흥미롭습니다.

Cloudflare OS: 에이전트를 사내 시스템에 붙이는 안전한 방법

가장 큰 화제는 Cloudflare OS였습니다. 이름과 달리 커널이 있는 운영체제가 아니라, 사내 시스템에 에이전트를 연결하는 워크스페이스 플랫폼입니다. Apache 2.0 라이선스로 GitHub에 공개됐습니다.

구조의 핵심은 권한 모델입니다. 에이전트는 권한 0에서 출발합니다. 사내 시스템(위키, 티켓, DB, 배포 도구) 각각의 앞에 Gatekeeper Worker라는 관문 코드를 두고, 에이전트는 그 관문이 허용하는 범위의 작업만 요청할 수 있습니다. "에이전트에게 사내 API 키를 통째로 주면 어디까지 뒤질지 모른다"는 공포를, 시스템별로 좁게 정의된 통로로 바꾸는 설계입니다.

인프라 회사가 사내 업무 계층까지 내려와 그걸 오픈소스로 풀었다는 점, 그리고 하필 "OS"라는 이름을 붙였다는 점 때문에 Hacker News에서 논쟁이 붙었습니다. 이름이야 어떻든, 에이전트 권한 문제를 프록시 계층으로 푸는 패턴 자체는 참고할 가치가 있습니다. 뒤에 나올 이야기지만, 이 패턴은 사내 DB에 에이전트를 붙일 때 그대로 필요해지는 물건입니다.

Wallets: 에이전트에게 용돈을 준다

Cloudflare Wallets는 문제 정의가 직관적이라 Fortune 같은 일반 매체까지 다뤘습니다. 에이전트는 은행 계좌를 못 만든다는 것입니다. 에이전트가 유료 API를 호출하고 리소스를 사려면 결국 사람의 카드가 어딘가에 물려 있어야 하는데, 그 카드에는 한도도 범위도 걸 수 없습니다.

Wallets의 구조는 위임입니다. 사람이 보유한 Account Wallet에 스테이블코인을 담아 두고, 에이전트마다 Virtual Wallet을 만들어 지출 한도, 허용 목록, 건당 최대 금액을 걸어 위임합니다. 결제 프로토콜로는 x402를 지원합니다. 8월 4일에는 cloudflare.pay 핸들 예약이 열렸고, 실제 지갑 인프라는 몇 달에 걸쳐 나온다고 합니다.

에이전트 지출 통제를 IAM처럼 다루는 첫 대형 시도라는 점에서, 실물이 나오면 다시 볼 가치가 있습니다.

나머지 발표들: 프로토콜 계층

한 주의 나머지를 채운 것은 프로토콜과 도구입니다.

  • WebMCP 프리뷰: 웹사이트가 자신을 MCP 서버로 노출하는 방식의 프리뷰. 에이전트가 화면을 스크래핑하는 대신 구조화된 인터페이스로 사이트와 대화합니다
  • MCPv2: MCP 프로토콜의 다음 버전 지원
  • Agent Access Model: 에이전트의 리소스 접근을 사람 사용자와 구분해 다루는 모델
  • Kitesurf: 에이전트 우선(agent-first) 브라우저

그리고 주가 끝난 뒤에도 같은 결의 발표가 이어졌습니다. 8월 14일에는 MCP 트래픽을 식별하고 보호하는 기능이 나왔습니다. 에이전트가 만드는 트래픽을 봇도 사람도 아닌 제3의 유형으로 인정하고 전용 보안 장치를 붙이기 시작한 것입니다.

관통하는 그림

발표를 나열하면 잡다해 보이지만, 겹쳐 놓으면 한 문장이 됩니다. 에이전트를 인터넷의 1급 시민으로 만들기 위한 인프라 계층을 선점하겠다는 것입니다.

사람 사용자에게는 이미 계정(신원), 결제 수단, 브라우저, 접근 제어가 있습니다. Cloudflare는 그 네 가지의 에이전트 버전을 한 주에 걸쳐 내놨습니다. Agent Access Model이 신원, Wallets가 결제, Kitesurf가 브라우저, Cloudflare OS와 Gatekeeper가 접근 제어입니다. CDN 회사의 신사업 나열이 아니라 계층 하나를 통째로 짜는 그림입니다.

DBA 관점에서 눈여겨볼 지점을 하나 꼽자면 Gatekeeper 패턴입니다. 에이전트에게 DB 접근을 열어 달라는 요청은 이미 현실에서 오고 있습니다(AlloyDB의 MCP 서버 때도 같은 이야기를 했습니다). 그때 계정과 권한을 직접 주는 대신, 좁게 정의된 관문 계층을 사이에 두는 설계가 표준이 될 가능성이 큽니다. 미리 그려 두면 요청이 왔을 때 당황하지 않을 수 있어요.

참고 자료

커스텀 도메인과 SSL

· 약 4분

이 글은 Hugo + PaperMod 블로그 세팅 시리즈의 마지막 글이에요.

  1. Hugo + PaperMod로 기술 블로그 만들기
  2. NCP 서버에 Hugo 블로그 배포하기
  3. 커스텀 도메인 연결과 Let's Encrypt SSL 설정 ← 현재 글

진행 순서

도메인과 SSL 설정에는 순서가 있습니다. 순서가 바뀌면 인증서 발급이 실패합니다.

도메인 구매 → DNS A 레코드 설정 → Nginx server_name 변경
→ hugo.toml baseURL 변경 → certbot SSL 발급 → 재배포

1. 도메인 구매

도메인 구매처는 여러 곳이 있습니다.

업체특징
Cloudflare원가 판매, DNS 관리 편리
가비아국내 최대, 한글 지원
NCP서버와 같은 곳에서 관리

저는 Cloudflare를 선택했어요. DNS 관리 UI가 깔끔하고 추가 비용이 없어요.

.dev 도메인 참고사항

.dev는 Google이 관리하는 TLD로, HSTS preload가 적용되어 있습니다.

  • 모든 .dev 도메인은 브라우저에서 HTTPS만 허용됩니다.
  • HTTP로는 접속 자체가 불가능합니다.
  • SSL 설정이 필수입니다.

기술 블로그에 어울리는 도메인이지만, SSL 없이는 쓸 수 없다는 점을 알고 있어야 합니다.

2. DNS 설정

Cloudflare DNS에서 A 레코드를 추가합니다.

TypeNameContentProxy
A@서버IPDNS only

중요: Proxy를 반드시 꺼야 합니다(회색 구름). Cloudflare 프록시가 켜져 있으면 서버 IP가 Cloudflare로 대체되어 Let's Encrypt 인증서 발급이 복잡해집니다.

DNS 전파를 확인합니다.

dig +short dbalog.dev
# 서버IP가 나오면 성공

보통 Cloudflare는 수 분 내로 전파됩니다.

3. Hugo 설정 변경

hugo.toml의 baseURL을 변경합니다.

# 변경 전
baseURL = "https://example.com/"
# 변경 후
baseURL = "https://dbalog.dev/"

이 값은 sitemap, RSS, 메타 태그 등 블로그 전체에서 사용되므로 반드시 변경해야 합니다.

4. Nginx server_name 변경

deploy/nginx/blog.conf:

server_name dbalog.dev; # IP 또는 example.com에서 변경

서버에 반영:

scp deploy/nginx/blog.conf ncp-blog:/etc/nginx/conf.d/blog.conf
ssh ncp-blog "nginx -t && systemctl reload nginx"

5. Let's Encrypt SSL 인증서 발급

certbot 설치

Rocky Linux 9 기본 저장소에는 certbot이 없습니다. EPEL 저장소를 먼저 추가합니다.

ssh ncp-blog "dnf install -y epel-release"
ssh ncp-blog "dnf install -y certbot python3-certbot-nginx"

인증서 발급

certbot의 --nginx 플러그인이 인증서 발급과 Nginx 설정 수정을 한번에 처리합니다.

ssh ncp-blog "certbot --nginx -d dbalog.dev \
--non-interactive \
--agree-tos \
--email your@email.com \
--redirect"
  • --nginx: Nginx 설정을 자동으로 수정
  • --redirect: HTTP→HTTPS 301 리다이렉트 자동 추가
  • --non-interactive: 대화형 프롬프트 없이 실행

성공하면 이런 메시지가 나옵니다.

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/dbalog.dev/fullchain.pem
Key is saved at: /etc/letsencrypt/live/dbalog.dev/privkey.pem
This certificate expires on 2026-07-09.

certbot이 Nginx에 추가한 설정

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/dbalog.dev/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dbalog.dev/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

그리고 HTTP→HTTPS 리다이렉트를 위한 별도 server 블록:

server {
if ($host = dbalog.dev) {
return 301 https://$host$request_uri;
}
listen 80;
server_name dbalog.dev;
return 404;
}

인증서 자동 갱신

Let's Encrypt 인증서는 90일마다 만료됩니다. certbot이 systemd 타이머를 자동 등록하므로 수동 갱신이 필요 없습니다.

갱신 테스트:

ssh ncp-blog "certbot renew --dry-run"

6. 재배포

baseURL이 변경되었으므로 빌드 후 재배포합니다.

./deploy.sh

확인

# HTTPS 접속
curl -s -o /dev/null -w "%{http_code}" https://dbalog.dev/
# 200

# HTTP → HTTPS 리다이렉트
curl -s -o /dev/null -w "%{http_code}" http://dbalog.dev/
# 301

브라우저에서 https://dbalog.dev/에 접속하면 자물쇠 아이콘이 표시됩니다.

시리즈 마무리

3편에 걸쳐 Hugo 블로그를 처음부터 세팅했어요.

  1. Hugo + PaperMod 설치 및 설정
  2. NCP 서버에 Nginx + rsync 배포
  3. 도메인 연결 + SSL 인증서

이제 남은 건 글을 쓰는 것뿐이고, 마크다운 파일을 만들고 ./deploy.sh를 실행하면 끝이에요.

참고 자료