小型框架大多是某些大型框架的缩影,或者由好几个框架取其长处糅合成一个新的框架,但是其核心功能的实现原理,不会相差太多

在不想触碰大型框架但是又想对框架进行一定的学习,那么就可以找一些比较轻量级的框架源码进行阅览学习 ;)

阅览官方文档

官方开发文档

虽然有很多细节没有呈现,但是也大致的能够了解到各个功能组件的功能说明和加载、调用方式

由于是学习框架的,所以跳过了一些设计原理和优化过程,想的是在请求到达应用程序的时候,会经过哪些流程处理,最终才会给予一定的返回

然后翻到了架构图,如下:

(虽然自己跟代码也能跟出来这个图,但是有图不就更快的理解运作流程了么)

由图中所示,请求过的第一个就是 JFinalFilter ,继续往下就是各种 Handler 的处理流程,最后一个才是 ActionHandler,在整个过程中,都可以选择调用 Plugin
进入 ActionHandler 后就是调用各种 Interceptor ,然后才交给 Controller 处理真正的业务,最终返回 Render

那么,我们需要去查看的几个点:Filter、Handler、Interceptor、Plugin
学习他们的加载、使用方式,并且看看能不能找出漏洞点
(不看 Controller 是因为已经到达控制层,代码是万变的....最多也仅仅是看一下 Controller 里的一些函数,但是其核心功能实现一般不会在这里呈现)

Filter

很简洁,做了一下uri里的字符切割,然后调用了 handler 的 handle 函数,这里的 handler 是 handler 栈中的栈顶,栈底就是 ActionHandler,是在 JFianlConfig 中预先设置的,也可以继承 JFinalConfig 进行 API 引导式配置

跟进 handle 函数,跟到了抽象函数处,然后查看有哪些地方对其进行了实现

Handler

如何自定义加载 handler,跟踪的过程中发现,还是从 config 里获取的信息,也就是需要自己手动添加,使用如下图的 api

me.add 就OK,然后会在 JFinal 中的 init 过程中,去获取已经在 config 处理流程中手动加载的 handler

在对各个 Handler 的查看中,发现 DruidStatViewHandler 有意思的地方,可以读取一些 classpath 里的东西,后缀名可以控制输出格式
(在 returnResourceFile 函数里)

这样的话,结合执着上传,可能会有存储型的 xss 什么的,行任意js代码

可是在后续的调试中发现,classpath 太深了.....在依赖包中,并且虽然文件名可控,但是是直接在 url 的路径中,而不是以参数的形式传递,所以也没法跨目录.....杯具

那我们继续看最后一个 ActionHandler,如下

这里是根据相关的 config 去提前做关键字映射的,在访问的时候从映射中直接提取出来,能不能访问到任意函数,还需要待会儿深入跟踪,先不管

获取到 action 后,会进行 controller 的实例化,然后再使用 Invocation 类自带的 invoke 函数去请求相关的函数

注意到,这里面全都是请求的同样的构造函数,但是在 Invocation 中有两类构造函数

上面那一类,就是直接 url 请求过去时候所调用的,下面的是 callback 所调用

大致看了一下,callback 是 enhancer 、Duang、controller 都会使用的,可能是在控制类函数处理过程中进行一些调用其他 controller 的一些操作实现,暂且不管

在实际对 controller 中的函数进行请求的时候,如下过程

第一个框:intercept 是其预加载的拦截器实现的函数,对访问请求进行拦截处理,实施得很巧妙,将自身的 Invocation 传递过去,然后拦截器处理完后,继续调用 inv.invoke ,继而继续进行 index 和 拦截器的数量进行比较,如果还有拦截器未处理,那么继续调用拦截器

在拦截器全部处理完成后,就开始具体调用 controller 中的各种函数了

第二个框就是普通的 action 加载,也就是对 controller 其中的函数进行调用,注意这里的 target 就是具体函数,args 是无参数的形态

第三个框:这个 useInjectTarget 是在 callback 中确定的,但是如果 action 为空的话,那么进入的应该是 else 分支中,调用 invokeSuper 函数

在 Invocation.invoke 调用全部完成后,又回到了 ActionHandler 中,继续向下看

从已经在 controller 实例化的 render,然后做一下内部转发啥的,如果没有实例化的话,那么就拿 一个 默认的 render 作为返回内容。此时 ActionHandler 已经全部跑完

回到 JFinalFilter 中,又进行了下一个 Filter 的调用

Interceptor

如何自定义加载 Interceptor,如下

和 Handler 一样,也可以使用 API 加载
或者是使用 annotation 也可以,如下

那么我们可以直接根据继承关系找出各种 Interceptor

其他的 interceptor 没什么特别的,最后一个 L18nInterceptor ,它将 cookie 中的 _locale 列中的值,作为了 将要访问的控制器中 request 里的一个 attribute 进行存储,可能在后续的过程中将会对这个 attribute 进行数据的获取,对应的 attribute 名为 _res ,但是怎么使用,也得看开发者的心情了,一般来说是对应着国际化输出啥的...

Plugin

Plugin 就不细说了,因为除了 RedisPlugin 和一个模板解析的 Plugin,其他都是数据库驱动有关的,模板解析的没细看,倒是 Redis 吸引了注意

作为第三方key-value存储系统,在 java 中存储相关信息的时候,很有可能对其 value 进行一定格式的序列化,那么有没有可能搞事情,后文详细分析

Contorller?路由?

整个输入流已经全部跟踪完毕,输出流没去关心,但是现在有一个问题,就是如何才能让请求正常的访问到 Controller ,也就是路由这一块功能的实现

controller 的加载:

首先需要自己配置 JFinalConfig 里的 configRoute ,去加载路由
如下图这样

注意 controllerKey ,这个和第二个参数 class 做了一个 map 映射,在后续的过程中也是根据 controllerKey 来提取相关 controller 类的

在 JFinal 初始化全部完成之前,会在 ActionMapping 中调用 buildActionMapping 函数去获取 configRoute 中的 Routes 里携带的路由信息,然后根据具体的 controller 类去反射获取类中的函数,满足一定规则的情况下,就将其放入到 mapping 里,作为此后请求中所含有的路由信息来加载具体的 controller 和执行相关的函数
(这里的规则是指,首先不能是 Controller 类中的函数,其次函数不能带有参数,最后满足不是 Controller 类的子类或者是 public 属性的函数二者中其一即可)

在做 action mapping 的时候,如果相应的 controller 类中的函数有 ActionKey 的 Annotation 时,就会直接使用注释中的字符串作为 actionKey ,如果没有设置 ActionKey 的注释的话,那么就有两种情况,一是如果函数名为 index,那么 actionKey 的取值由 controllerKey 决定,二是先判断 controllerKey 是否为 / ,如果是的话,取值是 / 和 函数名 的拼接,如果不是的话,取值是 controllerKey 和 / 和 函数名 的拼接,然后才是将相关的 controllerKey、actionKey、controllerClass、method、methodName、actionInterceptor、viewPath 等去实例化一个 Action,然后将 actionKey 和 action 加入到 map 集合中

最终,这个 actionKey 用来当做在后续的请求中,作为一个映射的 key 值,可以通过 url 中的路径(target 变量)获取到相关的 action

那么终上所述,我们是无法通过路由造成任意函数调用的

反序列化

到目前为止,路由也已经搞定了,输入流经过的各个功能模块已经摸清,那么我们回到之前提出的有关 RedisPlugin 的疑问,是否有反序列化的存在

简单粗暴的使用了关键字搜索,readObject

下面的 FstSerializer.java 有兴趣的朋友可以自行研究一下,我就图个方便,直接查看 JdkSerializer

确实未做任何防护就进行反序列化了,那么从函数名来看,肯定还有个序列化的函数

就在同类中找到了,那么现在我们看看它的调用点:

明摆着的,跟进 Cache,如下

继续找 valueToBytes调用点

呃.......调用点过多了,那就尽量找一些流程简单的调用点,比如:

哈,还有函数说明,这里具体的 jedis.set 是怎么实现的我们不用关心,只需要知道 valueToBytes 返回的是二进制流就OK

既然有 set ,那也得有 get

跟进 valueFromBytes 发现,也正好调用了 JdkSerializer 的 valueFromBytes

测试

  • 配置
    在 configPlugin api 中如下图配置:

  • 触发
    在整个框架中,如下调用:

控制台打印:

官方也给出了相应 Cache 与 Redis 联合使用的方法:

以上就是学习、分析过程,但是很多点我都没有深入跟进,仅仅是懂得了其相应功能的作用、实现方式等,并没有深入挖掘,有兴趣的朋友可自行深入分析

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖