The Oracle Network Architecture
Overview
The Oracle network architecture encompasses many components - all of which neatly corresponds to the OSI networking model (see Figure 2-1). This architecture enables Oracle client and server applications to transparently communicate over protocols such as TCP/IP. The session protocol that interfaces between the applications (Oracle Call Interface, or OCI, on the client and Oracle Program Interface, or OPI, on the server) and the network layer is known as Net8 (Net9), and before that SQL*Net. Between the OCI/OPI and Net8 layer is a presentation protocol called Two-Task Common (TTC) that is responsible for character set and data type conversion differences between the client and the server. The Net8 session protocol has three components - the Net Foundation and Routing/Naming/Auth and TNS - the last two making up Protocol Support. Supported transport protocols include TCP/IP, with or without TCP, Named Pipes and Sockets Direct Protocol (SDP), which enables communication over Infiband high-speed networks. Underpinning all of this is the Transparent Network Substrate protocol, also known as TNS. The task of TNS is to select the Oracle Protocol Adapter, wrapping the communication in one of the supported transport protocols.
Figure 2-1: The OSI networking model
The TNS Protocol
When developing raw tools to troubleshoot problems in Oracle, it is necessary to understand the TNS protocol. This section details information about the TNS protocol. The Oracle JDBC client (classes12.zip) is a useful resource when seeking to understand the TNS protocol.
The TNS Header
Every TNS packet has an eight-byte header. The first two bytes (WORD) of the header are used for the packet length - inclusive of the header size. The size, like all values, is big-endian. The next WORD is for the packet checksum if checksumming is done - by default it is not, and the value for this WORD is 0x0000. The next byte is used to indicate the packet type - for example, the most common are as follows:
Connect packet Type 1 Accept packet Type 2 Ack packet Type 3 Refuse packet Type 4 Redirect packet Type 5 Data packet Type 6 NULL packet Type 7 Abort packet Type 9 Resend packet Type 11 Marker packet Type 12 Attention packet Type 13 Control packet Type 14
When connecting to Oracle, at the TNS level the client sends the server a Connect packet (type 1) specifying the service name they wish to access. Provided the Listener knows of such a service, one of two things can happen: The Listener could send an Accept packet (type 2) or it could redirect the client to another port with a Redirect packet (type 5). If the former option occurs, then the client attempts to authenticate. This is covered in detail in Chapter 4, "Attacking the Authentication Process." If the latter occurs, then the client sends a Connect packet to the port to which they've been redirected and requests access to the service. If all goes well, the server issues an Accept packet and authentication takes place. All authentication packets are Data packets with a type of 6.
Going back, if the Listener does not know of the service to which the client is requesting access, then it issues a Refuse packet - type 4. Once authenticated, queries and results packets are Data packets. Every so often you'll see a packet of type 12 (0x0C) - this is a Marker packet, which is used for interrupting. For example, if the server wishes the client to stop sending data, then it will send the client a Marker packet.
Continuing with the details of the TNS header, the next byte is the header flags. Generally the flags are unused, but the 10g client may set the value to 0x04.
The final two bytes form a WORD for the header checksum - not used by default and set to 0x0000:
WORD 00 00 Packet Size WORD 00 00 Packet Checksum BYTE 00 Packet Type BYTE 00 Flags WORD 00 00 Header Checksum
Before delving further into the packet, it would be useful to take a look at Refuse packets - type 4. Refuse packets indicate an error of some kind - for example, a logon denied error with an "invalid username/password" - ORA-01017. With these errors, the 54th byte indicates the problem. A 3 is an invalid password; a 2 indicates no such user. Clearly, you can derive potentially useful information even from Refuse packets.
Inside the Packet
Most packets you'll see on the wire are Data packets (type 6). With Data packets, the WORD after the TNS header is for the Data Flags. If the packet is a disconnect packet, then this WORD is set to 0x0040 - otherwise, it is generally 0x0000.
Note |
There is a bug in all versions of Oracle when a server processes a Data packet (type 6) that has the second bit of the Data Flags set but the first (least significant) bit unset (e.g., numbers 2, 6, 10, 14, and so on). When the server receives such a packet it winds up in an endless loop, hogging all available CPU processing time. Obviously, this negatively impacts server performance. |
The next byte after the Data Flags (byte 11) determines what's in the Data packet:
- 0x01 indicates protocol negotiation. Here, the client sends to the server acceptable protocol versions - these are 6, 5, 4, 3, 2, 1, and 0. The server will reply with a common version - for example, 6 or 5 - but it will also send over information such as the character set it uses, how many characters are in the set, a version string, and server flags.
- 0x02 indicates an exchange of data type representations.
- 0x03 indicates a Two-Task Interface (TTI) function call. The following table lists some of the functions:
0x02 Open 0x03 Query 0x04 Execute 0x05 Fetch 0x08 Close 0x09 Disconnect/logoff 0x0C AutoCommit ON 0x0D AutoCommit OFF 0x0E Commit 0x0F Rollback 0x14 Cancel 0x2B Describe 0x30 Startup 0x31 Shutdown 0x3B Version 0x43 K2 Transactions 0x47 Query 0x4A OSQL7 0x5C OKOD 0x5E Query 0x60 LOB Operations 0x62 ODNY 0x67 Transaction - end 0x68 Transaction - begin 0x69 OCCA 0x6D Startup 0x51 Logon (present password) 0x52 Logon (present username) 0x73 Logon (present password - send AUTH_PASSWORD) 0x76 Logon (present username - request AUTH_SESSKEY) 0x77 Describe 0x7F OOTCM 0x8B OKPFC
Some of these may be called prior to authentication - for example, the Version (0x3B) TTI function:
- 0x08 indicates "OK" - sent from the server in response to a client.
- 0x11 indicates extended TTI functions. These were introduced in later versions of Oracle, so for backward compatibility do not use 0x03.
- Here are some function codes:
0x6b Switch or Detach session 0x78 Close 0x87 OSCID 0x9A OKEYVAL
- 0x20 is used when calling external procedures and with service registrations.
- 0x44 is also used when calling external procedures and with service registrations.
The best way to get a handle on the TNS protocol, other than examining the Oracle JDBC client (classes12.zip), is to grab some packets off the wire with a network sniffer and see what's going on.
Throughout the remainder of this book you'll see a number of packet dumps; refer back to this chapter when examining the contents.
Getting the Oracle Version
There are many ways of getting the Oracle version number back prior to authentication - so many in fact that people who worry about attackers being able to get the version number should stop worrying and just accept it. Of course, you can use TCP node valid checking and set firewall rules to disallow traffic, but if an attacker is coming from an "allowed" source, i.e., node, then you'll have to put up with people being able to get your version number; doing so is built into the protocol itself. Let's explore a few of the ways to get the version number - some methods are widely known, others less so.
The Listener Version and Status Command
The Listener Control Utility has both a version command and a status command. They both can be issued from the client to request the Listener's version number. Details about the operating system on which the Listener runs are also revealed. Note that while Oracle prevents the status command from working on 10g remotely, version still works. Here's the output from a version command:
C:>lsnrctl LSNRCTL for 32-bit Windows: Version 8.1.7.4.0 - Production on 19-JUN-2006 17:54:42 (c) Copyright 1998 Oracle Corporation. All rights reserved. Welcome to LSNRCTL, type "help" for information. LSNRCTL> set current_listener 192.168.0.120 Current Listener is 192.168.0.120 LSNRCTL> version Connecting to (DESCRIPTION=(CONNECT_DATA=(SID=*)(SERVICE_NAME=192.168.0.120))(ADDRESS= (PROTOCOL=TCP)(HOST=192.168.0.120)(PORT=1521))) TNSLSNR for 32-bit Windows: Version 10.2.0.1.0 - Production TNS for 32-bit Windows: Version 10.2.0.1.0 - Production Windows NT Named Pipes NT Protocol Adapter for 32-bit Windows: Version 10.2.0.1.0 - Production Windows NT TCP/IP NT Protocol Adapter for 32-bit Windows: Version 10.2.0.1.0 - Production,, The command completed successfully LSNRCTL>
From the preceding output, you can see that the server is running 10g Release 2 on Windows.
Using the TNS Protocol Version
In a TNS connect packet, the WORD (which is two bytes in size) found nine bytes into the packet specifies the TNS protocol version being used. The next WORD, bytes 11 and 12, specifies the earliest version number the sending system understands. For example, if an Oracle client running version 8.1.7.4 connects to the Listener on an Oracle server, then the client sends 0x0136 as the TNS protocol version being used and 0x012C as the earliest version it understands. This way, two different versions of Oracle can communicate by choosing a TNS version they both understand. Clearly, because later versions of Oracle use improved versions of the TNS protocol, we can use these two WORDs to determine the server version. We do this by specifying the earliest version number as the same version as the current version being used - so both WORDs in the connect packet have the same value. Furthermore, we start with a high version number, one known not to exist yet, and work down. Thus, we send 0x13C for both the current and earliest versions in our first connect packet. The server should not understand this version so it will either time-out or produce an error. Next, we reduce this to 0x13B, then to 0x13A, then to 0x139, and so on all the way down to 0x00CC. As soon as the server starts responding as expected, we know the latest version of the TNS protocol it speaks. From this we can deduce what the Oracle greater version is, e.g., 10, 9, or 8, etc.
Oracle 10r2 supports 0x139 Oracle 9r2 supports 0x138 Oracle 9i supports 0x137 Oracle 8 supports 0x136.
For more information, see tnsver.c at the end of this chapter.
Using the XML Database Version
If the XML database is running, one can telnet to TCP port 2100 to get the version information. This service is an ftp service and the banner reveals the information:
220 PILUM FTP Server (Oracle XML DB/Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production) ready. Also, the XDB Web server on TCP port 8080 gives up the version number: GET / HTTP/1.1 Host: PILUM HTTP/1.1 401 Unauthorized MS-Author-Via: DAV DAV: 1,2,<">http://www.oracle.com/xdb/webdav/props> Server: Oracle XML DB/Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production WWW-Authenticate: Basic Realm=" XDB" Date: Mon, 19 Jun 2006 18:57:59 GMT Content-Type: text/html Content-Length: 147
Using TNS Error Text
If the Listener receives a TNS command it doesn't understand, it sends an error back. This error text contains a VSNNUM that holds a decimal number such as 169869568. If we convert this number to hexadecimal, look what we get: 0x0A200100. This is the Oracle version number in disguise - in this case 10.2.0.1.0. The following packer dump is taken from a Listener that doesn't understand the ‘unbreakable' command:
IP Header Length and version: 0x45 Type of service: 0x00 Total length: 181 Identifier: 13914 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x41e5 Source IP: 192.168.0.120 Dest IP: 192.168.0.59 TCP Header Source port: 1521 Dest port: 3004 Sequence: 1152664576 ack: 2478634793 Header length: 0x50 Flags: 0x18 (ACK PSH) Window Size: 17451 Checksum: 0xcae1 Urgent Pointer: 0 Raw Data 00 8d 00 00 04 00 00 00 22 00 00 81 28 44 45 53 " (DES 43 52 49 50 54 49 4f 4e 3d 28 45 52 52 3d 31 31 CRIPTION=(ERR=11 35 33 29 28 56 53 4e 4e 55 4d 3d 31 36 39 38 36 53)(VSNNUM=16986 39 35 36 38 29 28 45 52 52 4f 52 5f 53 54 41 43 9568)(ERROR_STAC 4b 3d 28 45 52 52 4f 52 3d 28 43 4f 44 45 3d 31 K=(ERROR=(CODE=1 31 35 33 29 28 45 4d 46 49 3d 34 29 28 41 52 47 153)(EMFI=4)(ARG 53 3d 27 75 6e 62 72 65 61 6b 61 62 6c 65 27 29 S='unbreakable') 29 28 45 52 52 4f 52 3d 28 43 4f 44 45 3d 33 30 )(ERROR=(CODE=30 33 29 28 45 4d 46 49 3d 31 29 29 29 29 3)(EMFI=1))))
See tnsver.c at the end of this chapter.
Using the TNS Version TTC Function
We discussed TTC functions earlier and mentioned the version function 0x3B. This will cause an Oracle server to reveal its version prior to authentication.
Using Additional Network Option Negotiation
Once an Accept packet has been received by the client from the server, the client may choose to negotiate additional network services such as Authentication, Encryption, Data Integrity, and Supervisor. The version of the client or server can be found three bytes after the ANO negotiation header (0xDEADBEEF) - 17 bytes into the packet. In the following capture, you can see the version is 8.1.7.4 - highlighted in bold:
IP Header Length and version: 0x45 Type of service: 0x00 Total length: 203 Identifier: 14473 Flags: 0x4000 TTL: 128 Protocol: 6 (TCP) Checksum: 0x3fa0 Source IP: 192.168.0.59 Dest IP: 192.168.0.120 TCP Header Source port: 4194 Dest port: 1495 Sequence: 422372252 ack: 597087647 Header length: 0x50 Flags: 0x18 (ACK PSH) Window Size: 65087 Checksum: 0x7e36 Urgent Pointer: 0 Raw Data 00 a3 00 00 06 00 00 00 00 00 de ad be ef 00 99 08 10 74 00 00 04 00 00 04 00 03 00 00 00 00 00 t 04 00 05 08 10 74 00 00 02 00 06 00 1f 00 0e 00 t 01 de ad be ef 00 03 00 00 00 02 00 04 00 01 00 01 00 07 00 00 00 00 00 04 00 05 08 10 74 00 00 t 02 00 06 fa ff 00 01 00 02 01 00 03 00 00 4e 54 NT 53 00 04 00 05 02 00 00 00 00 04 00 04 00 00 00 S 00 00 04 00 04 00 00 00 02 00 02 00 02 00 00 00 00 00 04 00 05 08 10 74 00 00 01 00 02 00 00 03 t 00 02 00 00 00 00 00 04 00 05 08 10 74 00 00 01 t 00 02 00
Wrapping Up
This chapter described the TNS protocol and some of the ways in which it is possible to get the version number using features in the protocol. You can see how this occurs from tnsver.c:
/************************************ / Compile from a command line / / C:>cl /TC tnsver.c /link wsock32.lib / */ #include #include #include struct hostent *he; struct sockaddr_in s_sa; int ListenerPort=1521; char host[260]=""; int GetOracleVersion(unsigned char *pkt, unsigned int pkt_len, unsigned char *resp, unsigned int resp_len, int dr); int StartWinsock(void); int InitTNSPacket(unsigned char *data, unsigned short data_length); int bswap_s(unsigned int v); int bswap_i(unsigned int v); int error(); int GetOracleVersionByError(); int GetOracleVersionByProtocolVersion(); int GetOracleVersionByVersionCommand(); typedef struct TNSHeader { unsigned short Length; unsigned short PacketChecksum; unsigned char Type; unsigned char Flags; unsigned short HeaderChecksum; }TNSHeader; typedef struct TNSConnect { unsigned short Version; unsigned short MinVersion; unsigned short GlobalServiceOptions; unsigned short SessionDataUnit; unsigned short TransportDataUnit; unsigned short Characteristics; unsigned short MaxPacketsBeforeAck; unsigned short ByteOrder; unsigned short Length; unsigned short Offset; unsigned int MaxRecv; unsigned short AdditionalNetworkOptions; unsigned char buf[24]; } TNSConnect; #define TNS_CONNECT 1 #define MAX_VER 0x0139 #define MIN_VER 0x012C #define SDU_MAX 0x0800 #define TDU_MAX 0x7FFF #define DATA_LENGTH 12 unsigned char TNSPacket[2000]=""; int InitTNSPacket(unsigned char *data, unsigned short data_length) { TNSConnect tnsconnect; TNSHeader tnsheader; memset(&tnsheader,0,sizeof(TNSHeader)); memset(&tnsconnect,0,sizeof(TNSConnect)); tnsheader.Length = bswap_s(data_length + 0x3A); tnsheader.PacketChecksum = 0; tnsheader.Type = TNS_CONNECT; tnsheader.Flags = 0; tnsheader.HeaderChecksum = 0; tnsconnect.Version = bswap_s(MAX_VER); tnsconnect.MinVersion = bswap_s(MIN_VER); tnsconnect.GlobalServiceOptions = 0; tnsconnect.SessionDataUnit = bswap_s(SDU_MAX); tnsconnect.TransportDataUnit = bswap_s(TDU_MAX); tnsconnect.Characteristics = bswap_s(0x860E); tnsconnect.MaxPacketsBeforeAck = 0; tnsconnect.ByteOrder = 0x1; tnsconnect.Length = bswap_s(data_length); tnsconnect.Offset = bswap_s(0x3A); tnsconnect.MaxRecv = bswap_i(0x000007F8); tnsconnect.AdditionalNetworkOptions = 0x0C0C; memmove(TNSPacket,&tnsheader,sizeof(TNSHeader)); memmove(&TNSPacket[sizeof(TNSHeader)],&tnsconnect,50); memmove(&TNSPacket[0x3A],data,data_length); return 0; } int main(int argc, char *argv[]) { unsigned int err=0; unsigned short val = 0x13B; if(argc == 1) { printf(" *** OraVer ***"); printf(" Gets the Oracle version number."); printf(" C:\>%s host [port]",argv[0]); printf(" David Litchfield davidl@ngssoftware.com 22th April 2003 "); return 0; } strncpy(host,argv[1],256); if(argc == 3) ListenerPort = atoi(argv[2]); err = StartWinsock(); if(err==0) printf("Error starting Winsock. "); else { GetOracleVersionByError(); GetOracleVersionByProtocolVersion(); GetOracleVersionByVersionCommand(); } WSACleanup(); return 0; } int GetOracleVersionByProtocolVersion() { int res=0; unsigned char buff[2000]=""; unsigned char *ptr = NULL; unsigned short ver = 0x13B; InitTNSPacket("AAAABBBBCCCC",DATA_LENGTH); while(ver > 0xCC) { ver = (unsigned short)bswap_s(ver); memmove(&TNSPacket[8],&ver,2); memmove(&TNSPacket[10],&ver,2); ver = (unsigned short)bswap_s(ver); res = GetOracleVersion(TNSPacket,0x3A+DATA_LENGTH,buff,2000,0); if(res == -1) return printf("Failed to connect. "); if(res > 0x20) { printf("TNS version 0x%.2X is supported ",ver); break; } else printf("TNS version 0x%.2X is not supported ",ver); ver --; } return 0; } int GetOracleVersionByVersionCommand() { int res=0; unsigned char buff[2000]=""; unsigned char *ptr = NULL; unsigned char *vercmd = "(CONNECT_DATA=(COMMAND=version))"; InitTNSPacket(vercmd,(unsigned short)strlen(vercmd)); res = GetOracleVersion(TNSPacket,0x3A+strlen(vercmd),buff,2000,1); if(res == -1) return printf("Failed to connect. "); if(res > 0x36) { ptr = &buff[10]; printf(" Version command: %s ",ptr); } else error(); return 0; } int GetOracleVersionByError() { int res=0; unsigned char buff[2000]=""; unsigned char ver[8]=""; unsigned char *ptr = NULL; unsigned char h=0,l=0,p=0,q=0; InitTNSPacket("ABCDEFGHIJKL",DATA_LENGTH); res = GetOracleVersion(TNSPacket,0x3A+DATA_LENGTH,buff,2000,0); if(res == -1) return printf("Failed to connect to listener. "); if(res > 0x32) { ptr = &buff[36]; ptr[6]=0x00; if(strcmp(ptr," VSNNUM")==0) { ptr = &ptr[7]; res = atoi(ptr); res = res << 4; memmove(ver,&res,4); h = ver[3] >> 4; l = ver[3] << 4; l = l >> 4; p = ver[1] >> 4; q = ver[0] >> 4; printf(" Version of Oracle is %d.%d.%d.%d.%d ",h,l,ver[2],p,q); } else return error(); } else return error(); return 0; } int error() { return printf("There was an error getting the version number. "); } int bswap_s(unsigned int v) { __asm { xor eax, eax mov eax,v bswap eax shr eax,16 mov v, eax } return v; } int bswap_i(unsigned int v) { __asm { xor eax, eax mov eax,v bswap eax mov v, eax } return v; } int StartWinsock() { int err=0; unsigned int addr; WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD(2, 0); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) return 0; if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) return 0; s_sa.sin_addr.s_addr=INADDR_ANY; s_sa.sin_family=AF_INET; if (isalpha(host[0])) { he = gethostbyname(host); if(he == NULL) { printf("Failed to look up %s ",host); return 0; } memcpy(&s_sa.sin_addr,he->h_addr,he->h_length); } else { addr = inet_addr(host); memcpy(&s_sa.sin_addr,&addr,4); } return 1; } int GetOracleVersion(unsigned char *pkt, unsigned int pkt_len, unsigned char *resp, unsigned int resp_len, int dr) { unsigned char ver[8]=""; unsigned char h=0,l=0,p=0,q=0; int snd=0,rcv=0,count=0; unsigned int to=1000; SOCKET cli_sock; char *ptr = NULL; cli_sock=socket(AF_INET,SOCK_STREAM,0); if (cli_sock==INVALID_SOCKET) return printf(" Failed to create the socket. "); setsockopt(cli_sock,SOL_SOCKET,SO_RCVTIMEO,(char *)&to,sizeof(unsigned int)); s_sa.sin_port=htons((unsigned short)ListenerPort); if (connect(cli_sock,(LPSOCKADDR)&s_sa,sizeof(s_sa))==SOCKET_ERROR) { printf(" Failed to connect to the Listener. "); return -1; } snd=send(cli_sock, pkt , pkt_len , 0); rcv = recv(cli_sock,resp,resp_len,0); if(dr) rcv = recv(cli_sock,resp,resp_len,0); closesocket(cli_sock); if(rcv == SOCKET_ERROR) return 0; else return rcv; }
Later in the book, you'll learn how to get the version information down to the exact patch level.