漏洞介绍

ATutor是ATutor团队的一套开源的基于Web的学习内容管理系统(LCMS),该系统包括教学内容管理、论坛、聊天室等模块。Atutor与Claroline、 Moddle及Sakai号称为四大开源课程管理系统。

ATutor2.2.4语言导入功能处存在一处安全漏洞(CVE-2019-12169),攻击者可利用该漏洞进行远程代码执行攻击。

经过分析发现,除了CVE-2019-1216所报道的语言导入功能外,ATutor在其他功能模块中也大量存在着相似的漏洞,本文会在后面针对这一点进行介绍。

漏洞分析

据漏洞披露可知,漏洞触发点存在于mods/_core/languages/language_import.php文件中

首先跟入language_import.php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
/****************************************************************/
/* ATutor */
/****************************************************************/
/* Copyright (c) 2002-2010 */
/* Inclusive Design Institute */
/* http://atutor.ca */
/* */
/* This program is free software. You can redistribute it and/or*/
/* modify it under the terms of the GNU General Public License */
/* as published by the Free Software Foundation. */
/****************************************************************/
// $Id$

define('AT_INCLUDE_PATH', '../../../include/');
require(AT_INCLUDE_PATH.'vitals.inc.php');
admin_authenticate(AT_ADMIN_PRIV_LANGUAGES);

require_once(AT_INCLUDE_PATH.'classes/pclzip.lib.php');
require_once(AT_INCLUDE_PATH.'../mods/_core/languages/classes/LanguageEditor.class.php');
require_once(AT_INCLUDE_PATH.'../mods/_core/languages/classes/LanguagesParser.class.php');

/* to avoid timing out on large files */
@set_time_limit(0);

$_SESSION['done'] = 1;

if (isset($_POST['submit_import'])){
require_once(AT_INCLUDE_PATH.'../mods/_core/languages/classes/RemoteLanguageManager.class.php');
$remoteLanguageManager = new RemoteLanguageManager();
$language_code = explode("_",$_POST['language']);
$remoteLanguageManager->import($_POST['language']);
header('Location: language_import.php');
exit;
} else if (isset($_POST['submit']) && (!is_uploaded_file($_FILES['file']['tmp_name']) || !$_FILES['file']['size'])) {
$msg->addError('LANG_IMPORT_FAILED');
} else if (isset($_POST['submit']) && !$_FILES['file']['name']) {
$msg->addError('IMPORTFILE_EMPTY');
} else if (isset($_POST['submit']) && is_uploaded_file($_FILES['file']['tmp_name'])) {
$languageManager->import($_FILES['file']['tmp_name']);
header('Location: ./language_import.php');
exit;
}

从language_import.php文件中35行起,可以发现文件上传相关代码

f1d2c8415c4c633298881ab7bf76d404.png

从上图红框中代码可知,此处代码块是对文件上传情况进行校验

在文件成功上传后,进入下一个if分支

1121c09a5fd3ee682fdc0f35913ee33d.png

在这个分支里,程序将调用$languageManager->import方法对文件进行处理

继续跟入import方法,位于/mods/_core/languages/classes/LanguageManager.class.php文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// public
// import language pack from specified file
function import($filename) {
global $languageManager, $msg;

if(strstr($_FILES['file']['name'], 'master')){
// hack to create path to subdir for imported github language packs
$import_dir = str_replace(".zip", "", $_FILES['file']['name']).'/';
} else if(isset($_POST['language'])){
$import_dir = $_POST['language'].'-master/';
}
require_once(AT_INCLUDE_PATH.'classes/pclzip.lib.php');
require_once(AT_INCLUDE_PATH.'../mods/_core/languages/classes/LanguagesParser.class.php');

$import_path = AT_CONTENT_DIR . 'import/';
$import_path_tmp = $import_path.$import_dir;
$language_xml = @file_get_contents($import_path.'language.xml');
$archive = new PclZip($filename);

if ($archive->extract( PCLZIP_OPT_PATH, $import_path) == 0) {
exit('Error : ' . $archive->errorInfo(true));
}

在import方法中程序调用PclZip对压缩包进行处理

1
2
3
4
5
$archive = new PclZip($filename);

if ($archive->extract( PCLZIP_OPT_PATH, $import_path) == 0) {
exit('Error : ' . $archive->errorInfo(true));
}

为了更好的理解import方法的执行流程,我们将会进行动态调试。首先构造一个poc.php

1
<?php phpinfo(); ?>

将这个poc.php打包为poc.zip

0a70cb1152c4da8afdb860bb66d642d9.png

访问如下链接以进入上传页面

http://target/ATutor/mods/_core/languages/language_import.php

5a822f926e62f0615ed92126333ba784.png

在上传语言包页面中选择构造好的poc.zip并点击import按钮上传。当请求发送给后台服务器,程序执行到下图断点处

ed87891f5ab9fc8afbf586cb8eade5ed.png

此时的$import_path值为atutor应用的/content/import路径:”content/import/”,程序调用PclZip的extract方法对压缩包进行解压。接下来我们介绍以下PclZip类

PclZip

PclZip是一个强大的压缩与解压缩zip文件的PHP类,PclZip library不仅能够压缩与解压缩Zip格式的文件;还能解压缩文档中的内容,同时也可以对现有的ZIP包进行添加或删除文件。

我们接下来看下import方法中是如何使用PclZip,见下图

bef56d465f0158a156524a4d431262e2.png

程序创建了上传的zip压缩包的一个PclZip对象进行操作与控制,在解压过程中使用了extract方法。该方法中第一个参数是设置项,第二个是对应设置项的值

我们来看下PCLZIP_OPT_PATH设置项的作用

6bf20e6b27427f39a0b6d536daa372e8.png

可见,PCLZIP_OPT_PATH设置项指定我们上传的zip文件解压目录为$import_path参数对应的路径

解压成功后,poc.zip中内容出现在对应文件夹中

dbfe063613c36620cdfc66cc3665443d.png

查看poc.php中的值,可以发现poc上传成功

64cc162245f48311e259db798bb674a4.png

访问如下地址,触发poc

5da715061c21b6f94f2365b838a1a8bc.png

除此之外,该应用几乎所有import接口,在后台都采用PclZip将上传的zip解压到对应目录中。然而这些操作无一例外的未对压缩包中的文件进行校验。下面举几个例子:

位于mods/_core/themes/import.php文件中的主题导入功能,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* Imports a theme from a URL or Zip file to Atutor
* @access private
* @author Shozub Qureshi
*/
function import_theme() {
global $db;
global $msg;

if (isset($_POST['url']) && ($_POST['url'] != 'http://') ) {
if ($content = @file_get_contents($_POST['url'])) {



// unzip file and save into directory in themes
$archive = new PclZip($_FILES['file']['tmp_name']);

//extract contents to importpath/foldrname
if (!$archive->extract($import_path)) {
$errors = array('IMPORT_ERROR_IN_ZIP', $archive->errorInfo(true));
clr_dir($import_path);
$msg->addError($errors);
header('Location: index.php');
exit;
}

可以发现这里也使用了extract方法将上传文件进行解压

来看一下导入主题功能对应的前端页面

118a2873749d6200f96edfa1790637c9.png

这里页面与导入语音包的页面极其相似,只不过最终解压后存放的路径不同,不再是content/import/,而是themes/

在此处上传构造好的poc.zip,最终poc.php将会被解压到themes文件夹中

25a945cf7202608265fdab6909a4e825.png

位于/mods/_standard/tests/question_import.php文件的问题导入功能

54cecd39032e9d1de591ef8498bd8c04.png

位于mods/_standard/patcher/index_admin.php文件的补丁导入功能

1962fed0465d4cdf835a3274e8065876.png

这些功能无一例外的存在着相似的漏洞

总结

针对单一文件上传,大多数Web应用都会进行严格的文件类型检测,但是涉及到压缩包上传,很多应用都不会检测压缩包中的内容,直接将压缩包中内容解压到对应目录中,这样就会导致了这类漏洞的产生。