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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy