CVE: CVE-2021-0218

Tested Versions:

  • Junos OS 20.1R1.11

Product URL(s):

Description of the vulnerability

license-check is a daemon to manage license in Juniper device. By default, this daemon is running as root. There is a command injection vulnerability in license-check daemon that allows an attacker with low privilege to execute a command with root privilege.

The command injection exists in the license update feature. To update license, user run command request system license update in cli console. First, when run this command, the mgd_update_license() function in /usr/lib/dd/libjunos-actions-impl.so is called.

int __cdecl mgd_update_license(int a1)
{
 
  v1 = *(_DWORD *)(*(_DWORD *)(a1 + 52) + 61688);
  if ( !(unsigned __int8)license_is_nextgen_infra_active() )
  {
    v11 = v1;
    v2 = ddl_get_value_length(a1, "trial", 0);
    v13 = ddl_get_value_string(a1, (int)"trial", 0, ((unsigned int)&v10 - ((v2 + 3) & 0xFFFFFFFC)) & 0xFFFFFFF0, v2);
    v3 = ddl_get_value_length(a1, "url", 0);
    v12 = (const char *)ddl_get_value_string(
                          a1,
                          (int)"url",
                          0,
                          ((unsigned int)&v10 - ((v3 + 3) & 0xFFFFFFFC)) & 0xFFFFFFF0,
                          v3);
    v4 = fopen("/tmp/.autoupdate", "w");
    if ( v4 )
    {
      if ( v12 )
        fputs(v12, v4);
      else
        fwrite("https://ae1.juniper.net/junos/key_retrieval", 0x2Bu, 1u, v4);
      fclose(v4);
    }
    if ( v13 )
      daemon_request_signal((int)"license-check", 31, 0, 0, 0, 1);
    else
      daemon_request_signal((int)"license-check", 30, 0, 0, 0, 1);

If value of "url" is specified, function write this value to file "/tmp/.autoupdate". If not, "https://ae1.juniper.net/junos/key_retrieval" is written. Then, function send signal to license-check daemon. When receive signal, lc_fetch_license_keys() function in license-check is called.

int lc_fetch_license_keys()
{
  memset(&s, 0, 0x400u);
  v0 = fopen("/tmp/.autoupdate", "r");
  if ( v0 )
  {
    memset(&command, 0, 0x400u);
    fgets(&command, 1024, v0);
    v1 = strlen(&command);
    if ( v1 > 0 )
    {
      v2 = v1 + 1;
      do
      {
        v3 = *((_BYTE *)&v6 + v2 + 3);
        if ( v3 != '\r' && v3 != '\n' )
          break;
        *((_BYTE *)&v6 + v2-- + 3) = 0;
      }
      while ( v2 > 1 );
    }
    strlcpy(&s, &command, 1024);
    fclose(v0);
    memset(&command, 0, 0x400u);
    v6 = "/tmp/.autoupdate";
    snprintf(&command, 0x400u, "/bin/rm -f %s", "/tmp/.autoupdate");
    system(&command);
    if ( s )
      return sub_8358ED0((int)&s);

The above function reads content from "/tmp/.autoupdate" to buffer s, then removes file "/tmp/.autoupdate". Next, buffer s is passed to sub_8358ED0() function:

__pid_t __fastcall sub_8358ED0(int a1) {
  v28 = a1;
  ...
        v21 = &command[strlen(command)];
        if ( v20 == 2 )
          snprintf(
            v21,
            0x400u,
            "/usr/sbin/license_fetch -o /tmp/license.keys.%d '%s?serial=%s&version=%s&trial=1' 2> %s",
            v27,
            v28,
            v3,
            &s,
            &filename);
        else
          snprintf(
            v21,
            0x400u,
            "/usr/sbin/license_fetch -o /tmp/license.keys.%d '%s?serial=%s&version=%s' 2> %s",
            v27,
            v28,
            v3,
            &s,
            &filename);
        system(command);

The system() function is called with the command which is built from buffer s. Because there is no validate function is called to sanitise buffer s, so it could lead to a command injection vulnerability if an attacker could control data in buffer s (read from "/tmp/.autoupdate" file).

The exploitation

  • Control content in "/tmp/.autoupdate"

The "/tmp/.autoupdate" is created and written in mgd_update_license() function. An attacker can control the content of this file through the "url" parameter in the command request system license update. Alternatively, if an attacker only has a lower privilege user, he could directly create "/tmp/.autoupdate" in "/tmp" directory because any user can create file in "/tmp" directory. Next, when update license, mgd_update_license() function writes content to file just create. We can invoke the function in the license-check daemon by sending a signal, it takes a certain amount of time from the time the file was written until the lc_fetch_license_keys() function is called, and during that period of time, content in this file could be overwritten.

  • To summarise, in order to exploit this vulnerability, there are two scenarios:

    • An attacker has a user with privilege at least “maintenance” (to run request system license update command). PoC1

    • An attacker only has a user with "shell" privilege (only permission to start shell). However, it requires that the automatic license update feature is enabled in the system if this feature is enabled, the system will autorun request system license update command after a period. PoC2

      For ease of visualisation, PoC simulates an automatic update license in the system by running the request system license update command. The while loop is used to constantly create and write to "/tmp/.autoupdate" file, purpose to overwrite to content which is written by mgd_update_license() function in this file.

timeline

  • 2020-06-12 Reported to Vendor, Vendor acknowledged on same day
  • 2021-01-12 Vendor patched the vulnerability