CVE-2018-1322
Description
Apache Syncope administrator with search entitlements can recover sensitive security values via fiql and orderby parameters.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache Syncope administrator with search entitlements can recover sensitive security values via fiql and orderby parameters.
Vulnerability
An information disclosure vulnerability exists in Apache Syncope 1.2.x before 1.2.11, 2.0.x before 2.0.8, and unsupported releases 1.0.x and 1.1.x [1][2]. An administrator with user search entitlements can use the fiql and orderby parameters to recover sensitive security values that should not be searchable or sortable [1][2]. The affected code did not properly filter fields such as securityAnswer, token, and tokenExpireTime from search and sort operations [3][4].
Exploitation
An authenticated attacker who is an administrator with user search entitlements can craft malicious fiql or orderby queries to access sensitive security fields [1][2]. The attacker does not need any additional privileges beyond the standard search entitlements. By using the fiql parameter to search on sensitive fields or the orderby parameter to sort results by those fields, the attacker can extract the values [3][4].
Impact
Successful exploitation allows the attacker to disclose sensitive security values, such as security answers, tokens, and token expiration times, for users in the Apache Syncope system [3][4]. This constitutes a confidentiality breach of sensitive authentication-related data. The attacker may use this information for further attacks, such as account takeover or privilege escalation.
Mitigation
Apache Syncope 1.2.11, 2.0.8, and later versions fix this issue by restricting the fields usable for search and sorting [1]. The fix adds securityAnswer, token, tokenExpireTime, and other sensitive fields to the list of attributes not included in searchable fields and order-by clauses [3][4]. Users should upgrade to the patched versions immediately [1]. No workaround is available, and unsupported releases (1.0.x, 1.1.x) are also affected but will not receive patches [1].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.syncope:syncope-coreMaven | < 1.2.11 | 1.2.11 |
org.apache.syncope:syncope-coreMaven | >= 2.0.0, < 2.0.8 | 2.0.8 |
Affected products
2- Apache Software Foundation/Apache Syncopev5Range: Releases prior to 1.2.11, Releases prior to 2.0.8
Patches
444a5ca0fbd35Review fields usable for search and orderBy
3 files changed · +36 −3
common/src/main/java/org/apache/syncope/common/search/SearchableFields.java+1 −1 modified@@ -33,7 +33,7 @@ public class SearchableFields { protected static final String[] ATTRIBUTES_NOTINCLUDED = { "attrs", "derAttrs", "virAttrs", "serialVersionUID", "memberships", "entitlements", "resources", "password", - "propagationTOs", "propagationStatusMap" + "propagationTOs", "propagationStatusMap", "securityAnswer", "token", "tokenExpireTime" }; public static final List<String> get(final SubjectType subjectType) {
core/src/main/java/org/apache/syncope/core/persistence/dao/impl/SubjectSearchDAOImpl.java+18 −2 modified@@ -63,6 +63,10 @@ public class SubjectSearchDAOImpl extends AbstractDAOImpl implements SubjectSear private static final String[] SUBJECT_FIELDS = new String[] { "parent", "userOwner", "roleOwner" }; + private static final String[] ORDER_BY_NOT_ALLOWED = { + "serialVersionUID", "password", "securityQuestion", "securityAnswer", "token", "tokenExpireTime" + }; + @Autowired private UserDAO userDAO; @@ -285,12 +289,24 @@ private StringBuilder buildOrderBy(final OrderBySupport obs) { return orderBy; } - private OrderBySupport parseOrderBy(final SearchSupport svs, final List<OrderByClause> orderByClauses) { + protected List<OrderByClause> filterOrderBy(final List<OrderByClause> orderBy) { + List<OrderByClause> result = new ArrayList<OrderByClause>(); + + for (OrderByClause clause : orderBy) { + if (!ArrayUtils.contains(ORDER_BY_NOT_ALLOWED, clause.getField())) { + result.add(clause); + } + } + + return result; + } + + private OrderBySupport parseOrderBy(final SearchSupport svs, final List<OrderByClause> orderBy) { final AttributableUtil attrUtil = AttributableUtil.getInstance(svs.type.asAttributableType()); OrderBySupport obs = new OrderBySupport(); - for (OrderByClause clause : orderByClauses) { + for (OrderByClause clause : filterOrderBy(orderBy)) { OrderBySupport.Item item = new OrderBySupport.Item(); Field subjectField = ReflectionUtils.findField(attrUtil.attributableClass(), clause.getField());
core/src/test/java/org/apache/syncope/core/rest/SearchTestITCase.java+17 −0 modified@@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.syncope.client.SyncopeClient; import org.apache.syncope.common.reqres.PagedResult; import org.apache.syncope.common.services.UserSelfService; @@ -199,6 +200,22 @@ public void nested() { } } + @Test + public void searchBySecurityAnswer() { + String securityAnswer = RandomStringUtils.randomAlphanumeric(10); + UserTO userTO = UserTestITCase.getUniqueSampleTO("securityAnswer@syncope.apache.org"); + userTO.setSecurityQuestion(1L); + userTO.setSecurityAnswer(securityAnswer); + + userTO = createUser(userTO); + assertNotNull(userTO.getSecurityQuestion()); + + PagedResult<UserTO> matchingUsers = userService.search(SyncopeClient.getUserSearchConditionBuilder(). + is("securityAnswer").equalTo(securityAnswer).query()); + assertNotNull(matchingUsers); + assertTrue(matchingUsers.getResult().isEmpty()); + } + @Test public void orderBy() { PagedResult<UserTO> users = userService.search(
44a5ca0fbd35Review fields usable for search and orderBy
3 files changed · +36 −3
common/src/main/java/org/apache/syncope/common/search/SearchableFields.java+1 −1 modified@@ -33,7 +33,7 @@ public class SearchableFields { protected static final String[] ATTRIBUTES_NOTINCLUDED = { "attrs", "derAttrs", "virAttrs", "serialVersionUID", "memberships", "entitlements", "resources", "password", - "propagationTOs", "propagationStatusMap" + "propagationTOs", "propagationStatusMap", "securityAnswer", "token", "tokenExpireTime" }; public static final List<String> get(final SubjectType subjectType) {
core/src/main/java/org/apache/syncope/core/persistence/dao/impl/SubjectSearchDAOImpl.java+18 −2 modified@@ -63,6 +63,10 @@ public class SubjectSearchDAOImpl extends AbstractDAOImpl implements SubjectSear private static final String[] SUBJECT_FIELDS = new String[] { "parent", "userOwner", "roleOwner" }; + private static final String[] ORDER_BY_NOT_ALLOWED = { + "serialVersionUID", "password", "securityQuestion", "securityAnswer", "token", "tokenExpireTime" + }; + @Autowired private UserDAO userDAO; @@ -285,12 +289,24 @@ private StringBuilder buildOrderBy(final OrderBySupport obs) { return orderBy; } - private OrderBySupport parseOrderBy(final SearchSupport svs, final List<OrderByClause> orderByClauses) { + protected List<OrderByClause> filterOrderBy(final List<OrderByClause> orderBy) { + List<OrderByClause> result = new ArrayList<OrderByClause>(); + + for (OrderByClause clause : orderBy) { + if (!ArrayUtils.contains(ORDER_BY_NOT_ALLOWED, clause.getField())) { + result.add(clause); + } + } + + return result; + } + + private OrderBySupport parseOrderBy(final SearchSupport svs, final List<OrderByClause> orderBy) { final AttributableUtil attrUtil = AttributableUtil.getInstance(svs.type.asAttributableType()); OrderBySupport obs = new OrderBySupport(); - for (OrderByClause clause : orderByClauses) { + for (OrderByClause clause : filterOrderBy(orderBy)) { OrderBySupport.Item item = new OrderBySupport.Item(); Field subjectField = ReflectionUtils.findField(attrUtil.attributableClass(), clause.getField());
core/src/test/java/org/apache/syncope/core/rest/SearchTestITCase.java+17 −0 modified@@ -27,6 +27,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.syncope.client.SyncopeClient; import org.apache.syncope.common.reqres.PagedResult; import org.apache.syncope.common.services.UserSelfService; @@ -199,6 +200,22 @@ public void nested() { } } + @Test + public void searchBySecurityAnswer() { + String securityAnswer = RandomStringUtils.randomAlphanumeric(10); + UserTO userTO = UserTestITCase.getUniqueSampleTO("securityAnswer@syncope.apache.org"); + userTO.setSecurityQuestion(1L); + userTO.setSecurityAnswer(securityAnswer); + + userTO = createUser(userTO); + assertNotNull(userTO.getSecurityQuestion()); + + PagedResult<UserTO> matchingUsers = userService.search(SyncopeClient.getUserSearchConditionBuilder(). + is("securityAnswer").equalTo(securityAnswer).query()); + assertNotNull(matchingUsers); + assertTrue(matchingUsers.getResult().isEmpty()); + } + @Test public void orderBy() { PagedResult<UserTO> users = userService.search(
735579b6f987Review fields usable for search and orderBy
5 files changed · +40 −4
common/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java+1 −1 modified@@ -36,7 +36,7 @@ public final class SearchableFields { private static final String[] ATTRIBUTES_NOTINCLUDED = { - "serialVersionUID", "password", "type", "udynMembershipCond" + "serialVersionUID", "password", "type", "udynMembershipCond", "securityAnswer", "token", "tokenExpireTime" }; private static final Set<String> ANY_FIELDS = new HashSet<>();
core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java+17 −0 modified@@ -30,7 +30,10 @@ import javax.validation.constraints.Max; import javax.validation.constraints.Min; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.Predicate; import org.apache.commons.collections4.Transformer; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.tuple.Pair; @@ -68,6 +71,10 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implements AnySearchDAO { + private static final String[] ORDER_BY_NOT_ALLOWED = { + "serialVersionUID", "password", "securityQuestion", "securityAnswer", "token", "tokenExpireTime" + }; + @Autowired protected RealmDAO realmDAO; @@ -134,6 +141,16 @@ public <T extends Any<?>> List<T> search( return search(SyncopeConstants.FULL_ADMIN_REALMS, cond, -1, -1, orderBy, kind); } + protected List<OrderByClause> filterOrderBy(final List<OrderByClause> orderBy) { + return ListUtils.select(orderBy, new Predicate<OrderByClause>() { + + @Override + public boolean evaluate(final OrderByClause clause) { + return !ArrayUtils.contains(ORDER_BY_NOT_ALLOWED, clause.getField()); + } + }); + } + protected abstract <T extends Any<?>> List<T> doSearch( Set<String> adminRealms, SearchCond searchCondition,
core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java+2 −2 modified@@ -276,13 +276,13 @@ private StringBuilder buildOrderBy(final OrderBySupport obs) { } private OrderBySupport parseOrderBy( - final AnyTypeKind kind, final SearchSupport svs, final List<OrderByClause> orderByClauses) { + final AnyTypeKind kind, final SearchSupport svs, final List<OrderByClause> orderBy) { AnyUtils attrUtils = anyUtilsFactory.getInstance(kind); OrderBySupport obs = new OrderBySupport(); - for (OrderByClause clause : orderByClauses) { + for (OrderByClause clause : filterOrderBy(orderBy)) { OrderBySupport.Item item = new OrderBySupport.Item(); // Manage difference among external key attribute and internal JPA @Id
ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java+1 −1 modified@@ -147,7 +147,7 @@ private void addSort( AnyUtils attrUtils = anyUtilsFactory.getInstance(kind); - for (OrderByClause clause : orderBy) { + for (OrderByClause clause : filterOrderBy(orderBy)) { String sortName = null; // Manage difference among external key attribute and internal JPA @Id
fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java+19 −0 modified@@ -29,6 +29,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.Predicate; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.patch.AnyObjectPatch; @@ -406,6 +407,24 @@ public boolean evaluate(final UserTO user) { })); } + @Test + public void searchBySecurityAnswer() { + String securityAnswer = RandomStringUtils.randomAlphanumeric(10); + UserTO userTO = UserITCase.getUniqueSampleTO("securityAnswer@syncope.apache.org"); + userTO.setSecurityQuestion("887028ea-66fc-41e7-b397-620d7ea6dfbb"); + userTO.setSecurityAnswer(securityAnswer); + + userTO = createUser(userTO).getEntity(); + assertNotNull(userTO.getSecurityQuestion()); + + PagedResult<UserTO> matchingUsers = userService.search( + new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM). + fiql(SyncopeClient.getUserSearchConditionBuilder(). + is("securityAnswer").equalTo(securityAnswer).query()).build()); + assertNotNull(matchingUsers); + assertTrue(matchingUsers.getResult().isEmpty()); + } + @Test public void assignable() { PagedResult<GroupTO> groups = groupService.search(new AnyQuery.Builder().realm("/even/two").page(1).size(1000).
735579b6f987Review fields usable for search and orderBy
5 files changed · +40 −4
common/lib/src/main/java/org/apache/syncope/common/lib/search/SearchableFields.java+1 −1 modified@@ -36,7 +36,7 @@ public final class SearchableFields { private static final String[] ATTRIBUTES_NOTINCLUDED = { - "serialVersionUID", "password", "type", "udynMembershipCond" + "serialVersionUID", "password", "type", "udynMembershipCond", "securityAnswer", "token", "tokenExpireTime" }; private static final Set<String> ANY_FIELDS = new HashSet<>();
core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractAnySearchDAO.java+17 −0 modified@@ -30,7 +30,10 @@ import javax.validation.constraints.Max; import javax.validation.constraints.Min; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.collections4.Predicate; import org.apache.commons.collections4.Transformer; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.tuple.Pair; @@ -68,6 +71,10 @@ public abstract class AbstractAnySearchDAO extends AbstractDAO<Any<?>> implements AnySearchDAO { + private static final String[] ORDER_BY_NOT_ALLOWED = { + "serialVersionUID", "password", "securityQuestion", "securityAnswer", "token", "tokenExpireTime" + }; + @Autowired protected RealmDAO realmDAO; @@ -134,6 +141,16 @@ public <T extends Any<?>> List<T> search( return search(SyncopeConstants.FULL_ADMIN_REALMS, cond, -1, -1, orderBy, kind); } + protected List<OrderByClause> filterOrderBy(final List<OrderByClause> orderBy) { + return ListUtils.select(orderBy, new Predicate<OrderByClause>() { + + @Override + public boolean evaluate(final OrderByClause clause) { + return !ArrayUtils.contains(ORDER_BY_NOT_ALLOWED, clause.getField()); + } + }); + } + protected abstract <T extends Any<?>> List<T> doSearch( Set<String> adminRealms, SearchCond searchCondition,
core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAAnySearchDAO.java+2 −2 modified@@ -276,13 +276,13 @@ private StringBuilder buildOrderBy(final OrderBySupport obs) { } private OrderBySupport parseOrderBy( - final AnyTypeKind kind, final SearchSupport svs, final List<OrderByClause> orderByClauses) { + final AnyTypeKind kind, final SearchSupport svs, final List<OrderByClause> orderBy) { AnyUtils attrUtils = anyUtilsFactory.getInstance(kind); OrderBySupport obs = new OrderBySupport(); - for (OrderByClause clause : orderByClauses) { + for (OrderByClause clause : filterOrderBy(orderBy)) { OrderBySupport.Item item = new OrderBySupport.Item(); // Manage difference among external key attribute and internal JPA @Id
ext/elasticsearch/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/ElasticsearchAnySearchDAO.java+1 −1 modified@@ -147,7 +147,7 @@ private void addSort( AnyUtils attrUtils = anyUtilsFactory.getInstance(kind); - for (OrderByClause clause : orderBy) { + for (OrderByClause clause : filterOrderBy(orderBy)) { String sortName = null; // Manage difference among external key attribute and internal JPA @Id
fit/core-reference/src/test/java/org/apache/syncope/fit/core/SearchITCase.java+19 −0 modified@@ -29,6 +29,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.Predicate; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.patch.AnyObjectPatch; @@ -406,6 +407,24 @@ public boolean evaluate(final UserTO user) { })); } + @Test + public void searchBySecurityAnswer() { + String securityAnswer = RandomStringUtils.randomAlphanumeric(10); + UserTO userTO = UserITCase.getUniqueSampleTO("securityAnswer@syncope.apache.org"); + userTO.setSecurityQuestion("887028ea-66fc-41e7-b397-620d7ea6dfbb"); + userTO.setSecurityAnswer(securityAnswer); + + userTO = createUser(userTO).getEntity(); + assertNotNull(userTO.getSecurityQuestion()); + + PagedResult<UserTO> matchingUsers = userService.search( + new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM). + fiql(SyncopeClient.getUserSearchConditionBuilder(). + is("securityAnswer").equalTo(securityAnswer).query()).build()); + assertNotNull(matchingUsers); + assertTrue(matchingUsers.getResult().isEmpty()); + } + @Test public void assignable() { PagedResult<GroupTO> groups = groupService.search(new AnyQuery.Builder().realm("/even/two").page(1).size(1000).
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- www.exploit-db.com/exploits/45400/mitreexploitx_refsource_EXPLOIT-DB
- github.com/advisories/GHSA-v3vf-2r98-xw8wghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-1322ghsaADVISORY
- syncope.apache.org/security.htmlghsax_refsource_MISCWEB
- www.securityfocus.com/bid/103507ghsavdb-entryx_refsource_BIDWEB
- github.com/apache/syncope/commit/44a5ca0fbd357b8b5d81aa9313fb01cca30d8adghsaWEB
- github.com/apache/syncope/commit/735579b6f987b407049ac1f1da08e675d957c3eghsaWEB
- www.exploit-db.com/exploits/45400ghsaWEB
News mentions
0No linked articles in our index yet.