webForumDet fria alternativet

mysql gränssnittet är skräp

PHPur PHP

7 svar · 927 visningar · startad av itpastorn

itpastornMedlem sedan feb. 2007278 inlägg
#1

Sedan ett par år finns PHP 5. En an dess nyheter var att det kom ett nytt bättre gränssnitt för att ansluta till MySQL, nämligen mysqli - där i står för improved.

För något år sedan kom PHP 5.1 som har gränssnittet PDO.

Båda dessa stödjer det enda helsäkra sättet att slippa SQL-injektion, nämligen prepared statements. Trots detta fortsätter varenda forum på nätet att vara nedlusat med diskussioner om mysql-gränssnittet, det långsammaste och potentiellt osäkraste av dem alla.

Inte ens ihop med funktionen mysql_real_escape_string() är man helt säker mot SQL-injektion:
http://ilia.ws/archives/103-mysql_real_escape_string-versus-Prepared-Statements.html

magic quotes (och således också addslashes) läcker som ett såll och är synnerligen otillräckligt som skydd mot SQL-injektion. Men eftersom magic quotes ofta är aktiverat på servrar världen över kan det inte ignoreras.

Följande kod åtgärdar problemet:

if ( get_magic_quotes_gpc() && ( ! ini_get('magic_quotes_sybase') ) ) {
    array_walk_recursive($_GET,   'stripslashes');
    array_walk_recursive($_POST,  'stripslashes');
    array_walk_recursive($_COOKIE,'stripslashes');
}

Men nu står vi utan skydd helt och hållet!

Dags att validera och filtrera indata.

Utför noggranna manuella kontroller på all indata. Skall en ålder uppges? Kontrollera att du fått en siffra. En mejladress, kontrollera att det uppfyller åtminstone minimikraven på att vara en rimlig adress. (Det är en avancerad vetenskap att verkligen kontrollera addresser...) Kontrollera att ett minimum finns av tecken när så krävs, liksom att man inte överskrider maxgränser.

Ev. kontroller på klientsidan är endast ett komplement till dessa, för att avlasta servern och skapa bättre användbarhet.

Tillåt aldrig formulärdata som inte kommit från ditt eget formulär.

Först nu är det dags att titta på databakopplingen. Här följer ett utkast enligt mitt tycke och smak.

/**
 * Denna klass används för att instansiera eller hämta en databaskoppling
 *
 * Syftet med att använda factory-pattern (kring en singleton instans av ett PDO-objekt)
 * är att:
 * 1. Underlätta växlingen mellan just PDO (eller annat sätt att ansluta till databasen)
 * 2. Snyggare kod i control-skripten ;-)
 * 3. Ställa in alla per-connection inställningar
 * Klassen keryxDB_cx fungerar enligt följande:
 * 1. Om objektet redan finns, returnera det.
 * 2. Annars, instansiera singleton, ställ in värden och returnera objektet.
 *
 * @author     Lars Gunther <gunther@keryx.se>
 * @copyright  Lars Gunther <gunther@keryx.se>
 * @license    Creative Commons Attribution-Noncommercial-Share Alike 3.0 http://creativecommons.org/licenses/by-nc-sa/3.0/
 * @package    KeryxDB
 * @filesource
 *
 * @todo Felhantering
 * @todo Tidszon etc funkar ännu bara i MySQL
 */
class keryxDB_cx
{
    /**
     * Felmeddelanden
     */
    const BAD_PARAMETER = 'keryxDB_cx::get anropad med felaktig parameter: %s.'; 
    const UNSUP_DRIVER  = 'Stöd saknas ännu i applikationen för denna driver: %s';
    /**
     * Felmeddelanden
     * @var object
     */
    private static $db;
    /**
     * Denna metod instansierar en databaskoppling
     * 
     * Metoden kan anropas hur många gånger som helst utan att man upprepar åtgärderna
     *
     * Indata ('dbuser', 'dbpass', 'dbtype', 'dsn' och ev. 'dbtime') hämtas ifrån {@see keryxApp_appRegistry}
     *
     * @return object Databasobjekt
     * @throws Exception vid fel.
     */
    public static function get()
    {
        if ( is_null(self::$db) ) {
            // Kontroll av "parametrar"
            $dbtype = keryxApp_appRegistry::get('dbtype');
            $dsn    = keryxApp_appRegistry::get('dsn');
            $dbuser = keryxApp_appRegistry::get('dbuser');
            $dbpass = keryxApp_appRegistry::get('dbpass');
            try {
                self::$db = new PDO($dsn,$dbuser,$dbpass);
                self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
                // @todo Stöd för alternativa drivers
                if ( self::$db->getAttribute(PDO::ATTR_DRIVER_NAME) != 'mysql' ) {
                    throw new Exception(sprintf(self::UNSUP_DRIVER,self::$db->getAttribute(PDO::ATTR_DRIVER_NAME)));
                }
                // Se http://netevil.org/blog/2006/apr/using-pdo-mysql
                self::$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
                self::$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);
                // Databasförbindelsens inställningar
                // @todo: Delegera nedanstående till specifika metoder per DBMS
                // Default exception handlers gäller från och med nu. Därför inga try-catch
                // Tidszon per connection kräver MySQL 4.1 eller senare och data i MySQL:s tidszonstabeller
                $dbtime = keryxApp_appRegistry::get('dbtime')  ?
                      keryxApp_appRegistry::get('dbtime') : 'Europe/Stockholm' ;
                $ts_sql   = "SET time_zone = '$dbtime'";
                $svar     = self::$db->query($ts_sql);
                // Lite inställningar för MySQL, se http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
                // Tolerera INGA fel under utveckling, bli generösare under drift.
                // Mina ändringar från default: STRICT_TRANS_TABLES -> STRICT_ALL_TABLES, NO_ZERO_DATE
                // NO_AUTO_CREATE_USER och NO_ENGINE_SUBSTITUTION är på som standard, men vanliga
                // PHP-skript borde aldrig utföra operationer där åtgärder de reglerar förekommer
                // För maximal portabilitet, överväg ANSI (ej implementerat här)
                $mode_sql = "SET SESSION sql_mode = 'STRICT_ALL_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE'";
                $svar     = self::$db->query($mode_sql);
            }
            catch(Exception $e) { 
                // Felhantering saknas
                // under experimentstadiet kan följande användas:
                echo "<pre>\n"; var_dump($e); echo "</pre>\n"; exit;
            }
        }
        return self::$db;
    }
    // Förhindra kloning eller instansiering
    final private function __clone() { }
    final private function __construct() { }
}

Som synes är det fritt fram att använda koden till icke-kommersiella projekt.

Närhelst man vill ha en databaskoppling skriver man bara:

// Om man autoladdar sina klasser så behövs inte nästa rad
require_once('keryxDB/cx.php'); // PEAR namngivningskonvention

$db = keryxDB_cx::get();

Sedan är det bara att tuta och köra med sina prepared statements.

Ett enklare exempel på uppkoppling och exempel på prepared statements finns här:

http://blog.c0la.se/blog/63

Mer info (i PDF-format) finns här:
http://images.omniti.net/omniti.com/talks/furlong-pdo-long.pdf
http://netevil.org/downloads/pdo-mysql.pdf
http://netevil.org/talks/PHP-Data-Objects.pdf
http://images.omniti.net/omniti.com/talks/pm_security_2007.pdf

Manualen:
http://php.net/pdo

Metoden closeCursor brukar vara vanligast att man missar som nybörjare.

Lars Gunther

GunnarDMedlem sedan juni 20014 290 inlägg
#2

Räcker det inte med en tråd om du vill klaga, måste du skapa 2 lika dana trådar?

Känns som du vill göra reklam för din lilla class, men det kanske hjälper någon.

itpastornMedlem sedan feb. 2007278 inlägg
#3

I mitt förra inlägg försökte jag tipsa om bättre och säkrare anslutning till en databas som svar på en fråga. Då valde jag att visa den icke-objektorienterade varianten, för att hålla det enkelt.

När jag gjorde det insåg jag att det kanske kunde finnas ett värde i att tillhandahålla en mer fullfjädrad version också och skapade denna tråd.

Dessutom har PHP som språk ett gigantiskt problem med att det oförtjänt uppfattas som osäkert. Ruby, asp, etc har liknande säkerhetsaspekter som PHP, men inte lika många nybörjare som - sig själva ovetande - kodar skräpkod. Det är ett gigantiskt problem att det ligger tusentals tutorials ute på nätet som lär ut dålig kodning i PHP.

Lyssna på expertisen. Har du läst länken jag hade till Ilia Alshanetskys jämförelse av prepared statements och mysql_real_escape_string() ?

Har du läst vad Chris Schifflett skriver? Wez Furlong? PHP Security Consortium? Killarna på Zend? Omni TI?

Två snabba tips:
Zends podcast: http://devzone.zend.com/node/view/id/2046
PHP Architects podcast: http://podcast.phparch.com/

Lars Gunther

fiddlerMedlem sedan juli 20023 617 inlägg
#4

Tack för länkarna pastorn! Skall studeras. :)

TroxyMedlem sedan mars 20041 505 inlägg
#5

PDO rocks helt enkelt. Har använt det ett tag nu och det är helt underbart. :h
Inte lika smidigt kanske, men helt klart värt att byta till.
För mig är det helt obegripligt varför så många fortfarande hänger kvar vid de gamla mysql_*-funktionerna när det finns fullgoda alternativ som är säkrare.
För mig så räcker det horribla funktionsnamnet "mysql_real_escape_string" som argument för att byta till PDO. :P
Idag bygger vissa t.o.m. sina SQL-frågor baserade på magic_quotes, omg...

TroxyMedlem sedan mars 20041 505 inlägg
#6

itpastorn skrev:

Mer info (i PDF-format) finns här:
http://images.omniti.net/omniti.com/talks/furlong-pdo-long.pdf

Det där är ju material till en presentation, som onekligen verkar intressant.
Går det att få tag i själva presentationen någonstans?

itpastornMedlem sedan feb. 2007278 inlägg
#7

Troxy skrev:

Det där är ju material till en presentation, som onekligen verkar intressant.
Går det att få tag i själva presentationen någonstans?

Inte vad jag vet, men man lär sig ganska mycket av att läsa slides också.

Apropå slides: Ingen har väl missat http://talks.php.net/ ?

Där finns de flesta av Rasmus L:s tal.

TroxyMedlem sedan mars 20041 505 inlägg
#8

itpastorn, själv blir jag bara irriterad av att läsa en slide där det står ex. "Data Typing: Very loose" utan någon som helst förklaring.

135 ms totalt · 3 externa anrop · v20260731065814-full.29ac60f6
124 ms — hämta forumlista (db)
0 ms — hämta statistik (cache)
131 ms — hämta tråd, inlägg och bilagor (db)