Stripes MVC框架的对象自动绑定分析
Stripes MVC框架介绍
之前在对某产品的审计过程中发现其使用了一套小众的java mvc框架,联想到今年spring4shell的精彩利用,于是有了这篇文章。今天的主角是Stripes MVC 框架,其诞生的比较早,模仿Struts 的MVC风格,虽然到目前已经停止维护了,但是仍然展示着他的小巧精炼。既然是Struts风格,也就同样具有变量自动绑定的功能,也就是我们所重点关注的功能 – 变量的自动绑定。
基础概念
首先介绍一下整体框架的两个基本概念,ActionBean、Handle。熟悉Struts开发风格的同学应该能大体知道是什么意思。ActionBean类是所有用户自定义Controller的基类,通过实现ActionBean,用户可以创建Controller。通过在ActionBean的实现类中定义Resolution类方法,创建Handler。该handler用于真正处理用户请求。示例代码如下
package corp.qihoo.net.action;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
public class HelloActionBean implements ActionBean {
private static final String VIEW = "/WEB-INF/jsp/Hello.jsp";
private ActionBeanContext context;
private String firstStr = "";
public HelloActionBean() {
}
public String getFirstStr() {
return this.firstStr;
}
public void setFirstStr(String firstStr) {
this.firstStr = firstStr;
}
public void setContext(ActionBeanContext context) {
this.context = context;
}
public ActionBeanContext getContext() {
return this.context;
}
@DefaultHandler
public Resolution hello() {
this.firstStr = "Hello World";
return new ForwardResolution("/WEB-INF/jsp/Hello.jsp");
}
}
如上所示,用户创建了HelloActionBean类,并且创建了默认Handler,由此,用户可通过访问 Hello.action 实现默认handler的调用,即返回VIEW常量所定义的view视图。
变量的自动绑定分析
在了解了整个框架的基本使用之后,我们聚焦于变量的自动绑定。首先我们假设访问 Hello.action ,并且提交如下参数 corp.qihoo.net.HelloActionBean.PropertyA=ValueA
框架的整体解析过程如下 net.sourceforge.stripes.controller.DispatcherServlet#service
Resolution resolution = this.requestInit(ctx);
if (resolution == null) {
resolution = this.resolveActionBean(ctx);
if (resolution == null) {
resolution = this.resolveHandler(ctx);
if (resolution == null) {
resolution = this.doBindingAndValidation(ctx);
if (resolution == null) {
resolution = this.doCustomValidation(ctx);
if (resolution == null) {
resolution = this.handleValidationErrors(ctx);
if (resolution == null) {
resolution = this.invokeEventHandler(ctx);
}
}
}
}
}
}
resolveActionBean
: 根据传递的url,解析ActionBean,resolveHandler
: 根据上面解析的ActionBean, 解析后面要用到的HandledoBindingAndValidation
:这是我们要真正审计的地方,此处实现了参数自动绑定,具体实现为net.sourceforge.stripes.controller.DefaultActionBeanPropertyBinder#bind(net.sourceforge.stripes.action.ActionBean, net.sourceforge.stripes.action.ActionBeanContext, boolean)
该函数处理流程如下
当传参 corp.qihoo.net.HelloActionBean.PropertyA= ValueA
时,首先遍历传递的参数,依次进行解析,解析过程如下
try {
eval = new PropertyExpressionEvaluation(PropertyExpression.getExpression(pname), bean);
} catch (Exception var20) {
if (pname.equals(context.getEventName())) {
continue;
}
throw var20;
}
首先根据corp.qihoo.net.HelloActionBean.PropertyA
获取PropertyExpressionEvaluation
。
其中net.sourceforge.stripes.util.bean.PropertyExpression#getExpression
如下
public static PropertyExpression getExpression(String expression) throws ParseException {
PropertyExpression parsed = (PropertyExpression)expressions.get(expression);
if (parsed == null) {
parsed = new PropertyExpression(expression);
expressions.put(expression, parsed);
}
return parsed;
}
在new PropertyExpression()
时,将对String expression
进行解析,具体如下
private PropertyExpression(String expression) throws ParseException {
this.source = expression;
this.parse(expression);
}
protected void parse(String expression) throws ParseException {
...
for(int i = 0; i < chars.length; ++i) {
char ch = chars[i];
if (escapedChar) {
builder.append(ch);
escapedChar = false;
} else if (ch == '\\') {
escapedChar = true;
} else if (!inSingleQuotedString && ch == '\'') {
inSingleQuotedString = true;
} else {
String value;
if (inSingleQuotedString && ch == '\'') {
inSingleQuotedString = false;
...
}
value = builder.toString();
this.addNode(value, value.length() == 1 ? value.charAt(0) : value, inSquareBrackets);
builder.setLength(0);
} else if (inSingleQuotedString) {
builder.append(ch);
} else if (!inDoubleQuotedString && ch == '"') {
inDoubleQuotedString = true;
} else if (inDoubleQuotedString && ch == '"') {
inDoubleQuotedString = false;
...
}
value = builder.toString();
this.addNode(value, value, inSquareBrackets);
builder.setLength(0);
} else if (inDoubleQuotedString) {
builder.append(ch);
} else if (!inSquareBrackets && ch == '[') {
if (builder.length() > 0) {
this.addNode(builder.toString(), (Object)null, inSquareBrackets);
builder.setLength(0);
}
inSquareBrackets = true;
} else if (inSquareBrackets) {
if (ch == ']') {
if (builder.length() > 0) {
this.addNode(builder.toString(), (Object)null, inSquareBrackets);
builder.setLength(0);
}
inSquareBrackets = false;
} else {
builder.append(ch);
}
} else if (ch == '.') {
if (builder.length() >= 1) {
this.addNode(builder.toString(), (Object)null, inSquareBrackets);
builder = new StringBuilder();
}
} else {
builder.append(ch);
}
}
if (i == chars.length - 1) {
...
if (builder.length() > 0) {
this.addNode(builder.toString(), (Object)null, inSquareBrackets);
}
}
}
}
进而得到一个类似链表的存在,链表中记录了每一个的具体类名和数值类型。之后便是根据链表将参数绑定到内存对象中。
具体的绑定方式如下
在net.sourceforge.stripes.controller.DefaultActionBeanPropertyBinder#bindNonNullValue
使用 net.sourceforge.stripes.util.bean.PropertyExpressionEvaluation#setValue
进行绑定, 最后通过net.sourceforge.stripes.util.bean.JavaBeanPropertyAccessor#setValue
落地实现参数绑定。
public void setValue(NodeEvaluation evaluation, Object bean, Object value) {
String property = evaluation.getNode().getStringValue();
PropertyDescriptor pd = ReflectUtil.getPropertyDescriptor(bean.getClass(), property);
...
if (pd != null) {
Method m = pd.getWriteMethod();
...
m = ReflectUtil.findAccessibleMethod(m);
m.invoke(bean, value);
} else {
Field field = ReflectUtil.getField(bean.getClass(), property);
...
field.set(bean, value);
}
...
}
}
如上所示,可见通过net.sourceforge.stripes.util.ReflectUtil#getPropertyDescriptor
获取 PropertyDescriptor
,该函数的具体实现如下
public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) {
if (propertyDescriptors.containsKey(clazz)) {
Collection<PropertyDescriptor> pds = ((Map)propertyDescriptors.get(clazz)).values();
return (PropertyDescriptor[])pds.toArray(new PropertyDescriptor[pds.size()]);
} else {
try {
PropertyDescriptor[] pds = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
pds = (PropertyDescriptor[])Arrays.asList(pds).toArray(new PropertyDescriptor[pds.length]);
...
...
显然,在该函数中,使用了Introspector.getBeanInfo
进行内省,同时未指定stopclass
,出现了和springmvc相同的问题,由此可以使用 class.module.classLoader.resources.context.parent.pipeline.first
这一串参数获取到accesslog
对象
进而设置该内存对象的属性,利用日志功能进行getshell。
StripesMVC框架是存在相同问题的,但是经过对最新版的分析,自 stripes 1.5 8
及之后,在net.sourceforge.stripes.controller.DefaultActionBeanPropertyBinder#bind(net.sourceforge.stripes.action.ActionBean, net.sourceforge.stripes.action.ActionBeanContext, boolean)
添加了net.sourceforge.stripes.controller.DefaultActionBeanPropertyBinder#isBindingAllowed
判断
protected boolean isBindingAllowed(PropertyExpressionEvaluation eval) {
boolean allowed = BindingPolicyManager.getInstance(eval.getBean().getClass()).isBindingAllowed(eval);
if (!allowed) {
String param = eval.getExpression().getSource();
log.warn(new Object[]{"Binding denied for parameter [", param, "]"});
}
return allowed;
}
在该判断中,通过黑名单的方式过滤之前提到过的链表中的类名,黑名单如下
private static final List<Class<?>> ILLEGAL_NODE_VALUE_TYPES = Arrays.asList(ActionBeanContext.class, Class.class, ClassLoader.class, HttpSession.class, ServletRequest.class, ServletResponse.class);
如果链表中出现了ActionBeanContext.class
, Class.class
, ClassLoader.class
, HttpSession.class
, ServletRequest.class
, ServletResponse.class
这些类将直接中断解析,从而利用失败,由此关于变量自动绑定部分的分析就到此结束了。
虽然整个过程走下来最终并没有得到想要的效果,但是也希望能为其他同学在审计时提供参考。