Translated ['', 'src/pentesting-web/dependency-confusion.md'] to ko

This commit is contained in:
Translator 2025-08-21 02:40:56 +00:00
parent c33fe60822
commit 6e08a82077

View File

@ -2,44 +2,266 @@
{{#include ../banners/hacktricks-training.md}}
## Basic Information
요약하자면, dependency confusion 취약점은 프로젝트가 **잘못된** 이름, **존재하지 않는** 이름 또는 **명시되지 않은 버전**의 라이브러리를 사용할 때 발생하며, 사용된 의존성 저장소가 **공개** 저장소에서 **업데이트된 버전을 수집**할 수 있도록 허용합니다.
Dependency Confusion (또는 대체 공격)은 패키지 관리자가 의도하지 않은, 덜 신뢰할 수 있는 레지스트리/소스(보통 공개 레지스트리)에서 의존성 이름을 해결할 때 발생합니다. 이는 일반적으로 공격자가 제어하는 패키지가 설치되는 결과를 초래합니다.
- **잘못된**: **`reqests`**를 가져오는 대신 `requests`를 가져오기
- **존재하지 않는**: 더 이상 **존재하지 않는** 내부 라이브러리인 `company-logging` 가져오기
- **명시되지 않은 버전**: **내부**에 **존재하는** `company-requests` 라이브러리를 가져오지만, 저장소가 **더 높은 버전**이 있는지 **공개 저장소**를 확인합니다.
일반적인 근본 원인:
- 타이포스쿼팅/오타: `reqests` 대신 `requests`를 가져오기(공개 레지스트리에서 해결됨).
- 존재하지 않거나 방치된 내부 패키지: 더 이상 내부에 존재하지 않는 `company-logging`을 가져오기 때문에 해결자가 공개 레지스트리에서 공격자의 패키지를 찾습니다.
- 여러 레지스트리 간의 버전 선호: 내부 `company-requests`를 가져오면서 해결자가 공개 레지스트리도 쿼리할 수 있고 공격자가 공개적으로 게시한 "최고의"/더 최신 버전을 선호합니다.
핵심 아이디어: 해결자가 동일한 패키지 이름에 대해 여러 레지스트리를 볼 수 있고 "최고" 후보를 전 세계적으로 선택할 수 있다면, 해결을 제한하지 않는 한 취약합니다.
## Exploitation
> [!WARNING]
> 모든 경우에 공격자는 피해 회사에서 사용하는 라이브러리의 **이름**으로 **악성 패키지**를 게시하기만 하면 됩니다.
> 모든 경우에 공격자는 공개 레지스트리에서 빌드가 해결하는 의존성과 동일한 이름의 악성 패키지만 게시하면 됩니다. 설치 시 후크(예: npm 스크립트) 또는 가져오기 시 코드 경로는 종종 코드 실행을 제공합니다.
### Misspelled & Inexistent
귀사가 **내부 라이브러리가 아닌 라이브러리**를 **가져오려고** 한다면, 라이브러리 저장소가 **공개 저장소**에서 이를 검색할 가능성이 높습니다. 공격자가 이를 생성했다면, 귀하의 코드와 실행 중인 머신은 매우 높은 확률로 손상될 것입니다.
프로젝트가 개인 레지스트리에서 사용할 수 없는 라이브러리를 참조하고 도구가 공개 레지스트리로 되돌아가면, 공격자는 공개 레지스트리에 해당 이름의 악성 패키지를 심을 수 있습니다. 귀하의 러너/CI/개발 머신은 이를 가져와 실행합니다.
### Unspecified Version
### Unspecified Version / “Best-version” selection across indexes
개발자들이 사용된 라이브러리의 **버전을 명시하지 않거나** 단지 **주 버전**만 명시하는 경우가 매우 흔합니다. 그러면 인터프리터는 해당 요구 사항에 맞는 **최신 버전**을 다운로드하려고 시도합니다.\
라이브러리가 **잘 알려진 외부 라이브러리**(예: python `requests`)인 경우, **공격자는 많은 것을 할 수 없습니다**, 왜냐하면 그는 `requests`라는 이름의 라이브러리를 만들 수 없기 때문입니다(그가 원래 저자가 아닌 한).\
그러나 라이브러리가 **내부**에 있는 경우, 이 예제의 `requests-company`처럼, **라이브러리 저장소**가 **외부에서도 새로운 버전을 확인**할 수 있도록 허용한다면, 공개적으로 사용 가능한 더 최신 버전을 검색할 것입니다.\
따라서 **공격자가** 회사가 `requests-company` 라이브러리 **버전 1.0.1**(소규모 업데이트 허용)을 사용하고 있다는 것을 알게 되면, 그는 `requests-company` **버전 1.0.2**를 **게시**할 수 있으며, 회사는 내부 라이브러리 대신 **해당 라이브러리**를 **사용하게 됩니다**.
개발자는 종종 버전을 고정하지 않거나 넓은 범위를 허용합니다. 해결자가 내부 및 공개 인덱스 모두로 구성된 경우, 출처에 관계없이 최신 버전을 선택할 수 있습니다. 내부 이름인 `requests-company`의 경우, 내부 인덱스에 `1.0.1`이 있지만 공격자가 공개 레지스트리에 `1.0.2`를 게시하고 해결자가 둘 다 고려하면, 공개 패키지가 우선할 수 있습니다.
## AWS Fix
이 취약점은 AWS **CodeArtifact**에서 발견되었습니다(자세한 내용은 [**이 블로그 게시물**](https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d)에서 읽어보세요).\
AWS는 라이브러리가 내부인지 외부인지 명시할 수 있도록 하여 외부 저장소에서 내부 의존성을 다운로드하는 것을 방지했습니다.
이 취약점은 AWS CodeArtifact에서 발견되었습니다(자세한 내용은 이 블로그 게시물을 참조하십시오). AWS는 클라이언트가 상위 공개 레지스트리에서 "내부" 이름을 가져오지 않도록 의존성/피드를 내부와 외부로 표시하는 제어를 추가했습니다.
## Finding Vulnerable Libraries
[**dependency confusion에 대한 원본 게시물**](https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610)에서 저자는 자바스크립트 프로젝트의 의존성을 포함하는 수천 개의 노출된 package.json 파일을 검색했습니다.
의존성 혼란에 대한 원래 게시물에서 저자는 수천 개의 노출된 매니페스트(예: `package.json`, `requirements.txt`, lockfiles)를 찾아 내부 패키지 이름을 유추한 다음, 더 높은 버전의 패키지를 공개 레지스트리에 게시했습니다.
## References
## Practical Attacker Playbook (for red teams in authorized tests)
- Enumerate names:
- 매니페스트/락 파일 및 내부 네임스페이스에 대한 리포지토리 및 CI 구성 grep.
- 조직 특정 접두사 찾기(예: `@company/*`, `company-*`, 내부 groupIds, NuGet ID 패턴, Go의 개인 모듈 경로 등).
- 공개 레지스트리에서 가용성 확인:
- 이름이 공개적으로 등록되지 않은 경우, 등록하십시오; 존재하는 경우 내부 전이 이름을 대상으로 하여 하위 의존성 탈취를 시도하십시오.
- 우선 순위로 게시:
- "이기는" 세멘틱 버전 선택(예: 매우 높은 버전) 또는 해결자 규칙에 맞추기.
- 적용 가능한 경우 최소 설치 시간 실행 포함(예: npm `preinstall`/`install`/`postinstall` 스크립트). Python의 경우, 휠이 일반적으로 설치 시 임의 코드를 실행하지 않으므로 가져오기 시간 실행 경로를 선호합니다.
- Exfil control:
- CI에서 귀하의 제어된 엔드포인트로의 아웃바운드가 허용되는지 확인하십시오; 그렇지 않으면 DNS 쿼리 또는 오류 메시지를 사이드 채널로 사용하여 코드 실행을 증명하십시오.
> [!CAUTION]
> 항상 서면 승인을 받고, 참여를 위해 고유한 패키지 이름/버전을 사용하며, 테스트가 종료되면 즉시 게시 취소하거나 정리를 조정하십시오.
## Defender Playbook (what actually prevents confusion)
생태계 전반에 걸쳐 작동하는 고급 전략:
- 고유한 내부 네임스페이스를 사용하고 이를 단일 레지스트리에 바인딩합니다.
- 해결 시간에 신뢰 수준 혼합을 피하십시오. 승인된 공개 패키지를 프록시하는 단일 내부 레지스트리를 선호하고 패키지 관리자에게 내부 및 공개 엔드포인트를 모두 제공하지 마십시오.
- 지원하는 관리자에 대해 패키지를 특정 소스에 매핑하십시오(레지스트리 간의 전역 "최고 버전" 없음).
- Pin and lock:
- 해결된 레지스트리 URL을 기록하는 락파일(npm/yarn/pnpm)을 사용하거나 해시/증명 고정을 사용하십시오(pip `--require-hashes`, Gradle 의존성 검증).
- 레지스트리/네트워크 계층에서 내부 이름에 대한 공개 폴백을 차단하십시오.
- 가능할 경우 공개 레지스트리에서 내부 이름을 예약하여 향후 스쿼트를 방지하십시오.
## Ecosystem Notes and Secure Config Snippets
아래는 의존성 혼란을 줄이거나 제거하기 위한 실용적이고 최소한의 구성입니다. CI 및 개발 환경에서 이를 시행하는 것을 선호합니다.
### JavaScript/TypeScript (npm, Yarn, pnpm)
- 모든 내부 코드에 대해 범위가 지정된 패키지를 사용하고 범위를 개인 레지스트리에 고정합니다.
- CI에서 설치를 불변으로 유지합니다(npm lockfile, `yarn install --immutable`).
.npmrc (프로젝트 수준)
```
# Bind internal scope to private registry; do not allow public fallback for @company/*
@company:registry=https://registry.corp.example/npm/
# Always authenticate to the private registry
//registry.corp.example/npm/:_authToken=${NPM_TOKEN}
strict-ssl=true
```
package.json (내부 패키지용)
```
{
"name": "@company/api-client",
"version": "1.2.3",
"private": false,
"publishConfig": {
"registry": "https://registry.corp.example/npm/",
"access": "restricted"
}
}
```
Yarn Berry (.yarnrc.yml)
```
npmScopes:
company:
npmRegistryServer: "https://registry.corp.example/npm/"
npmAlwaysAuth: true
# CI should fail if lockfile would change
enableImmutableInstalls: true
```
운영 팁:
- `@company` 범위 내에서만 내부 패키지를 게시하세요.
- 타사 패키지의 경우, 클라이언트에서 직접가 아닌 개인 프록시/미러를 통해 공개 레지스트리를 허용하세요.
- 게시하는 공개 패키지에 대해 npm 패키지 출처를 활성화하여 추적 가능성을 높이는 것을 고려하세요 (혼란을 방지하지는 않습니다).
### Python (pip / Poetry)
핵심 규칙: 신뢰 수준을 혼합하기 위해 `--extra-index-url`을 사용하지 마세요. 다음 중 하나를 선택하세요:
- 승인된 PyPI 패키지를 프록시하고 캐시하는 단일 내부 인덱스를 노출하거나,
- 명시적 인덱스 선택 및 해시 고정을 사용하세요.
pip.conf
```
[global]
index-url = https://pypi.corp.example/simple
# Disallow source distributions when possible
only-binary = :all:
# Lock with hashes generated via pip-tools
require-hashes = true
```
pip-tools로 해시된 요구 사항 생성:
```
# From pyproject.toml or requirements.in
pip-compile --generate-hashes -o requirements.txt
pip install --require-hashes -r requirements.txt
```
공식 PyPI에 접근해야 하는 경우, 내부 프록시를 통해 접근하고 명시적인 허용 목록을 유지하십시오. CI에서 `--extra-index-url` 사용을 피하십시오.
### .NET (NuGet)
패키지 ID 패턴을 명시적인 소스에 연결하고 예상치 못한 피드에서의 해제를 방지하기 위해 패키지 소스 매핑을 사용하십시오.
nuget.config
```
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="corp" value="https://nuget.corp.example/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="corp">
<package pattern="Company.*" />
<package pattern="Internal.Utilities" />
</packageSource>
</packageSourceMapping>
</configuration>
```
### Java (Maven/Gradle)
Maven settings.xml (모든 것을 내부로 미러링; Enforcer를 통해 POM에서 임시 저장소를 허용하지 않음):
```
<settings>
<mirrors>
<mirror>
<id>internal-mirror</id>
<mirrorOf>*</mirrorOf>
<url>https://maven.corp.example/repository/group</url>
</mirror>
</mirrors>
</settings>
```
POM에 선언된 리포지토리를 금지하고 미러 사용을 강제하기 위해 Enforcer 추가:
```
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>enforce-no-repositories</id>
<goals><goal>enforce</goal></goals>
<configuration>
<rules>
<requireNoRepositories />
</rules>
</configuration>
</execution>
</executions>
</plugin>
```
Gradle: 종속성을 중앙 집중화하고 잠금.
- `settings.gradle(.kts)`에서만 리포지토리 강제 적용:
```
dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories {
maven { url = uri("https://maven.corp.example/repository/group") }
}
}
```
- 종속성 검증(체크섬/서명)을 활성화하고 `gradle/verification-metadata.xml`을 커밋합니다.
### Go Modules
공용 프록시와 체크섬 DB가 사용되지 않도록 개인 모듈을 구성합니다.
```
# Use corporate proxy first, then public proxy as fallback
export GOPROXY=https://goproxy.corp.example,https://proxy.golang.org
# Mark private paths to skip proxy and checksum db
export GOPRIVATE=*.corp.example.com,github.com/your-org/*
export GONOSUMDB=*.corp.example.com,github.com/your-org/*
```
### Rust (Cargo)
승인된 내부 미러 또는 공급업체 디렉토리로 crates.io를 교체하여 빌드를 수행하십시오; 임의의 공개 대체를 허용하지 마십시오.
.cargo/config.toml
```
[source.crates-io]
replace-with = "corp-mirror"
[source.corp-mirror]
registry = "https://crates-mirror.corp.example/index"
```
게시를 위해 `--registry`를 명시적으로 사용하고 자격 증명을 대상 레지스트리로 제한하십시오.
### Ruby (Bundler)
소스 블록을 사용하고 다중 소스 Gemfile을 비활성화하여 gem이 의도한 리포지토리에서만 오도록 하십시오.
Gemfile
```
source "https://gems.corp.example"
source "https://rubygems.org" do
gem "rails"
gem "pg"
end
source "https://gems.corp.example" do
gem "company-logging"
end
```
구성 수준에서 강제 적용:
```
bundle config set disable_multisource true
```
## CI/CD 및 레지스트리 제어
- 단일 진입점으로서의 개인 레지스트리:
- Artifactory/Nexus/CodeArtifact/GitHub Packages/Azure Artifacts를 개발자/CI가 접근할 수 있는 유일한 엔드포인트로 사용합니다.
- 내부 네임스페이스가 항상 업스트림 공개 소스에서 해결되지 않도록 차단/허용 규칙을 구현합니다.
- CI에서 잠금 파일은 불변입니다:
- npm: `package-lock.json`을 커밋하고, `npm ci`를 사용합니다.
- Yarn: `yarn.lock`을 커밋하고, `yarn install --immutable`을 사용합니다.
- Python: 해시된 `requirements.txt`를 커밋하고, `--require-hashes`를 강제합니다.
- Gradle: `verification-metadata.xml`을 커밋하고, 알 수 없는 아티팩트에서 실패합니다.
- 아웃바운드 이그레스 제어: 승인된 프록시를 통해서만 CI에서 공개 레지스트리에 직접 접근을 차단합니다.
- 이름 예약: 지원되는 공개 레지스트리에서 내부 이름/네임스페이스를 사전 등록합니다.
- 패키지 출처/증명: 공개 패키지를 게시할 때, 변조를 더 쉽게 감지할 수 있도록 출처/증명을 활성화합니다.
## 참고 문헌
- [https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610](https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610)
- [https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d](https://zego.engineering/dependency-confusion-in-aws-codeartifact-86b9ff68963d)
- [https://learn.microsoft.com/en-us/nuget/consume-packages/package-source-mapping](https://learn.microsoft.com/en-us/nuget/consume-packages/package-source-mapping)
- [https://yarnpkg.com/configuration/yarnrc/](https://yarnpkg.com/configuration/yarnrc/)
{{#include ../banners/hacktricks-training.md}}