Archive for the ‘PHP’ Category
Optimierung meines Blogs
SEO ist in aller Munde. Es gibt Millionen von SEO-Experten, die einen „100%-ig auf Platz 1 bei Google“ bringen können. Natürlich für viel Geld, und mit komischen, teils verbotenen Methoden.
Ich verlasse mich da lieber auf die kleinen Tipps und Tricks, die man so liest, und die auch Sinn machen. Auch Google gibt Webmastern Tipps, um ihre Seiten gut indizierbar zu machen, und so einen guten Pagerank zu erhalten. Interessante Hintergrund-Informationen gibt es zB bei Google’s Webmaster Tools.
Ich möchte hier keine riesen Sammlung eröffnen, sondern nur das WordPress-Plugin „All in One SEO Pack“ vorstellen. Dieses Plugin hilft dabei, auf jeder Seite unterschiedliche und vor allem passende Keywords und Descriptions setzen zu lassen (automatisch extrahiert aus dem Artikel-Text). Auch den Titel kann man auf jeder Seite anpassen lassen. Des weiteren unterstützt es die Möglichkeit, doppelte Inhalte unter verschiedenen URLs zu vermeiden mit dem „canonical“-Tag.
Damit sind die wichtigsten Dinge erstmal abgedeckt. Alle anderen Meta-Tags sind heutzutage relativ unwichtig geworden, jedenfalls liest man das oft.
Schaut es euch selbst an, ich finde es sehr hilfreich, und werde es auch vorerst dabei belassen.
So sehen die Optionen aus:
CSS aufräumen mit „Dust-Me Selectors“
Ein tolles Firefox-Addon habe ich in meinem Repertoire gefunden, das ich euch vorstellen möchte: Dust-Me Selectors.
Damit kann man einfach seine Seite durch browsen, und das Addon zeigt an, welche CSS-Selektoren geladen werden, welche davon genutzt werden und welche nicht genutzt werden. Es findet sowohl inline-CSS als auch externe CSS-Dateien.
Alternativ kann man auch eine Sitemap angeben, die dann genutzt wird, um alle HTML-Seiten nach ungenutztem CSS zu durchsuchen.
Dann kann man sich speziell die nicht genutzten angucken, ob sie wirklich nicht mehr genutzt werden, und dann entfernen, um die CSS-Datei von Altlasten, Test-Selektoren usw zu befreien. Man sollte allerdings aufpassen beim Entfernen, es könnte ja sein, dass man einfach die eine Seite vergessen hat, wo der spezielle Selektor genutzt wird. Ich durchsuche auf jeden Fall noch meine Templates und View-Scripte nach dem Selector, bevor ich ihn lösche.
Mehr wollte ich eigentlich heute garnicht schreiben 😉
Externe Javascript Dateien zusammenfassen
Wir alle wissen: Langsame Webseiten bekommen weniger Besucher. Dazu gibt es auch Auswertungen von großen Portalen und Suchmaschinen.
Doch wie beschleunigt man die eigene Webseite? Häufig übersieht man einen wichtigen Teil: Das Frontend bzw. den Client.
Mag das PHP-Script auch sehr schnell sein (die Zeit gemessen mit microtime() ergibt 0.1 Sekunden), die Seite kann sich trotzdem langsam anfühlen. Das liegt häufig an vielen externen Dateien, die der Browser noch nachladen muss. Dazu gehören sowohl CSS-Dateien, Javascript-Dateien, Bilder usw.
Wie erstelle ich einen Socket-Server in PHP?
In einem meiner letzten Artikel über Windows-Dienste habe ich ja bereits angesprochen, dass auch Dienste aller Art in PHP realisiert werden können. In jenem Artikel erwähnte ich auch, dass es bereits Webserver, FTP-Server, DNS-Server etc. in PHP gibt.
Heute zeige ich euch, wie man das machen kann. Grundsätzlich haben all diese Dienste gemeinsam, dass sie auf einem TCP-Port auf Verbindungen warten, und dort im Verbindungsfall Befehle entgegennehmen, Aktionen durchführen und Daten zurückliefern können.
Hier möchte ich einen kleinen Chat-Server erstellen, mit dem man sich verbinden kann, und mit allen anderen verbundenen Clients chatten kann. Dieser Chat-Server soll auch gleich als Dienst permanent laufen.
Dazu benötigen wir sogenannte Socket-Funktionen, die PHP seit Version 4.3 bietet. Die entsprechende Extension ist bereits in PHP enthalten, muss aber evtl. in der php.ini noch aktiviert werden:
extension=php_sockets.dll
Dann kann es auch schon loslegen. Das grobe Konzept: Wir erstellen ein Socket und lassen diesen auf Port 33380 lauschen. Wenn eine Verbindung reinkommt, begrüßen wir den neuen Benutzer und fügen ihn einem Array hinzu. Sollte sich ein weitere Benutzer verbinden, tun wir natürlich das selbe. Schreibt ein Benutzer irgendetwas, wird es an alle anderen Benutzer broadcasted. Es soll auch Spezial-Kommandos geben: mit „exit“ oder „quit“ trennt sich der Benutzer vom Server. Mit „term“ stoppt er den Chatserver.
Genug geschwatzt, hier der Code der Klasse:
class SocketChatServer { private $address = '0.0.0.0'; // 0.0.0.0 means all available interfaces private $port = 33379; // the TCP port that should be used private $maxClients = 10; private $clients; private $socket; public function __construct() { // Set time limit to indefinite execution set_time_limit(0); error_reporting(E_ALL ^ E_NOTICE); } public function start() { // Create a TCP Stream socket $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // Bind the socket to an address/port socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, 1); socket_bind($this->socket, $this->address, $this->port); // Start listening for connections socket_listen($this->socket, $this->maxClients); $this->clients = array('0' => array('socket' => $this->socket)); while (true) { // Setup clients listen socket for reading $read[0] = $this->socket; for($i=1; $i<count($this->clients)+1; ++$i) { if($this->clients[$i] != NULL) { $read[$i+1] = $this->clients[$i]['socket']; } } // Set up a blocking call to socket_select() $ready = socket_select($read, $write = NULL, $except = NULL, $tv_sec = NULL); /* if a new connection is being made add it to the client array */ if(in_array($this->socket, $read)) { for($i=1; $i < $this->maxClients+1; ++$i) { if(!isset($this->clients[$i])) { $this->clients[$i]['socket'] = socket_accept($this->socket); socket_getpeername($this->clients[$i]['socket'], $ip); $this->clients[$i]['ipaddy'] = $ip; socket_write($this->clients[$i]['socket'], 'Welcome to my Custom Socket Server'."\r\n"); socket_write($this->clients[$i]['socket'], 'There are '.(count($this->clients) - 1).' client(s) connected to this server.'."\r\n"); $this->log("New client #$i connected: " . $this->clients[$i]['ipaddy']); break; } elseif($i == $this->maxClients - 1) { $this->log('Too many Clients connected!'); } if($ready < 1) { continue; } } } // If a client is trying to write - handle it now for($i=1; $i<$this->maxClients+1; ++$i) { if(in_array($this->clients[$i]['socket'], $read)) { $data = @socket_read($this->clients[$i]['socket'], 1024, PHP_NORMAL_READ); if($data === FALSE) { unset($this->clients[$i]); $this->log('Client disconnected!'); continue; } $data = trim($data); if(!empty($data)) { switch ($data) { case 'exit': case 'quit': socket_write($this->clients[$i]['socket'], "Thanks for trying my Custom Socket Server, Goodbye.\r\n"); $this->log("Client #$i is exiting"); unset($this->clients[$i]); continue; case 'term': // first write a message to all connected clients for($j=1; $j < $this->maxClients+1; ++$j) { if(isset($this->clients[$j]['socket'])) { if($this->clients[$j]['socket'] != $this->socket) { socket_write($this->clients[$j]['socket'], "Server will be shut down now...\r\n"); } } } // Close the master sockets, server termination requested socket_close($this->socket); $this->log("Terminated server (requested by client #$i)"); exit; default: for($j=1; $j < $this->maxClients+1; ++$j) { if(isset($this->clients[$j]['socket'])) { if(($this->clients[$j]['socket'] != $this->clients[$i]['socket']) && ($this->clients[$j]['socket'] != $this->socket)) { $this->log($this->clients[$i]['ipaddy'] . ' is sending a message to ' . $this->clients[$j]['ipaddy'] . '!'); socket_write($this->clients[$j]['socket'], '[' . $this->clients[$i]['ipaddy'] . '] says: ' . $data . "\r\n"); } } } break(2); } } } } } // end while } private function log($msg) { // instead of echoing to console we could write this to a database or a textfile echo "[".date('Y-m-d H:i:s')."] " . $msg . "\r\n"; } }
Dieser Code funktioniert sowohl unter Windows als auch unter Linux und kann dort als Dienst installiert werden.
Hier einige Screenshots von Clients und vom Server:
Man kann natürlich noch leicht weitere Kommandos implementieren, wie zB ein kleiner Login für Admins, die Liste aller Clients anzeigen, einzelne Clients kicken, die Konfiguration ändern (dann müßte man sie in eine .ini auslagern und neu laden können) usw.
Man könnte auch eine Admin-Webseite implementieren, d.h. wenn man sich mit einem Browser auf den Port verbindet, erkennt das der Dienst und bietet eine html-Oberfläche mit diverse Admin-Funktionen.
Es gibt viele Dinge, die man so realisieren kann. Beispielsweise ein Online-Multiplayer-Spiel in der Konsole oder sogar mit grafischer Java-Oberfläche, eine eigene Steuerung für seinen Server im Keller, einen eigenen Mailserver, ein Backend für Flash-Casual-Games (wenn HTTP zuviel Overhead hat oder zu langsam ist) und und und.
Wie PHP bei einer sehr großer Anzahl von Verbindungen skaliert weiß ich nicht, kann ja jemand von euch mal ausprobieren 😉
Was ist „Cross Site Request Forgery“ (CSRF)?
Der ein oder andere mag es schon mal gelesen haben, aber es fristet nach wie vor ein Nischen-Dasein: Cross-Site Request Forgery.
Dieses Sicherheitsproblem von Webseiten aller Art (es macht eigentlich nur Sinn wenn es einen Login auf der Seite gibt) ist recht verbreitet, und ich möchte es hier zusammenfassen und Beispiele zeigen, wie auch eure Seiten dafür anfällig sind. Und natürlich Tipps geben, was man dagegen tun kann.
Das Grundproblem ist schnell erklärt: Ein Browser nutzt Tabs oder auch neue Fenster, um mehrere Seiten parallel öffnen und anzeigen zu können. Nehmen wir an, in einem Tab ist die Seite www.xing.com geöffnet, und in einem anderen Tab die Seite www.phpgangsta.de . Browser sind nun so konstruiert, dass sich diese beiden Seiten im Prinzip nicht gegenseitig beeinflussen können, d.h. mittels Javascript kann ich von meiner Seite aus nicht auf den Inhalt der geöffneten Xing-Seite zugreifen (Same Origin Policy).
Nun logge ich mich auf Xing ein. Wenn ich nun einen weiteren Tab öffne und www.xing.com aufrufe, bin ich dort ebenfalls eingeloggt. Beide Tabs nutzen also die selben Cookies, und genau das machen wir uns zu Nutze.
Was passiert nun also, wenn ich auf meiner Seite (www.phpgangsta.de) ein Bild einbinde, das so aussieht:
<img src=“http://www.xing.com/app/message?op=sendmessage&recipient=MichaelKliewe&body=Viagra+For+Free“>
oder auch
<iframe width=“0″ height=“0″ src=“http://www.xing.com/app/message?op=sendmessage&recipient=MichaelKliewe&body=Viagra+For+Free“></iframe>
(Dies sind nur Beispiele, Xing ist natürlich gegen CSRF abgesichert)
Der Browser des Besuchers, der ja noch einen weiteren Tab mit dem eingeloggten Xing-Account geöffnet hat, ruft diese URL auf, und verschickt damit Nachrichten über Xing, ohne dass es der Besucher möchte!
Genauso kann es also mit jeglichen URLs passieren, die irgendwelche Aktionen auslösen, beispielsweise User löschen, das Profil ändern.
<img src=“http://www.deine-seite.de/admin/user_delete.php?id=1″>
<img src=“http://www.meine-seite.de/add_admin.php?name=hacker&pass=hack“>
Sollte derjenige also gerade auf der entsprechenden Seite sein (in einem anderen Tab) und die Rechte für die jeweilige Aktion haben, wird sie ausgeführt. Der Browser sendet bei dem Aufruf des Links das passende Cookie mit, womit die Rechte passen und die Aktion erfolgreich ist. Da sind extrem üble Sachen denkbar. Beispielsweise war GMail anfällig, sodass jeder euren GMail-Account „fernsteuern“ konnte (Emails schreiben, Emails löschen, Kontakte löschen usw). Da viele Leute Gmail nutzen und dort permanent eingeloggt sind, ist das gar nicht so unwahrscheinlich.
Oder aber stellt euch eine solche Webseite vor:
<html> Bei BrowsergameX gibt es ein Update. Bitte log dich ein und schau nach! <a href="http://www.browsergameX.de" target="_blank">BrowsergameX</a> <script> var url = 'http://www.browsergameX.de/transferMoney?amount=1000&target=1234567'; setTimeout(30000, "window.open(url)"); </script> </html>
Der User loggt sich also in seinem Spiel ein. Nach 30 Sekunden geht dann ein weiteres Fenster auf, und schwupps sind 1000 Geld-Einheiten eures Browsergame-Spielgelds weg.
Bei diesen Beispielen wurden nur GET-Parameter verwendet. Aber auch POST Parameter sind anfällig, es reicht also nicht, alles von GET auf POST zu ändern.
Sobald es dann um sensible Daten oder echtes Geld geht, ist der Spass vorbei. Was können wir also dagegen tun?
Wir müssen also sicherstellen, dass ein Link-Klick oder Formulardaten NUR von unserer Seite kommen kann. Erste Idee: Wir prüfen den Referrer, der müßte ja unsere Domain enthalten. Doch leider sendet nicht jeder Browser einen Referrer, und er lässt sich fälschen (Alles was von Clients kommt lässt sich fälschen und manipulieren!). Cookies lassen sich auch nicht nutzen.
Die einzig effektive Methode, dich aktuell bekannt ist, ist JEDEN Link und JEDES Formular um ein „secret token“ zu ergänzen, das nur wir kennen und dynamisch generieren. Außerdem darf es nur begrenzt haltbar und nicht erratbar sein. Dieses Token prüfen wir dann nach dem nächsten Request und wissen dann, ob es von uns generiert wurde.
Marc Jakubowski hat in seinem Blog sehr brauchbaren Quelltext veröffentlicht, den man mit ein paar Anpassungen (unten drunter stehen einige Verbesserungsvorschläge) gut nutzen kann.
Schon haben wir einen effektiven Schutz gegen CSRF. Alle geschützten Aktionen können nur noch ausgeführt werden, wenn vorher auf unserer Seite der entsprechende Token generiert wurde. Außerdem unterdrücken wir damit als Nebeneffekt das doppelte Absenden von Formularen.
Natürlich ist das kein 100%-iger Schutz. Durch einfaches Brute-Force oder beispielsweise mit Hilfe der Browser-History (CSS Browser History Vulnerability) kann man den Token erraten.
Bei der Recherche bin ich auch über eine interessante Präsentation von Stefan Esser zum Thema „Secure Programming with Zend Framework“ gestossen, worin auch das Thema CSRF kurz abgesprochen wird.
Mein letzter Tip, um euch als User im Internet zu schützen: Nutzt Firefox und das Addon NoScript, denn es blockt zB Cross-Domain Formulare und bietet auch Schutz gegen XSS.
Mehr Material über das Thema:
http://www.cgisecurity.com/csrf-faq.html
http://shiflett.org/articles/cross-site-request-forgeries