CVE-2016-7784 (PHP, published 2017-03-07T16:59Z)
Description: value: SQL injection vulnerability in the getSection function in framework/core/subsystems/expRouter.php in Exponent CMS 2.3.9 and earlier allows remote attackers to execute arbitrary SQL commands via the section parameter.
Severity: HIGH (CVSS v3 base score: 9.8)
Weakness: CWE-89 \u2014 Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
Repository: https://github.com/exponentcms/exponent-cms
Files changed:
framework/core/subsystems/expRouter.php
framework/modules/blog/controllers/blogController.php
framework/modules/search/controllers/searchController.php
Commit message: fix a possible vulnerability and add some comments


Vulnerable code:
    
    {           
        if(substr($fulllink, -1) == '/') $fulllink = substr($fulllink, 0, -1);  
        return $fulllink;                                   
    
                
        if (isset($params['section']) && $params['section'] == SITE_DEFAULT_SECTION) {            
            
            if (isset($params['section']) && !isset($params['action'])) {                
                }                               
            } else {                
                $link = '';               
        
            
                
                //trim last / off                 
                        }                    
                // if this url wasn't a valid section, or action then kill it.  It might not actually be a "bad" url, 
                if (!$ret) $this->url_type = 'malformed';  
            // if we hit this it's an old school url coming in and we're trying to use SEF's. 
            header("Location: ".$this->makeLink(array('section'=>intval($_REQUEST['section']))),TRUE,301);          
        if ($this->url_type != 'malformed') {               
            // if page or controller is not found we will route to the not found controller.            
            $this->url_parts = explode('/', $this->sefPath);     
            
                              
        define('PRINTER_FRIENDLY', (isset($_REQUEST['printerfriendly']) || isset($this->params['printerfriendly'])) ? 1 : 0);         
    public function routePageRequest() {        
                $_REQUEST['section'] = SITE_DEFAULT_SECTION;  
            // check the regular page names.  If that still doesn't work then we'll redirect them to the 
                    'url_parts'=>array(                
                    'url_parts'=>array(                
            //if section is empty, we'll look for the page overrides first and route to 
                $c = new storeCategory();                
                        //fake parts and route to action  
                        $this->url_type = 'action';                   
                        $this->url_parts[1] = 'show'; //controller
                        $this->url_parts[2] = 'title'; //controller
                        $this->url_parts[3] = $sef_url; //controller
                    //fake parts and route to action 
                    $this->url_type = 'action';                                      
                    $this->url_parts[1] = 'showall'; //controller
                    $this->url_parts[2] = 'title'; //controller                    
                    $this->url_parts[3] = $sef_url; //controller
        
            if ($part_count == count($map['url_parts'])) {               
                    } 
            }            
              
        
                
    
        }        
    
            // most of the time we can just grab the action outta the POST array since this is passed as a hidden field, 
    
    
        
        return $url; 
        
    
        
        $this->sefPath = substr($this->sefPath,strlen(substr(PATH_RELATIVE,0,-1))); 
        
                    $this->ectid = $s[1];    
                }   
            }            
                $section = $this->url_style=="sef" ? $this->getPageByName($_REQUEST['section'])->id : intval($_REQUEST['section']) ;
    
    
    
    {        
            redirect_to(array('controller'=>'notfound','action'=>'page_not_found','title'=>expString::escape($this->params['title'])));
        return;
	public function searchQueryReport() {
		$records = $db->selectObjects('search_queries');
		$records = $db->selectObjects('search_queries', $where);

Fixed code:

    {
        if(substr($fulllink, -1) == '/') $fulllink = substr($fulllink, 0, -1);
        return $fulllink;


        if (isset($params['section']) && $params['section'] == SITE_DEFAULT_SECTION) {

            if (isset($params['section']) && !isset($params['action'])) {
                }
            } else {
                $link = '';



                //trim last / off
                        }
                // if this url wasn't a valid section, or action then kill it.  It might not actually be a "bad" url,
                if (!$ret) $this->url_type = 'malformed';
            // if we hit this it's an old school url coming in and we're trying to use SEF's.
            header("Location: ".$this->makeLink(array('section'=>intval($_REQUEST['section']))),TRUE,301);
        if ($this->url_type != 'malformed') {
            // if page or controller is not found we will route to the not found controller.
            $this->url_parts = explode('/', $this->sefPath);


        define('PRINTER_FRIENDLY', (isset($_REQUEST['printerfriendly']) || isset($this->params['printerfriendly'])) ? 1 : 0);
    public function routePageRequest() {
                $_REQUEST['section'] = SITE_DEFAULT_SECTION;
            // check the regular page names.  If that still doesn't work then we'll redirect them to the
                    'url_parts'=>array(
                    'url_parts'=>array(
            //if section is empty, we'll look for the page overrides first and route to
                $c = new storeCategory();
                        //fake parts and route to action
                        $this->url_type = 'action';
                        $this->url_parts[1] = 'show'; //action
                        $this->url_parts[2] = 'title'; //param name
                        $this->url_parts[3] = $sef_url; //param
                    //it's a store category
                    //fake parts and route to action
                    $this->url_type = 'action';
                    $this->url_parts[1] = 'showall'; //action
                    $this->url_parts[2] = 'title'; //param name
                    $this->url_parts[3] = $sef_url; //param
//fixme we may want to log missed pages (no existing store cat/product) requests and set up/use a redirect table (404)??
//fixme and we may also want to log any redirects taken??

            if ($part_count == count($map['url_parts'])) {
                    }
            }




        }

            // most of the time we can just grab the action outta the POST array since this is passed as a hidden field,



        return $url;



        $this->sefPath = substr($this->sefPath,strlen(substr(PATH_RELATIVE,0,-1)));

                    $this->ectid = $s[1];
                }
            }
                $section = $this->url_style=="sef" ? $this->getPageByName(expString::escape($_REQUEST['section']))->id : intval($_REQUEST['section']) ;



    {
            redirect_to(array('controller'=>'notfound','action'=>'page_not_found','title'=>$this->params['title']));
        return;  //fixme this negates the code below!
	public function searchQueryReport() {  //fixme this will typically create a 500 error due to number of search query records
		$records = $db->selectObjects('search_queries');  // note we get all records a first time?
		$records = $db->selectObjects('search_queries', $where);  // note we then get all records a 2nd time?