工作准备
·一台Oracle数据库服务 版本为10g、11g、12c三个版本
·甲骨文官方提供的Oracle数据库客户端instanceclient 18.3.x
·Java开发环境eclipse + jdk1.8
·Java逆向反编译工具JD-Core
反编译ODBC8分析甲骨文客户端(instanceclient)与服务器(Oracle database)通信原理
1、经过反编译分析T4CConnection.class文件我们可以看到登录函数logon()所执行的伪代码如下所示:
void logon() throws SQLException {
long var1 = 0L;
Object var3 = null;
boolean var22 = false;
try {
……
this.auth = new T4CTTIoauthenticate(this, this.resourceManagerId);
……
if (this.userName != null && this.userName.length() != 0) {
try {
this.auth.doOSESSKEY(this.userName, this.LOGON_MODE);
……
} catch (SQLException var27) {
if (var27.getErrorCode() != 1017) {
throw var27;
}
this.userName = null;
}
}
this.auth.doOAUTH(this.userName,this.password.get(),this.newPasswordValue.get(),this.LOGON_MODE);
……
}
从这段伪代码中可以看出首先程序实例化了T4CTTIoauthenticate实例,之后依次调用了该实例的doOSESSKEY(String,String)和doOAUTH(String,String,Stirng,int)两个函数。
2、分析反编译后doOSESSKEY和doOAUTH函数源代码
(1)首先是函数doOSESSKEY(String,String),具体如下
void doOSESSKEY(String var1, long var2) throws IOException, SQLException {
this.setFunCode((short) 118);
this.user = this.meg.conv.StringToCharBytes(var1);
this.logonMode = var2 | 1L;
this.keyValList = new T4CKvaldfList(this.meg.conv);
this.keyValList.add("AUTH_TERMINAL", this.terminal);
if (this.programName != null) {
this.keyValList.add("AUTH_PROGRAM_NM", this.programName);
}
this.keyValList.add("AUTH_MACHINE", this.machine);
this.keyValList.add("AUTH_PID", this.processID);
this.keyValList.add("AUTH_SID", this.sysUserName);
this.outNbPairs = 0;
this.outKeys = (byte[][]) null;
this.outValues = (byte[][]) null;
this.outFlags = new int[0];
this.doRPC();
}
配合tcp通信数据分析如下:
从通信数据分析可知客户端向服务器发送登录所使用的用户账号scott,服务器响应客户端一个AUTH_SESSKEY以及AUTH_VFR_DATA等一系列信息。
(2)函数doOAUTH(String,String,Stirng,int),由于此函数代码比较长此处只展示关键部分伪代码如下所示:
void doOAUTH(String var1, @Blind String var2, @Blind String var3, long var4) throws IOException, SQLException {
……
this.o5logonHelper = new O5Logon(this.connection,this.connection.isO7L_MRExposed, this.connection.thinUseJCEAPI);
……
this.encryptedKB = new byte[this.encryptedSK.length];
for (int var22 = 0; var22 < this.encryptedKB.length; ++var22) {
this.encryptedKB[var22] = 1;
}
int[] var34 = new int[1];
var23 = new byte[256];
int[] var24 = new int[1];
byte[] var25 = new byte[256];
for (int var26 = 0; var26 < 256; ++var26) {
var23[var26] = 0;
}
……
try {
this.o5logonHelper.generateOAuthResponse(this.verifierType, this.salt, var19, var20,
var7, this.encryptedSK, this.encryptedKB, var23, var34,
this.meg.conv.isServerCSMultiByte,
this.connection.getServerCompileTimeCapability(4), this.PBKDF2Salt,
this.PBKDF2VgenCount, this.PBKDF2SderCount, var25, var24);
} catch (Exception var30) {
;
}
……
String var33 = this.connection.sessionProperties.getProperty("AUTH_SVR_RESPONSE");
try {
if (this.o5logonHelper == null) {
this.o5logonHelper = new O5Logon(this.connection, this.connection.isO7L_MRExposed,
this.connection.thinUseJCEAPI);
}
if (!this.o5logonHelper.validateServerIdentity(var33)) {
throw (SQLException) ((SQLException) DatabaseError
.createSqlException(this.getConnectionDuringExceptionHandling(), 452).fillInStackTrace());
}
} catch (Exception var29) {
throw (SQLException) ((SQLException) DatabaseError
.createSqlException(this.getConnectionDuringExceptionHandling(), 452).fillInStackTrace());
}
}
}
配合tcp通信数据分析如下:
客户端向Oracle服务发送用户名scott、AUTH_PASSWORD、AUTH_SESSKEY等认证信息,Oracle服务响应客户端认证结果AUTH_SVR_RESPONSE等认证信息。
3、得出认证流程图如下所示:
认证方式分析
经过笔者逆向分析得知oracle在使用o5logon认证模式时目前常见认证方式有(数字表示):2361、6949、18453。其中2361为10g使用,6949为11g所使用,18453为12c所使用。
具体算法可参考O5Logon.class中函数public final void generateOAuthResponse(......)中的代码,伪代码示例如下所示:
public final void generateOAuthResponse(……) {
……
if (var1 == 18453) {
……
} else {
var2 = this.a(var1, var3, var4, var14, var2, var15);
}
……
}
} else {
throw new Exception("Resource A missing.");
}
}
……
private byte[] a(int var1, String var2, String var3, boolean var4, byte[] var5, byte var6) {
byte[] var7;
int var9;
if (var1 == 2361) {
……
} else if (var1 != 6949 && var1 != 45394) {
……
} else {
……
}
return var7;
}
完善用户名密码登录认证程序
由于代码比较长此处只展示main函数中的样例,有兴趣的读者可自行逆向分析阅读更多内容,笔者测试代码以及结果如下:
public static void main(String[] args) throws Exception {
int verifierType = 18453;
String user = "sys1111";
String password = "tiger";
String authPassword = "ECD6A199458BEBDE31A4C2814CC05E6BE0054757D038E97231D14BFF7784D044";
String encryptedSK = "307939F649C90D864E54427F9FB8F49EEB8521EC32C66B19EA63531533C32453";
String encryptedKB = "38037DE9995BE04119130D72398517471DB557A6834C588E05C5336333390960";
byte[] salt = "475DD077C5BEF46CC44E1C9961EAD417".getBytes();
byte[] AUTH_PBKDF2_CSK_SALT = "C33538DE162A29738CE7D5B663E14078".getBytes();
byte[] auth_pbkdf2_speedy_key = "F395C5279960450C939ADD704E5AE4AFFC90A551FD21C7E3550074B44C33BD423272D006536A3B712AD9BB5674DDB875A44AD8D44E9527C6DB3AE84F9739EA999E92B6BAF7E47BF8F3353A4D1005726B".getBytes();
String AUTH_SVR_RESPONSE = "7F60E6F9CA05504FD53650E40A6C3C42DE6B77E2C9740D38EA33CE04A01F7E75AE209C4165ABCF87DAF69C38AC79B1E6";
int vgenCount = 4096;
int sder_count = 3;
byte serverCompileTimeCapabilities = 0;
boolean isServerCSMultiByte = false;
BugByCodeO5Logon logon = new BugByCodeO5Logon(true);
byte[] encryptedSK_tmp = logon.getEncryptedSK(verifierType, salt, user, authPassword, vgenCount, serverCompileTimeCapabilities, isServerCSMultiByte);
encryptedSK = new String(encryptedSK_tmp);
System.out.println("loginName:" + user);
System.out.println("password:" + password);
System.out.println("=====================================");
System.out.println("verifierType:" + verifierType);
System.out.println("AUTH_VFR_DATA:" + new String(salt));
System.out.println("AUTH_PBKDF2_CSK_SALT:" + new String(AUTH_PBKDF2_CSK_SALT));
System.out.println("AUTH_PBKDF2_VGEN_COUNT:" + vgenCount);
System.out.println("AUTH_PBKDF2_SDER_COUNT:" + sder_count);
System.out.println("AUTH_SESSKEY:" + encryptedSK);
System.out.println("=====================================");
int[] var41 = new int[1];
byte[] var27 = new byte[256];
int[] var34 = new int[1];
byte[] var23 = new byte[256];
int[] var24 = new int[1];
byte[] var25 = new byte[256];
byte[] encryptedKB_tmp = new byte[encryptedSK_tmp.length];
logon.generateOAuthResponse(verifierType, salt, user, password, password,
password.getBytes(), password.getBytes(), encryptedSK.getBytes(), encryptedKB_tmp,
var23, var27, var34,
var41, isServerCSMultiByte, serverCompileTimeCapabilities,
AUTH_PBKDF2_CSK_SALT, vgenCount, sder_count, var25, var24);
encryptedKB = new String(encryptedKB_tmp);
System.out.println("AUTH_SESSKEY:" + encryptedKB);
auth_pbkdf2_speedy_key = new byte[var24[0]];
System.arraycopy(var25, 0, auth_pbkdf2_speedy_key, 0, auth_pbkdf2_speedy_key.length);
byte[] auth_password = new byte[var27[0]];
System.arraycopy(var23, 0, auth_password, 0, auth_password.length);
authPassword = new String(auth_password);
System.out.println("AUTH_PASSWORD:" + authPassword);
System.out.println("AUTH_PBKDF2_SPEEDY_KEY:" + new String(auth_pbkdf2_speedy_key));
boolean check = logon.auth(user, password, authPassword, encryptedSK, encryptedKB,
verifierType, salt, auth_pbkdf2_speedy_key, AUTH_PBKDF2_CSK_SALT,
vgenCount, sder_count, serverCompileTimeCapabilities, isServerCSMultiByte);
byte[] tmp = logon.getEncryptedResponse(user, password, authPassword, encryptedSK,
encryptedKB, verifierType, salt, AUTH_PBKDF2_CSK_SALT,
vgenCount, sder_count, serverCompileTimeCapabilities,isServerCSMultiByte);
AUTH_SVR_RESPONSE = new String(tmp);
System.out.println("auth:" + check);
System.out.println("AUTH_SVR_RESPONSE:" + AUTH_SVR_RESPONSE);
boolean validate = logon.validateServerIdentity(AUTH_SVR_RESPONSE);
System.out.println("validate:" + validate);
}
执行结果:
loginName:sys1111
password:tiger
=====================================
verifierType:18453
AUTH_VFR_DATA:475DD077C5BEF46CC44E1C9961EAD417
AUTH_PBKDF2_CSK_SALT:C33538DE162A29738CE7D5B663E14078
AUTH_PBKDF2_VGEN_COUNT:4096
AUTH_PBKDF2_SDER_COUNT:3
AUTH_SESSKEY:FC5EE3AE50760C84EBDDD2A1A98E97AAA5F0383CCEB4EAC734E05FF985D6420B
=====================================
AUTH_SESSKEY:BB4E34F91E5147055874B6EE3A96DF222ACFB6D684EA496A8639F425F0DC6657
AUTH_PASSWORD:7E7A73E7DC0E31CB37B25C712071277EBF5168EFBE80E9CD01790
AUTH_PBKDF2_SPEEDY_KEY:AD8A9A358C02BCF5EA690A71E137C75067D2847A2A17EABA45E13F66F59D8D57A957EE0490681E6D56EB09EE414E5B75C277168ADCE9876ED3B1B6EE2BF1A317DFF7EC8FE7ACA213C372CC094AC42C5D
auth:true
AUTH_SVR_RESPONSE:D58A5FC6B859AD185EBBF7138600276C91BA8C3A259CE1054D4AA67317E7CC09364E9EF36BD6211F10F9273F3821C4C7
validate:true
替换通信中的用户名和密码实现安全加固
服务器基本信息
以下使用测试oracle服务器版本11g为例:
1、服务器地址:192.168.150.130
2、用户名和密码分别为scott和j1d1sec.f0rt
3、安全加固所使用的用户名和密码分别为admin和admin123
安全加固服务基本信息
编程语言:Java
服务器框架:Netty 4.1.25
端口:1521
登录及转发指令格式:sqlplus admin/admin123@localhost:1521/scott@192.168.150.130@1521@orcl
安全加固关键部分代码如下:
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
int len = msg.readableBytes();
byte[] buff = new byte[len];
msg.readBytes(buff);
if(!isOracle12c) {
isOracle12c = TransferUtil.isOracle12c(buff);
}
logger.debug("0 " + new String(buff));
logger.debug("0 " + StringUtil.byteToHexString(buff));
if(TransferUtil.isConn(buff,isOracle12c)) {
OracleServer server = oracleHostFormatService.format(buff);
byte[] body_buff = server.getBody().getBytes();
int index = TransferUtil.findKey(AppConfig.CONNECTION_KEY, buff);
int conn_buff_len = index + body_buff.length;
byte[] conn_buff = new byte[conn_buff_len];
System.arraycopy(buff, 0, conn_buff, 0, index);
System.arraycopy(body_buff, 0, conn_buff, index, body_buff.length);
conn_buff[0x19] = (byte)((conn_buff[0x19] & 0xFF) + (conn_buff_len - buff.length));
TransferUtil.toHH(conn_buff_len, conn_buff,isOracle12c);
if(this.server == null) {
this.server = server;
this.client = new NettyClient(this.server.getOracleHost(), this.server.getOraclePort());
this.client.setOracleServerHandler(this);
this.client.connection();
this.client.waitConnect();
if(this.client.isClosed()) {
throw new RuntimeException("Connection error.");
}
this.client.writeAndFlush(conn_buff);
}else if(this.server.toString().equals(server.toString())){
this.client.writeAndFlush(conn_buff);
}else {
ctx.close();
}
}else if(TransferUtil.isAuthRecv(buff,isOracle12c)){
//将目标登录账号替换为目标设备账号
loginName = TransferUtil.getUserName(AppConfig.AUTH_TERMINAL,buff);
this.loginPassword = "admin123";
logger.debug("Login user : " + loginName);
byte[] login_name_buff = loginName.getBytes();
byte[] account_buff = server.getOracleAccount().getBytes();
byte[] tmp = TransferUtil.replaceAuthLoginName(login_name_buff,
account_buff, buff,isOracle12c);
logger.debug("8 " + new String(tmp));
logger.debug("8 " + StringUtil.byteToHexString(tmp));
buff = tmp;
this.client.writeAndFlush(buff);
}else if(TransferUtil.isLogin(buff,isOracle12c)){
//将目标登录账号替换为目标设备账号
loginName = TransferUtil.getUserName(AppConfig.AUTH_SESSKEY,buff);
AUTH_SESSKEY = TransferUtil.findKeyText(AppConfig.AUTH_SESSKEY.getBytes(), buff);
AUTH_PASSWORD = TransferUtil.findKeyText(AppConfig.AUTH_PASSWORD.getBytes(), buff);
AUTH_PBKDF2_SPEEDY_KEY = TransferUtil.findKeyText(AppConfig.AUTH_PBKDF2_SPEEDY_KEY.getBytes(), buff);
logger.debug("AUTH_SESSKEY:" + new String(AUTH_SESSKEY));
logger.debug("AUTH_PASSWORD:" + new String(AUTH_PASSWORD));
boolean useDes = false;
if(verifierType == 18453) {
useDes = true;
}
BugByCodeO5Logon logon = new BugByCodeO5Logon(useDes);
this.isAuth = logon.auth(loginName, loginPassword, new String(AUTH_PASSWORD), new String(custom_encryptedSK),
new String(AUTH_SESSKEY), verifierType, AUTH_VFR_DATA, AUTH_PBKDF2_SPEEDY_KEY,
AUTH_PBKDF2_CSK_SALT, AUTH_PBKDF2_VGEN_COUNT, AUTH_PBKDF2_SDER_COUNT, (byte)0, false);
logger.debug("Login auth : " + this.isAuth);
if(!this.isAuth) {
throw new RuntimeException("Username or password error.");
}
this.AUTH_SVR_RESPONSE = logon.getEncryptedResponse(loginName, loginPassword,
new String(AUTH_PASSWORD), new String(custom_encryptedSK), new String(AUTH_SESSKEY), verifierType,
AUTH_VFR_DATA, AUTH_PBKDF2_CSK_SALT, AUTH_PBKDF2_VGEN_COUNT, AUTH_PBKDF2_SDER_COUNT, (byte)0,false);
//10G 11G
/*byte[] var8 = new byte[256];
int[] var9 = new int[1];
byte[] var15 = new byte[256];
int[] var16 = new int[1];
*/
//10g 11g 12C
int[] var41 = new int[1];
byte[] var27 = new byte[256];
int[] var34 = new int[1];
byte[] var23 = new byte[256];
int[] var24 = new int[1];
byte[] var25 = new byte[256];
byte[] encryptedKB = new byte[encryptedSK.length];
String oraclePassword = "j1d1sec.f0rt";
/*
logon.generateOAuthResponse(verifierType, AUTH_VFR_DATA, server.getOracleAccount(), oraclePassword,
oraclePassword.getBytes(), encryptedSK, encryptedKB, var8, var9,
false, (byte)0, AUTH_PBKDF2_CSK_SALT,
AUTH_PBKDF2_VGEN_COUNT, AUTH_PBKDF2_SDER_COUNT, var15, var16);
byte[] tmp = new byte[var9[0]];
System.arraycopy(var8, 0, tmp, 0, tmp.length);
var8 = tmp;
*/
logon.generateOAuthResponse(verifierType, AUTH_VFR_DATA, server.getOracleAccount(), oraclePassword, oraclePassword,
oraclePassword.getBytes(), oraclePassword.getBytes(), encryptedSK, encryptedKB,
var23, var27, var34,
var41, false, (byte)0,
AUTH_PBKDF2_CSK_SALT, AUTH_PBKDF2_VGEN_COUNT, AUTH_PBKDF2_SDER_COUNT, var25, var24);
byte[] tmp = new byte[var34[0]];
System.arraycopy(var23, 0, tmp, 0, tmp.length);
byte[] var8 = tmp;
tmp = new byte[var41[0]];
System.arraycopy(var27, 0, tmp, 0, tmp.length);
byte[] new_pwd = tmp;
byte[] auth_pbkdf2_speedy_key = new byte[var24[0]];
System.arraycopy(var25, 0, auth_pbkdf2_speedy_key, 0, auth_pbkdf2_speedy_key.length);
logger.debug("NEW_AUTH_PASSWOR : " + new String(var8));
logger.debug("NEW_AUTH_NEWPASSWOR : " + new String(new_pwd));
logger.debug("NEW_AUTH_SESSKEY : " + new String(encryptedKB));
logger.debug("NEW_AUTH_PBKDF2_SPEEDY_KEY : " + new String(auth_pbkdf2_speedy_key));
int index = TransferUtil.findKey(AUTH_SESSKEY, buff);
System.arraycopy(encryptedKB, 0, buff, index, encryptedKB.length);
index = TransferUtil.findKey(AUTH_PASSWORD, buff);
System.arraycopy(var8, 0, buff, index, var8.length);
if(AUTH_PBKDF2_SPEEDY_KEY.length > 0) {
index = TransferUtil.findKey(AUTH_PBKDF2_SPEEDY_KEY, buff);
System.arraycopy(auth_pbkdf2_speedy_key, 0, buff, index, auth_pbkdf2_speedy_key.length);
}
logger.debug("Login user : " + loginName);
byte[] login_name_buff = loginName.getBytes();
byte[] account_buff = server.getOracleAccount().getBytes();
tmp = TransferUtil.replaceAuthLoginName(login_name_buff, account_buff, buff,isOracle12c);
buff = tmp;
this.client.writeAndFlush(buff);
}else {
this.client.writeAndFlush(buff);
}
}
private class WorkThread extends Thread{
@Override
public void run() {
while(!isClosed) {
try {
byte[] data = read();
logger.debug("1 " + new String(data));
logger.debug("1 " + StringUtil.byteToHexString(data));
if(TransferUtil.contailsEncryptedSK(data,isOracle12c)) {
AUTH_VFR_DATA = TransferUtil.findSalt(data);
encryptedSK = TransferUtil.findKeyText(AppConfig.AUTH_SESSKEY.getBytes(), data);
verifierType = TransferUtil.getVerifierType(data);
int offset = TransferUtil.findKey(AppConfig.AUTH_PBKDF2_CSK_SALT.getBytes(), data);
if(offset != -1) {
AUTH_PBKDF2_CSK_SALT = TransferUtil.findKeyText(AppConfig.AUTH_PBKDF2_CSK_SALT.getBytes(), data);
}
offset = TransferUtil.findKey(AppConfig.AUTH_PBKDF2_VGEN_COUNT.getBytes(), data);
if(offset != -1) {
byte[] vgenCountByte = TransferUtil.findKeyText(AppConfig.AUTH_PBKDF2_VGEN_COUNT.getBytes(), data);
AUTH_PBKDF2_VGEN_COUNT = Integer.valueOf(new String(vgenCountByte));
}
offset = TransferUtil.findKey(AppConfig.AUTH_PBKDF2_SDER_COUNT.getBytes(), data);
if(offset != -1) {
byte[] sderCountByte = TransferUtil.findKeyText(AppConfig.AUTH_PBKDF2_SDER_COUNT.getBytes(), data);
AUTH_PBKDF2_SDER_COUNT = Integer.valueOf(new String(sderCountByte));
}
logger.debug("AUTH_VFR_DATA:" + new String(AUTH_VFR_DATA));
logger.debug("encryptedSK:" + new String(encryptedSK));
logger.debug("verifierType:" + verifierType);
if(AUTH_PBKDF2_CSK_SALT != null) {
logger.debug("AUTH_PBKDF2_CSK_SALT:" + new String(AUTH_PBKDF2_CSK_SALT));
}
logger.debug("AUTH_PBKDF2_VGEN_COUNT:" + AUTH_PBKDF2_VGEN_COUNT);
logger.debug("AUTH_PBKDF2_SDER_COUNT:" + AUTH_PBKDF2_SDER_COUNT);
boolean useDes = false;
if(verifierType == 18453) {
useDes = true;
}
BugByCodeO5Logon logon = new BugByCodeO5Logon(useDes);
try {
custom_encryptedSK = logon.getEncryptedSK(verifierType, AUTH_VFR_DATA, loginName, loginPassword, AUTH_PBKDF2_VGEN_COUNT, (byte)0, false);
if(custom_encryptedSK.length != encryptedSK.length) {
throw new InterruptedException("EncryptedSK error.");
}
int index = TransferUtil.findKey(encryptedSK, data);
System.arraycopy(custom_encryptedSK, 0, data, index, custom_encryptedSK.length);
} catch (Exception e) {
e.printStackTrace();
throw new InterruptedException(e.getMessage());
}
}else if(TransferUtil.contailsResponse(data,isOracle12c)) {
byte[] old_response = TransferUtil.findKeyText(AppConfig.AUTH_SVR_RESPONSE.getBytes(), data);
logger.debug("old_response : " + new String(old_response));
int index = TransferUtil.findKey(old_response, data);
System.arraycopy(AUTH_SVR_RESPONSE, 0, data, index, AUTH_SVR_RESPONSE.length);
}
ByteBuf buf = oracleServerChannel.alloc().buffer(data.length);
buf.writeBytes(data);
writeAndFlush(buf);
} catch (InterruptedException e) {
logger.error(e.getLocalizedMessage());
}
}
}
}
加固后效果
使用sqlplus命令行登录如下所示:
使用 PLSQL Developer 12 (64 bit)工具登录效果如下所示: