Alt 01.06.2006, 22:14:08   #1 (permalink)
theHacker PREMIUM-User
sieht vor lauter Ads
den Content nicht mehr
Benutzerbild von theHacker

ID: 69505
Lose senden

theHacker eine Nachricht über ICQ schicken theHacker eine Nachricht über Skype™ schicken
Reg: 20.04.2006
Beiträge: 22.685
Standard [PHP/MySQL] Vermeidung von SQL-Injections

Ich poste hier mal meine Funktion (hier vereinfacht, da ich eigentlich eine Klasse benutze), die ich immer für Datenbank-Abfragen verwende. Bei dieser Funktion wird jeder Parameter, der an die Datenbank übergeben wird, vorher escaped, d.h. es sind keine SQL-Injections möglich.
PHP-Code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function mysql_queryf($queryformat /* ,$queryarg1,$queryarg2,.... */)
{
    
$queryargs=func_get_args();
    
    
$vars=array();
    for(
$i=1;$i<count($queryargs);$i++)
        
$vars[]=mysql_real_escape_string($queryargs[$i]);
        
    
$querystring=vsprintf($queryformat,$vars);
    return 
mysql_query($querystring);

Benutzt wird sie so, wie man es von xprintf() gewohnt ist:
PHP-Code:
1:
2:
3:
4:
5:
// früher mit mysql_query() direkt:
$res=mysql_query("SELECT `field` FROM `table` WHERE `userid`=".$_POST['userid']." AND `pw`='".$_POST['pw']."'");

// besser mit mysql_queryf():
$res=mysql_queryf("SELECT `field` FROM `table` WHERE `userid`=%u AND `pw`='%s'",$_POST['userid'],$_POST['pw']); 
NEU theHacker.blog NEU
– It's just a glitch in the Matrix –

OpenIsles - das freie Insel-Aufbauspiel | www.theHacker.ws v3 | WhatPulse-Team
theHacker ist offline   Mit Zitat antworten
Alt 02.06.2006, 13:05:03   #2 (permalink)
MrToiz
Erfahrener Benutzer

ID: 72115
Lose senden

Reg: 28.04.2006
Beiträge: 769
Standard

Ich habe mir die Funktion noch leicht modifiziert
PHP-Code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
function mysql_queryf()
{
    
$args    func_get_args();
    
$query    array_shift($args);
    
// Padding falls %s am Ende der Query
    
$query    .= ' ';
    
// %s die nicht in Quotes stehen ersetzen durch "%s"
    
$query    preg_replace('/([^\'"])%s([^\'"])/''\\1"%s"\\2'$query);
    
// Padding wieder entfernen (eigentlich unnötig aber egal ;))
    
$query    substr($query0, -1);
    
$args    array_map('mysql_real_escape_string'$args);
    return 
mysql_query(vsprintf($query$args));
}

// Benutzung
$result    mysql_queryf('SELECT field FROM table WHERE userid = %d AND pw = %s'$_POST['userid'], $_POST['pw']); 
Bin zwar noch nicht ganz zufrieden so (z.B. kann man jetzt kein VARCHAR mehr auf NULL setzen), aber immerhin spart es schonmal 2 Zeichen pro String, der in der Query verwendet wird (beim Tippen zumindest)

Gruß,
Xgame

P.S.: Ich ziehe %d gegenüber %u vor, da sonst negative Zahlen auf einmal doch zu positiven werden, aber das ist Geschmackssache...
 
MrToiz ist offline   Mit Zitat antworten
Alt 02.06.2006, 13:15:13   #3 (permalink)
theHacker PREMIUM-User
sieht vor lauter Ads
den Content nicht mehr
Benutzerbild von theHacker

ID: 69505
Lose senden

theHacker eine Nachricht über ICQ schicken theHacker eine Nachricht über Skype™ schicken
Reg: 20.04.2006
Beiträge: 22.685
Standard

Zitat:
Zitat von Xgame
P.S.: Ich ziehe %d gegenüber %u vor, da sonst negative Zahlen auf einmal doch zu positiven werden, aber das ist Geschmackssache...
Wenn ich weiß, dass mein Integer negativ werden kann, nehm ich freilich auch %d. Aber bei UserIDs, wo ich mir für -1 etc. keine Sonderbedeutung ausgedacht hab, kann man immer von positiven IDs ausgehen.
NEU theHacker.blog NEU
– It's just a glitch in the Matrix –

OpenIsles - das freie Insel-Aufbauspiel | www.theHacker.ws v3 | WhatPulse-Team
theHacker ist offline Threadstarter   Mit Zitat antworten
Alt 16.04.2007, 13:04:42   #4 (permalink)
Paladin
Programmierer

ID: 485530
Lose senden

Reg: 16.11.2006
Beiträge: 1.651
Standard

Nur für mich, damit ich besser schlafen kann. Ich habe die Funktion mal 'verkürzt', könnt ihr mir sagen, ob die Bedingung (vermeidung von Injections) noch sicher erfüllt ist.
PHP-Code:
1:
2:
3:
4:
5:
6:
7:
function mysql_query_safe()
{
    
$args    func_get_args();
    
$query   array_shift($args);
    
$args    array_map('mysql_real_escape_string'$args);
    return 
mysql_query(vsprintf($query$args));

 

Geändert von Paladin (18.04.2007 um 15:01:44 Uhr)
Paladin ist offline   Mit Zitat antworten
Alt 24.04.2007, 16:14:40   #5 (permalink)
tleilax PREMIUM-User
be forever curious
Benutzerbild von tleilax

ID: 27936
Lose senden

Reg: 20.04.2006
Beiträge: 2.428
Standard

Damit hier auch mal 'ne Antwort kommt:

Deine Variante ist auf jeden Fall immer noch sicher. Und dazu noch eigentlich überaus schick.

Das einzige, was halt zu Problemen führen könnte, sind NULL-Werte, aber das wird bei den wenigsten selbstgeschriebenen SQL-Klassen bedacht.
.lange tage und angenehme nächte, tlx
:.whatthemovie.com (Screenshots raten) | PHP ExportForce-Klasse
tleilax ist offline   Mit Zitat antworten
Alt 20.10.2007, 20:50:33   #6 (permalink)
topfkanne
♪ ♫
Benutzerbild von topfkanne

ID: 80534
Lose senden
Abwesend

topfkanne eine Nachricht über ICQ schicken
Reg: 20.04.2006
Beiträge: 1.578
Standard

für was steht eigentlich das %s und %u ??

ich suche noch ne schöne mysql klasse zur einfachen handhabung und gegen sql injections... hab die im 1. post probiert aber irgendwie ging das nicht.

über google kann man auch kaum klassen finden :/
This is Schäuble. Copy Schäuble into your signature to help him on his way to Überwachungsstaat.
topfkanne ist offline   Mit Zitat antworten
Alt 20.10.2007, 21:20:23   #7 (permalink)
theHacker PREMIUM-User
sieht vor lauter Ads
den Content nicht mehr
Benutzerbild von theHacker

ID: 69505
Lose senden

theHacker eine Nachricht über ICQ schicken theHacker eine Nachricht über Skype™ schicken
Reg: 20.04.2006
Beiträge: 22.685
Standard

Zitat:
Zitat von topfkanne Beitrag anzeigen
für was steht eigentlich das %s und %u ??
Für String und unsigned-Integers. Siehe hierzu die Manual-Page zu sprintf().
NEU theHacker.blog NEU
– It's just a glitch in the Matrix –

OpenIsles - das freie Insel-Aufbauspiel | www.theHacker.ws v3 | WhatPulse-Team
theHacker ist offline Threadstarter   Mit Zitat antworten
Alt 20.04.2008, 15:30:38   #8 (permalink)
DadyCool
Erfahrener Benutzer

ID: 81813
Lose senden

Reg: 30.04.2006
Beiträge: 601
Standard

Hi,

Also ich habe als erstes eine Klasse für den DB Zugriff angelegt: (habe einige Anregungen aus Foren zu Hilfe genommen)

PHP-Code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
<?php

class Datenbankzugriff {

  
//Variablendefinitionund-deklaration
    // Für die Datenbank Verbindung
    
private $database null;      
    private 
$sql_server "localhost";
    private 
$sql_user "xxx";
    private 
$sql_password "xxx";
    private 
$sql_db_name "entwicklung";  // Datenbankname

    // Für die Datenbankanfrage
    
private $_sgl null;
    private 
$_result null;
    private 
$_errno 0;
    private 
$_error "";


    
//Konstruktor
function __construct() {
  
//Datenbankverbindungherstellen
  
$this->database mysql_connect($this->sql_server$this->sql_user$this->sql_password);
  if(
false === $this->database) {
    
//return false;
    
echo "mysql_connect: ".mysql_error()."<br>";
    die();
  }

  if(
$this->database) {
    
$this->db_select mysql_select_db($this->sql_db_name,$this->database);
    if(
false === $this->db_select) {
      
//return false;
      
echo "mysql_select_db: ".mysql_error()."<br>";
      die();
    }
  }

}
    
//Destruktor
function __destruct() {
      
// Verbindung zu Server trennen
    
$close = @mysql_close($this->database);
    if(
false === $close) {
      
//return flase;
      
echo "mysql_close ".mysql_error()."<br>";
    } else {
      echo 
"DB geschlossen";
    }


  }
    
// mysql query
function mysql_queryf() {
    
// Benutzung wie xprintf()
    // $result = mysql_queryf('SELECT field FROM table WHERE userid = %d AND pw = %s', $_POST['userid'], $_POST['pw']);
    
$args    func_get_args();
    
$query    array_shift($args);
    
// Padding falls %s am Ende der Query
    
$query    .= ' ';
    
// %s die nicht in Quotes stehen ersetzen durch "%s"
    
$query    preg_replace('/([^\'"])%s([^\'"])/''\\1"%s"\\2'$query);
    
// Padding wieder entfernen (eigentlich unnötig aber egal ;))
    
$query    substr($query0, -1);
    
$args    array_map('mysql_real_escape_string'$args);
    
$this->_result mysql_query(vsprintf($query$args));
    if(!
$this->_result) {
      
$this->_errno mysql_errno();
      
$this->_error mysql_error();
    }
    
$this->errorCheck();
  
//  if (!isset($this->_result)) { $this->_result = 0; }
}
    
// error
function error() {
    
// Result-ID in einer tmp-Variablen speichern
    
$tmp $this->_result;

    
// Variable in boolean umwandeln
    
$tmp = (bool)$tmp;

    
// Variable invertieren
    
$tmp = !$tmp;

    
// und zurückgeben
    
return $tmp;
  }
    
// Error beschreiben
function getError() {
    if(
$this->error()) {
        
$str  "Anfrage:\n".$this->_sql."\n";
        
$str .= "Antwort:\n".$this->_error."\n";
        
$str .= "Fehlercode: ".$this->_errno;
    } else {
        
$str "Kein Fehler aufgetreten.";
    }
   return 
$str;
  }
    
// Result fetchen
function fetch() {
    if(
$this->error()) {
        echo 
"Es trat ein Fehler auf. Bitte überprüfen sie ihr\n";
        echo 
"MySQL-Query.\n";
        
$return null;
    } else {
        
$return mysql_fetch_array($this->_result);
    }
    return 
$return;
  }

function 
numRows() {
    if(
$this->error()) {
        
$return = -1;
    } else {
        
$return mysql_num_rows($this->_result);
    }
    return 
$return;
  }

function 
errorCheck() {
    if(
$this->error()) {
        echo 
"<pre>\n";
        echo 
$this->getError();
        echo 
"</pre>\n";
        echo 
"</body>\n";
        echo 
"</html>\n";
        die();
    }
}

}
Würde mich über eure Kritik freuen

Jetzt habe ich mal eine allgemeine Frage zu euren SQL statments.

Definiert ihr für jede Abfrage ein neues SQL Statement, oder macht ihr z.B. Funktionen für jedes Statement?

z.b. habe ich mir folgendes überlegt: z.B. eine SQL Klasse mit Klassenmethoden...

PHP-Code:
1:
2:
3:
4:
5:
6:
7:
8:
function gibAlleDatenseatze($FROM) {
  if (
in_array($FROM$this->ERLAUBTE_TABELLEN)) {
    
$result 'SELECT * FROM '.$FROM.'';
    return 
$result;
  } else {
    die(
"Fehlercode: 001");
  }


Aufruf:

PHP-Code:
1:
2:
3:
$db = new Datenbankzugriff();
$sql = new Sql();
$db->mysql_queryf($sql->gibAlleDatenseatze("testTabelle")); 

in wie weit macht sowas sin? Wie geht ihr denn so vor?

DadyCool
 

Geändert von DadyCool (20.04.2008 um 15:39:06 Uhr)
DadyCool ist offline   Mit Zitat antworten
Alt 20.04.2008, 16:01:05   #9 (permalink)
ice-breaker
return void
Benutzerbild von ice-breaker

ID: 93995
Lose senden

ice-breaker eine Nachricht über ICQ schicken
Reg: 27.04.2006
Beiträge: 6.271
Standard

1. Klasssen werden gekapselt und machen bestimmt kein die, suche mal nach Exceptions
2. Naming-Konzept der privaten Attribute überdenken, entweder oder
3. Wenn du schon Attribute private machst, warum nicht noch den Methoden den richtigen access geben
4. Was ist wenn ich gerne per %s ein CURRENT_TIMESTAMP einfügen möchte? der darf nicht in Anführungszeichen
5. getError sollte ein assoziaitves Array zurückgeben statt einen String
6. Der Db-Zugang sollte im Konstruktor übergeben werden und nicht statisch da drinne stehen

7. das mit deinem gibAlleDatensätze geht in Richtung ORM, schau dir das mal an

Bedenke beim Schreiben einer Klasse, dass sie nicht nur für diesen einen Fall genutzt werden soll, sondern dass sie so variable ist, dass du sie ohne Änderung in jeder Software nutzen kannst, dann musst du das nicht jedesmal ändern/neu schreiben
"Die Wahrheit entgeht dem, der nicht mit beiden Augen sieht." -Orici

www.internet-dsl-flatrate.de
ice-breaker ist offline   Mit Zitat antworten
Alt 21.04.2008, 20:41:05   #10 (permalink)
DadyCool
Erfahrener Benutzer

ID: 81813
Lose senden

Reg: 30.04.2006
Beiträge: 601
Standard

@ice-breaker
Danke für deine Anregungen
 
DadyCool ist offline   Mit Zitat antworten
Alt 23.04.2008, 09:50:45   #11 (permalink)
ZeroCCC
wasn das?
Benutzerbild von ZeroCCC

ID: 46810
Lose senden

ZeroCCC eine Nachricht über ICQ schicken
Reg: 10.05.2006
Beiträge: 1.734
Standard

-Und wenn du grade bei Punkt 1 bist kannst du mal die Fehlerbehandlung grundlegend überdenken. Du hast da einmal die() drin, einmal error() und zusätzlich echos. Du würdest bei weitem besser kommen würdest du das alles einheitlich mit Exceptions lösen.

-Was soll der quatsch im Destructor? Mit mysql_close() geh ich noch mit, aber die echos

-unvorteilhaft ist auch du kannst nur 1 Query aufeinmal ausfrühren... kommt zwar relativ selten vor dass man Queries verschachteln muss, aber es kann vorkommen

-dir fehlen da irgendwie noch ein paar funktionen, es gibt noch mehr als nur mysql_num_rows


und zu gibAlleDatenseatze()... das ist in der Form quatsch. Wie oft verwendest du SELECT * FROM blub? Wenn du jetzt oft gesagt hast machst du was falsch. Es gibt solche Ansätze wie schon ice-breaker gepostet hat, aber die gehen in eine andere Richtung.
 
ZeroCCC ist offline   Mit Zitat antworten
Alt 23.04.2008, 15:06:14   #12 (permalink)
ice-breaker
return void
Benutzerbild von ice-breaker

ID: 93995
Lose senden

ice-breaker eine Nachricht über ICQ schicken
Reg: 27.04.2006
Beiträge: 6.271
Standard

Zitat:
Zitat von ZeroCCC Beitrag anzeigen
-unvorteilhaft ist auch du kannst nur 1 Query aufeinmal ausfrühren... kommt zwar relativ selten vor dass man Queries verschachteln muss, aber es kann vorkommen
ist doch mit PDO ebenso, das muss doch erst über PDO::MYSQL_ATTR_USE_BUFFERED_QUERY aktvieren, dass sich Querys "parallel" ausführen lassen
"Die Wahrheit entgeht dem, der nicht mit beiden Augen sieht." -Orici

www.internet-dsl-flatrate.de
ice-breaker ist offline   Mit Zitat antworten
Alt 23.04.2008, 17:11:15   #13 (permalink)
ZeroCCC
wasn das?
Benutzerbild von ZeroCCC

ID: 46810
Lose senden

ZeroCCC eine Nachricht über ICQ schicken
Reg: 10.05.2006
Beiträge: 1.734
Standard

Zitat:
Zitat von ice-breaker Beitrag anzeigen
ist doch mit PDO ebenso, das muss doch erst über PDO::MYSQL_ATTR_USE_BUFFERED_QUERY aktvieren, dass sich Querys "parallel" ausführen lassen
Mag sein, hab mir pdo noch nie so genau angeschaut. Ich finds aber drotzdem unvorteilhaft.
 
ZeroCCC ist offline   Mit Zitat antworten
Alt 23.04.2008, 17:56:06   #14 (permalink)
DadyCool
Erfahrener Benutzer

ID: 81813
Lose senden

Reg: 30.04.2006
Beiträge: 601
Standard

habe es mal überarbeitet, was haltet ihr davon?

PHP-Code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
require_once('Klassen/DBException.php');
require_once(
'Klassen/DBiterator.php');


/**
  * Abfrage und Manipulation einer Mysql-Datenbank.
  */
class MysqlDB {

  
/**
  * Datenbankhandle.
   */
  
private $resource false;

/**
  * Datenbank Result.
   */
  
private $result '';


  
/**
  * Konstruktor.
  */
  
function __construct($host$name$user$passwd)
  {
     
// Mit der Datenbank verbinden
     
if (!$this->resource mysql_connect($host$user$passwd)) {
       throw new 
DBException();
     }
     
// Datenbank auswählen
     
if (!mysql_select_db($name$this->resource)) {
      throw new 
DBException();
     }
   }
   
 
/**
  * Dekonstruktor.
  */
  
function __destruct()
  {
    if(
false === mysql_close($this->resource)) {
      throw new 
DBException();
    }
  }
  
 
/**
  * SQL die an die DB schicken.
  * Ergbniss an DBIteration geben
  */
  
function query() {
    
$args    func_get_args();
    
$query    array_shift($args);
    
// Padding falls %s am Ende der Query
    
$query    .= ' ';
    
// %s die nicht in Quotes stehen ersetzen durch "%s"
    
$query    preg_replace('/([^\'"])%s([^\'"])/''\\1"%s"\\2'$query);
    
// Padding wieder entfernen (eigentlich unnötig aber egal ;))
    
$query    substr($query0, -1);
    
$args    array_map('mysql_real_escape_string'$args);
    
$this->result mysql_query(vsprintf($query$args));
     if(!
$this->result) {
       throw new 
DBException();

    }
    return new 
DBiterator($this->result);
  }



PHP-Code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:

/**
 * Ursprung ist von Hinrich Donner
 * http://www.phpbar.de/
 **/


class DBException extends Exception {

  function 
__construct()
  {
    
$message sprintf('%04d: %s'mysql_errno(), mysql_error());
    
parent::__construct($messagemysql_errno());
  }


PHP-Code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:

/**
 * Ursprung ist von Hinrich Donner
 * http://www.phpbar.de/
 **/


class DBiterator implements Iterator
  
{
     protected 
$num_rows 0;
     protected 
$rows = array();

     
/**
      * Fetch
      */
     
public function __construct($resource) {
        while (
$row mysql_fetch_assoc($resource))
        {
            
$this->_rows[] = $row;
            ++ 
$this->num_rows;
         }

     }

     public function 
current()
     {
         return 
current($this->rows);
     }

     
/**
      * Das nächste Element 
      */
     
public function next()
     {
         return 
next($this->rows);
     }

     
/**
      * Der Index des aktuellen Elements
      */
     
public function key()
     {
         return 
key($this->rows);
     }

     
/**
      * erste Element
      */
     
public function rewind()
     {
         return 
reset($this->rows);
     }

     
/**
      * aktuellen Elements 
      */
     
public function valid()
     {
         return (bool) 
is_array($this->current());
     } 
 

Geändert von DadyCool (23.04.2008 um 18:44:45 Uhr)
DadyCool ist offline   Mit Zitat antworten
Alt 23.04.2008, 18:29:11   #15 (permalink)
ice-breaker
return void
Benutzerbild von ice-breaker

ID: 93995
Lose senden

ice-breaker eine Nachricht über ICQ schicken
Reg: 27.04.2006
Beiträge: 6.271
Standard

wie wäre es mit Meldungen in den Exceptions, die den Fehler beschreiben?

und ohne die DBIterator-Klasse kann man kaum etwas über den Aufbau sagen
"Die Wahrheit entgeht dem, der nicht mit beiden Augen sieht." -Orici

www.internet-dsl-flatrate.de
ice-breaker ist offline   Mit Zitat antworten
Antwort

Anzeige


Aktive Benutzer in diesem Thema: 1 (Registrierte Benutzer: 0, Gäste: 1)
 
Themen-Optionen
Ansicht

Forumregeln
Es ist Ihnen nicht erlaubt, neue Themen zu verfassen.
Es ist Ihnen nicht erlaubt, auf Beiträge zu antworten.
Es ist Ihnen nicht erlaubt, Anhänge hochzuladen.
Es ist Ihnen nicht erlaubt, Ihre Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks sind an
Pingbacks sind an
Refbacks sind aus


Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
[PHP/MYSQL]MySQL-class auf dem Prüfstand + ein paar Fragen BartTheDevil89 Programmierung 16 12.06.2008 09:38:44
[PHP/MySQL] Fehler - aber wo? Daten in mySQL eintragen concept Programmierung 5 14.04.2007 10:48:55
[PHP/MySQL] Zufälligen wert aus der Mysql auslesen birwac66 Programmierung 2 13.10.2006 21:55:25
[mysql/localhost] Mein MySql auf meinem PC macht Probleme 27o8 Sonstiges 4 01.07.2006 15:59:00
[PHP/MySQL] supplied argument is not a valid MySQL result resource theHacker FAQ und Archiv 0 29.04.2006 15:57:29


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:58:45 Uhr.