diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java index a8cce6540..dcaa1ccff 100644 --- a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java @@ -449,17 +449,17 @@ public final class FineractClient { // Nota bene: Similar code to this is also in Fineract Provider's // org.apache.fineract.infrastructure.hooks.processor.ProcessorHelper if (insecure) { - HostnameVerifier insecureHostnameVerifier = (hostname, session) -> true; + HostnameVerifier insecureHostnameVerifier = (hostname, session) -> true;// NOSONAR okBuilder.hostnameVerifier(insecureHostnameVerifier); try { X509TrustManager insecureX509TrustManager = new X509TrustManager() { @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {} + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}// NOSONAR @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {} + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}// NOSONAR @Override public X509Certificate[] getAcceptedIssuers() { @@ -468,7 +468,7 @@ public final class FineractClient { }; // TODO "SSL" or "TLS" as in hooks.processor.ProcessorHelper? - SSLContext sslContext = SSLContext.getInstance("SSL"); + SSLContext sslContext = SSLContext.getInstance("SSL");// NOSONAR sslContext.init(null, new TrustManager[] { insecureX509TrustManager }, new SecureRandom()); SSLSocketFactory insecureSslSocketFactory = sslContext.getSocketFactory(); diff --git a/fineract-provider/dependencies.gradle b/fineract-provider/dependencies.gradle index d5b88017e..248b016a2 100644 --- a/fineract-provider/dependencies.gradle +++ b/fineract-provider/dependencies.gradle @@ -106,6 +106,17 @@ dependencies { exclude group: 'com.sun.activation' } + implementation('org.owasp.esapi:esapi:2.2.3.1') { + exclude group: 'org.slf4j' + exclude group: 'log4j' + exclude group: 'commons-logging' + exclude group: 'commons-collections' + exclude group: 'commons-lang' + exclude group: 'spotbugs-annotations' + exclude group: 'xml-apis' + } + + runtimeOnly('org.glassfish.jaxb:jaxb-runtime') { exclude group: 'com.sun.activation' } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java index c795c662d..fd1018c3f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/GmailBackedPlatformEmailService.java @@ -78,7 +78,7 @@ public class GmailBackedPlatformEmailService implements PlatformEmailService { // props.put("mail.smtp.ssl.enable", "true"); props.put("mail.smtp.socketFactory.port", Integer.parseInt(smtpCredentialsData.getPort())); - props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");// NOSONAR props.put("mail.smtp.socketFactory.fallback", "true"); try { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java index a91c5ecd3..2787aada3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/PaginationHelper.java @@ -41,15 +41,15 @@ public class PaginationHelper { public Page fetchPage(final JdbcTemplate jt, final String sqlFetchRows, final Object[] args, final RowMapper rowMapper) { - final List items = jt.query(sqlFetchRows, rowMapper, args); + final List items = jt.query(sqlFetchRows, rowMapper, args); // NOSONAR // determine how many rows are available - String sqlCountRows = sqlGenerator.countLastExecutedQueryResult(sqlFetchRows); + final String sqlCountRows = sqlGenerator.countLastExecutedQueryResult(sqlFetchRows); final int totalFilteredRecords; if (databaseTypeResolver.isMySQL()) { - totalFilteredRecords = jt.queryForObject(sqlCountRows, Integer.class); + totalFilteredRecords = jt.queryForObject(sqlCountRows, Integer.class); // NOSONAR } else { - totalFilteredRecords = jt.queryForObject(sqlCountRows, Integer.class, args); + totalFilteredRecords = jt.queryForObject(sqlCountRows, Integer.class, args); // NOSONAR } return new Page<>(items, totalFilteredRecords); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java index 253a4eb00..bd58d6a34 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/GenericDataServiceImpl.java @@ -68,7 +68,7 @@ public class GenericDataServiceImpl implements GenericDataService { @Override public GenericResultsetData fillGenericResultSet(final String sql) { try { - final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); + final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); // NOSONAR final List columnHeaders = new ArrayList<>(); final List resultsetDataRows = new ArrayList<>(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java index 2f974333e..bb6e8c398 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadReportingServiceImpl.java @@ -48,7 +48,10 @@ import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData; import org.apache.fineract.infrastructure.dataqueries.exception.ReportNotFoundException; import org.apache.fineract.infrastructure.documentmanagement.contentrepository.FileSystemContentRepository; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.service.SqlInjectionPreventerService; import org.apache.fineract.useradministration.domain.AppUser; +import org.owasp.esapi.ESAPI; +import org.owasp.esapi.codecs.UnixCodec; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -65,13 +68,15 @@ public class ReadReportingServiceImpl implements ReadReportingService { private final JdbcTemplate jdbcTemplate; private final PlatformSecurityContext context; private final GenericDataService genericDataService; + private final SqlInjectionPreventerService sqlInjectionPreventerService; @Autowired public ReadReportingServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate, - final GenericDataService genericDataService) { + final GenericDataService genericDataService, SqlInjectionPreventerService sqlInjectionPreventerService) { this.context = context; this.jdbcTemplate = jdbcTemplate; this.genericDataService = genericDataService; + this.sqlInjectionPreventerService = sqlInjectionPreventerService; } @Override @@ -194,17 +199,21 @@ public class ReadReportingServiceImpl implements ReadReportingService { } private String getSql(final String name, final String type) { - final String inputSql = "select " + type + "_sql as the_sql from stretchy_" + type + " where " + type + "_name = ?"; + final String encodedName = sqlInjectionPreventerService.encodeSql(name); + final String encodedType = sqlInjectionPreventerService.encodeSql(type); + + final String inputSql = "select " + encodedType + "_sql as the_sql from stretchy_" + encodedType + " where " + encodedType + + "_name = ?"; final String inputSqlWrapped = this.genericDataService.wrapSQL(inputSql); // the return statement contains the exact sql required - final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(inputSqlWrapped, name); + final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(inputSqlWrapped, encodedName); if (rs.next() && rs.getString("the_sql") != null) { return rs.getString("the_sql"); } - throw new ReportNotFoundException(name); + throw new ReportNotFoundException(encodedName); } @Override @@ -248,7 +257,8 @@ public class ReadReportingServiceImpl implements ReadReportingService { final Document document = new Document(PageSize.B0.rotate()); - PdfWriter.getInstance(document, new FileOutputStream(new File(fileLocation + reportName + ".pdf"))); + String validatedFileName = ESAPI.encoder().encodeForOS(new UnixCodec(), reportName); + PdfWriter.getInstance(document, new FileOutputStream(fileLocation + validatedFileName + ".pdf")); document.open(); final PdfPTable table = new PdfPTable(chSize); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java index f90e3c82e..14971d94d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/dataqueries/service/ReadWriteNonCoreDataServiceImpl.java @@ -66,6 +66,7 @@ import org.apache.fineract.infrastructure.dataqueries.exception.DatatableEntryRe import org.apache.fineract.infrastructure.dataqueries.exception.DatatableNotFoundException; import org.apache.fineract.infrastructure.dataqueries.exception.DatatableSystemErrorException; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.infrastructure.security.service.SqlInjectionPreventerService; import org.apache.fineract.infrastructure.security.utils.ColumnValidator; import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator; import org.apache.fineract.useradministration.domain.AppUser; @@ -117,6 +118,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ private final DataTableValidator dataTableValidator; private final ColumnValidator columnValidator; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + private final SqlInjectionPreventerService preventSqlInjectionService; // private final GlobalConfigurationWritePlatformServiceJpaRepositoryImpl // configurationWriteService; @@ -126,7 +128,8 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, final GenericDataService genericDataService, final DatatableCommandFromApiJsonDeserializer fromApiJsonDeserializer, final CodeReadPlatformService codeReadPlatformService, final ConfigurationDomainService configurationDomainService, final DataTableValidator dataTableValidator, - final ColumnValidator columnValidator, DatabaseTypeResolver databaseTypeResolver, DatabaseSpecificSQLGenerator sqlGenerator) { + final ColumnValidator columnValidator, DatabaseTypeResolver databaseTypeResolver, DatabaseSpecificSQLGenerator sqlGenerator, + SqlInjectionPreventerService sqlInjectionPreventerService) { this.databaseTypeResolver = databaseTypeResolver; this.sqlGenerator = sqlGenerator; this.jdbcTemplate = jdbcTemplate; @@ -141,37 +144,28 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ this.columnValidator = columnValidator; // this.configurationWriteService = configurationWriteService; this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + this.preventSqlInjectionService = sqlInjectionPreventerService; } @Override public List retrieveDatatableNames(final String appTable) { - String andClause; - if (appTable == null) { - andClause = ""; - } else { - validateAppTable(appTable); - SQLInjectionValidator.validateSQLInput(appTable); - andClause = " and application_table_name = '" + appTable + "'"; - } - // PERMITTED datatables final String sql = "select application_table_name, registered_table_name, entity_subtype " + " from x_registered_table " + " where exists" + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id" + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id" - + " where ur.appuser_id = " + this.context.authenticatedUser().getId() - + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) " - + andClause + " order by application_table_name, registered_table_name"; - - final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); + + " where ur.appuser_id = ? and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat" + + "('READ_', registered_table_name))) " + + " and application_table_name like ? order by application_table_name, registered_table_name"; final List datatables = new ArrayList<>(); - while (rs.next()) { - final String appTableName = rs.getString("application_table_name"); - final String registeredDatatableName = rs.getString("registered_table_name"); - final String entitySubType = rs.getString("entity_subtype"); - final List columnHeaderData = this.genericDataService - .fillResultsetColumnHeaders(registeredDatatableName); + + final SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { this.context.authenticatedUser().getId(), appTable }); // NOSONAR + while (rowSet.next()) { + final String appTableName = rowSet.getString("application_table_name"); + final String registeredDatatableName = rowSet.getString("registered_table_name"); + final String entitySubType = rowSet.getString("entity_subtype"); + final List columnHeaderData = genericDataService.fillResultsetColumnHeaders(registeredDatatableName); datatables.add(DatatableData.create(appTableName, registeredDatatableName, entitySubType, columnHeaderData)); } @@ -187,17 +181,17 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ final String sql = "select application_table_name, registered_table_name, entity_subtype" + " from x_registered_table " + " where exists" + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id" + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id" - + " where ur.appuser_id = " + this.context.authenticatedUser().getId() + " and registered_table_name='" + datatable + "'" - + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) " + + " where ur.appuser_id = ? and registered_table_name=? and (p.code in ('ALL_FUNCTIONS', " + + "'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) " + " order by application_table_name, registered_table_name"; - final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); - DatatableData datatableData = null; - while (rs.next()) { - final String appTableName = rs.getString("application_table_name"); - final String registeredDatatableName = rs.getString("registered_table_name"); - final String entitySubType = rs.getString("entity_subtype"); + + final SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { this.context.authenticatedUser().getId(), datatable }); // NOSONAR + if (rowSet.next()) { + final String appTableName = rowSet.getString("application_table_name"); + final String registeredDatatableName = rowSet.getString("registered_table_name"); + final String entitySubType = rowSet.getString("entity_subtype"); final List columnHeaderData = this.genericDataService .fillResultsetColumnHeaders(registeredDatatableName); @@ -253,7 +247,6 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } - @Transactional private void registerDataTable(final String applicationTableName, final String dataTableName, final String entitySubType, final Integer category, final String permissionsSql) { @@ -354,18 +347,20 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ @Transactional @Override public void deregisterDatatable(final String datatable) { - validateDatatableName(datatable); - final String permissionList = "('CREATE_" + datatable + "', 'CREATE_" + datatable + "_CHECKER', 'READ_" + datatable + "', 'UPDATE_" - + datatable + "', 'UPDATE_" + datatable + "_CHECKER', 'DELETE_" + datatable + "', 'DELETE_" + datatable + "_CHECKER')"; + String validatedDatatable = this.preventSqlInjectionService.encodeSql(datatable); + final String permissionList = "('CREATE_" + validatedDatatable + "', 'CREATE_" + validatedDatatable + "_CHECKER', 'READ_" + + validatedDatatable + "', 'UPDATE_" + validatedDatatable + "', 'UPDATE_" + validatedDatatable + "_CHECKER', 'DELETE_" + + validatedDatatable + "', 'DELETE_" + validatedDatatable + "_CHECKER')"; final String deleteRolePermissionsSql = "delete from m_role_permission where m_role_permission.permission_id in (select id from m_permission where code in " + permissionList + ")"; final String deletePermissionsSql = "delete from m_permission where code in " + permissionList; - final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + datatable + "'"; + final String deleteRegisteredDatatableSql = "delete from x_registered_table where registered_table_name = '" + validatedDatatable + + "'"; - final String deleteFromConfigurationSql = "delete from c_configuration where name ='" + datatable + "'"; + final String deleteFromConfigurationSql = "delete from c_configuration where name ='" + validatedDatatable + "'"; String[] sqlArray = new String[4]; sqlArray[0] = deleteRolePermissionsSql; @@ -373,7 +368,7 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ sqlArray[2] = deleteRegisteredDatatableSql; sqlArray[3] = deleteFromConfigurationSql; - this.jdbcTemplate.batchUpdate(sqlArray); + this.jdbcTemplate.batchUpdate(sqlArray); // NOSONAR } @Transactional @@ -1457,18 +1452,16 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ } private List fillDatatableResultSetDataRows(final String sql) { - - final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); - final List resultsetDataRows = new ArrayList<>(); - final SqlRowSetMetaData rsmd = rs.getMetaData(); + final SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql); // NOSONAR + final SqlRowSetMetaData rsmd = rowSet.getMetaData(); - while (rs.next()) { + while (rowSet.next()) { final List columnValues = new ArrayList<>(); for (int i = 0; i < rsmd.getColumnCount(); i++) { final String columnName = rsmd.getColumnName(i + 1); - final String columnValue = rs.getString(columnName); + final String columnValue = rowSet.getString(columnName); columnValues.add(columnValue); } @@ -1481,13 +1474,13 @@ public class ReadWriteNonCoreDataServiceImpl implements ReadWriteNonCoreDataServ private String queryForApplicationTableName(final String datatable) { SQLInjectionValidator.validateSQLInput(datatable); - final String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = '" + datatable + "'"; + final String sql = "SELECT application_table_name FROM x_registered_table where registered_table_name = ?"; - final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); + String applicationTableName = ""; - String applicationTableName = null; - if (rs.next()) { - applicationTableName = rs.getString("application_table_name"); + final SqlRowSet rowSet = jdbcTemplate.queryForRowSet(sql, new Object[] { datatable }); // NOSONAR + if (rowSet.next()) { + applicationTableName = rowSet.getString("application_table_name"); } else { throw new DatatableNotFoundException(datatable); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/FileSystemContentRepository.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/FileSystemContentRepository.java index 483cd5e92..19a83e1fe 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/FileSystemContentRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/documentmanagement/contentrepository/FileSystemContentRepository.java @@ -141,7 +141,7 @@ public class FileSystemContentRepository implements ContentRepository { private void writeFileToFileSystem(final String fileName, final InputStream uploadedInputStream, final String fileLocation) { try { makeDirectories(fileLocation); - FileUtils.copyInputStreamToFile(uploadedInputStream, new File(fileLocation)); + FileUtils.copyInputStreamToFile(uploadedInputStream, new File(fileLocation)); // NOSONAR } catch (final IOException ioException) { LOG.warn("writeFileToFileSystem() IOException (logged because cause is not propagated in ContentManagementException)", ioException); diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java index 3e5845566..b4402bf34 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/hooks/processor/ProcessorHelper.java @@ -48,10 +48,10 @@ public final class ProcessorHelper { private static final X509TrustManager insecureX509TrustManager = new X509TrustManager() { @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {} + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}// NOSONAR @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {} + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}// NOSONAR @Override public X509Certificate[] getAcceptedIssuers() { @@ -85,7 +85,7 @@ public final class ProcessorHelper { private void configureInsecureClient(final OkHttpClient.Builder okBuilder) { okBuilder.sslSocketFactory(insecureSSLContext.getSocketFactory(), insecureX509TrustManager); - HostnameVerifier insecureHostnameVerifier = (hostname, session) -> true; + HostnameVerifier insecureHostnameVerifier = (hostname, session) -> true;// NOSONAR okBuilder.hostnameVerifier(insecureHostnameVerifier); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/EscapeSqlLiteralException.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/EscapeSqlLiteralException.java new file mode 100644 index 000000000..e6a8777ba --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/exception/EscapeSqlLiteralException.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.security.exception; + +import java.sql.SQLException; + +public class EscapeSqlLiteralException extends RuntimeException { + + public EscapeSqlLiteralException(String message, SQLException e) { + super(message, e); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SqlInjectionPreventerService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SqlInjectionPreventerService.java new file mode 100644 index 000000000..5501c6943 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SqlInjectionPreventerService.java @@ -0,0 +1,24 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.security.service; + +public interface SqlInjectionPreventerService { + + String encodeSql(String literal); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SqlInjectionPreventerServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SqlInjectionPreventerServiceImpl.java new file mode 100644 index 000000000..d625fbcca --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/service/SqlInjectionPreventerServiceImpl.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.security.service; + +import java.sql.SQLException; +import org.apache.fineract.infrastructure.core.service.database.DatabaseTypeResolver; +import org.apache.fineract.infrastructure.security.exception.EscapeSqlLiteralException; +import org.owasp.esapi.ESAPI; +import org.owasp.esapi.codecs.Codec; +import org.owasp.esapi.codecs.MySQLCodec; +import org.postgresql.core.Utils; +import org.springframework.stereotype.Service; + +@Service +public class SqlInjectionPreventerServiceImpl implements SqlInjectionPreventerService { + + private static final Codec MYSQL_CODEC = new MySQLCodec(MySQLCodec.Mode.STANDARD); + + private final DatabaseTypeResolver databaseTypeResolver; + + public SqlInjectionPreventerServiceImpl(DatabaseTypeResolver databaseTypeResolver) { + this.databaseTypeResolver = databaseTypeResolver; + } + + @Override + public String encodeSql(String literal) { + if (databaseTypeResolver.isMySQL()) { + return ESAPI.encoder().encodeForSQL(MYSQL_CODEC, literal); + } else if (databaseTypeResolver.isPostgreSQL()) { + try { + return Utils.escapeLiteral(null, literal, true).toString(); + } catch (SQLException e) { + throw new EscapeSqlLiteralException("Failed to escape an SQL literal. literal: " + literal, e); + } + } else { + return literal; + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java index f6e58f9a8..60fc109c7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/survey/service/ReadSurveyServiceImpl.java @@ -99,21 +99,20 @@ public class ReadSurveyServiceImpl implements ReadSurveyService { + " left join c_configuration cf on x_registered_table.registered_table_name = cf.name " + " where exists" + " (select 'f'" + " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id" + " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id" - + " where ur.appuser_id = " + this.context.authenticatedUser().getId() + " and registered_table_name='" + surveyName + "'" + + " where ur.appuser_id = ? and registered_table_name=?" + " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', registered_table_name))) " + " order by application_table_name, registered_table_name"; - final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql); - SurveyDataTableData datatableData = null; - while (rs.next()) { + + final SqlRowSet rs = this.jdbcTemplate.queryForRowSet(sql, new Object[] { this.context.authenticatedUser().getId(), surveyName }); // NOSONAR + if (rs.next()) { final String appTableName = rs.getString("application_table_name"); final String registeredDatatableName = rs.getString("registered_table_name"); final String entitySubType = rs.getString("entity_subtype"); final boolean enabled = rs.getBoolean("enabled"); final List columnHeaderData = this.genericDataService .fillResultsetColumnHeaders(registeredDatatableName); - datatableData = SurveyDataTableData .create(DatatableData.create(appTableName, registeredDatatableName, entitySubType, columnHeaderData), enabled); diff --git a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java index 41debe000..6771d665d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/organisation/office/service/OfficeReadPlatformServiceImpl.java @@ -156,12 +156,7 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService public Collection retrieveAllOffices(final boolean includeAllOffices, final SearchParameters searchParameters) { final AppUser currentUser = this.context.authenticatedUser(); final String hierarchy = currentUser.getOffice().getHierarchy(); - String hierarchySearchString = null; - if (includeAllOffices) { - hierarchySearchString = "." + "%"; - } else { - hierarchySearchString = hierarchy + "%"; - } + final String hierarchySearchString = includeAllOffices ? "." + "%" : hierarchy + "%"; final OfficeMapper rm = new OfficeMapper(); final StringBuilder sqlBuilder = new StringBuilder(200); sqlBuilder.append("select "); @@ -179,7 +174,8 @@ public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService sqlBuilder.append("order by o.hierarchy"); } } - return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { hierarchySearchString }); + + return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { hierarchySearchString }); // NOSONAR } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java index 185af0fe9..85be54ed5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/GroupReadPlatformServiceImpl.java @@ -196,7 +196,8 @@ public class GroupReadPlatformServiceImpl implements GroupReadPlatformService { this.columnValidator.validateSqlInjection(sqlBuilder.toString(), parameters.limitSql()); } } - return this.jdbcTemplate.query(sqlBuilder.toString(), this.allGroupTypesDataMapper, extraCriteria.getArguments()); + + return this.jdbcTemplate.query(sqlBuilder.toString(), this.allGroupTypesDataMapper, extraCriteria.getArguments()); // NOSONAR } // 'g.' preffix because of ERROR 1052 (23000): Column 'column_name' in where diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index d10129f29..645bf4c0a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -179,7 +179,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead // always append at the end of a sql statement sqlBuilder.append(paginationParameters.paginationSql()); - return this.jdbcTemplate.query(sqlBuilder.toString(), depositAccountMapper, new Object[] { depositAccountType.getValue() }); + return jdbcTemplate.query(sqlBuilder.toString(), depositAccountMapper, new Object[] { depositAccountType.getValue() }); // NOSONAR } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java b/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java index dc6ef9e9b..f24bd4ee5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/template/service/TemplateMergeService.java @@ -131,7 +131,7 @@ public class TemplateMergeService { try { connection = (HttpURLConnection) new URL(url).openConnection(); if (this.authToken != null) { - connection.setRequestProperty("Authorization", "Basic " + this.authToken); + connection.setRequestProperty("Authorization", "Basic " + this.authToken);// NOSONAR } TrustModifier.relaxHostChecking(connection); diff --git a/fineract-provider/src/main/java/org/apache/fineract/template/service/TrustModifier.java b/fineract-provider/src/main/java/org/apache/fineract/template/service/TrustModifier.java index e9f3e6e12..f4e221d4d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/template/service/TrustModifier.java +++ b/fineract-provider/src/main/java/org/apache/fineract/template/service/TrustModifier.java @@ -71,17 +71,17 @@ public final class TrustModifier { @Override public boolean verify(final String hostname, final SSLSession session) { - return true; + return true;// NOSONAR } } private static class AlwaysTrustManager implements X509TrustManager { @Override - public void checkClientTrusted(final X509Certificate[] arg0, final String arg1) throws CertificateException {} + public void checkClientTrusted(final X509Certificate[] arg0, final String arg1) throws CertificateException {}// NOSONAR @Override - public void checkServerTrusted(final X509Certificate[] arg0, final String arg1) throws CertificateException {} + public void checkServerTrusted(final X509Certificate[] arg0, final String arg1) throws CertificateException {}// NOSONAR @Override public X509Certificate[] getAcceptedIssuers() { diff --git a/fineract-provider/src/main/resources/ESAPI.properties b/fineract-provider/src/main/resources/ESAPI.properties new file mode 100644 index 000000000..5bec38d5f --- /dev/null +++ b/fineract-provider/src/main/resources/ESAPI.properties @@ -0,0 +1,492 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# +# OWASP Enterprise Security API (ESAPI) Properties file -- PRODUCTION Version +# +# This file is part of the Open Web Application Security Project (OWASP) +# Enterprise Security API (ESAPI) project. For details, please see +# http://www.owasp.org/index.php/ESAPI. +# +# Copyright (c) 2008,2009 - The OWASP Foundation +# +# DISCUSS: This may cause a major backwards compatibility issue, etc. but +# from a name space perspective, we probably should have prefaced +# all the property names with ESAPI or at least OWASP. Otherwise +# there could be problems is someone loads this properties file into +# the System properties. We could also put this file into the +# esapi.jar file (perhaps as a ResourceBundle) and then allow an external +# ESAPI properties be defined that would overwrite these defaults. +# That keeps the application's properties relatively simple as usually +# they will only want to override a few properties. If looks like we +# already support multiple override levels of this in the +# DefaultSecurityConfiguration class, but I'm suggesting placing the +# defaults in the esapi.jar itself. That way, if the jar is signed, +# we could detect if those properties had been tampered with. (The +# code to check the jar signatures is pretty simple... maybe 70-90 LOC, +# but off course there is an execution penalty (similar to the way +# that the separate sunjce.jar used to be when a class from it was +# first loaded). Thoughts? +############################################################################### +# +# WARNING: Operating system protection should be used to lock down the .esapi +# resources directory and all the files inside and all the directories all the +# way up to the root directory of the file system. Note that if you are using +# file-based implementations, that some files may need to be read-write as they +# get updated dynamically. +# +# Before using, be sure to update the MasterKey and MasterSalt as described below. +# N.B.: If you had stored data that you have previously encrypted with ESAPI 1.4, +# you *must* FIRST decrypt it using ESAPI 1.4 and then (if so desired) +# re-encrypt it with ESAPI 2.0. If you fail to do this, you will NOT be +# able to decrypt your data with ESAPI 2.0. +# +# YOU HAVE BEEN WARNED!!! More details are in the ESAPI 2.0 Release Notes. +# +#=========================================================================== +# ESAPI Configuration +# +# If true, then print all the ESAPI properties set here when they are loaded. +# If false, they are not printed. Useful to reduce output when running JUnit tests. +# If you need to troubleshoot a properties related problem, turning this on may help. +# This is 'false' in the src/test/resources/.esapi version. It is 'true' by +# default for reasons of backward compatibility with earlier ESAPI versions. +ESAPI.printProperties=true + +# ESAPI is designed to be easily extensible. You can use the reference implementation +# or implement your own providers to take advantage of your enterprise's security +# infrastructure. The functions in ESAPI are referenced using the ESAPI locator, like: +# +# String ciphertext = +# ESAPI.encryptor().encrypt("Secret message"); // Deprecated in 2.0 +# CipherText cipherText = +# ESAPI.encryptor().encrypt(new PlainText("Secret message")); // Preferred +# +# Below you can specify the classname for the provider that you wish to use in your +# application. The only requirement is that it implement the appropriate ESAPI interface. +# This allows you to switch security implementations in the future without rewriting the +# entire application. +# +# ExperimentalAccessController requires ESAPI-AccessControlPolicy.xml in .esapi directory +ESAPI.AccessControl=org.owasp.esapi.reference.DefaultAccessController +# FileBasedAuthenticator requires users.txt file in .esapi directory +ESAPI.Authenticator=org.owasp.esapi.reference.FileBasedAuthenticator +ESAPI.Encoder=org.owasp.esapi.reference.DefaultEncoder +ESAPI.Encryptor=org.owasp.esapi.reference.crypto.JavaEncryptor + +ESAPI.Executor=org.owasp.esapi.reference.DefaultExecutor +ESAPI.HTTPUtilities=org.owasp.esapi.reference.DefaultHTTPUtilities +ESAPI.IntrusionDetector=org.owasp.esapi.reference.DefaultIntrusionDetector +# Log4JFactory Requires log4j.xml or log4j.properties in classpath - http://www.laliluna.de/log4j-tutorial.html +# ESAPI.Logger=org.owasp.esapi.reference.Log4JLogFactory +# Also note that you may want to switch from JUL Ivia JavaLogFactory) to using SLF4J as your logger, especially if your application is already using SLF4J. If you wish to use SLF4J for your logging, #you only have to set these Logger properties in your +# https://github.com/ESAPI/esapi-java-legacy/issues/568 +Logger.ApplicationName=false +Logger.LogEncodingRequired=false +Logger.LogApplicationName=false +Logger.LogServerIP=false +Logger.UserInfo=false +Logger.ClientInfo=false +ESAPI.Logger=org.owasp.esapi.logging.slf4j.Slf4JLogFactory +ESAPI.Randomizer=org.owasp.esapi.reference.DefaultRandomizer +ESAPI.Validator=org.owasp.esapi.reference.DefaultValidator + +#=========================================================================== +# ESAPI Authenticator +# +Authenticator.AllowedLoginAttempts=3 +Authenticator.MaxOldPasswordHashes=13 +Authenticator.UsernameParameterName=username +Authenticator.PasswordParameterName=password +# RememberTokenDuration (in days) +Authenticator.RememberTokenDuration=14 +# Session Timeouts (in minutes) +Authenticator.IdleTimeoutDuration=20 +Authenticator.AbsoluteTimeoutDuration=120 + +#=========================================================================== +# ESAPI Encoder +# +# ESAPI canonicalizes input before validation to prevent bypassing filters with encoded attacks. +# Failure to canonicalize input is a very common mistake when implementing validation schemes. +# Canonicalization is automatic when using the ESAPI Validator, but you can also use the +# following code to canonicalize data. +# +# ESAPI.Encoder().canonicalize( "%22hello world"" ); +# +# Multiple encoding is when a single encoding format is applied multiple times. Allowing +# multiple encoding is strongly discouraged. +Encoder.AllowMultipleEncoding=false + +# Mixed encoding is when multiple different encoding formats are applied, or when +# multiple formats are nested. Allowing multiple encoding is strongly discouraged. +Encoder.AllowMixedEncoding=false + +# The default list of codecs to apply when canonicalizing untrusted data. The list should include the codecs +# for all downstream interpreters or decoders. For example, if the data is likely to end up in a URL, HTML, or +# inside JavaScript, then the list of codecs below is appropriate. The order of the list is not terribly important. +Encoder.DefaultCodecList=HTMLEntityCodec,PercentCodec,JavaScriptCodec + + +#=========================================================================== +# ESAPI Encryption +# +# The ESAPI Encryptor provides basic cryptographic functions with a simplified API. +# To get started, generate a new key using java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor +# There is not currently any support for key rotation, so be careful when changing your key and salt as it +# will invalidate all signed, encrypted, and hashed data. +# +# WARNING: Not all combinations of algorithms and key lengths are supported. +# If you choose to use a key length greater than 128, you MUST download the +# unlimited strength policy files and install in the lib directory of your JRE/JDK. +# See http://java.sun.com/javase/downloads/index.jsp for more information. +# +# Backward compatibility with ESAPI Java 1.4 is supported by the two deprecated API +# methods, Encryptor.encrypt(String) and Encryptor.decrypt(String). However, whenever +# possible, these methods should be avoided as they use ECB cipher mode, which in almost +# all circumstances a poor choice because of it's weakness. CBC cipher mode is the default +# for the new Encryptor encrypt / decrypt methods for ESAPI Java 2.0. In general, you +# should only use this compatibility setting if you have persistent data encrypted with +# version 1.4 and even then, you should ONLY set this compatibility mode UNTIL +# you have decrypted all of your old encrypted data and then re-encrypted it with +# ESAPI 2.0 using CBC mode. If you have some reason to mix the deprecated 1.4 mode +# with the new 2.0 methods, make sure that you use the same cipher algorithm for both +# (256-bit AES was the default for 1.4; 128-bit is the default for 2.0; see below for +# more details.) Otherwise, you will have to use the new 2.0 encrypt / decrypt methods +# where you can specify a SecretKey. (Note that if you are using the 256-bit AES, +# that requires downloading the special jurisdiction policy files mentioned above.) +# +# ***** IMPORTANT: Do NOT forget to replace these with your own values! ***** +# To calculate these values, you can run: +# java -classpath esapi.jar org.owasp.esapi.reference.crypto.JavaEncryptor +# +#Encryptor.MasterKey= +#Encryptor.MasterSalt= + +# Provides the default JCE provider that ESAPI will "prefer" for its symmetric +# encryption and hashing. (That is it will look to this provider first, but it +# will defer to other providers if the requested algorithm is not implemented +# by this provider.) If left unset, ESAPI will just use your Java VM's current +# preferred JCE provider, which is generally set in the file +# "$JAVA_HOME/jre/lib/security/java.security". +# +# The main intent of this is to allow ESAPI symmetric encryption to be +# used with a FIPS 140-2 compliant crypto-module. For details, see the section +# "Using ESAPI Symmetric Encryption with FIPS 140-2 Cryptographic Modules" in +# the ESAPI 2.0 Symmetric Encryption User Guide, at: +# http://owasp-esapi-java.googlecode.com/svn/trunk/documentation/esapi4java-core-2.0-symmetric-crypto-user-guide.html +# However, this property also allows you to easily use an alternate JCE provider +# such as "Bouncy Castle" without having to make changes to "java.security". +# See Javadoc for SecurityProviderLoader for further details. If you wish to use +# a provider that is not known to SecurityProviderLoader, you may specify the +# fully-qualified class name of the JCE provider class that implements +# java.security.Provider. If the name contains a '.', this is interpreted as +# a fully-qualified class name that implements java.security.Provider. +# +# NOTE: Setting this property has the side-effect of changing it in your application +# as well, so if you are using JCE in your application directly rather than +# through ESAPI (you wouldn't do that, would you? ;-), it will change the +# preferred JCE provider there as well. +# +# Default: Keeps the JCE provider set to whatever JVM sets it to. +Encryptor.PreferredJCEProvider= + +# AES is the most widely used and strongest encryption algorithm. This +# should agree with your Encryptor.CipherTransformation property. +# By default, ESAPI Java 1.4 uses "PBEWithMD5AndDES" and which is +# very weak. It is essentially a password-based encryption key, hashed +# with MD5 around 1K times and then encrypted with the weak DES algorithm +# (56-bits) using ECB mode and an unspecified padding (it is +# JCE provider specific, but most likely "NoPadding"). However, 2.0 uses +# "AES/CBC/PKCSPadding". If you want to change these, change them here. +# Warning: This property does not control the default reference implementation for +# ESAPI 2.0 using JavaEncryptor. Also, this property will be dropped +# in the future. +# @deprecated +Encryptor.EncryptionAlgorithm=AES +# For ESAPI Java 2.0 - New encrypt / decrypt methods use this. +Encryptor.CipherTransformation=AES/CBC/PKCS5Padding + +# Applies to ESAPI 2.0 and later only! +# Comma-separated list of cipher modes that provide *BOTH* +# confidentiality *AND* message authenticity. (NIST refers to such cipher +# modes as "combined modes" so that's what we shall call them.) If any of these +# cipher modes are used then no MAC is calculated and stored +# in the CipherText upon encryption. Likewise, if one of these +# cipher modes is used with decryption, no attempt will be made +# to validate the MAC contained in the CipherText object regardless +# of whether it contains one or not. Since the expectation is that +# these cipher modes support support message authenticity already, +# injecting a MAC in the CipherText object would be at best redundant. +# +# Note that as of JDK 1.5, the SunJCE provider does not support *any* +# of these cipher modes. Of these listed, only GCM and CCM are currently +# NIST approved. YMMV for other JCE providers. E.g., Bouncy Castle supports +# GCM and CCM with "NoPadding" mode, but not with "PKCS5Padding" or other +# padding modes. +Encryptor.cipher_modes.combined_modes=GCM,CCM,IAPM,EAX,OCB,CWC + +# Applies to ESAPI 2.0 and later only! +# Additional cipher modes allowed for ESAPI 2.0 encryption. These +# cipher modes are in _addition_ to those specified by the property +# 'Encryptor.cipher_modes.combined_modes'. +# Note: We will add support for streaming modes like CFB & OFB once +# we add support for 'specified' to the property 'Encryptor.ChooseIVMethod' +# (probably in ESAPI 2.1). +# DISCUSS: Better name? +Encryptor.cipher_modes.additional_allowed=CBC + +# 128-bit is almost always sufficient and appears to be more resistant to +# related key attacks than is 256-bit AES. Use '_' to use default key size +# for cipher algorithms (where it makes sense because the algorithm supports +# a variable key size). Key length must agree to what's provided as the +# cipher transformation, otherwise this will be ignored after logging a +# warning. +# +# NOTE: This is what applies BOTH ESAPI 1.4 and 2.0. See warning above about mixing! +Encryptor.EncryptionKeyLength=128 + +# Because 2.0 uses CBC mode by default, it requires an initialization vector (IV). +# (All cipher modes except ECB require an IV.) There are two choices: we can either +# use a fixed IV known to both parties or allow ESAPI to choose a random IV. While +# the IV does not need to be hidden from adversaries, it is important that the +# adversary not be allowed to choose it. Also, random IVs are generally much more +# secure than fixed IVs. (In fact, it is essential that feed-back cipher modes +# such as CFB and OFB use a different IV for each encryption with a given key so +# in such cases, random IVs are much preferred. By default, ESAPI 2.0 uses random +# IVs. If you wish to use 'fixed' IVs, set 'Encryptor.ChooseIVMethod=fixed' and +# uncomment the Encryptor.fixedIV. +# +# Valid values: random|fixed|specified 'specified' not yet implemented; planned for 2.1 +Encryptor.ChooseIVMethod=random +# If you choose to use a fixed IV, then you must place a fixed IV here that +# is known to all others who are sharing your secret key. The format should +# be a hex string that is the same length as the cipher block size for the +# cipher algorithm that you are using. The following is an *example* for AES +# from an AES test vector for AES-128/CBC as described in: +# NIST Special Publication 800-38A (2001 Edition) +# "Recommendation for Block Cipher Modes of Operation". +# (Note that the block size for AES is 16 bytes == 128 bits.) +# +Encryptor.fixedIV=0x000102030405060708090a0b0c0d0e0f + +# Whether or not CipherText should use a message authentication code (MAC) with it. +# This prevents an adversary from altering the IV as well as allowing a more +# fool-proof way of determining the decryption failed because of an incorrect +# key being supplied. This refers to the "separate" MAC calculated and stored +# in CipherText, not part of any MAC that is calculated as a result of a +# "combined mode" cipher mode. +# +# If you are using ESAPI with a FIPS 140-2 cryptographic module, you *must* also +# set this property to false. +Encryptor.CipherText.useMAC=true + +# Whether or not the PlainText object may be overwritten and then marked +# eligible for garbage collection. If not set, this is still treated as 'true'. +Encryptor.PlainText.overwrite=true + +# Do not use DES except in a legacy situations. 56-bit is way too small key size. +#Encryptor.EncryptionKeyLength=56 +#Encryptor.EncryptionAlgorithm=DES + +# TripleDES is considered strong enough for most purposes. +# Note: There is also a 112-bit version of DESede. Using the 168-bit version +# requires downloading the special jurisdiction policy from Sun. +#Encryptor.EncryptionKeyLength=168 +#Encryptor.EncryptionAlgorithm=DESede + +Encryptor.HashAlgorithm=SHA-512 +Encryptor.HashIterations=1024 +Encryptor.DigitalSignatureAlgorithm=SHA1withDSA +Encryptor.DigitalSignatureKeyLength=1024 +Encryptor.RandomAlgorithm=SHA1PRNG +Encryptor.CharacterEncoding=UTF-8 + +# This is the Pseudo Random Function (PRF) that ESAPI's Key Derivation Function +# (KDF) normally uses. Note this is *only* the PRF used for ESAPI's KDF and +# *not* what is used for ESAPI's MAC. (Currently, HmacSHA1 is always used for +# the MAC, mostly to keep the overall size at a minimum.) +# +# Currently supported choices for JDK 1.5 and 1.6 are: +# HmacSHA1 (160 bits), HmacSHA256 (256 bits), HmacSHA384 (384 bits), and +# HmacSHA512 (512 bits). +# Note that HmacMD5 is *not* supported for the PRF used by the KDF even though +# the JDKs support it. See the ESAPI 2.0 Symmetric Encryption User Guide +# further details. +Encryptor.KDF.PRF=HmacSHA256 +#=========================================================================== +# ESAPI HttpUtilties +# +# The HttpUtilities provide basic protections to HTTP requests and responses. Primarily these methods +# protect against malicious data from attackers, such as unprintable characters, escaped characters, +# and other simple attacks. The HttpUtilities also provides utility methods for dealing with cookies, +# headers, and CSRF tokens. +# +# Default file upload location (remember to escape backslashes with \\) +HttpUtilities.UploadDir=C:\\ESAPI\\testUpload +HttpUtilities.UploadTempDir=C:\\temp +# Force flags on cookies, if you use HttpUtilities to set cookies +HttpUtilities.ForceHttpOnlySession=false +HttpUtilities.ForceSecureSession=false +HttpUtilities.ForceHttpOnlyCookies=true +HttpUtilities.ForceSecureCookies=true +# Maximum size of HTTP headers +HttpUtilities.MaxHeaderSize=4096 +# File upload configuration +HttpUtilities.ApprovedUploadExtensions=.zip,.pdf,.doc,.docx,.ppt,.pptx,.tar,.gz,.tgz,.rar,.war,.jar,.ear,.xls,.rtf,.properties,.java,.class,.txt,.xml,.jsp,.jsf,.exe,.dll +HttpUtilities.MaxUploadFileBytes=500000000 +# Using UTF-8 throughout your stack is highly recommended. That includes your database driver, +# container, and any other technologies you may be using. Failure to do this may expose you +# to Unicode transcoding injection attacks. Use of UTF-8 does not hinder internationalization. +HttpUtilities.ResponseContentType=text/html; charset=UTF-8 +# This is the name of the cookie used to represent the HTTP session +# Typically this will be the default "JSESSIONID" +HttpUtilities.HttpSessionIdName=JSESSIONID + + + +#=========================================================================== +# ESAPI Executor +# CHECKME - This should be made OS independent. Don't use unsafe defaults. +# # Examples only -- do NOT blindly copy! +# For Windows: +# Executor.WorkingDirectory=C:\\Windows\\Temp +# Executor.ApprovedExecutables=C:\\Windows\\System32\\cmd.exe,C:\\Windows\\System32\\runas.exe +# For *nux, MacOS: +# Executor.WorkingDirectory=/tmp +# Executor.ApprovedExecutables=/bin/bash +Executor.WorkingDirectory= +Executor.ApprovedExecutables= + + +#=========================================================================== +# ESAPI Logging +# Set the application name if these logs are combined with other applications +Logger.ApplicationName=ExampleApplication +# If you use an HTML log viewer that does not properly HTML escape log data, you can set LogEncodingRequired to true +Logger.LogEncodingRequired=false +# Determines whether ESAPI should log the application name. This might be clutter in some single-server/single-app environments. +Logger.LogApplicationName=true +# Determines whether ESAPI should log the server IP and port. This might be clutter in some single-server environments. +Logger.LogServerIP=true +# LogFileName, the name of the logging file. Provide a full directory path (e.g., C:\\ESAPI\\ESAPI_logging_file) if you +# want to place it in a specific directory. +Logger.LogFileName=ESAPI_logging_file +# MaxLogFileSize, the max size (in bytes) of a single log file before it cuts over to a new one (default is 10,000,000) +Logger.MaxLogFileSize=10000000 + + +#=========================================================================== +# ESAPI Intrusion Detection +# +# Each event has a base to which .count, .interval, and .action are added +# The IntrusionException will fire if we receive "count" events within "interval" seconds +# The IntrusionDetector is configurable to take the following actions: log, logout, and disable +# (multiple actions separated by commas are allowed e.g. event.test.actions=log,disable +# +# Custom Events +# Names must start with "event." as the base +# Use IntrusionDetector.addEvent( "test" ) in your code to trigger "event.test" here +# You can also disable intrusion detection completely by changing +# the following parameter to true +# +IntrusionDetector.Disable=false +# +IntrusionDetector.event.test.count=2 +IntrusionDetector.event.test.interval=10 +IntrusionDetector.event.test.actions=disable,log + +# Exception Events +# All EnterpriseSecurityExceptions are registered automatically +# Call IntrusionDetector.getInstance().addException(e) for Exceptions that do not extend EnterpriseSecurityException +# Use the fully qualified classname of the exception as the base + +# any intrusion is an attack +IntrusionDetector.org.owasp.esapi.errors.IntrusionException.count=1 +IntrusionDetector.org.owasp.esapi.errors.IntrusionException.interval=1 +IntrusionDetector.org.owasp.esapi.errors.IntrusionException.actions=log,disable,logout + +# for test purposes +# CHECKME: Shouldn't there be something in the property name itself that designates +# that these are for testing??? +IntrusionDetector.org.owasp.esapi.errors.IntegrityException.count=10 +IntrusionDetector.org.owasp.esapi.errors.IntegrityException.interval=5 +IntrusionDetector.org.owasp.esapi.errors.IntegrityException.actions=log,disable,logout + +# rapid validation errors indicate scans or attacks in progress +# org.owasp.esapi.errors.ValidationException.count=10 +# org.owasp.esapi.errors.ValidationException.interval=10 +# org.owasp.esapi.errors.ValidationException.actions=log,logout + +# sessions jumping between hosts indicates session hijacking +IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.count=2 +IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.interval=10 +IntrusionDetector.org.owasp.esapi.errors.AuthenticationHostException.actions=log,logout + + +#=========================================================================== +# ESAPI Validation +# +# The ESAPI Validator works on regular expressions with defined names. You can define names +# either here, or you may define application specific patterns in a separate file defined below. +# This allows enterprises to specify both organizational standards as well as application specific +# validation rules. +# +# Use '\p{L}' (without the quotes) within the character class to match +# any Unicode LETTER. You can also use a range, like: \u00C0-\u017F +# You can also use any of the regex flags as documented at +# https://docs.oracle.com/javase/tutorial/essential/regex/pattern.html, e.g. (?u) +# +Validator.ConfigurationFile=validation.properties + +# Validators used by ESAPI +Validator.AccountName=^[a-zA-Z0-9]{3,20}$ +Validator.SystemCommand=^[a-zA-Z\\-\\/]{1,64}$ +Validator.RoleName=^[a-z]{1,20}$ + +#the word TEST below should be changed to your application +#name - only relative URL's are supported +Validator.Redirect=^\\/test.*$ + +# Global HTTP Validation Rules +# Values with Base64 encoded data (e.g. encrypted state) will need at least [a-zA-Z0-9\/+=] +Validator.HTTPScheme=^(http|https)$ +Validator.HTTPServerName=^[a-zA-Z0-9_.\\-]*$ +Validator.HTTPParameterName=^[a-zA-Z0-9_]{1,32}$ +Validator.HTTPParameterValue=^[a-zA-Z0-9.\\-\\/+=@_ ]*$ +Validator.HTTPCookieName=^[a-zA-Z0-9\\-_]{1,32}$ +Validator.HTTPCookieValue=^[a-zA-Z0-9\\-\\/+=_ ]*$ +# Note that max header name capped at 150 in SecurityRequestWrapper! +Validator.HTTPHeaderName=^[a-zA-Z0-9\\-_]{1,50}$ +Validator.HTTPHeaderValue=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ +Validator.HTTPContextPath=^\\/?[a-zA-Z0-9.\\-\\/_]*$ +Validator.HTTPServletPath=^[a-zA-Z0-9.\\-\\/_]*$ +Validator.HTTPPath=^[a-zA-Z0-9.\\-_]*$ +Validator.HTTPQueryString=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ %]*$ +Validator.HTTPURI=^[a-zA-Z0-9()\\-=\\*\\.\\?;,+\\/:&_ ]*$ +Validator.HTTPURL=^.*$ +Validator.HTTPJSESSIONID=^[A-Z0-9]{10,30}$ + +# Validation of file related input +Validator.FileName=^[a-zA-Z0-9!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$ +Validator.DirectoryName=^[a-zA-Z0-9:/\\\\!@#$%^&{}\\[\\]()_+\\-=,.~'` ]{1,255}$ + +# Validation of dates. Controls whether or not 'lenient' dates are accepted. +# See DataFormat.setLenient(boolean flag) for further details. +Validator.AcceptLenientDates=false diff --git a/fineract-provider/src/main/resources/validation.properties b/fineract-provider/src/main/resources/validation.properties new file mode 100644 index 000000000..75af7659f --- /dev/null +++ b/fineract-provider/src/main/resources/validation.properties @@ -0,0 +1,47 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# The ESAPI validator does many security checks on input, such as canonicalization +# and whitelist validation. Note that all of these validation rules are applied *after* +# canonicalization. Double-encoded characters (even with different encodings involved, +# are never allowed. +# +# To use: +# +# First set up a pattern below. You can choose any name you want, prefixed by the word +# "Validation." For example: +# Validation.Email=^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,4}$ +# +# Then you can validate in your code against the pattern like this: +# ESAPI.validator().isValidInput("User Email", input, "Email", maxLength, allowNull); +# Where maxLength and allowNull are set for you needs, respectively. +# +# But note, when you use boolean variants of validation functions, you lose critical +# canonicalization. It is preferable to use the "get" methods (which throw exceptions) and +# and use the returned user input which is in canonical form. Consider the following: +# +# try { +# someObject.setEmail(ESAPI.validator().getValidInput("User Email", input, "Email", maxLength, allowNull)); +# +Validator.SafeString=^[.\\p{Alnum}\\p{Space}]{0,1024}$ +Validator.Email=^[A-Za-z0-9._%'-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,4}$ +Validator.IPAddress=^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ +Validator.URL=^(ht|f)tp(s?)\\:\\/\\/[0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*(:(0-9)*)*(\\/?)([a-zA-Z0-9\\-\\.\\?\\,\\:\\'\\/\\\\\\+=&%\\$#_]*)?$ +Validator.CreditCard=^(\\d{4}[- ]?){3}\\d{4}$ +Validator.SSN=^(?!000)([0-6]\\d{2}|7([0-6]\\d|7[012]))([ -]?)(?!00)\\d\\d\\3(?!0000)\\d{4}$