代理检测扫描的一些经验
最近要给一个扫描器(Java 写的)添加代理检测的插件。
思路很简单,尝试使用待检测的 IP 和端口作为代理,访问 http://ipinfo.io/ip/ 之类的可以返回用户 IP 的网站。
如果返回的 IP 和自己本机不一样,就判断为代理。
不过也遇到了不少坑。
刚开始直接向端口发送 GET http://ipinfo.io/ip/ HTTP/1.1\r\n\r\n 检查代理,速度虽然很快,但这种方式部分 HTTP 代理不支持,而且显然对 Socks 代理不适用。
后来换成 HttpURLConnection,代码类似:
String checkUrl = "http://ipinfo.io/ip/";
Proxy proxy = new Proxy(Proxy.Type.HTTP, addr);
URL url = new URL(checkUrl);
uc = (HttpURLConnection)url.openConnection(proxy);
uc.setConnectTimeout(3000);
uc.connect();
然后跑了一会儿发现有卡住不动的情况,经小伙伴提醒加上了 uc.setReadTimeout(3000) 解决。
这里 setReadTimeout 的意思就是 “If the timeout expires before there is data available for read, a java.net.SocketTimeoutException is raised”。
搞定超时之后扫了一堆端口,扫出不少代理——然而大部分是用来翻墙的。
以百度为例,223.252.xxx.xxx:12345 是百度公司的 IP,但代理出口 IP 220.130.xxx.xxx 却属于台湾。
怎么判断是不是目标公司的内网代理呢?
如果有百度的外网 IP 段列表的话,直接查查代理出口 IP 在不在列表里就行,然而我们肯定不可能有完整的 IP 列表。去 https://www.ipip.net/ 查出口 IP 是不是属于百度的?很多都查不出来。
于是只能采取迂回的办法,如果出口 IP 和检查的 IP 是一样的,那肯定是内网代理,级别定为高危。如果不一样,那可能是用来翻墙的代理,调低危险级别。
最后上线扫描时又出了个问题,代理扫描器还是卡住了。已经设置超时了为啥也会卡住?
查看 log 怀疑是类似 Slowloris DOS 的情况。比如检查的端口返回了畸形的 HTTP Header,或是一直发送数据,但每次只发几个字节,维持 HTTP 连接,使 HttpURLConnection 无法关闭。
但这种情况可能性不大,查了下发现有人提到过在使用代理的情况下 setConnectTimeout 没用,可是至今没有人解答。
找出造成扫描器卡住的 IP 在本地测试却没问题,给其他小伙伴测试了下也一切正常,想到扫描器环境用的是 OpenJDK 1.6,最终只能猜测是 OpenJDK 的 bug。
纠结半天后决定把 HttpURLConnection 换成 HttpClient,HttpClient 想要支持 SOCKS 代理的话要重写一下 SOCKS 工厂类:
public static String connectSocksProxy(String checkHost, String proxyHost, int proxyPort, int timeout) throws Exception{
String result;
Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", new MyConnectionSocketFactory()).build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(reg);
CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(cm).build();
try {
InetSocketAddress socksaddr = new InetSocketAddress(proxyHost, proxyPort);
RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout).setConnectTimeout(timeout)
.setConnectionRequestTimeout(timeout).build();
HttpClientContext context = HttpClientContext.create();
context.setAttribute("socks.address", socksaddr);
context.setRequestConfig(config);
HttpHost target = new HttpHost(checkHost, 80, "http");
HttpGet request = new HttpGet("/ip/");
logger.info("Executing request " + request + " to " + target + " via SOCKS proxy " + socksaddr);
result = getResponse(httpclient, target, request, context);
} finally {
httpclient.close();
}
return result;
}
static class MyConnectionSocketFactory extends PlainConnectionSocketFactory {
@Override
public Socket createSocket(final HttpContext context) throws IOException {
InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socks.address");
Proxy proxy = new Proxy(Proxy.Type.SOCKS, socksaddr);
return new Socket(proxy);
}
@Override
public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress,
InetSocketAddress localAddress, HttpContext context) throws IOException {
// Convert address to unresolved
InetSocketAddress unresolvedRemote = InetSocketAddress
.createUnresolved(host.getHostName(), remoteAddress.getPort());
return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context);
}
}
总结:人生苦短,快用 Python …