fix(sql-collection): block dangerous keywords (#5913)

This commit is contained in:
YANG QIA 2024-12-19 13:16:06 +08:00 committed by GitHub
parent 3eb4907fcc
commit d3de8c4ffd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 85 additions and 4 deletions

View File

@ -42,6 +42,14 @@ describe('sql collection', () => {
});
expect(res.status).toBe(400);
expect(res.body.errors[0].message).toMatch('Only supports SELECT statements or WITH clauses');
res = await agent.resource('sqlCollection').execute({
values: {
sql: "select pg_read_file('/etc/passwd');",
},
});
expect(res.status).toBe(400);
expect(res.body.errors[0].message).toMatch('SQL statements contain dangerous keywords');
});
it('sqlCollection:execute', async () => {
@ -244,4 +252,16 @@ describe('sql collection', () => {
const loadedFields2 = db.getCollection('sqlCollection').fields;
expect(loadedFields2.size).toBe(1);
});
it('should check sql when creating', async () => {
const res = await agent.resource('collections').create({
values: {
name: 'sqlCollection',
sql: "select pg_read_file('/etc/passwd');",
template: 'sql',
},
});
expect(res.status).toBe(400);
expect(res.body.errors[0].message).toMatch('SQL statements contain dangerous keywords');
});
});

View File

@ -11,6 +11,7 @@ import { Plugin } from '@nocobase/server';
import { Collection } from '@nocobase/database';
import { SQLCollection } from './sql-collection';
import sqlResourcer from './resources/sql';
import { checkSQL } from './utils';
export class PluginCollectionSQLServer extends Plugin {
async beforeLoad() {
@ -34,6 +35,21 @@ export class PluginCollectionSQLServer extends Plugin {
name: `pm.data-source-manager.collection-sql `,
actions: ['sqlCollection:*'],
});
this.app.resourceManager.use(async (ctx, next) => {
const { resourceName, actionName } = ctx.action;
if (resourceName === 'collections' && actionName === 'create') {
const { sql } = ctx.action.params.values || {};
if (sql) {
try {
checkSQL(sql);
} catch (e) {
ctx.throw(400, ctx.t(e.message));
}
}
}
return next();
});
}
}

View File

@ -9,6 +9,7 @@
import { Context, Next } from '@nocobase/actions';
import { SQLCollection, SQLModel } from '../sql-collection';
import { checkSQL } from '../utils';
const updateCollection = async (ctx: Context, transaction: any) => {
const { filterByTk, values } = ctx.action.params;
@ -48,13 +49,14 @@ export default {
name: 'sqlCollection',
actions: {
execute: async (ctx: Context, next: Next) => {
let { sql } = ctx.action.params.values || {};
const { sql } = ctx.action.params.values || {};
if (!sql) {
ctx.throw(400, ctx.t('Please enter a SQL statement'));
}
sql = sql.trim().split(';').shift();
if (!/^select/i.test(sql) && !/^with([\s\S]+)select([\s\S]+)/i.test(sql)) {
ctx.throw(400, ctx.t('Only supports SELECT statements or WITH clauses'));
try {
checkSQL(sql);
} catch (e) {
ctx.throw(400, ctx.t(e.message));
}
const tmpCollection = new SQLCollection({ name: 'tmp', sql }, { database: ctx.db });
const model = tmpCollection.model as typeof SQLModel;

View File

@ -0,0 +1,43 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
export const checkSQL = (sql: string) => {
const dangerKeywords = [
// PostgreSQL
'pg_read_file',
'pg_read_binary_file',
'pg_stat_file',
'pg_ls_dir',
'pg_logdir_ls',
'pg_terminate_backend',
'pg_cancel_backend',
'current_setting',
'set_config',
'pg_reload_conf',
'pg_sleep',
'generate_series',
// MySQL
'LOAD_FILE',
'BENCHMARK',
'@@global.',
'@@session.',
// SQLite
'sqlite3_load_extension',
'load_extension',
];
sql = sql.trim().split(';').shift();
if (!/^select/i.test(sql) && !/^with([\s\S]+)select([\s\S]+)/i.test(sql)) {
throw new Error('Only supports SELECT statements or WITH clauses');
}
if (dangerKeywords.some((keyword) => sql.toLowerCase().includes(keyword.toLowerCase()))) {
throw new Error('SQL statements contain dangerous keywords');
}
};