We participated SCTF last week, it was hosted by Syclover, if interested can try it out on https://sctf2021.xctf.org.cn/

I only managed to solve 4 challenges, here are some of the writeups

Challenges

Loginme

Description

http://124.71.166.197:18001/
Try to loginme!

Challenge file

Goto the website http://124.71.166.197:18001/:

image1

Click the link below, it say error 401 (means unauthorized)

image2

After I look at the source code, found that middleware.go is where it stop us to the link:

package middleware

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

func LocalRequired() gin.HandlerFunc {
	return func(c *gin.Context) {
		if c.GetHeader("x-forwarded-for") != "" || c.GetHeader("x-client-ip") != "" {
			c.AbortWithStatus(403)
			return
		}
		ip := c.ClientIP()
		if ip == "127.0.0.1" {
			c.Next()
		} else {
			c.AbortWithStatus(401)
		}
	}
}

Can see it only check for x-forwarded-for and x-client-ip header

By searching Bypass client IP, will lead to this StackOverflow link

It says we can use X-Forwarded-For, X-Real-IP, True-Client-IP to bypass the check for localhost (127.0.0.1) IP address

By using Burp Suite to add the X-Real-IP it works!

image3

By changing the id parameter, the server response changes

image4

After I investigate and reading the source code, found that it was using template to generate the website page:

package route

import (
	_ "embed"
	"fmt"
	"html/template"
	"loginme/structs"
	"loginme/templates"
	"strconv"

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

func Index(c *gin.Context) {
	c.HTML(200, "index.tmpl", gin.H{
		"title": "Try Loginme",
	})
}

func Login(c *gin.Context) {
	idString, flag := c.GetQuery("id")
	if !flag {
		idString = "1"
	}
	id, err := strconv.Atoi(idString)
	if err != nil {
		id = 1
	}
	TargetUser := structs.Admin
	for _, user := range structs.Users {
		if user.Id == id {
			TargetUser = user
		}
	}

	age := TargetUser.Age
	if age == "" {
		age, flag = c.GetQuery("age")
		if !flag {
			age = "forever 18 (Tell me the age)"
		}
	}

	if err != nil {
		c.AbortWithError(500, err)
	}

	html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age)
	if err != nil {
		c.AbortWithError(500, err)
	}

	tmpl, err := template.New("admin_index").Parse(html)
	if err != nil {
		c.AbortWithError(500, err)
	}

	tmpl.Execute(c.Writer, TargetUser)
}

And can see it follow the name and age in the structs.go file:

package structs

type UserInfo struct {
	Id       int
	Username string
	Age      string
	Password string
}

var Users = []UserInfo{
	{
		Id:       1,
		Username: "Grandpa Lu",
		Age:      "22",
		Password: "hack you!",
	},
	{
		Id:       2,
		Username: "Longlone",
		Age:      "??",
		Password: "i don't know",
	},
	{
		Id:       3,
		Username: "Teacher Ma",
		Age:      "20",
		Password: "guess",
	},
}

var Admin = UserInfo{
	Id:       0,
	Username: "Admin",
	Age:      "",
	Password: "flag{}",
}

As you can see, the Admin’s Password is the flag!

But how do we get it? The website only prints Id and Age

Solving

By searching Golang SSTI will lead to this link: https://www.onsecurity.io/blog/go-ssti-method-research/

It actually vulnerable to SSTI attack (Server Side Template Injection)

The source code also shows that it is vulnerable:

html := fmt.Sprintf(templates.AdminIndexTemplateHtml, age)
if err != nil {
	c.AbortWithError(500, err)
}

tmpl, err := template.New("admin_index").Parse(html)
if err != nil {
	c.AbortWithError(500, err)
}

More info about SSTI here

Follow the link above, putting GET parameter id=0 and age={{.Password}} will show the flag

image5

Easy flag!!

Flag

SCTF{E@zy_SIGn_Ch3eR!}

This_is_A_tree

Description

一颗圣诞树,还有好多礼物,flag需要SCTF{}噢 ,a beautiful tree,U need to know some Chinese traditional knowledge,flag need a “SCTF{your_flag}”

Hint

This_is_A_Tree New Hint :The text you see is very ancient Chinese knowledge. We call it "gua". The ancients combined it with destiny and prediction. And I combined it with base64, you can refer to https://zh.wikipedia.org/wiki/%E5%85%AD%E5%8D%81%E5%9B%9B%E5%8D%A6, Note that the "gua" is from bottom to top

Challenge file

Unzip it, saw many array files nested inside left and right folders:

Archive:  tree.zip
 extracting: array
 extracting: letf/array
 extracting: letf/letf/array
 extracting: letf/letf/letf/array
 extracting: letf/letf/letf/letf/array
 extracting: letf/letf/letf/letf/letf/array
 extracting: letf/letf/letf/letf/Right/array
 extracting: letf/letf/letf/Right/array
 extracting: letf/letf/letf/Right/letf/array
 extracting: letf/letf/letf/Right/Right/array
 ...
 ...
 creating: Right/Right/Right/Right/Right/
 extracting: Right/Right/Right/Right/Right/array

See one of the array file looks like base64:

cat array
# Q2hp

Therefore, I tried to cat all array files together by using the command:

find ./ -name array -exec cat {} \;
# Q2hpbmVzZSB0cmFkaXRpb25hbCBjdWx0dXJlIGlzIGJyb2FkIGFuZCBwcm9mb3VuZCEgU28gSSBXYW50IEdpdmUgWW91IE15IEZsYWcgQnV0IFlvdSBOZWVkIERlY29kZSBJdC5FbmpveSBUaGUgRmxhZyEhOuW4iCDlhZEg5aSNIOaNnyDlt70g6ZyHIOaZiyDlp6Qg5aSn6L+HIOiuvCDlmazll5Eg6ZyHIOaBkiDoioIg6LGrIA==

Yeah! Confirm is Base64 cause == at the end

Pipe it with base64 -d getting:

find ./ -name array -exec cat {} \;| base64 -d
# Chinese traditional culture is broad and profound! So I Want Give You My Flag But You Need Decode It.Enjoy The Flag!!:师 兑 复 损 巽 震 晋 姤 大过 讼 噬嗑 震 恒 节 豫

As a chinese I don’t even know what is it LOL XD

After received the hint, then I know how to decode it

Basically, you have to find which character represent the coresponding base64 symbol

For example: is Q, is 2 etc.

Solving

Solving this is abit troublesome for non-chinese people, because the text is simplified chinese, but the wikipedia page is tradisional chinese

Anyway, you can change to simplified chinese by click here:

image1

Then copy the whole row beside 六十四卦:

image2

Then follow the Base64 character index to decode the flag!!

I wrote the script in Python:

import string
gua = ['坤','剥','比','观','豫','晋','萃','否','谦','艮','蹇','渐','小过','旅','咸','遁','师','蒙','坎','涣','解','未济','困','讼','升','蛊','井','巽','恒','鼎','大过','姤','复','颐','屯','益','震','噬嗑','随','无妄','明夷','贲','既济','家人','丰','离','革','同人','临','损','节','中孚','归妹','睽','兑','履','泰','大畜','需','小畜','大壮','大有','夬','乾']

b64 = string.ascii_uppercase + string.ascii_lowercase + string.digits + "+/"

text = "师 兑 复 损 巽 震 晋 姤 大过 讼 噬嗑 震 恒 节 豫".split()
flag = ''
for t in text:
	flag += b64[gua.index(t)]
print(flag)
# Q2gxbkFfeXlkcyE

Pipe it into base64 -d will the flag!

python3 solve.py | base64 -d
# Ch1nA_yyds!base64: invalid input

Flag

SCTF{Ch1nA_yyds!}

Godness Dance

Challenge file

We are given an Linux executable file (ELF) and is stripped (no debug info):

dance.out: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1de3e5d06173644a6c8d0a65e085646c1ebc9a9f, for GNU/Linux 3.2.0, stripped

Try run it:

./dance.out
Input:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Count wrong!

Open it with Ghidra and decompile it:


undefined8 FUN_00101100(void)

{
  char cVar1;
  uint uVar2;
  long i;
  char *pointer;
  char *pointer2;
  char *buffer;
  long in_FS_OFFSET;
  undefined uStack2056;
  char buffer [28];
  char local_7eb [1979];
  long local_30;
  
  local_30 = *(long *)(in_FS_OFFSET + 0x28);
  buffer = buffer;
  __printf_chk(1,"Input:");
  pointer = buffer;
  do {
    pointer2 = pointer + 1;
    __isoc99_scanf(&DAT_0010201f,pointer);
    pointer = pointer2;
  } while (pointer2 != local_7eb);
  if (0 < DAT_00107ed4) {
    i= DAT_00107ed4 - 1;
    pointer = buffer;
    do {
      cVar1 = *pointer;
      pointer = pointer + 1;
      (&array)[cVar1 + -0x61] = (&array)[cVar1 + -0x61] + 1;
    } while (pointer != buffer + (ulong)i+ 1);
  }
  i = 0;
  do {
    if (*(int *)((long)&array + i) != *(int *)((long)&DATA + i)) {
      FUN_00101360();
      goto LAB_00101260;
    }
    i = i + 4;
  } while (i != 0x68);
  FUN_00101400(&uStack2056,0x1c,200);
  i = 4;
  do {
    if (*(int *)(&DAT_0010dd20 + i) != *(int *)(&DAT_00104020 + i)) goto LAB_00101260;
    i = i + 4;
  } while (i != 0x74);
  __printf_chk(1,"Good for you!\nflag:SCTF{");
  do {
    cVar1 = *buffer;
    buffer = buffer + 1;
    putchar((int)cVar1);
  } while (buffer != local_7eb);
  putchar(0x7d);
  if (local_30 == *(long *)(in_FS_OFFSET + 0x28)) {
    return 0;
  }
LAB_00101267:
                      /* WARNING: Subroutine does not return */
  __stack_chk_fail();
LAB_00101260:
  FUN_00101390();
  goto LAB_00101267;
}

The code looks quite messy, let me make it more readble:

undefined8 FUN_00101100(void)

{
  char cVar1;
  uint i;
  long i;
  char *pointer;
  char *pointer2;
  char *buffer;
  long in_FS_OFFSET;
  undefined uStack2056;
  char buffer [28];
  char local_7eb [1979];
  long local_30;
  
 
  __printf_chk(1,"Input:");
  pointer = buffer;
  do {
    pointer2 = pointer + 1;
    __isoc99_scanf("%c",pointer);
    pointer = pointer2;
  } while (pointer2 != local_7eb);
  
  pointer = buffer;
  
  do {
    cVar1 = *pointer;
    pointer++;
    array[cVar1 -0x61] = array[cVar1 -0x61] + 1;
  } while (pointer != buffer + 29);
  
  i = 0;
  do {
    if (array[i] != DATA[i]) {
      __printf_chk(1,"Count wrong!");
			exit(0);
    }
    i = i + 4;
  } while (i != 0x68);

  FUN_00101400(&uStack2056,0x1c,200);
  i = 4;
  do {
    if (DAT_0010dd20[i] != DATA2[i]){
    	  __printf_chk(1,"Wrong!");
    	  exit(0);
    }
    i = i + 4;
  } while (i != 0x74);
  __printf_chk(1,"Good for you!\nflag:SCTF{");
  do {
    cVar1 = *buffer;
    buffer = buffer + 1;
    putchar((int)cVar1);
  } while (buffer != local_7eb);
  putchar(0x7d);
}

First Check

First it take our input and count the number of character we input, and store it in array

And we know 0x61(97) is a in ASCII, means first index of array is a 2nd b third c and so on

Then it compare to DATA if not equal will print wrong

Goto Ghidra, double click DAT_00105f80 hightlight it and right click > Copy as byte string:

01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 01

Now we can base on this data and generate a valid input!!

Can easily guessed, it contains all alphabet letters and i and u occur twice

The valid input should be abcdefghiijklmnopqrstuuvwxyz:

./dance.out
Input:abcdefghiijklmnopqrstuuvwxyz
Wrong!

Yeah! Looks like we pass the first check!

Second Check

After the first check, it pass the input to FUN_0010140 function (Quite complicated function)

I decided to analyse dynamicly, since the function is hard to reverse

I use GDB to help me

Set a breakpoint after the function (base address 0x555555555000):

b * 0x5555555551d8

image

Then the proceed input is at rip + 0xcb3c

Then type x/32w $rip + 0xcb3c to view the address memory 32 words (32*4bytes)

pwndbg> x/32w $rip + 0xcb3c
0x555555561d14: 0x00000000      0x00000000      0x00000000      0x00000000
0x555555561d24: 0x00000001      0x00000002      0x00000003      0x00000004
0x555555561d34: 0x00000005      0x00000006      0x00000007      0x00000008
0x555555561d44: 0x00000009      0x0000000a      0x0000000b      0x0000000c
0x555555561d54: 0x0000000d      0x0000000e      0x0000000f      0x00000010
0x555555561d64: 0x00000011      0x00000012      0x00000013      0x00000014
0x555555561d74: 0x00000015      0x00000016      0x00000017      0x00000018
0x555555561d84: 0x00000019      0x0000001a      0x0000001b      0x0000001c

We can see it is a sequence number from 1 to 28

Lets try swap some characeters and observe how the data changes

I swap a and b :bacdefghiijklmnopqrstuuvwxyz

Run it and the data shows:

pwndbg> x/32w $rip + 0xcb3c
0x555555561d14: 0x00000000      0x00000000      0x00000000      0x00000000
0x555555561d24: 0x00000002      0x00000001      0x00000003      0x00000004
0x555555561d34: 0x00000005      0x00000006      0x00000007      0x00000008
0x555555561d44: 0x00000009      0x0000000a      0x0000000b      0x0000000c
0x555555561d54: 0x0000000d      0x0000000e      0x0000000f      0x00000010
0x555555561d64: 0x00000011      0x00000012      0x00000013      0x00000014
0x555555561d74: 0x00000015      0x00000016      0x00000017      0x00000018
0x555555561d84: 0x00000019      0x0000001a      0x0000001b      0x0000001c

As you can see, the 1st and 2nd element changes

At first I thought it is the ASCII value, just plus 97

But when I look carefully, it is actually the index of our original input abcdefghiijklmnopqrstuuvwxyz

Because it has 28 elements so should not be ASCII value, then it must be index

change input to zbcdefghiijklmnopqrstuuvwxya, as you can see the 1st element is 28 (a is 28th character in our input), and last element is 1 (z is 1st character in our input)

 x/32w $rip + 0xcb3c
0x555555561d14: 0x00000000      0x00000000      0x00000000      0x00000000
0x555555561d24: 0x0000001c      0x00000002      0x00000003      0x00000004
0x555555561d34: 0x00000005      0x00000006      0x00000007      0x00000008
0x555555561d44: 0x00000009      0x0000000a      0x0000000b      0x0000000c
0x555555561d54: 0x0000000d      0x0000000e      0x0000000f      0x00000010
0x555555561d64: 0x00000011      0x00000012      0x00000013      0x00000014
0x555555561d74: 0x00000015      0x00000016      0x00000017      0x00000018
0x555555561d84: 0x00000019      0x0000001a      0x0000001b      0x00000001

Therefore, we can take the DATA2 and generate our input!

Solving

Hightlight the DAT_00104024 in Ghirda and right click > Copy as Byte string:

020000001a000000110000001c000000180000000b000000150000000a000000100000001400000013000000120000000300000008000000060000000c000000090000000e0000000d00000016000000040000001b0000000f0000001700000001000000190000000700000005

Then delete all 00, write a python script to generate:

text = bytes.fromhex("021a111c180b150a101413120308060c090e0d16041b0f1701190705")
c = "abcdefghiijklmnopqrstuuvwxyz"
flag = bytearray(28)
for i in range(28):
	flag[text[i]-1] = ord(c[i])
print(flag)
# bytearray(b'waltznymphforquickjigsvexbud')

Yeah!! Try with the binary, and we got the flag!!:

./dance.out
Input:waltznymphforquickjigsvexbud
Good for you!
flag:SCTF{waltznymphforquickjigsvexbud}

Flag

SCTF{waltznymphforquickjigsvexbud}