TẤN CÔNG ROOTKIT TRONG ORACLE
1.1.11. Khai thác các PL/SQL Package
Package là một schema object nhóm các kiểu, biến và chương trình PL/SQL có liên quan. Package thường có hai phần, phần đặc tả (spec) và phần thân (body), đôi khi phần body không cần thiết. Phần đặc tả là phần giao tiếp của package. Nó khai báo các kiểu, các biến, hằng, ngoại lệ, con
trỏ và các chương trình con mà bên ngoài có thể tham chiếu tới. Phần body định nghĩa các truy vấn cho con trỏ và mã cho các chương trình con (các procedure, function…). Phần spec được coi là phần hộp trắng, phần body là hộp đen. Có thể debug, thay đổi phần body mà không ảnh hưởng tới phần spec.
Để tạo package spec, dùng lệnh: Create package. Lệnh Create package body định nghĩa phần body. Phần spec sẽ được public với tất cả các procedure và code khác bên ngoài package. Phần body chứa các thực thi và các khai báo riêng, không được hiển thị đối với bên ngoài.
Package DBMS_METADATA
Để thay đổi thông tin mà các view đưa ra, attacker cần phải tác động tới mã nguồn (mã tạo ra) các view, table. Trong các function, procedure được viết để thực hiện ý đồ của mình, attacker sẽ cần phải lấy mã nguồn của các view, table cần thay đổi, sau đó, thêm vào phần mã của mình và thực hiện tạo lại view, table. Attacker có thể sử dụng package dbms_metadata của Oracle để lấy được mã nguồn (DDL) này.
Thực tế, Oracle đưa ra package dbms_metadata nhằm làm đơn giản hóa một số công việc trong database mà cần phải trích (extract) thông tin về các table, index từ Oracle và chuyển chúng sang hệ thống khác. Việc trích DDL của các schema object từ dictionary sẽ rất hữu ích khi phải chuyển sang hệ thống mới và muốn có lại các object đó trong tablespace mới. Oracle cung cấp package dbms_metadata giúp các nhà phát triển dễ dàng lấy các metadata về các object từ data dictionary và đưa ra dưới dạng XMl hoặc DDL. Sau đó load XMl hoặc chạy DDL để tạo lại object. Dbms_metadata sử dụng một số object và table được định nghĩa trong schema SYS. Thường gặp nhất là ku$_parsed item và ku$_ddl.
Trong dbms_metadata những user thông thường chỉ có thể xem metadata các object mà họ sở hữu, các object họ được gán quyền, các object được public. SYS và user có quyền Select_catalog_role xem được metadata của tất cả các object trong database. Trong các procedure, function và các package định nghĩa quyền, các role như select_catalog_role không được kích hoạt. Vì thế, các chương trình PL/SQL chỉ có thể lấy metadata cho các object ở trong schema của chính nó. Package dbms_metadata định nghĩa trong schema SYS các object và table sau:
Table Các column
sys.ku$_parsed_item item, values, object_row sys.ku$_ddl ddlText, parsedItem sys.ku$_Errorline errorNumber, errorText sys.ku$_submitresult ddl, errorlines
Hinh 6: dbms_metadata Các chương trình con của dbms_metadata
Sử dụng các chương trình con trong dbms_metadata để lấy DDL của các object hoặc đăng ký dạng XML của các object với database.
Subprogram Nội dung
Add_transform function Xác định một transform mà fetch_xxx áp dụng đối với dạng thể hiện XML của object
Fetch_xxx Trả về metadata cho object thỏa mãn các tiêu chuẩn thiết lập bởi open, set_filter, set_count, add_transform.
Get_query Trả về dạng text của query mà fetch_xxx sử dụng Get_xxx Lấy về metadata cho một object cụ thể
Open Chỉ ra kiểu của object cần lấy, phiên bản metadata của nó
Set_count Thiết lập số object được lấy trong một lời gọi fetch_xxx
Set_parse_item xác định một thuộc tính object được phân tích Set_filter Đặt các tiêu chuẩn để tìm kiếm object, ví dụ tên
hay schema.
Put Gửi dạng XML cho database
Ví dụ để lấy metadata dạng XML của Scott.emp:
Set long 2000000 Set pagesize 0
Select dbms_metadata.get_xml(‘table’, ‘emp’, ‘scott’) From dual;
Package DBMS_OUTPUT
Dbms_output package cho phép gửi message từ pl/sql. Thường dùng nhất là kích hoạt package dbms_output và hiển thị nội dung của message buffer. Trong sql*plus điều này có thể thực hiện bằng lệnh: ‘Set serveroutput on’ hoặc ‘Setserverout on’. Dbms_output package thường được dùng trong
việc debug. Procedure put và put_line cho phép đặt thông tin vào buffer để bất kỳ trigger, procedure, package nào cũng có thể đọc được. Hiển thị thông tin ở vùng buffer bằng cách gọi procedure get_line và get_lines.
Package dbms_output được chạy bằng script dbmsotpt.sql bởi user Sys.
Package này được gán Execute cho tất cả user (public)
Subprogram Nội dung
Dbms_output.enable Enable các chương trình con Put, put_line, get_line, get_lines và new_line.
Dbms_output.new_line Thêm một end-of-line vào buffer Dbms_output.get_line(s) Lấy 1 hoặc nhiều dòng từ buffer Dbms_output.put Chuyển tham số được gọi vào buffer Dbms_output.put_line them một end-of-line vào buffer
Hinh 7: dbms_output
Package DBMS_JOB
Dbms_job cung cấp một job cho nhiều instance. Mặc định job có thể chạy trên bất kỳ instance nào, nhưng chỉ một instance sẽ chạy nó. Không yêu cầu phải có system privilege nào để có thể sử dụng dbms_job. Và cũng không có system privilege nào quản lý dbms_job. Các job không thể bị sửa đổi hay xóa ngoài job sở hữu bởi user đó. Một khi job được start và chạy sẽ không dễ dàng để ngừng job lại.
Subprogram Nội dung
Broken Chặn thực hiện job
Change Chỉnh sửa tham số người dung định nghĩa liên quan tới job
Instance Gán một job cho một instance chạy Next_date Lần thực hiện job tiếp theo
Remove Xóa một job khỏi hang đợi job Run Thực hiện chạy job
Submit Đưa một job vào hàng đợi job What Thay đổi đặc tả của một job
Hinh 8: dbms_jobs Package DBMS_SQL
Package dbms_sql cho phép truy nhập tới dynamic SQL và dynamic PL/SQL từ bên trong chương trình PL/SQL. Dynamic có nghĩa là các câu lệnh SQL trong package sử dụng những tham số chưa được tạo trong chương
trình. Chúng là các chuỗi ký tự được xây dựng tại thời điểm chạy và sau đó được đưa vào SQL engine để thực hiện. Package dbms_sql cho phép thực hiện những hành động là không thể trong chương trình PL/SQL, bao gồm:
- Execute câu lệnh ddl: câu lệnh ddl như drop table hay create index, là không được phép trong native PL/SQL.
- Tạo một ad-hoc query interface: với dbms_sql, không cần phải nhọc nhằn gõ các câu lệnh select cho một truy vấn hay con trỏ. Bạn có thể để cho user xác định các thứ tự sắp xếp, các điều kiện và bất kỳ trường hợp nào của câu lệnh select
- Execute dynamic các chương trình PL/SQL đã được tạo: Trong table, chúng ta lưu tên của procedure thực hiện một phép tính toán nào đó. Sau đó dựng một front- end cho table đó, để user lựa chọn chức năng phù hợp, đưa giá trị đầu vào, và thực thi thay vì phải thêm các hàng và tương tác với khá nhiều giao diện.
Với sức mạnh, sự ảnh hưởng và linh hoạt của dynamic SQL, thông thường chỉ nên gán quyền Execute cho những user mà cần thực hiện dynamic SQL. Để che giấu dbms_sql thực hiện lệnh bằng tài khoản sys:
Revoke execute on dbms_sql from public;
Để gán quyền execute cho user nào đó, thực hiện:
Grant execute on dbms_sql to User_name;
Khi thực hiện một chương trình dbms_sql từ một anonymous block, chương trình đó được thực hiện với quyền của schema hiện tại. Nếu nhúng chương trình dbms_sql vào trong một chương trình được lưu trữ, các chương trình dynamic SQL sẽ thực hiện sử dụng quyền của người sở hữu chương trình đó. Dbms_sql còn được gọi là package ‘run as user’ hơn là ‘run as owner’.
Name Miêu tả
Close(open)_cursor Đóng (mở) con trỏ
Column_value Lấy giá trị từ con trỏ vào biến cục bộ
Execute Thực thi con trỏ
Fetch_rows Nhập giá trị row từ con trỏ
Is_open Trả về giá trị true nếu con trỏ đang mở Last_row_count Trả về tổng số row được fetch từ con trỏ Last_SQL_function_code Trả về function code cho câu lệnh SQL
Define_column Xác định một column được chọn từ một con trỏ cụ thể
Parse Phân tích cú pháp câu lệnh SQL, nếu nó là DDL, thủ tục phân tích sẽ thực thi luôn câu lệnh
Variable_value Lấy giá trị một biến trong con trỏ Hinh 9: dbms_sql
Dbms_sql thực sự là một package mạnh, nhưng nó cũng là một package khá phức tạp để sử dụng. Thông thường, chúng ta có thể tạo và thực thi dựa vào các câu lệnh SQL. Nhưng việc tạo như vậy khá thủ công và vất vả, phải xác định tất cả các câu lệnh SQL, với các lời gọi thủ tục, các kiểu, biến….
Quá trình xử lý của Dynamic SQL
- Mở vùng nhớ: Trước khi thực hiện bất kỳ sql dynamic nào, phải có một con trỏ trỏ tới vùng nhớ mà sql dynamic sẽ được quản lý. Khi mở vùng nhớ, Rdbms sẽ thiết lập một vùng nhớ và một con trỏ tỏ tới vùng nhơ đó. Rdbms trả về một integer gán với con trỏ. Chúng ta sẽ sử dụng giá trị này trong các lời gọi sau đó tới các chương trình dbms_sql để thực hiện câu lệnh sql dynamic này. Có thể sử dụng một con trỏ để thực hiện nhiều câu lệnh SQL. Nội dung của vùng lưu dữ liệu sẽ được reset nếu một câu lệnh mới được phân tích vì thế không phải close và open lại con trỏ.
- Phân tích câu lệnh: Sau khi đã xác định con trỏ trỏ tới vùng nhớ, để liên kết nó với câu lệnh sql cần phải phân tích (parse) câu lệnh bằng lời gọi tới thủ tục parse. Nó xác định các câu lệnh này có đúng cấu trúc không, và liên kết câu lệnh với con trỏ. Lưu ý khi phân tích một câu lệnh DDL, nó cũng ngay tức khắc được thực thi và rdbms cũng thực hiện một lệnh commit ngầm định.
- Ràng buộc tất cả giá trị host: câu lệnh mà chúng ta thực hiện ở dạng chuỗi tại thời điểm chạy. Khi sử dụng dynamic sql, tại thời điểm compile chúng ta chưa biết giá trị, vì thế phải chuyển giá trị vào câu lệnh sql tại thời điểm chạy. Có 2 cách để thực hiện: móc nối(concatenation) và ràng buộc (binding). Với móc nối, chuyển tất các thành phần trong câu lệnh sql thành chuỗi và nối chúng lại. Với ràng buộc, chúng ta chèn nơi lưu trữ (placeholder) vào chuỗi với dấu ‘:’ đặt trước. Nếu câu lệnh sql chứa tham chiếu tới các biến PL/SQL, chúng ta phải chỉ ra nơi đặt các biến đó trong câu lệnh sql bằng cách đặt dấu ‘:’
trước tên. Sau đó gán giá trị thật cho biến đó trong câu truy vấn.
Nếu nối giá trị vào một chuỗi thì không cần phải gọi các thủ tục bind_varaiable hay bind_array. Ví dụ:
DBMS_SQL.PARSE
(cur, 'SELECT * FROM emp WHERE ename LIKE ' || v_ename);
Nếu gán giá trị cần phải goi các thủ tục bind_variable hoặc bind_array như sau:
DBMS_SQL.PARSE (the_cursor,
'SELECT COUNT(*) freq FROM call WHERE call_date = :call_date ' ||
'AND call_type_cd = :call_type', DBMS_SQL.V7);
DBMS_SQL.BIND_VARIABLE (the_cursor, 'call_date', :call.last_date_called);
DBMS_SQL.BIND_VARIABLE (the_cursor, 'call_type', :call.call_status);
Để tránh phải tạo các lời gọi riêng rẽ tới bind_variable, có thể đưa các giá trị này vào câu lệnh tại thời điểm phân tích. Ví dụ trên có thể viết thành:
DBMS_SQL.PARSE (the_cursor,
'SELECT COUNT(*) freq FROM call WHERE call_date = ''' ||
TO_CHAR (:call.last_date_called) ||
''' AND call_type_cd = ''' || :call.call_status || '''', DBMS_SQL.V7);
Các dấu ‘’’ được nhóm lại cùng nhau( 3 dấu ‘’’ ở cuối chuỗi sẽ trở thành một dấu ‘ ở cuối giá trị được đưa vào câu lệnh sql, móc nối hay kiểu giá trị.
- Định nghĩa các cột trong lệnh select: Mỗi cột trong danh sách select phải được định nghĩa. Nó xác định sự tương ứng giữa danh sách mà câu lệnh sql liệt kê với các biến PL/SQL nhận giá trị đó. Bước này chỉ dành cho câu lệnh select.
- Thực thi câu lệnh: thực thi tại vùng nhớ đã chỉ ra- tức là câu lệnh tại vùng nhớ đó. Nếu câu lệnh sql là insert, update hay delete, lệnh execute sẽ trả về số các hàng được xử lý. Còn lại, chúng ta có thể bỏ qua giá trị trả về.
- Lấy các hàng từ truy vấn sql dynamic: Nếu thực thi truy vấn, sau đó phải lấy các hàng từ vùng nhớ. Tuy nhiên, không lấy trực tiếp vào các biến PL/SQL cục bộ.
- Tìm kiếm các giá trị từ việc thực thi sql dynamic: Nếu câu lệnh SQL là một truy vấn, tìm kiếm các giá trị từ danh sách trả về của lệnh select bằng column_value.
- Đóng con trỏ: giải phóng vùng nhớ liên quan tới con trỏ.
Utl_tcp
Utl_tcp là package cơ bản nhất có thể truy xuất network. UTL_TCP có thể tạo các kết nối TCP tới server khác, gửi và nhận data. Hơn nữa, không có giới hạn trong định dạng của data này, tức là nó có thể ở dạng binary hoặc text-based. Nó cho phép rdbms giao tiếp với bất kỳ kiểu server nào trên network mà nó cần, web server hay RPC server. Đây là một package rất hữu ích cho attacker. Ví dụ, attacker có thể dựa vào utl_tcp để tạo một chương trình quét cổng TCP.
Các function cơ bản: