我试图使用以下PHP代码连接到Tor隐藏服务:

$url = 'http://jhiwjjlqpyawmpjx.onion/'
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_PROXY, "http://127.0.0.1:9050/");
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
$output = curl_exec($ch);
$curl_error = curl_error($ch);
curl_close($ch);

print_r($output);
print_r($curl_error);

当我运行它时,我得到以下错误:

无法解析主机名

然而,当我在Ubuntu的命令行中运行以下命令时:

curl -v --socks5-hostname localhost:9050 http://jhiwjjlqpyawmpjx.onion

我得到了预期的回复。

PHP cURL文档是这样说的:

--socks5-hostname
Use  the  specified  SOCKS5 proxy (and let the proxy resolve the host name).

我相信它从命令行工作的原因是因为Tor(代理)正在解析.onion主机名,它可以识别。在运行上面的PHP代码时,我猜测cURL或PHP试图解析.onion主机名,但无法识别它。我已经寻找了一种方法告诉cURL/PHP让代理解析主机名,但我找不到一种方法。

还有一个非常类似的Stack Overflow问题,使用socks5代理的cURL请求在使用PHP时失败,但它通过命令行工作。


当前回答

TL;DR:设置CURLOPT_PROXYTYPE以使用CURLPROXY_SOCKS5_HOSTNAME(如果您使用的是现代PHP),否则为7,并/或更正CURLOPT_PROXY值。

正如你正确推断的那样,你不能通过正常的DNS系统解析。onion域,因为这是一个专门供Tor使用的保留顶级域,而且这样的域在设计上没有IP地址可以映射。

使用CURLPROXY_SOCKS5将指示cURL命令将其流量发送到代理,但不会对域名解析执行相同的操作。在cURL尝试建立与Onion站点的实际连接之前发出的DNS请求仍然会被发送到系统的正常DNS解析器。这些DNS请求肯定会失败,因为系统的正常DNS解析器不知道如何处理.onion地址,除非它也专门将此类查询转发到Tor。

您必须使用CURLPROXY_SOCKS5_HOSTNAME,而不是CURLPROXY_SOCKS5。或者,您也可以使用CURLPROXY_SOCKS4A,但SOCKS5更受欢迎。这些代理类型中的任何一种都通知cURL通过代理执行DNS查找和实际数据传输。这是成功解析任何.onion域所必需的。

在原始问题的代码中还有两个额外的错误,尚未由之前的评论者更正。这些都是:

第1行末尾缺少分号。 代理地址值设置为HTTP URL,但其类型为SOCKS;这些是不相容的。对于SOCKS代理,必须为不带方案/协议/前缀的IP或域名和端口号的组合。

下面是正确的完整代码,并带有注释以指示更改。

<?php
$url = 'http://jhiwjjlqpyawmpjx.onion/'; // Note the addition of a semicolon.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_PROXY, "127.0.0.1:9050"); // Note the address here is just `IP:port`, not an HTTP URL.
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); // Note use of `CURLPROXY_SOCKS5_HOSTNAME`.
$output = curl_exec($ch);
$curl_error = curl_error($ch);
curl_close($ch);

print_r($output);
print_r($curl_error);

你也可以完全省略CURLOPT_PROXYTYPE的设置,通过改变CURLOPT_PROXY的值来包含socks5h://前缀:

// Note no trailing slash, as this is a SOCKS address, not an HTTP URL.
curl_setopt(CURLOPT_PROXY, 'socks5h://127.0.0.1:9050');

其他回答

试着加上这句话:

curl_setopt($ch, CURLOPT_HEADER, 1); 
curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); 

我使用Privoxy和cURL来抓取Tor页面:

<?php
    $ch = curl_init('http://jhiwjjlqpyawmpjx.onion'); // Tormail URL
    curl_setopt($ch, CURLOPT_HEADER, 1);
    curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
    curl_setopt($ch, CURLOPT_PROXY, "localhost:8118"); // Default privoxy port
    curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
    curl_exec($ch);
    curl_close($ch);
?>

安装Privoxy后,您需要将这一行添加到配置文件(/etc/ Privoxy /config)。注意空格和'。“这是终点。

forward-socks4a / localhost:9050 .

然后重新启动Privoxy。

/etc/init.d/privoxy restart

您需要将选项CURLOPT_PROXYTYPE设置为CURLPROXY_SOCKS5_HOSTNAME,遗憾的是在旧的PHP版本中没有定义,大约在5.6之前;如果你在前面有,但你可以显式地使用它的值,它等于7:

curl_setopt($ch, CURLOPT_PROXYTYPE, 7);

TL;DR:设置CURLOPT_PROXYTYPE以使用CURLPROXY_SOCKS5_HOSTNAME(如果您使用的是现代PHP),否则为7,并/或更正CURLOPT_PROXY值。

正如你正确推断的那样,你不能通过正常的DNS系统解析。onion域,因为这是一个专门供Tor使用的保留顶级域,而且这样的域在设计上没有IP地址可以映射。

使用CURLPROXY_SOCKS5将指示cURL命令将其流量发送到代理,但不会对域名解析执行相同的操作。在cURL尝试建立与Onion站点的实际连接之前发出的DNS请求仍然会被发送到系统的正常DNS解析器。这些DNS请求肯定会失败,因为系统的正常DNS解析器不知道如何处理.onion地址,除非它也专门将此类查询转发到Tor。

您必须使用CURLPROXY_SOCKS5_HOSTNAME,而不是CURLPROXY_SOCKS5。或者,您也可以使用CURLPROXY_SOCKS4A,但SOCKS5更受欢迎。这些代理类型中的任何一种都通知cURL通过代理执行DNS查找和实际数据传输。这是成功解析任何.onion域所必需的。

在原始问题的代码中还有两个额外的错误,尚未由之前的评论者更正。这些都是:

第1行末尾缺少分号。 代理地址值设置为HTTP URL,但其类型为SOCKS;这些是不相容的。对于SOCKS代理,必须为不带方案/协议/前缀的IP或域名和端口号的组合。

下面是正确的完整代码,并带有注释以指示更改。

<?php
$url = 'http://jhiwjjlqpyawmpjx.onion/'; // Note the addition of a semicolon.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_PROXY, "127.0.0.1:9050"); // Note the address here is just `IP:port`, not an HTTP URL.
curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); // Note use of `CURLPROXY_SOCKS5_HOSTNAME`.
$output = curl_exec($ch);
$curl_error = curl_error($ch);
curl_close($ch);

print_r($output);
print_r($curl_error);

你也可以完全省略CURLOPT_PROXYTYPE的设置,通过改变CURLOPT_PROXY的值来包含socks5h://前缀:

// Note no trailing slash, as this is a SOCKS address, not an HTTP URL.
curl_setopt(CURLOPT_PROXY, 'socks5h://127.0.0.1:9050');

这里有一个简单的函数来帮助你。 但是为了节省时间,首先你需要确保你检查代理是否有效,使用fsocketopen()简单检查

        try {
            $fp = fsockopen($ip, $port, $errno, $errstr, 10);
            fclose($fp);
            return true;
        } catch (\Throwable $th) {
            return false;
        }

如果socket返回true,则使用requestUrl函数

    private function requestUrl($url, $proxy)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_PROXY, $proxy);
        curl_setopt($curl, CURLOPT_HEADER, 1); 
        curl_setopt($curl, CURLOPT_HTTPPROXYTUNNEL, 1); 
        curl_setopt($curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
        curl_setopt($curl, CURLOPT_TIMEOUT, 10);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        $contents = curl_exec($curl);
        //Check for errors.
        // if (curl_errno($curl)) {
        //     return new \Exception(curl_error($curl));
        // }
        curl_close($curl);
        return $contents;
    }