Eigener Dynamischen DNS

Thomas22

New Member
Hi,
ich bin am verzweifeln einen Dynamischen DNS auf meinem Server zu installieren, es kommt beim nsupdate immer ein update failed: REFUSED#
Bin schon drei Tage dran und wohl Betriebsblind geworden.
Bekomme es auch nicht hin die IP eines Homeserver per DNS aufgelößen.
irgendwo muckt es..

Suche jemand der sich damit auskennt und es mir für ein paar Euro zeitnahn installieren kann.

Danke

Bitte per PN.
 

Joe User

Zentrum der Macht
Über das PHP-Script lässt sich der Server ganz einfach komplett übernehmen, daher sollte die Anleitung ganz schnell aus dem Netz verschwinden und die bisherigen Nutzer unverzüglich informiert werden.
 

DeaD_EyE

Blog Benutzer
Kann zwar kein PHP, aber der Block wird nur ausgeführt, wenn username und password richtig angegeben sind.


Code:
$username = "meintollerdnydnsdienst";
$pass = "miteinemgeheimenpasswort";


if($username == $_GET['username'] && $pass == $_GET['password'])
{
 #code
}
Doof ist nur, dass username und password auch für die Datenbank verwendet wird.

Was mich etwas irritiert, ist dieser Block:

Code:
       if (!$mysqli->query("INSERT INTO dyndns(domain, ipv4, ipv6) VALUES ('" . $_GET['domain'] . "', '" . $_GET['ipv4'] . "', '" . $_GET['ipv6'] . "')"))
Ist meine Annahme richtig, dass die Werte mittels string interpolation in den Query mit eingefügt werden? Ich hab mal gelernt, dass man das nicht machen soll :-D

Schau mal hier http://php.net/manual/de/pdo.prepare.php und hier http://php.net/manual/de/pdostatement.execute.php. Alternativ musst du halt die Values vorher escapen.



Ich würde es nicht wegschmeißen. Einfach nur so erweitern, dass es sicher ist.
- getrennte logins für den post request und die Datenbank.
- Nur den POST Request zulassen, keinen GET Request verwenden um Daten zu aktualisieren
- Daten zuvor Validieren
- Daten escapen bzw. direkt PDO verwenden.


Gibt es nicht direkt einen kleinen minimalistischen DNS Server, der nur A, AAAA und MX records kann und direkt einen eingebauten Webserver hat?
 
Last edited by a moderator:

ThomasChr

Member
@Joe User:
Das ist ja nur ein Lösungsansatz und soll keine Komplettlösung darstellen. Ja, es ist nicht explizit gegen SQL Injections abgesichert, ich denke aber trotzdem dass es sicher ist. Erklärst du mir bitte wie man damit den kompletten Server übernehmen kann?
 

danton

Debian User
@Joe User:
Das ist ja nur ein Lösungsansatz und soll keine Komplettlösung darstellen.
Du bsit lange genug hier im Forum unterwegs um zu wissen, dass es genug Server-Anmieter gibt, die solche Scripte per Copy&Paste übernehmen und sich nicht um Sicherheitsbelange kümmern - und das als Komplettlösung ansehen.

Ja, es ist nicht explizit gegen SQL Injections abgesichert, ich denke aber trotzdem dass es sicher ist.
Nein, die SQL-Injection ist noch das kleinere Problem bei dem ganzen, wenn der verwendet User nur Zugriff auf diese eine Tabelle hat - sonst sieht auch das schon wieder ganz anders aus.

Erklärst du mir bitte wie man damit den kompletten Server übernehmen kann?
Ganz einfach: Du überprüfst die per GET übermittelten Parameter nicht, sondern übernimmst sie einfach. Das hat zur Folge:
1. Die schon genannte SQL-Injection Lücke
2. Der Inhalt der erstellten Text-Datei wird in einem Script mit Root-Rechten verarbeitet und dort erfolgt auch keine Plausiblitätsprüfung - Code-Injection möglich.
Das waren so die beiden offensichtlichen Sachen, die mir aufgefallen sind.
 

ThomasChr

Member
Und wie genau kommt man an dem Vergleich auf Usernamen und Passwort vorbei?

Wenn man da nicht vorbei kommt gibt es schließlich keine Möglichkeit einer SQL Injection.

Aber ja, der Code ist nicht Idiotensicher und das verwenden eines Mysql Users mit viel zu hohen Rechten und das nicht verwenden von Prepared Statements ist definitiv Quick & Dirty und entspricht nicht den gängigen Standards. Da habt ihr Recht!
 
Last edited by a moderator:

Joe User

Zentrum der Macht
Die GET-Parameter werden grundsätzlich im Klartext durchs Netz gejagt (auch bei SSL-Verbindungen) und können entsprechend mitgelesen werden.
Da auch keine Massnahme gegen Brute-Force im Script existiert, ist auch dieser Weg frei.


Und da weder $username noch $password geprüft werden, lässt sich auch die IF-Klausel ganz leicht per Injection erweitern und schon ist sie immer true, egal was man als username und password übergibt.
Klassische Code-Injection ist an dieser Stelle ebenfalls gegeben.


Du kannst doch programmieren (zumindest suggeriert es Deine Webseite), also baue wenigstens primitive Plausibilitätsprüfungen für sämtlichen externen Input ins Script, das macht es immerhin schwerer ausnutzbar.
 
Last edited by a moderator:

ThomasChr

Member
Das ist mir alles klar.
Aber erkläre mir doch bitte welche Parameter ich mitgeben muss um an der IF-Abfrage vorbeizukommen.
Ich kann SQL Code injecten aber doch keinen PHP Code.

Meiner Meinung nach ist der einzige Angriffsvektor dass jemand Usernamen und Passwort mitliest. Ohne das sollte es nicht möglich sein.
Falls man doch an der IF-Abfrage vorbeikommen kann erkläre mir doch bitte mal wie.
 

Joe User

Zentrum der Macht
Ich kann SQL Code injecten aber doch keinen PHP Code.
Was unterscheidet denn SQL-Code von PHP-Code? Nichts, Beides sind ASCII-Folgen und Beide lassen sich problemlos in GET-Parametern übergeben.
Somit lässt sich die IF einfach um Bedingungen wie etwa "|| foo == foo" erweitern und schon ist die IF ausgehebelt.
Hinter dem foo lässt sich die IF auch einfach schliessen und eigener kann Code injected werden, zusätzlich kommentiert man einfach Deinen restlichen Code und voila, fertig ist die Backdoor.
Einfach eine remote Shell hinterher schieben und das wars.
Alternativ nutzt man Dein Bash-Script aus, welches die gleichen Lücken aufweist wie das PHP-Script, nur wird das Bash-Script auch noch als root ausgeführt, sehr praktisch.
Und wenn man es weniger komfortabel mag, dann nimmt man den Weg über die SQL-Injections.

Meiner Meinung nach ist der einzige Angriffsvektor dass jemand Usernamen und Passwort mitliest.
Das ist nichtmal notwendig, siehe oben.


Da sieht man mal wieder, wie wichtig es ist, auch im kleinsten und einfachsten Script eine geeignete Input Validation einzubauen.
 
Last edited by a moderator:

ThomasChr

Member
Hallo Joe User,

du kannst keinen PHP Code in einer Variablen injecten.
Ich bin mir da ziemlich sicher und würde gerne einen POC sehen oder alternativ jemanden der hier etwas Licht ins Dunkle bringen kann.

Vielleicht irre ich mich, aber ich bin mir ziemlich sicher dass ein PHP-Script mit

Code:
if $_GET['username'] == "lala" then
nicht einfach mit
Code:
&user=" or 1 = 1"
aufgerufen werden kann.

Der komplette Text einer Variable wird hier geprüft. Es findet hier also quasi schon ein prepare statt, so wie bei prepared SQL Statements auch.
 

Thomas22

New Member
Eure Diskusion ist ja ganz nett, aber leider an meinem Thema vorbei.
Um ein Updatescript geht es mir garnicht, weder PHP oder Perl, lediglich das manuelle Update,

Hier mal mein Weg..

Ich lege hier zwei Schlüssel ins Verzeichnis /ect/bind/..

Code:
dnssec-keygen -a HMAC-SHA256 -b 256 -n HOST test.domain.de
Danach packe ich in die /etc/bind/named.conf.local :

Code:
zone "test.domain.de" {
    type master;
    file "/var/lib/bind/test.domain.de";
    allow-update {
        localhost; };
    allow-query {
        any;
    };
    allow-transfer {
        127.0.0.1;
        IP.DES.SERVERS;
    };
    notify yes;
};


key test.domain.de {
    algorithm HMAC-SHA256;
    secret "KEY";
};
Erzeuge ein Zonen File in /var/lib/bind/test.domain.de

Code:
$ORIGIN .
$TTL 60 ;
test.domain.de IN SOA domain.de. test.domain.de. (
    1          ; serial
    3600       ; refresh (1 hour)
    900        ; retry (15 minutes)
    2419200    ; expire (4 weeks)
    180        ; minimum (3 minutes)
)

  IN NS           ns3.namesr.de.
  IN NS           ns4.namesr.de.
  IN MX 10        test.domain.de.
 
*       IN A            33.9.37.157
www     IN A            33.9.37.157
mail    IN A            3.9.37.157
Passe die Rechte an

Code:
chown bind:bind /var/lib/bind/test.domain.de
chmod 700 /var/lib/bind/test.domain.de

Local updaten

Code:
nsupdate -k /pfad/zum/keyfile.private

> server 127.0.0.1
> zone test.domain.de
> update test.domain.de
> update add test.domain.de 60 A ÖFFENTLICHE.IP.DES.DYNDNS-SYSTEMS
> send
oder auch so

Code:
nsupdate -k /pfad/zum/keyfile.private

> server ns3.nameserver.de
> zone domain.de
> update delete test.domain.de
> update add test.domain.de 60 A 34.9.144.44
> send
Kommt immer eine Fehlermeldung

Code:
TSIG error with server: expected a TSIG or SIG(0)
update failed: REFUSED
Dig Abfrage bringt folgendes :

Code:
; <<>> DiG 9.9.5-9+deb8u15-Debian <<>> ANY @44.9.447157 domain.de

; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3717
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;domain.de.               IN      ANY

;; ANSWER SECTION:
test.domain.de.        86400   IN      SOA     www.domain.de. root.domain.de. 2014023602 21600 3600 604800 86400
domain.de.        86400   IN      NS      ns3.nameserver.de.
domain.de.        86400   IN      NS      ns4.nameserver.de.
domain.de.        86400   IN      MX      10 mail.domain.de.

;; ADDITIONAL SECTION:
mail.domain.de.   86400   IN      A       44.9.447

;; Query time: 0 msec
;; SERVER: 5.9.147.157#53(5.9.147.157)
;; WHEN: Tue Jul 03 15:40:15 CEST 2018
;; MSG SIZE  rcvd: 183
Auch das einfügen von

Code:
update-policy {
      grant  test.domain.de.  name   test.domain.de. A;
};
hat nichts gebracht, wie gesast, irgendwie bin ich Betriebsblind geworden, vielleicht findet jemand den Fehler.

Danke


Nachtrag : In Webmin habe ich unter der Zone test.domain.de folgende Fehler nach der Prüfung :
Was ich überhaupt nicht verstehe, oben müsste doch alles soweit stimmen, auch die beiden NS.

Code:
    /var/lib/bind/test.domain.de:10: ignoring out-of-zone data (NS)
    /var/lib/bind/test.domain.de:10: unknown RR type 'test.domain.de.'
    /var/lib/bind/test.domain.de:11: ignoring out-of-zone data (MX)
    /var/lib/bind/test.domain.de:11: unknown RR type 'domain.de.'
    /var/lib/bind/test.domain.de:12: ignoring out-of-zone data (A)
    /var/lib/bind/test.domain.de:12: unknown RR type '4.55.144.157'
    zone test.domain.de/IN: loading from master file /var/lib/bind/test.domain.de failed: unknown class/type
    zone test.domain.de/IN: not loaded due to errors.
 
Last edited by a moderator:

ThomasChr

Member
Hallo Thomas22,

ja, tut mir leid dass dein Thread gekapert wurde - aber ob man nun einfach über Variablen PHP Programmcode injecten kann das hätte ich schon gerne noch abschließend geklärt. Wäre gut wenn ein Moderator diese Diskussion einfach abtrennt! *wink*

Was dich betrifft vermute ich dass es fehlende Rechte an irgendeiner Datei sind. Probiere doch mal entweder mit strace / fatrace herauszufinden welche Dateien beteiligt sind und deren Rechte zu checken, oder aber einfach mal die Befehle als 'root' auszuführen.

Gibts denn ein logfile von BIND (bzw. im Syslog) wo du nachsehen kannst ob du mehr Details bekommst?
Eventuell kannst du in einer Bind Konfig das Loglevel höher stellen! (DEBUG)
 

Joe User

Zentrum der Macht
Der komplette Text einer Variable wird hier geprüft. Es findet hier also quasi schon ein prepare statt, so wie bei prepared SQL Statements auch.
Nein, wie in jeder anderen Sprache auch, wird zuerst die Variable ($_GET['username']) stumpf mit dem ungeprüften RAW-Inhalt des GET ersetzt und erst danach erfolgt der von Dir gewünschte Vergleich "if $username == username".
Wenn man jetzt also per GET in etwa soetwas übergibt
Code:
script.php?username=foo||bar==bar&password=foo||bar==bar
dann ergibt Dein IF immer true und der Angreifer ist drin.
Hinter dem zweiten bar kann man so auch das IF schliessen und die Backdoor einschleusen.
 

Joe User

Zentrum der Macht
Dein Original-Script sieht so aus:
Code:
<?php

$username = "meintollerdnydnsdienst";
$pass = "miteinemgeheimenpasswort";
$dyntxt = "/var/www/html/dyndns.txt";
$db = "dyndnsoettingen";

if($username == $_GET['username'] && $pass == $_GET['password'])
{
       $a = fopen("$dyntxt", "w");
       fwrite($a, $_GET['ipv4']);
       fclose($a);

       $mysqli = new mysqli("localhost", $username, $pass, $db);
       if ($mysqli->connect_errno)
       {
           echo "Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
       }

       if (!$mysqli->query("INSERT INTO dyndns(domain, ipv4, ipv6) VALUES ('" . $_GET['domain'] . "', '" . $_GET['ipv4'] . "', '" . $_GET['ipv6'] . "')"))
       {
               echo "Insert-Error: (" . $mysqli->errno . ") " . $mysqli->error;
       }

       mysqli_close($mysqli);

       echo "success";
}

?>
Wenn man nun "script.php?username=foo||bar==bar&password=foo||bar==bar" übergibt, dann sieht das Script für den PHP-Interpreter plötzlich so aus:
Code:
<?php

$username = "meintollerdnydnsdienst";
$pass = "miteinemgeheimenpasswort";
$dyntxt = "/var/www/html/dyndns.txt";
$db = "dyndnsoettingen";

if($username == foo||bar==bar && $pass == foo||bar==bar) # ergibt true
{
       $a = fopen("$dyntxt", "w");
       fwrite($a, $_GET['ipv4']);
       fclose($a);

       $mysqli = new mysqli("localhost", $username, $pass, $db);
       if ($mysqli->connect_errno)
       {
           echo "Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
       }

       if (!$mysqli->query("INSERT INTO dyndns(domain, ipv4, ipv6) VALUES ('" . $_GET['domain'] . "', '" . $_GET['ipv4'] . "', '" . $_GET['ipv6'] . "')"))
       {
               echo "Insert-Error: (" . $mysqli->errno . ") " . $mysqli->error;
       }

       mysqli_close($mysqli);

       echo "success";
}

?>
Vielleicht verstehst Du es jetzt.
 

marce

Well-Known Member
Ausprobiert oder nur behauptet? Bin kein php-Crack, aber ohne eval passiert das AFAIK eben genau nicht, was Du dort schreibst.
 

greystone

Member
@Joe: Das hat mich jetzt auch nochmal überrascht.

Ich habe das mal nachgestellt. Das führt aber nicht zum Erfolg:

Code:
<?php

$username = "meintollerdnydnsdienst";
$pass = "miteinemgeheimenpasswort";

if($username == $_GET['username'] && $pass == $_GET['password'])
{
        print("<p>AUTH passed</p>");

} else {

        print("<p>access denied</p>");
}


?>
Mit dem Request von Dir:

Code:
script.php?username=foo||True&password=foo||True
Auch nochmal die Parameter vorher urlencoded...

Code:
script.php?username=foo%7C%7CTrue&password=foo%7C%7CTrue
Zeige Doch bitte mal den Code, der sich gemäß Deiner Aussage verhält!
 

ThomasChr

Member
Das denke ich auch. Sowas kann nur mit eval() passieren. Auch nach langer google Suche konnte ich nichts gegenteiliges finden.
 

elias5000

Site Reliability Engineer
Es ist zwar schon eine Weile her aber PHP funktioniert nicht so. Das ist zwar aus diversen Gründen eine Sprache, die ich nicht mehr anfasse, aber so schlimm ist sie dann doch nicht.

@Joe: Ich finde es etwas schade, dass du trotz gegenteiliger Aussagen ungeprüft an deiner Aussage festhältst. Das entwertet die Fälle in denen du Recht hast weil man dann nicht genau weiß, ob du es jetzt tatsächlich besser weißt oder nur gerade nicht zur Selbstkorrektur fähig bist. :-S
 
Last edited by a moderator:
@Joe: Sorry, aber das ist Blödsinn hoch 10! So funktioniert PHP nicht… :rolleyes:

Die Sache mit der SQL-Injection ist natürlich eine andere Sache, das gehört trotzdem behoben.
Außerdem würde ich dringend empfehlen, dass man sich bei PHP ausschließlich auf === verlässt.

Das entwertet die Fälle in denen du Recht hast weil man dann nicht genau weiß, ob du es jetzt tatsächlich besser weißt oder nur gerade nicht zur Selbstkorrektur fähig bist.
Da kann man nur zustimmen.
 
Top