From 59fede7acaaa1579b561407aefa582311f7ebe78 Mon Sep 17 00:00:00 2001
From: Andrea Boriero <andrea@hibernate.org>
Date: Tue, 29 Sep 2020 20:56:30 +0100
Subject: [PATCH] HHH-14225 CVE-2020-25638 Potential for SQL injection on
 use_sql_comments logging enabled

---
 .../java/org/hibernate/dialect/Dialect.java   |  15 ++-
 .../internal/SelectStatementBuilder.java      |   2 +-
 .../main/java/org/hibernate/sql/Delete.java   |   4 +-
 .../main/java/org/hibernate/sql/Insert.java   |   2 +-
 .../java/org/hibernate/sql/InsertSelect.java  |   2 +-
 .../java/org/hibernate/sql/QuerySelect.java   |   2 +-
 .../main/java/org/hibernate/sql/Select.java   |   2 +-
 .../java/org/hibernate/sql/SimpleSelect.java  |   2 +-
 .../main/java/org/hibernate/sql/Update.java   |   2 +-
 .../hibernate/test/comments/TestEntity.java   |  46 ++++++++
 .../hibernate/test/comments/TestEntity2.java  |  37 ++++++
 .../test/comments/UseSqlCommentTest.java      | 111 ++++++++++++++++++
 12 files changed, 218 insertions(+), 9 deletions(-)
 create mode 100644 hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity.java
 create mode 100644 hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity2.java
 create mode 100644 hibernate-core/src/test/java/org/hibernate/test/comments/UseSqlCommentTest.java

Index: src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java
===================================================================
--- src.orig/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java
+++ src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java
@@ -24,6 +24,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
+import java.util.regex.Pattern;
 
 import org.hibernate.HibernateException;
 import org.hibernate.LockMode;
@@ -140,6 +141,9 @@ public abstract class Dialect implements
 	 */
 	public static final String CLOSED_QUOTE = "`\"]";
 
+	private static final Pattern ESCAPE_CLOSING_COMMENT_PATTERN = Pattern.compile( "\\*/" );
+	private static final Pattern ESCAPE_OPENING_COMMENT_PATTERN = Pattern.compile( "/\\*" );
+
 	private final TypeNames typeNames = new TypeNames();
 	private final TypeNames hibernateTypeNames = new TypeNames();
 
@@ -3002,6 +3006,14 @@ public abstract class Dialect implements
 	}
 
 	protected String prependComment(String sql, String comment) {
-		return  "/* " + comment + " */ " + sql;
+		return "/* " + escapeComment( comment ) + " */ " + sql;
+	}
+
+	public static String escapeComment(String comment) {
+		if ( StringHelper.isNotEmpty( comment ) ) {
+			final String escaped = ESCAPE_CLOSING_COMMENT_PATTERN.matcher( comment ).replaceAll( "*\\\\/" );
+			return ESCAPE_OPENING_COMMENT_PATTERN.matcher( escaped ).replaceAll( "/\\\\*" );
+		}
+		return comment;
 	}
 }
Index: src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java
===================================================================
--- src.orig/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java
+++ src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/query/internal/SelectStatementBuilder.java
@@ -187,7 +187,7 @@ public class SelectStatementBuilder {
 		StringBuilder buf = new StringBuilder( guesstimatedBufferSize );
 
 		if ( StringHelper.isNotEmpty( comment ) ) {
-			buf.append( "/* " ).append( comment ).append( " */ " );
+			buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " );
 		}
 
 		buf.append( "select " )
Index: src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Delete.java
===================================================================
--- src.orig/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Delete.java
+++ src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Delete.java
@@ -9,6 +9,8 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
+import org.hibernate.dialect.Dialect;
+
 /**
  * An SQL <tt>DELETE</tt> statement
  *
@@ -36,7 +38,7 @@ public class Delete {
 	public String toStatementString() {
 		StringBuilder buf = new StringBuilder( tableName.length() + 10 );
 		if ( comment!=null ) {
-			buf.append( "/* " ).append(comment).append( " */ " );
+			buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " );
 		}
 		buf.append( "delete from " ).append(tableName);
 		if ( where != null || !primaryKeyColumns.isEmpty() || versionColumnName != null ) {
Index: src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Insert.java
===================================================================
--- src.orig/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Insert.java
+++ src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Insert.java
@@ -90,7 +90,7 @@ public class Insert {
 	public String toStatementString() {
 		StringBuilder buf = new StringBuilder( columns.size()*15 + tableName.length() + 10 );
 		if ( comment != null ) {
-			buf.append( "/* " ).append( comment ).append( " */ " );
+			buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " );
 		}
 		buf.append("insert into ")
 			.append(tableName);
Index: src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java
===================================================================
--- src.orig/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java
+++ src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/InsertSelect.java
@@ -65,7 +65,7 @@ public class InsertSelect {
 
 		StringBuilder buf = new StringBuilder( (columnNames.size() * 15) + tableName.length() + 10 );
 		if ( comment!=null ) {
-			buf.append( "/* " ).append( comment ).append( " */ " );
+			buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " );
 		}
 		buf.append( "insert into " ).append( tableName );
 		if ( !columnNames.isEmpty() ) {
Index: src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java
===================================================================
--- src.orig/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java
+++ src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/QuerySelect.java
@@ -126,7 +126,7 @@ public class QuerySelect {
 	public String toQueryString() {
 		StringBuilder buf = new StringBuilder( 50 );
 		if ( comment != null ) {
-			buf.append( "/* " ).append( comment ).append( " */ " );
+			buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " );
 		}
 		buf.append( "select " );
 		if ( distinct ) {
Index: src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Select.java
===================================================================
--- src.orig/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Select.java
+++ src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Select.java
@@ -40,7 +40,7 @@ public class Select {
 	public String toStatementString() {
 		StringBuilder buf = new StringBuilder(guesstimatedBufferSize);
 		if ( StringHelper.isNotEmpty(comment) ) {
-			buf.append("/* ").append(comment).append(" */ ");
+			buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " );
 		}
 		
 		buf.append("select ").append(selectClause)
Index: src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java
===================================================================
--- src.orig/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java
+++ src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/SimpleSelect.java
@@ -148,7 +148,7 @@ public class SimpleSelect {
 		);
 
 		if ( comment != null ) {
-			buf.append( "/* " ).append( comment ).append( " */ " );
+			buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " );
 		}
 
 		buf.append( "select " );
Index: src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Update.java
===================================================================
--- src.orig/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Update.java
+++ src/hibernate-orm-5.3.7/hibernate-core/src/main/java/org/hibernate/sql/Update.java
@@ -166,7 +166,7 @@ public class Update {
 	public String toStatementString() {
 		StringBuilder buf = new StringBuilder( (columns.size() * 15) + tableName.length() + 10 );
 		if ( comment!=null ) {
-			buf.append( "/* " ).append( comment ).append( " */ " );
+			buf.append( "/* " ).append( Dialect.escapeComment( comment ) ).append( " */ " );
 		}
 		buf.append( "update " ).append( tableName ).append( " set " );
 		boolean assignmentsAppended = false;
Index: src/hibernate-orm-5.3.7/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity.java
===================================================================
--- /dev/null
+++ src/hibernate-orm-5.3.7/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity.java
@@ -0,0 +1,46 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
+ * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
+ */
+package org.hibernate.test.comments;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+/**
+ * @author Andrea Boriero
+ */
+@Entity
+public class TestEntity {
+	@Id
+	private String id;
+
+	private String value;
+
+	public TestEntity() {
+
+	}
+
+	public TestEntity(String id, String value) {
+		this.id = id;
+		this.value = value;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+}
Index: src/hibernate-orm-5.3.7/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity2.java
===================================================================
--- /dev/null
+++ src/hibernate-orm-5.3.7/hibernate-core/src/test/java/org/hibernate/test/comments/TestEntity2.java
@@ -0,0 +1,37 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
+ * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
+ */
+package org.hibernate.test.comments;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+
+/**
+ * @author Andrea Boriero
+ */
+@Entity
+public class TestEntity2 {
+	@Id
+	private String id;
+
+	private String value;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+}
Index: src/hibernate-orm-5.3.7/hibernate-core/src/test/java/org/hibernate/test/comments/UseSqlCommentTest.java
===================================================================
--- /dev/null
+++ src/hibernate-orm-5.3.7/hibernate-core/src/test/java/org/hibernate/test/comments/UseSqlCommentTest.java
@@ -0,0 +1,111 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
+ * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
+ */
+package org.hibernate.test.comments;
+
+import java.util.List;
+import java.util.Map;
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import javax.persistence.criteria.CompoundSelection;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Path;
+import javax.persistence.criteria.Root;
+
+import org.hibernate.cfg.AvailableSettings;
+import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Andrea Boriero
+ */
+public class UseSqlCommentTest extends BaseEntityManagerFunctionalTestCase {
+
+	@Override
+	protected Class<?>[] getAnnotatedClasses() {
+		return new Class[] { TestEntity.class, TestEntity2.class };
+	}
+
+	@Override
+	protected void addMappings(Map settings) {
+		settings.put( AvailableSettings.USE_SQL_COMMENTS, "true" );
+		settings.put( AvailableSettings.FORMAT_SQL, "false" );
+	}
+
+	@Before
+	public void setUp() {
+		doInJPA( this::entityManagerFactory, entityManager -> {
+			TestEntity testEntity = new TestEntity();
+			testEntity.setId( "test1" );
+			testEntity.setValue( "value1" );
+			entityManager.persist( testEntity );
+
+			TestEntity2 testEntity2 = new TestEntity2();
+			testEntity2.setId( "test2" );
+			testEntity2.setValue( "value2" );
+			entityManager.persist( testEntity2 );
+		} );
+	}
+
+	@Test
+	public void testIt() {
+		String appendLiteral = "*/select id as col_0_0_,value as col_1_0_ from testEntity2 where 1=1 or id=?--/*";
+		doInJPA( this::entityManagerFactory, entityManager -> {
+
+			List<TestEntity> result = findUsingQuery( "test1", appendLiteral, entityManager );
+
+			TestEntity test1 = result.get( 0 );
+			assertThat( test1.getValue(), is( appendLiteral ) );
+		} );
+
+		doInJPA( this::entityManagerFactory, entityManager -> {
+
+			List<TestEntity> result = findUsingCriteria( "test1", appendLiteral, entityManager );
+
+			TestEntity test1 = result.get( 0 );
+			assertThat( test1.getValue(), is( appendLiteral ) );
+		} );
+	}
+
+	public List<TestEntity> findUsingCriteria(String id, String appendLiteral, EntityManager entityManager) {
+		CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+		CriteriaQuery<TestEntity> criteria = builder.createQuery( TestEntity.class );
+		Root<TestEntity> root = criteria.from( TestEntity.class );
+
+		Path<Object> idPath = root.get( "id" );
+		CompoundSelection<TestEntity> selection = builder.construct(
+				TestEntity.class,
+				idPath,
+				builder.literal( appendLiteral )
+		);
+		criteria.select( selection );
+
+		criteria.where( builder.equal( idPath, builder.parameter( String.class, "where_id" ) ) );
+
+		TypedQuery<TestEntity> query = entityManager.createQuery( criteria );
+		query.setParameter( "where_id", id );
+		return query.getResultList();
+	}
+
+	public List<TestEntity> findUsingQuery(String id, String appendLiteral, EntityManager entityManager) {
+		TypedQuery<TestEntity> query =
+				entityManager.createQuery(
+						"select new org.hibernate.test.comments.TestEntity(id, '"
+								+ appendLiteral.replace( "'", "''" )
+								+ "') from TestEntity where id=:where_id",
+						TestEntity.class
+				);
+		query.setParameter( "where_id", id );
+		return query.getResultList();
+	}
+}
