作者:綠盟科技伏影實驗室
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org

環境搭建

使用atlas-debug調試

  1. 下載安裝Atlassian SDK地址
  2. atlas-create-jira-plugin創建一個插件,參考
  3. atlas-debug開啟調試,http端口2990,調試端口5005;
  4. IDEA打開MyPlugin,把WEB-INF/classesWEB-INF/lib加入library;
  5. 新建Remote調試;

其他:

  1. 如果沒有設置%JAVA_HOME%可以通過SET JAVA_HOME=d:\jdk1.8設置;
  2. 默認不開啟電子郵件發送,通過atlas-debug --jvmargs -Datlassian.mail.senddisabled=false開啟;

第一部分:注入代碼并生成郵件

post的數據通過JiraSafeActionParameterSetter->setActionProperty()方法

通過反射調用到ContactAdministrators.setSubject()方法,把ContactAdministrators對象的subject屬性設置為傳入的subject

隨后通過ContactAdministrators.doExecute()調用send()方法,在這個方法中會查找系統中已激活的管理員,通過this.sendTo(administrator)將郵件發送給該管理員

sendTo()流程中,Jira需要通過EmailBuilder()方法創建一個郵件隊列對象,隨后將該對象放入郵件發送隊列中。由于隊列等待原因,所以觸發payload可能需要等待一段時間,并且當郵件發送失敗時系統會繼續嘗試發送郵件,所以payload可能會觸發多次。

創建隊列的方法有點長,精簡一下就是這個樣子

MailQueueItem item = (new EmailBuilder()).withSubject(this.subject).withBodyFromFile().addParameters().renderLater();

通過EmailBuilderwithSubject()方法,創建一個TemplateSources$fragment對象,參數即是我們傳入的payload,隨后調用renderLater()方法創建出EmailBuilder對象,再將該對象作為參數傳遞給RenderingMailQueueItem類,RenderingMailQueueItem的繼承關系是如下圖,于是最終創建出一個MailQueueItem對象,并將該對象放入郵件發送隊列。

第二部分:發送郵件

當我們把payload注入到模板中之后,郵件進入待發送隊列,Jira中處理郵件隊列的具體流程如下:

通過模板引擎(getTemplatingEngine)生成一個Velocity模板,通過applying()方法生成RenderRequest對象,之后根據該對象成員變量source的類型,調用不同的方法解析模板,漏洞的產生正是由于這個差異造成的,下面詳細分析一下。

首先進入RenderingMailQueueItem().send()方法,調用this.emailRenderer.render(),隨后調用

this.getTemplatingEngine().render(this.subjectTemplate).applying(contextParams).asPlainText();

這個過程中前面是為了獲取模板解析引擎(VelocityTemplatingEngine)并傳入主題模板(此處為payload數據),通過applying()方法創建VelocityContext對象并把payload賦值給成員變量source

隨后重寫了抽象類StringRepresentationwith()方法,在with()方法中調用了asPlainText()方法

DefaultRenderRequest.this.asPlainText(sw)

asPlainText()的作用是通過Velocity模板引擎解析模板,其中的調用鏈是

toWriterImpl()->writeEncodedBodyForContent()->evaluate()

而在evaluate()方法中生成了AST結構,隨后通過反射調用傳入的payload,完成代碼執行。

asPlainText()之后的調用棧如下

在處理完Object模板后會調用父類SingleMailQueueItem的send()方法,通過smtpMailServer.sendWithMessageId()發送郵件,由于沒有正確配置SMTP服務會拋出異常,但在連接SMTP服務之前漏洞已經觸發了,控制臺也能看到MailQueue執行的過程。

思考

上述漏洞流程走完了,但還有一個關鍵問題沒有解決:為什么郵件主題Subject會被解析成AST結構并被執行呢?按照正常發送反饋的邏輯,一封郵件的主題(字符串)似乎沒有必要解析成AST,導致差異的原因是什么?

發送一封正常的“聯系管理員”郵件,走一遍流程

對比一下兩個處理流程,當發送正常反饋時,writeEncodedBody()中調用的是this.getVe().mergeTemplate,通過Velocity引擎的ClasspathResourceLoader()類的getResourceStream()方法加載模板文件,此處的模板是templates/email/html/contactadministrator.vm,隨后還會進行headerfooter等正常加載流程,最終渲染出整個頁面。而發送payload時,通過asPlainText()創建出TemplateSource$Fragment對象,再通過DefaultRenderRequest構造方法把source成員變量賦值為這個Fragment對象,于是進入第一個分支,調用的是this.getVe().evaluate(),最終調用ASTMethod.execute(),這正是前面說的差異性導致的兩個不同處理邏輯。

回過頭看一下Velocity渲染的大致流程:

Velocity渲染引擎首先磁盤加載模板文件到內存,然后解析模板模板文件為AST結構,并對AST中每個節點進行初始化,第二次加載同一個模板文件時候如果開啟了緩存則直接返回模板資源,通過使用資源緩存節省了從磁盤加載并重新解析為AST的開銷。

ASTMethod.execute()方法設計之初是在Velocity parse解析模板的過程中,通過反射調用相關方法完成正常模板渲染動作,例如獲取背景顏色、獲取text內容、獲取頁面編碼等,但當此處攻擊者傳入精心構造的數據后,利用反射執行了java.lang.Runtime.getRuntime,成功達到命令執行的目的,漏洞利用十分精巧。

參考


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/982/