• Save
Good practices for PrestaShop code security and optimization
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Good practices for PrestaShop code security and optimization

on

  • 12,607 views

Good practices for PrestaShop code security and optimization - Rémi Gaillard - SITTI group

Good practices for PrestaShop code security and optimization - Rémi Gaillard - SITTI group

Statistics

Views

Total Views
12,607
Views on SlideShare
12,605
Embed Views
2

Actions

Likes
3
Downloads
0
Comments
1

2 Embeds 2

http://translate.googleusercontent.com 1
https://twitter.com 1

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Good practices for PrestaShop code security and optimization Presentation Transcript

  • 1. PrestaShop securityimprovements and optimizations
  • 2.
    • Group Sitti (www.sitti.fr)
    • 3. Team of 6 developers & integrators
    • 4. 400 Prestashop installed – ranging from 0.9.6 to 1.3.1
    • 5. Shared hosting – cluster of 10+ machines (load balancers, web servers, file servers, database servers)
    About us ?
  • 6. 4 Pillars of performance
    • Infrastructure(servers, databases)
    • 7. Our focus: Server-side code (1-st tier, php + sql)
    • 8. Network, transport protocols
    • 9. Client-side code (2-nd tier: html + css + javascript)
  • (…) Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: 1
    premature optimization is the root
    of all evil.
    Yet we should not pass up our opportunities in that critical 3%. A good programmer will not be lulled into complacency by such reasoning, he will be wise to look carefully at the critical code; but only after that code has been identified.
    Most important disclaimer (Donald E. Knuth)
  • 10. Your architecture has to be efficient (good planning)
    You have to code using best practices (don't do **obviously** stupid things)
    But prefer rather maintability and readibility of code over the speed
    When speed is not critical (i.e. real time systems, high traffic sites), you can improve it in  later iterations
    When to optimize?
  • 11. Measure first! You should know bottlenecks.
    Benchmark different scenarios and configs
    Going Linux? Test Linux, not Win. There are differences
    Will have 10000 products in your store? Test your modules with db of 10000, not 5
    Is a 1% improvement worth of additional work?
    What about 5%? 10%?
    Try to estimate coding cost vs. hardware cost
    Sometimes it's just cheaper to add RAM
    What to optimize?
  • 12. Small performance gains
    Using (int) instead of intval() can be even 4 X faster
    But overall gain is negligable (unless you are Facebook)
    Code executed once
    Tools::setCookieLanguage could be improved, but it is executed once
    Mythical optimisations ( ” vs ' )
    But ”$a $b $c” … is faster than $a.” ”.$b.” ”.$c
    Whatshouldn'tbeoptimised
  • 13. Server load:
    ab, siege, multi-mechanize ...
    Databaseload:
    MySql Slow Query Log, mysql proxy, ...
    EXPLAIN
    PHP:
    xdebug, dbg, xhprof ...
    Network / client side
    Yslow, firebug, WebKitinspector, dynaTrace AJAX, fiddler, google webmaster tools
    How to measure?
  • 14. Server:
    Difficult task, often impossible on shared hostings
    Ask your admin
    CPU is rarely a bottleneck, generally indicates problems with suboptimal code
    RAM is cheap but not unlimited – attention to memory consuming scripts
    Typical problem: gd + jpg -> 2 Mb on disk, 33 Mb decompressed into memory
    Ramdisk for often accessed, not critical files (frameworks, configuration, tmp)
    Most common bottleneck: I/O (filesystem, dbs)
    Improving infrastructure
  • 15. Every call to fs costs, depending the OS, filesystem and number of files
    Always use absolute paths in require / include
    Performance may start to degrade if you have more than 50 000 files in a directory
    Each product has image, each image has 6 thumbnails
    Debian + Apache 1.3 (shared hosting, nfs):
    Filesystem
    # Files
    Glob('*') exec. in sec.
    file_exists / sec.
    1000
    4,59
    36000
    11000
    13,30
    21000
    65000
    55,81
    1475
    122000
    142,16
    718
  • 16. Directory content splitting:
    img/p/534-189-small.jpg
    becomes
    img/p/small/534-189.jpg
    Reading transparently via .htaccess
    RewriteRule (.*)/p/([^/]*)home.jpg $1/p/home/$2home.jpg
    Writingtransparently via class 
    if (!imageResize($file, $dir.$imageType['name'].'/'.$language['iso_code'].'-default- '.stripslashes($imageType['name']).'.jpg', ...
    Solution
  • 17. Database!
    • Check your indexes!
    • 18. Avoid to using too many JOINS
    SELECT * FROM ps_feature` f LEFT JOIN ps_feature_lang` fl ON ( f.`id_feature` = fl.`id_feature` AND fl.`id_lang` = 1) WHERE f.`id_feature` = 1SELECT * FROM ps_feature_lang` fl WHER fl.`id_feature` = 1 AND fl.`id_lang` = 1
    Version
    Tables
    Columns
    Without index
    1.1.0.5
    88
    458
    50
    1.2.0.5
    134
    670
    50
    1.3.10
    135
    679
    2 (cool! :)
  • 19. Use VIEWS instead of complicated SELECTS
    Are you needing ps_connections & ps_connections_page?
    If you are expecting high traffic, thay can rise 10+ Mb / day
    Database
  • 20. Big problem - non unique queries
    1.3.10, simulation of command process:
    Index – search – authentication – order (11 pages total)
    3001 SQL queries, but only 1314 uniques! (44%)
    PHP - SQL
  • 21. Repeatedqueries
  • 22. Non–optimisedqueries
  • 23. Best is use mysql proxy or memcachedNot always possible
    Do not resolve overhead of unnecessary calls
    Use internal cacheCan be scoped or globalPrestashop partially uses scoped cacheEasy to implement, tune, and … forget
    Each method / class is responsable for caching its query results
    Solutions
  • 24. static public function getCurrency($id_currency){
    return Db::getInstance()->getRow('SELECT * FROM `'._DB_PREFIX_.'currency` WHERE `deleted` = 0 AND `id_currency` = '.intval($id_currency));
    }
    static public functiongetCurrency($id_currency){
    if (!isset(self::$_cache[$id_currency])) {
    self::$_cache[$id_currency] = Db::getInstance()->getRow('SELECT * FROM `'._DB_PREFIX_.'currency` WHERE `deleted` = 0 AND `id_currency` = '.intval($id_currency));
    }
    return self::$_cache[$id_currency];
    }
    Scoped cache
  • 25.
    • Direct in MySqlclass
    • 26. Catches all output
    • 27. Harder to implement
    • 28. Some queries can be repeated but expecting different result (->cart)
    • 29. Needs kind of "blacklist"
    • 30. Once implemented, makes application maintenance much easier
    • 31. Should be implemented as core feature
    Global cache
  • 32.
    • Regexp is costly, and complicated
    return preg_match('/^[a-z0-9!#$%&'*+/=?^`{}|~_-]+[.a-z0- 9!#$%&'*+/=?^`{}|~_-]*@[a-z0-9]+[._a-z0-9-]*.[a-z0-9]+$/ui', $email);
    • Use filters (> PHP 5.2):
    return filter_var($email, FILTER_VALIDATE_EMAIL);
    • Or test before running costly tests:
    if (strpos($email, '@')!==false)
    • Use str_replace instead of preg_replace:
    preg_replace('/"/', '"', $value)
    Faster: str_replace('"', '"', $value)
    Avoiding regexpSome people, when confronted with a problem, think  “I know, I'll use regular expressions.” Now they have two problems. (jwz)
  • 33.
    • Avoid capturing groups
    return preg_match('/^([^<>{}]|<br />)*$/ui', $text);
    return preg_match('/^(?:[^<>{}]|<br />)*$/ui', $text);
    ?: = non capturing group (no memory allocation!)
    • You often don't need regexp
    return trim($table,'a..zA..Z0..9_') == '';
    equals to
    return preg_match('/^[a-z0-9_-]+$/ui', $table);
    but is up to 2 times faster!
    Avoidingregexp (2)
  • 34. foreach($cart->getProducts() as $product)
       if ($orderStatus->logable)
          ProductSale::addProductSale(intval($product['id_product']), intval($product['cart_quantity']));
    Should be:
    if ($orderStatus->logable)
         foreach($cart->getProducts() as $product)
                ProductSale::addProductSale(intval($product['id_product']), intval($product['cart_quantity']));
    (no need to test if in every iteration if it does not change)
    Use conditions wisely
  • 35. // Send an e-mail to customer
    if ($id_order_state!= _PS_OS_ERROR_ AND $id_order_state!= _PS_OS_CANCELED_ AND $customer->id)
    {
    $invoice = new Address(intval($order->id_address_invoice));
    $delivery = new Address(intval($order->id_address_delivery));
    $carrier = new Carrier(intval($order->id_carrier));
    $delivery_state= $delivery->id_state ? new State(intval($delivery->id_state)) : false;
    $invoice_state= $invoice->id_state ? new State(intval($invoice->id_state)) : false;
    $data = array(
    '{firstname}' => $customer->firstname,
    '{lastname}' => $customer->lastname,
    '{email}' => $customer->email,
    '{delivery_company}' => $delivery->company,
    '{delivery_firstname}' => $delivery->firstname,
    '{delivery_lastname}' => $delivery->lastname,
    '{delivery_address1}' => $delivery->address1,
    '{delivery_address2}' => $delivery->address2,
    '{delivery_city}' => $delivery->city,
    '{delivery_postal_code}' => $delivery->postcode,
    '{delivery_country}' => $delivery->country,
    '{delivery_state}' => $delivery->id_state ? $delivery_state->name : '',
    '{delivery_phone}' => $delivery->phone,
    '{delivery_other}' => $delivery->other,
    '{invoice_company}' => $invoice->company,
    '{invoice_firstname}' => $invoice->firstname,
    '{invoice_lastname}' => $invoice->lastname,
    '{invoice_address2}' => $invoice->address2,
    '{invoice_address1}' => $invoice->address1,
    '{invoice_city}' => $invoice->city,
    '{invoice_postal_code}' => $invoice->postcode,
    '{invoice_country}' => $invoice->country,
    '{invoice_state}' => $invoice->id_state ? $invoice_state->name : '',
    '{invoice_phone}' => $invoice->phone,
    '{invoice_other}' => $invoice->other,
    {order_name}' => sprintf("#%06d", intval($order->id)),
    '{date}' => Tools::displayDate(date('Y-m-d H:i:s'), intval($order->id_lang), 1),
    '{carrier}' => (strval($carrier->name) != '0' ? $carrier->name : Configuration::get('PS_SHOP_NAME')),
    '{payment}' => $order->payment,
    Can you spot the problem?
  • 36. '{products}' => $productsList,
    '{discounts}' => $discountsList,
    '{total_paid}' => Tools::displayPrice($order->total_paid, $currency, false, false),
    '{total_products}' => Tools::displayPrice($order->total_paid - $order->total_shipping - $order->total_wrapping + $order->total_discounts, $currency, false, false),
    '{total_discounts}' => Tools::displayPrice($order->total_discounts, $currency, false, false),
    '{total_shipping}' => Tools::displayPrice($order->total_shipping, $currency, false, false),
    '{total_wrapping}' => Tools::displayPrice($order->total_wrapping, $currency, false, false));
    if (is_array($extraVars))
    $data = array_merge($data, $extraVars);
    // Join PDF invoice
    if (intval(Configuration::get('PS_INVOICE')) AND Validate::isLoadedObject($orderStatus) AND $orderStatus->invoice AND $order->invoice_number)
    {
    $fileAttachment['content'] = PDF::invoice($order, 'S');
    $fileAttachment['name'] = Configuration::get('PS_INVOICE_PREFIX', intval($order->id_lang)).sprintf('%06d', $order->invoice_number).'.pdf';
    $fileAttachment['mime'] = 'application/pdf';
    }
    else
    $fileAttachment= NULL;
    if ($orderStatus->send_email AND Validate::isEmail($customer->email))
    Mail::Send(intval($order->id_lang), 'order_conf', 'Order confirmation', $data, $customer->email, $customer->firstname.' '.$customer->lastname, NULL, NULL, $fileAttachment);
    $this->currentOrder = intval($order->id);
    return true;
    }
    $this->currentOrder = intval($order->id);
    return true;
  • 37. We are preparing whole mail, including pdfattachement, even if we are not sending it.
    Every times you do it, a little kitten dies
    Non optimised conditions
  • 38.
    • Consider export / import as heavy operations
    • 39. For flux Beezup we are using ObjectModel
    • 40. It works, but we have 17 sql queries / product to collect all data (product, features, attributes, images...)
    • 41. Ok for 100 products. What about 100 000 ?
    • 42. Risky if we had to generate it on-demand
    • 43. Cron prepares output before robot crawls
    • 44. Robot hits cached xml
    Use cron to generate cache
  • 45.
    • Sending compressed content saves bandwith
    • 46. For static content use mod_gzip / mod_deflate
    • 47. For php files there is simple patch
    • 48. In init.php change:
    ob_start();
    to:
    ob_start('ob_gzhandler');
    And enjoy! (page index.php: before: 21.1 kb, after: 5.3 kb)
    (yet better is to use zlib.* directives in php.ini)
    • Minify your js & css and combine them in one js/css file
    Network
  • 49.
    • Use CDN (Content Delivery Network) for js
    • 50. Use Cache (mod_expires, Etags) for static content such as images
    you can do it in htacces or httpd.conf
    ExpiresActiveOn
    ExpiresDefault"access plus 15 days“
    ExpiresByTypeimage/gif A2592000
    Network
  • 51.
    • Keep DOM selectors simple. Avoid complicated researches; use ids if possible.
    • 52. Jquery isn't always fastest. Search native methods.
    • 53. Avoid passing HTML / XML as AJAX result. Use JSON instead of. You can reduce amount of data by magnitude of 75% (which if of course faster. Which is of course better).
    Client-sidestuff
  • 54. Botnets
    Dishonestcompetitors
    Fraudulentcustomers

    Whyprotect?
  • 55. SQL Injection
    CSRF
    XSS
    Pathtranversal

    Different types of attacks
  • 56. Allowsyou to interactwith the database
    Sanitize all your variables before use in SQL requests!<?php
    ......
    $order_detail = Db::getInstance()->ExecuteS('
    SELECT *
    FROM .'_DB_PREFIX_.'order_detail
    WHERE id_order='.(int)$_GET['id_order']
    AND payment=''.pSQL($_GET['payment']).''');
    SQL Injection
  • 57. Exploit the site's trust in your identity
    Use tokens
    Requiring authentication in GET and POST parameters
    index.php?tab=AdminOrders&token=e84b3fda0b04b922b3bc27b08d4fe136
    CSRF
  • 58. Inject HTML code in the page
    Sanitize all your variables before output!
    <input type="text" name="lastname" value="{$smarty.post.lastname|htmlentities}" />
    preg_replace('/.*script/ui', '', $_POST['lastname']);
    preg_replace('/.*onmousedown|onmousemove|onmmouseup|onmouseover|onmouseout|onload|onunload|onfocus|onblur|onchange|onsubmit|ondblclick|onclick|onkeydown|onkeyup|onkeypress|onmouseenter|onmouseleave/ui', '', $_POST['lastname']);
    ...
    XSS
  • 59. Access to unauthorized datas
    Sanitize all your variables before load files!
    Check the extention of the file
    include (dirname(__FILE__).'/mails/'. preg_replace(‘/.{2,}/', '.', Tools::getValue('mail')).'html');
    Path transversal
  • 60. ?
    Questions