sugarcrm php代码注入分析

这篇文章准备通过通过请求语句来看传入的数据在代码中流向,这样或许更方便来理解这个漏洞

PoC

1
http://[host]/[sugar]/index.php?module=Connectors&action=RunTest&source_id=ext_rest_insideview&ext_rest_insideview_[%27.phpinfo().%27]=1

最后的效果就是程序会执行phpinfo()函数。

流程分析

入口函数action_RunTest()

当访问POC时,程序会进入到modules/Connectors/controller.php中的action_RunTest()函数中。

其中的source_id就是PoC中传入的ext_rest_insideview,至于为什么会传入这个,之后的分析会讲到。

SourceFactory::getSource()

跟踪SourceFactory::getSource()函数,进入include/connectors/sources/SourceFactory.php

发现在其中调用了ConnectorFactory::load()方法,其中的$class就是传入的ext_rest_insideview

load()

跟踪ConnectorFactory::load()函数,进入include/connectors/ConnectorFactory.php。发现load()函数又调用loadClass()函数。

在loadClass()函数中,会尝试导入一个文件,导入的格式就是.../connectors/{$type}/{$dir}/$file。其中$type是传入的sources,$dir是将$class(在本PoC中为ext_rest_insideview)字符串中的_替换为/的一个路径,所以最后$dir的值为ext/rest/insideview,这个在图片上也有显示,$file就是路径的最后一个值insideview.php。最后程序就会尝试去寻找对应的文件,如果没有找到就会报错。

所以Poc中的source_id=ext_rest_insideview并不能随便写为任意值。假若写为source_id=a_b_c,那么在执行loadClass()的时候无法找到文件导致程序无法往下执行,那么payload就无用了。

setsetProperties()

在对loadClass()分析完毕之后,最后回到入口函数action_RunTest()
程序往下执行进入到setProperties()方法中。

其中的foreach()就会对传入值赋值到$properties中,最后得到的$properties的值如左边的图所示,即为
[''][''.phpinfo().''] = '1';
跟踪setProperties(),进入include/connectors/sources/default/source.php,setProperties()代码如下:

1
2
3
4
5
6
7
public function setProperties($properties=array())
{
if(!empty($this->_config) && isset($this->_config['properties'])) {
$this->_config['properties'] = $properties;
$this->config_decrypted = true; // Don't decrypt external configs
}
}

那么最后,得到在config中得到就是:

1
$config['properties'][''][''.phpinfo().''] = '1';

saveConfig()

对setProperties()分析完毕之后,回到入口函数action_RunTest()
程序继续往下执行,进入到saveConfig()中。

其中关键的地方就在于将变量$this_config中的键值对,调用override_value_to_string_recursive2()函数变为一个字符串。

override_value_to_string_recursive2()

跟踪override_value_to_string_recursive2(),进入到include/utils/array_utils.php中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function override_value_to_string_recursive2($array_name, $value_name, $value, $save_empty = true) {
if (is_array($value)) {
$str = '';
$newArrayName = $array_name . "['$value_name']";
foreach($value as $key=>$val) {
$str.= override_value_to_string_recursive2($newArrayName, $key, $val, $save_empty);
}
return $str;
} else {
if(!$save_empty && empty($value)){
return;
}else{
return "\$$array_name" . "['$value_name'] = " . var_export($value, true) . ";\n";
}
}
}

可以看到这就是一个普通的将一个数组类型的变量转化为一个字符串,最后$this_conifg变为:

1
2
3
4
5
<?php
/***CONNECTOR SOURCE***/
$config['name'] = 'InsideView&#169;';
$config['order'] = 65;
$config['properties'][''][''.phpinfo().''] = '1';

这个赋值给变量$config_str

PoC执行

回到saveConfig()函数中,程序最后执行

1
file_put_contents("custom/modules/Connectors/connectors/sources/{$dir}/config.php", $config_str);

其中的$dir为ext/rest/insideview,那么最后程序就会在custom/modules/Connectors/connectors/sources/ext/rest/insideview/config.php写入$config_str的值,最后就会触发其中的phpinfo()函数,导致代码执行。
最后在config.php中写入的代码是:

1
2
3
4
5
6
7
<?php
$config = array (
'name' => 'InsideView&#169;',
'order' => 65,
'properties' => array (
),
);

自此漏洞就分析完毕了

修复

修复方法很简单,在override_value_to_string_recursive2()函数中进行修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function override_value_to_string_recursive2($array_name, $value_name, $value, $save_empty = true) {
$quoted_vname = var_export($value_name, true);
if (is_array($value)) {
$str = '';
$newArrayName = $array_name . "[$quoted_vname]";
foreach($value as $key=>$val) {
$str.= override_value_to_string_recursive2($newArrayName, $key, $val, $save_empty);
}
return $str;
} else {
if(!$save_empty && empty($value)){
return;
}else{
return "\$$array_name" . "[$quoted_vname] = " . var_export($value, true) . ";\n";
}
}
}

修复的代码就是使用了var_export()函数对$value_name变量进行了字符串的表示。这样写之后,最终得到$config_str的值是

1
2
3
4
5
<?php
/***CONNECTOR SOURCE***/
$config['name'] = 'InsideView&#169;';
$config['order'] = 65;
$config['properties']['']['\'.phpinfo().\''] = '1';

上面的代码就可以正常地写入到文件中,不会触发代码执行了。

总结

通过分步调试的方法,能够对这个漏洞理解得更加的透彻,通过这个漏洞也增加了自己调试漏洞的能力。

参考

PHP漏洞跟踪报告 http://blog.nsfocus.net/php-vulnerability-tracking-report/