mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['', 'src/pentesting-web/dependency-confusion.md'] to ko
This commit is contained in:
parent
c33fe60822
commit
6e08a82077
@ -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}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user