Your SlideShare is downloading. ×
PrestaShop securityimprovements and optimizations<br />
<ul><li>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) </li></ul>About us ...
4 Pillars of performance<br /><ul><li>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) </li></li></ul><li>(…) Programmers waste enormous amounts of time th...
Your architecture has to be efficient (good planning)<br />You have to code using best practices (don't do **obviously** s...
Measure first! You should know bottlenecks.<br />Benchmark different scenarios and configs<br />Going Linux? Test Linux, n...
Small performance gains<br />Using (int) instead of intval() can be even 4 X faster<br />But overall gain is negligable (u...
Server load:<br />ab, siege, multi-mechanize ...<br />Databaseload:<br />MySql Slow Query Log, mysql proxy, ...<br />EXPLA...
Server:<br />Difficult task, often impossible on shared hostings<br />Ask your admin<br />CPU is rarely a bottleneck, gene...
Every call to fs costs, depending the OS, filesystem and number of files <br />Always use absolute paths in require / incl...
Directory content splitting:<br />img/p/534-189-small.jpg<br />becomes<br />img/p/small/534-189.jpg<br />Reading transpare...
Database!<br /><ul><li>Check your indexes!
Avoid to using too many JOINS</li></ul>SELECT * FROM ps_feature` f LEFT JOIN ps_feature_lang` fl ON ( f.`id_feature` = fl....
Use VIEWS instead of complicated SELECTS<br />Are you needing ps_connections & ps_connections_page?<br />If you are expect...
Big problem - non unique queries<br />1.3.10, simulation of command process:<br />Index – search – authentication – order ...
Repeatedqueries<br />
Non–optimisedqueries<br />
Best is use mysql proxy or memcachedNot always possible<br />Do not resolve overhead of unnecessary calls <br />Use intern...
static public function getCurrency($id_currency){<br />	return Db::getInstance()->getRow('SELECT * FROM `'._DB_PREFIX_.'cu...
<ul><li>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</li></ul>Global cache<br />
<ul><li>Regexp is costly, and complicated</li></ul>	return preg_match('/^[a-z0-9!#$%&'*+/=?^`{}|~_-]+[.a-z0-	9!#$%&'*+/=?^...
<ul><li>Avoid capturing groups</li></ul>return preg_match('/^([^<>{}]|<br />)*$/ui', $text);<br />	return preg_match('/^(?...
foreach($cart->getProducts() as $product)<br />   if ($orderStatus->logable)<br />      ProductSale::addProductSale(intval...
// Send an e-mail to customer<br />if ($id_order_state!= _PS_OS_ERROR_ AND $id_order_state!= _PS_OS_CANCELED_ AND $custome...
'{products}' => $productsList,<br />'{discounts}' => $discountsList,<br />'{total_paid}' => Tools::displayPrice($order->to...
Upcoming SlideShare
Loading in...5
×

Good practices for PrestaShop code security and optimization

11,730

Published on

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

Published in: Business
1 Comment
4 Likes
Statistics
Notes
No Downloads
Views
Total Views
11,730
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
0
Comments
1
Likes
4
Embeds 0
No embeds

No notes for slide

Transcript of "Good practices for PrestaShop code security and optimization"

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

×