Skip to content

Commit d43373d

Browse files
authored
Merge pull request mybatis#3117 from harawata/3115-secure-invocation
Prevent `Invocation` from invoking arbitrary method
2 parents 0ec8fcb + 319da58 commit d43373d

File tree

5 files changed

+191
-13
lines changed

5 files changed

+191
-13
lines changed

src/main/java/org/apache/ibatis/plugin/Invocation.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2022 the original author or authors.
2+
* Copyright 2009-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,17 +17,29 @@
1717

1818
import java.lang.reflect.InvocationTargetException;
1919
import java.lang.reflect.Method;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
23+
import org.apache.ibatis.executor.Executor;
24+
import org.apache.ibatis.executor.parameter.ParameterHandler;
25+
import org.apache.ibatis.executor.resultset.ResultSetHandler;
26+
import org.apache.ibatis.executor.statement.StatementHandler;
2027

2128
/**
2229
* @author Clinton Begin
2330
*/
2431
public class Invocation {
2532

33+
private static final List<Class<?>> targetClasses = Arrays.asList(Executor.class, ParameterHandler.class,
34+
ResultSetHandler.class, StatementHandler.class);
2635
private final Object target;
2736
private final Method method;
2837
private final Object[] args;
2938

3039
public Invocation(Object target, Method method, Object[] args) {
40+
if (!targetClasses.contains(method.getDeclaringClass())) {
41+
throw new IllegalArgumentException("Method '" + method + "' is not supported as a plugin target.");
42+
}
3143
this.target = target;
3244
this.method = method;
3345
this.args = args;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2009-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.apache.ibatis.plugin;
18+
19+
import org.apache.ibatis.annotations.Select;
20+
21+
public interface Mapper {
22+
23+
@Select("select name from users where id = #{id}")
24+
String selectNameById(Integer id);
25+
26+
}
Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,27 +16,87 @@
1616
package org.apache.ibatis.plugin;
1717

1818
import static org.junit.jupiter.api.Assertions.assertEquals;
19-
import static org.junit.jupiter.api.Assertions.assertNotEquals;
19+
import static org.junit.jupiter.api.Assertions.fail;
2020

21+
import java.io.Reader;
22+
import java.sql.Connection;
2123
import java.util.HashMap;
2224
import java.util.Map;
2325

26+
import org.apache.ibatis.BaseDataTest;
27+
import org.apache.ibatis.executor.statement.StatementHandler;
28+
import org.apache.ibatis.io.Resources;
29+
import org.apache.ibatis.session.SqlSession;
30+
import org.apache.ibatis.session.SqlSessionFactory;
31+
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
32+
import org.junit.jupiter.api.BeforeAll;
2433
import org.junit.jupiter.api.Test;
2534

2635
class PluginTest {
2736

37+
private static SqlSessionFactory sqlSessionFactory;
38+
39+
@BeforeAll
40+
static void setUp() throws Exception {
41+
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/plugin/mybatis-config.xml")) {
42+
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
43+
}
44+
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
45+
"org/apache/ibatis/plugin/CreateDB.sql");
46+
}
47+
2848
@Test
29-
void mapPluginShouldInterceptGet() {
30-
Map map = new HashMap();
31-
map = (Map) new AlwaysMapPlugin().plugin(map);
32-
assertEquals("Always", map.get("Anything"));
49+
void shouldPluginSwitchSchema() {
50+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
51+
Mapper mapper = sqlSession.getMapper(Mapper.class);
52+
assertEquals("Public user 1", mapper.selectNameById(1));
53+
}
54+
55+
SchemaHolder.set("MYSCHEMA");
56+
57+
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
58+
Mapper mapper = sqlSession.getMapper(Mapper.class);
59+
assertEquals("Private user 1", mapper.selectNameById(1));
60+
}
61+
}
62+
63+
static class SchemaHolder {
64+
private static ThreadLocal<String> value = ThreadLocal.withInitial(() -> "PUBLIC");
65+
66+
public static void set(String tenantName) {
67+
value.set(tenantName);
68+
}
69+
70+
public static String get() {
71+
return value.get();
72+
}
73+
}
74+
75+
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }))
76+
public static class SwitchCatalogInterceptor implements Interceptor {
77+
@Override
78+
public Object intercept(Invocation invocation) throws Throwable {
79+
Object[] args = invocation.getArgs();
80+
Connection con = (Connection) args[0];
81+
con.setSchema(SchemaHolder.get());
82+
return invocation.proceed();
83+
}
3384
}
3485

3586
@Test
36-
void shouldNotInterceptToString() {
37-
Map map = new HashMap();
38-
map = (Map) new AlwaysMapPlugin().plugin(map);
39-
assertNotEquals("Always", map.toString());
87+
void shouldPluginNotInvokeArbitraryMethod() {
88+
Map<?, ?> map = new HashMap<>();
89+
map = (Map<?, ?>) new AlwaysMapPlugin().plugin(map);
90+
try {
91+
map.get("Anything");
92+
fail("Exected IllegalArgumentException, but no exception was thrown.");
93+
} catch (IllegalArgumentException e) {
94+
assertEquals(
95+
"Method 'public abstract java.lang.Object java.util.Map.get(java.lang.Object)' is not supported as a plugin target.",
96+
e.getMessage());
97+
} catch (Exception e) {
98+
fail("Exected IllegalArgumentException, but was " + e.getClass(), e);
99+
}
40100
}
41101

42102
@Intercepts({ @Signature(type = Map.class, method = "get", args = { Object.class }) })
@@ -45,7 +105,5 @@ public static class AlwaysMapPlugin implements Interceptor {
45105
public Object intercept(Invocation invocation) {
46106
return "Always";
47107
}
48-
49108
}
50-
51109
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--
2+
-- Copyright 2009-2024 the original author or authors.
3+
--
4+
-- Licensed under the Apache License, Version 2.0 (the "License");
5+
-- you may not use this file except in compliance with the License.
6+
-- You may obtain a copy of the License at
7+
--
8+
-- https://www.apache.org/licenses/LICENSE-2.0
9+
--
10+
-- Unless required by applicable law or agreed to in writing, software
11+
-- distributed under the License is distributed on an "AS IS" BASIS,
12+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
-- See the License for the specific language governing permissions and
14+
-- limitations under the License.
15+
--
16+
17+
drop schema public if exists; -- empty public remains
18+
19+
create table public.users (
20+
id int,
21+
name varchar(20)
22+
);
23+
24+
insert into public.users (id, name) values (1, 'Public user 1');
25+
26+
drop schema myschema if exists;
27+
create schema myschema;
28+
29+
create table myschema.users (
30+
id int,
31+
name varchar(20)
32+
);
33+
34+
insert into myschema.users (id, name) values (1, 'Private user 1');
35+
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<!--
3+
4+
Copyright 2009-2024 the original author or authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
https://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
-->
19+
<!DOCTYPE configuration
20+
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
21+
"https://mybatis.org/dtd/mybatis-3-config.dtd">
22+
23+
<configuration>
24+
25+
<plugins>
26+
<plugin
27+
interceptor="org.apache.ibatis.plugin.PluginTest$SwitchCatalogInterceptor" />
28+
</plugins>
29+
30+
<environments default="development">
31+
<environment id="development">
32+
<transactionManager type="JDBC">
33+
<property name="" value="" />
34+
</transactionManager>
35+
<dataSource type="UNPOOLED">
36+
<property name="driver" value="org.hsqldb.jdbcDriver" />
37+
<property name="url" value="jdbc:hsqldb:mem:plugintest" />
38+
<property name="username" value="sa" />
39+
</dataSource>
40+
</environment>
41+
</environments>
42+
43+
<mappers>
44+
<mapper class="org.apache.ibatis.plugin.Mapper" />
45+
</mappers>
46+
47+
</configuration>

0 commit comments

Comments
 (0)