CLOVER🍀

That was when it all began.

curl+フォワードプロキシで、HTTP CONNECTトンネリング(proxytunnel)する

これは、なにをしたくて書いたもの?

curlの「--proxytunnel」オプションを使うと、フォワードプロキシサーバー+HTTP CONNECTでトンネリングができるというのを知り。

試してみましょうかと。

環境

今回の環境は、Ubuntu Linux 18.04 LTSです。

$ uname -srvmpio
Linux 4.15.0-96-generic #97-Ubuntu SMP Wed Apr 1 03:25:46 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux


$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic

curlのバージョンはこちら。

$ curl --version
curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3
Release-Date: 2018-01-24
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL

お題として、Apacheフォワードプロキシサーバーに、背後に簡単なHTTPサーバーを立て、curlを使ってHTTP CONNECTで
トンネリングできることを確認してみましょう。

ApacheProxyサーバーを作る

まずは、Apacheをインストールします。

$ sudo apt install apache2

バージョン。

$ apache2 -v
Server version: Apache/2.4.29 (Ubuntu)
Server built:   2020-03-13T12:26:16

プロキシ系のモジュールを有効にします。HTTP CONNECTだけを使うという目線なら「proxy_http」は不要なのですが、今回は
比較用に入れておきました。

$ sudo a2enmod proxy proxy_connect proxy_http

フォワードプロキシの設定は、こんな感じで。8080ポートでリッスンするようにしました。

Listen 8080
<VirtualHost *:8080>
  ProxyRequests On
  ProxyVia On

  AllowCONNECT 443 8000

  <Proxy *>
    Require host localhost
  </Proxy>

  ErrorLog ${APACHE_LOG_DIR}/proxy_error.log
  CustomLog ${APACHE_LOG_DIR}/proxy_access.log combined
</VirtualHost>

背後のHTTPサーバーはポート8000を使うことにするので、AllowCONNECTで443、8000を指定。

設定したら、Apacheを再起動します。

$ sudo systemctl restart apache2

これで、準備は完了です。

Pythonで簡単なHTTPサーバーを立てる

では、フォワードプロキシ越しにアクセスするHTTPサーバーを用意します。

Pythonで簡単に。

$ echo 'Hello Python!!' > hello.txt
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

単体で確認。

$ curl localhost:8000/hello.txt
Hello Python!!

HTTP CONNECTでトンネリングする

それでは、プロキシ越しにアクセスしてみましょう。最初はなにも考えずにアクセスしてみます。

$ curl -v localhost:8000/hello.txt --proxy http://localhost:8080
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET http://localhost:8000/hello.txt HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Accept: */*
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.1 200 OK
< Date: Thu, 09 Apr 2020 12:46:30 GMT
< Server: SimpleHTTP/0.6 Python/3.6.9
< Content-type: text/plain
< Content-Length: 15
< Last-Modified: Thu, 09 Apr 2020 12:41:29 GMT
< Via: 1.1 ubuntu1804.localdomain:8000
< Vary: Accept-Encoding
< 
Hello Python!!
* Connection #0 to host localhost left intact

まあ、ふつうにこうなりますよね。

> GET http://localhost:8000/hello.txt HTTP/1.1

アクセスログ

localhost - - [09/Apr/2020:12:46:30 +0000] "GET http://localhost:8000/hello.txt HTTP/1.1" 200 261 "-" "curl/7.58.0"

ここで、「--proxytunnel」オプションを付けると挙動が変わります。

$ curl -v localhost:8000/hello.txt --proxy http://localhost:8080 --proxytunnel
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to localhost:8000
> CONNECT localhost:8000 HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.0 200 Connection Established
< Proxy-agent: Apache/2.4.29 (Ubuntu)
< 
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* CONNECT phase completed!
* CONNECT phase completed!
> GET /hello.txt HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Accept: */*
> 
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.6.9
< Date: Thu, 09 Apr 2020 12:46:56 GMT
< Content-type: text/plain
< Content-Length: 15
< Last-Modified: Thu, 09 Apr 2020 12:41:29 GMT
< 
Hello Python!!
* Closing connection 0

HTTP CONNECTでのアクセスになりましたね。

> CONNECT localhost:8000 HTTP/1.1

アクセスログ

localhost - - [09/Apr/2020:12:47:09 +0000] "CONNECT localhost:8000 HTTP/1.1" 200 276 "-" "curl/7.58.0"

無事、できました、と。

ちなみに、HTTPの場合はhttp_proxy環境変数と「--proxytunnel」オプションでも良いのですが、プロトコルを変えると効かなくなるので、
今回は「--proxy」オプションを使用しています。

$ http_proxy=http://localhost:8080 curl -v http://localhost:8000/hello.txt --proxytunnel

続いて、telnetプロトコルでアクセスしてみましょう。

$ curl -v telnet://localhost:8000 --proxy http://localhost:8080
* Rebuilt URL to: telnet://localhost:8000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to localhost:8000
> CONNECT localhost:8000 HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.0 200 Connection Established
< Proxy-agent: Apache/2.4.29 (Ubuntu)
< 
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* CONNECT phase completed!
GET /hello.txt

Hello Python!!

この場合、実は「--proxytunnel」オプションを使用しなくてもHTTP CONNECTでのアクセスとなります。

localhost - - [09/Apr/2020:12:56:47 +0000] "CONNECT localhost:8000 HTTP/1.1" 200 91 "-" "curl/7.58.0"

「--proxytunnel」オプションをつけてもいいんですけど、挙動は変わらずです。

$ curl -v telnet://localhost:8000 --proxy http://localhost:8080 --proxytunnel
* Rebuilt URL to: telnet://localhost:8000/
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
* allocate connect buffer!
* Establish HTTP proxy tunnel to localhost:8000
> CONNECT localhost:8000 HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.58.0
> Proxy-Connection: Keep-Alive
> 
< HTTP/1.0 200 Connection Established
< Proxy-agent: Apache/2.4.29 (Ubuntu)
< 
* Proxy replied 200 to CONNECT request
* CONNECT phase completed!
* CONNECT phase completed!

curlのオプションの確認と、HTTP CONNECTでのトンネリングの確認ができました、と。