Chybeta

Nexus Repository Manager 3 RCE 分析 -【CVE-2019-7238】

中文版本:chinese edition

Summary

https://support.sonatype.com/hc/en-us/articles/360017310793-CVE-2019-7238-Nexus-Repository-Manager-3-Missing-Access-Controls-and-Remote-Code-Execution-February-5th-2019

Affected Versions: Nexus Repository Manager 3.6.2 OSS/Pro versions up to and including 3.14.0

Fixed in Version: Nexus Repository Manager OSS/Pro version 3.15.0

Nice find from Rico @ Tencent Security Yunding Lab and voidfyoo @ Chaitin Tech

Analysis

In plugins/nexus-coreui-plugin/src/main/java/org/sonatype/nexus/coreui/ComponentComponent.groovy:185

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Named
@Singleton
@DirectAction(action = 'coreui_Component')
class ComponentComponent
extends DirectComponentSupport
{
...
@DirectMethod
@Timed
@ExceptionMetered
PagedResponse<AssetXO> previewAssets(final StoreLoadParameters parameters) {
String repositoryName = parameters.getFilter('repositoryName')
String expression = parameters.getFilter('expression')
String type = parameters.getFilter('type')
// get three parameters repositoryName 、 expression 、 type
if (!expression || !type || !repositoryName) {
return null
}
// set the repositoryName
RepositorySelector repositorySelector = RepositorySelector.fromSelector(repositoryName)
// according the type to get validator
if (type == JexlSelector.TYPE) {
jexlExpressionValidator.validate(expression)
}
else if (type == CselSelector.TYPE) {
cselExpressionValidator.validate(expression)
}
List<Repository> selectedRepositories = getPreviewRepositories(repositorySelector)
if (!selectedRepositories.size()) {
return null
}
def result = browseService.previewAssets(
repositorySelector,
selectedRepositories,
expression,
toQueryOptions(parameters))
return new PagedResponse<AssetXO>(
result.total,
result.results.collect(ASSET_CONVERTER.rcurry(null, null, [:], 0)) // buckets not needed for asset preview screen
)
}
...
}

Nexus introduced CSEL based selectors to support changes coming in future releases. CSEL is a light version of JEXL used to script queries along specific paths and coordinates available to your repository manager formats. Step in browseService.previewAssets,and its implementations in components/nexus-repository/src/main/java/org/sonatype/nexus/repository/browse/internal/BrowseServiceImpl.java:233

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Named
@Singleton
public class BrowseServiceImpl
extends ComponentSupport
implements BrowseService
{
...
@Override
public BrowseResult<Asset> previewAssets(final RepositorySelector repositorySelector,
final List<Repository> repositories,
final String jexlExpression,
final QueryOptions queryOptions)
{
checkNotNull(repositories);
checkNotNull(jexlExpression);
final Repository repository = repositories.get(0);
try (StorageTx storageTx = repository.facet(StorageFacet.class).txSupplier().get()) {
storageTx.begin();
List<Repository> previewRepositories;
if (repositories.size() == 1 && groupType.equals(repository.getType())) {
previewRepositories = repository.facet(GroupFacet.class).leafMembers();
}
else {
previewRepositories = repositories;
}
PreviewAssetsSqlBuilder builder = new PreviewAssetsSqlBuilder(
repositorySelector,
jexlExpression,
queryOptions,
getRepoToContainedGroupMap(repositories));
String whereClause = String.format("and (%s)", builder.buildWhereClause());
//The whereClause is passed in as the querySuffix so that contentExpression will run after repository filtering
return new BrowseResult<>(
storageTx.countAssets(null, builder.buildSqlParams(), previewRepositories, whereClause),
Lists.newArrayList(storageTx.findAssets(null, builder.buildSqlParams(),
previewRepositories, whereClause + builder.buildQuerySuffix()))
);
}
}
...
}

Pay attention to the comment: whereClause will run after repository filtering! We need to know how it is constructed. In the components/nexus-repository/src/main/java/org/sonatype/nexus/repository/browse/internal/PreviewAssetsSqlBuilder.java:51 , which introduce contentExpression and jexlExpression:

1
2
3
4
5
6
7
8
9
public class PreviewAssetsSqlBuilder
{
...
public String buildWhereClause() {
return whereClause("contentExpression(@this, :jexlExpression, :repositorySelector, " +
":repoToContainedGroupMap) == true", queryOptions.getFilter() != null);
}
...
}

So after repository filtering,whereClause will run automatically which call contentExpression.execute() method 。In components/nexus-repository/src/main/java/org/sonatype/nexus/repository/selector/internal/ContentExpressionFunction.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ContentExpressionFunction
extends OSQLFunctionAbstract
{
public static final String NAME = "contentExpression";
...
@Inject
public ContentExpressionFunction(final VariableResolverAdapterManager variableResolverAdapterManager,
final SelectorManager selectorManager,
final ContentAuthHelper contentAuthHelper)
{
super(NAME, 4, 4);
this.variableResolverAdapterManager = checkNotNull(variableResolverAdapterManager);
this.selectorManager = checkNotNull(selectorManager);
this.contentAuthHelper = checkNotNull(contentAuthHelper);
}
@Override
public Object execute(final Object iThis,
final OIdentifiable iCurrentRecord,
final Object iCurrentResult,
final Object[] iParams,
final OCommandContext iContext)
{
OIdentifiable identifiable = (OIdentifiable) iParams[0];
// asset
ODocument asset = identifiable.getRecord();
RepositorySelector repositorySelector = RepositorySelector.fromSelector((String) iParams[2]);
// jexlExpression 即 iParams[1]
String jexlExpression = (String) iParams[1];
List<String> membersForAuth;
...
return contentAuthHelper.checkAssetPermissions(asset, membersForAuth.toArray(new String[membersForAuth.size()])) &&
checkJexlExpression(asset, jexlExpression, asset.field(AssetEntityAdapter.P_FORMAT, String.class));
}

According to the code contentExpression(@this, :jexlExpression, :repositorySelector, " +":repoToContainedGroupMap) == true , you can map contentExpression parameters to iParams[i]:

  • @this -> iParams[0]
  • jexlExpression -> iParams[1]
  • repositorySelector -> iParams[2]

In last, it will call checkJexlExpression() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
...
private boolean checkJexlExpression(final ODocument asset,
final String jexlExpression,
final String format)
{
VariableResolverAdapter variableResolverAdapter = variableResolverAdapterManager.get(format);
VariableSource variableSource = variableResolverAdapter.fromDocument(asset);
SelectorConfiguration selectorConfiguration = new SelectorConfiguration();
selectorConfiguration.setAttributes(ImmutableMap.of("expression", jexlExpression));
// JexlSelector.TYPE which is defined as 'jexl'
selectorConfiguration.setType(JexlSelector.TYPE);
selectorConfiguration.setName("preview");
try {
// evaluate!!!
return selectorManager.evaluate(selectorConfiguration, variableSource);
}
catch (SelectorEvaluationException e) {
log.debug("Unable to evaluate expression {}.", jexlExpression, e);
return false;
}
}
}

So, we can step in selectorManager.evaluate,which is implemented in components/nexus-core/src/main/java/org/sonatype/nexus/internal/selector/SelectorManagerImpl.java:156 ,and finally evaluate the expression:

  @Override
  @Guarded(by = STARTED)
  public boolean evaluate(final SelectorConfiguration selectorConfiguration, final VariableSource variableSource)
      throws SelectorEvaluationException
  {

    Selector selector = createSelector(selectorConfiguration);

    try {

      return selector.evaluate(variableSource);
    }
    catch (Exception e) {
      throw new SelectorEvaluationException("Selector '" + selectorConfiguration.getName() + "' evaluation in error",
          e);
    }
  }

Reproducible steps

According to DOCS:
https://help.sonatype.com/repomanager3/configuration/repository-management#RepositoryManagement-CreatingaQuery

To reproduce the issue successfully, we need upload some assets to the repo firstly。For excample, upload a jar:

Then go here to intercept the request:

POC:

Fix

Add the permission requirement: @RequiresPermissions('nexus:selectors:*')

点击赞赏二维码,您的支持将鼓励我继续创作!
chybeta WeChat Pay

微信打赏

chybeta Alipay

支付宝打赏

本文标题:Nexus Repository Manager 3 RCE 分析 -【CVE-2019-7238】

文章作者:chybeta

发布时间:2019年02月18日 - 21:02

最后更新:2019年02月20日 - 10:02

原始链接:http://chybeta.github.io/2019/02/18/Nexus-Repository-Manager-3-RCE-分析-【CVE-2019-7238】/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。