SuiteCRM是一个开源的客户关系管理(CRM)软件应用程序。在版本7.14.4和8.6.1之前存在未授权SQL注入漏洞。
下载SuiteCRM 7.14.3,解压放在web目录,访问/install.php进行安装,配置完mysql和web管理员账号密码,点击next安装完后会自动跳转登录页面。
发送下面poc
GET /SuiteCRM-7.14.3/index.php?entryPoint=responseEntryPoint&event=1&delegate=<"+UNION+SELECT+SLEEP(3);--+&type=c&response=accept HTTP/1.1
Host: 192.168.1.100
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15
Accept-Encoding: gzip
Connection: close
Cookie: XDEBUG_SESSION=XDEBUG_ECLIPSE
Conent-Length: 2
成功延时3秒后返回
调用堆栈
MysqliManager.php:142, MysqliManager->query()
MysqlManager.php:285, MysqlManager->limitQuery()
DBManager.php:1659, DBManager->getOne()
responseEntryPoint.php:24, require_once()
SugarController.php:1017, SugarController->handleEntryPoint()
SugarController.php:465, SugarController->process()
SugarController.php:361, SugarController->execute()
SugarApplication.php:101, SugarApplication->execute()
index.php:52, {main}()
在SugarController::process方法下断点,可以看到会遍历$process_tasks数组中的6个proccess进行处理,并通过$_processed判断是否处理完成。
遍历到handleEntryPoint,跟进对应方法,会根据传入的entryPoint参数从entry_point_registry中获取对应的php文件进行包含。
跟进包含的modules/FP_events/responseEntryPoint.php文件,可以看到将get请求获取到的delegate参数直接拼接到了sql语句并执行,这就导致了sql注入。
细心的同学会发现,POC中的sql语句前面有一个<字符,这是必须的吗?没错,是必须的
如果去掉<符号,用"+UNION+SELECT+SLEEP(3);--+这样的payload请求,并没有延时返回。
调试代码可以看到,responseEntryPoint.php文件中通过$_GET获取到的$delegate_id由”变成了",被html实体编码了,这就导致无法闭合sqli语句前面的双引号,导致sql注入失败。
为什么加上<之后,”不会被html实体编码,而不加<,”却会被html实体编码?
这是由于系统的全局xss过滤机制在做怪!
访问index.php会包含entryPoint.php,entryPoint.php会调用utils.php的clean_incoming_data来清理所有的请求参数,包括xss的清理,堆栈如下
utils.php:2639, securexss()
utils.php:2520, array_map()
utils.php:2520, clean_incoming_data()
entryPoint.php:93, require_once()
index.php:47, {main}()
在securexss方法下断点,使用<"+UNION+SELECT+SLEEP(3);-- 请求,可以看到会通过$xss_cleanup这个数组来过滤xss的关键符号,经过过滤后的payload变成了<" UNION SELECT SLEEP(3);-- ,这时<和”都被html实体编码了。
如果到这里直接返回编码后的字符串$partialString,sql注入将不会存在,但是这里还交给了AntiXSS做进一步的过滤,看下再经过AntiXSS过滤后的结果。
"又变回了“,这就导致了单引号逃逸。
跟进xss_clean方法看看怎么回事儿,主要逻辑都在AntiXSS.php文件,处理路由在\\voku\\helper\\AntiXSS::_do,调用_decode_string会先html解码。
走到_sanitize_naughty_html会通过正则匹配标签开头如<、标签内容、标签结尾如>,然后将匹配的数组传入_close_html_callback。
_close_html_callback把标签内容部分的<和> html实体编码,标签内容部分是"+UNION+SELECT+SLEEP(3);--,前面再拼接一个<
_do方法处理后,会比较xss处理前和处理后的字符串是否相等,如果不相等,则将_xss_found标记为true,否则为false
所以传入<” 经过str_replace后变成<" ,再经过_do方法会变成<"并返回
如果传入”不包含<,经过str_replace后变成",再经过_do方法处理后_xss_found为false,即处理前和处理后字符串没变化,则保留原有的"返回。
这就解释了为什么传入<”能注入而”注入不了。
前面提到AntiXSS的_decode_string方法会对参数做html解码,所以可以AntiXSS对参数解码的特性对payload做一些变形,html编码+url编码SELECT关键字效果如下
对比最新版7.14.4,在responseEntryPoint.php中增加了$db->quote()来防止sql注入
官方7.14.4版本的更新公告中同时也修复了其他的漏洞,本次修复的其他sql注入漏洞利用方式跟本文分析的大同小异,就不做赘述。