Assert table is not deleted in SQL injection tests.

This commit is contained in:
Sam Rose 2024-10-24 15:32:08 +01:00
parent e54bb3fbdc
commit 6e6e1368c1
No known key found for this signature in database
1 changed files with 54 additions and 23 deletions

View File

@ -7,6 +7,7 @@ import {
import { import {
context, context,
db as dbCore, db as dbCore,
docIds,
features, features,
MAX_VALID_DATE, MAX_VALID_DATE,
MIN_VALID_DATE, MIN_VALID_DATE,
@ -130,14 +131,14 @@ describe.each([
} }
}) })
async function createTable(schema: TableSchema) { async function createTable(schema?: TableSchema) {
const table = await config.api.table.save( const table = await config.api.table.save(
tableForDatasource(datasource, { schema }) tableForDatasource(datasource, { schema })
) )
return table._id! return table._id!
} }
async function createView(tableId: string, schema: ViewV2Schema) { async function createView(tableId: string, schema?: ViewV2Schema) {
const view = await config.api.viewV2.create({ const view = await config.api.viewV2.create({
tableId: tableId, tableId: tableId,
name: generator.guid(), name: generator.guid(),
@ -154,22 +155,34 @@ describe.each([
rows = await config.api.row.fetch(tableOrViewId) rows = await config.api.row.fetch(tableOrViewId)
} }
async function getTable(tableOrViewId: string): Promise<Table> {
if (docIds.isViewId(tableOrViewId)) {
const view = await config.api.viewV2.get(tableOrViewId)
return await config.api.table.get(view.tableId)
} else {
return await config.api.table.get(tableOrViewId)
}
}
describe.each([ describe.each([
["table", createTable], ["table", createTable],
[ [
"view", "view",
async (schema: TableSchema) => { async (schema?: TableSchema) => {
const tableId = await createTable(schema) const tableId = await createTable(schema)
const viewId = await createView( const viewId = await createView(
tableId, tableId,
Object.keys(schema).reduce<ViewV2Schema>((viewSchema, fieldName) => { Object.keys(schema || {}).reduce<ViewV2Schema>(
const field = schema[fieldName] (viewSchema, fieldName) => {
viewSchema[fieldName] = { const field = schema![fieldName]
visible: field.visible ?? true, viewSchema[fieldName] = {
readonly: false, visible: field.visible ?? true,
} readonly: false,
return viewSchema }
}, {}) return viewSchema
},
{}
)
) )
return viewId return viewId
}, },
@ -3476,36 +3489,45 @@ describe.each([
isSql && isSql &&
describe("SQL injection", () => { describe("SQL injection", () => {
const badStrings = [ const badStrings = [
"1; DROP TABLE test;", "1; DROP TABLE %table_name%;",
"1; DELETE FROM test;", "1; DELETE FROM %table_name%;",
"1; UPDATE test SET name = 'foo';", "1; UPDATE %table_name% SET name = 'foo';",
"1; INSERT INTO test (name) VALUES ('foo');", "1; INSERT INTO %table_name% (name) VALUES ('foo');",
"' OR '1'='1' --", "' OR '1'='1' --",
"'; DROP TABLE users; --", "'; DROP TABLE %table_name%; --",
"' OR 1=1 --", "' OR 1=1 --",
"' UNION SELECT null, null, null; --", "' UNION SELECT null, null, null; --",
"' AND (SELECT COUNT(*) FROM users) > 0 --", "' AND (SELECT COUNT(*) FROM %table_name%) > 0 --",
"\"; EXEC xp_cmdshell('dir'); --", "\"; EXEC xp_cmdshell('dir'); --",
"\"' OR 'a'='a", "\"' OR 'a'='a",
"OR 1=1;", "OR 1=1;",
"'; SHUTDOWN --", "'; SHUTDOWN --",
] ]
describe.each(badStrings)("bad string: %s", badString => { describe.each(badStrings)("bad string: %s", badStringTemplate => {
// The SQL that knex generates when you try to use a double quote in a // The SQL that knex generates when you try to use a double quote in a
// field name is always invalid and never works, so we skip it for these // field name is always invalid and never works, so we skip it for these
// tests. // tests.
const skipFieldNameCheck = isOracle && badString.includes('"') const skipFieldNameCheck = isOracle && badStringTemplate.includes('"')
!skipFieldNameCheck && !skipFieldNameCheck &&
it("should not allow SQL injection as a field name", async () => { it("should not allow SQL injection as a field name", async () => {
const tableOrViewId = await createTableOrView({ const tableOrViewId = await createTableOrView()
[badString]: { const table = await getTable(tableOrViewId)
name: badString, const badString = badStringTemplate.replace(
type: FieldType.STRING, /%table_name%/g,
table.name
)
await config.api.table.save({
...table,
schema: {
[badString]: { name: badString, type: FieldType.STRING },
}, },
}) })
expect(await client!.schema.hasTable(table.name)).toBeTrue()
await config.api.row.save(tableOrViewId, { [badString]: "foo" }) await config.api.row.save(tableOrViewId, { [badString]: "foo" })
const { rows } = await config.api.row.search( const { rows } = await config.api.row.search(
@ -3515,6 +3537,7 @@ describe.each([
) )
expect(rows).toHaveLength(1) expect(rows).toHaveLength(1)
expect(await client!.schema.hasTable(table.name)).toBeTrue()
}) })
it("should not allow SQL injection as a field value", async () => { it("should not allow SQL injection as a field value", async () => {
@ -3524,6 +3547,13 @@ describe.each([
type: FieldType.STRING, type: FieldType.STRING,
}, },
}) })
const table = await getTable(tableOrViewId)
const badString = badStringTemplate.replace(
/%table_name%/g,
table.name
)
expect(await client!.schema.hasTable(table.name)).toBeTrue()
await config.api.row.save(tableOrViewId, { foo: "foo" }) await config.api.row.save(tableOrViewId, { foo: "foo" })
@ -3534,6 +3564,7 @@ describe.each([
) )
expect(rows).toBeEmpty() expect(rows).toBeEmpty()
expect(await client!.schema.hasTable(table.name)).toBeTrue()
}) })
}) })
}) })