mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
fix(sql-collection): block dangerous keywords (#5913)
This commit is contained in:
parent
3eb4907fcc
commit
d3de8c4ffd
@ -42,6 +42,14 @@ describe('sql collection', () => {
|
|||||||
});
|
});
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
expect(res.body.errors[0].message).toMatch('Only supports SELECT statements or WITH clauses');
|
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 () => {
|
it('sqlCollection:execute', async () => {
|
||||||
@ -244,4 +252,16 @@ describe('sql collection', () => {
|
|||||||
const loadedFields2 = db.getCollection('sqlCollection').fields;
|
const loadedFields2 = db.getCollection('sqlCollection').fields;
|
||||||
expect(loadedFields2.size).toBe(1);
|
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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,7 @@ import { Plugin } from '@nocobase/server';
|
|||||||
import { Collection } from '@nocobase/database';
|
import { Collection } from '@nocobase/database';
|
||||||
import { SQLCollection } from './sql-collection';
|
import { SQLCollection } from './sql-collection';
|
||||||
import sqlResourcer from './resources/sql';
|
import sqlResourcer from './resources/sql';
|
||||||
|
import { checkSQL } from './utils';
|
||||||
|
|
||||||
export class PluginCollectionSQLServer extends Plugin {
|
export class PluginCollectionSQLServer extends Plugin {
|
||||||
async beforeLoad() {
|
async beforeLoad() {
|
||||||
@ -34,6 +35,21 @@ export class PluginCollectionSQLServer extends Plugin {
|
|||||||
name: `pm.data-source-manager.collection-sql `,
|
name: `pm.data-source-manager.collection-sql `,
|
||||||
actions: ['sqlCollection:*'],
|
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();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
import { Context, Next } from '@nocobase/actions';
|
import { Context, Next } from '@nocobase/actions';
|
||||||
import { SQLCollection, SQLModel } from '../sql-collection';
|
import { SQLCollection, SQLModel } from '../sql-collection';
|
||||||
|
import { checkSQL } from '../utils';
|
||||||
|
|
||||||
const updateCollection = async (ctx: Context, transaction: any) => {
|
const updateCollection = async (ctx: Context, transaction: any) => {
|
||||||
const { filterByTk, values } = ctx.action.params;
|
const { filterByTk, values } = ctx.action.params;
|
||||||
@ -48,13 +49,14 @@ export default {
|
|||||||
name: 'sqlCollection',
|
name: 'sqlCollection',
|
||||||
actions: {
|
actions: {
|
||||||
execute: async (ctx: Context, next: Next) => {
|
execute: async (ctx: Context, next: Next) => {
|
||||||
let { sql } = ctx.action.params.values || {};
|
const { sql } = ctx.action.params.values || {};
|
||||||
if (!sql) {
|
if (!sql) {
|
||||||
ctx.throw(400, ctx.t('Please enter a SQL statement'));
|
ctx.throw(400, ctx.t('Please enter a SQL statement'));
|
||||||
}
|
}
|
||||||
sql = sql.trim().split(';').shift();
|
try {
|
||||||
if (!/^select/i.test(sql) && !/^with([\s\S]+)select([\s\S]+)/i.test(sql)) {
|
checkSQL(sql);
|
||||||
ctx.throw(400, ctx.t('Only supports SELECT statements or WITH clauses'));
|
} catch (e) {
|
||||||
|
ctx.throw(400, ctx.t(e.message));
|
||||||
}
|
}
|
||||||
const tmpCollection = new SQLCollection({ name: 'tmp', sql }, { database: ctx.db });
|
const tmpCollection = new SQLCollection({ name: 'tmp', sql }, { database: ctx.db });
|
||||||
const model = tmpCollection.model as typeof SQLModel;
|
const model = tmpCollection.model as typeof SQLModel;
|
||||||
|
@ -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');
|
||||||
|
}
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user