CVE-2022-24815: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: JHipster is a development platform to quickly generate, develop, & deploy modern web applications & microservice architectures. SQL Injection vulnerability in entities for applications generated with the option "reactive with Spring WebFlux" enabled and an SQL database using r2dbc. Applications created without "reactive with Spring WebFlux" and applications with NoSQL databases are not affected. Users who have generated a microservice Gateway using the affected version may be impacted as Gateways are reactive by default. Currently, SQL injection is possible in the findAllBy(Pageable pageable, Criteria criteria) method of an entity repository class generated in these applications as the where clause using Criteria for queries are not sanitized and user input is passed on as it is by the criteria. This issue has been patched in v7.8.1. Users unable to upgrade should be careful when combining criterias and conditions as the root of the issue lies in the `EntityManager.java` class when creating the where clause via `Conditions.just(criteria.toString())`. `just` accepts the literal string provided. Criteria's `toString` method returns a plain string and this combination is vulnerable to sql injection as the string is not sanitized and will contain whatever used passed as input using any plain SQL.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 8.1
Published: 2022-04-11T20:15Z
Repository: https://github.com/jhipster/generator-jhipster
Language: Other
Files changed: generators/entity-server/templates/src/main/java/package/repository/EntityRepositoryInternalImpl_reactive.java.ejs, generators/entity-server/templates/src/main/java/package/repository/EntityRepository_reactive.java.ejs, generators/server/templates/src/main/java/package/repository/EntityManager.java.ejs
Commit date: 2022-04-05 22:47:52 +0200
Commit message: use conditions instead of criteria as workaround

closes #18269

Diff: diff --git a/generators/entity-server/templates/src/main/java/package/repository/EntityRepositoryInternalImpl_reactive.java.ejs b/generators/entity-server/templates/src/main/java/package/repository/EntityRepositoryInternalImpl_reactive.java.ejs
index 96072b50ff..bca607a29f 100644
--- a/generators/entity-server/templates/src/main/java/package/repository/EntityRepositoryInternalImpl_reactive.java.ejs
+++ b/generators/entity-server/templates/src/main/java/package/repository/EntityRepositoryInternalImpl_reactive.java.ejs
@@ -48,10 +48,13 @@ import org.springframework.data.r2dbc.convert.R2dbcConverter;
 import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
 import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
 import org.springframework.data.r2dbc.repository.support.SimpleR2dbcRepository;
 import org.springframework.data.relational.core.query.Criteria;
 import org.springframework.data.relational.core.sql.Column;
+import org.springframework.data.relational.core.sql.Comparison;
+import org.springframework.data.relational.core.sql.Condition;
+import org.springframework.data.relational.core.sql.Conditions;
 import org.springframework.data.relational.core.sql.Expression;
 import org.springframework.data.relational.core.sql.Select;
 import org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndJoin<% if (reactiveEagerRelations.length > 0) { %>Condition<% } %>;
 import org.springframework.data.relational.core.sql.Table;
 import org.springframework.data.relational.repository.support.MappingRelationalEntityInformation;
@@ -119,40 +122,36 @@ _%>
 <%_ }); _%>
     }
 
     @Override
     public Flux<<%= persistClass %>> findAllBy(Pageable pageable) {
-        return findAllBy(pageable, null);
+        return createQuery(pageable, null).all();
     }
 
-    @Override
-    public Flux<<%= persistClass %>> findAllBy(Pageable pageable, Criteria criteria) {
-        return createQuery(pageable, criteria).all();
-    }
-
-    RowsFetchSpec<<%= persistClass %>> createQuery(Pageable pageable, Criteria criteria) {
+    RowsFetchSpec<<%= persistClass %>> createQuery(Pageable pageable, Condition whereClause) {
         List<Expression> columns = <%= entityClass %>SqlHelper.getColumns(entityTable, EntityManager.ENTITY_ALIAS);
 <%_ reactiveEagerRelations.forEach(function(rel) { _%>
         columns.addAll(<%= rel.otherEntityNameCapitalized %>SqlHelper.getColumns(<%= rel.relationshipName %>Table, "<%= rel.relationshipName %>"));
 <%_ }); _%>
         SelectFromAndJoin<% if (reactiveEagerRelations.length > 0) { %>Condition<% } %> selectFrom = Select.builder().select(columns).from(entityTable)
 <%_ reactiveEagerRelations.forEach(function(rel) { _%>
             .leftOuterJoin(<%= rel.relationshipName %>Table).on(Column.create("<%= rel.joinColumnNames[0] %>", entityTable)).equals(Column.create("<%= rel.otherEntity.primaryKey.fields[0].columnName %>", <%= rel.relationshipName %>Table ))
 <%_ }); _%>;
-
-        String select = entityManager.createSelect(selectFrom, <%= persistClass %>.class, pageable, criteria);
+        // we do not support Criteria here for now as of https://github.com/jhipster/generator-jhipster/issues/18269
+        String select = entityManager.createSelect(selectFrom, <%= persistClass %>.class, pageable, whereClause);
         return db.sql(select).map(this::process);
     }
 
     @Override
     public Flux<<%= persistClass %>> findAll() {
-        return findAllBy(null, null);
+        return findAllBy(null);
     }
 
     @Override
     public Mono<<%= persistClass %>> findById(<%= primaryKey.type %> id) {
-        return createQuery(null, where(EntityManager.ENTITY_ALIAS + ".<%= primaryKey.fields[0].columnName %>").is(id)).one();
+        Comparison whereClause = Conditions.isEqual(entityTable.column("<%= primaryKey.fields[0].columnName %>"), Conditions.just(id.toString()));
+        return createQuery(null, whereClause).one();
     }
 
 <%_ if (implementsEagerLoadApis) { _%>
 
     @Override
diff --git a/generators/entity-server/templates/src/main/java/package/repository/EntityRepository_reactive.java.ejs b/generators/entity-server/templates/src/main/java/package/repository/EntityRepository_reactive.java.ejs
index 08c64d4f62..c8405f983d 100644
--- a/generators/entity-server/templates/src/main/java/package/repository/EntityRepository_reactive.java.ejs
+++ b/generators/entity-server/templates/src/main/java/package/repository/EntityRepository_reactive.java.ejs
@@ -142,12 +142,12 @@ interface <%= entityClass %>RepositoryInternal {
     Flux<<%= persistClass %>> findAllBy(Pageable pageable);
 
     Flux<<%= persistClass %>> findAll();
 
     Mono<<%= persistClass %>> findById(<%= primaryKey.type %> id);
-
-    Flux<<%= persistClass %>> findAllBy(Pageable pageable, Criteria criteria);
+    // this is not supported at the moment because of https://github.com/jhipster/generator-jhipster/issues/18269
+    // Flux<<%= persistClass %>> findAllBy(Pageable pageable, Criteria criteria);
 
   <%_ if (implementsEagerLoadApis) { _%>
 
     Mono<<%= persistClass %>> findOneWithEagerRelationships(<%= primaryKey.type %> id);
 
diff --git a/generators/server/templates/src/main/java/package/repository/EntityManager.java.ejs b/generators/server/templates/src/main/java/package/repository/EntityManager.java.ejs
index 24286eccda..697e0d73e6 100644
--- a/generators/server/templates/src/main/java/package/repository/EntityManager.java.ejs
+++ b/generators/server/templates/src/main/java/package/repository/EntityManager.java.ejs
@@ -31,10 +31,11 @@ import org.springframework.data.r2dbc.convert.R2dbcConverter;
 import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
 import org.springframework.data.r2dbc.core.StatementMapper;
 import org.springframework.data.r2dbc.mapping.OutboundRow;
 import org.springframework.data.r2dbc.query.UpdateMapper;
 import org.springframework.data.relational.core.query.Criteria;
+import org.springframework.data.relational.core.sql.Condition;
 import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
 import org.springframework.data.relational.core.sql.Conditions;
 import org.springframework.data.relational.core.sql.OrderByField;
 import org.springframework.data.relational.core.sql.Select;
 import org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndJoin;
@@ -91,46 +92,38 @@ public class EntityManager {
      * @param selectFrom a representation of a select statement.
      * @param entityType the entity type which holds the table name.
      * @param pageable page parameter, or null, if everything needs to be returned
      * @return sql select statement
      */
-    public String createSelect(SelectFromAndJoin selectFrom, Class<?> entityType, Pageable pageable, Criteria criteria) {
+    public String createSelect(SelectFromAndJoin selectFrom, Class<?> entityType, Pageable pageable) {
         if (pageable != null) {
-            if (criteria != null) {
-                return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()).where(Conditions.just(criteria.toString())), entityType, pageable.getSort());
-            } else {
-                return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()), entityType, pageable.getSort());
-            }
+            return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()), entityType, pageable.getSort());
         } else {
-            if (criteria != null) {
-                return createSelectImpl(selectFrom.where(Conditions.just(criteria.toString())), entityType, null);
-            } else {
-                return createSelectImpl(selectFrom, entityType, null);
-            }
+            return createSelectImpl(selectFrom, entityType, null);
         }
     }
 
     /**
      * Creates an SQL select statement from the given fragment and pagination parameters.
      * @param selectFrom a representation of a select statement.
      * @param entityType the entity type which holds the table name.
      * @param pageable page parameter, or null, if everything needs to be returned
      * @return sql select statement
      */
-    public String createSelect(SelectFromAndJoinCondition selectFrom, Class<?> entityType, Pageable pageable, Criteria criteria) {
+    public String createSelect(SelectFromAndJoinCondition selectFrom, Class<?> entityType, Pageable pageable, Condition where) {
         if (pageable != null) {
-            if (criteria != null) {
-                return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()).where(Conditions.just(criteria.toString())), entityType, pageable.getSort());
-            } else {
-                return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()), entityType, pageable.getSort());
-            }
+            return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()), entityType, pageable.getSort());
         } else {
-            if (criteria != null) {
-                return createSelectImpl(selectFrom.where(Conditions.just(criteria.toString())), entityType, null);
-            } else {
-                return createSelectImpl(selectFrom, entityType, null);
-            }
+            return createSelectImpl(selectFrom.where(where), entityType, null);
+        }
+    }
+
+    public String createSelect(SelectFromAndJoin selectFrom, Class<?> entityType, Pageable pageable, Condition where) {
+        if (pageable != null) {
+            return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()).where(where), entityType, pageable.getSort());
+        } else {
+            return createSelectImpl(selectFrom.where(where), entityType, null);
         }
     }
 
     private String createSelectImpl(SelectOrdered selectFrom, Class<?> entityType, Sort sortParameter) {
         if (sortParameter != null && sortParameter.isSorted()) {

Vulnerable code:         return findAllBy(pageable, null);
    @Override
    public Flux<<%= persistClass %>> findAllBy(Pageable pageable, Criteria criteria) {
        return createQuery(pageable, criteria).all();
    }

    RowsFetchSpec<<%= persistClass %>> createQuery(Pageable pageable, Criteria criteria) {

        String select = entityManager.createSelect(selectFrom, <%= persistClass %>.class, pageable, criteria);
        return findAllBy(null, null);
        return createQuery(null, where(EntityManager.ENTITY_ALIAS + ".<%= primaryKey.fields[0].columnName %>").is(id)).one();

    Flux<<%= persistClass %>> findAllBy(Pageable pageable, Criteria criteria);
    public String createSelect(SelectFromAndJoin selectFrom, Class<?> entityType, Pageable pageable, Criteria criteria) {
            if (criteria != null) {
                return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()).where(Conditions.just(criteria.toString())), entityType, pageable.getSort());
            } else {
                return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()), entityType, pageable.getSort());
            }
            if (criteria != null) {
                return createSelectImpl(selectFrom.where(Conditions.just(criteria.toString())), entityType, null);
            } else {
                return createSelectImpl(selectFrom, entityType, null);
            }
    public String createSelect(SelectFromAndJoinCondition selectFrom, Class<?> entityType, Pageable pageable, Criteria criteria) {
            if (criteria != null) {
                return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()).where(Conditions.just(criteria.toString())), entityType, pageable.getSort());
            } else {
                return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()), entityType, pageable.getSort());
            }
            if (criteria != null) {
                return createSelectImpl(selectFrom.where(Conditions.just(criteria.toString())), entityType, null);
            } else {
                return createSelectImpl(selectFrom, entityType, null);
            }
Fixed code: import org.springframework.data.relational.core.sql.Comparison;
import org.springframework.data.relational.core.sql.Condition;
import org.springframework.data.relational.core.sql.Conditions;
        return createQuery(pageable, null).all();
    RowsFetchSpec<<%= persistClass %>> createQuery(Pageable pageable, Condition whereClause) {
        // we do not support Criteria here for now as of https://github.com/jhipster/generator-jhipster/issues/18269
        String select = entityManager.createSelect(selectFrom, <%= persistClass %>.class, pageable, whereClause);
        return findAllBy(null);
        Comparison whereClause = Conditions.isEqual(entityTable.column("<%= primaryKey.fields[0].columnName %>"), Conditions.just(id.toString()));
        return createQuery(null, whereClause).one();
    // this is not supported at the moment because of https://github.com/jhipster/generator-jhipster/issues/18269
    // Flux<<%= persistClass %>> findAllBy(Pageable pageable, Criteria criteria);
import org.springframework.data.relational.core.sql.Condition;
    public String createSelect(SelectFromAndJoin selectFrom, Class<?> entityType, Pageable pageable) {
            return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()), entityType, pageable.getSort());
            return createSelectImpl(selectFrom, entityType, null);
    public String createSelect(SelectFromAndJoinCondition selectFrom, Class<?> entityType, Pageable pageable, Condition where) {
            return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()), entityType, pageable.getSort());
            return createSelectImpl(selectFrom.where(where), entityType, null);
        }
    }

    public String createSelect(SelectFromAndJoin selectFrom, Class<?> entityType, Pageable pageable, Condition where) {
        if (pageable != null) {
            return createSelectImpl(selectFrom.limitOffset(pageable.getPageSize(), pageable.getOffset()).where(where), entityType, pageable.getSort());
        } else {
            return createSelectImpl(selectFrom.where(where), entityType, null);