SQL-Injection, die mysql_real_escape_string () umgeht
Gibt es eine SQL-Injection-Möglichkeit, auch wenn die mysql_real_escape_string()
Funktion verwendet wird?
Betrachten Sie diese Beispielsituation. SQL ist in PHP wie folgt aufgebaut:
$login = mysql_real_escape_string(GetFromPost('login')); $password = mysql_real_escape_string(GetFromPost('password'));
$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";
Ich habe zahlreiche Leute zu mir sagen hören, dass ein solcher Code immer noch gefährlich ist und auch mit der verwendeten mysql_real_escape_string()
Funktion gehackt werden kann. Aber ich kann mir keinen möglichen Exploit vorstellen?
Klassische Injektionen wie diese:
aaa' OR 1=1 --
arbeite nicht.
Kennen Sie eine mögliche Injektion, die durch den obigen PHP-Code gelangen würde?
Antworten
Betrachten Sie die folgende Abfrage:
$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";
mysql_real_escape_string()
wird dich nicht davor schützen. Die Tatsache, dass Sie ' '
in Ihrer Abfrage einfache Anführungszeichen ( ) um Ihre Variablen verwenden, schützt Sie davor. Folgendes ist ebenfalls eine Option:
$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";
Die kurze Antwort lautet: Ja, ja, es gibt einen Weg, sich fortzubewegenmysql_real_escape_string()
. #Für sehr OBSCURE EDGE CASES !!!
Die lange Antwort ist nicht so einfach. Es basiert auf einem hier demonstrierten Angriff .
Der Angriff
Beginnen wir also damit, den Angriff zu zeigen ...
mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*"); mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
Unter bestimmten Umständen wird mehr als eine Zeile zurückgegeben. Lassen Sie uns analysieren, was hier los ist:
Zeichensatz auswählen
mysql_query('SET NAMES gbk');
Für diesen Angriff zu arbeiten, müssen wir die Codierung , dass der Server die auf der Verbindung erwartet sowohl codieren
'
als in ASCII dh0x27
und einige Zeichen zu haben , deren letzte Byte ist ein ASCII\
dh0x5c
. Wie sich herausstellt, gibt es 5 solche Codierungen in MySQL 5.6 standardmäßig unterstützt:big5
,cp932
,gb2312
,gbk
undsjis
. Wir werdengbk
hier auswählen .Nun ist es sehr wichtig, die Verwendung von
SET NAMES
hier zu beachten . Dies legt den Zeichensatz auf dem Server fest . Wenn wir den Aufruf der C-API-Funktion verwenden würdenmysql_set_charset()
, wäre dies in Ordnung (bei MySQL-Versionen seit 2006). Aber mehr dazu in einer Minute ...Die Nutzlast
Die Nutzlast, die wir für diese Injektion verwenden werden, beginnt mit der Bytesequenz
0xbf27
. Ingbk
ist das ein ungültiges Multibyte-Zeichen. inlatin1
, es ist die Zeichenfolge¿'
. Beachten Sie, dass inlatin1
undgbk
,0x27
auf seinem eigenen ein wörtlicher ist'
Charakter.Wir haben diese Nutzlast gewählt, weil wir, wenn wir
addslashes()
sie aufrufen würden, ein ASCII einfügen würden,\
dh0x5c
vor dem'
Zeichen. Also haben wir mit aufzuwickeln würde0xbf5c27
, die ingbk
eine zwei Zeichenfolge:0xbf5c
gefolgt von0x27
. Oder mit anderen Worten, ein gültiges Zeichen, gefolgt von einem nicht entflohenen'
. Aber wir benutzen nichtaddslashes()
. Also weiter zum nächsten Schritt ...mysql_real_escape_string ()
Der C-API-Aufruf von
mysql_real_escape_string()
unterscheidet sichaddslashes()
darin, dass er den Verbindungszeichensatz kennt. So kann die Escape-Anweisung für den vom Server erwarteten Zeichensatz ordnungsgemäß ausgeführt werden. Bis zu diesem Punkt glaubt der Client jedoch, dass wir immer nochlatin1
für die Verbindung verwenden, da wir es nie anders gesagt haben. Wir haben dem Server mitgeteiltgbk
, dass wir ihn verwenden , aber der Client glaubt immer noch, dass dies der Fall istlatin1
.Daher der Aufruf,
mysql_real_escape_string()
den Backslash einzufügen, und wir haben einen frei hängenden'
Charakter in unserem "entkommenen" Inhalt! Wenn wir uns$var
dengbk
Zeichensatz ansehen würden, würden wir tatsächlich sehen:縗 'OR 1 = 1 / *
Welches ist genau das, was der Angriff erfordert.
Die Abfrage
Dieser Teil ist nur eine Formalität, aber hier ist die gerenderte Abfrage:
SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
Herzlichen Glückwunsch, Sie haben gerade erfolgreich ein Programm mit mysql_real_escape_string()
...
Das Schlechte
Es wird schlimmer. PDO
Standardmäßig werden vorbereitete Anweisungen mit MySQL emuliert . Das bedeutet, dass auf der Client-Seite im Grunde genommen ein Sprintf-Through mysql_real_escape_string()
(in der C-Bibliothek) durchgeführt wird, was bedeutet, dass Folgendes zu einer erfolgreichen Injektion führt:
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
Nun ist es erwähnenswert, dass Sie dies verhindern können, indem Sie emulierte vorbereitete Anweisungen deaktivieren:
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Dies führt normalerweise zu einer wirklich vorbereiteten Anweisung (dh die Daten werden in einem von der Abfrage getrennten Paket gesendet). Beachten Sie jedoch, dass PDO leise Rückfall auf Anweisungen emuliert , dass MySQL nicht nativ vorbereiten können: diejenigen , die es können , sind aufgelistet in dem Handbuch, aber passen Sie den entsprechenden Server - Version wählen).
Das hässliche
Ich sagte ganz am Anfang, dass wir all dies hätten verhindern können, wenn wir mysql_set_charset('gbk')
stattdessen verwendet hätten SET NAMES gbk
. Und das stimmt, vorausgesetzt, Sie verwenden seit 2006 eine MySQL-Version.
Wenn Sie eine frühere MySQL - Release verwenden, dann einen Fehler in mysql_real_escape_string()
gemeint , dass ungültige Mehrbytezeichen wie die in unserer Nutzlast als einzelnes Bytes behandelt wurden , für die Zwecke entkommen , auch wenn der Kunde hat richtig die Verbindung Codierung informiert worden und so dieser Angriff würde immer noch erfolgreich. Der Fehler wurde in MySQL 4.1.20 , 5.0.22 und 5.1.11 behoben .
Aber das Schlimmste ist , dass PDO
nicht den C - API für belichten haben mysql_set_charset()
bis 5.3.6, so in früheren Versionen es kann nicht verhindern , diesen Angriff für jeden möglichen Befehl! Es wird jetzt als DSN-Parameter angezeigt .
Die rettende Gnade
Wie eingangs erwähnt, muss die Datenbankverbindung mit einem anfälligen Zeichensatz codiert werden, damit dieser Angriff funktioniert. utf8mb4ist nicht anfällig und kann dennoch jedes Unicode-Zeichen unterstützen. Sie können sich also dafür entscheiden, dieses zu verwenden. Es ist jedoch erst seit MySQL 5.5.3 verfügbar. Eine Alternative ist utf8, die ebenfalls nicht anfällig ist und die gesamte mehrsprachige Unicode- Grundebene unterstützen kann .
Alternativ können Sie den NO_BACKSLASH_ESCAPESSQL-Modus aktivieren , der (unter anderem) den Betrieb von ändert mysql_real_escape_string()
. Wenn dieser Modus aktiviert ist, 0x27
wird er durch 0x2727
anstelle von ersetzt 0x5c27
und daher kann der Escape-Prozess keine gültigen Zeichen in einer der anfälligen Codierungen erstellen, in denen sie zuvor nicht vorhanden waren (dh 0xbf27
immer noch 0xbf27
usw.). Daher lehnt der Server die Zeichenfolge weiterhin als ungültig ab . In der Antwort von @ eggyal finden Sie jedoch eine andere Sicherheitsanfälligkeit, die sich aus der Verwendung dieses SQL-Modus ergeben kann.
Sichere Beispiele
Die folgenden Beispiele sind sicher:
mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*"); mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
Weil der Server erwartet utf8
...
mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*"); mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
Weil wir den Zeichensatz richtig eingestellt haben, damit Client und Server übereinstimmen.
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
Weil wir emulierte vorbereitete Anweisungen deaktiviert haben.
$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
Weil wir den Zeichensatz richtig eingestellt haben.
$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*"; $stmt->bind_param('s', $param); $stmt->execute();
Weil MySQLi die ganze Zeit wirklich vorbereitete Anweisungen ausführt.
Einpacken
Wenn du:
- Verwenden Sie moderne Versionen von MySQL (Ende 5.1, alle 5.5, 5.6 usw.) UND
mysql_set_charset()
/$mysqli->set_charset()
/ PDOs DSN-Zeichensatzparameter (in PHP ≥ 5.3.6).
ODER
- Verwenden Sie keinen anfälligen Zeichensatz für die Verbindungscodierung (Sie verwenden nur
utf8
/latin1
/ascii
/ etc).
Du bist 100% sicher.
Andernfalls sind Sie verwundbar , obwohl Siemysql_real_escape_string()
...
TL; DR
mysql_real_escape_string()
wird bieten keinerlei Schutz (weiterhin und könnte Ihre Daten munge) , wenn:
Der NO_BACKSLASH_ESCAPESSQL-Modus von MySQL ist aktiviert (was möglicherweise der Fall ist, es sei denn, Sie wählen bei jeder Verbindung explizit einen anderen SQL-Modus aus ). und
Ihre SQL-Zeichenfolgenliterale werden in Anführungszeichen gesetzt
"
.Dies wurde als Fehler # 72458 abgelegt und in MySQL v5.7.6 behoben (siehe Abschnitt " The Saving Grace " weiter unten).
Dies ist ein weiterer, (vielleicht weniger?) Dunkler EDGE CASE !!!
Als Hommage an @ ircmaxells ausgezeichnete Antwort (eigentlich soll dies Schmeichelei und kein Plagiat sein!) Werde ich sein Format übernehmen:
Der Angriff
Beginnen Sie mit einer Demonstration ...
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- '); mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
Dadurch werden alle Datensätze aus der test
Tabelle zurückgegeben. Eine Dissektion:
Auswählen eines SQL-Modus
mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
Wie unter String Literals dokumentiert :
Es gibt verschiedene Möglichkeiten, Anführungszeichen in eine Zeichenfolge aufzunehmen:
Ein "
'
" in einer mit "'
" zitierten Zeichenfolge kann als "''
" geschrieben werden.Ein "
"
" in einer mit ""
" zitierten Zeichenfolge kann als """
" geschrieben werden.Vor dem Anführungszeichen steht ein Escape-Zeichen ("
\
").Ein "
'
" in einer mit ""
" zitierten Zeichenfolge erfordert keine besondere Behandlung und muss nicht verdoppelt oder maskiert werden. Ebenso bedarf „"
“ in einer mit „'
“ zitierten Zeichenfolge keiner besonderen Behandlung.
Wenn der SQL-Modus des Servers Folgendes enthält NO_BACKSLASH_ESCAPES, ist die dritte dieser Optionen - der übliche Ansatz von
mysql_real_escape_string()
- nicht verfügbar: Stattdessen muss eine der ersten beiden Optionen verwendet werden. Beachten Sie, dass der Effekt des vierten Aufzählungszeichens darin besteht, dass man unbedingt das Zeichen kennen muss, das zum Zitieren des Literals verwendet wird, um zu vermeiden, dass die eigenen Daten durcheinander gebracht werden.Die Nutzlast
" OR 1=1 --
Die Nutzlast initiiert diese Injektion buchstäblich mit dem
"
Charakter. Keine besondere Kodierung. Keine Sonderzeichen. Keine seltsamen Bytes.mysql_real_escape_string ()
$var = mysql_real_escape_string('" OR 1=1 -- ');
Zum Glück wird
mysql_real_escape_string()
der SQL-Modus überprüft und sein Verhalten entsprechend angepasst. Siehe libmysql.c:ulong STDCALL mysql_real_escape_string(MYSQL *mysql, char *to,const char *from, ulong length) { if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) return escape_quotes_for_mysql(mysql->charset, to, 0, from, length); return escape_string_for_mysql(mysql->charset, to, 0, from, length); }
Daher wird eine andere zugrunde liegende Funktion
escape_quotes_for_mysql()
aufgerufen, wenn derNO_BACKSLASH_ESCAPES
SQL-Modus verwendet wird. Wie oben erwähnt, muss eine solche Funktion wissen, mit welchem Zeichen das Literal zitiert wird, um es zu wiederholen, ohne dass das andere Anführungszeichen wörtlich wiederholt wird.Diese Funktion setzt jedoch willkürlich voraus, dass die Zeichenfolge in Anführungszeichen gesetzt
'
wird. Siehe charset.c:/* Escape apostrophes by doubling them up // [ deletia 839-845 ] DESCRIPTION This escapes the contents of a string by doubling up any apostrophes that it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in effect on the server. // [ deletia 852-858 ] */ size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info, char *to, size_t to_length, const char *from, size_t length) { // [ deletia 865-892 ] if (*from == '\'') { if (to + 2 > to_end) { overflow= TRUE; break; } *to++= '\''; *to++= '\''; }
Daher
"
bleiben Zeichen in doppelten Anführungszeichen unberührt (und verdoppeln alle'
Zeichen in einfachen Anführungszeichen ), unabhängig von dem tatsächlichen Zeichen, mit dem das Literal zitiert wird ! In unserem Fall$var
bleibt genau das gleiche wie das Argument, die zur Verfügung gestellt wurdemysql_real_escape_string()
-es ist , als ob kein Entkommen stattgefunden hat überhaupt .Die Abfrage
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
Die gerenderte Abfrage ist eine Art Formalität:
SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1
Wie mein gelehrter Freund es ausdrückte: Herzlichen Glückwunsch, Sie haben gerade erfolgreich ein Programm mit mysql_real_escape_string()
...
Das Schlechte
mysql_set_charset()kann nicht helfen, da dies nichts mit Zeichensätzen zu tun hat; Das kann auch nicht mysqli::real_escape_string(), da dies nur ein anderer Wrapper um dieselbe Funktion ist.
Das Problem ist, wenn auch nicht bereits offensichtlich, dass der Aufruf, mysql_real_escape_string()
nicht zu wissen, mit welchem Zeichen das Literal zitiert wird, da dies dem Entwickler überlassen bleibt, um zu einem späteren Zeitpunkt zu entscheiden. Im NO_BACKSLASH_ESCAPES
Modus gibt es also buchstäblich keine Möglichkeit, dass diese Funktion sicher jeder Eingabe für die Verwendung mit willkürlichen Anführungszeichen entgeht (zumindest nicht ohne das Verdoppeln von Zeichen, die kein Verdoppeln und damit das Munging Ihrer Daten erfordern).
Das hässliche
Es wird schlimmer. NO_BACKSLASH_ESCAPES
In freier Wildbahn ist dies möglicherweise nicht allzu ungewöhnlich, da es für die Kompatibilität mit Standard-SQL erforderlich ist (siehe z. B. Abschnitt 5.3 der SQL-92-Spezifikation , nämlich die <quote symbol> ::= <quote><quote>
Grammatikproduktion und das Fehlen einer besonderen Bedeutung für Backslash). Darüber hinaus wurde die Verwendung ausdrücklich als Problemumgehung für den (längst behobenen ) Fehler empfohlen, den der Beitrag von ircmaxell beschreibt. Wer weiß, einige Datenbankadministratoren konfigurieren es möglicherweise sogar so, dass es standardmäßig aktiviert ist, um die Verwendung falscher Escape-Methoden wie z addslashes().
Außerdem wird der SQL-Modus einer neuen Verbindung vom Server entsprechend seiner Konfiguration festgelegt (die ein SUPER
Benutzer jederzeit ändern kann). Um sicherzugehen, dass sich der Server verhält, müssen Sie nach dem Herstellen der Verbindung immer explizit den gewünschten Modus angeben.
Die rettende Gnade
Solange Sie den SQL-Modus immer explizit so einstellen NO_BACKSLASH_ESCAPES
, dass MySQL-Zeichenfolgenliterale nicht mit dem einfachen Anführungszeichen eingeschlossen oder zitiert werden, kann dieser Fehler seinen hässlichen Kopf nicht aufrichten: escape_quotes_for_mysql()
wird nicht verwendet, oder die Annahme, welche Anführungszeichen wiederholt werden müssen, wird richtig sein.
Aus diesem Grund empfehle ich jedem, der NO_BACKSLASH_ESCAPES
auch den ANSI_QUOTESModus aktiviert , da dadurch die gewohnheitsmäßige Verwendung von String-Literalen in einfachen Anführungszeichen erzwungen wird. Beachten Sie, dass dies die SQL-Injection nicht verhindert, falls Literale in doppelten Anführungszeichen verwendet werden. Es verringert lediglich die Wahrscheinlichkeit, dass dies geschieht (da normale, nicht böswillige Abfragen fehlschlagen würden).
In PDO PDO::quote()rufen sowohl die äquivalente Funktion als auch der vorbereitete Anweisungsemulator auf mysql_handle_quoter()- was genau dies tut: Er stellt sicher, dass das maskierte Literal in einfache Anführungszeichen gesetzt wird, sodass Sie sicher sein können, dass PDO immer gegen diesen Fehler immun ist.
Ab MySQL v5.7.6 wurde dieser Fehler behoben. Siehe Änderungsprotokoll :
Funktionalität hinzugefügt oder geändert
Inkompatible Änderung: Eine neue C-API-Funktion wurdemysql_real_escape_string_quote()als Ersatz für implementiert,mysql_real_escape_string()da die letztere Funktion Zeichenmöglicherweisenicht richtig codieren kann, wenn derNO_BACKSLASH_ESCAPESSQL-Modus aktiviert ist. In diesem Fallmysql_real_escape_string()können Anführungszeichen nur durch Verdoppeln entgangen werden. Um dies ordnungsgemäß zu tun, müssen mehr Informationen über den Anführungskontext bekannt sein, als verfügbar sind. mysql_real_escape_string_quote()Nimmt ein zusätzliches Argument für die Angabe des Angebotskontexts. Einzelheiten zur Verwendung finden Sie unter mysql_real_escape_string_quote () .
Hinweis
Anwendungen sollten so geändert werden mysql_real_escape_string_quote(), dass sie anstelle von verwendet mysql_real_escape_string()werden. Dies schlägt jetzt fehl und führt zu einem CR_INSECURE_API_ERRFehler, wenn NO_BACKSLASH_ESCAPESes aktiviert ist.
Referenzen: Siehe auch Bug # 19211994.
Sichere Beispiele
Zusammen mit dem von ircmaxell erläuterten Fehler sind die folgenden Beispiele völlig sicher (vorausgesetzt, man verwendet MySQL später als 4.1.20, 5.0.22, 5.1.11; oder man verwendet keine GBK / Big5-Verbindungscodierung). ::
mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*'); mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
... weil wir explizit einen SQL-Modus ausgewählt haben, der nicht enthalten ist NO_BACKSLASH_ESCAPES
.
mysql_set_charset($charset); $var = mysql_real_escape_string("' OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
... weil wir unser String-Literal in einfache Anführungszeichen setzen.
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $stmt->execute(["' OR 1=1 /*"]);
... weil von PDO vorbereitete Anweisungen gegen diese Sicherheitsanfälligkeit immun sind (und auch von ircmaxell, vorausgesetzt, Sie verwenden PHP ≥ 5.3.6 und der Zeichensatz wurde im DSN korrekt festgelegt, oder die Emulation vorbereiteter Anweisungen wurde deaktiviert). .
$var = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");
... weil die quote()
Funktion von PDO dem Literal nicht nur entgeht, sondern es auch zitiert (in einfachen Anführungszeichen '
); Beachten Sie, dass Sie PHP ≥ 5.3.6 verwenden und den Zeichensatz im DSN korrekt eingestellt haben müssen , um den Fehler von ircmaxell in diesem Fall zu vermeiden .
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1'); $param = "' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
... weil von MySQLi vorbereitete Anweisungen sicher sind.
Einpacken
Also, wenn Sie:
- Verwenden Sie native vorbereitete Anweisungen
ODER
- Verwenden Sie MySQL v5.7.6 oder höher
ODER
in zusätzlich eine der Lösungen in ircmaxell Zusammenfassung, die Verwendung mindestens eines zu beschäftigen:
- PDO;
- String-Literale in einfachen Anführungszeichen; oder
- Ein explizit festgelegter SQL-Modus, der nicht enthalten ist
NO_BACKSLASH_ESCAPES
... dann sollten Sie absolut sicher sein (Schwachstellen außerhalb des Bereichs, in dem Zeichenfolgen ausgeblendet werden).
Nun, es gibt nichts, was das wirklich passieren kann, außer %
Platzhalter. Es könnte gefährlich sein, wenn Sie eine LIKE
Anweisung verwenden, da der Angreifer sich genauso %
anmelden könnte, wenn Sie dies nicht herausfiltern, und nur ein Kennwort eines Ihrer Benutzer brutal erzwingen müsste. Oft wird empfohlen, vorbereitete Anweisungen zu verwenden, um die Sicherheit zu 100% zu gewährleisten, da Daten die Abfrage selbst auf diese Weise nicht stören können. Aber für solch einfache Abfragen wäre es wahrscheinlich effizienter, so etwas zu tun$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);