
HackTheBox "Nexus" 권한 상승에 대한 PoC 및 해설: 위조된 Git 객체를 통한 경로 탐색에 취약한 root 권한으로 실행되는 Gitea 템플릿 동기화 서비스. 교육 목적으로만 사용하세요.
위조된 Git 객체를 통한 Gitea template-sync 경로 순회 → root
HackTheBox 머신 Nexus를 위한 개념 증명(proof-of-concept) 권한 상승 익스플로잇.
root 소유의 systemd 서비스가 Gitea template 저장소를 디스크로 동기화할 때
엔트리 이름을 정제하지 않아, 인증된 저권한 사용자가 디렉터리 순회를 통해
root 권한으로 임의 파일을 쓸 수 있습니다 — 여기서는 /root/.ssh/authorized_keys에
SSH 키를 심는 데 사용됩니다.
이 코드는 교육 목적 및 승인된 보안 테스트 전용으로 제공됩니다 — 즉, HackTheBox와 같은 랩 환경, 또는 본인이 소유하거나 명시적인 서면 허가를 받은 시스템에 한합니다. 통제권이 없는 시스템에 대한 무단 사용은 불법입니다. 저자는 오용에 대해 어떠한 책임도 지지 않습니다.
Nexus 머신이 HackTheBox에서 아직 활성 상태라면 이 익스플로잇을 게시하거나 공유하지 마십시오 — HTB 규칙은 활성 머신에 대한 해법 배포를 금지합니다.
| 분류 | 경로 순회 (CWE-22) → 로컬 권한 상승 |
| 구성 요소 | gitea-template-sync.service (커스텀 /etc/gitea/template-sync.py) |
| 실행 권한 | root, gitea-template-sync.timer에 의해 약 60초마다 트리거됨 |
| 근본 원인 | Git tree 객체에서 가져온 정제되지 않은 이름에 대한 os.path.join() |
| 영향 | 인증된 저권한 사용자 → root |
동기화 스크립트는 Gitea에서 template으로 표시된 저장소의 파일을 읽어 로컬 파일시스템에 씁니다. 대상 경로는 대략 다음과 같이 구성됩니다:
dest = os.path.join(base_dir, name) # name comes straight from the Git tree
open(dest, "w").write(content)
os.path.join()은 .. 시퀀스를 중화하지 않습니다. 따라서 이름이 순회
경로(예: ../../../../../root/.ssh/authorized_keys)인 tree 엔트리는 권한 있는
프로세스가 의도된 디렉터리 밖에 쓰도록 만듭니다.
Git은 내용과 이름을 별도로 저장합니다:
name → object를 매핑합니다 (파일명이 여기에 있습니다);tree 엔트리의 파일명은 그저 텍스트이므로 ..일 수 있습니다 — 하지만 표준
Git 클라이언트는 바로 이 공격을 방지하기 위해 그러한 엔트리 생성을 거부합니다.
이 PoC는 원시 Git 객체를 .git/objects/에 직접 기록하고(SHA-1 id를 수동으로
계산) refs/heads/main을 수동으로 조작하여 클라이언트 측 검사를 우회합니다.
그러면 푸시된 저장소에 ..라는 이름의 tree 엔트리가 포함되고, root 동기화
프로세스가 이를 해석하여 따라갑니다.
www-data로 RCE..env의 평문 자격 증명이 시스템 비밀번호로 재사용됨
→ 저권한 사용자로 SSH.root.세 단계 모두에서 반복되는 주제: 검증 없이 공격자가 제어하는 데이터를 신뢰하는 권한 있는 프로세스 (위조된 MIME 타입, 재사용된 비밀번호, 정제되지 않은 파일명).
http://localhost:3000에서 접근 가능.git, python3, curl, ssh 사용 가능 (박스 기본 제공).chmod +x nexus_privesc.sh
./nexus_privesc.sh -t <TARGET_IP> -u <GITEA_USER> -p <GITEA_PASS> [-d DEPTH]
예시:
./nexus_privesc.sh -t 10.129.21.192 -u jones -p 'REDACTED' -d 5
/tmp/.exploit_key)./root/.ssh/authorized_keys에 매핑되는 .. 순회 tree를 인코딩한
원시 Git 객체를 위조하며, 내용은 공격자의 공개 키입니다.root로 로그인합니다.대상에서 동기화를 모니터링하려면:
tail -f /var/log/template-sync.log
성공적인 실행은 다음과 같은 줄을 기록합니다:
synced: ../../../../../root/.ssh/authorized_keys
DEPTH 조정DEPTH는 동기화/스테이징 디렉터리에서 파일시스템 루트(/)까지 올라가는 데
필요한 .. 레벨 수입니다. 로그에서 동기화는 성공했지만 root SSH가 실패하면
순회가 잘못된 레벨에 도달한 것입니다 — -d 4 또는 -d 6으로 재시도하십시오.
정확한 값은 /etc/gitea/template-sync.py의 스테이징 경로를 읽어 확인할 수 있습니다.
신뢰할 수 없는 이름으로 os.path.join()만 사용하여 파일시스템 경로를
구성하지 마십시오. 경로를 정규화하고 제한하십시오, 예:
base = os.path.realpath(base_dir)
dest = os.path.realpath(os.path.join(base, name))
if not dest.startswith(base + os.sep):
raise ValueError("path traversal blocked")
.., 절대 경로, 또는 경로 구분자를 포함하는 tree 엔트리 이름을 거부하십시오.
동기화 서비스를 root가 아닌 전용 저권한 계정으로 실행하십시오.
Gitea에서 receive.fsckObjects = true를 활성화하여 잘못된 형식의 푸시된
객체를 거부하십시오.
<your name / handle> — HackTheBox에서의 교육적 사용을 위함.
| 플래그 | 설명 | 기본값 |
|---|
-t | 대상 IP (최종 root SSH용) | — (필수) |
-u | Gitea 사용자 이름 | — (필수) |
-p | Gitea 비밀번호 | — (필수) |
-d | 동기화 디렉터리에서 /에 도달하기 위한 .. 레벨 수 | 5 |