Felix Riesterer: HTTPS-Resource via PHP-Socket hinter Proxy laden

Liebe Mitlesende,

ich möchte wie im Titel schon benannt eine HTTPS-Resource laden, die ein PHP-Script hinter einem Proxy über eine Socket-Verbindung vom Webserver anfordert. Folgender Code funktioniert mit http (also ohne s) wie gewünscht (Erfolg durch Trial&Error):

$response = '';
$url = 'https://felix-riesterer.de/';

$h = fsockopen(
  // Host directly or via Proxy?
  (empty($t->settings['proxy-server'])
    ? $t->settings['host']
    : $t->settings['proxy-server']
  ),
  // Port
  (empty($t->settings['proxy-server']) || empty($t->settings['proxy-port'])
    ? 80
    : $t->settings['proxy-port']
  )
);

if ($h !== false) {

  $http = sprintf(
    // generate HTTP request
    'GET http://%1$s/%2$s HTTP/1.1'."\r\n"
      // Proxy Server
      . 'Host: %1$s'."\r\n"
      // Proxy-Authorization?
      . '%3$s'
      . 'User-Agent: %4$s'."\r\n"
      . 'Connection: close'."\r\n"
      // empty line to end HTTP headers
      . "\r\n",
    $t->settings['host'],
    $url,
    (empty($t->settings['proxy-server'])
      ? ''
      : sprintf(
        'Proxy-Authorization: Basic %s'."\r\n",
        $t->settings['proxy-auth']
      )
    ),
    $t->settings['user-agent']
  );

  fputs($h, $http);

  while (!feof($h)) {
    $response .= fread($h, 4096);
  }
}

debug($response); // HTTP/1.1 200 OK\r\nDate: Mon, 04 Jul 2016 11:58:47 GMT\r\nServer...

Wenn ich nun den String mit 'GET http://%1$s/%2$s HTTP/1.1' durch ein s ergänze, damit er nun auf 'GET https://%1$s/%2$s HTTP/1.1' lautet, ändert das nicht das Protokoll - was mich eigentlich auch gewundert hätte.

Muss ich nun als Protokoll ssl://felix-riesterer.de verwenden, oder kann ich die bestehende Codebasis durch eine klitzekleine Anpassung dazu bringen, dass HTTPS verwendet wird?

Liebe Grüße,

Felix Riesterer.

  1. Hallo Felix,

    ich weiß nicht, ob ich den Knackpunkt erkannt habe, aber ...

      $http = sprintf(
        // generate HTTP request
        'GET http://%1$s/%2$s HTTP/1.1'."\r\n"    // das ist definitiv falsch!
                                                  // Protokoll und Hostname haben in
                                                  // dieser Zeile nichts verloren!
          // Proxy Server
          . 'Host: %1$s'."\r\n"
          // Proxy-Authorization?
          . '%3$s'
          . 'User-Agent: %4$s'."\r\n"
          . 'Connection: close'."\r\n"
          // empty line to end HTTP headers
          . "\r\n",
        $t->settings['host'],
        $url,
        (empty($t->settings['proxy-server'])
          ? ''
          : sprintf(
            'Proxy-Authorization: Basic %s'."\r\n",
            $t->settings['proxy-auth']
          )
        ),
        $t->settings['user-agent']
      );
    

    An der kommentierten Stelle ist definitiv fehlerhaftes HTTP; es überrascht mich, dass der Server das anscheinend klaglos akzeptiert. Korrekt heißt die erste Zeile des Requests beispielsweise:

    GET /sub/somedoc.txt HTTP/1.1
    

    Wenn ich nun den String mit 'GET http://%1$s/%2$s HTTP/1.1' durch ein s ergänze, damit er nun auf 'GET https://%1$s/%2$s HTTP/1.1' lautet, ändert das nicht das Protokoll - was mich eigentlich auch gewundert hätte.

    Eben. Das ändert nur den Lieferschein, der in der Sendung liegt.

    Muss ich nun als Protokoll ssl://felix-riesterer.de verwenden

    AFAIS ja, und zwar beim fsockopen(), denn HTTPS ist ja genau das: HTTP über eine zuvor hergestellte SSL-Verbindung. Ich habe das aber auch noch nie gemacht, daher vermute ich nur, dass es so gehen muss.

    Ciao,
     Martin

    --
    Nothing travels faster than the speed of light with the possible exception of bad news, which obeys its own special laws.
    - Douglas Adams, The Hitchhiker's Guide To The Galaxy
    1. Lieber Martin,

          'GET http://%1$s/%2$s HTTP/1.1'."\r\n"    // das ist definitiv falsch!
                                                    // Protokoll und Hostname haben in
                                                    // dieser Zeile nichts verloren!
      

      An der kommentierten Stelle ist definitiv fehlerhaftes HTTP; es überrascht mich, dass der Server das anscheinend klaglos akzeptiert. Korrekt heißt die erste Zeile des Requests beispielsweise:

      GET /sub/somedoc.txt HTTP/1.1
      

      das habe ich alles schon so durchprobiert. Warum es in der von Dir vorgeschlagenen Weise nicht klappen will, ist mir unbekannt. Auf den Proxy habe ich keinerlei administrativen Einfluss. Daher schrieb ich von "Trial&Error".

      Muss ich nun als Protokoll ssl://felix-riesterer.de verwenden

      AFAIS ja, und zwar beim fsockopen(), denn HTTPS ist ja genau das: HTTP über eine zuvor hergestellte SSL-Verbindung. Ich habe das aber auch noch nie gemacht, daher vermute ich nur, dass es so gehen muss.

      Werde das morgen mal in der Schule testen. Vielen Dank!

      Liebe Grüße,

      Felix Riesterer.

    2. Tach!

      An der kommentierten Stelle ist definitiv fehlerhaftes HTTP; es überrascht mich, dass der Server das anscheinend klaglos akzeptiert. Korrekt heißt die erste Zeile des Requests beispielsweise:

      GET /sub/somedoc.txt HTTP/1.1
      

      Nicht, wenn man mit einem Proxy spricht. Da fehlt Protokoll und Host und so weiß er gar nicht, wie und wohin er den Request schicken soll. Und nein, das steht auch nicht in der Host-Zeile, denn da steht der Proxy selbst drin.

      Aber wenn ich da nochmal genauer schaue, dann scheint mir als Ergebnis
      GET http://proxy_adresse/https://felix-riesterer.de/ HTTP/1.1
      rauszukommen und das ist definitiv verkehrt.

      dedlfix.

      1. Hallo,

        GET /sub/somedoc.txt HTTP/1.1
        

        Nicht, wenn man mit einem Proxy spricht. Da fehlt Protokoll und Host und so weiß er gar nicht, wie und wohin er den Request schicken soll. Und nein, das steht auch nicht in der Host-Zeile, denn da steht der Proxy selbst drin.

        da hatte ich eine andere Vorstellung - aber eben kein Wissen. Ich dachte, der Proxy wird ganz normal angesprochen, als sei er der Ziel-Host, und macht dann selbst eine DNS-Abfrage auf den gewünschten Hostnamen und leitet den Request weiter. Ob HTTP oder HTTPS, hätte er - nach meiner Vorstellung - danach entschieden, ob der Request vom Client schon verschlüsselt käme oder nicht.

        Aber wie gesagt, HTTP mit Proxy habe ich auch noch nie genauer untersucht.

        Aber wenn ich da nochmal genauer schaue, dann scheint mir als Ergebnis

        GET http://proxy_adresse/https://felix-riesterer.de/ HTTP/1.1  
        

        rauszukommen und das ist definitiv verkehrt.

        Yo, das sieht irgendwie falsch aus.

        So long,
         Martin

        --
        Nothing travels faster than the speed of light with the possible exception of bad news, which obeys its own special laws.
        - Douglas Adams, The Hitchhiker's Guide To The Galaxy
  2. Tach!

    Wenn ich nun den String mit 'GET http://%1$s/%2$s HTTP/1.1' durch ein s ergänze, damit er nun auf 'GET https://%1$s/%2$s HTTP/1.1' lautet, ändert das nicht das Protokoll - was mich eigentlich auch gewundert hätte.

    Bei HTTPS muss der Proxy normalerweise auf Durchzug stellen, sonst wäre er ein man in the middle. Er darf nur die Pakete zum Ziel leiten und zurück. Was darin ist, sieht er nicht. Nicht mal den Pfad der URL braucht er, nur die IP. Er kann in dem Fall auch nicht HTTP sprechen. Such mal nach "https over proxy", da liest man dann sowas wie CONNECT. Mehr weiß ich aber auch nicht.

    dedlfix.

    1. Lieber dedlfix,

      Bei HTTPS muss der Proxy normalerweise auf Durchzug stellen, sonst wäre er ein man in the middle.

      daher die Nachfrage nach ssl://felix-riesterer.de/ anstelle von https://felix-riesterer.de/.

      Such mal nach "https over proxy", da liest man dann sowas wie CONNECT. Mehr weiß ich aber auch nicht.

      Vielen Dank für die Stichworte!

      Liebe Grüße,

      Felix Riesterer.

      1. Tach,

        Bei HTTPS muss der Proxy normalerweise auf Durchzug stellen, sonst wäre er ein man in the middle.

        daher die Nachfrage nach ssl://felix-riesterer.de/ anstelle von https://felix-riesterer.de/.

        nein, du kommunizierst zuerst mit dem Proxy via HTTP und (falls der Proxy das via CONNECT handelt) machst einen CONNECT-Request:

        CONNECT server.example.com:80 HTTP/1.1
        Host: server.example.com:443
        

        Wenn du dann ein 200er-Ergebnis bekommst, ist ab der nächsten Zeile die Kommunikation dann TCP mit dem angeforderten Host, in deinem Falle dann also HTTP über TLS über TCP.

        mfg\ Woodfighter

        1. Tach!

          nein, du kommunizierst zuerst mit dem Proxy via HTTP und (falls der Proxy das via CONNECT handelt) machst einen CONNECT-Request:

          CONNECT server.example.com:80 HTTP/1.1
          Host: server.example.com:443
          

          Wobei bei Host der Name vom Proxy eingetragen werden muss und beim CONNECT die Zieladresse. Zweimal denselben Namen zu verwenden wäre sinnlos. Wozu braucht man einen Proxy, wenn man den Server direkt ansprechen kann (wenn nicht grad sein Port 80 gefirewallt ist)?

          dedlfix.

          Folgende Beiträge verweisen auf diesen Beitrag:

          1. Tach,

            Tach!

            nein, du kommunizierst zuerst mit dem Proxy via HTTP und (falls der Proxy das via CONNECT handelt) machst einen CONNECT-Request:

            CONNECT server.example.com:80 HTTP/1.1
            Host: server.example.com:443
            

            Wobei bei Host der Name vom Proxy eingetragen werden muss und beim CONNECT die Zieladresse. Zweimal denselben Namen zu verwenden wäre sinnlos.

            äh, korrekt; hatte das Beispiel aus der RFC, die ich eigentlich auch verlinken wollte, kopiert, aber nur den Port angepasst

            Wozu braucht man einen Proxy, wenn man den Server direkt ansprechen kann (wenn nicht grad sein Port 80 gefirewallt ist)?

            Das Beispiel geht ursprünglich davon aus, dass es um ein SSL-Upgrade geht, ähnlich STARTTLS in anderen Protokollen.

            mfg\ Woodfighter

          2. Hallo,

            Wozu braucht man einen Proxy, wenn man den Server direkt ansprechen kann (wenn nicht grad sein Port 80 gefirewallt ist)?

            transparentes Caching, Logging, Content-Filterung, ...?

            So long,
             Martin

            --
            Nothing travels faster than the speed of light with the possible exception of bad news, which obeys its own special laws.
            - Douglas Adams, The Hitchhiker's Guide To The Galaxy
  3. Hi,

    ich möchte wie im Titel schon benannt eine HTTPS-Resource laden, die ein PHP-Script hinter einem Proxy über eine Socket-Verbindung vom Webserver anfordert. Folgender Code funktioniert mit http (also ohne s) wie gewünscht (Erfolg durch Trial&Error):

      // Port
      (empty($t->settings['proxy-server']) || empty($t->settings['proxy-port'])
        ? 80
        : $t->settings['proxy-port']
      )
    

    Wenn ich nun den String mit 'GET http://%1$s/%2$s HTTP/1.1' durch ein s ergänze, damit er nun auf 'GET https://%1$s/%2$s HTTP/1.1' lautet, ändert das nicht das Protokoll - was mich eigentlich auch gewundert hätte.

    Muss ich nun als Protokoll ssl://felix-riesterer.de verwenden, oder kann ich die bestehende Codebasis durch eine klitzekleine Anpassung dazu bringen, dass HTTPS verwendet wird?

    https geht üblicherweise (soweit nicht anders deklariert) über Port 443, nicht 80. Hast Du den Port auch angepaßt? Ist aus Deinem Schnipsel nicht erkennbar, da nicht erkennbar ist, ob etwas und wenn ja was in $t->settings['proxy-port'] steckt ...

    cu,
    Andreas a/k/a MudGuard

    1. Tach!

      https geht üblicherweise (soweit nicht anders deklariert) über Port 443, nicht 80.

      Das ist erstmal nur die Verbindung zum Proxy. Der muss für HTTPS nicht auf einem Extraport lauschen, sondern kann das auch über Port 80 oder einen beliebigen anderen abfackeln.

      dedlfix.

      1. Lieber dedlfix,

        Das ist erstmal nur die Verbindung zum Proxy. Der muss für HTTPS nicht auf einem Extraport lauschen, sondern kann das auch über Port 80 oder einen beliebigen anderen abfackeln.

        in meinem Fall Port 8080 sowohl für HTTP als auch HTTPS.

        Liebe Grüße,

        Felix Riesterer.

  4. Nachtrag,

    bin jetzt mit der EDV-Abteilung im Dialog, um das Problem zu lösen. Auf der einen Seite muss ich natürlich korrektes HTTP verwenden, auf der anderen Seite aber muss der Proxy auch so konfiguriert sein, dass mein Ansinnen umsetzbar ist. Ja, mit einem Browser kann man HTTPS-Resourcen über diesen Proxy aufrufen, also muss es prinzipiell auch mit PHP über eine Socket-Verbindung klappen. Mit dem Wie lasse ich mir eben von denen helfen, die ihre Finger am Proxy haben.

    Wenn ich neue Erkenntnisse habe, poste ich sie hier.

    Liebe Grüße,

    Felix Riesterer.

    1. Tach!

      Ja, mit einem Browser kann man HTTPS-Resourcen über diesen Proxy aufrufen, also muss es prinzipiell auch mit PHP über eine Socket-Verbindung klappen. Mit dem Wie lasse ich mir eben von denen helfen, die ihre Finger am Proxy haben.

      Du kannst dir auch vom Leitungshai helfen lassen. Zumindest bis zu dem Teil, an dem die Verschlüsslung ansetzt, kannst du zuschauen, was die Beteiligten so tun.

      Das ist ja dann übrigens noch das nächste Problem. Wenn du Low-Level arbeiten willst, sprich selbst das Protokoll sprechen willst, musst du auch den Teil mit der Verschlüsslung selbst lösen.

      Nimm mal lieber Curl oder die PHP-Dateifunktionen. Dem Curl kann man sicher einen Proxy verkaufen, den Dateifunktionen muss man das über den Stream-Kontext mitgeben.

      dedlfix.

      1. Lieber dedlfix,

        Stream-Kontext

        DAS war der entscheidende Hinweis! Mega-vielen Dank! dedlfix for the win!!!

        Liebe Grüße,

        Felix Riesterer.

    2. Nochmal Nachtrag.

      Wenn ich neue Erkenntnisse habe, poste ich sie hier.

      mit stream_context geht's jetzt:

      // new data
      $response = '';
      
      $stream = stream_context_create(
        Array(
          'http' => Array(
            'method' => 'GET',
            'timeout' => 20,
            'header' => array(
              'Connection: close',
              'User-agent: '.$t->settings['user-agent'],
              'Proxy-Authorization: Basic '.$t->settings['proxy-auth']
            ),
            'protocol_version' => '1.1',
            'proxy' => sprintf(
              'tcp://%1$s:%2$s',
              $t->settings['proxy-server'],
              $t->settings['proxy-port']
            ),
            'request_fulluri' => true /* without this option we get an HTTP error! */
          ),
          'ssl' => Array(
            'SNI_enabled' => true,
            'SNI_server_name' => $t->settings['host']
          ),
        )
      );
      
      $fp = fopen(
        sprintf(
          'https://%1$s/%2$s',
          $t->settings['host'],
          // ensure absolute path
          preg_replace(
            '~^/?~',
            '',
            $display['url']
          )
        ),
        'r',
        false,
        $stream
      );
      
      if ($fp !== -1) {
      
        while (!feof($fp)) {
          $response .= fread($fp, 4096);
        }
      
        fclose($fp);
      }
      
      if (strlen($response)) {
      
        $filename = 'downloaded.html';
      
        file_put_contents($filename, $response);
      }
      

      Liebe Grüße,

      Felix Riesterer.

      Folgende Beiträge verweisen auf diesen Beitrag:

  5. Hallo,

    wie wäre es, wenn du einfach eine fertige HTTP-Client-Library nutzt? In Zeiten von PSR-7 existieren davon einige zueinander kompatible, die bekannteste ist wohl Guzzle, sehr einfach über composer zu installieren.

    Manuelles fsockopen und HTTP mit String-Operationen schreiben will später doch niemand mehr warten.

    Viele Grüße, Matti

    1. Lieber Matti,

      Guzzle

      interessantes Modul! Vielen Dank für diese Anregung.

      Ich habe mir die Docs im Berreich Proxy angeschaut. Im Vergleich zu meiner Lösung sehe ich - wie gesagt für meinen Einsatzzweck - keinen Vorteil.

      Liebe Grüße,

      Felix Riesterer.