Attacker Value
Low
(1 user assessed)
Exploitability
High
(1 user assessed)
User Interaction
Required
Privileges Required
None
Attack Vector
Local
1

CVE-2021-21956

Disclosure Date: November 22, 2021
Add MITRE ATT&CK tactics and techniques that apply to this CVE.

Description

A php unserialize vulnerability exists in the Ai-Bolit functionality of CloudLinux Inc Imunify360 5.10.2. A specially-crafted malformed file can lead to potential arbitrary command execution. An attacker can provide a malicious file to trigger this vulnerability.

Add Assessment

2
Ratings
Technical Analysis

This is an interesting vulnerability in the Ai-Bolit AV scanner functionality of CloudLinux Inc Imunify360 running Ai-Bolit prior to AI-Bolit 31.1.2-1, Interestingly whilst it was stated that 30.8.8-1, 30.8.9-1, 30.10.3-1, 31.0.3-1, and 31.1.1-1 were affected, in my testing the Ubuntu dpkg files that were available on CloudLinux’s servers only contained versions 30.10.3-1 and later, and surprisingly only 30.10.3-1 actually contained the vulnerability. All later versions were found to be patched. Older versions were not found on the server so it was not possible to obtain a copy of these affected versions, however it is likely they were also affected and the patch was either introduced with 31.10.3-1 or was introduced with 31.1.2-1 and then backported to versions 30.10.3-1 and later.

Note that the software used to be at https://repo.imunify360.cloudlinux.com/imunify360/ubuntu/20.04/pool/main/a/ai-bolit/ as ai-bolit_30.10.3-1_amd64.deb however it appears it has since been removed. If you need a copy of this file please feel free to reach out.

Working from the original PoC at https://talosintelligence.com/vulnerability_reports/TALOS-2021-1383 we can see that the vulnerability occurs due to an unserialize() call within deobfuscateDecodedFileGetContentsWithFunc($str, $matches) where the line $resCode = implode(' ', @unserialize(base64_decode($matches[5]))); is executed, which first base64 decodes the 5th match, which contains a subset of the attacker controlled file that is being scanned, Base64 decodes it, and then passes it, unchecked, into a call to unserialize().

Tracing back we can see that there is a regex in the Obfuscator class associated with the ID decodedFileGetContentsWithFunc(). This is the regex that is used to scan the file, and then if any patterns are found that match this regex, then deobfuscateDecodedFileGetContentsWithFunc() is called to deobfuscate the file.

function\s(\w{1,50})\((\$\w{1,50})\)\s?{.*?\$\w+\s?=\s?"[^"]+";\$\w{1,50}\s?=\s?str_split\(\$\w{1,50}\);\$\w{1,50}\s?=\s?array_flip\(\$\w{1,50}\);\$\w{1,50}\s?=\s?0;\$\w{1,50}\s?=\s?"";\$\w{1,50}\s?=\s?preg_replace\("[^"]+",\s?"",\s?\$\w{1,50}\);do\s?{(?:\$\w{1,50}\s?=\s?\$\w{1,50}\[\$\w{1,50}\[\$\w{1,50}\+\+\]\];){4}\$\w{1,50}\s?=\s?\(\$\w{1,50}\s?<<\s?2\)\s?\|\s?\(\$\w{1,50}\s?>>\s?4\);\$\w{1,50}\s?=\s?\(\(\$\w{1,50}\s?&\s?15\)\s?<<\s?4\)\s?\|\s?\(\$\w{1,50}\s?>>\s?2\);\$\w{1,50}\s?=\s?\(\(\$\w{1,50}\s?&\s?3\)\s?<<\s?6\)\s?\|\s?\$\w{1,50};\$\w{1,50}\s?=\s?\$\w{1,50}\s?\.\s?chr\(\$\w{1,50}\);if\s?\(\$\w{1,50}\s?!=\s?64\)\s?{\$\w{1,50}\s?=\s?\$\w{1,50}\s?\.\s?chr\(\$\w{1,50}\);}if\s?\(\$\w{1,50}\s?!=\s?64\)\s?{\$\w{1,50}\s?=\s?\$\w{1,50}\s?\.\s?chr\(\$\w{1,50}\);}}\s?while\s?\(\$\w{1,50}\s?<\s?strlen\(\$\w{1,50}\)\);return\s?\$\w{1,50};}\s?.*?function\s(\w{1,50})\(\){\$\w{1,50}\s?=\s?@file_get_contents\(\w{1,50}\(\)\);.*?(\$\w{1,50})\s?=\s?"([^"]{1,20000})";.*?\4\s?=\s?@unserialize\(\1\(\4\)\);.*?(function\s(\w{1,50})\(\$\w{1,50}=NULL\){foreach\s?\(\3\(\)\s?as.*?eval\(\$\w{1,50}\);}}}).*?(\7\(\);)

After a lot of time spent manually going through the regex using regex101.com I was able to recreate a file that would trigger the regex and result in deserialization:

<?php
function func1($param){ $asdf = "cool";$asc = str_split($foobar);$word = array_flip($asc);$food = 0;$cored="";$bored=preg_replace("asdf", "", $deda);do {$asdf = $asdf[$acfd[$foa++]];$asdf = $asdf[$acfd[$foa++]];$asdf = $asdf[$acfd[$foa++]];$asdf = $asdf[$acfd[$foa++]];$coolio=($joko << 2) | ($foko >> 4);$cood = (($aed & 15) << 4) | ($ave >> 2);$aesd = (($ke & 3) << 6) | $hes;$hec = $hek . chr($jok);if ($asdf != 64) {$foobd = $coor . chr($foj);}if ($hdo != 64) {$fed = $asdf . chr($ase);}} while ($asdfas < strlen($ddhdh));return $asdfe;} function func2(){$corlan = @file_get_contents(asdfasdf()); $variable1 = "INSERTTHEPAYLOADHEREBASE64ENCODED"; $variable1 = @unserialize(func1($variable1)); function func3($faccc=NULL){foreach (func2() as eval($fjdjd);}}} func3();
?>

Note that most names are junk however anything with variable1 or func1 is specifically named as such (the string “variable” or “func” followed by a number) as they need to be the same name everywhere they are used. This is due to the regex using some of the results from earlier checks later on in its code, so the names of these functions or variables must remain the same otherwise the regex won’t match.

Now whilst deserialization attacks can result in RCE, arbitrary file deletion, or otherwise remember that it really depends what classes are available to the end user. In my experiments I found the following classes were available when the unserialize() call was made:

stdClass
Exception
ErrorException
Error
CompileError
ParseError
TypeError
ArgumentCountError
ArithmeticError
DivisionByZeroError
Closure
Generator
ClosedGeneratorException
WeakReference
DateTime
DateTimeImmutable
DateTimeZone
DateInterval
DatePeriod
LibXMLError
CURLFile
HashContext
GMP
LogicException
BadFunctionCallException
BadMethodCallException
DomainException
InvalidArgumentException
LengthException
OutOfRangeException
RuntimeException
OutOfBoundsException
OverflowException
RangeException
UnderflowException
UnexpectedValueException
RecursiveIteratorIterator
IteratorIterator
FilterIterator
RecursiveFilterIterator
CallbackFilterIterator
RecursiveCallbackFilterIterator
ParentIterator
LimitIterator
CachingIterator
RecursiveCachingIterator
NoRewindIterator
AppendIterator
InfiniteIterator
RegexIterator
RecursiveRegexIterator
EmptyIterator
RecursiveTreeIterator
ArrayObject
ArrayIterator
RecursiveArrayIterator
SplFileInfo
DirectoryIterator
FilesystemIterator
RecursiveDirectoryIterator
GlobIterator
SplFileObject
SplTempFileObject
SplDoublyLinkedList
SplQueue
SplStack
SplHeap
SplMinHeap
SplMaxHeap
SplPriorityQueue
SplFixedArray
SplObjectStorage
MultipleIterator
ReflectionException
Reflection
ReflectionFunctionAbstract
ReflectionFunction
ReflectionGenerator
ReflectionParameter
ReflectionType
ReflectionNamedType
ReflectionMethod
ReflectionClass
ReflectionObject
ReflectionProperty
ReflectionClassConstant
ReflectionExtension
ReflectionZendExtension
ReflectionReference
SessionHandler
__PHP_Incomplete_Class
php_user_filter
Directory
AssertionError
SimpleXMLElement
SimpleXMLIterator
LevelDB
LevelDBWriteBatch
LevelDBIterator
LevelDBSnapshot
LevelDBException
JsonException
AibolitHelpers
Variables
Logger
LoadSignaturesForScan
InternalSignatures
CmsVersionDetector
CloudAssistedRequest
Report
JSONReport
PHPReport
PlainReport
CSVReport
DoublecheckReport
HTMLReport
CloudAssistedFiles
DetachedMode
ResidentMode
DebugMode
FileInfo
HashTable
Finder
FileFilter
ScanList
Scanner
ScanUnit
ScanCheckers
TemplateList
TranslateList
UserList
HyperScan
PerfomanceStats
Progress
OsReleaseInfo
FileHashMemoryDb
FilepathEscaper
StringToStreamWrapper
Normalization
Encoding
RapidScanStorageRecord
RapidScanStorage
CloudAssistedStorage
RapidAccountScan
DbFolderSpecification
LevelDBWrap
CriticalFileSpecification
Helpers
MathCalc
FuncCalc
Deobfuscator
SharedMem
Factory
Template
Translate

Unfortunately of these I was mainly only able to find cases where deserializing an object would cause file deletion. In cases where file creation was possible, it was either in a fixed location with a nonexecutable extension, or more often than not the content of the file would be a fixed string which wasn’t useful for the purposes of exploitation.

The most promising class appeared to be CloudAssistedFiles which could cause a CURL request to be made, however it appears that the URL, as well as the contents of the request could not be controlled, so unfortunately there would not be any way to retrieve data from the server from what I could tell.

Arbitrary file deletion can be achieved via the Logger class and the $log_file parameter of the __construct function.

At the end of the research I was not able to form a useful payload for this vulnerability. In my opinion, given what I saw so far, the most likely possibility for exploitation would be that someone used the exploit chain to delete something like .htaccess and then abuse that to bypass a file upload filter or similar.

However this is highly dependent on the application and a variety of other factors, vs this being a bug that can be installed purely by virtue of having Ai-Bolit AV scanner installed.

A final important note is that CloudLinux Inc Imunify360 is, by default, configured to check for updates automatically once daily. This means its very likely that most installations in the wild are up to date, and I personally noticed this when trying to find servers with Imunify360 installed, as most of these servers were running up to date versions of Imunify360 or one of its respective components.

CVSS V3 Severity and Metrics
Base Score:
7.8 High
Impact Score:
5.9
Exploitability Score:
1.8
Vector:
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Attack Vector (AV):
Local
Attack Complexity (AC):
Low
Privileges Required (PR):
None
User Interaction (UI):
Required
Scope (S):
Unchanged
Confidentiality (C):
High
Integrity (I):
High
Availability (A):
High

General Information

Vendors

  • cloudlinux

Products

  • imunify360 5.10.2,
  • imunify360 5.8,
  • imunify360 5.9

Additional Info

Technical Analysis