原文:https://erpscan.com/press-center/blog/oracle-ebs-password-decryption/
0x00 简介
作为企业软件的领导厂商之一,甲骨文拥有着丰富的产品线,比如Oracle EBS(电子商务套件)便是其中之一,这是一款广泛应用于汽车、航空和国防、工程和建筑、健康科学、酒店、专业服务等各种行业的ERP系统。
据报道,Oracle EBS已经曝出多个安全漏洞,其实,除此之外,该软件的体系结构本身的问题,也对其安全性构成了严重的威胁。在黑客眼里,这些弱点没多大区别,因为都可以派上用场。
在本文中,我们将为读者深入介绍Oracle EBS的安全机制,如它是如何存储EBS应用程序用户的密码的,以及黑客是如何利用这一安全缺陷的。
0x01 Oracle EBS应用程序的用户密码简介
实际上,Oracle EBS应用程序用户的密码具有两种不同的存储模式:不可逆的哈希模式和加密模式。
所谓不可逆的哈希模式,就是使用哈希函数将密码转换为特殊的哈希值,而这些哈希值通常很难被恢复为原始值,所以称之为不可逆的。并且,在这种模式下,检查密码的完整性也是非常容易的事情。
Oracle EBS可以使用诸如SHA-1、SHA-256、SHA-368、SHA-512之类的哈希算法。即使攻击者获得了对Oracle EBS数据库的访问权限并窃取了密码数据,也只能通过使用暴力攻击才可能获得原始密码。不过,这种攻击通常需要消耗大量的时间和计算机资源,并且破解难度具体取决于所使用的哈希算法的类型。
0x02 Oracle EBS密码的加密模式
值得注意的是,在默认情况下该系统是不会启用哈希模式的。如果读者对如何迁移到密码哈希模式感兴趣的话,不仅网上有大量的文章可供参考,同时,还可以从Oracle EBS安全指南中的“Switch to Hashed Passwords”(第10-11页)部分找到相关的答案。
在我们的安全审计实践过程中,经常发现有客户在EBS系统上采用加密模式。但是,这种模式实际上是不安全的,具体原因读完本文您就知道了。
在加密模式下,EBS会把应用程序用户的密码存储在FND_USER表中。
FND_USER表中有两列非常重要,它们分别是:ENCRYPTED_FOUNDATION_PASSWORD和ENCRYPTED_USER_PASSWORD。
其中,ENCRYPTED_FOUNDATION_PASSWORD列用于存储对用户名和用户密码的组合加密而成的APPS用户密码。
而ENCRYPTED_USER_PASSWORD列则用来存储对APPS用户密码加密而成的用户密码。
所以,如果有人找到了APPS用户密码,那么,自然就可以解密所有EBS应用程序用户的密码了,但是具体该如何实施呢?
一种方法是在数据库中创建一个专门的过程。
CREATE OR replace FUNCTION decrypt(KEY IN VARCHAR2,
value IN VARCHAR2)
RETURN VARCHAR2 AS LANGUAGE JAVA name 'oracle.apps.fnd.security.WebSessionManagerProc.decrypt(java.lang.String,java.lang.String) return java.lang.String';
CREATE OR replace FUNCTION decrypt(KEY IN VARCHAR2,
value IN VARCHAR2)
RETURN VARCHAR2 AS LANGUAGE JAVA name 'oracle.apps.fnd.security.WebSessionManagerProc.decrypt(java.lang.String,java.lang.String) return java.lang.String';
/
这个过程可以通过EBS Java类的内部函数来解密EBS系统中的所有EBS应用程序用户的密码(这一点,将在后面详细探讨)。这个过程需要两个参数:第一个参数是密钥值,第二个参数是密文。所以,这里还需要找到密钥值。根据我们的经验,这个问题可以借助于EBS的默认功能来解决。
我们可以通过如下所示的sql查询来获取GUEST用户的密码:
SELECT fnd_web_sec.get_guest_username_pwd FROM dual;
这样,既然有了GUEST的GUEST用户密码值和ENCRYPTED_FOUNDATION_PASSWORD(如果您还记得话,该列是用于存储加密后的APPS用户密码的),就可以使用我们的“解密”过程了。我们可以使用以下sql语句来解密APPS用户的密码:
SELECT (SELECT Decrypt((SELECT fnd_web_sec.get_guest_username_pwd
FROM dual), fu.encrypted_foundation_password)
FROM dual) AS apps_password
FROM fnd_user fu
WHERE fu.user_name LIKE (SELECT
Substr(fnd_web_sec.get_guest_username_pwd, 1, Instr(
fnd_web_sec.get_guest_username_pwd, '/') - 1)
FROM dual);
SELECT (SELECT Decrypt((SELECT fnd_web_sec.get_guest_username_pwd
FROM dual), fu.encrypted_foundation_password)
FROM dual) AS apps_password
FROM fnd_user fu
WHERE fu.user_name LIKE (SELECT
Substr(fnd_web_sec.get_guest_username_pwd, 1, Instr(
fnd_web_sec.get_guest_username_pwd, '/') - 1)
FROM dual);
现在,我们就能得到APPS用户密码了,有了它,便可以进一步解密所有EBS应用程序用户的密码了。
所以,正如你所看到的,这种机制存在严重的安全漏洞。通过该漏洞,黑客甚至发动网络攻击。所以,我们有必要对加密算法的细节做进一步的研究。不过,用户的密码加密是如何实现的呢?实际上,这个任务是由EBS的oracle.apps.fnd.security.AolSecurity'java类完成的。这个类实现了一个“AolSecurityPrivate.decrypt”函数,该函数会解析输入的密文的前两个字符。如果它们等于ZH或ZG,则执行“newDecrypt”函数,否则,会执行另一个函数,即“oldDecrypt”函数。下面,让我们来深入了解这些Java代码,同时学习一下EBS加密的工作原理。
0x03 Oracle EBS密码的新解密方法
为了便于理解,我们将使用一个简单的示例密钥和密文值进行演示。下面,我们为您展示“newDecrypt”函数的密文值示例。
ZG40BAC40988B247F17B1C64007417B5AD0FE61E671B0344765749CAC2733D4A912FB99DC43C6BEDBC460E2DDA98636E1974
就像所有的示例那样,这里的密钥值也是默认的APPS用户密码,即APPS(惊讶吗!)
“newDecrypt”函数会从加密值中提取ZH或ZG,并将其他部分转换为字节数据。
现在,我们就生成了一个字节数组。
40 BA C4 09 88 B2 47 F1 7B 1C 64 00 74 17 B5 AD 0F E6 1E 67 1B 03 44 76 57 49 CA C2 73 3D 4A 91 2F B9 9D C4 3C 6B ED BC 46 0E 2D DA 98 63 6E 19 74
此外,一些salt字节也计算在内,这些字节是从加密值的尾部提取的。我们称这个新的加密值为freshBytes。salt字节的数量取决于加密值的长度(其长度也计算在内)。在我们的例子中,加密值的长度为100,salt字节数为1,因此,freshBytes值为:
40 BA C4 09 88 B2 47 F1 7B 1C 64 00 74 17 B5 AD 0F E6 1E 67 1B 03 44 76 57 49 CA C2 73 3D 4A 91 2F B9 9D C4 3C 6B ED BC 46 0E 2D DA 98 63 6E 19 74
然后,将这个密钥值表示为十六进制:
‘APPS’ -> 41 50 50 53
并添加salt字节,这样新的密钥值变成:
74 41 50 50 53
之后,将这个密钥值传给SHA-1函数进行处理,不过,这里的处理方式有些特殊:最终生成的密钥哈希值是SHA-1 {Key +'1'}的 20个字节和SHA-1 {Key +'2'}的前12个字节的连接而成的。
然后,使用密钥的哈希值,借助3DES CBC算法对freshBytes进行相应的转换。最后,利用“newDecrypt”函数从转换后的freshBytes中获取解密后的值,即取第三个字节至00字节之间的值。在下面的例子中,我们可以看到解密后的freshBytes值以及
13 EC 45 52 50 53 63 61 6E 54 65 61 6D 00 1E DD 49 C1 7B 2D 9D 5F 67 D0 A6 10 13 86 3D 5 FB E1 15 AF 97 9E
解密后的值是45 52 50 53 63 61 6E 54 65 61 6D,这实际上就是ERPScanTeam的值的ASCII表示形式。
0x04 Oracle EBS密码的旧解密方式
就“oldDecrypt”函数而言,与“newDecrypt”函数有许多不同之处。下面,让我们看看它的具体实现。
首先,“oldDecrypt”会接收密钥值,然后通过类SHA-1算法进行相应的转换。这种算法之所以被称为类SHA-1,因为它们是在SHA-1的默认实现的基础上,修改一些步骤而得到的。
类SHA-1算法
其中,第一个不同的地方出现在数据块的创建步骤中。SHA-1算法会接收密钥字节值,然后依次追加\x80和K\x00-s,最后添加消息的长度,并将所有内容交由预处理函数处理。SHA-1和类SHA-1算法在这里的主要区别是K值。其中,SHA-1是这样计算的:
而类SHA-1算法则是这样处理:
那么,这到底意味着什么呢?举例来说,如果密钥长度等于55,那么类SHA-1算法会创建长度为128的数据块,而SHA-1算法创建的数据块的长度为64,具体如下图所示。
第二个不同的步骤,出现在数据块的预处理阶段。SHA-1算法将每个数据块转换为16个4字节的大端字。而类SHA-1的算法则将前面的数据块转换为16个4字节小端字,并将最后一个数据块转换为14个4字节小端字加2个4字节大端字。
下一个重大区别出现在哈希轮内的数据置换阶段:这两种算法采用的处理方式是完全不同的。SHA-1算法的处理方式为:
在类SHA-1算法中,相应的处理方式如下所示:
最后,类SHA-1算法的最后一个不同的地方是结果值初始化。SHA-1算法会将新的哈希值与先前的哈希值相加并返回结果。而类SHA-1算法则直接对新的哈希值进行置换并将其返回。
下面,让我们来看一个"oldDecrypt"函数的密钥和密文的示例。
C4 E9 B5 91 09 8E A0
类SHA-1算法将密钥(仍然是APPS)转换为如下所示的密钥哈希值数组。
0xcfee3a71, 0x7f7a2c54, 0x95e23be4, 0x25788ef9, 0x9a78bf48
稍后,“oldDecrypt”会接收类SHA-1算法生成的最终密钥哈希值,将每个值以字节为单位进行分隔,并通过异或操作来提取相应的字节。因此,它创建的是精简型密钥哈希值。
在我们的例子中,精简型密钥哈希值为:
0x6a, 0x7d, 0xa8, 0x2a, 0x15
并且,精简型密钥哈希值长度总是5个字节,但是这对于暴力破解攻击来说,这个长度是远远不够的。
最后,“oldDecrypt”会使用精简型密钥哈希值(类似RC4算法中的密钥)来解密密码数据。
密文被转换成APPSKEY,这就是解密后的值。
0x05 Oracle EBS密码解密方案
下面展示的是EBS Application用户密码解密的完整方案。
所以,为了达到无声无息的安全工作的目标,Oracle决定实现自己的哈希函数版本。由于实现的这个类SHA-1算法是建立在SHA-1算法的基础上的,所以,它也同时继承了原算法的所有局限性和安全漏洞。
0x06 小结
那么,我们为什么要对解密函数的实现进行如此深入的分析呢?这是因为,有时候(例如,测试生产系统中的某些功能,或数据库的某些交互功能失效)能够通过最少的数据库交互来解密Oracle EBS应用程序用户的密码是至关重要的。如果遇到这些情况,可以借助我们通过python语言实现的Oracle EBS解密算法。另外,这本身也是一个非常有趣的研究主题。最后,如果需要,可以在我们的GitHub存储库中找到Oracle EBS解密算法的python实现。