[HFCTF2020]虎符部分web

昨天看了一下题,大概有些简单的思路,BUU上了复现环境,今天及时来复现一下,顺便学一些新的知识

[HFCTF2020]EasyLogin

打开靶机,一个登录界面

查看页面源代码,看到 app.js 中有以下注释

猜测有任意文件读取,经过队内师傅提醒,有一个 controllers/api.js 尤其重要

读一下代码逻辑

我们发现传入 username 和 password,先判断 username 不为 admin,然后生成一个 秘钥 ,生成一个 jwt 令牌

登录页面中进行JWT的验证,如果通过就登录

flag中,如果登录的用户名是admin,就输出flag

所以我们的思路是—构造用户名为admin拿到flag

JSNOTE也是一门弱语言,根据之前PHP和JWT的经验

如果我们设置secretid为数组,加密算法为空

const jwt=require('jsonwebtoken');
const secretid=[];
const username='admin';
const password='123456';
console.log(jwt.sign({secretid,username,password}, null, {algorithm: 'none'});

能不能绕过呢

再试亿次

尝试过以后发现我们需要先随意注册一个用户,在登录的时候将username和password设置成jwt中的数据,更改Authorization为我们伪造的jwt,forward到本地,即可登录

然后访问一下flag地址即可获得flag


[HFCTF2020]JustEscape

打开靶机

随意fuzz一下(fuzz一天),发现是一个notejs的代码

传入var err = new Error();err.stack;出现错误信息

Error
at vm.js:1:11
at Script.runInContext (vm.js:131:20)
at VM.run (/app/node_modules/vm2/lib/main.js:219:62)
at /app/server.js:51:33
at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
at next (/app/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/app/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
at /app/node_modules/express/lib/router/index.js:281:22
at Function.process_params (/app/node_modules/express/lib/router/index.js:335:12)

发现一个VM2,需要沙盒逃逸

到github找一下VM2逃逸的payload

https://github.com/patriksimek/vm2/issues/225

代码复制过来

过滤了部分字符。用数组就可以绕过

发现还过滤了return

执行命令就行了

在guoke师傅的WP中还写道

js中. 可以用[]代替. 
TypeError.prototype==TypeError[`\xxx\xxx\xxx\xxx`] 
``反引号代替双引号

所以在VM2的那个github中

这个也可以代替使用,

在赵师傅的博客中使用了占位符来拼接字符串

比如这里 prototype 被过滤了,我们可以这样书写
`${`${`prototyp`}e`}`

虎符 CTF Web 部分 Writeup


[HFCTF2020]BabyUpload

这题是最难的了,当时是完全不会,赛后看着WP复现下

咕咕咕来了

给了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?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[‘usernmae’]===’admin’ 并且检测var/babyctf/success.txt是否存在

如果存在删除该文件并直接输出flag

然后的代码我就看不懂了

$direction 参数决定是download还是upload
可以发现,attr会直接拼接在babyctf/的后面,如果attr===’orivate’则会直接把用户名拼接
然后来看一下这个upload功能
我们审计后可以发现,文件名拼接在后面,加上 _ 和这个文件内容的 sha256 摘要值,文件是我们上传的,文件内容的sha256可以在本地算出。然后判断是否有路径穿越,逐级创建目录,将文件储存到下面
再看看下载操作
读取文件名,返回文件内容。
所以我们现在可以知道想要读取文件需要伪造session为admin,并且在babyctf目录下创建一个success.txt,即可返回flag
首先我们来读取一下当前session是什么内容吧
发现返回了username:guest
根据之前的题意,就构造一下吧
然后算一下sha256
php -r “echo hash_file(‘sha256’, ‘1.php’);”
得到:
432b8b09e30c4a75986b719d1312b63a69f1b833ab602c9ad5f0299d1d76a5a4
然后将这个改名为 sess,上传
尝试访问一下
发现返回了admin
那么第一步就完成了
然后构造success.txt
用 file_exists 函数判断文件是否存在的。
官方手册说到这个函数是判断文件或者目录是否存在,文件名不可控但目录可控呀
我们将其改为 success.txt,即可创建一个 success.txt 目录。
然后再次刷新页面就存在flag啦

发表评论

邮箱地址不会被公开。 必填项已用*标注