From 4ea53773b9a0921beb161dd089ceedd7d406dcd0 Mon Sep 17 00:00:00 2001 From: Vasco-jofra <11303847+Vasco-jofra@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:35:46 +0200 Subject: [PATCH 1/3] Model the TypeORM Repository API --- .../experimental/semmle/javascript/SQL.qll | 33 ++++++++++++++++++- .../ql/test/experimental/TypeOrm/test.ts | 7 +++- .../test/experimental/TypeOrm/tests.expected | 1 + 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/experimental/semmle/javascript/SQL.qll b/javascript/ql/src/experimental/semmle/javascript/SQL.qll index 3581106e2f85..5127881085d8 100644 --- a/javascript/ql/src/experimental/semmle/javascript/SQL.qll +++ b/javascript/ql/src/experimental/semmle/javascript/SQL.qll @@ -146,11 +146,42 @@ module ExperimentalSql { override DataFlow::Node getAQueryArgument() { result = this.getArgument(0) } } + /** + * A call to a TypeORM `Repository` (https://orkhan.gitbook.io/typeorm/docs/repository-api) + */ + private class RepositoryCall extends DatabaseAccess { + API::Node repository; + + RepositoryCall() { + ( + repository = API::moduleImport("typeorm").getMember("Repository").getInstance() or + repository = dataSource().getMember("getRepository").getReturn() + ) and + this = repository.getMember(_).asSource() + } + + override DataFlow::Node getAResult() { + result = + repository + .getMember([ + "find", "findBy", "findOne", "findOneBy", "findOneOrFail", "findOneByOrFail", + "findAndCount", "findAndCountBy" + ]) + .getReturn() + .asSource() + } + + override DataFlow::Node getAQueryArgument() { + result = repository.getMember("query").getParameter(0).asSink() + } + } + /** An expression that is passed to the `query` function and hence interpreted as SQL. */ class QueryString extends SQL::SqlString { QueryString() { this = any(QueryRunner qr).getAQueryArgument() or - this = any(QueryBuilderCall qb).getAQueryArgument() + this = any(QueryBuilderCall qb).getAQueryArgument() or + this = any(RepositoryCall rc).getAQueryArgument() } } } diff --git a/javascript/ql/test/experimental/TypeOrm/test.ts b/javascript/ql/test/experimental/TypeOrm/test.ts index 39e4dbb6ec35..3e0f6b3a717b 100644 --- a/javascript/ql/test/experimental/TypeOrm/test.ts +++ b/javascript/ql/test/experimental/TypeOrm/test.ts @@ -72,7 +72,7 @@ function makePaginationQuery(q: SelectQueryBuilder): SelectQueryBuilder AppDataSource.initialize().then(async () => { const BadInput = "A user controllable Remote Source like `' 1=1 --` " - + // Active record await UserActiveRecord.findByName(BadInput, "Saw") @@ -217,4 +217,9 @@ AppDataSource.initialize().then(async () => { qb.where(BadInput).orWhere(BadInput) // test: SQLInjectionPoint }), ).getMany() + + // Repository.query sink + await AppDataSource.getRepository(User2) + .query(BadInput) // test: SQLInjectionPoint + }).catch(error => console.log(error)) diff --git a/javascript/ql/test/experimental/TypeOrm/tests.expected b/javascript/ql/test/experimental/TypeOrm/tests.expected index a8f092c33e31..cbcf7785c787 100644 --- a/javascript/ql/test/experimental/TypeOrm/tests.expected +++ b/javascript/ql/test/experimental/TypeOrm/tests.expected @@ -29,4 +29,5 @@ passingPositiveTests | PASSED | SQLInjectionPoint | test.ts:210:28:210:53 | // test ... onPoint | | PASSED | SQLInjectionPoint | test.ts:213:56:213:81 | // test ... onPoint | | PASSED | SQLInjectionPoint | test.ts:217:56:217:81 | // test ... onPoint | +| PASSED | SQLInjectionPoint | test.ts:223:29:223:54 | // test ... onPoint | failingPositiveTests From ddf77a0b728ee712dc592178006fe03c5e4590dc Mon Sep 17 00:00:00 2001 From: Vasco-jofra <11303847+Vasco-jofra@users.noreply.github.com> Date: Fri, 13 Jun 2025 15:37:27 +0200 Subject: [PATCH 2/3] Remove unnecessary spaces --- javascript/ql/test/experimental/TypeOrm/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/test/experimental/TypeOrm/test.ts b/javascript/ql/test/experimental/TypeOrm/test.ts index 3e0f6b3a717b..3f6cd54d22d0 100644 --- a/javascript/ql/test/experimental/TypeOrm/test.ts +++ b/javascript/ql/test/experimental/TypeOrm/test.ts @@ -72,7 +72,7 @@ function makePaginationQuery(q: SelectQueryBuilder): SelectQueryBuilder AppDataSource.initialize().then(async () => { const BadInput = "A user controllable Remote Source like `' 1=1 --` " - + // Active record await UserActiveRecord.findByName(BadInput, "Saw") From 8a7516528d76917012cd8322592bf1f14830a8a6 Mon Sep 17 00:00:00 2001 From: Vasco-jofra <11303847+Vasco-jofra@users.noreply.github.com> Date: Thu, 26 Jun 2025 09:29:07 +0200 Subject: [PATCH 3/3] Update formatting --- javascript/ql/src/experimental/semmle/javascript/SQL.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/experimental/semmle/javascript/SQL.qll b/javascript/ql/src/experimental/semmle/javascript/SQL.qll index 5127881085d8..f91172ccc14d 100644 --- a/javascript/ql/src/experimental/semmle/javascript/SQL.qll +++ b/javascript/ql/src/experimental/semmle/javascript/SQL.qll @@ -154,8 +154,8 @@ module ExperimentalSql { RepositoryCall() { ( - repository = API::moduleImport("typeorm").getMember("Repository").getInstance() or - repository = dataSource().getMember("getRepository").getReturn() + repository = API::moduleImport("typeorm").getMember("Repository").getInstance() or + repository = dataSource().getMember("getRepository").getReturn() ) and this = repository.getMember(_).asSource() }