Low
CVE-2021-21956
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
CVE-2021-21956
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
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
Ratings
-
Attacker ValueLow
-
ExploitabilityHigh
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.
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportCVSS V3 Severity and Metrics
General Information
Vendors
- cloudlinux
Products
- imunify360 5.10.2,
- imunify360 5.8,
- imunify360 5.9
References
Additional Info
Technical Analysis
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: