Accueil Faille d'inclusion PHP dans le plugin Wordpress JS Job Manager
Post
Annuler

Faille d'inclusion PHP dans le plugin Wordpress JS Job Manager

Présentation du plugin

Le plugin Wordpress JS Job Manager (slug: js-jobs) se présente de cette façon :

JS Jobs allows you to run your own, unique jobs classifieds service where you or employer can advertise their jobs, job seekers can upload their resume and apply to any jobs.

No need to setup anything, just click and install it. It comes with 250+ configurations and 45 shortcode with full power. JS Jobs also have its own login/register pages.

La version testée est la 2.0.1 qui date d’il y a 5 mois.

La vulnérabilité

Dans le script jsjobs.php on trouve la classe principale qui se va charger le code des différents modules via une fonction nommée includes() :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class jsjobs {                                                                                                         
                                                                                                                       
    public static $_path;                                                                                              
    public static $_pluginpath;                                                                                        
    public static $_data; /* data[0] for list , data[1] for total paginition ,data[2] fieldsorderring , data[3] userfield for form , data[4] for reply , data[5] for ticket history  , data[6] for internal notes  , data[7] for ban email  , data['ticket_attachment'] for attachment */
    public static $_pageid;                                                                                            
    public static $_db;                                                                                                
    public static $_configuration;                                                                                     
    public static $_sorton;                                                                                            
    public static $_sortorder;                                                                                         
    public static $_ordering;                                                                                          
    public static $_sortlinks;                                                                                         
    public static $_msg;                                                                                               
    public static $_error_flag;                                                                                        
    public static $_error_flag_message;                                                                                
    public static $_currentversion;                                                                                    
    public static $_error_flag_message_for;                                                                            
    public static $_error_flag_message_for_link;                                                                       
    public static $_error_flag_message_for_link_text;                                                                  
    public static $_error_flag_message_register_for;                                                                   
    public static $theme_chk;                                                                                          
    public static $_search;                                                                                            
    public static $_captcha;                                                                                           
    public static $_jsjobsession;                                                                                      
                                                                                                                       
    function __construct() {                                                                                           
        self::includes();                                                                                              
        //  self::registeractions();                                                                                   
        self::$_path = plugin_dir_path(__FILE__);                                                                      
        self::$_pluginpath = plugins_url('/', __FILE__);
        --- snip ---

L’un des scripts chargés est includes/shortcodes.php qui contient une classe JSJOBSshortcodes dont les méthodes correspondent à différents panels comme le Job Seeker :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function show_jobseeker_controlpanel($raw_args, $content = null) {                                                 
    //default set of parameters for the front end shortcodes                                                       
    ob_start();                                                                                                    
    $defaults = array(                                                                                             
        'jsjobsme' => 'jobseeker',                                                                                 
        'jsjobslt' => 'controlpanel',                                                                              
    );                                                                                                             
    $sanitized_args = shortcode_atts($defaults, $raw_args);                                                        
    if(isset(jsjobs::$_data['sanitized_args']) && !empty(jsjobs::$_data['sanitized_args'])){                       
        jsjobs::$_data['sanitized_args'] += $sanitized_args;                                                       
    }else{                                                                                                         
        jsjobs::$_data['sanitized_args'] = $sanitized_args;                                                        
    }                                                                                                              
    $pageid = get_the_ID();                                                                                        
    jsjobs::setPageID($pageid);                                                                                    
    jsjobs::addStyleSheets();                                                                                      
    $offline = JSJOBSincluder::getJSModel('configuration')->getConfigurationByConfigName('offline');               
    if ($offline == 1) {                                                                                           
        JSJOBSlayout::getSystemOffline();                                                                          
    } elseif (JSJOBSincluder::getObjectClass('user')->isdisabled()) { // handling for the user disabled            
        JSJOBSlayout::getUserDisabledMsg();                                                                        
    } else {                                                                                                       
        $module = JSJOBSrequest::getVar('jsjobsme', null, 'jobseeker');                                            
        $layout = JSJOBSrequest::getLayout('jsjobslt', null, 'controlpanel');                                      
        $jobseekerarray = array('addcoverletter', 'mycoverletters', 'myresumes','myappliedjobs');                  
        $isouruser = JSJOBSincluder::getObjectClass('user')->isJSJobsUser();                                       
        $isguest = JSJOBSincluder::getObjectClass('user')->isguest();                                              
        if (in_array($layout, $jobseekerarray) && $isouruser == false && $isguest == false) {                      
            JSJOBSincluder::include_file('newinjsjobs', 'common');                                                 
        } else {                                                                                                   
        echo "hi there<br />\n";                                                                                   
            JSJOBSincluder::include_file($module);                                                                 
        }                                                                                                          
    }                                                                                                              
    unset(jsjobs::$_data['sanitized_args']);                                                                       
    $content .= ob_get_clean();                                                                                    
    return $content;                                                                                               
}

La fonction JSJOBSrequest::getVar permet de récupérer une variable passée dans l’URL.

Ici la valeur du paramètre jsjobsme est utilisée pour remplir la variable $module. On a ensuite une inclusion déclenchée via l’instruction suivante :

1
JSJOBSincluder::include_file($module);

La fonction include_file est déclarée dans includes/includer.php :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static function include_file($filename, $module_name = null) {                                              
    if ($module_name != null) {                                                                                    
        if (file_exists(JSJOBS_PLUGIN_PATH . 'modules/' . $module_name . '/tmpl/' . $filename . '.inc.php')) {     
            require_once(JSJOBS_PLUGIN_PATH . 'modules/' . $module_name . '/tmpl/' . $filename . '.inc.php');      
        }                                                                                                          
        if (locate_template('js-jobs/' . $module_name . '-' . $filename . '.php', 1, 1)) {                         
            return;                                                                                                
        // } elseif (locate_template($module_name . '-' . $filename . '.php', 1, 1)) { // to add layout in root template directory
        //     return;                                                                                             
        } else {                                                                                                   
            include_once JSJOBS_PLUGIN_PATH . 'modules/' . $module_name . '/tmpl/' . $filename . '.php';           
        }                                                                                                          
    } else {                                                                                                       
        include_once JSJOBS_PLUGIN_PATH . 'modules/' . $filename . '/controller.php';                              
    }                                                                                                              
    return;                                                                                                        
}

Dans notre cas $module_name n’est pas défini et prend la valeur par défaut (null).

Nous nous retrouvons donc dans le dernier cas d’inclusion.

En raison des contraintes de préfixe et de suffixe, il faut pour exploiter la vulnérabilité être en mesure de placer un script controller.php à un endroit que le serveur web est capable d’inclure.

PoC si on est en mesure de créer le fichier dans /tmp :

1
http://localhost:8000/?page_id=10&jsjobsme=../../../../../../../../../../tmp

L’ID de la page vulnérable doit être celle correspondant à JS Jobs (un lien Jobseeker doit être présent dans la page).

Cet article est sous licence CC BY 4.0 par l'auteur.