我在 Apache 访问日志中发现了一个奇怪的 HTTP 请求:
“获取 www.example.com HTTP/1.0”
使用浏览器(Waterfox、Firefox 或 Chrome(按顺序))或 PuTTY/cURL|终端如何重现此 HTTP 请求?最好是
localhost
,因为我可以实时看到日志。
我做了一些阅读,显然 cURL 只能发出绝对 URL 请求。我也无法通过 Waterfox 的“网络”面板中的“编辑并重新发送”来重现此情况。此请求触发了一些我希望在系统中修复的小错误。
在终端中执行任何请求都非常容易。
你可以
telnet 127.0.0.1 80
。然后你必须编写请求(你可能需要快速输入,服务器可能会因为结尾行错误而拒绝你
代替
)printf
:# regular HTTP query
printf 'GET / HTTP/1.0\r\nHost: www.example.com\r\n\r\n'
# or the 'absolute url' mode
printf 'GET www.example.com HTTP/1.0\r\n\r\n'
# or even the official absolute uri mode, I think that's the correct one
printf 'GET http://www.example.com/ HTTP/1.0\r\n\r\n'
但是
printf
与 echo
是一样的,有特殊的字符处理(如 \r\n
)。所以你必须复制结果并将其粘贴到telnet。
我更喜欢使用
netcat
(有时称为 nc
)来管理 tcp/ip 套接字连接而不是 telnet,并使用管道将 printf 结果直接放入 tcp/ip 套接字(如果您需要 https,您将拥有使用 openssl_client 而不是 netcat)。那是:
printf 'GET www.example.com HTTP/1.0\r\n\r\n' | nc -q 3 127.0.0.1 80
# or
printf 'GET http://www.example.com/ HTTP/1.0\r\n\r\n' | nc -q 3 127.0.0.1 80
这就是您的低技术浏览器。优点是能够编写任何类型的错误查询。 RFC 中存在用于代替第一个标头中的位置的绝对 url,以处理与非常旧的 HTTP 0.9 的兼容性:
# HTTP 0.9
printf 'GET www.example.com/foo/bar \r\n' | nc -q 3 127.0.0.1 80
在此请求中,要使用的正确 VirtualHost 是来自该位置的虚拟主机,而不是来自 Host 标头的虚拟主机:
# regular HTTP query
printf 'GET http://www.vhost2.com/foo HTTP/1.0\r\nHost: www.vhost1.com\r\n\r\n' |\
nc -q 3 127.0.0.1 80
这是 2013 年 James Kettle 的 Host 头球攻击的基础。因此,我建议您有效跟踪小事件并修复它们。学习如何在服务器中注入任何特殊的 URL 总是好的。
您也可以使用curl发送精心设计的请求,但根据定义,真正的浏览器和命令行浏览器往往会避免发送精心设计的请求。
在处理大量此类服务时,在跟踪哪个服务器或代理发生故障时,从终端跟踪 HTTP 服务器的真实响应也非常有效。学习如何在 tcp 套接字中进行 HTTP 通信,无需使用curl,这非常有用。
首先安装Telnet。
然后将以下内容复制并粘贴到终端窗口中:
telnet localhost 80
GET www.example.com HTTP/1.0
按 Enter 两次。
这就是我的访问日志中的样子:
::1 - - [30/Oct/2020:23:37:28 -0700] "GET www.example.com HTTP/1.0" 400 310
您也可以使用 PHP 来完成此操作。这是我创建的一个名为 test.php 的文件:
<?php
$fp = stream_socket_client("tcp://localhost:80", $errno, $errstr, 30) or die("$errstr ($errno)");
$request = "GET www.example.com HTTP/1.0\r\n\r\n";
$written = 0;
do {
$bytes = fwrite($fp, substr($request, $written)) or die('fwrite error');
$written+= $bytes;
}
while ($written!==strlen($request));
fclose($fp) or die('fclose error');
echo 'Request sent successfully';
exit;
上面做了同样的事情,只是使用 PHP 来完成。使用上述文本创建一个 PHP 文件,将浏览器指向该文件,它应该显示“请求发送成功”,然后检查您的访问日志。
编辑:
如果您需要使用 TLS,请尝试以下操作。再次保存为 test.php 并将浏览器指向此文件:
$fp = fsockopen("tls://localhost", 443, $errno, $errstr, 30) or die("$errstr ($errno)");
$request = "GET www.example.com HTTP/1.0\r\n\r\n";
$written = 0;
do {
$bytes = fwrite($fp, substr($request, $written)) or die('fwrite error');
$written+= $bytes;
}
while ($written!==strlen($request));
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp) or die('fclose error');
echo 'Request sent successfully';
exit;
您可以使用curl(在某种程度上)发布精心设计的请求。 Curl 不验证方法(-X、--request 或 CURLOPT_CUSTOMREQUEST)。因此你可以使用“换行注入”。
这里是我通过 HTTP 发送代理请求的示例,我指示 apache 充当反向代理,为我启动 TLS(当我需要某些 anciet 服务器上的软件能够与仅接受最近的服务器进行通信时,我使用此技术TLS 版本)。
请注意,示例使用
bash
语法来正确插入 CR
和 LF
字符。
$ curl -v -X "$(echo -en 'GET https://target.domain/actual/path HTTP/1.1\r\nX-Ignore-Injection:' )" my.apache.proxy.example:8080/ignored/path
* Trying 1.2.3.4:8080...
* Connected to my.apache.proxy.example (1.2.3.4) port 8080 (#0)
> GET https://target.domain/actual/path HTTP/1.1
> X-Ignore-Injection: /ignored/path HTTP/1.1
> Host: my.apache.proxy.example:8080
> User-Agent: curl/7.83.0
> Accept: */*
...