• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Good practices for PrestaShop code security and optimization
 

Good practices for PrestaShop code security and optimization

on

  • 12,189 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,189
Views on SlideShare
12,187
Embed Views
2

Actions

Likes
2
Downloads
0
Comments
0

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 Good practices for PrestaShop code security and optimization Presentation Transcript

    • PrestaShop securityimprovements and optimizations
      • Group Sitti (www.sitti.fr)
      • Team of 6 developers & integrators
      • 400 Prestashop installed – ranging from 0.9.6 to 1.3.1
      • Shared hosting – cluster of 10+ machines (load balancers, web servers, file servers, database servers)
      About us ?
    • 4 Pillars of performance
      • Infrastructure(servers, databases)
      • Our focus: Server-side code (1-st tier, php + sql)
      • Network, transport protocols
      • 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)
    • 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?
    • 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?
    • 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
    • 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?
    • 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
    • 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
    • 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
    • Database!
      • Check your indexes!
      • 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! :)
    • 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
    • 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
    • Repeatedqueries
    • Non–optimisedqueries
    • 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
    • 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
      • Direct in MySqlclass
      • Catches all output
      • Harder to implement
      • Some queries can be repeated but expecting different result (->cart)
      • Needs kind of "blacklist"
      • Once implemented, makes application maintenance much easier
      • Should be implemented as core feature
      Global cache
      • 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)
      • 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)
    • 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
    • // 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?
    • '{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;
    • 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
      • Consider export / import as heavy operations
      • For flux Beezup we are using ObjectModel
      • It works, but we have 17 sql queries / product to collect all data (product, features, attributes, images...)
      • Ok for 100 products. What about 100 000 ?
      • Risky if we had to generate it on-demand
      • Cron prepares output before robot crawls
      • Robot hits cached xml
      Use cron to generate cache
      • Sending compressed content saves bandwith
      • For static content use mod_gzip / mod_deflate
      • For php files there is simple patch
      • 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
      • Use CDN (Content Delivery Network) for js
      • 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
      • Keep DOM selectors simple. Avoid complicated researches; use ids if possible.
      • Jquery isn't always fastest. Search native methods.
      • 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
    • Botnets
      Dishonestcompetitors
      Fraudulentcustomers

      Whyprotect?
    • SQL Injection
      CSRF
      XSS
      Pathtranversal

      Different types of attacks
    • 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
    • Exploit the site's trust in your identity
      Use tokens
      Requiring authentication in GET and POST parameters
      index.php?tab=AdminOrders&token=e84b3fda0b04b922b3bc27b08d4fe136
      CSRF
    • 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
    • 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
    • ?
      Questions