A crazy bug of net http library

I use net http to fetch a https page, for some reason, i need use
connect with ip, and set Host by myself. It’s ok normally(return 200),
but some site is return 400.

I write a POC to reply the bug:

#!/usr/bin/env ruby
require ‘net/http’
require ‘uri’
require ‘open-uri’
require ‘openssl’

@uri = URI(‘https://ebs.shasteel.cn’)

def get_ip_of_host(host)
require ‘socket’
ip = Socket.getaddrinfo(host, nil)
return nil if !ip || !ip[0] || !ip[0][2]
ip[0][2]
rescue => e
nil
end

def test_connect_ip
ip = get_ip_of_host(@uri.host)
http = Net::HTTP.new(ip, @uri.port)
http.use_ssl = true if @uri.scheme == ‘https’
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.set_debug_output($stdout)
http.start { |h|
request = Net::HTTP::Get.new @uri.request_uri
request[‘Host’] = @uri.host
response = h.request request
puts response.code
}
end

def test_connect_host
http = Net::HTTP.new(@uri.host, @uri.port)
http.use_ssl = true if @uri.scheme == ‘https’
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.set_debug_output($stdout)
http.start { |h|
request = Net::HTTP::Get.new @uri.request_uri
response = h.request request
puts response.code
}
end

test_connect_ip #will be 400
test_connect_host #will be 200

============Output==============
opening connection to 61.177.60.85:443…
opened
starting SSL for 61.177.60.85:443…
SSL established
← “GET / HTTP/1.1\r\nAccept-Encoding:
gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: /\r\nUser-Agent:
Ruby\r\nHost: ebs.shasteel.cn\r\n\r\n”
→ “HTTP/1.1 400 Bad Request\r\n”
→ “Date: Wed, 03 Sep 2014 11:43:55 GMT\r\n”
→ “Server: Apache/2.2.17 (Win32) mod_ssl/2.2.17 OpenSSL/0.9.8o\r\n”
→ “Content-Length: 226\r\n”
→ “Connection: close\r\n”
→ “Content-Type: text/html; charset=iso-8859-1\r\n”
→ “\r\n”
reading 226 bytes…
→ “”
→ “\n\n400 Bad
Request\n\n

Bad Request

\n

Your browser
sent a request that this server could not understand.
\n

\n\n”
read 226 bytes
Conn close
400

opening connection to ebs.shasteel.cn:443
opened
starting SSL for ebs.shasteel.cn:443
SSL established
← “GET / HTTP/1.1\r\nAccept-Encoding:
gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: /\r\nUser-Agent:
Ruby\r\nHost: ebs.shasteel.cn\r\n\r\n”
→ “HTTP/1.1 200 OK\r\n”
→ “Date: Wed, 03 Sep 2014 11:43:55 GMT\r\n”
→ “Server: Apache/2.2.17 (Win32) mod_ssl/2.2.17 OpenSSL/0.9.8o\r\n”
→ “Accept-Ranges: bytes\r\n”
→ “Content-Length: 341\r\n”
→ “Last-Modified: Fri, 16 Dec 2011 01:35:36 GMT\r\n”
→ “X-Powered-By: Servlet/2.4 JSP/2.0\r\n”
→ “Content-Type: text/html\r\n”
→ “\r\n”
reading 341 bytes…
→ “”
→ “<%@ page language="java" contentType="text/html;
charset=UTF-8"\n pageEncoding="UTF-8"%>\n\n\n\t \n\t\t<meta
http-equiv="refresh" content="10">\n\t\t<meta http-equiv="refresh"
content="0;url=/pur_portal">\n\n\n\n”
read 341 bytes
Conn keep-alive
200

===================
The send header is the same, but the response is different, I need some
help, please…

Thanks for reply.
but if the site use CDN, and i want to test all node through ip, then it
not suite.

Dansei Yuuki wrote in post #1156790:

Remove

request['Host'] = @uri.host

Or make that

request[‘Host’] = ip

Remove

request['Host'] = @uri.host

Or make that

request[‘Host’] = ip

The following code prints “true”

#!/usr/bin/env ruby
require ‘net/http’
require ‘uri’
require ‘open-uri’
require ‘openssl’

@uri = URI(‘https://ebs.shasteel.cn’)

def get_ip_of_host(host)
require ‘socket’
ip = Socket.getaddrinfo(host, nil)
return nil if !ip || !ip[0] || !ip[0][2]
ip[0][2]
rescue => e
nil
end

def test_connect_ip
@uri.host = get_ip_of_host(@uri.host)
test_connect_host
end

def test_connect_host
http = Net::HTTP.new(@uri.host, @uri.port)
http.use_ssl = true if @uri.scheme == ‘https’
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http.set_debug_output($stdout)
http.start do |h|
request = Net::HTTP::Get.new @uri.request_uri
response = h.request request
return response.body
end
end

puts test_connect_ip == test_connect_host