
CVE-2020-13277 실습 환경: GitLab 논리적 취약점 - 임의 사용자의 비공개 저장소 무단 접근
CVE-2020-13277 랩: Gitlab 논리적 취약점 - 임의 사용자가 권한을 초과하여 개인 저장소에 접근
CVE-2020-13277
├── README.md ............... [이 README 설명]
├── imgs .................... [README 설명을 돕는 이미지]
├── gitlab .................. [Gitlab 컨테이너의 마운트 디렉터리]
│ ├── Dockerfile .......... [Gitlab의 Docker 빌드 파일]
│ ├── config .............. [Gitlab 설정 마운트 디렉터리]
│ ├── data ................ [Gitlab 데이터 마운트 디렉터리]
│ ├── logs ................ [Gitlab 로그 마운트 디렉터리]
│ ├── keys ................ [Gitlab 크랙 License 저장 디렉터리]
│ └── runner .............. [Runner 컨테이너의 마운트 디렉터리]
├── license ................. [크랙 License의 컨테이너 빌드 디렉터리]
│ ├── Dockerfile .......... [License의 Docker 빌드 파일]
│ └── license.rb .......... [크랙 License를 생성하는 Ruby 스크립트]
├── docker-compose.yml ...... [Docker 빌드 설정]
├── keygen.ps1 .............. [Windows: 원클릭으로 크랙 License 생성]
├── keygen.sh ............... [Linux: 원클릭으로 크랙 License 생성]
├── run.ps1 ................. [Windows: 원클릭으로 Gitlab 랩 실행]
├── run.sh .................. [Linux: 원클릭으로 Gitlab 랩 실행]
├── register.ps1 ............ [Windows: 원클릭으로 Runner 등록]
├── register.sh ............. [Linux: 원클릭으로 Runner 등록]
├── stop.ps1 ................ [Windows: 원클릭으로 Gitlab 랩 중지]
└── stop.sh ................. [Linux: 원클릭으로 Gitlab 랩 중지]
이 취약점의 핵심은 주로 Mirror Repository - 저장소의 미러 동기화 백업 기능을 이용하는 것입니다.
여기서 Mirror의 동기화 방향은 두 가지로 나뉩니다:
이 취약점은 Pull 방향의 Mirror Repository를 이용합니다.
아시다시피 Gitlab은 CE(커뮤니티 무료 버전)와 EE(엔터프라이즈 유료 버전) 두 가지 버전으로 나뉘며, Gitlab 공식도 이 취약점이 CE와 EE의 다음 버전에 동시에 영향을 미친다고 밝혔습니다:
>=10.6, <12.9.10>=12.10, <12.10.11>=13.0, <13.0.6하지만 이 버전들의 Gitlab Docker Image를 모두 랩 구축에 사용할 수 있다는 의미는 아닙니다. 그 이유는:
바꿔 말하면 Docker로 랩을 구축하려면 Gitlab-EE 버전을 선택하여 크랙하고(부자라면 License를 구매하는 방법도 선택할 수 있습니다) Mirror Repository - Pull 기능을 활성화해야 합니다.
하지만 Gitlab-EE를 크랙하더라도 10.x, 12.x, 13.x 모두 Mirror Repository - Pull의 URL에 로컬 경로가 포함되면 Import url is blocked: Requests to localhost are not allowed 오류가 발생합니다.
Admin area => Settings => Network => Outbound requests에서 Allow requests to the local network from hooks and services를 설정하면 로컬 URL을 구성할 수 있지만, 미러를 동기화할 때 2:Fetching remote upstream failed: fatal: unable to access http://127.0.0.1/xxxx/: The requested URL returned error: 301 오류가 발생합니다. 즉 Pull Remote Repository만 사용할 수 있습니다.
다행히도 12.x와 13.x는 로컬 URL 판별 방법이 매우 엄격하지만, 10.x 버전에는 대응 방법이 있습니다. Pull URL을 구성할 때 로컬에 설정된 DNS 서비스 이름만 구성하면 우회할 수 있습니다.
이상을 종합하면 결국 gitlab-ee:10.6.0-ee.0 버전의 Docker Image로만 이 랩을 구축할 수 있습니다.
사실 앞선 설명을 통해서도 알 수 있듯이 이 취약점의 악용 조건은 비교적 까다로워서, 기본적으로 돈 없는 사용자는 이 취약점의 영향을 받기 어렵습니다
./keygen.sh 또는 ./keygen.ps1./run.sh 또는 ./run.ps1앞서 크랙 키페어를 생성할 때 이미 공개 키를 Gitlab 컨테이너 백엔드에 기록해 두었습니다. 이제 개인 키를 프런트엔드를 통해 Gitlab에 업로드하여 크랙을 완료해야 합니다:
./gitlab/keys/ 디렉터리에 생성됩니다. 그 안의 .gitlab-license 내용(개인 키)을 복사합니다Enter license key를 선택하고 개인 키를 붙여넣은 다음 Upload license 버튼을 클릭하면 크랙이 완료됩니다이로써 Mirror Repository - Pull 기능이 활성화되었습니다

Outbound requests를 찾아 Allow requests to the local network from hooks and services를 체크하고 저장합니다이로써 Mirror Repository - Pull이 로컬 Repository를 가져올 수 있게 되었습니다

./register.sh $TOKEN 또는 ./register.ps1 $TOKEN이로써 모든 Repository에서 이 Runner를 사용하여 CI 스크립트(Pipeline Jobs)를 실행할 수 있습니다

검증 과정은 공식 Issue를 참고할 수 있습니다. 하지만 아래의 검증 과정은 이 랩에 맞추어 일부 단계를 미세 조정합니다
root 사용자로 http://127.0.0.1/admin/users 페이지를 열어 3개의 계정을 생성합니다:
victim: 피해자 계정attacker1: 공격자 계정1attacker2: 공격자 계정2계정 생성 시 초기 비밀번호를 설정할 수 없으며, Gitlab은 기본적으로 설정된 Email로 초기 비밀번호를 보냅니다. 편의상 Email은 아무거나 입력해도 되며, 먼저 계정을 생성한 후 즉시 해당 계정을 편집하면 root로 초기 비밀번호를 설정할 수 있으므로 Email을 거칠 필요가 없습니다

victim 계정으로 Gitlab에 로그인New Project 생성:
targetPrivateREADME.md 파일을 생성하고 내용을 임의로 mykey is abcxyz로 설정합니다명백히
target은victim의 개인 저장소이며, 우리의 목표는 취약점을 이용해 이 저장소의 내용을 획득하는 것입니다

attacker1 계정으로 Gitlab에 로그인New Project 생성:
pocPublic.gitlab-ci.yml 파일을 생성합니다. 내용은 다음과 같습니다:image: "ruby:2.6"
rspec:
script:
- git clone http://gitlab-ci-token:[email protected]/victim/target.git
- cd target
- ls -lah .
- cat README.md
【목적】이어지는 작업에서는 몇 가지 수법을 통해 victim이 인지하지 못하는 상황에서 자신의 권한으로 이 CI 스크립트를 실행하도록 합니다.
현재 랩에는 인증서가 설정되어 있지 않으므로
http프로토콜만 사용할 수 있습니다. 또한172.168.30.2는docker-compose.yml이 Gitlab 컨테이너에 할당한 IP입니다. 이 CI 스크립트는 결국 Runner를 통해 실행되며, 현재 랩의 Runner와 Gitlab은 같은 컨테이너가 아니므로 Docker가 할당한 IP 주소를 사용해야 합니다
