
Slopsquatting: AI 환각(Hallucination)을 무기화하는 공급망 공격
요약
AI 모델의 환각(Hallucination) 현상을 악용하여 존재하지 않는 패키지 이름을 생성하고, 공격자가 이를 선점하는 'Slopsquatting' 공격 기법을 소개합니다. 기존의 타이포스쿼팅과 달리 인간의 오타가 아닌 AI의 오류를 타겟으로 삼는 새로운 공급망 공격의 위험성을 경고합니다.
핵심 포인트
- Slopsquatting은 AI의 환각으로 생성된 가짜 패키지 이름을 공격자가 선점하는 방식임
- 인간의 오타에 의존하던 기존 타이포스쿼팅보다 공격 성공 확률이 높음
- 개발자가 AI의 제안을 과도하게 신뢰할 때 공급망 공격에 노출될 수 있음
- npm, pip 등 주요 패키지 매니저 생태계 전반에 걸친 보안 위협임
Typosquatting(타이포스쿼팅)이 당신의 오타에 도박을 건다면, Slopsquatting(슬롭스쿼팅)은 당신의 AI 어시스턴트에 도박을 겁니다. 모델이 존재하지 않는 패키지를 만들어내면, 공격자는 그 이름을 등록하고 설치가 실행되기를 기다립니다. 여기서는 킬 체인(kill chain), 기존 방어 체계가 이를 놓치는 이유, 그리고 npm, Composer, pip 전반에서 이를 실제로 차단하는 방법에 대해 알아봅니다.
코딩 어시스턴트가 방금 당신에게 다음과 같은 명령어를 실행하라고 말했다고 상상해 보세요:
pip install requests-oauth2-helper
올바르게 보입니다. 당신은 requests 호출에 OAuth2 토큰을 첨부하는 깔끔한 방법을 요청했고, 그 이름은 정확히 그런 기능을 수행할 것 같은 패키지처럼 읽힙니다. 대소문자 표기법은 관용적이고, 하이픈 사용은 생태계와 일치하며, 다른 50개의 실제 PyPI 헬퍼 패키지처럼 들립니다. 그래서 당신은 실행합니다. 테스트는 통과합니다. 그리고 다음 단계로 넘어갑니다.
문제는 모델이 이 패키지를 제안하도록 학습되었을 당시 requests-oauth2-helper라는 패키지가 존재하지 않았다는 점입니다. 모델이 지어낸 것입니다. 그리고 만약 공격자가 주의를 기울이고 있었다면, 그 이름은 더 이상 비어 있지 않습니다.
이것이 바로 slopsquatting입니다. Typosquatting의 핵심인 인간의 오타를 기계의 환각(hallucination)으로 대체하는 공급망 공격(supply chain attack)입니다. 이 용어는 Python Software Foundation의 Developer-in-Residence인 Seth Larson에 의해 만들어졌으며, 2025년 Ecosyste.ms의 Andrew Nesbitt에 의해 대중화되었습니다. 이는 작지만 끔찍한 결과를 초래하는 아이디어입니다. 이제 더 이상 reqeusts라고 오타를 낼 필요가 없습니다. 당신의 AI는 아주 당당하게 가짜 패키지를 건네줄 것이고, 당신은 자신의 오타보다 AI를 더 신뢰하게 될 것입니다.
Typosquatting은 당신의 실수를 필요로 했지만, 이것은 그렇지 않습니다.
Typosquatting은 오래된 방식입니다. express 옆에 expres를 등록하거나, python-dateutil 옆에 python-dateutl을 등록하고 누군가의 손가락이 미끄러지기를 기다리는 방식입니다. 효과는 있지만, 확률이 낮은 숫자 게임입니다. 대부분의 사람은 express를 정확하게 철자합니다. 공격자는 철자를 틀리는 아주 적은 비율의 사람들을 낚으려 합니다.
Slopsquatting은 인간의 실수에 대한 의존성을 완전히 제거합니다. 개발자는 글자 하나하나를 정확하게 입력합니다. 왜냐하면 현재 자기 자신보다 더 신뢰하는 소스, 즉 방금 주변 코드를 작성한 어시스턴트(Assistant)로부터 이름을 복사해 오기 때문입니다. 실수는 이미 상류(Upstream) 단계에서 모델에 의해 발생했으며, 인간은 이를 충실히 재현하고 있는 것입니다.
여기서 중요한 변화가 일어납니다. Typosquatting(타이포스쿼팅)에서는 공격자가 사용자의 손가락이 실수할 만한 것을 추측합니다. 반면 Slopsquatting에서는 공격자가 전혀 추측할 필요가 없습니다. 그들은 모델이 실제로 출력하는 내용을 대규모로 읽고, 거기서 나타나는 이름들을 등록하기만 하면 됩니다. 모델이 공격자를 대신해 타겟 선정(Target selection)을 해주고 있는 셈입니다.
이것이 작동하는 이유: 환각(Hallucination)은 빈번하며, 더 나쁜 것은 반복된다는 점입니다
만약 모델의 환각(Hallucination)이 드물고 무작위적이었다면, Slopsquatting은 각주 정도로 끝났을 것입니다. 가짜 이름을 등록하고, 영원히 기다려도, 아무도 잡지 못했을 테니까요. 이것이 실제 위협이 되는 이유는 위의 두 가지 사항이 모두 사실이 아니기 때문입니다.
첫 번째는 규모(Scale)입니다. USENIX Security 2025 연구인 We Have a Package for You!는 16개의 LLM(대규모 언어 모델)을 통해 Python과 JavaScript에서 576,000개의 코드 샘플을 생성하고 모델이 추천한 모든 패키지를 확인했습니다. 추천된 패키지의 19.7%가 존재하지 않았습니다. 이는 벤치마크의 끝자락에서 발생하는 반올림 오차 수준이 아닙니다. 다섯 개 중 하나꼴입니다. 이 연구는 205,474개의 고유한 환각 패키지 이름을 기록했습니다. 상용 모델은 상대적으로 더 나은 동작을 보였지만(평균 최소 5.2%), 오픈 소스 모델은 더 나빴으며(최소 21.7%), 그 누구도 깨끗하지 않았습니다.
만약 모든 환각이 저마다 다른 고유한 형태(Unique snowflake)였다면 규모 면에서 여전히 관리 가능한 수준이었을 것입니다. 공격자가 20만 개의 이름을 등록할 수는 없으며, 어떤 이름이 다시 나타날지 알 방법도 없기 때문입니다. 하지만 치명적인 발견은 환각이 **재현 가능하다(Reproducible)**는 점입니다. 연구진은 가짜 패키지를 생성했던 500개의 프롬프트를 가져와 각각 10번씩 더 실행했습니다. 환각 패키지의 43%는 매번 똑같이 나타났습니다. 58%는 한 번 이상의 실행에서 나타났습니다. 단 39%만이 다시 나타나지 않았습니다.
이 사실을 곱씹어 보십시오. 이러한 조작의 거의 절반은 노이즈가 아니라 안정적인 모델의 동작입니다. 이는 경제적 논리를 완전히 뒤바꿔 놓습니다. 공격자는 모든 것을 등록할 필요가 없습니다. 모델의 출력을 채굴하여 반복되는 이름을 유지하고, 고착화된(sticky) 이름들을 등록하기만 하면 됩니다. 재현성(reproducibility) 자체가 정찰(reconnaissance)이 됩니다. 모델은 미래의 개발자에게 어떤 가짜 이름이 전달될 가능성이 가장 높은지 반복해서 정확히 알려주는 셈입니다.
경고
이름이 실제 패키지처럼 보일 필요조차 없습니다. 레벤슈타인 거리(Levenshtein distance)를 사용한 동일한 연구에 따르면, 환각(hallucination)된 이름 중 실제 이름의 단순 오타인 경우는 13%에 불과했습니다. 약 38%는 중간 정도의 유사성을 보였으며, 거의 절반은 매우 이질적이었습니다. 즉, 완전히 조작되었지만 주변 코드의 맥락상 여전히 믿을 만한 형태였습니다. 마지막 그룹은 타이포스쿼팅(typosquatting) 탐지를 그대로 통과해 버리는 그룹이며, 그 이유는 나중에 다시 다루겠습니다.
킬 체인(Kill chain), 단계별 분석
이 모든 것은 이론적인 이야기가 아닙니다. 이는 깔끔하고 반복 가능한 시퀀스이며, 각 연결 고리는 여러분이 평범한 업무 일과 중에 목격했을 법한 일들입니다.
1. 모델이 임포트(import)를 제안합니다. 여러분이 특정 기능을 요청하면, 어시스턴트는 코드를 작성하며 문제의 형태에 맞는 의존성(dependency)을 찾습니다. 그리고 이름을 출력합니다. 그 이름은 지어낸 것이지만 문법적으로는 완벽합니다. 해당 생태계의 관례, 대소문자 구분, 그리고
4. 자격 증명(Credentials)이 유출됩니다. 페이로드(Payload)는 빌드 에이전트(Build agent)가 항상 주변에 두고 다니는 것들을 읽어들입니다: 환경 변수(Environment variables), ~/.aws/credentials, ~/.npmrc 토큰, GITHUB_TOKEN, .env 파일 등입니다. 그리고 이를 공격자가 제어하는 엔드포인트(Endpoint)로 POST 요청을 보냅니다. 외부에서 보기에는 패키지가 설치 중에 메타데이터를 가져오는 것처럼 보입니다. 하지만 내부적으로는, 여러분의 CI(지속적 통합) 시스템이 낯선 이에게 열쇠를 건네준 셈입니다.
이 모든 과정은 소름 끼칠 정도로 조용합니다. 익스플로잇(Exploit)도, CVE도, 교묘한 메모리 오염(Memory corruption)도 없습니다. 개발자가 이름을 믿었고, 하루에 50번씩 실행하는 표준 설치 명령어를 실행했을 뿐입니다. 그것이 공격의 전부입니다.
그리고 사람들이 신뢰 단계에서 속아 넘어가는 것은 가설이 아닙니다. Lasso Security의 보안 연구원 Bar Lanyado는 모델들이 huggingface-cli라는 이름의 Python 패키지를 반복적으로 환각(Hallucination)하는 것을 발견했습니다. 그는 실험 삼아 PyPI에 정확히 그 이름으로 된 빈 패키지를 등록했습니다. 이후 3개월 동안 이 패키지는 15,000회 이상의 실제 다운로드를 기록했으며, Alibaba의 GraphTranslator 프로젝트는 실제 도구가 pip install -U "huggingface_hub[cli]"로 설치되어야 함에도 불구하고 README에 pip install huggingface-cli를 권장하게 되었습니다. Lanyado의 패키지는 의도적으로 무해했습니다. 하지만 Slopsquatter의 패키지는 그렇지 않을 것입니다.
동일한 아이디어, 네 가지 생태계, 네 가지 다른 피해 범위(Blast radii)
요약하자면 "JavaScript, PHP, Go 모두에 영향을 미친다"는 것입니다. 더 정확하게 말하자면, 각 생태계가 공격자에게 서로 다른 양의 밧줄(공격 수단)을 쥐여준다는 것이며, 그 차이를 아는 것이 여러분의 방어 예산을 어디에 써야 할지를 알려줍니다.
npm은 공격자에게 가장 많은 것을 제공합니다. 라이프사이클 스크립트 (preinstall, install, postinstall)는 npm install 시 자동으로 실행됩니다. 이것이 전형적인 공격 벡터(vector)이며, 생태계가 마침내 변화하고 있는 정확한 이유입니다. pnpm v10은 2025년 초에 기본적으로 의존성 라이프사이클 스크립트를 비활성화했으며, npm도 이를 따랐습니다. 2026년 6월에 발표된 npm v12는 자동 스크립트 실행을 기본적으로 꺼둡니다. 해당 버전들을 사용하기 전까지 postinstall은 여러분의 CI(지속적 통합)를 겨냥한 장전된 총과 같습니다.
package.json (악성 의존성)
{
"name": "requests-oauth2-helper",
"version": "1.0.3",
...
pip도 그 뒤를 바짝 쫓고 있습니다. 소스 배포판(source distribution)은 설치 시점에 메타데이터를 계산하고 빌드하기 위해 setup.py를 실행하며, 이는 pip install 시 임의의 코드(arbitrary code)가 실행될 수 있음을 의미합니다. 휠(wheel, .whl) 파일은 설치 시점에 동일한 방식으로 코드를 실행하지 않으므로, --only-binary 옵션은 단순한 편의 기능이 아니라 실질적인 보안 강화(hardening) 수단입니다.
# 소스 빌드를 거부하고, 미리 빌드된 휠(wheel)만 허용합니다
pip install --only-binary :all: requests-oauth2-helper
Composer는 설계상 이 세 가지 중 조용히 가장 안전합니다. Composer는 오직 루트(root) 패키지의 composer.json에 정의된 스크립트만 실행합니다. 의존성 자체의 scripts 블록은 무시됩니다. 따라서 Slopsquatting된 Composer 패키지는 npm 패키지가 postinstall을 전달하는 방식처럼 자동으로 post-install-cmd를 넘겨줄 수 없습니다. 한 가지 주의할 점은 플러그인입니다. 악성 Composer 플러그인은 설치 이벤트를 후킹(hook)할 수 있으므로, 신뢰할 수 없는 트리(tree)를 위해 composer install --no-plugins --no-scripts 명령어가 존재합니다. 페이로드(payload)는 여러분의 코드가 실제로 해당 플러그인을 호출할 때까지 기다려야 하며, 이는 "설치 시 실행됨"보다 유의미하게 높은 진입 장벽입니다.
Go에는 설치 스크립트(install scripts)가 전혀 없으므로, 킬 체인(kill chain)이 변화합니다. go get과 go build는 패키지를 추가하는 즉시 해당 패키지의 임의적인 설정 코드를 실행하지 않습니다. Slopsquatting된 Go 모듈 내의 악성 코드는 프로그램이 이를 처음 실행할 때, 즉 임포트(import) 시 패키지의 init() 함수를 통해 실행되거나 go test 도중에 실행됩니다. 즉, 나중에 실행될 뿐, 실행되지 않는 것은 아닙니다.
Go는 모듈 프록시(module proxy)를 통해 자신만의 독특한 방식을 더합니다. Socket은 BoltDB의 백도어가 심어진 타이포스쿼팅(typosquat) 패키지인 github.com/boltdb-go/bolt를 발견했습니다. 이 패키지는 2021년 11월에 업로드되어 Go 모듈 미러(mirror)에 캐싱되었으며, 이후 Git 태그가 깨끗한 코드로 재작성되었습니다. 하지만 프록시는 캐싱된 악성 버전을 계속해서 제공했습니다. 이 공격은 3년 넘게 탐지되지 않았습니다. 빌드를 재현 가능하게(reproducible) 만들기 위한 캐싱이 독이 든 버전을 지속 가능하게(durable) 만들기도 하는 것입니다.
따라서 사고 모델(mental model)은 "하나의 공격, 세 가지 언어"가 아닙니다. 다음과 같습니다: npm과 pip는 설치 시점에 발사되고, Composer는 플러그인이나 호출을 기다리며, Go는 실행될 때까지 기다렸다가 그 악성 버전을 영원히 기억합니다. Slopsquatter는 자신에게 가장 빠르고 조용하게 트리거(trigger)를 제공하는 생태계를 선택합니다.
기존의 공급망 방어 체계가 이를 잡아내지 못하는 이유
여기 불편한 진실이 있습니다. 여러분이 이미 운영 중인 대부분의 통제 수단은 다른 위협 모델(threat model)을 위해 구축되었으며, Slopsquatting은 그 사이의 틈새를 타고 들어옵니다.
락파일(Lockfiles)은 첫 설치 이후에만 도움이 됩니다. package-lock.json이나 composer.lock은 정확한 버전과 해시(hash)를 고정하여 의존성이 사용자 모르게 변경되지 않도록 합니다. 이는 이미 검증하고 고정한 패키지에는 매우 유용합니다. 하지만 Slopsquatting은 완전히 새로운 이름의 첫 설치를 공격합니다. 이 패키지를 이전에 설치한 적이 없기 때문에 락파일에는 아직 아무것도 기록되어 있지 않습니다. AI가 불과 30초 전에 해당 패키지를 제안했을 뿐이니까요. 락파일은 여러분이 처음 가져온 악성 버전을 충실히 기록하고, 이제 그것을 고정해 버립니다. 락파일은 연속성(continuity)을 보호할 뿐, 첫 접촉(first contact)을 보호하지는 못합니다.
스캐너는 알려진 악성(known-bad)을 탐색하며, 이것은 한 번도 본 적 없는 것입니다. 취약점 스캐너(vulnerability scanner)나 멀웨어 피드(malware feed)는 누군가가 이미 플래그(flag)를 지정한 패키지와 대조하여 확인합니다. 어제 등록되었고, 이번 분기에 들어서야 재발하기 시작한 환각(hallucination)을 겨냥한 슬롭스쿼팅(slopsquatted) 패키지는 CVE도, 권고문(advisory)도, 평판(reputation)도, 이력(history)도 없습니다. 그것은 부재(absence)를 통해 깨끗한 상태로 남습니다. 스캐너가 고장 난 것이 아닙니다. 스캐너는 다른 질문에 답하고 있는 것입니다. "이것이 알려진 위협인가?"라는 질문은 "이것이 인간이 선택한 실제 패키지인가?"라는 질문과 다릅니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기