chennbnbnb opened a new issue, #12215:
URL: https://github.com/apache/inlong/issues/12215
### What happened
INLONG-11731 / PR #11732 hardened `MySQLSensitiveUrlUtils` (case-insensitive
matching and the `yeſ` long-s variant) for the **MySQL** sink entry. However,
the **OceanBase** sink entry never passes the JDBC URL through any
sensitive-parameter filter at all, and the URL is forwarded verbatim to the
Flink JDBC connector. A tenant user who creates/updates an OceanBase sink can
therefore transport `autoDeserialize=true` (and other dangerous Connector/J
parameters) end-to-end and trigger Java deserialization in the Sort runtime.
Root cause chain (all verified against released artifacts):
1. `inlong-manager/manager-pojo/.../sink/oceanbase/OceanBaseSinkDTO.java`
`getFromRequest(...)` never calls `filterSensitive(...)` — although its
`@apiNote` explicitly says "filter sensitive params before saving". The
raw
attacker-controlled URL is stored in the sink config.
2. `inlong-sort/sort-common/.../node/load/OceanBaseLoadNode.java`
`tableOptions()` does `options.put("url", url)` verbatim (line ~89).
3. `jdbc-inlong` connector (Flink 1.15/1.18) `JdbcDynamicTableFactory`
passes the URL to Connector/J without any runtime scrubbing
(`JdbcUrlUtils.replaceInvalidUrlProperty` exists only in the 1.13 module,
and even that only replaces the exact string `autoDeserialize=true`;
`autoDeserialize=yes` survives it).
4. With `autoDeserialize=true`, Connector/J auto-deserializes Java-serialized
BLOB values on `ResultSet.getObject()`. If the Sort classpath contains a
usable gadget chain, this is RCE in the Flink TaskManager.
The Manager-side MySQL entry (`MySQLSinkDTO.getFromRequest`) does call
`filterSensitive`, so the OceanBase entry is the gap left by PR #11732 —
`OceanBaseJdbcUtils.establishDatabaseConnection` (Manager-side connectivity
check) filters, but that is not the path that feeds Sort.
### What you expected to happen
OceanBase sink creation should apply the same sensitive-parameter filtering
as
the MySQL sink does after INLONG-11731, i.e.
```
OceanBaseSinkDTO.getFromRequest(request, extParams)
```
should neutralize `autoDeserialize` / `allowLoadLocalInfile` /
`allowUrlInLocalInfile`
/ `allowLoadLocalInfileInPath` (and ideally reject `queryInterceptors` /
`statementInterceptors`) before the config is saved — so that what reaches
`OceanBaseLoadNode.tableOptions()` and eventually Connector/J is incapable of
enabling auto-deserialization.
Current actual behaviour (verified): URL containing `autoDeserialize=true`
submitted via the OceanBase sink request passes unchanged through
`OceanBaseSinkDTO.getFromRequest` -> `OceanBaseLoadNode.tableOptions()` ->
Flink `jdbc-inlong` connector -> Connector/J, and deserialization is executed
during `ResultSet.getObject()`.
### How to reproduce
Minimal end-to-end reproduction using released InLong 2.2.0 artifacts and a
real MySQL server (Docker):
1. Start MySQL:
```
docker run -d --name ob-poc -e MYSQL_ROOT_PASSWORD=root123 \
-e MYSQL_DATABASE=testdb -p 3307:3306 mysql:5.7
```
2. Prepare the "attacker-controlled" table: write one row whose BLOB column
contains a Java-serialized object of a class whose `readObject()` has a
visible side effect (e.g., creates a marker file). Any real-world exploit
would use a public gadget chain (e.g., CommonsCollections) instead of the
marker class.
3. Run the chain with the official released classes (manager-pojo 2.2.0,
sort-common 2.2.0, mysql-connector-j 8.0.33):
```java
// 1) Manager entry: exactly what OceanBaseSinkDTO.getFromRequest does with
// an OceanBaseSinkRequest carrying the attacker URL.
OceanBaseSinkDTO dto = OceanBaseSinkDTO.getFromRequest(request, null);
System.out.println("after getFromRequest url = " + dto.getJdbcUrl());
// -> jdbc:mysql://127.0.0.1:3307/testdb?autoDeserialize=true (NOT
filtered)
// 2) Sort layer: what OceanBaseLoadNode emits for the connector
Map<String, String> opts = new OceanBaseLoadNode(dto.getJdbcUrl(),
...).tableOptions();
System.out.println("tableOptions url = " + opts.get("url"));
// -> jdbc:mysql://127.0.0.1:3307/testdb?autoDeserialize=true (verbatim)
// 3) Runtime: what the jdbc-inlong connector ultimately does
Connection conn = DriverManager.getConnection(opts.get("url"), "root",
"root123");
ResultSet rs = conn.createStatement().executeQuery("SELECT blob_col FROM
loot").get(1), ...
Object o = rs.getObject(1); // triggers deserialization
// -> marker file PWNED.txt created by the serialized object's readObject()
```
Negative control: the identical query with `autoDeserialize` absent returns
`byte[]` instead and creates no marker file — the behaviour difference is
caused solely by this one parameter.
Also reproducible: with the 1.13 `JdbcUrlUtils.replaceInvalidUrlProperty`,
`jdbc:mysql://...?autoDeserialize=yes` is NOT neutralized (only the exact
string `autoDeserialize=true` is replaced), and Connector/J parses
`autoDeserialize=yes` as true.
### Environment
- OS: Linux x86_64
- JDK: OpenJDK 1.8.0_452
- MySQL Server: 5.7 (Docker `mysql:5.7`)
- Connector/J: com.mysql:mysql-connector-j 8.0.33 (also verified with
mysql:mysql-connector-java 8.0.28)
- Apache Spark/Flink runtime: not required – the invocation was exercised
with the released InLong jars directly
### InLong version
master
### InLong Component
InLong Manager, InLong Sort
### Are you willing to submit PR?
- [ ] Yes, I am willing to submit a PR!
### Code of Conduct
- [x] I agree to follow this project's [Code of
Conduct](https://www.apache.org/foundation/policies/conduct)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]