Django:从Apache mod_rewrite重定向时出现错误的请求语法



Django在8000端口上运行,Apache在80端口上运行。我在apache中配置了以下重写规则以重定向到django:

RewriteRule ^/?checkout/ http://%{HTTP_HOST}:8000/checkout/ [L,QSA]

如果在浏览器中打开一个url,它可以很好地工作并完美地重定向。

然而,外部客户端(在没有apache的情况下直接连接到django时运行良好)总是会在django服务器上导致Bad Request Syntax错误。以下是Django日志中的片段。看起来Apache会自动将那些"内容长度"的东西附加到查询中,为什么?

[05/Mar/2014 18:01:35] code 400, message Bad request syntax ('GET /checkout/wx_signature?signature=b226bb8f6e9ce2fdecb752c6808a979c62e235f7&echostr=5987526888415258224&timestamp=1394042480&nonce=1394079741Content-Length: 445Connection: closeContent-Type: text/html; charset=iso-8859-1 HTTP/1.0')

tl;dr:这是由你的"外部客户端"中的一个错误引起的。它是一个设计糟糕的HTTP客户端,应该避免,因为它不仅会导致这个错误,而且可能会为安全漏洞开辟途径。

为了了解正在发生的事情,你需要逆向工作。


首先,让我们从Django内置服务器的日志行开始:

[05/Mar/2014 18:01:35] code 400, message Bad request syntax ('GET /checkout/wx_signature?signature=b226bb8f6e9ce2fdecb752c6808a979c62e235f7&echostr=5987526888415258224&timestamp=1394042480&nonce=1394079741Content-Length: 445Connection: closeContent-Type: text/html; charset=iso-8859-1 HTTP/1.0')

"代码400"是指HTTP状态代码400。这意味着实际的HTTP请求构造不正确,无法理解。幸运的是,Django记录了错误的输入,因此我们可以对其进行分析


现在我们了解了问题的本质,我们将删除不相关的日志错误和长签名,以更深入地了解实际请求:

GET /checkout/wx_signature?[SIGNATURE REMOVED]Content-Length: 445Connection: closeContent-Type: text/html; charset=iso-8859-1 HTTP/1.0

在这里,我们看到HTTP请求的第一行无效。


来自RFC2616第5.1节:

请求行以方法令牌开始,然后是请求URI和协议版本,最后以CRLF结束。元素由SP字符分隔。除最终CRLF序列外,不允许使用CR或LF。

Request-Line   = Method SP Request-URI SP HTTP-Version CRLF

在无效请求中,我们可以识别出HTTP谓词GET在那里,HTTP/1.0的版本结尾在那里,所以这些都不是问题。中间部分,应该是URL,如下所示:

/checkout/wx_signature?[SIGNATURE REMOVED]Content-Length: 445Connection: closeContent-Type: text/html; charset=iso-8859-1

在发送到服务器之前,URL中的空格通常被+%20替换。正如您所看到的,这里的情况并非如此,它是无效请求的原因。一个好的HTTP客户端永远不会这样做,因为它会自动转义URL这是一个危险信号,表明您使用的"外部客户端"质量较差


请注意,该空间出现在许多看起来奇怪的字段旁边。

如果您查看RFC2616第14.13节,就会发现Content-Length实际上是HTTP1.1标头的名称。ConnectionContent-Type也是如此。

它显然不属于那里,那么为什么它与URL连接在一起呢?

从这里我只能猜测,因为我无法访问您的代码。然而,我认为我对正在发生的事情有一个很好的了解。


让我们先了解一下HTTP标头的性质。我们将发送一个原始请求,以模拟我们访问时发生的情况。"http://google.com"。这将触发谷歌将我们重定向到"http://www.google.com".

原始请求

GET / HTTP/1.1
Host: google.com

原始响应

HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
Date: Thu, 15 May 2014 21:28:46 GMT
Expires: Sat, 14 Jun 2014 21:28:46 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 219
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Alternate-Protocol: 80:quic
[HTML content removed]

哇,谷歌返回了一大堆标题!不过,我们只对前几行感兴趣:

HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/
Content-Type: text/html; charset=UTF-8
...
Content-Length: 219

在这里,您可以看到Content-TypeContent-Length和其他标头跟在Location标头之后。实际订单通常并不重要,因为HTTP客户端或服务器足够聪明,能够理解每一个订单的含义。但是,如果去掉Location标头之后的行结尾,该怎么办?

你最终会得到这样的东西:

HTTP/1.1 301 Moved Permanently
Location: http://www.google.com/Content-Type: text/html; charset=UTF-8Content-Length: 219

哦。。。如果您是HTTP客户端,您会认为我想将您重定向到http://www.google.com/Content-Type: text/html; charset=UTF-8Content-Length: 219


这看起来就像你的症状。。。但为什么会发生这种情况呢?

Apache不太可能以这种损坏的形式返回标头(除非您自定义了插件或类似的东西)。

这也是不太可能你的"外部客户端">故意在收到信头后剥离信头中的行尾。

可能的情况是,"外部客户端"被编码为将内容之前和Location:之后的所有内容解释为URL,然后在某个地方去掉CRLF字符(通常是出于安全原因在处理HTTP标头时这样做的,具有讽刺意味的是,在这种情况下做得不正确)。客户端尝试使用HTTP/1.0而不是HTTP/1.1发送请求,这一事实支持这一点,因为HTTP/1.0客户端在功能方面通常非常有限,并且往往会根据其过时的知识做出沉重的假设。

您的"外部客户端"很可能在请求行之后将整个标头读取为字符串,字符串处理程序会自动剥离CRLF。


我认为很明显,问题在于"外部客户端",尽管没有足够的信息来深入研究。

我建议您使用不同的客户端或库来完成请求。

当您在Django中使用HTTPS url时,似乎会出现此消息。您可能还需要在Apache2中配置HTTPS配置,灵感来自这个问题,例如:带有django和mod_wsgi 的基于SSL的虚拟主机

相关内容

  • 没有找到相关文章

最新更新